001: /*
002: * Geotools2 - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: */
017: package org.geotools.data.complex.config;
018:
019: import java.io.File;
020: import java.io.FileNotFoundException;
021: import java.io.IOException;
022: import java.io.InputStream;
023: import java.net.MalformedURLException;
024: import java.net.URL;
025: import java.util.Collections;
026: import java.util.HashMap;
027: import java.util.HashSet;
028: import java.util.Iterator;
029: import java.util.LinkedList;
030: import java.util.List;
031: import java.util.Map;
032: import java.util.NoSuchElementException;
033: import java.util.Set;
034: import java.util.Map.Entry;
035: import java.util.logging.Level;
036: import java.util.logging.Logger;
037:
038: import javax.xml.XMLConstants;
039: import javax.xml.namespace.QName;
040:
041: import org.apache.xml.resolver.Catalog;
042: import org.apache.xml.resolver.tools.ResolvingXMLReader;
043: import org.geotools.data.DataAccess;
044: import org.geotools.data.DataAccessFinder;
045: import org.geotools.data.DataSourceException;
046: import org.geotools.data.complex.AttributeMapping;
047: import org.geotools.data.complex.FeatureTypeMapping;
048: import org.geotools.data.complex.filter.XPath;
049: import org.geotools.data.complex.filter.XPath.Step;
050: import org.geotools.data.complex.filter.XPath.StepList;
051: import org.geotools.data.feature.FeatureAccess;
052: import org.geotools.data.feature.FeatureSource2;
053: import org.geotools.feature.iso.Types;
054: import org.geotools.filter.text.cql2.CQL;
055: import org.geotools.filter.text.cql2.ParseException;
056: import org.opengis.feature.type.AttributeDescriptor;
057: import org.opengis.feature.type.AttributeType;
058: import org.opengis.feature.type.Name;
059: import org.opengis.filter.expression.Expression;
060: import org.xml.sax.helpers.NamespaceSupport;
061:
062: /**
063: * Utility class to create a set of {@linkPlain
064: * org.geotools.data.complex.FeatureTypeMapping} objects from a complex
065: * datastore's configuration object ({@link
066: * org.geotools.data.complex.config.ComplexDataStoreDTO}).
067: *
068: * @author Gabriel Roldan, Axios Engineering
069: * @version $Id: ComplexDataStoreConfigurator.java 29005 2008-01-31 00:11:43Z groldan $
070: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/community-schemas/community-schema-ds/src/main/java/org/geotools/data/complex/config/ComplexDataStoreConfigurator.java $
071: * @since 2.4
072: */
073: public class ComplexDataStoreConfigurator {
074: /** DOCUMENT ME! */
075: private static final Logger LOGGER = org.geotools.util.logging.Logging
076: .getLogger(ComplexDataStoreConfigurator.class.getPackage()
077: .getName());
078:
079: /** DOCUMENT ME! */
080: private ComplexDataStoreDTO config;
081:
082: private Map typeRegistry;
083: private Map descriptorRegistry;
084:
085: private Map sourceDataStores;
086:
087: /**
088: * Placeholder for the prefix:namespaceURI mappings declared in the
089: * Namespaces section of the mapping file.
090: */
091: private NamespaceSupport namespaces;
092:
093: /**
094: * Creates a new ComplexDataStoreConfigurator object.
095: *
096: * @param config
097: * DOCUMENT ME!
098: */
099: private ComplexDataStoreConfigurator(ComplexDataStoreDTO config) {
100: this .config = config;
101: namespaces = new NamespaceSupport();
102: Map nsMap = config.getNamespaces();
103: for (Iterator it = nsMap.entrySet().iterator(); it.hasNext();) {
104: Map.Entry entry = (Entry) it.next();
105: String prefix = (String) entry.getKey();
106: String namespace = (String) entry.getValue();
107: namespaces.declarePrefix(prefix, namespace);
108: }
109: }
110:
111: /**
112: * Takes a config object and creates a set of mappings.
113: *
114: * <p>
115: * In the process will parse xml schemas to geotools' Feature Model types
116: * and descriptors, connect to source datastores and build the mapping
117: * objects from source FeatureTypes to the target ones.
118: * </p>
119: *
120: * @param config
121: * DOCUMENT ME!
122: *
123: * @return a Set of {@link org.geotools.data.complex.FeatureTypeMapping}
124: * source to target FeatureType mapping definitions
125: *
126: * @throws IOException
127: * if any error occurs while creating the mappings
128: */
129: public static Set buildMappings(ComplexDataStoreDTO config)
130: throws IOException {
131: ComplexDataStoreConfigurator mappingsBuilder;
132:
133: mappingsBuilder = new ComplexDataStoreConfigurator(config);
134: Set mappingObjects = mappingsBuilder.buildMappings();
135:
136: return mappingObjects;
137: }
138:
139: /**
140: * Actually builds the mappings from the config dto.
141: *
142: * <p>
143: * Build steps are: - parse xml schemas to FM types - connect to source
144: * datastores - build mappings
145: * </p>
146: *
147: * @return
148: *
149: * @throws IOException
150: * DOCUMENT ME!
151: */
152: private Set buildMappings() throws IOException {
153: // -parse target xml schemas, let parsed types on <code>registry</code>
154: parseGmlSchemas();
155:
156: // -create source datastores
157: sourceDataStores = aquireSourceDatastores();
158:
159: // -create FeatureType mappings
160: Set featureTypeMappings = createFeatureTypeMappings();
161:
162: return featureTypeMappings;
163: }
164:
165: private Set createFeatureTypeMappings() throws IOException {
166: Set mappingsConfigs = config.getTypeMappings();
167:
168: Set featureTypeMappings = new HashSet();
169:
170: for (Iterator it = mappingsConfigs.iterator(); it.hasNext();) {
171: TypeMapping dto = (TypeMapping) it.next();
172:
173: FeatureSource2 featureSoruce = getFeatureSource(dto);
174: AttributeDescriptor target = getTargetDescriptor(dto);
175: List attMappings = getAttributeMappings(target, dto
176: .getAttributeMappings());
177: List groupByAtts = dto.getGroupbyAttributeNames();
178:
179: FeatureTypeMapping mapping;
180:
181: mapping = new FeatureTypeMapping(featureSoruce, target,
182: attMappings, namespaces);
183:
184: if (groupByAtts.size() > 0) {
185: mapping.setGroupByAttNames(groupByAtts);
186: }
187:
188: featureTypeMappings.add(mapping);
189: }
190: return featureTypeMappings;
191: }
192:
193: private AttributeDescriptor getTargetDescriptor(TypeMapping dto)
194: throws IOException {
195: if (descriptorRegistry == null) {
196: throw new IllegalStateException("schemas not yet parsed");
197: }
198:
199: String prefixedTargetName = dto.getTargetElementName();
200: Name targetNodeName = degloseName(prefixedTargetName);
201:
202: AttributeDescriptor targetDescriptor;
203: targetDescriptor = (AttributeDescriptor) descriptorRegistry
204: .get(targetNodeName);
205: if (targetDescriptor == null) {
206: throw new NoSuchElementException("descriptor "
207: + targetNodeName + " not found in parsed schema");
208: }
209: return targetDescriptor;
210: }
211:
212: /**
213: * Creates a list of {@link org.geotools.data.complex.AttributeMapping} from
214: * the attribute mapping configurations in the provided list of
215: * {@link AttributeMapping}
216: *
217: * @param attDtos
218: * @return
219: */
220: private List getAttributeMappings(final AttributeDescriptor root,
221: final List attDtos) throws IOException {
222: List attMappings = new LinkedList();
223:
224: for (Iterator it = attDtos.iterator(); it.hasNext();) {
225:
226: org.geotools.data.complex.config.AttributeMapping attDto;
227: attDto = (org.geotools.data.complex.config.AttributeMapping) it
228: .next();
229:
230: String idExpr = attDto.getIdentifierExpression();
231: String sourceExpr = attDto.getSourceExpression();
232: String expectedInstanceTypeName = attDto
233: .getTargetAttributeSchemaElement();
234:
235: final String targetXPath = attDto.getTargetAttributePath();
236: final StepList targetXPathSteps = XPath.steps(root,
237: targetXPath, namespaces);
238: validateConfiguredNamespaces(targetXPathSteps);
239:
240: final boolean isMultiValued = attDto.isMultiple();
241:
242: final Expression idExpression = parseOgcCqlExpression(idExpr);
243: final Expression sourceExpression = parseOgcCqlExpression(sourceExpr);
244:
245: final AttributeType expectedInstanceOf;
246:
247: final Map clientProperties = getClientProperties(attDto);
248:
249: if (expectedInstanceTypeName != null) {
250: Name expectedNodeTypeName = null;
251: expectedNodeTypeName = degloseTypeName(expectedInstanceTypeName);
252: expectedInstanceOf = (AttributeType) typeRegistry
253: .get(expectedNodeTypeName);
254: if (expectedInstanceOf == null) {
255: String msg = "mapping expects and instance of "
256: + expectedNodeTypeName
257: + " for attribute "
258: + targetXPath
259: + " but the attribute descriptor was not found";
260: throw new DataSourceException(msg);
261: }
262: } else {
263: expectedInstanceOf = null;
264: }
265:
266: AttributeMapping attMapping = new AttributeMapping(
267: idExpression, sourceExpression, targetXPathSteps,
268: expectedInstanceOf, isMultiValued, clientProperties);
269: attMappings.add(attMapping);
270: }
271: return attMappings;
272: }
273:
274: /**
275: * Throws an IllegalArgumentException if some Step in the given xpath
276: * StepList has a prefix for which no prefix to namespace mapping were
277: * provided (as in the Namespaces section of the mappings xml configuration
278: * file)
279: *
280: * @param targetXPathSteps
281: */
282: private void validateConfiguredNamespaces(StepList targetXPathSteps) {
283: for (Iterator it = targetXPathSteps.iterator(); it.hasNext();) {
284: Step step = (Step) it.next();
285: QName name = step.getName();
286: if (!XMLConstants.DEFAULT_NS_PREFIX
287: .equals(name.getPrefix())) {
288: if (XMLConstants.DEFAULT_NS_PREFIX.equals(name
289: .getNamespaceURI())) {
290: throw new IllegalArgumentException(
291: "location step "
292: + step
293: + " has prefix "
294: + name.getPrefix()
295: + " for which no namespace was set. "
296: + "(Check the Namespaces section in the config file)");
297: }
298: }
299: }
300: }
301:
302: private Expression parseOgcCqlExpression(String sourceExpr)
303: throws DataSourceException {
304: Expression expression = Expression.NIL;
305: if (sourceExpr != null && sourceExpr.trim().length() > 0) {
306: try {
307: expression = CQL.toExpression(sourceExpr);
308: } catch (ParseException e) {
309: String formattedErrorMessage = e.getMessage();
310: ComplexDataStoreConfigurator.LOGGER.log(Level.SEVERE,
311: formattedErrorMessage, e);
312: throw new DataSourceException(
313: "Error parsing expression " + sourceExpr
314: + ":\n" + formattedErrorMessage);
315: } catch (Exception e) {
316: e.printStackTrace();
317: String msg = "parsing expression " + sourceExpr;
318: ComplexDataStoreConfigurator.LOGGER.log(Level.SEVERE,
319: msg, e);
320: throw new DataSourceException(msg + ": "
321: + e.getMessage(), e);
322: }
323: }
324: return expression;
325: }
326:
327: /**
328: *
329: * @param dto
330: * @return Map<Name, Expression> with the values per qualified name
331: * (attribute name in the mapping)
332: * @throws DataSourceException
333: */
334: private Map getClientProperties(
335: org.geotools.data.complex.config.AttributeMapping dto)
336: throws DataSourceException {
337:
338: if (dto.getClientProperties().size() == 0) {
339: return Collections.EMPTY_MAP;
340: }
341:
342: Map clientProperties = new HashMap();
343: for (Iterator it = dto.getClientProperties().entrySet()
344: .iterator(); it.hasNext();) {
345: Map.Entry entry = (Map.Entry) it.next();
346: String name = (String) entry.getKey();
347: Name qName = degloseName(name);
348: String cqlExpression = (String) entry.getValue();
349: Expression expression = parseOgcCqlExpression(cqlExpression);
350: clientProperties.put(qName, expression);
351: }
352: return clientProperties;
353: }
354:
355: private FeatureSource2 getFeatureSource(TypeMapping dto)
356: throws IOException {
357: String dsId = dto.getSourceDataStore();
358: String typeName = dto.getSourceTypeName();
359:
360: DataAccess sourceDataStore = (DataAccess) sourceDataStores
361: .get(dsId);
362: if (sourceDataStore == null) {
363: throw new DataSourceException("datastore " + dsId
364: + " not found for type mapping " + dto);
365: }
366:
367: ComplexDataStoreConfigurator.LOGGER.fine("asking datastore "
368: + sourceDataStore + " for source type " + typeName);
369: Name name = degloseName(typeName);
370: FeatureSource2 fSource = (FeatureSource2) sourceDataStore
371: .access(name);
372: ComplexDataStoreConfigurator.LOGGER
373: .fine("found feature source for " + typeName);
374: return fSource;
375: }
376:
377: /**
378: * Parses the target xml schema files and stores the generated types in
379: * {@link #typeRegistry} and AttributeDescriptors in
380: * {@link #descriptorRegistry}.
381: *
382: * <p>
383: * The list of file names to parse is obtained from
384: * config.getTargetSchemasUris(). If a file name contained in that list is a
385: * relative path (i.e., does not starts with file: or http:,
386: * config.getBaseSchemasUrl() is used to resolve relative paths against.
387: * </p>
388: *
389: * @throws IOException
390: */
391: private void parseGmlSchemas() throws IOException {
392: ComplexDataStoreConfigurator.LOGGER
393: .finer("about to parse target schemas");
394:
395: final URL baseUrl = new URL(config.getBaseSchemasUrl());
396:
397: final List schemaFiles = config.getTargetSchemasUris();
398:
399: final Catalog oasisCatalog = getCatalog();
400: EmfAppSchemaReader schemaParser;
401: schemaParser = EmfAppSchemaReader.newInstance();
402: schemaParser.setCatalog(oasisCatalog);
403:
404: for (Iterator it = schemaFiles.iterator(); it.hasNext();) {
405: String schemaLocation = (String) it.next();
406: final URL schemaUrl = resolveResourceLocation(baseUrl,
407: schemaLocation);
408: ComplexDataStoreConfigurator.LOGGER.fine("parsing schema "
409: + schemaUrl.toExternalForm());
410:
411: schemaParser.parse(schemaUrl);
412: }
413:
414: typeRegistry = schemaParser.getTypeRegistry();
415: descriptorRegistry = schemaParser.getDescriptorRegistry();
416: }
417:
418: private Catalog getCatalog() throws MalformedURLException,
419: IOException {
420: Catalog oasisCatalog = null;
421: String catalogLocation = config.getCatalog();
422: if (catalogLocation != null) {
423: final URL baseUrl = new URL(config.getBaseSchemasUrl());
424: final URL resolvedResourceLocation = resolveResourceLocation(
425: baseUrl, catalogLocation);
426: catalogLocation = resolvedResourceLocation.toExternalForm();
427: boolean exists = resourceExists(resolvedResourceLocation);
428: if (!exists) {
429: throw new FileNotFoundException(
430: "Catalog file does not exists: "
431: + catalogLocation);
432: }
433: final ResolvingXMLReader reader = new ResolvingXMLReader();
434: final Catalog catalog = reader.getCatalog();
435: catalog.getCatalogManager().setVerbosity(9);
436: catalog.getCatalogManager().setIgnoreMissingProperties(
437: false);
438: catalog.parseCatalog(catalogLocation);
439: oasisCatalog = catalog;
440: }
441: return oasisCatalog;
442: }
443:
444: private boolean resourceExists(final URL resolvedResourceLocation) {
445: InputStream in;
446: try {
447: in = resolvedResourceLocation.openStream();
448: in.close();
449: } catch (IOException e) {
450: return false;
451: }
452: return true;
453: }
454:
455: private URL resolveResourceLocation(final URL baseUrl,
456: String schemaLocation) throws MalformedURLException {
457: final URL schemaUrl;
458: if (schemaLocation.startsWith("file:")
459: || schemaLocation.startsWith("http:")) {
460: ComplexDataStoreConfigurator.LOGGER
461: .fine("using resource location as absolute path: "
462: + schemaLocation);
463: schemaUrl = new URL(schemaLocation);
464: } else {
465: if (baseUrl == null) {
466: schemaUrl = new URL(schemaLocation);
467: ComplexDataStoreConfigurator.LOGGER
468: .warning("base url not provided, may be unable to locate"
469: + schemaLocation
470: + ". Path resolved to: "
471: + schemaUrl.toExternalForm());
472: } else {
473: ComplexDataStoreConfigurator.LOGGER
474: .fine("using schema location " + schemaLocation
475: + " as relative to " + baseUrl);
476: schemaUrl = new URL(baseUrl, schemaLocation);
477: }
478: }
479: return schemaUrl;
480: }
481:
482: /**
483: * DOCUMENT ME!
484: *
485: * @return a Map<String,DataStore> where the key is the id given to
486: * the datastore in the configuration.
487: *
488: * @throws IOException
489: * @throws DataSourceException
490: * DOCUMENT ME!
491: */
492: private Map/* <String, FeatureAccess> */aquireSourceDatastores()
493: throws IOException {
494: ComplexDataStoreConfigurator.LOGGER.entering(getClass()
495: .getName(), "aquireSourceDatastores");
496:
497: final Map datastores = new HashMap();
498: final List dsParams = config.getSourceDataStores();
499: String id;
500:
501: for (Iterator it = dsParams.iterator(); it.hasNext();) {
502: SourceDataStore dsconfig = (SourceDataStore) it.next();
503: id = dsconfig.getId();
504:
505: Map datastoreParams = dsconfig.getParams();
506:
507: datastoreParams = resolveRelativePaths(datastoreParams);
508:
509: ComplexDataStoreConfigurator.LOGGER
510: .fine("looking for datastore " + id);
511:
512: DataAccess dataStore = DataAccessFinder
513: .createAccess((Object) datastoreParams);
514:
515: if (!(dataStore instanceof FeatureAccess)) {
516: throw new DataSourceException(
517: "Cannot find a DataAccess for parameters "
518: + datastoreParams);
519: }
520:
521: ComplexDataStoreConfigurator.LOGGER.fine("got datastore "
522: + dataStore);
523: datastores.put(id, dataStore);
524: }
525:
526: return datastores;
527: }
528:
529: /**
530: * Resolves any source datastore parameter settled as a file path relative
531: * to the location of the xml mappings configuration file as an absolute
532: * path and returns a new Map with it.
533: *
534: * @param datastoreParams
535: * @return
536: * @throws MalformedURLException
537: */
538: private Map resolveRelativePaths(final Map datastoreParams)
539: throws MalformedURLException {
540: Map resolvedParams = new HashMap();
541:
542: for (Iterator it = datastoreParams.entrySet().iterator(); it
543: .hasNext();) {
544: Map.Entry entry = (Map.Entry) it.next();
545: String key = (String) entry.getKey();
546: String value = (String) entry.getValue();
547: if (value != null && value.startsWith("file:")) {
548: value = value.substring("file:".length());
549: File f = new File(value);
550: if (!f.isAbsolute()) {
551: LOGGER.fine("resolving relative path " + value
552: + " for dataURLstore parameter " + key);
553: URL baseSchemasUrl = new URL(config
554: .getBaseSchemasUrl());
555: URL resolvedUrl = new URL(baseSchemasUrl, value);
556: value = resolvedUrl.toExternalForm();
557: //HACK for shapefile: shapefile requires file:/...
558: if (!"url".equals(key) && value.startsWith("file:")) {
559: value = value.substring("file:".length());
560: }
561: LOGGER.fine("new value for " + key + ": " + value);
562: }
563: }
564:
565: resolvedParams.put(key, value);
566: }
567:
568: return resolvedParams;
569: }
570:
571: /**
572: * Takes a prefixed attribute name and returns an {@link Name} by
573: * looking which namespace belongs the prefix to in
574: * {@link ComplexDataStoreDTO#getNamespaces()}.
575: *
576: * @param prefixedName
577: * @return
578: * @throws IllegalArgumentException
579: * if <code>prefixedName</code> has no prefix.
580: */
581: private Name degloseTypeName(String prefixedName)
582: throws IllegalArgumentException {
583: Name name = null;
584:
585: if (prefixedName == null) {
586: return null;
587: }
588:
589: int prefixIdx = prefixedName.indexOf(':');
590: if (prefixIdx == -1) {
591: return Types.typeName(prefixedName);
592: // throw new IllegalArgumentException(prefixedName + " is not
593: // prefixed");
594: }
595:
596: String nsPrefix = prefixedName.substring(0, prefixIdx);
597: String localName = prefixedName.substring(prefixIdx + 1);
598: String nsUri = namespaces.getURI(nsPrefix);
599:
600: name = Types.typeName(nsUri, localName);
601:
602: return name;
603: }
604:
605: private Name degloseName(String prefixedName)
606: throws IllegalArgumentException {
607: Name name = null;
608:
609: if (prefixedName == null) {
610: return null;
611: }
612:
613: int prefixIdx = prefixedName.indexOf(':');
614: if (prefixIdx == -1) {
615: return Types.typeName(prefixedName);
616: // throw new IllegalArgumentException(prefixedName + " is not
617: // prefixed");
618: }
619:
620: String nsPrefix = prefixedName.substring(0, prefixIdx);
621: String localName = prefixedName.substring(prefixIdx + 1);
622: String nsUri = namespaces.getURI(nsPrefix);
623:
624: name = Types.typeName(nsUri, localName);
625:
626: return name;
627: }
628: }
|