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