001: //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/trunk/src/org/deegree/ogcwebservices/wfs/operation/GetFeature.java $
002: /*---------------- FILE HEADER ------------------------------------------
003:
004: This file is part of deegree.
005: Copyright (C) 2001-2008 by:
006: EXSE, Department of Geography, University of Bonn
007: http://www.giub.uni-bonn.de/deegree/
008: lat/lon GmbH
009: http://www.lat-lon.de
010:
011: This library is free software; you can redistribute it and/or
012: modify it under the terms of the GNU Lesser General Public
013: License as published by the Free Software Foundation; either
014: version 2.1 of the License, or (at your option) any later version.
015:
016: This library is distributed in the hope that it will be useful,
017: but WITHOUT ANY WARRANTY; without even the implied warranty of
018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019: Lesser General Public License for more details.
020:
021: You should have received a copy of the GNU Lesser General Public
022: License along with this library; if not, write to the Free Software
023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024:
025: Contact:
026:
027: Andreas Poth
028: lat/lon GmbH
029: Aennchenstraße 19
030: 53177 Bonn
031: Germany
032: E-Mail: poth@lat-lon.de
033:
034: Prof. Dr. Klaus Greve
035: Department of Geography
036: University of Bonn
037: Meckenheimer Allee 166
038: 53115 Bonn
039: Germany
040: E-Mail: greve@giub.uni-bonn.de
041:
042: ---------------------------------------------------------------------------*/
043: package org.deegree.ogcwebservices.wfs.operation;
044:
045: import java.net.URI;
046: import java.util.ArrayList;
047: import java.util.HashMap;
048: import java.util.List;
049: import java.util.Map;
050:
051: import org.deegree.datatypes.QualifiedName;
052: import org.deegree.framework.log.ILogger;
053: import org.deegree.framework.log.LoggerFactory;
054: import org.deegree.framework.util.KVP2Map;
055: import org.deegree.framework.xml.NamespaceContext;
056: import org.deegree.i18n.Messages;
057: import org.deegree.model.filterencoding.FeatureFilter;
058: import org.deegree.model.filterencoding.FeatureId;
059: import org.deegree.model.filterencoding.Filter;
060: import org.deegree.ogcbase.PropertyPath;
061: import org.deegree.ogcbase.PropertyPathFactory;
062: import org.deegree.ogcbase.PropertyPathStep;
063: import org.deegree.ogcbase.SortProperty;
064: import org.deegree.ogcwebservices.InconsistentRequestException;
065: import org.deegree.ogcwebservices.InvalidParameterValueException;
066: import org.deegree.ogcwebservices.OGCWebServiceException;
067: import org.w3c.dom.Element;
068:
069: /**
070: * Represents a <code>GetFeature</code> request to a web feature service.
071: * <p>
072: * The GetFeature operation allows the retrieval of features from a web feature service. A GetFeature request is
073: * processed by a WFS and when the value of the outputFormat attribute is set to text/gml; subtype=gml/3.1.1, a GML
074: * instance document, containing the result set, is returned to the client.
075: *
076: * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a>
077: * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
078: * @author last edited by: $Author: mschneider $
079: *
080: * @version $Revision: 9534 $, $Date: 2008-01-14 09:37:41 -0800 (Mon, 14 Jan 2008) $
081: */
082: public class GetFeature extends AbstractWFSRequest {
083:
084: private static final ILogger LOG = LoggerFactory
085: .getLogger(GetFeature.class);
086:
087: private static final long serialVersionUID = 8885456550385433051L;
088:
089: /** Serialized java object format (deegree specific extension) * */
090: public static final String FORMAT_FEATURECOLLECTION = "FEATURECOLLECTION";
091:
092: /**
093: * Known result types.
094: */
095: public static enum RESULT_TYPE {
096:
097: /** A full response should be generated. */
098: RESULTS,
099:
100: /** Only a count of the number of features should be returned. */
101: HITS
102: }
103:
104: protected RESULT_TYPE resultType = RESULT_TYPE.RESULTS;
105:
106: protected String outputFormat;
107:
108: protected int maxFeatures;
109:
110: private int traverseXLinkDepth;
111:
112: private int traverseXLinkExpiry;
113:
114: protected List<Query> queries;
115:
116: // deegree specific extension, default: 1 (start at first feature)
117: protected int startPosition;
118:
119: /**
120: * Creates a new <code>GetFeature</code> instance.
121: *
122: * @param version
123: * request version
124: * @param id
125: * id of the request
126: * @param handle
127: * @param resultType
128: * desired result type (results | hits)
129: * @param outputFormat
130: * requested result format
131: * @param maxFeatures
132: * @param startPosition
133: * deegree specific parameter defining where to start considering features
134: * @param traverseXLinkDepth
135: * @param traverseXLinkExpiry
136: * @param queries
137: * @param vendorSpecificParam
138: */
139: GetFeature(String version, String id, String handle,
140: RESULT_TYPE resultType, String outputFormat,
141: int maxFeatures, int startPosition, int traverseXLinkDepth,
142: int traverseXLinkExpiry, Query[] queries,
143: Map<String, String> vendorSpecificParam) {
144: super (version, id, handle, vendorSpecificParam);
145: this .setQueries(queries);
146: this .outputFormat = outputFormat;
147: this .maxFeatures = maxFeatures;
148: this .startPosition = startPosition;
149: this .resultType = resultType;
150: this .traverseXLinkDepth = traverseXLinkDepth;
151: this .traverseXLinkExpiry = traverseXLinkExpiry;
152: }
153:
154: protected GetFeature() {
155: super (null, null, null, null);
156: }
157:
158: /**
159: * Creates a new <code>GetFeature</code> instance from the given parameters.
160: *
161: * @param version
162: * request version
163: * @param id
164: * id of the request
165: * @param resultType
166: * desired result type (results | hits)
167: * @param outputFormat
168: * requested result format
169: * @param handle
170: * @param maxFeatures
171: * default = -1 (all features)
172: * @param startPosition
173: * default = 0 (starting at the first feature)
174: * @param traverseXLinkDepth
175: * @param traverseXLinkExpiry
176: * @param queries
177: * a set of Query objects that describes the query to perform
178: * @return new <code>GetFeature</code> request
179: */
180: public static GetFeature create(String version, String id,
181: RESULT_TYPE resultType, String outputFormat, String handle,
182: int maxFeatures, int startPosition, int traverseXLinkDepth,
183: int traverseXLinkExpiry, Query[] queries) {
184: return new GetFeature(version, id, handle, resultType,
185: outputFormat, maxFeatures, startPosition,
186: traverseXLinkDepth, traverseXLinkExpiry, queries, null);
187: }
188:
189: /**
190: * Creates a new <code>GetFeature</code> instance from a document that contains the DOM representation of the
191: * request.
192: *
193: * @param id
194: * of the request
195: * @param root
196: * element that contains the DOM representation of the request
197: * @return new <code>GetFeature</code> request
198: * @throws OGCWebServiceException
199: */
200: public static GetFeature create(String id, Element root)
201: throws OGCWebServiceException {
202: GetFeatureDocument doc = new GetFeatureDocument();
203: doc.setRootElement(root);
204: GetFeature request;
205: try {
206: request = doc.parse(id);
207: } catch (Exception e) {
208: LOG.logError(e.getMessage(), e);
209: throw new OGCWebServiceException("GetFeature", e
210: .getMessage());
211: }
212: return request;
213: }
214:
215: /**
216: * Creates a new <code>GetFeature</code> instance from the given key-value pair encoded request.
217: *
218: * @param id
219: * request identifier
220: * @param request
221: * @return new <code>GetFeature</code> request
222: * @throws InvalidParameterValueException
223: * @throws InconsistentRequestException
224: */
225: public static GetFeature create(String id, String request)
226: throws InconsistentRequestException,
227: InvalidParameterValueException {
228: Map<String, String> map = KVP2Map.toMap(request);
229: map.put("ID", id);
230: return create(map);
231: }
232:
233: /**
234: * Creates a new <code>GetFeature</code> request from the given map.
235: *
236: * @param kvp
237: * key-value pairs, keys have to be uppercase
238: * @return new <code>GetFeature</code> request
239: * @throws InvalidParameterValueException
240: * @throws InconsistentRequestException
241: */
242: public static GetFeature create(Map<String, String> kvp)
243: throws InconsistentRequestException,
244: InvalidParameterValueException {
245:
246: // SERVICE
247: checkServiceParameter(kvp);
248:
249: // ID (deegree specific)
250: String id = kvp.get("ID");
251:
252: // VERSION
253: String version = checkVersionParameter(kvp);
254:
255: // OUTPUTFORMAT
256: String outputFormat = getParam("OUTPUTFORMAT", kvp, FORMAT_GML3);
257:
258: // RESULTTYPE
259: RESULT_TYPE resultType = RESULT_TYPE.RESULTS;
260: String resultTypeString = kvp.get("RESULTTYPE");
261: if ("hits".equals(resultTypeString)) {
262: resultType = RESULT_TYPE.HITS;
263: }
264:
265: // FEATUREVERSION
266: String featureVersion = kvp.get("FEATUREVERSION");
267:
268: // MAXFEATURES
269: String maxFeaturesString = kvp.get("MAXFEATURES");
270: // -1: fetch all features
271: int maxFeatures = -1;
272: if (maxFeaturesString != null) {
273: try {
274: maxFeatures = Integer.parseInt(maxFeaturesString);
275: if (maxFeatures < 1) {
276: throw new NumberFormatException();
277: }
278: } catch (NumberFormatException e) {
279: LOG.logError(e.getMessage(), e);
280: String msg = Messages.getMessage(
281: "WFS_PARAMETER_INVALID_INT", maxFeaturesString,
282: "MAXFEATURES");
283: throw new InvalidParameterValueException(msg);
284: }
285: }
286:
287: // STARTPOSITION (deegree specific)
288: String startPosString = getParam("STARTPOSITION", kvp, "1");
289: int startPosition = 1;
290: try {
291: startPosition = Integer.parseInt(startPosString);
292: if (startPosition < 1) {
293: throw new NumberFormatException();
294: }
295: } catch (NumberFormatException e) {
296: LOG.logError(e.getMessage(), e);
297: String msg = Messages.getMessage(
298: "WFS_PARAMETER_INVALID_INT", startPosString,
299: "STARTPOSITION");
300: throw new InvalidParameterValueException(msg);
301: }
302:
303: // SRSNAME
304: String srsName = kvp.get("SRSNAME");
305:
306: // SORTBY
307: SortProperty[] sortProperties = null;
308:
309: // TRAVERSEXLINKDEPTH
310: int traverseXLinkDepth = -1;
311:
312: // TRAVERSEXLINKEXPIRY
313: int traverseXLinkExpiry = -1;
314:
315: Map<QualifiedName, Filter> filterMap = null;
316:
317: // TYPENAME
318: QualifiedName[] typeNames = extractTypeNames(kvp);
319: if (typeNames.length == 0) {
320: // check if FEATUREID is present
321: String featureId = kvp.get("FEATUREID");
322: if (featureId != null) {
323: // no TYPENAME parameter -> request needs to be augmented later (with configuration)
324: return new AugmentableGetFeature(version, id, null,
325: resultType, outputFormat, maxFeatures,
326: startPosition, traverseXLinkDepth,
327: traverseXLinkExpiry, new Query[0], kvp);
328: }
329: String msg = Messages
330: .getMessage("WFS_TYPENAME+FID_PARAMS_MISSING");
331: throw new InvalidParameterValueException(msg);
332: }
333:
334: // check if FEATUREID is present
335: String featureId = kvp.get("FEATUREID");
336: if (featureId != null) {
337: String[] featureIds = featureId.split(",");
338: if (typeNames.length != 1
339: && featureIds.length != typeNames.length) {
340: String msg = Messages.getMessage(
341: "WFS_TYPENAME+FID_COUNT_MISMATCH",
342: typeNames.length, featureIds.length);
343: throw new InvalidParameterValueException(msg);
344: } else if (typeNames.length == 1) {
345: // build one filter
346: ArrayList<FeatureId> fids = new ArrayList<FeatureId>(
347: featureIds.length);
348: for (String fid : featureIds) {
349: fids.add(new FeatureId(fid));
350: }
351: Filter filter = new FeatureFilter(fids);
352: filterMap = new HashMap<QualifiedName, Filter>();
353: filterMap.put(typeNames[0], filter);
354: } else {
355: throw new InvalidParameterValueException(
356: "Usage of FEATUREID with multiple TYPENAME values is not supported yet.");
357: }
358: }
359:
360: // BBOX
361: Filter bboxFilter = extractBBOXFilter(kvp);
362:
363: // FILTER (mutually exclusive with FEATUREID or BBOX, prequisite: TYPENAME)
364: if (filterMap != null || bboxFilter != null) {
365: if (kvp.containsKey("FILTER")) {
366: String msg = Messages
367: .getMessage("WFS_GET_FEATURE_FEATUREID_BBOX_AND_FILTER");
368: throw new InvalidParameterValueException(msg);
369: }
370: } else {
371: filterMap = extractFilters(kvp, typeNames);
372: }
373:
374: // PROPERTYNAME
375: Map<QualifiedName, PropertyPath[]> propertyNameMap = extractPropNames(
376: kvp, typeNames);
377:
378: // build a Query instance for each requested feature type (later also for each featureid...)
379: Query[] queries = new Query[typeNames.length];
380: for (int i = 0; i < queries.length; i++) {
381: QualifiedName ftName = typeNames[i];
382: PropertyPath[] properties = propertyNameMap.get(ftName);
383: Filter filter = filterMap.get(ftName);
384: QualifiedName[] ftNames = new QualifiedName[] { ftName };
385: queries[i] = new Query(properties, null, sortProperties,
386: null, featureVersion, ftNames, null, srsName,
387: filter, resultType, maxFeatures, startPosition);
388: }
389:
390: // build a GetFeature request that contains all queries
391: GetFeature request = new GetFeature(version, id, null,
392: resultType, outputFormat, maxFeatures, startPosition,
393: traverseXLinkDepth, traverseXLinkExpiry, queries, kvp);
394: return request;
395: }
396:
397: /**
398: * Extracts the PROPERTYNAME parameter and assigns them to the requested type names.
399: *
400: * @param kvp
401: * @param typeNames
402: * @return map with the assignments of type names to property names
403: * @throws InvalidParameterValueException
404: */
405: protected static Map<QualifiedName, PropertyPath[]> extractPropNames(
406: Map<String, String> kvp, QualifiedName[] typeNames)
407: throws InvalidParameterValueException {
408: Map<QualifiedName, PropertyPath[]> propMap = new HashMap<QualifiedName, PropertyPath[]>();
409: String propNameString = kvp.get("PROPERTYNAME");
410: if (propNameString != null) {
411: String[] propNameLists = propNameString.split("\\)");
412: if (propNameLists.length != typeNames.length) {
413: String msg = Messages.getMessage(
414: "WFS_PROPNAME_PARAM_WRONG_COUNT", Integer
415: .toString(propNameLists.length),
416: Integer.toString(typeNames.length));
417: throw new InvalidParameterValueException(msg);
418: }
419: NamespaceContext nsContext = extractNamespaceParameter(kvp);
420: for (int i = 0; i < propNameLists.length; i++) {
421: String propNameList = propNameLists[i];
422: if (propNameList.startsWith("(")) {
423: propNameList = propNameList.substring(1);
424: }
425: String[] propNames = propNameList.split(",");
426: PropertyPath[] paths = new PropertyPath[propNames.length];
427: for (int j = 0; j < propNames.length; j++) {
428: PropertyPath path = transformToPropertyPath(
429: propNames[j], nsContext);
430: paths[j] = (path);
431: }
432: propMap.put(typeNames[i], paths);
433: }
434: }
435: return propMap;
436: }
437:
438: /**
439: * Transforms the given property name to a (qualified) <code>PropertyPath</code> object by using the specified
440: * namespace bindings.
441: *
442: * @param propName
443: * @param nsContext
444: * @return (qualified) <code>PropertyPath</code> object
445: * @throws InvalidParameterValueException
446: */
447: private static PropertyPath transformToPropertyPath(
448: String propName, NamespaceContext nsContext)
449: throws InvalidParameterValueException {
450: String[] steps = propName.split("/");
451: List<PropertyPathStep> propertyPathSteps = new ArrayList<PropertyPathStep>(
452: steps.length);
453:
454: for (int i = 0; i < steps.length; i++) {
455: PropertyPathStep propertyStep = null;
456: QualifiedName propertyName = null;
457: String step = steps[i];
458: boolean isAttribute = false;
459: boolean isIndexed = false;
460: int selectedIndex = -1;
461:
462: // check if step begins with '@' -> must be the final step then
463: if (step.startsWith("@")) {
464: if (i != steps.length - 1) {
465: String msg = "PropertyName '"
466: + propName
467: + "' is illegal: the attribute specifier may only "
468: + "be used for the final step.";
469: throw new InvalidParameterValueException(msg);
470: }
471: step = step.substring(1);
472: isAttribute = true;
473: }
474:
475: // check if the step ends with brackets ([...])
476: if (step.endsWith("]")) {
477: if (isAttribute) {
478: String msg = "PropertyName '"
479: + propName
480: + "' is illegal: if the attribute specifier ('@') is used, "
481: + "index selection ('[...']) is not possible.";
482: throw new InvalidParameterValueException(msg);
483: }
484: int bracketPos = step.indexOf('[');
485: if (bracketPos < 0) {
486: String msg = "PropertyName '"
487: + propName
488: + "' is illegal. No opening brackets found for step '"
489: + step + "'.";
490: throw new InvalidParameterValueException(msg);
491: }
492: try {
493: selectedIndex = Integer.parseInt(step.substring(
494: bracketPos + 1, step.length() - 1));
495: } catch (NumberFormatException e) {
496: LOG.logError(e.getMessage(), e);
497: String msg = "PropertyName '"
498: + propName
499: + "' is illegal. Specified index '"
500: + step.substring(bracketPos + 1, step
501: .length() - 1)
502: + "' is not a number.";
503: throw new InvalidParameterValueException(msg);
504: }
505: step = step.substring(0, bracketPos);
506: isIndexed = true;
507: }
508:
509: // determine namespace prefix and binding (if any)
510: int colonPos = step.indexOf(':');
511: String prefix = "";
512: String localName = step;
513: if (colonPos > 0) {
514: prefix = step.substring(0, colonPos);
515: localName = step.substring(colonPos + 1);
516: }
517: URI nsURI = nsContext.getURI(prefix);
518: if (nsURI == null && prefix.length() > 0) {
519: String msg = "PropertyName '" + propName
520: + "' uses an unbound namespace prefix: "
521: + prefix;
522: throw new InvalidParameterValueException(msg);
523: }
524: propertyName = new QualifiedName(prefix, localName, nsURI);
525:
526: if (isAttribute) {
527: propertyStep = PropertyPathFactory
528: .createAttributePropertyPathStep(propertyName);
529: } else if (isIndexed) {
530: propertyStep = PropertyPathFactory
531: .createPropertyPathStep(propertyName,
532: selectedIndex);
533: } else {
534: propertyStep = PropertyPathFactory
535: .createPropertyPathStep(propertyName);
536: }
537: propertyPathSteps.add(propertyStep);
538: }
539: return PropertyPathFactory
540: .createPropertyPath(propertyPathSteps);
541: }
542:
543: /**
544: * Returns the output format.
545: * <p>
546: * The outputFormat attribute defines the format to use to generate the result set. Vendor specific formats,
547: * declared in the capabilities document are possible. The WFS-specs implies GML as default output format.
548: *
549: * @return the output format.
550: */
551: public String getOutputFormat() {
552: return this .outputFormat;
553: }
554:
555: /**
556: * The query defines which feature type to query, what properties to retrieve and what constraints (spatial and
557: * non-spatial) to apply to those properties.
558: * <p>
559: * only used for xml-coded requests
560: *
561: * @return contained queries
562: */
563: public Query[] getQuery() {
564: return queries.toArray(new Query[queries.size()]);
565: }
566:
567: /**
568: * sets the <Query>
569: *
570: * @param query
571: */
572: public void setQueries(Query[] query) {
573: this .queries = new ArrayList<Query>(query.length);
574: if (query != null) {
575: for (int i = 0; i < query.length; i++) {
576: this .queries.add(query[i]);
577: }
578: }
579: }
580:
581: /**
582: * The optional maxFeatures attribute can be used to limit the number of features that a GetFeature request
583: * retrieves. Once the maxFeatures limit is reached, the result set is truncated at that point. If not limit is set
584: * -1 will be returned.
585: *
586: * @return number of feature to fetch, -1 if no limit is set
587: */
588: public int getMaxFeatures() {
589: return maxFeatures;
590: }
591:
592: /**
593: * @see #getMaxFeatures()
594: * @param max
595: */
596: public void setMaxFeatures(int max) {
597: this .maxFeatures = max;
598: for (int i = 0; i < queries.size(); i++) {
599: queries.get(i).setMaxFeatures(max);
600: }
601: }
602:
603: /**
604: * The startPosition parameter identifies the first result set entry to be returned specified the default is the
605: * first record. If not startposition is set 0 will be returned
606: *
607: * @return the first result set entry to be returned
608: */
609: public int getStartPosition() {
610: return startPosition;
611: }
612:
613: /**
614: * Returns the desired result type of the GetFeature operation. Possible values are 'results' and 'hits'.
615: *
616: * @return the desired result type
617: */
618: public RESULT_TYPE getResultType() {
619: return this .resultType;
620: }
621:
622: /**
623: * The optional traverseXLinkDepth attribute indicates the depth to which nested property XLink linking element
624: * locator attribute (href) XLinks in all properties of the selected feature(s) are traversed and resolved if
625: * possible. A value of "1" indicates that one linking element locator attribute (href) XLink will be traversed and
626: * the referenced element returned if possible, but nested property XLink linking element locator attribute (href)
627: * XLinks in the returned element are not traversed. A value of "*" indicates that all nested property XLink linking
628: * element locator attribute (href) XLinks will be traversed and the referenced elements returned if possible. The
629: * range of valid values for this attribute consists of positive integers plus "*".
630: *
631: * @return the depth to which nested property XLinks are traversed and resolved
632: */
633: public int getTraverseXLinkDepth() {
634: return traverseXLinkDepth;
635: }
636:
637: /**
638: * The traverseXLinkExpiry attribute is specified in minutes. It indicates how long a Web Feature Service should
639: * wait to receive a response to a nested GetGmlObject request. If no traverseXLinkExpiry attribute is present for a
640: * GetGmlObject request, the WFS wait time is implementation dependent.
641: *
642: * @return how long to wait to receive a response to a nested GetGmlObject request
643: */
644: public int getTraverseXLinkExpiry() {
645: return traverseXLinkExpiry;
646: }
647:
648: @Override
649: public String toString() {
650: String ret = null;
651: ret = "WFSGetFeatureRequest: { \n ";
652: ret += "outputFormat = " + outputFormat + "\n";
653: ret += ("handle = " + getHandle() + "\n");
654: ret += ("query = " + queries + "\n");
655: ret += "}\n";
656: return ret;
657: }
658: }
|