001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2004-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: package org.geotools.data.view;
017:
018: import java.io.IOException;
019: import java.net.URI;
020: import java.util.LinkedList;
021: import java.util.List;
022: import java.util.Set;
023: import java.util.logging.Logger;
024:
025: import org.geotools.data.DataSourceException;
026: import org.geotools.data.DataStore;
027: import org.geotools.data.DataUtilities;
028: import org.geotools.data.DefaultQuery;
029: import org.geotools.data.FeatureListener;
030: import org.geotools.data.FeatureLocking;
031: import org.geotools.data.FeatureSource;
032: import org.geotools.data.FeatureStore;
033: import org.geotools.data.Query;
034: import org.geotools.data.crs.ForceCoordinateSystemFeatureResults;
035: import org.geotools.data.crs.ReprojectFeatureResults;
036: import org.geotools.factory.CommonFactoryFinder;
037: import org.geotools.feature.FeatureCollection;
038: import org.geotools.feature.FeatureType;
039: import org.geotools.feature.SchemaException;
040: import org.geotools.filter.FilterFactoryFinder;
041: import org.opengis.filter.Filter;
042: import org.opengis.filter.FilterFactory;
043: import org.opengis.referencing.crs.CoordinateReferenceSystem;
044:
045: import com.vividsolutions.jts.geom.Envelope;
046:
047: /**
048: * Wrapper for FeatureSource constrained by a Query.
049: *
050: * <p>
051: * Support FeatureSource decorator that takes care of mapping a Query &
052: * FeatureSource with the schema and definition query configured for it.
053: * </p>
054: *
055: * <p>
056: * Because GeoServer requires that attributes always be returned in the same
057: * order we need a way to smoothly inforce this. Could we use this class to do
058: * so?
059: * </p>
060: * <p>
061: * WARNING: this class is a placeholder for ideas right now - it may not always
062: * impement FeatureSource.
063: * </p>
064: *
065: * @author Gabriel Rold�n
066: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/main/src/main/java/org/geotools/data/view/DefaultView.java $
067: */
068: public class DefaultView implements FeatureSource {
069:
070: /** Shared package logger */
071: private static final Logger LOGGER = org.geotools.util.logging.Logging
072: .getLogger("org.geotools.data.view");
073:
074: /** FeatureSource being served up */
075: protected FeatureSource source;
076:
077: /**
078: * Schema generated by provided constraintQuery
079: */
080: private FeatureType schema;
081:
082: /** Query provided as a constraint */
083: private Query constraintQuery;
084:
085: /**
086: * Creates a new GeoServerFeatureSource object.
087: * <p>
088: * Grabs the following from query:
089: * <ul>
090: * <li>typeName - only used if client does not supply
091: * <li>cs - only used if client does not supply
092: * <li>csForce - only used if client does not supply
093: * <li>filter - combined with client filter
094: * <li>propertyNames - combined with client filter (indicate property names
095: * that *must* be included)
096: * </ul>
097: * </p>
098: * Schema is generated based on this information.
099: * </p>
100: *
101: * @param source
102: * a FeatureSource
103: * @param query
104: * Filter used to limit results
105: * @throws SchemaException
106: */
107: public DefaultView(FeatureSource source, Query query)
108: throws SchemaException {
109: this .source = source;
110: this .constraintQuery = query;
111:
112: FeatureType origionalType = source.getSchema();
113:
114: CoordinateReferenceSystem cs = null;
115: if (query.getCoordinateSystemReproject() != null) {
116: cs = query.getCoordinateSystemReproject();
117: } else if (query.getCoordinateSystem() != null) {
118: cs = query.getCoordinateSystem();
119: }
120: schema = DataUtilities.createSubType(origionalType, query
121: .getPropertyNames(), cs, query.getTypeName(), null);
122: }
123:
124: /**
125: * Factory that make the correct decorator for the provided featureSource.
126: *
127: * <p>
128: * This factory method is public and will be used to create all required
129: * subclasses. By comparison the constructors for this class have package
130: * visibiliy.
131: * </p>
132: *
133: * TODO: revisit this - I am not sure I want write access to views
134: * (especially if they do reprojection).
135: *
136: * @param source
137: * @param query
138: *
139: * @return @throws
140: * SchemaException
141: */
142: public static FeatureSource create(FeatureSource source, Query query)
143: throws SchemaException {
144: if (source instanceof FeatureLocking) {
145: // return new GeoServerFeatureLocking((FeatureLocking) source,
146: // schema, definitionQuery);
147: } else if (source instanceof FeatureStore) {
148: //return new GeoServerFeatureStore((FeatureStore) source, schema,
149: // definitionQuery);
150: }
151: return new DefaultView(source, query);
152: }
153:
154: /**
155: * Takes a query and adapts it to match re definitionQuery filter configured
156: * for a feature type. It won't handle coordinate system changes
157: * <p>
158: * Grabs the following from query:
159: * <ul>
160: * <li>typeName - only used if client does not supply
161: * <li>filter - combined with client filter
162: * <li>propertyNames - combined with client filter (indicate property names
163: * that *must* be included)
164: * </ul>
165: * </p>
166: *
167: * @param query
168: * Query against this DataStore
169: *
170: * @return Query restricted to the limits of definitionQuery
171: *
172: * @throws IOException
173: * See DataSourceException
174: * @throws DataSourceException
175: * If query could not meet the restrictions of definitionQuery
176: */
177: protected DefaultQuery makeDefinitionQuery(Query query)
178: throws IOException {
179: if ((query == Query.ALL) || query.equals(Query.ALL)) {
180: return new DefaultQuery(constraintQuery);
181: }
182:
183: try {
184: String[] propNames = extractAllowedAttributes(query);
185:
186: String typeName = query.getTypeName();
187: if (typeName == null) {
188: typeName = constraintQuery.getTypeName();
189: }
190:
191: URI namespace = query.getNamespace();
192: if (namespace == null || namespace == Query.NO_NAMESPACE) {
193: namespace = constraintQuery.getNamespace();
194: }
195: Filter filter = makeDefinitionFilter(query.getFilter());
196:
197: int maxFeatures = Math.min(query.getMaxFeatures(),
198: constraintQuery.getMaxFeatures());
199:
200: String handle = query.getHandle();
201: if (handle == null) {
202: handle = constraintQuery.getHandle();
203: } else if (constraintQuery.getHandle() != null) {
204: handle = handle + "(" + constraintQuery.getHandle()
205: + ")";
206: }
207:
208: DefaultQuery defaultQuery = new DefaultQuery(typeName,
209: namespace, filter, maxFeatures, propNames, handle);
210: defaultQuery.setSortBy(query.getSortBy());
211: return defaultQuery;
212: } catch (Exception ex) {
213: throw new DataSourceException(
214: "Could not restrict the query to the definition criteria: "
215: + ex.getMessage(), ex);
216: }
217: }
218:
219: /**
220: * List of allowed attributes.
221: *
222: * <p>
223: * Creates a list of FeatureTypeInfo's attribute names based on the
224: * attributes requested by <code>query</code> and making sure they not
225: * contain any non exposed attribute.
226: * </p>
227: *
228: * <p>
229: * Exposed attributes are those configured in the "attributes" element of
230: * the FeatureTypeInfo's configuration
231: * </p>
232: *
233: * @param query
234: * User's origional query
235: *
236: * @return List of allowed attribute types
237: */
238: private String[] extractAllowedAttributes(Query query) {
239: String[] propNames = null;
240:
241: if (query.retrieveAllProperties()) {
242: propNames = new String[schema.getAttributeCount()];
243:
244: for (int i = 0; i < schema.getAttributeCount(); i++) {
245: propNames[i] = schema.getAttributeType(i).getName();
246: }
247: } else {
248: String[] queriedAtts = query.getPropertyNames();
249: int queriedAttCount = queriedAtts.length;
250: List allowedAtts = new LinkedList();
251:
252: for (int i = 0; i < queriedAttCount; i++) {
253: if (schema.getAttributeType(queriedAtts[i]) != null) {
254: allowedAtts.add(queriedAtts[i]);
255: } else {
256: LOGGER.info("queried a not allowed property: "
257: + queriedAtts[i]
258: + ". Ommitting it from query");
259: }
260: }
261:
262: propNames = (String[]) allowedAtts
263: .toArray(new String[allowedAtts.size()]);
264: }
265:
266: return propNames;
267: }
268:
269: /**
270: * If a definition query has been configured for the FeatureTypeInfo, makes
271: * and return a new Filter that contains both the query's filter and the
272: * layer's definition one, by logic AND'ing them.
273: *
274: * @param filter
275: * Origional user supplied Filter
276: *
277: * @return Filter adjusted to the limitations of definitionQuery
278: *
279: * @throws DataSourceException
280: * If the filter could not meet the limitations of
281: * definitionQuery
282: */
283: protected Filter makeDefinitionFilter(Filter filter)
284: throws DataSourceException {
285: Filter newFilter = filter;
286: Filter constraintFilter = constraintQuery.getFilter();
287: try {
288: if (constraintFilter != Filter.INCLUDE) {
289: FilterFactory ff = CommonFactoryFinder
290: .getFilterFactory(null);
291: newFilter = ff.and(constraintFilter, filter);
292: }
293: } catch (Exception ex) {
294: throw new DataSourceException(
295: "Can't create the constraint filter", ex);
296: }
297: return newFilter;
298: }
299:
300: /**
301: * Implement getDataStore.
302: *
303: * <p>
304: * Description ...
305: * </p>
306: *
307: * @return @see org.geotools.data.FeatureSource#getDataStore()
308: */
309: public DataStore getDataStore() {
310: return source.getDataStore();
311: }
312:
313: /**
314: * Implement addFeatureListener.
315: *
316: * <p>
317: * Description ...
318: * </p>
319: *
320: * @param listener
321: *
322: * @see org.geotools.data.FeatureSource#addFeatureListener(org.geotools.data.FeatureListener)
323: */
324: public void addFeatureListener(FeatureListener listener) {
325: source.addFeatureListener(listener);
326: }
327:
328: /**
329: * Implement removeFeatureListener.
330: *
331: * <p>
332: * Description ...
333: * </p>
334: *
335: * @param listener
336: *
337: * @see org.geotools.data.FeatureSource#removeFeatureListener(org.geotools.data.FeatureListener)
338: */
339: public void removeFeatureListener(FeatureListener listener) {
340: source.removeFeatureListener(listener);
341: }
342:
343: /**
344: * Implement getFeatures.
345: *
346: * <p>
347: * Description ...
348: * </p>
349: *
350: * @param query
351: *
352: * @return @throws
353: * IOException
354: *
355: * @see org.geotools.data.FeatureSource#getFeatures(org.geotools.data.Query)
356: */
357: public FeatureCollection getFeatures(Query query)
358: throws IOException {
359: DefaultQuery mergedQuery = makeDefinitionQuery(query);
360: FeatureCollection results = source.getFeatures(mergedQuery);
361:
362: // Get all the coordinate systems involved in the two queries
363: CoordinateReferenceSystem cCs = constraintQuery
364: .getCoordinateSystem();
365: CoordinateReferenceSystem cCsr = constraintQuery
366: .getCoordinateSystemReproject();
367: CoordinateReferenceSystem qCs = query.getCoordinateSystem();
368: CoordinateReferenceSystem qCsr = query
369: .getCoordinateSystemReproject();
370:
371: /*
372: * Here we create all the needed transformations. We assume for the
373: * moment that the data stores are incapable of any kind of cs
374: * transformation and neither capable of forcing cs. We also assume that
375: * concatenating multiple forced and reprojected wrappers is inexpensive
376: * since they are optimized to recognize each other and to avoid useless
377: * object creation
378: */
379: try {
380: if (qCsr != null && cCsr != null) {
381: if (cCs != null)
382: results = new ForceCoordinateSystemFeatureResults(
383: results, cCs);
384: results = new ReprojectFeatureResults(results, cCsr);
385: if (qCs != null)
386: results = new ForceCoordinateSystemFeatureResults(
387: results, qCs);
388: results = new ReprojectFeatureResults(results, qCsr);
389: } else if (qCs != null && cCsr != null) {
390: // complex case 2, reprojected then forced
391: // mergedQuery.setCoordinateSystem(cCs);
392: // mergedQuery.setCoordinateSystemReproject(cCsr);
393: try {
394: if (cCs != null)
395: results = new ForceCoordinateSystemFeatureResults(
396: results, cCs);
397: results = new ReprojectFeatureResults(source
398: .getFeatures(mergedQuery), cCsr);
399:
400: results = new ForceCoordinateSystemFeatureResults(
401: results, qCs);
402: } catch (SchemaException e) {
403: throw new DataSourceException(
404: "This should not happen", e);
405: }
406: } else {
407: // easy case, we can just put toghether one forced cs and one
408: // reprojection cs
409: // in the mixed query and let it go
410:
411: // mergedQuery.setCoordinateSystem(qCs != null ? qCs : cCs);
412: // mergedQuery.setCoordinateSystemReproject(qCsr != null ? qCsr
413: // : cCsr);
414: CoordinateReferenceSystem forcedCS = qCs != null ? qCs
415: : cCs;
416: CoordinateReferenceSystem reprojectCS = qCsr != null ? qCsr
417: : cCsr;
418:
419: if (forcedCS != null)
420: results = new ForceCoordinateSystemFeatureResults(
421: results, forcedCS);
422: if (reprojectCS != null)
423: results = new ReprojectFeatureResults(results,
424: reprojectCS);
425: }
426: } catch (IOException e) {
427: throw e;
428: } catch (Exception e) {
429: throw new DataSourceException(
430: "A problem occurred while handling forced "
431: + "coordinate systems and reprojection", e);
432: }
433:
434: return results;
435: }
436:
437: /**
438: * Implement getFeatures.
439: *
440: * <p>
441: * Description ...
442: * </p>
443: *
444: * @param filter
445: *
446: * @return @throws
447: * IOException
448: */
449: public FeatureCollection getFeatures(Filter filter)
450: throws IOException {
451: return getFeatures(new DefaultQuery(schema.getTypeName(),
452: filter));
453: }
454:
455: /**
456: * Implement getFeatures.
457: *
458: * <p>
459: * Description ...
460: * </p>
461: *
462: * @return @throws
463: * IOException
464: *
465: * @see org.geotools.data.FeatureSource#getFeatures()
466: */
467: public FeatureCollection getFeatures() throws IOException {
468: return getFeatures(Query.ALL);
469: }
470:
471: /**
472: * Implement getSchema.
473: *
474: * <p>
475: * Description ...
476: * </p>
477: *
478: * @return @see org.geotools.data.FeatureSource#getSchema()
479: */
480: public FeatureType getSchema() {
481: return schema;
482: }
483:
484: /**
485: * Retrieves the total extent of this FeatureSource.
486: *
487: * <p>
488: * Please note this extent will reflect the provided definitionQuery.
489: * </p>
490: *
491: * @return Extent of this FeatureSource, or <code>null</code> if no
492: * optimizations exist.
493: *
494: * @throws IOException
495: * If bounds of definitionQuery
496: */
497: public Envelope getBounds() throws IOException {
498: if (constraintQuery.getCoordinateSystemReproject() == null) {
499: if (constraintQuery.getFilter() == null
500: || constraintQuery.getFilter() == Filter.INCLUDE
501: || Filter.INCLUDE.equals(constraintQuery
502: .getFilter())) {
503: return source.getBounds();
504: }
505: return source.getBounds(constraintQuery);
506:
507: }
508: // this will create a feature results that can reproject the
509: // features, and will
510: // properly compute the bouds
511: return getFeatures().getBounds();
512:
513: }
514:
515: /**
516: * Retrive the extent of the Query.
517: *
518: * <p>
519: * This method provides access to an optimized getBounds opperation. If no
520: * optimized opperation is available <code>null</code> will be returned.
521: * </p>
522: *
523: * <p>
524: * You may still make use of getFeatures( Query ).getCount() which will
525: * return the correct answer (even if it has to itterate through all the
526: * results to do so.
527: * </p>
528: *
529: * @param query
530: * User's query
531: *
532: * @return Extend of Query or <code>null</code> if no optimization is
533: * available
534: *
535: * @throws IOException
536: * If a problem is encountered with source
537: */
538: public Envelope getBounds(Query query) throws IOException {
539: if (constraintQuery.getCoordinateSystemReproject() == null) {
540: try {
541: query = makeDefinitionQuery(query);
542: } catch (IOException ex) {
543: return null;
544: }
545:
546: return source.getBounds(query);
547: }
548: // this will create a feature results that can reproject the
549: // features, and will
550: // properly compute the bouds
551: return getFeatures(query).getBounds();
552: }
553:
554: /**
555: * Adjust query and forward to source.
556: *
557: * <p>
558: * This method provides access to an optimized getCount opperation. If no
559: * optimized opperation is available <code>-1</code> will be returned.
560: * </p>
561: *
562: * <p>
563: * You may still make use of getFeatures( Query ).getCount() which will
564: * return the correct answer (even if it has to itterate through all the
565: * results to do so).
566: * </p>
567: *
568: * @param query
569: * User's query.
570: *
571: * @return Number of Features for Query, or -1 if no optimization is
572: * available.
573: */
574: public int getCount(Query query) {
575: try {
576: query = makeDefinitionQuery(query);
577: } catch (IOException ex) {
578: return -1;
579: }
580: try {
581: return source.getCount(query);
582: } catch (IOException e) {
583: return 0;
584: }
585: }
586:
587: public Set getSupportedHints() {
588: return source.getSupportedHints();
589: }
590: }
|