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         *  &lt;ogc:Div&gt;
420         *   &lt;ogc:PropertyName&gt;population&lt;/ogc:PropertyName&gt;
421         *   &lt;ogc:PropertyName&gt;area&lt;/ogc:PropertyName&gt;
422         *  &lt;/ogc:Div&gt;
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    }