001 //$HeadURL: http://svn.wald.intevation.org/svn/deegree/base/trunk/src/org/deegree/ogcwebservices/wfs/operation/Query.java $
002 /*----------------------------------------------------------------------------
003 This file is part of deegree, http://deegree.org/
004 Copyright (C) 2001-2009 by:
005 Department of Geography, University of Bonn
006 and
007 lat/lon GmbH
008
009 This library is free software; you can redistribute it and/or modify it under
010 the terms of the GNU Lesser General Public License as published by the Free
011 Software Foundation; either version 2.1 of the License, or (at your option)
012 any later version.
013 This library is distributed in the hope that it will be useful, but WITHOUT
014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016 details.
017 You should have received a copy of the GNU Lesser General Public License
018 along with this library; if not, write to the Free Software Foundation, Inc.,
019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020
021 Contact information:
022
023 lat/lon GmbH
024 Aennchenstr. 19, 53177 Bonn
025 Germany
026 http://lat-lon.de/
027
028 Department of Geography, University of Bonn
029 Prof. Dr. Klaus Greve
030 Postfach 1147, 53001 Bonn
031 Germany
032 http://www.geographie.uni-bonn.de/deegree/
033
034 e-mail: info@deegree.org
035 ----------------------------------------------------------------------------*/
036 package org.deegree.ogcwebservices.wfs.operation;
037
038 import java.net.URI;
039 import java.util.Arrays;
040
041 import org.deegree.datatypes.QualifiedName;
042 import org.deegree.framework.log.ILogger;
043 import org.deegree.framework.log.LoggerFactory;
044 import org.deegree.framework.xml.XMLParsingException;
045 import org.deegree.model.filterencoding.ComparisonOperation;
046 import org.deegree.model.filterencoding.ComplexFilter;
047 import org.deegree.model.filterencoding.Filter;
048 import org.deegree.model.filterencoding.Function;
049 import org.deegree.model.filterencoding.LogicalOperation;
050 import org.deegree.model.filterencoding.Operation;
051 import org.deegree.model.filterencoding.PropertyIsBetweenOperation;
052 import org.deegree.model.filterencoding.PropertyIsCOMPOperation;
053 import org.deegree.model.filterencoding.PropertyIsInstanceOfOperation;
054 import org.deegree.model.filterencoding.PropertyIsLikeOperation;
055 import org.deegree.model.filterencoding.PropertyIsNullOperation;
056 import org.deegree.model.filterencoding.PropertyName;
057 import org.deegree.model.filterencoding.SpatialOperation;
058 import org.deegree.ogcbase.ElementStep;
059 import org.deegree.ogcbase.PropertyPath;
060 import org.deegree.ogcbase.PropertyPathStep;
061 import org.deegree.ogcbase.SortProperty;
062 import org.deegree.ogcwebservices.InvalidParameterValueException;
063 import org.deegree.ogcwebservices.wfs.WFService;
064 import org.deegree.ogcwebservices.wfs.operation.GetFeature.RESULT_TYPE;
065 import org.deegree.ogcwebservices.wfs.operation.GetFeatureDocument.BBoxTest;
066 import org.w3c.dom.Element;
067
068 /**
069 * Represents a <code>Query</code> operation as a part of a {@link GetFeature} request.
070 *
071 * Each individual query packaged in a {@link GetFeature} request is defined using the query value. The query value
072 * defines which feature type to query, what properties to retrieve and what constraints (spatial and non-spatial) to
073 * apply to those properties.
074 * <p>
075 * The mandatory <code>typeName</code> attribute is used to indicate the name of one or more feature type instances or
076 * class instances to be queried. Its value is a list of namespace-qualified names (XML Schema type QName, e.g.
077 * myns:School) whose value must match one of the feature types advertised in the Capabilities document of the WFS.
078 * Specifying more than one typename indicates that a join operation is being performed. All the names in the typeName
079 * list must be valid types that belong to this query's feature content as defined by the GML Application Schema.
080 * Optionally, individual feature type names in the typeName list may be aliased using the format QName=Alias. The
081 * following is an example typeName value that indicates that a join operation is to be performed and includes aliases:
082 * <BR>
083 * <code>typeName="ns1:InwaterA_1m=A,ns1:InwaterA_1m=B,ns2:CoastL_1M=C"</code><BR>
084 * This example encodes a join between three feature types aliased as A, B and C. The join between feature type A and B
085 * is a self-join.
086 * </p>
087 *
088 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a>
089 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
090 * @author last edited by: $Author: apoth $
091 *
092 * @version $Revision: 29966 $, $Date: 2011-03-09 15:19:04 +0100 (Wed, 09 Mar 2011) $
093 */
094 public class Query {
095
096 private static ILogger LOG = LoggerFactory.getLogger( Query.class );
097
098 private String handle;
099
100 private QualifiedName[] typeNames;
101
102 private String[] aliases;
103
104 private String featureVersion;
105
106 private String srsName;
107
108 private PropertyPath[] propertyNames;
109
110 private Function[] functions;
111
112 private Filter filter;
113
114 private SortProperty[] sortProperties;
115
116 // deegree specific extension ("inherited" from GetFeature container)
117 private RESULT_TYPE resultType;
118
119 // deegree specific extension ("inherited" from GetFeature container)
120 private int maxFeatures = -1;
121
122 // deegree specific extension ("inherited" from GetFeature container)
123 private int startPosition = 1;
124
125 private BBoxTest test;
126
127 /**
128 * @param propertyNames
129 * @param functions
130 * @param sortProperties
131 * @param handle
132 * @param featureVersion
133 * @param typeNames
134 * @param aliases
135 * @param srsName
136 * @param filter
137 * @param resultType
138 * @param maxFeatures
139 * @param startPosition
140 * @param test
141 */
142 public Query( PropertyPath[] propertyNames, Function[] functions, SortProperty[] sortProperties, String handle,
143 String featureVersion, QualifiedName[] typeNames, String[] aliases, String srsName, Filter filter,
144 RESULT_TYPE resultType, int maxFeatures, int startPosition, BBoxTest test ) {
145 this( propertyNames, functions, sortProperties, handle, featureVersion, typeNames, aliases, srsName, filter,
146 resultType, maxFeatures, startPosition );
147
148 this.test = test;
149 }
150
151 /**
152 * Creates a new <code>Query</code> instance.
153 *
154 * @param propertyNames
155 * names of the requested properties, may be null or empty
156 * @param functions
157 * names of the requested functions, may be null or empty
158 * @param sortProperties
159 * sort criteria, may be null or empty
160 * @param handle
161 * client-generated identifier for the query, may be null
162 * @param featureVersion
163 * version of the feature instances to fetched, may be null
164 * @param typeNames
165 * list of requested feature types
166 * @param aliases
167 * list of aliases for the feature types, must either be null or have the same length as the typeNames
168 * array
169 * @param srsName
170 * name of the spatial reference system
171 * @param filter
172 * spatial and none-spatial constraints
173 * @param resultType
174 * deegree specific extension ("inherited" from GetFeature container)
175 * @param maxFeatures
176 * deegree specific extension ("inherited" from GetFeature container)
177 * @param startPosition
178 * deegree specific extension ("inherited" from GetFeature container)
179 */
180 public Query( PropertyPath[] propertyNames, Function[] functions, SortProperty[] sortProperties, String handle,
181 String featureVersion, QualifiedName[] typeNames, String[] aliases, String srsName, Filter filter,
182 RESULT_TYPE resultType, int maxFeatures, int startPosition ) {
183 if ( propertyNames == null ) {
184 this.propertyNames = new PropertyPath[0];
185 // this.propertyNames[0] = new PropertyPath( typeNames[0] );
186 } else {
187 this.propertyNames = propertyNames;
188 }
189 this.functions = functions;
190 this.sortProperties = sortProperties;
191 this.handle = handle;
192 this.featureVersion = featureVersion;
193 this.typeNames = typeNames;
194 this.aliases = aliases;
195 assert aliases == null || aliases.length == typeNames.length;
196 if ( LOG.isDebug() ) {
197 LOG.logDebug( "The query contains following aliases: " + Arrays.toString( aliases ) );
198 }
199 this.srsName = srsName;
200 this.filter = filter;
201 this.resultType = resultType;
202 this.maxFeatures = maxFeatures;
203 this.startPosition = startPosition;
204 }
205
206
207
208 /**
209 * Creates a new <code>Query</code> instance.
210 *
211 * @param propertyNames
212 * names of the requested properties, may be null or empty
213 * @param functions
214 * names of the requested functions, may be null or empty
215 * @param sortProperties
216 * sort criteria, may be null or empty
217 * @param handle
218 * client-generated identifier for the query, may be null
219 * @param featureVersion
220 * version of the feature instances to fetched, may be null
221 * @param typeNames
222 * list of requested feature types. if more than one feature types is set a JOIN will be created (not yet
223 * supported)
224 * @param aliases
225 * list of aliases for the feature types, must either be null or have the same length as the typeNames
226 * array
227 * @param srsName
228 * name of the spatial reference system
229 * @param filter
230 * spatial and none-spatial constraints
231 * @param resultType
232 * deegree specific extension ("inherited" from GetFeature container)
233 * @param maxFeatures
234 * deegree specific extension ("inherited" from GetFeature container)
235 * @param startPosition
236 * deegree specific extension ("inherited" from GetFeature container)
237 * @return new <code>Query</code> instance
238 */
239 public static Query create( PropertyPath[] propertyNames, Function[] functions, SortProperty[] sortProperties,
240 String handle, String featureVersion, QualifiedName[] typeNames, String[] aliases,
241 String srsName, Filter filter, int maxFeatures, int startPosition,
242 RESULT_TYPE resultType ) {
243 return new Query( propertyNames, functions, sortProperties, handle, featureVersion, typeNames, aliases,
244 srsName, filter, resultType, maxFeatures, startPosition );
245 }
246
247 /**
248 * Creates a new simple <code>Query</code> instance that selects the whole feature type.
249 *
250 * @param typeName
251 * name of the feature to be queried
252 * @return new <code>Query</code> instance
253 */
254 public static Query create( QualifiedName typeName ) {
255 return new Query( null, null, null, null, null, new QualifiedName[] { typeName }, null, null, null,
256 RESULT_TYPE.RESULTS, -1, 0 );
257 }
258
259 /**
260 * Creates a new simple <code>Query</code> instance that selects the whole feature type.
261 *
262 * @param typeName
263 * name of the feature to be queried
264 * @param filter
265 * spatial and none-spatial constraints
266 * @return new <code>Query</code> instance
267 */
268 public static Query create( QualifiedName typeName, Filter filter ) {
269 return new Query( null, null, null, null, null, new QualifiedName[] { typeName }, null, null, filter,
270 RESULT_TYPE.RESULTS, -1, 0 );
271 }
272
273 /**
274 * Creates a <code>Query</code> instance from a document that contains the DOM representation of the request, using
275 * the 1.1.0 filter encoding.
276 * <p>
277 * Note that the following attributes from the surrounding element are also considered (if it present):
278 * <ul>
279 * <li>resultType</li>
280 * <li>maxFeatures</li>
281 * <li>startPosition</li>
282 * </ul>
283 *
284 * @param element
285 * @return corresponding <code>Query</code> instance
286 * @throws XMLParsingException
287 */
288 public static Query create( Element element )
289 throws XMLParsingException {
290 return create( element, false );
291 }
292
293 /**
294 * Creates a <code>Query</code> instance from a document that contains the DOM representation of the request.
295 * <p>
296 * Note that the following attributes from the surrounding element are also considered (if it present):
297 * <ul>
298 * <li>resultType</li>
299 * <li>maxFeatures</li>
300 * <li>startPosition</li>
301 * </ul>
302 *
303 * @param element
304 * @param useVersion_1_0_0
305 * if the filterencoding 1.0.0 rules should be applied.
306 * @return corresponding <code>Query</code> instance
307 * @throws XMLParsingException
308 */
309 public static Query create( Element element, boolean useVersion_1_0_0 )
310 throws XMLParsingException {
311
312 GetFeatureDocument doc = new GetFeatureDocument();
313 Query query = doc.parseQuery( element, useVersion_1_0_0 );
314 return query;
315 }
316
317 /**
318 * Returns the handle attribute.
319 * <p>
320 * The handle attribute is included to allow a client to associate a mnemonic name to the query. The purpose of the
321 * handle attribute is to provide an error handling mechanism for locating a statement that might fail.
322 *
323 * @return the handle attribute
324 */
325 public String getHandle() {
326 return this.handle;
327 }
328
329 /**
330 * Returns the names of the requested feature types.
331 *
332 * @return the names of the requested feature types
333 */
334 public QualifiedName[] getTypeNames() {
335 return this.typeNames;
336 }
337
338 /**
339 * Returns the aliases for the requested feature types.
340 * <p>
341 * The returned array is either null or has the same length as the array returned by {@link #getTypeNames()}.
342 *
343 * @see #getTypeNames()
344 * @return the aliases for the requested feature types, or null if no aliases are used
345 */
346 public String[] getAliases() {
347 return this.aliases;
348 }
349
350 /**
351 * Returns the srsName attribute.
352 *
353 * @return the srsName attribute
354 */
355 public String getSrsName() {
356 return this.srsName;
357 }
358
359 /**
360 * Sets the srsName attribute to given value.
361 *
362 * @param srsName
363 * name of the requested SRS
364 */
365 public void setSrsName( String srsName ) {
366 this.srsName = srsName;
367 }
368
369 /**
370 * @throws InvalidParameterValueException
371 */
372 public void performBBoxTest()
373 throws InvalidParameterValueException {
374 if ( test != null ) {
375 test.performTest();
376 }
377 }
378
379 /**
380 * Sets the test to null, thus enabling the gc.
381 */
382 public void deleteBBoxTest() {
383 test = null;
384 }
385
386 /**
387 * Returns the featureVersion attribute.
388 *
389 * The version attribute is included in order to accommodate systems that support feature versioning. A value of ALL
390 * indicates that all versions of a feature should be fetched. Otherwise an integer can be specified to return the n
391 * th version of a feature. The version numbers start at '1' which is the oldest version. If a version value larger
392 * than the largest version is specified then the latest version is return. The default action shall be for the
393 * query to return the latest version. Systems that do not support versioning can ignore the parameter and return
394 * the only version that they have.
395 *
396 * @return the featureVersion attribute
397 */
398 public String getFeatureVersion() {
399 return this.featureVersion;
400 }
401
402 /**
403 * Returns all requested properties.
404 *
405 * @return all requested properties
406 *
407 * @see #getFunctions()
408 */
409 public PropertyPath[] getPropertyNames() {
410 return this.propertyNames;
411 }
412
413 /**
414 * Beside property names a query may contains 0 to n functions modifying the values of one or more original
415 * properties. E.g. instead of area and population the density of a country can be requested by using a function
416 * instead:
417 *
418 * <pre>
419 * <ogc:Div>
420 * <ogc:PropertyName>population</ogc:PropertyName>
421 * <ogc:PropertyName>area</ogc:PropertyName>
422 * </ogc:Div>
423 * </pre>
424 *
425 * <p>
426 * If no functions and no property names are specified all properties should be fetched.
427 * </p>
428 *
429 * @return requested functions
430 *
431 * @see #getPropertyNames()
432 */
433 public Function[] getFunctions() {
434 return this.functions;
435 }
436
437 /**
438 * Returns the filter that limits the query.
439 *
440 * @return the filter that limits the query
441 */
442 public Filter getFilter() {
443 return this.filter;
444 }
445
446 /**
447 * Returns the sort criteria for the result.
448 *
449 * @return the sort criteria for the result
450 */
451 public SortProperty[] getSortProperties() {
452 return this.sortProperties;
453 }
454
455 /**
456 * Returns the value of the resultType attribute ("inherited" from the GetFeature container).
457 *
458 * @return the value of the resultType attribute
459 */
460 public RESULT_TYPE getResultType() {
461 return this.resultType;
462 }
463
464 /**
465 * Returns the value of the maxFeatures attribute ("inherited" from the GetFeature container).
466 *
467 * The optional maxFeatures attribute can be used to limit the number of features that a GetFeature request
468 * retrieves. Once the maxFeatures limit is reached, the result set is truncated at that point. If not limit is set
469 * -1 will be returned
470 *
471 * @return the value of the maxFeatures attribute
472 */
473 public int getMaxFeatures() {
474 return this.maxFeatures;
475 }
476
477 /**
478 * @param maxFeatures
479 */
480 public void setMaxFeatures( int maxFeatures ) {
481 this.maxFeatures = maxFeatures;
482 }
483
484 /**
485 * Returns the value of the startPosition attribute ("inherited" from the GetFeature container).
486 * <p>
487 * The startPosition parameter identifies the first result set entry to be returned. If no startPosition is set
488 * explicitly, 1 will be returned.
489 *
490 * @return the value of the startPosition attribute, 1 if undefined
491 */
492 public int getStartPosition() {
493 return this.startPosition;
494 }
495
496 /**
497 * @see #getStartPosition()
498 * @param startPosition
499 */
500 public void setStartPosition( int startPosition ) {
501 this.startPosition = startPosition;
502 }
503
504 /**
505 * Adds missing namespaces in the names of requested feature types.
506 * <p>
507 * If the {@link QualifiedName} of a requested type has a null namespace, the first qualified feature type name of
508 * the given {@link WFService} with the same local name is used instead.
509 * <p>
510 * Note: The method changes this request part (the feature type names) and should only be called by the
511 * <code>WFSHandler</code> class.
512 *
513 * @param wfs
514 * {@link WFService} instance that is used for the lookup of proper (qualified) feature type names
515 */
516 public void guessMissingTypeNameNamespace( WFService wfs ) {
517 for ( int i = 0; i < typeNames.length; i++ ) {
518 QualifiedName typeName = typeNames[i];
519 if ( typeName.getNamespace() == null ) {
520 if ( typeName.getLocalName().equals( typeName.getLocalName() ) ) {
521 LOG.logWarning( "Requested feature type name has no namespace information. Guessing namespace for feature type '"
522 + typeName.getLocalName() + "' (quirks lookup mode)." );
523 for ( QualifiedName ftName : wfs.getMappedFeatureTypes().keySet() ) {
524 if ( ftName.getLocalName().equals( typeName.getLocalName() ) ) {
525 LOG.logWarning( "Using feature type '" + ftName + "'." );
526 typeNames[i] = ftName;
527 break;
528 }
529 }
530 }
531 }
532 }
533 }
534
535 /**
536 * Adds missing namespaces to requested feature type names, property names, filter properties and sort properties.
537 * <p>
538 * Note: The method changes the request and should only be called by the <code>WFSHandler</code> class.
539 *
540 * @param wfs
541 * {@link WFService} instance that is used for the lookup of proper (qualified) feature and property
542 * names
543 */
544 public void guessAllMissingNamespaces( WFService wfs ) {
545 guessMissingTypeNameNamespace( wfs );
546
547 URI defaultNamespace = typeNames[0].getNamespace();
548 augmentFilterWithNamespace( defaultNamespace );
549 augmentSortPropertiesWithNamespace( defaultNamespace );
550 augmentQueriedProperties( defaultNamespace );
551 }
552
553 private void augmentQueriedProperties( URI defaultNamespace ) {
554 if ( propertyNames != null ) {
555 for ( PropertyPath propertyPath : propertyNames ) {
556 augmentPropertyPath( propertyPath, defaultNamespace );
557 }
558 }
559 }
560
561 private void augmentSortPropertiesWithNamespace( URI defaultNamespace ) {
562 if ( sortProperties != null ) {
563 for ( SortProperty sortCriterion : sortProperties ) {
564 augmentPropertyPath( sortCriterion.getSortProperty(), defaultNamespace );
565 }
566 }
567 }
568
569 private void augmentFilterWithNamespace( URI defaultNamespace ) {
570 if ( filter != null ) {
571 if ( filter instanceof ComplexFilter ) {
572 Operation operation = ( (ComplexFilter) filter ).getOperation();
573 augmentFilterOperationWithNamespace( operation, defaultNamespace );
574 }
575 }
576 }
577
578 private void augmentFilterOperationWithNamespace( Operation operation, URI defaultNamespace ) {
579 if ( operation instanceof ComparisonOperation ) {
580 if ( operation instanceof PropertyIsBetweenOperation ) {
581 PropertyIsBetweenOperation propOperation = (PropertyIsBetweenOperation) operation;
582 augmentPropertyPath( propOperation.getPropertyName().getValue(), defaultNamespace );
583 if ( propOperation.getLowerBoundary() instanceof PropertyName ) {
584 augmentPropertyPath( ( (PropertyName) propOperation.getLowerBoundary() ).getValue(),
585 defaultNamespace );
586 }
587 if ( propOperation.getUpperBoundary() instanceof PropertyName ) {
588 augmentPropertyPath( ( (PropertyName) propOperation.getUpperBoundary() ).getValue(),
589 defaultNamespace );
590 }
591 } else if ( operation instanceof PropertyIsCOMPOperation ) {
592 PropertyIsCOMPOperation propOperation = (PropertyIsCOMPOperation) operation;
593 if ( propOperation.getFirstExpression() instanceof PropertyName ) {
594 augmentPropertyPath( ( (PropertyName) propOperation.getFirstExpression() ).getValue(),
595 defaultNamespace );
596 }
597 if ( propOperation.getSecondExpression() instanceof PropertyName ) {
598 augmentPropertyPath( ( (PropertyName) propOperation.getSecondExpression() ).getValue(),
599 defaultNamespace );
600 }
601 } else if ( operation instanceof PropertyIsInstanceOfOperation ) {
602 PropertyIsInstanceOfOperation propOperation = (PropertyIsInstanceOfOperation) operation;
603 augmentPropertyPath( propOperation.getPropertyName().getValue(), defaultNamespace );
604 } else if ( operation instanceof PropertyIsLikeOperation ) {
605 PropertyIsLikeOperation propOperation = (PropertyIsLikeOperation) operation;
606 augmentPropertyPath( propOperation.getPropertyName().getValue(), defaultNamespace );
607 } else if ( operation instanceof PropertyIsNullOperation ) {
608 PropertyIsNullOperation propOperation = (PropertyIsNullOperation) operation;
609 augmentPropertyPath( propOperation.getPropertyName().getValue(), defaultNamespace );
610 }
611 } else if ( operation instanceof LogicalOperation ) {
612 LogicalOperation logicalOperation = (LogicalOperation) operation;
613 for ( Operation argument : logicalOperation.getArguments() ) {
614 augmentFilterOperationWithNamespace( argument, defaultNamespace );
615 }
616 } else if ( operation instanceof SpatialOperation ) {
617 SpatialOperation spatialOperation = (SpatialOperation) operation;
618 PropertyName propertyName = spatialOperation.getPropertyName();
619 if ( propertyName != null ) {
620 augmentPropertyPath( propertyName.getValue(), defaultNamespace );
621 }
622 }
623
624 }
625
626 private void augmentPropertyPath( PropertyPath propertyPath, URI defaultNamespace ) {
627 for ( PropertyPathStep step : propertyPath.getAllSteps() ) {
628 QualifiedName name = step.getPropertyName();
629 if ( name.getNamespace() == null && step instanceof ElementStep ) {
630 LOG.logWarning( "Augmenting missing namespace: '" + name + "' -> '" + defaultNamespace + "'" );
631 step.setPropertyName( new QualifiedName( name.getPrefix(), name.getLocalName(), defaultNamespace ) );
632 }
633 }
634 }
635
636 /**
637 * Returns a string representation of the object.
638 *
639 * @return a string representation of the object
640 */
641 @Override
642 public String toString() {
643 String ret = null;
644 ret = "propertyNames = " + propertyNames + "\n";
645 ret += ( "handle = " + handle + "\n" );
646 ret += ( "version = " + featureVersion + "\n" );
647 ret += ( "typeName = " + typeNames + "\n" );
648 ret += ( "filter = " + filter + "\n" );
649 return ret;
650 }
651 }