001: /*
002: * GeoTools - 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;
018:
019: import java.io.IOException;
020: import java.util.ArrayList;
021: import java.util.Arrays;
022: import java.util.Collections;
023: import java.util.HashMap;
024: import java.util.HashSet;
025: import java.util.Iterator;
026: import java.util.LinkedList;
027: import java.util.List;
028: import java.util.Map;
029: import java.util.Set;
030: import java.util.logging.Logger;
031:
032: import org.geotools.catalog.ServiceInfo;
033: import org.geotools.data.DataSourceException;
034: import org.geotools.data.DefaultQuery;
035: import org.geotools.data.FeatureReader;
036: import org.geotools.data.FeatureSource;
037: import org.geotools.data.FeatureWriter;
038: import org.geotools.data.LockingManager;
039: import org.geotools.data.Query;
040: import org.geotools.data.SchemaNotFoundException;
041: import org.geotools.data.Source;
042: import org.geotools.data.Transaction;
043: import org.geotools.data.complex.filter.UnmappingFilterVisitor;
044: import org.geotools.data.complex.filter.XPath;
045: import org.geotools.data.complex.filter.XPath.StepList;
046: import org.geotools.data.feature.FeatureAccess;
047: import org.geotools.data.feature.FeatureSource2;
048: import org.geotools.data.feature.adapter.GTComlexFeatureTypeAdapter;
049: import org.geotools.factory.CommonFactoryFinder;
050: import org.geotools.feature.Feature;
051: import org.geotools.feature.FeatureCollection;
052: import org.geotools.feature.SchemaException;
053: import org.geotools.filter.FilterAttributeExtractor;
054: import org.opengis.feature.type.AttributeDescriptor;
055: import org.opengis.feature.type.FeatureType;
056: import org.opengis.feature.type.Name;
057: import org.opengis.filter.Filter;
058: import org.opengis.filter.FilterFactory;
059: import org.opengis.filter.expression.Expression;
060: import org.opengis.filter.expression.PropertyName;
061: import org.xml.sax.helpers.NamespaceSupport;
062:
063: import com.vividsolutions.jts.geom.Envelope;
064:
065: /**
066: *
067: * @author Gabriel Roldan, Axios Engineering
068: * @version $Id: ComplexDataStore.java 28577 2008-01-03 15:44:29Z groldan $
069: * @source $URL:
070: * http://svn.geotools.org/geotools/branches/2.4.x/modules/unsupported/community-schemas/community-schema-ds/src/main/java/org/geotools/data/complex/ComplexDataStore.java $
071: * @since 2.4
072: */
073: public class ComplexDataStore
074: /* extends AbstractDataStore */implements FeatureAccess {
075:
076: private static final boolean IS_WRITABLE = false;
077:
078: private static final Logger LOGGER = org.geotools.util.logging.Logging
079: .getLogger(ComplexDataStore.class.getPackage().getName());
080:
081: private Map/* <String, FeatureTypeMapping> */mappings = Collections.EMPTY_MAP;
082:
083: private FilterFactory filterFac = CommonFactoryFinder
084: .getFilterFactory(null);
085:
086: /**
087: *
088: * @param mappings
089: * a Set containing a {@linkplain FeatureTypeMapping} for each
090: * FeatureType this DataStore is going to hold.
091: */
092: public ComplexDataStore(Set/* <FeatureTypeMapping> */mappings) {
093: // super(ComplexDataStore.IS_WRITABLE);
094: FeatureTypeMapping mapping;
095: this .mappings = new HashMap();
096: for (Iterator it = mappings.iterator(); it.hasNext();) {
097: mapping = (FeatureTypeMapping) it.next();
098: Name mappedElement = mapping.getTargetFeature().getName();
099: this .mappings.put(mappedElement.getLocalPart(), mapping);
100: }
101: }
102:
103: /**
104: * Returns the set of target type names this DataStore holds, where the term
105: * 'target type name' refers to the name of one of the types this datastore
106: * produces by mapping another ones through the definitions stored in its
107: * {@linkplain FeatureTypeMapping}s
108: */
109: public String[] getTypeNames() throws IOException {
110: String[] typeNames = new String[mappings.size()];
111: this .mappings.keySet().toArray(typeNames);
112: return typeNames;
113: }
114:
115: /**
116: * Finds the target FeatureType named <code>typeName</code> in this
117: * ComplexDatastore's internal list of FeatureType mappings and returns it.
118: */
119: public org.geotools.feature.FeatureType getSchema(String typeName)
120: throws IOException {
121: FeatureTypeMapping mapping = getMapping(typeName);
122: AttributeDescriptor targetFeature = mapping.getTargetFeature();
123:
124: org.geotools.feature.FeatureType gtType;
125: gtType = new GTComlexFeatureTypeAdapter(targetFeature);
126: return gtType;
127: }
128:
129: /**
130: * Returns the mapping suite for the given target type name.
131: *
132: * <p>
133: * Note this method is public just for unit testing pourposes
134: * </p>
135: *
136: * @param typeName
137: * @return
138: * @throws IOException
139: */
140: public FeatureTypeMapping getMapping(String typeName)
141: throws IOException {
142: FeatureTypeMapping mapping = (FeatureTypeMapping) this .mappings
143: .get(typeName);
144: if (mapping == null) {
145: StringBuffer availables = new StringBuffer("[");
146: for (Iterator it = mappings.keySet().iterator(); it
147: .hasNext();) {
148: availables.append(it.next());
149: availables.append(it.hasNext() ? ", " : "");
150: }
151: availables.append("]");
152: throw new DataSourceException(typeName + " not found "
153: + availables);
154: }
155: return mapping;
156: }
157:
158: /**
159: * GR: this method is called from inside getFeatureReader(Query ,Transaction )
160: * to allow subclasses return an optimized FeatureReader wich supports the
161: * filter and attributes truncation specified in <code>query</code>
162: * <p>
163: * A subclass that supports the creation of such an optimized FeatureReader
164: * shold override this method. Otherwise, it just returns
165: * <code>getFeatureReader(typeName)</code>
166: * <p>
167: */
168: protected FeatureReader getFeatureReader(String typeName,
169: Query query) throws IOException {
170: throw new UnsupportedOperationException("Use access(typeName)");
171: /*
172: * FeatureTypeMapping mapping = getMapping(typeName);
173: * MappingFeatureReader reader = new MappingFeatureReader(mapping,
174: * query); return reader;
175: */
176: }
177:
178: /**
179: *
180: * @param typeName
181: */
182: protected FeatureReader getFeatureReader(String typeName)
183: throws IOException {
184:
185: throw new UnsupportedOperationException(
186: "Not needed since we support getFeatureReader(String, Query)");
187: }
188:
189: /**
190: * Computes the bounds of the features for the specified feature type that
191: * satisfy the query provided that there is a fast way to get that result.
192: * <p>
193: * Will return null if there is not fast way to compute the bounds. Since
194: * it's based on some kind of header/cached information, it's not guaranteed
195: * to be real bound of the features
196: * </p>
197: *
198: * @param query
199: * @return the bounds, or null if too expensive
200: * @throws SchemaNotFoundException
201: * @throws IOException
202: */
203: protected Envelope getBounds(Query query) throws IOException {
204: String typeName = query.getTypeName();
205: FeatureTypeMapping mapping = getMapping(typeName);
206: Query unmappedQuery = unrollQuery(query, mapping);
207: FeatureSource mappedSource = mapping.getSource();
208:
209: Envelope bounds = mappedSource.getBounds(unmappedQuery);
210: return bounds;
211: }
212:
213: /**
214: * Gets the number of the features that would be returned by this query for
215: * the specified feature type.
216: * <p>
217: * If getBounds(Query) returns <code>-1</code> due to expense consider
218: * using <code>getFeatures(Query).getCount()</code> as a an alternative.
219: * </p>
220: *
221: * @param targetQuery
222: * Contains the Filter and MaxFeatures to find the bounds for.
223: * @return The number of Features provided by the Query or <code>-1</code>
224: * if count is too expensive to calculate or any errors or occur.
225: * @throws IOException
226: *
227: * @throws IOException
228: * if there are errors getting the count
229: */
230: protected int getCount(final Query targetQuery) throws IOException {
231: final String typeName = targetQuery.getTypeName();
232: final FeatureTypeMapping mapping = getMapping(typeName);
233: final FeatureSource mappedSource = mapping.getSource();
234:
235: int count;
236: Query unmappedQuery = unrollQuery(targetQuery, mapping);
237:
238: if (mapping.getGroupByAttNames().size() == 0) {
239:
240: ((DefaultQuery) unmappedQuery).setMaxFeatures(targetQuery
241: .getMaxFeatures());
242: count = mappedSource.getCount(unmappedQuery);
243:
244: } else {
245: DefaultQuery groupAttsQuery = new DefaultQuery(
246: unmappedQuery);
247: groupAttsQuery.setPropertyNames(mapping
248: .getGroupByAttNames());
249: groupAttsQuery.setMaxFeatures(targetQuery.getMaxFeatures());
250: FeatureCollection features = mappedSource
251: .getFeatures(groupAttsQuery);
252: Iterator iterator = features.iterator();
253: count = 0;
254: Feature feature = null;
255: Object[] startingGroupValues = new Object[features
256: .getSchema().getAttributeCount()];
257: Object[] currFeatureAtts = new Object[features.getSchema()
258: .getAttributeCount()];
259: boolean breakGroup = true;
260: try {
261: while (iterator.hasNext()) {
262: feature = (Feature) iterator.next();
263: currFeatureAtts = feature
264: .getAttributes(currFeatureAtts);
265: if (!Arrays.equals(startingGroupValues,
266: currFeatureAtts)) {
267: count++;
268: startingGroupValues = feature
269: .getAttributes(startingGroupValues);
270: }
271: }
272: } finally {
273: features.close(iterator);
274: }
275: }
276: return count;
277: }
278:
279: /**
280: * Returns <code>Filter.INCLUDE</code>, as the whole filter is unrolled
281: * and passed back to the underlying DataStore to be treated.
282: *
283: * @return <code>Filter.INLCUDE</code>
284: */
285: protected Filter getUnsupportedFilter(String typeName, Filter filter) {
286: return Filter.INCLUDE;
287: }
288:
289: /**
290: * Creates a <code>org.geotools.data.Query</code> that operates over the
291: * surrogate DataStore, by unrolling the
292: * <code>org.geotools.filter.Filter</code> contained in the passed
293: * <code>query</code>, and replacing the list of required attributes by
294: * the ones of the mapped FeatureType.
295: *
296: * @param query
297: * @param mapping
298: * @return
299: */
300: public Query unrollQuery(Query query, FeatureTypeMapping mapping) {
301: Query unrolledQuery = Query.ALL;
302: Source source = mapping.getSource();
303:
304: if (!Query.ALL.equals(query)) {
305: Filter complexFilter = query.getFilter();
306: AttributeDescriptor descriptor = (AttributeDescriptor) source
307: .describe();
308:
309: Filter unrolledFilter = ComplexDataStore.unrollFilter(
310: complexFilter, mapping);
311:
312: List propNames = getSurrogatePropertyNames(query
313: .getPropertyNames(), mapping);
314:
315: DefaultQuery newQuery = new DefaultQuery();
316:
317: String name = descriptor.getName().getLocalPart();
318: newQuery.setTypeName(name);
319: newQuery.setFilter(unrolledFilter);
320: newQuery.setPropertyNames(propNames);
321: newQuery.setCoordinateSystem(query.getCoordinateSystem());
322: newQuery.setCoordinateSystemReproject(query
323: .getCoordinateSystemReproject());
324: newQuery.setHandle(query.getHandle());
325: newQuery.setMaxFeatures(query.getMaxFeatures());
326:
327: unrolledQuery = newQuery;
328: }
329: return unrolledQuery;
330: }
331:
332: /**
333: *
334: * @param mappingProperties
335: * @param mapping
336: * @return <code>null</code> if all surrogate attributes shall be queried,
337: * else the list of needed surrogate attributes to satisfy the
338: * mapping of prorperties in <code>mappingProperties</code>
339: */
340: private List getSurrogatePropertyNames(String[] mappingProperties,
341: FeatureTypeMapping mapping) {
342: List propNames = null;
343:
344: final FeatureType mappedType;
345:
346: final AttributeDescriptor targetDescriptor = mapping
347: .getTargetFeature();
348: mappedType = (FeatureType) targetDescriptor.type();
349:
350: if (mappingProperties != null && mappingProperties.length > 0) {
351: Set requestedSurrogateProperties = new HashSet();
352:
353: // add all surrogate attributes involved in mapping of the requested
354: // target schema attributes
355: List attMappings = mapping.getAttributeMappings();
356: List/* <String> */requestedProperties = Arrays
357: .asList(mappingProperties);
358:
359: for (Iterator itr = requestedProperties.iterator(); itr
360: .hasNext();) {
361: String requestedPropertyXPath = (String) itr.next();
362: StepList requestedPropertySteps;
363: NamespaceSupport namespaces = mapping.getNamespaces();
364: requestedPropertySteps = XPath.steps(targetDescriptor,
365: requestedPropertyXPath, namespaces);
366:
367: for (Iterator aitr = attMappings.iterator(); aitr
368: .hasNext();) {
369: final AttributeMapping entry = (AttributeMapping) aitr
370: .next();
371: final StepList targetSteps = entry.getTargetXPath();
372: final Expression sourceExpression = entry
373: .getSourceExpression();
374: final Expression idExpression = entry
375: .getIdentifierExpression();
376:
377: // i.e.: requested "measurement", found mapping of
378: // "measurement/result".
379: // "result" must be included to create "measurement"
380: if (targetSteps.containsAll(requestedPropertySteps)) {
381: FilterAttributeExtractor extractor = new FilterAttributeExtractor();
382: sourceExpression.accept(extractor, null);
383: idExpression.accept(extractor, null);
384:
385: Set exprAtts = extractor.getAttributeNameSet();
386:
387: for (Iterator eitr = exprAtts.iterator(); eitr
388: .hasNext();) {
389: String mappedAtt = (String) eitr.next();
390: PropertyName propExpr = filterFac
391: .property(mappedAtt);
392: Object object = propExpr
393: .evaluate(mappedType);
394: AttributeDescriptor mappedAttribute = (AttributeDescriptor) object;
395:
396: if (mappedAttribute != null) {
397: requestedSurrogateProperties
398: .add(mappedAtt);
399: } else {
400: LOGGER
401: .info("mapped type does not contains property "
402: + mappedAtt);
403: }
404: }
405: LOGGER.fine("adding atts needed for : "
406: + exprAtts);
407: }
408: }
409: }
410: propNames = new ArrayList(requestedSurrogateProperties);
411: }
412: return propNames;
413: }
414:
415: /**
416: * Takes a filter that operates against a {@linkplain FeatureTypeMapping}'s
417: * target FeatureType, and unrolls it creating a new Filter that operates
418: * against the mapping's source FeatureType.
419: *
420: * @param complexFilter
421: * @return TODO: implement filter unrolling
422: */
423: public static Filter unrollFilter(Filter complexFilter,
424: FeatureTypeMapping mapping) {
425: UnmappingFilterVisitor visitor = new UnmappingFilterVisitor(
426: mapping);
427: Filter unrolledFilter = (Filter) complexFilter.accept(visitor,
428: null);
429: return unrolledFilter;
430: }
431:
432: // //// FeatureAccess implementation /////
433:
434: public Source access(Name typeName) {
435: FeatureTypeMapping mapping;
436: try {
437: mapping = getMapping(typeName.getLocalPart());
438: } catch (IOException e) {
439: throw (RuntimeException) new RuntimeException()
440: .initCause(e);
441: }
442: MappingFeatureSource reader = new MappingFeatureSource(this ,
443: mapping);
444: return reader;
445: }
446:
447: public Object describe(Name typeName) {
448: FeatureTypeMapping mapping;
449: try {
450: mapping = getMapping(typeName.getLocalPart());
451: } catch (IOException e) {
452: throw (RuntimeException) new RuntimeException()
453: .initCause(e);
454: }
455: AttributeDescriptor targetFeature = mapping.getTargetFeature();
456: return targetFeature;
457: }
458:
459: public void dispose() {
460: // TODO Auto-generated method stub
461: }
462:
463: public ServiceInfo getInfo() {
464: throw new UnsupportedOperationException();
465: }
466:
467: public List getNames() {
468: List names = new LinkedList();
469: for (Iterator it = mappings.values().iterator(); it.hasNext();) {
470: FeatureTypeMapping mapping = (FeatureTypeMapping) it.next();
471: Name name = mapping.getTargetFeature().getName();
472: names.add(name);
473: }
474: return names;
475: }
476:
477: public void createSchema(
478: org.geotools.feature.FeatureType featureType)
479: throws IOException {
480: throw new UnsupportedOperationException();
481: }
482:
483: public FeatureReader getFeatureReader(Query query,
484: Transaction transaction) throws IOException {
485: // TODO Auto-generated method stub
486: return null;
487: }
488:
489: public FeatureSource getFeatureSource(String typeName)
490: throws IOException {
491: FeatureTypeMapping mapping = getMapping(typeName);
492: Name name = mapping.getTargetFeature().getName();
493: FeatureSource2 source = (FeatureSource2) access(name);
494: return source;
495: }
496:
497: public FeatureWriter getFeatureWriter(String typeName,
498: Filter filter, Transaction transaction) throws IOException {
499: throw new UnsupportedOperationException();
500: }
501:
502: public FeatureWriter getFeatureWriter(String typeName,
503: Transaction transaction) throws IOException {
504: throw new UnsupportedOperationException();
505: }
506:
507: public FeatureWriter getFeatureWriterAppend(String typeName,
508: Transaction transaction) throws IOException {
509: throw new UnsupportedOperationException();
510: }
511:
512: public LockingManager getLockingManager() {
513: return null;
514: }
515:
516: public FeatureSource getView(Query query) throws IOException,
517: SchemaException {
518: throw new UnsupportedOperationException();
519: }
520:
521: public void updateSchema(String typeName,
522: org.geotools.feature.FeatureType featureType)
523: throws IOException {
524: throw new UnsupportedOperationException();
525: }
526: }
|