001: /* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
002: * This code is licensed under the GPL 2.0 license, availible at the root
003: * application directory.
004: */
005: package org.geoserver.wfs;
006:
007: import net.opengis.wfs.AllSomeType;
008: import net.opengis.wfs.FeatureCollectionType;
009: import net.opengis.wfs.GetFeatureType;
010: import net.opengis.wfs.GetFeatureWithLockType;
011: import net.opengis.wfs.LockFeatureResponseType;
012: import net.opengis.wfs.LockFeatureType;
013: import net.opengis.wfs.LockType;
014: import net.opengis.wfs.QueryType;
015: import net.opengis.wfs.WfsFactory;
016:
017: import org.geotools.data.DataUtilities;
018: import org.geotools.data.DefaultQuery;
019: import org.geotools.data.FeatureSource;
020: import org.geotools.factory.CommonFactoryFinder;
021: import org.geotools.factory.GeoTools;
022: import org.geotools.feature.FeatureCollection;
023: import org.geotools.feature.FeatureType;
024: import org.geotools.feature.GeometryAttributeType;
025: import org.geotools.feature.SchemaException;
026: import org.geotools.filter.expression.AbstractExpressionVisitor;
027: import org.geotools.filter.visitor.AbstractFilterVisitor;
028: import org.geotools.referencing.CRS;
029: import org.geotools.referencing.factory.GeotoolsFactory;
030: import org.geotools.xml.EMFUtils;
031: import org.opengis.filter.Filter;
032: import org.opengis.filter.FilterFactory;
033: import org.opengis.filter.FilterFactory2;
034: import org.opengis.filter.expression.ExpressionVisitor;
035: import org.opengis.filter.expression.PropertyName;
036: import org.opengis.filter.sort.SortBy;
037: import org.opengis.referencing.crs.CoordinateReferenceSystem;
038: import org.vfny.geoserver.global.AttributeTypeInfo;
039: import org.vfny.geoserver.global.Data;
040: import org.vfny.geoserver.global.FeatureTypeInfo;
041: import java.io.IOException;
042: import java.math.BigInteger;
043: import java.util.ArrayList;
044: import java.util.Calendar;
045: import java.util.Iterator;
046: import java.util.LinkedList;
047: import java.util.List;
048: import java.util.logging.Level;
049: import java.util.logging.Logger;
050: import javax.xml.namespace.QName;
051:
052: /**
053: * Web Feature Service GetFeature operation.
054: * <p>
055: * This operation returns an array of {@link org.geotools.feature.FeatureCollection}
056: * instances.
057: * </p>
058: *
059: * @author Rob Hranac, TOPP
060: * @author Justin Deoliveira, The Open Planning Project, jdeolive@openplans.org
061: *
062: * @version $Id: GetFeature.java 8390 2008-02-13 10:52:25Z aaime $
063: */
064: public class GetFeature {
065: /** Standard logging instance for class */
066: private static final Logger LOGGER = org.geotools.util.logging.Logging
067: .getLogger("org.vfny.geoserver.requests");
068:
069: /** The catalog */
070: protected Data catalog;
071:
072: /** The wfs configuration */
073: protected WFS wfs;
074:
075: /** filter factory */
076: protected FilterFactory filterFactory;
077:
078: /**
079: * Creates the GetFeature operation.
080: *
081: */
082: public GetFeature(WFS wfs, Data catalog) {
083: this .wfs = wfs;
084: this .catalog = catalog;
085: }
086:
087: /**
088: * @return The reference to the GeoServer catalog.
089: */
090: public Data getCatalog() {
091: return catalog;
092: }
093:
094: /**
095: * @return The reference to the WFS configuration.
096: */
097: public WFS getWFS() {
098: return wfs;
099: }
100:
101: /**
102: * Sets the filter factory to use to create filters.
103: *
104: * @param filterFactory
105: */
106: public void setFilterFactory(FilterFactory filterFactory) {
107: this .filterFactory = filterFactory;
108: }
109:
110: public FeatureCollectionType run(GetFeatureType request)
111: throws WFSException {
112: List queries = request.getQuery();
113:
114: if (queries.isEmpty()) {
115: throw new WFSException("No query specified");
116: }
117:
118: if (EMFUtils.isUnset(queries, "typeName")) {
119: String msg = "No feature types specified";
120: throw new WFSException(msg);
121: }
122:
123: // Optimization Idea
124: //
125: // We should be able to reduce this to a two pass opperations.
126: //
127: // Pass #1 execute
128: // - Attempt to Locks Fids during the first pass
129: // - Also collect Bounds information during the first pass
130: //
131: // Pass #2 writeTo
132: // - Using the Bounds to describe our FeatureCollections
133: // - Iterate through FeatureResults producing GML
134: //
135: // And allways remember to release locks if we are failing:
136: // - if we fail to aquire all the locks we will need to fail and
137: // itterate through the the FeatureSources to release the locks
138: //
139: if (request.getMaxFeatures() == null) {
140: request.setMaxFeatures(BigInteger
141: .valueOf(Integer.MAX_VALUE));
142: }
143:
144: // take into consideration the wfs max features
145: int maxFeatures = Math.min(request.getMaxFeatures().intValue(),
146: wfs.getGeoServer().getMaxFeatures());
147:
148: int count = 0; //should probably be long
149:
150: List results = new ArrayList();
151: try {
152: for (int i = 0; (i < request.getQuery().size())
153: && (count < maxFeatures); i++) {
154: QueryType query = (QueryType) request.getQuery().get(i);
155:
156: FeatureTypeInfo meta = null;
157:
158: if (query.getTypeName().size() == 1) {
159: meta = featureTypeInfo((QName) query.getTypeName()
160: .get(0));
161: } else {
162: //TODO: a join is taking place
163: }
164:
165: FeatureSource source = meta.getFeatureSource();
166:
167: List atts = meta.getAttributes();
168: List attNames = meta.getAttributeNames();
169:
170: //make sure property names are cool
171: List propNames = query.getPropertyName();
172:
173: for (Iterator iter = propNames.iterator(); iter
174: .hasNext();) {
175: String propName = (String) iter.next();
176:
177: //HACK: strip off namespace
178: if (propName.indexOf(':') != -1) {
179: propName = propName.substring(propName
180: .indexOf(':') + 1);
181: }
182:
183: if (!attNames.contains(propName)) {
184: String mesg = "Requested property: " + propName
185: + " is " + "not available " + "for "
186: + query.getTypeName() + ". "
187: + "The possible propertyName "
188: + "values are: " + attNames;
189:
190: throw new WFSException(mesg);
191: }
192: }
193:
194: //we must also include any properties that are mandatory ( even if not requested ),
195: // ie. those with minOccurs > 0
196: List extraGeometries = new ArrayList();
197: List properties = new ArrayList();
198: if (propNames.size() != 0) {
199: Iterator ii = atts.iterator();
200:
201: while (ii.hasNext()) {
202: AttributeTypeInfo ati = (AttributeTypeInfo) ii
203: .next();
204: LOGGER.finer("checking to see if " + propNames
205: + " contains" + ati);
206:
207: if (((ati.getMinOccurs() > 0) && (ati
208: .getMaxOccurs() != 0))) {
209: //mandatory, add it
210: properties.add(ati.getName());
211:
212: continue;
213: }
214:
215: //check if it was requested
216: for (Iterator p = propNames.iterator(); p
217: .hasNext();) {
218: String propName = (String) p.next();
219:
220: if (propName.matches("(\\w+:)?"
221: + ati.getName())) {
222: properties.add(ati.getName());
223: break;
224: }
225: }
226:
227: // if we need to force feature bounds computation, we have to load
228: // all of the geometries, but we'll have to remove them in the
229: // returned feature type
230: if (wfs.isFeatureBounding()
231: && meta
232: .getFeatureType()
233: .getAttributeType(ati.getName()) instanceof GeometryAttributeType
234: && !properties.contains(ati.getName())) {
235: properties.add(ati.getName());
236: extraGeometries.add(ati.getName());
237: }
238: }
239:
240: //replace property names
241: query.getPropertyName().clear();
242: query.getPropertyName().addAll(properties);
243: }
244:
245: //make sure filters are sane
246: if (query.getFilter() != null) {
247: final FeatureType featureType = source.getSchema();
248: ExpressionVisitor visitor = new AbstractExpressionVisitor() {
249: public Object visit(PropertyName name,
250: Object data) {
251: // case of multiple geometries being returned
252: if (name.evaluate(featureType) == null) {
253: //we want to throw wfs exception, but cant
254: throw new WFSException(
255: "Illegal property name: "
256: + name
257: .getPropertyName(),
258: "InvalidParameterValue");
259: }
260:
261: return name;
262: };
263: };
264:
265: query.getFilter().accept(
266: new AbstractFilterVisitor(visitor), null);
267: }
268:
269: // handle local maximum
270: int queryMaxFeatures = maxFeatures - count;
271: if (meta.getMaxFeatures() > 0
272: && meta.getMaxFeatures() < queryMaxFeatures)
273: queryMaxFeatures = meta.getMaxFeatures();
274: org.geotools.data.Query gtQuery = toDataQuery(query,
275: queryMaxFeatures, source, request.getVersion());
276: LOGGER.fine("Query is " + query + "\n To gt2: "
277: + gtQuery);
278:
279: FeatureCollection features = getFeatures(request,
280: source, gtQuery);
281: count += features.size();
282:
283: // we may need to shave off geometries we did load only to make bounds
284: // computation happy
285: if (extraGeometries.size() > 0) {
286: List residualProperties = new ArrayList(properties);
287: residualProperties.removeAll(extraGeometries);
288: String[] residualNames = (String[]) residualProperties
289: .toArray(new String[residualProperties
290: .size()]);
291: FeatureType targetType = DataUtilities
292: .createSubType(features.getSchema(),
293: residualNames);
294: features = new FeatureBoundsFeatureCollection(
295: features, targetType);
296: }
297:
298: //JD: TODO reoptimize
299: // if ( i == request.getQuery().size() - 1 ) {
300: // //DJB: dont calculate feature count if you dont have to. The MaxFeatureReader will take care of the last iteration
301: // maxFeatures -= features.getCount();
302: // }
303:
304: //GR: I don't know if the featuresults should be added here for later
305: //encoding if it was a lock request. may be after ensuring the lock
306: //succeed?
307: results.add(features);
308: }
309: } catch (IOException e) {
310: throw new WFSException("Error occurred getting features",
311: e, request.getHandle());
312: } catch (SchemaException e) {
313: throw new WFSException("Error occurred getting features",
314: e, request.getHandle());
315: }
316:
317: //locking
318: String lockId = null;
319: if (request instanceof GetFeatureWithLockType) {
320: GetFeatureWithLockType withLockRequest = (GetFeatureWithLockType) request;
321:
322: LockFeatureType lockRequest = WfsFactory.eINSTANCE
323: .createLockFeatureType();
324: lockRequest.setExpiry(withLockRequest.getExpiry());
325: lockRequest.setHandle(withLockRequest.getHandle());
326: lockRequest.setLockAction(AllSomeType.ALL_LITERAL);
327:
328: for (int i = 0; i < request.getQuery().size(); i++) {
329: QueryType query = (QueryType) request.getQuery().get(i);
330:
331: LockType lock = WfsFactory.eINSTANCE.createLockType();
332: lock.setFilter(query.getFilter());
333: lock.setHandle(query.getHandle());
334:
335: //TODO: joins?
336: lock.setTypeName((QName) query.getTypeName().get(0));
337: lockRequest.getLock().add(lock);
338: }
339:
340: LockFeature lockFeature = new LockFeature(wfs, catalog);
341: lockFeature.setFilterFactory(filterFactory);
342:
343: LockFeatureResponseType response = lockFeature
344: .lockFeature(lockRequest);
345: lockId = response.getLockId();
346: }
347:
348: return buildResults(count, results, lockId);
349: }
350:
351: /**
352: * Allows subclasses to alter the result generation
353: * @param count
354: * @param results
355: * @param lockId
356: * @return
357: */
358: protected FeatureCollectionType buildResults(int count,
359: List results, String lockId) {
360: FeatureCollectionType result = WfsFactory.eINSTANCE
361: .createFeatureCollectionType();
362: result.setNumberOfFeatures(BigInteger.valueOf(count));
363: result.setTimeStamp(Calendar.getInstance());
364: result.setLockId(lockId);
365: result.getFeature().addAll(results);
366: return result;
367: }
368:
369: /**
370: * Allows subclasses to poke with the feature collection extraction
371: * @param source
372: * @param gtQuery
373: * @return
374: * @throws IOException
375: */
376: protected FeatureCollection getFeatures(GetFeatureType request,
377: FeatureSource source, org.geotools.data.Query gtQuery)
378: throws IOException {
379: return source.getFeatures(gtQuery);
380: }
381:
382: /**
383: * Get this query as a geotools Query.
384: *
385: * <p>
386: * if maxFeatures is a not positive value DefaultQuery.DEFAULT_MAX will be
387: * used.
388: * </p>
389: *
390: * <p>
391: * The method name is changed to toDataQuery since this is a one way
392: * conversion.
393: * </p>
394: *
395: * @param maxFeatures number of features, or 0 for DefaultQuery.DEFAULT_MAX
396: *
397: * @return A Query for use with the FeatureSource interface
398: *
399: */
400: public org.geotools.data.Query toDataQuery(QueryType query,
401: int maxFeatures, FeatureSource source, String wfsVersion)
402: throws WFSException {
403: if (maxFeatures <= 0) {
404: maxFeatures = DefaultQuery.DEFAULT_MAX;
405: }
406:
407: String[] props = null;
408:
409: if (!query.getPropertyName().isEmpty()) {
410: props = new String[query.getPropertyName().size()];
411:
412: for (int p = 0; p < query.getPropertyName().size(); p++) {
413: String propertyName = (String) query.getPropertyName()
414: .get(p);
415: props[p] = propertyName;
416: }
417: }
418:
419: Filter filter = (Filter) query.getFilter();
420:
421: if (filter == null) {
422: filter = Filter.INCLUDE;
423: }
424:
425: //figure out the crs the data is in
426: CoordinateReferenceSystem crs = (source.getSchema()
427: .getDefaultGeometry() != null) ? source.getSchema()
428: .getDefaultGeometry().getCoordinateSystem() : null;
429:
430: // gather declared CRS
431: CoordinateReferenceSystem declaredCRS = WFSReprojectionUtil
432: .getDeclaredCrs(crs, wfsVersion);
433:
434: // make sure every bbox and geometry that does not have an attached crs will use
435: // the declared crs, and then reproject it to the native crs
436: Filter transformedFilter = filter;
437: if (declaredCRS != null)
438: transformedFilter = WFSReprojectionUtil.normalizeFilterCRS(
439: filter, source.getSchema(), declaredCRS);
440:
441: //only handle non-joins for now
442: QName typeName = (QName) query.getTypeName().get(0);
443: DefaultQuery dataQuery = new DefaultQuery(typeName
444: .getLocalPart(), transformedFilter, maxFeatures, props,
445: query.getHandle());
446:
447: //handle reprojection
448: CoordinateReferenceSystem target;
449: if (query.getSrsName() != null) {
450: try {
451: target = CRS.decode(query.getSrsName().toString());
452: } catch (Exception e) {
453: String msg = "Unable to support srsName: "
454: + query.getSrsName();
455: throw new WFSException(msg, e);
456: }
457: } else {
458: target = declaredCRS;
459: }
460: //if the crs are not equal, then reproject
461: if (target != null && declaredCRS != null
462: && !CRS.equalsIgnoreMetadata(crs, target)) {
463: dataQuery.setCoordinateSystemReproject(target);
464: }
465:
466: //handle sorting
467: if (query.getSortBy() != null) {
468: List sortBy = query.getSortBy();
469: dataQuery.setSortBy((SortBy[]) sortBy
470: .toArray(new SortBy[sortBy.size()]));
471: }
472:
473: //handle version, datastore may be able to use it
474: if (query.getFeatureVersion() != null) {
475: dataQuery.setVersion(query.getFeatureVersion());
476: }
477:
478: return dataQuery;
479: }
480:
481: FeatureTypeInfo featureTypeInfo(QName name) throws WFSException,
482: IOException {
483: FeatureTypeInfo meta = catalog.getFeatureTypeInfo(name
484: .getLocalPart(), name.getNamespaceURI());
485:
486: if (meta == null) {
487: String msg = "Could not locate " + name + " in catalog.";
488: throw new WFSException(msg);
489: }
490:
491: return meta;
492: }
493: }
|