/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2008-2011, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.data.complex.config;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import org.apache.commons.io.FilenameUtils;
import org.geotools.data.DataAccess;
import org.geotools.data.DataAccessFinder;
import org.geotools.data.DataSourceException;
import org.geotools.data.DataUtilities;
import org.geotools.data.FeatureSource;
import org.geotools.data.complex.AppSchemaDataAccessRegistry;
import org.geotools.data.complex.AttributeMapping;
import org.geotools.data.complex.FeatureTypeMapping;
import org.geotools.data.complex.FeatureTypeMappingFactory;
import org.geotools.data.complex.NestedAttributeMapping;
import org.geotools.data.complex.filter.XPath;
import org.geotools.data.complex.filter.XPath.Step;
import org.geotools.data.complex.filter.XPath.StepList;
import org.geotools.data.complex.xml.XmlFeatureSource;
import org.geotools.data.joining.JoiningNestedAttributeMapping;
import org.geotools.factory.Hints;
import org.geotools.feature.Types;
import org.geotools.filter.AttributeExpressionImpl;
import org.geotools.filter.FilterFactoryImplReportInvalidProperty;
import org.geotools.filter.expression.FeaturePropertyAccessorFactory;
import org.geotools.filter.text.cql2.CQL;
import org.geotools.filter.text.cql2.CQLException;
import org.geotools.xml.AppSchemaCache;
import org.geotools.xml.AppSchemaCatalog;
import org.geotools.xml.AppSchemaResolver;
import org.geotools.xml.SchemaIndex;
import org.opengis.feature.Feature;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.AttributeType;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.Name;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.expression.Expression;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.xml.sax.helpers.NamespaceSupport;
/**
* Utility class to create a set of {@linkPlain org.geotools.data.complex.FeatureTypeMapping}
* objects from a complex datastore's configuration object (
* {@link org.geotools.data.complex.config.AppSchemaDataAccessDTO}).
*
* @author Gabriel Roldan (Axios Engineering)
* @author Rini Angreani (CSIRO Earth Science and Resource Engineering)
* @author Russell Petty (GeoScience Victoria)
* @version $Id$
*
*
* @source $URL$
* http://svn.osgeo.org/geotools/trunk/modules/unsupported/app-schema/app-schema/src/main
* /java/org/geotools/data/complex/config/AppSchemaDataAccessConfigurator.java $
* @since 2.4
*/
public class AppSchemaDataAccessConfigurator {
/** DOCUMENT ME! */
private static final Logger LOGGER = org.geotools.util.logging.Logging
.getLogger(AppSchemaDataAccessConfigurator.class.getPackage().getName());
public static String PROPERTY_JOINING = "app-schema.joining";
/** DOCUMENT ME! */
private AppSchemaDataAccessDTO config;
private FeatureTypeRegistry typeRegistry;
private FilterFactory ff = new FilterFactoryImplReportInvalidProperty();
/**
* Placeholder for the prefix:namespaceURI mappings declared in the Namespaces section of the
* mapping file.
*/
private NamespaceSupport namespaces;
private Map schemaURIs;
/**
* Convenience method for "joining" property.
* @return
*/
public static boolean isJoining() {
String s=AppSchemaDataAccessRegistry.getAppSchemaProperties().getProperty(PROPERTY_JOINING);
return s!=null && s.equalsIgnoreCase("true");
}
/**
* Creates a new ComplexDataStoreConfigurator object.
*
* @param config
* DOCUMENT ME!
*/
private AppSchemaDataAccessConfigurator(AppSchemaDataAccessDTO config) {
this.config = config;
namespaces = new NamespaceSupport();
Map nsMap = config.getNamespaces();
for (Iterator it = nsMap.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Entry) it.next();
String prefix = (String) entry.getKey();
String namespace = (String) entry.getValue();
namespaces.declarePrefix(prefix, namespace);
}
}
/**
* Takes a config object and creates a set of mappings.
*
* <p>
* In the process will parse xml schemas to geotools' Feature Model types and descriptors,
* connect to source datastores and build the mapping objects from source FeatureTypes to the
* target ones.
* </p>
*
* @param config
* DOCUMENT ME!
*
* @return a Set of {@link org.geotools.data.complex.FeatureTypeMapping} source to target
* FeatureType mapping definitions
*
* @throws IOException
* if any error occurs while creating the mappings
*/
public static Set<FeatureTypeMapping> buildMappings(AppSchemaDataAccessDTO config) throws IOException {
AppSchemaDataAccessConfigurator mappingsBuilder;
mappingsBuilder = new AppSchemaDataAccessConfigurator(config);
Set<FeatureTypeMapping> mappingObjects = mappingsBuilder.buildMappings();
return mappingObjects;
}
/**
* Actually builds the mappings from the config dto.
*
* <p>
* Build steps are: - parse xml schemas to FM types - connect to source datastores - build
* mappings
* </p>
*
* @return
*
* @throws IOException
* DOCUMENT ME!
*/
private Set<FeatureTypeMapping> buildMappings() throws IOException {
// -parse target xml schemas, let parsed types on <code>registry</code>
try {
parseGmlSchemas();
Map<String, DataAccess<FeatureType, Feature>> sourceDataStores = null;
Set<FeatureTypeMapping> featureTypeMappings = null;
try {
// -create source datastores
sourceDataStores = acquireSourceDatastores();
// -create FeatureType mappings
featureTypeMappings = createFeatureTypeMappings(sourceDataStores);
return featureTypeMappings;
} finally {
disposeUnusedSourceDataStores(sourceDataStores, featureTypeMappings);
}
} finally {
if (typeRegistry != null) {
typeRegistry.disposeSchemaIndexes();
}
}
}
/**
* Ensure any source data stores not used in a mapping are disposed.
*
* @param sourceDataStores
* @param featureTypeMappings
*/
private void disposeUnusedSourceDataStores(
Map<String, DataAccess<FeatureType, Feature>> sourceDataStores,
Set<FeatureTypeMapping> featureTypeMappings) {
if (sourceDataStores == null) {
return;
} else if (featureTypeMappings == null) {
for (DataAccess<FeatureType, Feature> dataAccess : sourceDataStores.values()) {
dataAccess.dispose();
}
} else {
for (DataAccess<FeatureType, Feature> dataAccess : sourceDataStores.values()) {
boolean usedDataAccess = false;
for (FeatureTypeMapping mapping : featureTypeMappings) {
if (mapping.getSource().getDataStore() == dataAccess) {
usedDataAccess = true;
break;
}
}
if (!usedDataAccess) {
dataAccess.dispose();
}
}
}
}
private Set<FeatureTypeMapping> createFeatureTypeMappings(
Map<String, DataAccess<FeatureType, Feature>> sourceDataStores) throws IOException {
Set mappingsConfigs = config.getTypeMappings();
Set<FeatureTypeMapping> featureTypeMappings = new HashSet<FeatureTypeMapping>();
for (Iterator it = mappingsConfigs.iterator(); it.hasNext();) {
TypeMapping dto = (TypeMapping) it.next();
try {
FeatureSource featureSource = getFeatureSource(dto, sourceDataStores);
// get CRS from underlying feature source and pass it on
CoordinateReferenceSystem crs;
try {
crs = featureSource.getSchema().getCoordinateReferenceSystem();
} catch (UnsupportedOperationException e) {
// web service back end doesn't support getSchema
crs = null;
}
AttributeDescriptor target = getTargetDescriptor(dto, crs);
// set original schema locations for encoding
target.getType().getUserData().put("schemaURI", schemaURIs);
List attMappings = getAttributeMappings(target, dto.getAttributeMappings(), dto
.getItemXpath());
FeatureTypeMapping mapping;
mapping = FeatureTypeMappingFactory.getInstance(featureSource, target, attMappings,
namespaces, dto.getItemXpath(), dto.isXmlDataStore());
String mappingName = dto.getMappingName();
if (mappingName != null) {
mapping.setName(Types.degloseName(mappingName, namespaces));
}
featureTypeMappings.add(mapping);
} catch (Exception e) {
LOGGER
.warning("Error creating app-schema data store for '"
+ dto.getMappingName() != null ? dto.getMappingName() : dto
.getTargetElementName()
+ "', caused by: " + e.getMessage());
throw new IOException(e.getMessage());
}
}
return featureTypeMappings;
}
private AttributeDescriptor getTargetDescriptor(TypeMapping dto, CoordinateReferenceSystem crs)
throws IOException {
String prefixedTargetName = dto.getTargetElementName();
Name targetNodeName = Types.degloseName(prefixedTargetName, namespaces);
AttributeDescriptor targetDescriptor = typeRegistry.getDescriptor(targetNodeName, crs, dto
.getAttributeMappings());
if (targetDescriptor == null) {
throw new NoSuchElementException("descriptor " + targetNodeName
+ " not found in parsed schema");
}
return targetDescriptor;
}
/**
* Creates a list of {@link org.geotools.data.complex.AttributeMapping} from the attribute
* mapping configurations in the provided list of {@link AttributeMapping}
*
* @param root
* @param attDtos
*
* @return
*/
private List getAttributeMappings(final AttributeDescriptor root, final List attDtos,
String itemXpath) throws IOException {
List attMappings = new LinkedList();
for (Iterator it = attDtos.iterator(); it.hasNext();) {
org.geotools.data.complex.config.AttributeMapping attDto;
attDto = (org.geotools.data.complex.config.AttributeMapping) it.next();
String idExpr = attDto.getIdentifierExpression();
String idXpath = null;
if (idExpr == null) {
// this might be because it's an XPath expression
idXpath = attDto.getIdentifierPath();
if (idXpath != null) {
//validate without indexed elements
final StepList inputXPathSteps = XPath.steps(root, itemXpath + "/"+ idXpath, namespaces);
validateConfiguredNamespaces(inputXPathSteps);
}
}
String sourceExpr = attDto.getSourceExpression();
String inputXPath = null;
if (sourceExpr == null) {
// this might be because it's an XPath expression
inputXPath = attDto.getInputAttributePath();
if (inputXPath != null) {
final StepList inputXPathSteps = XPath.steps(root, itemXpath + "/" + inputXPath, namespaces);
validateConfiguredNamespaces(inputXPathSteps);
}
}
String expectedInstanceTypeName = attDto.getTargetAttributeSchemaElement();
final String targetXPath = attDto.getTargetAttributePath();
final StepList targetXPathSteps = XPath.steps(root, targetXPath, namespaces);
validateConfiguredNamespaces(targetXPathSteps);
final boolean isMultiValued = attDto.isMultiple();
final Expression idExpression = (idXpath == null) ? parseOgcCqlExpression(idExpr)
: new AttributeExpressionImpl(idXpath, new Hints(
FeaturePropertyAccessorFactory.NAMESPACE_CONTEXT, this.namespaces));
// if the data source is a data access, the input XPath expression is the source
// expression
final Expression sourceExpression;
sourceExpression = (inputXPath == null) ? parseOgcCqlExpression(sourceExpr)
: new AttributeExpressionImpl(inputXPath, new Hints(
FeaturePropertyAccessorFactory.NAMESPACE_CONTEXT, this.namespaces));
final AttributeType expectedInstanceOf;
final Map clientProperties = getClientProperties(attDto);
if (expectedInstanceTypeName != null) {
Name expectedNodeTypeName = Types.degloseName(expectedInstanceTypeName, namespaces);
expectedInstanceOf = typeRegistry
.getAttributeType(expectedNodeTypeName, null, null);
if (expectedInstanceOf == null) {
String msg = "mapping expects and instance of " + expectedNodeTypeName
+ " for attribute " + targetXPath
+ " but the attribute descriptor was not found";
throw new DataSourceException(msg);
}
} else {
expectedInstanceOf = null;
}
AttributeMapping attMapping;
String sourceElement = attDto.getLinkElement();
if (sourceElement != null) {
// nested complex attributes, this could be a function expression for polymorphic
// types
Expression elementExpr = parseOgcCqlExpression(sourceElement);
String sourceField = attDto.getLinkField();
StepList sourceFieldSteps = null;
if (sourceField != null) {
// it could be null for polymorphism mapping,
// i.e. when the linked element maps to the same table as the container mapping
sourceFieldSteps = XPath.steps(root, sourceField, namespaces);
}
// a nested feature
if (isJoining()) {
attMapping = new JoiningNestedAttributeMapping(idExpression, sourceExpression,
targetXPathSteps, isMultiValued, clientProperties, elementExpr,
sourceFieldSteps, namespaces);
} else {
attMapping = new NestedAttributeMapping(idExpression, sourceExpression,
targetXPathSteps, isMultiValued, clientProperties, elementExpr,
sourceFieldSteps, namespaces);
}
} else {
attMapping = new AttributeMapping(idExpression, sourceExpression, targetXPathSteps,
expectedInstanceOf, isMultiValued, clientProperties);
}
/**
* Label and parent label are specific for web service backend
*/
if (attDto.getLabel() != null) {
attMapping.setLabel(attDto.getLabel());
}
if (attDto.getParentLabel() != null) {
attMapping.setParentLabel(attDto.getParentLabel());
}
if (attDto.getInstancePath() != null) {
attMapping.setInstanceXpath(attDto.getInstancePath());
}
attMappings.add(attMapping);
}
return attMappings;
}
/**
* Throws an IllegalArgumentException if some Step in the given xpath StepList has a prefix for
* which no prefix to namespace mapping were provided (as in the Namespaces section of the
* mappings xml configuration file)
*
* @param targetXPathSteps
*/
private void validateConfiguredNamespaces(StepList targetXPathSteps) {
for (Iterator it = targetXPathSteps.iterator(); it.hasNext();) {
Step step = (Step) it.next();
QName name = step.getName();
if (!XMLConstants.DEFAULT_NS_PREFIX.equals(name.getPrefix())) {
if (XMLConstants.DEFAULT_NS_PREFIX.equals(name.getNamespaceURI())) {
throw new IllegalArgumentException("location step " + step + " has prefix "
+ name.getPrefix() + " for which no namespace was set. "
+ "(Check the Namespaces section in the config file)");
}
}
}
}
private Expression parseOgcCqlExpression(String sourceExpr) throws DataSourceException {
Expression expression = Expression.NIL;
if (sourceExpr != null && sourceExpr.trim().length() > 0) {
try {
expression = CQL.toExpression(sourceExpr, ff);
} catch (CQLException e) {
String formattedErrorMessage = e.getMessage();
AppSchemaDataAccessConfigurator.LOGGER.log(Level.SEVERE, formattedErrorMessage, e);
throw new DataSourceException("Error parsing CQL expression " + sourceExpr + ":\n"
+ formattedErrorMessage);
} catch (Exception e) {
e.printStackTrace();
String msg = "parsing expression " + sourceExpr;
AppSchemaDataAccessConfigurator.LOGGER.log(Level.SEVERE, msg, e);
throw new DataSourceException(msg + ": " + e.getMessage(), e);
}
}
return expression;
}
/**
*
* @param dto
* @return Map<Name, Expression> with the values per qualified name (attribute name in the
* mapping)
* @throws DataSourceException
*/
private Map getClientProperties(org.geotools.data.complex.config.AttributeMapping dto)
throws DataSourceException {
if (dto.getClientProperties().size() == 0) {
return Collections.EMPTY_MAP;
}
Map clientProperties = new HashMap();
for (Iterator it = dto.getClientProperties().entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
String name = (String) entry.getKey();
Name qName = Types.degloseName(name, namespaces);
String cqlExpression = (String) entry.getValue();
final Expression expression = parseOgcCqlExpression(cqlExpression);
clientProperties.put(qName, expression);
}
return clientProperties;
}
private FeatureSource<FeatureType, Feature> getFeatureSource(TypeMapping dto,
Map<String, DataAccess<FeatureType, Feature>> sourceDataStores) throws IOException {
String dsId = dto.getSourceDataStore();
String typeName = dto.getSourceTypeName();
DataAccess<FeatureType, Feature> sourceDataStore = sourceDataStores.get(dsId);
if (sourceDataStore == null) {
throw new DataSourceException("datastore " + dsId + " not found for type mapping "
+ dto);
}
AppSchemaDataAccessConfigurator.LOGGER.fine("asking datastore " + sourceDataStore
+ " for source type " + typeName);
Name name = Types.degloseName(typeName, namespaces);
FeatureSource fSource = sourceDataStore.getFeatureSource(name);
if (fSource instanceof XmlFeatureSource) {
((XmlFeatureSource) fSource).setNamespaces(namespaces);
}
AppSchemaDataAccessConfigurator.LOGGER.fine("found feature source for " + typeName);
return fSource;
}
/**
* Parses the target xml schema files and stores the generated types in {@link #typeRegistry}
* and AttributeDescriptors in {@link #descriptorRegistry}.
*
* <p>
* The list of file names to parse is obtained from config.getTargetSchemasUris(). If a file
* name contained in that list is a relative path (i.e., does not starts with file: or http:,
* config.getBaseSchemasUrl() is used to resolve relative paths against.
* </p>
*
* @throws IOException
*/
private void parseGmlSchemas() throws IOException {
AppSchemaDataAccessConfigurator.LOGGER.finer("about to parse target schemas");
final URL baseUrl = new URL(config.getBaseSchemasUrl());
final List schemaFiles = config.getTargetSchemasUris();
EmfAppSchemaReader schemaParser;
schemaParser = EmfAppSchemaReader.newInstance();
schemaParser.setResolver(buildResolver());
// create a single type registry for all the schemas in the config
typeRegistry = new FeatureTypeRegistry(namespaces);
schemaURIs = new HashMap<String, String>(schemaFiles.size());
String nameSpace;
String schemaLocation;
for (Iterator it = schemaFiles.iterator(); it.hasNext();) {
schemaLocation = (String) it.next();
final URL schemaUrl = resolveResourceLocation(baseUrl, schemaLocation);
AppSchemaDataAccessConfigurator.LOGGER.fine("parsing schema "
+ schemaUrl.toExternalForm());
nameSpace = schemaParser.findSchemaNamespace(schemaUrl);
schemaLocation = schemaUrl.toExternalForm();
schemaURIs.put(nameSpace, schemaLocation);
SchemaIndex schemaIndex = schemaParser.parse(nameSpace, schemaLocation);
// add the resolved EMF schema so typeRegistry can find the needed type tree when it's
// asked for the mapped FeatureType
typeRegistry.addSchemas(schemaIndex);
}
}
/**
* Build the catalog for this data access.
*/
private AppSchemaCatalog buildCatalog() {
String catalogLocation = config.getCatalog();
if (catalogLocation == null) {
return null;
} else {
URL baseUrl;
try {
baseUrl = new URL(config.getBaseSchemasUrl());
URL resolvedCatalogLocation = resolveResourceLocation(baseUrl, catalogLocation);
return AppSchemaCatalog.build(resolvedCatalogLocation);
} catch (MalformedURLException e) {
LOGGER.warning("Malformed URL encountered while setting OASIS catalog location. "
+ "Mapping file URL: " + config.getBaseSchemasUrl() + " Catalog location: "
+ config.getCatalog() + " Detail: " + e.getMessage());
return null;
}
}
}
/**
* Build the cache for this data access.
*/
private AppSchemaCache buildCache() {
try {
return AppSchemaCache.buildFromGeoserverUrl(new URL(config.getBaseSchemasUrl()));
} catch (MalformedURLException e) {
LOGGER.warning("Malformed mapping file URL: " + config.getBaseSchemasUrl() + " Detail: "
+ e.getMessage());
return null;
}
}
/**
* Build the resolver (catalog plus cache) for this data access.
*/
private AppSchemaResolver buildResolver() {
return new AppSchemaResolver(buildCatalog(), buildCache());
}
private URL resolveResourceLocation(final URL baseUrl, String schemaLocation)
throws MalformedURLException {
final URL schemaUrl;
if (schemaLocation.startsWith("file:") || schemaLocation.startsWith("http:")) {
AppSchemaDataAccessConfigurator.LOGGER
.fine("using resource location as absolute path: " + schemaLocation);
schemaUrl = new URL(schemaLocation);
} else {
if (baseUrl == null) {
schemaUrl = new URL(schemaLocation);
AppSchemaDataAccessConfigurator.LOGGER
.warning("base url not provided, may be unable to locate" + schemaLocation
+ ". Path resolved to: " + schemaUrl.toExternalForm());
} else {
AppSchemaDataAccessConfigurator.LOGGER.fine("using schema location "
+ schemaLocation + " as relative to " + baseUrl);
schemaUrl = new URL(baseUrl, schemaLocation);
}
}
return schemaUrl;
}
/**
* DOCUMENT ME!
*
* @return a Map<String,DataStore> where the key is the id given to the datastore in the
* configuration.
*
* @throws IOException
* @throws DataSourceException
* DOCUMENT ME!
*/
private Map<String, DataAccess<FeatureType, Feature>> acquireSourceDatastores() throws IOException {
AppSchemaDataAccessConfigurator.LOGGER.entering(getClass().getName(),
"acquireSourceDatastores");
final Map<String, DataAccess<FeatureType, Feature>> datastores = new LinkedHashMap<String, DataAccess<FeatureType, Feature>>();
@SuppressWarnings("unchecked")
final List<SourceDataStore> dsParams = config.getSourceDataStores();
String id;
for (SourceDataStore dsconfig : dsParams) {
id = dsconfig.getId();
@SuppressWarnings("unchecked")
Map<String, Serializable> datastoreParams = dsconfig.getParams();
datastoreParams = resolveRelativePaths(datastoreParams);
AppSchemaDataAccessConfigurator.LOGGER.fine("looking for datastore " + id);
DataAccess<FeatureType, Feature> dataStore = DataAccessFinder.getDataStore(datastoreParams);
if (dataStore == null) {
AppSchemaDataAccessConfigurator.LOGGER.log(Level.SEVERE,
"Cannot find a DataAccess for parameters " + datastoreParams);
throw new DataSourceException("Cannot find a DataAccess for parameters "
+ "(some not shown) "
+ filterDatastoreParams(datastoreParams));
}
AppSchemaDataAccessConfigurator.LOGGER.fine("got datastore " + dataStore);
datastores.put(id, dataStore);
}
return datastores;
}
/**
* Database connection parameters that are probably safe to report to the end user.
* (Things we can be pretty sure are not passwords.)
*/
@SuppressWarnings("serial")
private static final List<String> SAFE_DATASTORE_PARAMS = Collections
.unmodifiableList(new ArrayList<String>() {
{
add("url"); // shapefile
add("directory"); // propertyfile
add("namespace"); // just about everything
add("dbtype"); // jdbc
add("jndiReferenceName"); // jdni
// these are all various jdbc options
add("host");
add("port");
add("database");
add("schema");
add("user");
}
});
/**
* Return datastore params filtered to include only known-safe parameters.
* We cannot try to find passwords, because even dbtype could be misspelled.
*
* @param datastoreParams
* @return
*/
@SuppressWarnings("unchecked")
private Map filterDatastoreParams(Map datastoreParams) {
Map filteredDatastoreParams = new LinkedHashMap();
for (String key : SAFE_DATASTORE_PARAMS) {
if (datastoreParams.containsKey(key)) {
filteredDatastoreParams.put(key, datastoreParams.get(key));
}
}
return filteredDatastoreParams;
}
/**
* Resolves any source datastore parameter settled as a file path relative to the location of
* the xml mappings configuration file as an absolute path and returns a new Map with it.
*
* @param datastoreParams
* @return parameter map with resolved file url
*/
private Map<String, Serializable> resolveRelativePaths(
final Map<String, Serializable> datastoreParams) {
Map<String, Serializable> resolvedParams = new HashMap<String, Serializable>();
AppSchemaDataAccessConfigurator.LOGGER.entering(getClass().getName(),
"resolveRelativePaths");
for (Map.Entry<String, Serializable> entry : (Set<Map.Entry<String, Serializable>>) datastoreParams
.entrySet()) {
String key = (String) entry.getKey();
String value = (String) entry.getValue();
if (value != null && value.startsWith("file:")) {
// a parameter prefix of "file:" is the only case that will be resolved
// any file paths entered without this prefix will remain unchanged
String oldValue = value;
String resolvedDataPath = null;
String inputDataPath = (String) value.substring("file:".length());
File f = new File(inputDataPath);
if (!f.isAbsolute()) {
AppSchemaDataAccessConfigurator.LOGGER.fine("resolving original parameter "
+ value + " for datastore parameter " + key);
try {
// use of URL here should be safe as the base schema url should
// not yet have undergone any conversion to file or
// encoding/decoding
URL mappingFileUrl = new URL(config.getBaseSchemasUrl());
AppSchemaDataAccessConfigurator.LOGGER.finer("mapping file URL is "
+ mappingFileUrl.toString());
String mappingFileDirPath = DataUtilities.urlToFile(mappingFileUrl)
.getParent();
AppSchemaDataAccessConfigurator.LOGGER
.finer("mapping file parent directory is " + mappingFileDirPath);
// FilenameUtils.concat handles a number of system-dependent issues here
// but it might be better to add this method to DataUtilities
resolvedDataPath = FilenameUtils.concat(mappingFileDirPath, inputDataPath);
if (resolvedDataPath == null) {
throw new RuntimeException(
"Relative path to datastore is incompatible with"
+ " the base path - check double dot steps.");
}
} catch (Exception e) {
AppSchemaDataAccessConfigurator.LOGGER.throwing(getClass().getName(),
"resolveRelativePaths", e);
throw new RuntimeException(e);
}
} else {
resolvedDataPath = inputDataPath;
}
AppSchemaDataAccessConfigurator.LOGGER.finer("Path to data has been resolved to "
+ resolvedDataPath);
/*
* Shapefile expects the protocol "file:" at the beginning of the parameter value,
* other file-based datastores do not. We can distinguish shapefiles from other
* cases because the key is "url" and as of 2010-09-25 no other file-based datastore
* uses this key (properties files use the key "directory"). If a new file-based
* datastore is created, everything will work fine provided the key "url" is used if
* and only if the datastore expects a parameter value starting with "file:"
*/
if ("url".equals(key) || key.startsWith("WSDataStoreFactory")) {
value = "file:" + resolvedDataPath;
} else {
value = resolvedDataPath;
}
AppSchemaDataAccessConfigurator.LOGGER
.fine("Resolved " + oldValue + " -> " + value);
}
resolvedParams.put(key, value);
}
return resolvedParams;
}
}