001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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.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: aschmitz $
091     * 
092     * @version $Revision: 24605 $, $Date: 2010-05-27 13:24:00 +0200 (Do, 27 Mai 2010) $
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         * Creates a new <code>Query</code> instance.
208         * 
209         * @param propertyNames
210         *            names of the requested properties, may be null or empty
211         * @param functions
212         *            names of the requested functions, may be null or empty
213         * @param sortProperties
214         *            sort criteria, may be null or empty
215         * @param handle
216         *            client-generated identifier for the query, may be null
217         * @param featureVersion
218         *            version of the feature instances to fetched, may be null
219         * @param typeNames
220         *            list of requested feature types. if more than one feature types is set a JOIN will be created (not yet
221         *            supported)
222         * @param srsName
223         *            name of the spatial reference system
224         * @param filter
225         *            spatial and none-spatial constraints
226         * @param resultType
227         *            deegree specific extension ("inherited" from GetFeature container)
228         * @param maxFeatures
229         *            deegree specific extension ("inherited" from GetFeature container)
230         * @param startPosition
231         *            deegree specific extension ("inherited" from GetFeature container)
232         * @return new <code>Query</code> instance
233         * @deprecated use create(PropertyPath[], Function[], SortProperty[], String, String, QualifiedName[], String[],
234         *             String, Filter, int, int, RESULT_TYPE) instead
235         */
236        @Deprecated
237        public static Query create( PropertyPath[] propertyNames, Function[] functions, SortProperty[] sortProperties,
238                                    String handle, String featureVersion, QualifiedName[] typeNames, String srsName,
239                                    Filter filter, int maxFeatures, int startPosition, RESULT_TYPE resultType ) {
240            return new Query( propertyNames, functions, sortProperties, handle, featureVersion, typeNames, null, srsName,
241                              filter, resultType, maxFeatures, startPosition );
242        }
243    
244        /**
245         * Creates a new <code>Query</code> instance.
246         * 
247         * @param propertyNames
248         *            names of the requested properties, may be null or empty
249         * @param functions
250         *            names of the requested functions, may be null or empty
251         * @param sortProperties
252         *            sort criteria, may be null or empty
253         * @param handle
254         *            client-generated identifier for the query, may be null
255         * @param featureVersion
256         *            version of the feature instances to fetched, may be null
257         * @param typeNames
258         *            list of requested feature types. if more than one feature types is set a JOIN will be created (not yet
259         *            supported)
260         * @param aliases
261         *            list of aliases for the feature types, must either be null or have the same length as the typeNames
262         *            array
263         * @param srsName
264         *            name of the spatial reference system
265         * @param filter
266         *            spatial and none-spatial constraints
267         * @param resultType
268         *            deegree specific extension ("inherited" from GetFeature container)
269         * @param maxFeatures
270         *            deegree specific extension ("inherited" from GetFeature container)
271         * @param startPosition
272         *            deegree specific extension ("inherited" from GetFeature container)
273         * @return new <code>Query</code> instance
274         */
275        public static Query create( PropertyPath[] propertyNames, Function[] functions, SortProperty[] sortProperties,
276                                    String handle, String featureVersion, QualifiedName[] typeNames, String[] aliases,
277                                    String srsName, Filter filter, int maxFeatures, int startPosition,
278                                    RESULT_TYPE resultType ) {
279            return new Query( propertyNames, functions, sortProperties, handle, featureVersion, typeNames, aliases,
280                              srsName, filter, resultType, maxFeatures, startPosition );
281        }
282    
283        /**
284         * Creates a new simple <code>Query</code> instance that selects the whole feature type.
285         * 
286         * @param typeName
287         *            name of the feature to be queried
288         * @return new <code>Query</code> instance
289         */
290        public static Query create( QualifiedName typeName ) {
291            return new Query( null, null, null, null, null, new QualifiedName[] { typeName }, null, null, null,
292                              RESULT_TYPE.RESULTS, -1, 0 );
293        }
294    
295        /**
296         * Creates a new simple <code>Query</code> instance that selects the whole feature type.
297         * 
298         * @param typeName
299         *            name of the feature to be queried
300         * @param filter
301         *            spatial and none-spatial constraints
302         * @return new <code>Query</code> instance
303         */
304        public static Query create( QualifiedName typeName, Filter filter ) {
305            return new Query( null, null, null, null, null, new QualifiedName[] { typeName }, null, null, filter,
306                              RESULT_TYPE.RESULTS, -1, 0 );
307        }
308    
309        /**
310         * Creates a <code>Query</code> instance from a document that contains the DOM representation of the request, using
311         * the 1.1.0 filter encoding.
312         * <p>
313         * Note that the following attributes from the surrounding element are also considered (if it present):
314         * <ul>
315         * <li>resultType</li>
316         * <li>maxFeatures</li>
317         * <li>startPosition</li>
318         * </ul>
319         * 
320         * @param element
321         * @return corresponding <code>Query</code> instance
322         * @throws XMLParsingException
323         */
324        public static Query create( Element element )
325                                throws XMLParsingException {
326            return create( element, false );
327        }
328    
329        /**
330         * Creates a <code>Query</code> instance from a document that contains the DOM representation of the request.
331         * <p>
332         * Note that the following attributes from the surrounding element are also considered (if it present):
333         * <ul>
334         * <li>resultType</li>
335         * <li>maxFeatures</li>
336         * <li>startPosition</li>
337         * </ul>
338         * 
339         * @param element
340         * @param useVersion_1_0_0
341         *            if the filterencoding 1.0.0 rules should be applied.
342         * @return corresponding <code>Query</code> instance
343         * @throws XMLParsingException
344         */
345        public static Query create( Element element, boolean useVersion_1_0_0 )
346                                throws XMLParsingException {
347    
348            GetFeatureDocument doc = new GetFeatureDocument();
349            Query query = doc.parseQuery( element, useVersion_1_0_0 );
350            return query;
351        }
352    
353        /**
354         * Returns the handle attribute.
355         * <p>
356         * The handle attribute is included to allow a client to associate a mnemonic name to the query. The purpose of the
357         * handle attribute is to provide an error handling mechanism for locating a statement that might fail.
358         * 
359         * @return the handle attribute
360         */
361        public String getHandle() {
362            return this.handle;
363        }
364    
365        /**
366         * Returns the names of the requested feature types.
367         * 
368         * @return the names of the requested feature types
369         */
370        public QualifiedName[] getTypeNames() {
371            return this.typeNames;
372        }
373    
374        /**
375         * Returns the aliases for the requested feature types.
376         * <p>
377         * The returned array is either null or has the same length as the array returned by {@link #getTypeNames()}.
378         * 
379         * @see #getTypeNames()
380         * @return the aliases for the requested feature types, or null if no aliases are used
381         */
382        public String[] getAliases() {
383            return this.aliases;
384        }
385    
386        /**
387         * Returns the srsName attribute.
388         * 
389         * @return the srsName attribute
390         */
391        public String getSrsName() {
392            return this.srsName;
393        }
394    
395        /**
396         * Sets the srsName attribute to given value.
397         * 
398         * @param srsName
399         *            name of the requested SRS
400         */
401        public void setSrsName( String srsName ) {
402            this.srsName = srsName;
403        }
404    
405        /**
406         * @throws InvalidParameterValueException
407         */
408        public void performBBoxTest()
409                                throws InvalidParameterValueException {
410            if ( test != null ) {
411                test.performTest();
412            }
413        }
414    
415        /**
416         * Sets the test to null, thus enabling the gc.
417         */
418        public void deleteBBoxTest() {
419            test = null;
420        }
421    
422        /**
423         * Returns the featureVersion attribute.
424         * 
425         * The version attribute is included in order to accommodate systems that support feature versioning. A value of ALL
426         * indicates that all versions of a feature should be fetched. Otherwise an integer can be specified to return the n
427         * th version of a feature. The version numbers start at '1' which is the oldest version. If a version value larger
428         * than the largest version is specified then the latest version is return. The default action shall be for the
429         * query to return the latest version. Systems that do not support versioning can ignore the parameter and return
430         * the only version that they have.
431         * 
432         * @return the featureVersion attribute
433         */
434        public String getFeatureVersion() {
435            return this.featureVersion;
436        }
437    
438        /**
439         * Returns all requested properties.
440         * 
441         * @return all requested properties
442         * 
443         * @see #getFunctions()
444         */
445        public PropertyPath[] getPropertyNames() {
446            return this.propertyNames;
447        }
448    
449        /**
450         * Beside property names a query may contains 0 to n functions modifying the values of one or more original
451         * properties. E.g. instead of area and population the density of a country can be requested by using a function
452         * instead:
453         * 
454         * <pre>
455         *  &lt;ogc:Div&gt;
456         *   &lt;ogc:PropertyName&gt;population&lt;/ogc:PropertyName&gt;
457         *   &lt;ogc:PropertyName&gt;area&lt;/ogc:PropertyName&gt;
458         *  &lt;/ogc:Div&gt;
459         * </pre>
460         * 
461         * <p>
462         * If no functions and no property names are specified all properties should be fetched.
463         * </p>
464         * 
465         * @return requested functions
466         * 
467         * @see #getPropertyNames()
468         */
469        public Function[] getFunctions() {
470            return this.functions;
471        }
472    
473        /**
474         * Returns the filter that limits the query.
475         * 
476         * @return the filter that limits the query
477         */
478        public Filter getFilter() {
479            return this.filter;
480        }
481    
482        /**
483         * Returns the sort criteria for the result.
484         * 
485         * @return the sort criteria for the result
486         */
487        public SortProperty[] getSortProperties() {
488            return this.sortProperties;
489        }
490    
491        /**
492         * Returns the value of the resultType attribute ("inherited" from the GetFeature container).
493         * 
494         * @return the value of the resultType attribute
495         */
496        public RESULT_TYPE getResultType() {
497            return this.resultType;
498        }
499    
500        /**
501         * Returns the value of the maxFeatures attribute ("inherited" from the GetFeature container).
502         * 
503         * The optional maxFeatures attribute can be used to limit the number of features that a GetFeature request
504         * retrieves. Once the maxFeatures limit is reached, the result set is truncated at that point. If not limit is set
505         * -1 will be returned
506         * 
507         * @return the value of the maxFeatures attribute
508         */
509        public int getMaxFeatures() {
510            return this.maxFeatures;
511        }
512    
513        /**
514         * @param maxFeatures
515         */
516        public void setMaxFeatures( int maxFeatures ) {
517            this.maxFeatures = maxFeatures;
518        }
519    
520        /**
521         * Returns the value of the startPosition attribute ("inherited" from the GetFeature container).
522         * <p>
523         * The startPosition parameter identifies the first result set entry to be returned. If no startPosition is set
524         * explicitly, 1 will be returned.
525         * 
526         * @return the value of the startPosition attribute, 1 if undefined
527         */
528        public int getStartPosition() {
529            return this.startPosition;
530        }
531    
532        /**
533         * @see #getStartPosition()
534         * @param startPosition
535         */
536        public void setStartPosition( int startPosition ) {
537            this.startPosition = startPosition;
538        }
539    
540        /**
541         * Adds missing namespaces in the names of requested feature types.
542         * <p>
543         * If the {@link QualifiedName} of a requested type has a null namespace, the first qualified feature type name of
544         * the given {@link WFService} with the same local name is used instead.
545         * <p>
546         * Note: The method changes this request part (the feature type names) and should only be called by the
547         * <code>WFSHandler</code> class.
548         * 
549         * @param wfs
550         *            {@link WFService} instance that is used for the lookup of proper (qualified) feature type names
551         */
552        public void guessMissingTypeNameNamespace( WFService wfs ) {
553            for ( int i = 0; i < typeNames.length; i++ ) {
554                QualifiedName typeName = typeNames[i];
555                if ( typeName.getNamespace() == null ) {
556                    if ( typeName.getLocalName().equals( typeName.getLocalName() ) ) {
557                        LOG.logWarning( "Requested feature type name has no namespace information. Guessing namespace for feature type '"
558                                        + typeName.getLocalName() + "' (quirks lookup mode)." );
559                        for ( QualifiedName ftName : wfs.getMappedFeatureTypes().keySet() ) {
560                            if ( ftName.getLocalName().equals( typeName.getLocalName() ) ) {
561                                LOG.logWarning( "Using feature type '" + ftName + "'." );
562                                typeNames[i] = ftName;
563                                break;
564                            }
565                        }
566                    }
567                }
568            }
569        }
570    
571        /**
572         * Adds missing namespaces to requested feature type names, property names, filter properties and sort properties.
573         * <p>
574         * Note: The method changes the request and should only be called by the <code>WFSHandler</code> class.
575         * 
576         * @param wfs
577         *            {@link WFService} instance that is used for the lookup of proper (qualified) feature and property
578         *            names
579         */
580        public void guessAllMissingNamespaces( WFService wfs ) {
581            guessMissingTypeNameNamespace( wfs );
582    
583            URI defaultNamespace = typeNames[0].getNamespace();
584            augmentFilterWithNamespace( defaultNamespace );
585            augmentSortPropertiesWithNamespace( defaultNamespace );
586            augmentQueriedProperties( defaultNamespace );
587        }
588    
589        private void augmentQueriedProperties( URI defaultNamespace ) {
590            if ( propertyNames != null ) {
591                for ( PropertyPath propertyPath : propertyNames ) {
592                    augmentPropertyPath( propertyPath, defaultNamespace );
593                }
594            }
595        }
596    
597        private void augmentSortPropertiesWithNamespace( URI defaultNamespace ) {
598            if ( sortProperties != null ) {
599                for ( SortProperty sortCriterion : sortProperties ) {
600                    augmentPropertyPath( sortCriterion.getSortProperty(), defaultNamespace );
601                }
602            }
603        }
604    
605        private void augmentFilterWithNamespace( URI defaultNamespace ) {
606            if ( filter != null ) {
607                if ( filter instanceof ComplexFilter ) {
608                    Operation operation = ( (ComplexFilter) filter ).getOperation();
609                    augmentFilterOperationWithNamespace( operation, defaultNamespace );
610                }
611            }
612        }
613    
614        private void augmentFilterOperationWithNamespace( Operation operation, URI defaultNamespace ) {
615            if ( operation instanceof ComparisonOperation ) {
616                if ( operation instanceof PropertyIsBetweenOperation ) {
617                    PropertyIsBetweenOperation propOperation = (PropertyIsBetweenOperation) operation;
618                    augmentPropertyPath( propOperation.getPropertyName().getValue(), defaultNamespace );
619                    if ( propOperation.getLowerBoundary() instanceof PropertyName ) {
620                        augmentPropertyPath( ( (PropertyName) propOperation.getLowerBoundary() ).getValue(),
621                                             defaultNamespace );
622                    }
623                    if ( propOperation.getUpperBoundary() instanceof PropertyName ) {
624                        augmentPropertyPath( ( (PropertyName) propOperation.getUpperBoundary() ).getValue(),
625                                             defaultNamespace );
626                    }
627                } else if ( operation instanceof PropertyIsCOMPOperation ) {
628                    PropertyIsCOMPOperation propOperation = (PropertyIsCOMPOperation) operation;
629                    if ( propOperation.getFirstExpression() instanceof PropertyName ) {
630                        augmentPropertyPath( ( (PropertyName) propOperation.getFirstExpression() ).getValue(),
631                                             defaultNamespace );
632                    }
633                    if ( propOperation.getSecondExpression() instanceof PropertyName ) {
634                        augmentPropertyPath( ( (PropertyName) propOperation.getSecondExpression() ).getValue(),
635                                             defaultNamespace );
636                    }
637                } else if ( operation instanceof PropertyIsInstanceOfOperation ) {
638                    PropertyIsInstanceOfOperation propOperation = (PropertyIsInstanceOfOperation) operation;
639                    augmentPropertyPath( propOperation.getPropertyName().getValue(), defaultNamespace );
640                } else if ( operation instanceof PropertyIsLikeOperation ) {
641                    PropertyIsLikeOperation propOperation = (PropertyIsLikeOperation) operation;
642                    augmentPropertyPath( propOperation.getPropertyName().getValue(), defaultNamespace );
643                } else if ( operation instanceof PropertyIsNullOperation ) {
644                    PropertyIsNullOperation propOperation = (PropertyIsNullOperation) operation;
645                    augmentPropertyPath( propOperation.getPropertyName().getValue(), defaultNamespace );
646                }
647            } else if ( operation instanceof LogicalOperation ) {
648                LogicalOperation logicalOperation = (LogicalOperation) operation;
649                for ( Operation argument : logicalOperation.getArguments() ) {
650                    augmentFilterOperationWithNamespace( argument, defaultNamespace );
651                }
652            } else if ( operation instanceof SpatialOperation ) {
653                SpatialOperation spatialOperation = (SpatialOperation) operation;
654                PropertyName propertyName = spatialOperation.getPropertyName();
655                if ( propertyName != null ) {
656                    augmentPropertyPath( propertyName.getValue(), defaultNamespace );
657                }
658            }
659    
660        }
661    
662        private void augmentPropertyPath( PropertyPath propertyPath, URI defaultNamespace ) {
663            for ( PropertyPathStep step : propertyPath.getAllSteps() ) {
664                QualifiedName name = step.getPropertyName();
665                if ( name.getNamespace() == null && step instanceof ElementStep ) {
666                    LOG.logWarning( "Augmenting missing namespace: '" + name + "' -> '" + defaultNamespace + "'" );
667                    step.setPropertyName( new QualifiedName( name.getPrefix(), name.getLocalName(), defaultNamespace ) );
668                }
669            }
670        }
671    
672        /**
673         * Returns a string representation of the object.
674         * 
675         * @return a string representation of the object
676         */
677        @Override
678        public String toString() {
679            String ret = null;
680            ret = "propertyNames = " + propertyNames + "\n";
681            ret += ( "handle = " + handle + "\n" );
682            ret += ( "version = " + featureVersion + "\n" );
683            ret += ( "typeName = " + typeNames + "\n" );
684            ret += ( "filter = " + filter + "\n" );
685            return ret;
686        }
687    }