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 }