001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/ogcwebservices/wfs/operation/GetFeature.java $
002 /*---------------- FILE HEADER ------------------------------------------
003
004 This file is part of deegree.
005 Copyright (C) 2001-2006 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.Filter;
058 import org.deegree.ogcbase.PropertyPath;
059 import org.deegree.ogcbase.PropertyPathFactory;
060 import org.deegree.ogcbase.PropertyPathStep;
061 import org.deegree.ogcbase.SortProperty;
062 import org.deegree.ogcwebservices.InconsistentRequestException;
063 import org.deegree.ogcwebservices.InvalidParameterValueException;
064 import org.deegree.ogcwebservices.OGCWebServiceException;
065 import org.w3c.dom.Element;
066
067 /**
068 * Represents a <code>GetFeature</code> request to a web feature service.
069 * <p>
070 * The GetFeature operation allows the retrieval of features from a web feature service. A
071 * GetFeature request is processed by a WFS and when the value of the outputFormat attribute is set
072 * to text/gml; subtype=gml/3.1.1, a GML instance document, containing the result set, is returned
073 * to the client.
074 *
075 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a>
076 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
077 * @author last edited by: $Author: rbezema $
078 *
079 * @version $Revision: 7923 $, $Date: 2007-08-06 14:34:52 +0200 (Mo, 06 Aug 2007) $
080 */
081 public class GetFeature extends AbstractWFSRequest {
082
083 private static final ILogger LOG = LoggerFactory.getLogger( GetFeature.class );
084
085 private static final long serialVersionUID = 8885456550385433051L;
086
087 /** Serialized java object format (deegree specific extension) * */
088 public static final String FORMAT_FEATURECOLLECTION = "FEATURECOLLECTION";
089
090 /**
091 * Known result types.
092 */
093 public static enum RESULT_TYPE {
094
095 /** A full response should be generated. */
096 RESULTS,
097
098 /** Only a count of the number of features should be returned. */
099 HITS
100 }
101
102 private RESULT_TYPE resultType = RESULT_TYPE.RESULTS;
103
104 private String outputFormat;
105
106 private int maxFeatures;
107
108 private int traverseXLinkDepth;
109
110 private int traverseXLinkExpiry;
111
112 private List<Query> queries;
113
114 // deegree specific extension, default: 1 (start at first feature)
115 private int startPosition;
116
117 /**
118 * Creates a new <code>GetFeature</code> instance.
119 *
120 * @param version
121 * request version
122 * @param id
123 * id of the request
124 * @param handle
125 * @param resultType
126 * desired result type (results | hits)
127 * @param outputFormat
128 * requested result format
129 * @param maxFeatures
130 * @param startPosition
131 * deegree specific parameter defining where to start considering features
132 * @param traverseXLinkDepth
133 * @param traverseXLinkExpiry
134 * @param queries
135 * @param vendorSpecificParam
136 */
137 GetFeature( String version, String id, String handle, RESULT_TYPE resultType, String outputFormat, int maxFeatures,
138 int startPosition, int traverseXLinkDepth, int traverseXLinkExpiry, Query[] queries,
139 Map<String, String> vendorSpecificParam ) {
140 super( version, id, handle, vendorSpecificParam );
141 this.setQueries( queries );
142 this.outputFormat = outputFormat;
143 this.maxFeatures = maxFeatures;
144 this.startPosition = startPosition;
145 this.resultType = resultType;
146 this.traverseXLinkDepth = traverseXLinkDepth;
147 this.traverseXLinkExpiry = traverseXLinkExpiry;
148 }
149
150 /**
151 * Creates a new <code>GetFeature</code> instance from the given parameters.
152 *
153 * @param version
154 * request version
155 * @param id
156 * id of the request
157 * @param resultType
158 * desired result type (results | hits)
159 * @param outputFormat
160 * requested result format
161 * @param handle
162 * @param maxFeatures
163 * default = -1 (all features)
164 * @param startPosition
165 * default = 0 (starting at the first feature)
166 * @param traverseXLinkDepth
167 * @param traverseXLinkExpiry
168 * @param queries
169 * a set of Query objects that describes the query to perform
170 * @return new <code>GetFeature</code> request
171 */
172 public static GetFeature create( String version, String id, RESULT_TYPE resultType, String outputFormat,
173 String handle, int maxFeatures, int startPosition, int traverseXLinkDepth,
174 int traverseXLinkExpiry, Query[] queries ) {
175 return new GetFeature( version, id, handle, resultType, outputFormat, maxFeatures, startPosition,
176 traverseXLinkDepth, traverseXLinkExpiry, queries, null );
177 }
178
179 /**
180 * Creates a new <code>GetFeature</code> instance from a document that contains the DOM
181 * representation of the request.
182 *
183 * @param id
184 * of the request
185 * @param root
186 * element that contains the DOM representation of the request
187 * @return new <code>GetFeature</code> request
188 * @throws OGCWebServiceException
189 */
190 public static GetFeature create( String id, Element root )
191 throws OGCWebServiceException {
192 GetFeatureDocument doc = new GetFeatureDocument();
193 doc.setRootElement( root );
194 GetFeature request;
195 try {
196 request = doc.parse( id );
197 } catch ( Exception e ) {
198 LOG.logError( e.getMessage(), e );
199 throw new OGCWebServiceException( "GetFeature", e.getMessage() );
200 }
201 return request;
202 }
203
204 /**
205 * Creates a new <code>GetFeature</code> instance from the given key-value pair encoded
206 * request.
207 *
208 * @param id
209 * request identifier
210 * @param request
211 * @return new <code>GetFeature</code> request
212 * @throws InvalidParameterValueException
213 * @throws InconsistentRequestException
214 */
215 public static GetFeature create( String id, String request )
216 throws InconsistentRequestException, InvalidParameterValueException {
217 Map<String, String> map = KVP2Map.toMap( request );
218 map.put( "ID", id );
219 return create( map );
220 }
221
222 /**
223 * Creates a new <code>GetFeature</code> request from the given map.
224 *
225 * @param kvp
226 * key-value pairs, keys have to be uppercase
227 * @return new <code>GetFeature</code> request
228 * @throws InconsistentRequestException
229 * @throws InvalidParameterValueException
230 */
231 public static GetFeature create( Map<String, String> kvp )
232 throws InconsistentRequestException, InvalidParameterValueException {
233
234 // SERVICE
235 checkServiceParameter( kvp );
236
237 // ID (deegree specific)
238 String id = kvp.get( "ID" );
239
240 // VERSION
241 String version = checkVersionParameter( kvp );
242
243 // OUTPUTFORMAT
244 String outputFormat = getParam( "OUTPUTFORMAT", kvp, FORMAT_GML3 );
245
246 // RESULTTYPE
247 RESULT_TYPE resultType = RESULT_TYPE.RESULTS;
248 String resultTypeString = kvp.get( "RESULTTYPE" );
249 if ( "hits".equals( resultTypeString ) ) {
250 resultType = RESULT_TYPE.HITS;
251 }
252
253 // FEATUREVERSION
254 String featureVersion = kvp.get( "FEATUREVERSION" );
255
256 // MAXFEATURES
257 String maxFeaturesString = kvp.get( "MAXFEATURES" );
258 // -1: fetch all features
259 int maxFeatures = -1;
260 if ( maxFeaturesString != null ) {
261 try {
262 maxFeatures = Integer.parseInt( maxFeaturesString );
263 if ( maxFeatures < 1 ) {
264 throw new NumberFormatException();
265 }
266 } catch ( NumberFormatException e ) {
267 LOG.logError( e.getMessage(), e );
268 String msg = Messages.getMessage( "WFS_PARAMETER_INVALID_INT", maxFeaturesString, "MAXFEATURES" );
269 throw new InvalidParameterValueException( msg );
270 }
271 }
272
273 // STARTPOSITION (deegree specific)
274 String startPosString = getParam( "STARTPOSITION", kvp, "1" );
275 int startPosition = 1;
276 try {
277 startPosition = Integer.parseInt( startPosString );
278 if ( startPosition < 1 ) {
279 throw new NumberFormatException();
280 }
281 } catch ( NumberFormatException e ) {
282 LOG.logError( e.getMessage(), e );
283 String msg = Messages.getMessage( "WFS_PARAMETER_INVALID_INT", startPosString, "STARTPOSITION" );
284 throw new InvalidParameterValueException( msg );
285 }
286
287 // SRSNAME
288 String srsName = kvp.get( "SRSNAME" );
289
290 // TYPENAME
291 QualifiedName[] typeNames = extractTypeNames( kvp );
292 if ( typeNames == null ) {
293 // no TYPENAME parameter -> FEATUREID must be present
294 String featureId = kvp.get( "FEATUREID" );
295 if ( featureId != null ) {
296 String msg = Messages.getMessage( "WFS_FEATUREID_PARAM_UNSUPPORTED" );
297 throw new InvalidParameterValueException( msg );
298 }
299 String msg = Messages.getMessage( "WFS_TYPENAME+FID_PARAMS_MISSING" );
300 throw new InvalidParameterValueException( msg );
301 }
302
303 // BBOX
304 Filter bboxFilter = extractBBOXFilter( kvp );
305
306 // FILTER (prequisite: TYPENAME)
307 Map<QualifiedName, Filter> filterMap = extractFilters( kvp, typeNames );
308 if ( bboxFilter != null && filterMap.size() > 0 ) {
309 String msg = Messages.getMessage( "WFS_BBOX_FILTER_INVALID" );
310 throw new InvalidParameterValueException( msg );
311 }
312
313 // PROPERTYNAME
314 Map<QualifiedName, PropertyPath[]> propertyNameMap = extractPropNames( kvp, typeNames );
315
316 // SORTBY
317 SortProperty[] sortProperties = null;
318
319 // TRAVERSEXLINKDEPTH
320 int traverseXLinkDepth = -1;
321
322 // TRAVERSEXLINKEXPIRY
323 int traverseXLinkExpiry = -1;
324
325 // build a Query instance for each requested feature type (later also for each featureid...)
326 Query[] queries = new Query[typeNames.length];
327 for ( int i = 0; i < queries.length; i++ ) {
328 QualifiedName ftName = typeNames[i];
329 PropertyPath[] properties = propertyNameMap.get( ftName );
330 Filter filter;
331 if ( bboxFilter != null ) {
332 filter = bboxFilter;
333 } else {
334 filter = filterMap.get( ftName );
335 }
336 QualifiedName[] ftNames = new QualifiedName[] { ftName };
337 queries[i] = new Query( properties, null, sortProperties, null, featureVersion, ftNames, null, srsName,
338 filter, resultType, maxFeatures, startPosition );
339 }
340
341 // build a GetFeature request that contains all queries
342 GetFeature request = new GetFeature( version, id, null, resultType, outputFormat, maxFeatures, startPosition,
343 traverseXLinkDepth, traverseXLinkExpiry, queries, kvp );
344 return request;
345 }
346
347 /**
348 * Extracts the PROPERTYNAME parameter and assigns them to the requested type names.
349 *
350 * @param kvp
351 * @param typeNames
352 * @return map with the assignments of type names to property names
353 * @throws InvalidParameterValueException
354 */
355 protected static Map<QualifiedName, PropertyPath[]> extractPropNames( Map<String, String> kvp,
356 QualifiedName[] typeNames )
357 throws InvalidParameterValueException {
358 Map<QualifiedName, PropertyPath[]> propMap = new HashMap<QualifiedName, PropertyPath[]>();
359 String propNameString = kvp.get( "PROPERTYNAME" );
360 if ( propNameString != null ) {
361 String[] propNameLists = propNameString.split( "\\)" );
362 if ( propNameLists.length != typeNames.length ) {
363 String msg = Messages.getMessage( "WFS_PROPNAME_PARAM_WRONG_COUNT", Integer.toString( propNameLists.length ),
364 Integer.toString( typeNames.length ) );
365 throw new InvalidParameterValueException( msg );
366 }
367 NamespaceContext nsContext = extractNamespaceParameter( kvp );
368 for ( int i = 0; i < propNameLists.length; i++ ) {
369 String propNameList = propNameLists[i];
370 if ( propNameList.startsWith( "(" ) ) {
371 propNameList = propNameList.substring( 1 );
372 }
373 String[] propNames = propNameList.split( "," );
374 PropertyPath[] paths = new PropertyPath[propNames.length];
375 for ( int j = 0; j < propNames.length; j++ ) {
376 PropertyPath path = transformToPropertyPath( propNames[j], nsContext );
377 paths[j] = ( path );
378 }
379 propMap.put( typeNames[i], paths );
380 }
381 }
382 return propMap;
383 }
384
385 /**
386 * Transforms the given property name to a (qualified) <code>PropertyPath</code> object by
387 * using the specified namespace bindings.
388 *
389 * @param propName
390 * @param nsContext
391 * @return (qualified) <code>PropertyPath</code> object
392 * @throws InvalidParameterValueException
393 */
394 private static PropertyPath transformToPropertyPath( String propName, NamespaceContext nsContext )
395 throws InvalidParameterValueException {
396 String[] steps = propName.split( "/" );
397 List<PropertyPathStep> propertyPathSteps = new ArrayList<PropertyPathStep>( steps.length );
398
399 for ( int i = 0; i < steps.length; i++ ) {
400 PropertyPathStep propertyStep = null;
401 QualifiedName propertyName = null;
402 String step = steps[i];
403 boolean isAttribute = false;
404 boolean isIndexed = false;
405 int selectedIndex = -1;
406
407 // check if step begins with '@' -> must be the final step then
408 if ( step.startsWith( "@" ) ) {
409 if ( i != steps.length - 1 ) {
410 String msg = "PropertyName '" + propName + "' is illegal: the attribute specifier may only "
411 + "be used for the final step.";
412 throw new InvalidParameterValueException( msg );
413 }
414 step = step.substring( 1 );
415 isAttribute = true;
416 }
417
418 // check if the step ends with brackets ([...])
419 if ( step.endsWith( "]" ) ) {
420 if ( isAttribute ) {
421 String msg = "PropertyName '" + propName
422 + "' is illegal: if the attribute specifier ('@') is used, "
423 + "index selection ('[...']) is not possible.";
424 throw new InvalidParameterValueException( msg );
425 }
426 int bracketPos = step.indexOf( '[' );
427 if ( bracketPos < 0 ) {
428 String msg = "PropertyName '" + propName + "' is illegal. No opening brackets found for step '"
429 + step + "'.";
430 throw new InvalidParameterValueException( msg );
431 }
432 try {
433 selectedIndex = Integer.parseInt( step.substring( bracketPos + 1, step.length() - 1 ) );
434 } catch ( NumberFormatException e ) {
435 LOG.logError( e.getMessage(), e );
436 String msg = "PropertyName '" + propName + "' is illegal. Specified index '"
437 + step.substring( bracketPos + 1, step.length() - 1 ) + "' is not a number.";
438 throw new InvalidParameterValueException( msg );
439 }
440 step = step.substring( 0, bracketPos );
441 isIndexed = true;
442 }
443
444 // determine namespace prefix and binding (if any)
445 int colonPos = step.indexOf( ':' );
446 String prefix = "";
447 String localName = step;
448 if ( colonPos > 0 ) {
449 prefix = step.substring( 0, colonPos );
450 localName = step.substring( colonPos + 1 );
451 }
452 URI nsURI = nsContext.getURI( prefix );
453 if ( nsURI == null && prefix.length() > 0 ) {
454 String msg = "PropertyName '" + propName + "' uses an unbound namespace prefix: " + prefix;
455 throw new InvalidParameterValueException( msg );
456 }
457 propertyName = new QualifiedName( prefix, localName, nsURI );
458
459 if ( isAttribute ) {
460 propertyStep = PropertyPathFactory.createAttributePropertyPathStep( propertyName );
461 } else if ( isIndexed ) {
462 propertyStep = PropertyPathFactory.createPropertyPathStep( propertyName, selectedIndex );
463 } else {
464 propertyStep = PropertyPathFactory.createPropertyPathStep( propertyName );
465 }
466 propertyPathSteps.add( propertyStep );
467 }
468 return PropertyPathFactory.createPropertyPath( propertyPathSteps );
469 }
470
471 /**
472 * Returns the output format.
473 * <p>
474 * The outputFormat attribute defines the format to use to generate the result set. Vendor
475 * specific formats, declared in the capabilities document are possible. The WFS-specs implies
476 * GML as default output format.
477 *
478 * @return the output format.
479 */
480 public String getOutputFormat() {
481 return this.outputFormat;
482 }
483
484 /**
485 * The query defines which feature type to query, what properties to retrieve and what
486 * constraints (spatial and non-spatial) to apply to those properties.
487 * <p>
488 * only used for xml-coded requests
489 *
490 * @return contained queries
491 */
492 public Query[] getQuery() {
493 return queries.toArray( new Query[queries.size()] );
494 }
495
496 /**
497 * sets the <Query>
498 *
499 * @param query
500 */
501 public void setQueries( Query[] query ) {
502 this.queries = new ArrayList<Query>( query.length );
503 if ( query != null ) {
504 for ( int i = 0; i < query.length; i++ ) {
505 this.queries.add( query[i] );
506 }
507 }
508 }
509
510 /**
511 * The optional maxFeatures attribute can be used to limit the number of features that a
512 * GetFeature request retrieves. Once the maxFeatures limit is reached, the result set is
513 * truncated at that point. If not limit is set -1 will be returned.
514 *
515 * @return number of feature to fetch, -1 if no limit is set
516 */
517 public int getMaxFeatures() {
518 return maxFeatures;
519 }
520
521 /**
522 * @see #getMaxFeatures()
523 * @param max
524 */
525 public void setMaxFeatures( int max ) {
526 this.maxFeatures = max;
527 for ( int i = 0; i < queries.size(); i++ ) {
528 queries.get( i ).setMaxFeatures( max );
529 }
530 }
531
532 /**
533 * The startPosition parameter identifies the first result set entry to be returned specified
534 * the default is the first record. If not startposition is set 0 will be returned
535 *
536 * @return the first result set entry to be returned
537 */
538 public int getStartPosition() {
539 return startPosition;
540 }
541
542 /**
543 * Returns the desired result type of the GetFeature operation. Possible values are 'results'
544 * and 'hits'.
545 *
546 * @return the desired result type
547 */
548 public RESULT_TYPE getResultType() {
549 return this.resultType;
550 }
551
552 /**
553 * The optional traverseXLinkDepth attribute indicates the depth to which nested property XLink
554 * linking element locator attribute (href) XLinks in all properties of the selected feature(s)
555 * are traversed and resolved if possible. A value of "1" indicates that one linking element
556 * locator attribute (href) XLink will be traversed and the referenced element returned if
557 * possible, but nested property XLink linking element locator attribute (href) XLinks in the
558 * returned element are not traversed. A value of "*" indicates that all nested property XLink
559 * linking element locator attribute (href) XLinks will be traversed and the referenced elements
560 * returned if possible. The range of valid values for this attribute consists of positive
561 * integers plus "*".
562 *
563 * @return the depth to which nested property XLinks are traversed and resolved
564 */
565 public int getTraverseXLinkDepth() {
566 return traverseXLinkDepth;
567 }
568
569 /**
570 * The traverseXLinkExpiry attribute is specified in minutes. It indicates how long a Web
571 * Feature Service should wait to receive a response to a nested GetGmlObject request. If no
572 * traverseXLinkExpiry attribute is present for a GetGmlObject request, the WFS wait time is
573 * implementation dependent.
574 *
575 * @return how long to wait to receive a response to a nested GetGmlObject request
576 */
577 public int getTraverseXLinkExpiry() {
578 return traverseXLinkExpiry;
579 }
580
581 @Override
582 public String toString() {
583 String ret = null;
584 ret = "WFSGetFeatureRequest: { \n ";
585 ret += "outputFormat = " + outputFormat + "\n";
586 ret += ( "handle = " + getHandle() + "\n" );
587 ret += ( "query = " + queries + "\n" );
588 ret += "}\n";
589 return ret;
590 }
591 }