001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/ogcwebservices/wfs/operation/GetFeature.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.ArrayList;
040    import java.util.HashMap;
041    import java.util.List;
042    import java.util.Map;
043    
044    import org.deegree.datatypes.QualifiedName;
045    import org.deegree.framework.log.ILogger;
046    import org.deegree.framework.log.LoggerFactory;
047    import org.deegree.framework.util.KVP2Map;
048    import org.deegree.framework.xml.NamespaceContext;
049    import org.deegree.framework.xml.XMLParsingException;
050    import org.deegree.i18n.Messages;
051    import org.deegree.model.filterencoding.FeatureFilter;
052    import org.deegree.model.filterencoding.FeatureId;
053    import org.deegree.model.filterencoding.Filter;
054    import org.deegree.ogcbase.PropertyPath;
055    import org.deegree.ogcbase.PropertyPathFactory;
056    import org.deegree.ogcbase.PropertyPathStep;
057    import org.deegree.ogcbase.SortProperty;
058    import org.deegree.ogcwebservices.InconsistentRequestException;
059    import org.deegree.ogcwebservices.InvalidParameterValueException;
060    import org.deegree.ogcwebservices.MissingParameterValueException;
061    import org.deegree.ogcwebservices.OGCWebServiceException;
062    import org.deegree.ogcwebservices.wfs.WFService;
063    import org.w3c.dom.Element;
064    
065    /**
066     * Represents a <code>GetFeature</code> request to a web feature service.
067     * <p>
068     * The GetFeature operation allows the retrieval of features from a web feature service. A GetFeature request is
069     * processed by a WFS and when the value of the outputFormat attribute is set to text/gml; subtype=gml/3.1.1, a GML
070     * instance document, containing the result set, is returned to the client.
071     *
072     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a>
073     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
074     * @author last edited by: $Author: mschneider $
075     *
076     * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
077     */
078    public class GetFeature extends AbstractWFSRequest {
079    
080        private static final ILogger LOG = LoggerFactory.getLogger( GetFeature.class );
081    
082        private static final long serialVersionUID = 8885456550385433051L;
083    
084        /** Serialized java object format (deegree specific extension) * */
085        public static final String FORMAT_FEATURECOLLECTION = "FEATURECOLLECTION";
086    
087        /**
088         * Known result types.
089         */
090        public static enum RESULT_TYPE {
091    
092            /** A full response should be generated. */
093            RESULTS,
094    
095            /** Only a count of the number of features should be returned. */
096            HITS
097        }
098    
099        protected RESULT_TYPE resultType = RESULT_TYPE.RESULTS;
100    
101        protected String outputFormat;
102    
103        protected int maxFeatures;
104    
105        private int traverseXLinkDepth;
106    
107        private int traverseXLinkExpiry;
108    
109        protected List<Query> queries;
110    
111        // deegree specific extension, default: 1 (start at first feature)
112        protected int startPosition;
113    
114        /**
115         * Creates a new <code>GetFeature</code> instance.
116         *
117         * @param version
118         *            request version
119         * @param id
120         *            id of the request
121         * @param handle
122         * @param resultType
123         *            desired result type (results | hits)
124         * @param outputFormat
125         *            requested result format
126         * @param maxFeatures
127         * @param startPosition
128         *            deegree specific parameter defining where to start considering features
129         * @param traverseXLinkDepth
130         *            indicates the depth to which nested property XLink linking element locator attribute (href) XLinks are
131         *            traversed and resolved if possible (not implemented yet, use -1 as default)
132         * @param traverseXLinkExpiry
133         *            indicates how long a Web Feature Service should wait to receive a response to a nested GetGmlObject
134         *            request (not implemented yet, use 0 as default)
135         * @param queries
136         * @param vendorSpecificParam
137         */
138        GetFeature( String version, String id, String handle, RESULT_TYPE resultType, String outputFormat, int maxFeatures,
139                    int startPosition, int traverseXLinkDepth, int traverseXLinkExpiry, Query[] queries,
140                    Map<String, String> vendorSpecificParam ) {
141            super( version, id, handle, vendorSpecificParam );
142            this.setQueries( queries );
143            this.outputFormat = outputFormat;
144            this.maxFeatures = maxFeatures;
145            this.startPosition = startPosition;
146            this.resultType = resultType;
147            this.traverseXLinkDepth = traverseXLinkDepth;
148            this.traverseXLinkExpiry = traverseXLinkExpiry;
149        }
150    
151        /**
152         * Creates an uninitialized {@link GetFeature} instance.
153         */
154        protected GetFeature() {
155            super( null, null, null, null );
156        }
157    
158        /**
159         * Creates a new <code>GetFeature</code> instance from the given parameters.
160         *
161         * @param version
162         *            request version
163         * @param id
164         *            id of the request
165         * @param resultType
166         *            desired result type (results | hits)
167         * @param outputFormat
168         *            requested result format
169         * @param handle
170         * @param maxFeatures
171         *            default = -1 (all features)
172         * @param startPosition
173         *            default = 0 (starting at the first feature)
174         * @param traverseXLinkDepth
175         * @param traverseXLinkExpiry
176         * @param queries
177         *            a set of Query objects that describes the query to perform
178         * @return new <code>GetFeature</code> request
179         */
180        public static GetFeature create( String version, String id, RESULT_TYPE resultType, String outputFormat,
181                                         String handle, int maxFeatures, int startPosition, int traverseXLinkDepth,
182                                         int traverseXLinkExpiry, Query[] queries ) {
183            return new GetFeature( version, id, handle, resultType, outputFormat, maxFeatures, startPosition,
184                                   traverseXLinkDepth, traverseXLinkExpiry, queries, null );
185        }
186    
187        /**
188         * Creates a new <code>GetFeature</code> instance from a document that contains the DOM representation of the
189         * request.
190         *
191         * @param id
192         *            of the request
193         * @param root
194         *            element that contains the DOM representation of the request
195         * @return new <code>GetFeature</code> request
196         * @throws OGCWebServiceException
197         */
198        public static GetFeature create( String id, Element root )
199                                throws OGCWebServiceException {
200            GetFeatureDocument doc = new GetFeatureDocument();
201            doc.setRootElement( root );
202            GetFeature request;
203            try {
204                request = doc.parse( id );
205            } catch ( InvalidParameterValueException e ) {
206                throw e;
207            } catch ( XMLParsingException e ) {
208                // have to wrap it, to set exception code
209                throw new InvalidParameterValueException( e.getMessage(), e );
210            } catch ( Exception e ) {
211                LOG.logError( e.getMessage(), e );
212                throw new OGCWebServiceException( "GetFeature", e.getMessage() );
213            }
214            return request;
215        }
216    
217        /**
218         * Creates a new <code>GetFeature</code> instance from the given key-value pair encoded request.
219         *
220         * @param id
221         *            request identifier
222         * @param request
223         * @return new <code>GetFeature</code> request
224         * @throws InvalidParameterValueException
225         * @throws InconsistentRequestException
226         * @throws MissingParameterValueException
227         */
228        public static GetFeature create( String id, String request )
229                                throws InconsistentRequestException, InvalidParameterValueException,
230                                MissingParameterValueException {
231            Map<String, String> map = KVP2Map.toMap( request );
232            map.put( "ID", id );
233            return create( map );
234        }
235    
236        /**
237         * Creates a new <code>GetFeature</code> request from the given map.
238         *
239         * @param kvp
240         *            key-value pairs, keys have to be uppercase
241         * @return new <code>GetFeature</code> request
242         * @throws InvalidParameterValueException
243         * @throws InconsistentRequestException
244         * @throws MissingParameterValueException
245         */
246        public static GetFeature create( Map<String, String> kvp )
247                                throws InconsistentRequestException, InvalidParameterValueException,
248                                MissingParameterValueException {
249    
250            // SERVICE
251            checkServiceParameter( kvp );
252    
253            // ID (deegree specific)
254            String id = kvp.get( "ID" );
255    
256            // VERSION
257            String version = checkVersionParameter( kvp );
258    
259            boolean is100 = version.equals( "1.0.0" );
260    
261            // OUTPUTFORMAT
262            String outputFormat = getParam( "OUTPUTFORMAT", kvp, is100 ? FORMAT_GML2_WFS100 : FORMAT_GML3 );
263    
264            // RESULTTYPE
265            RESULT_TYPE resultType = RESULT_TYPE.RESULTS;
266            String resultTypeString = kvp.get( "RESULTTYPE" );
267            if ( "hits".equals( resultTypeString ) ) {
268                resultType = RESULT_TYPE.HITS;
269            }
270    
271            // FEATUREVERSION
272            String featureVersion = kvp.get( "FEATUREVERSION" );
273    
274            // MAXFEATURES
275            String maxFeaturesString = kvp.get( "MAXFEATURES" );
276            // -1: fetch all features
277            int maxFeatures = -1;
278            if ( maxFeaturesString != null ) {
279                try {
280                    maxFeatures = Integer.parseInt( maxFeaturesString );
281                    if ( maxFeatures < 1 ) {
282                        throw new NumberFormatException();
283                    }
284                } catch ( NumberFormatException e ) {
285                    LOG.logError( e.getMessage(), e );
286                    String msg = Messages.getMessage( "WFS_PARAMETER_INVALID_INT", maxFeaturesString, "MAXFEATURES" );
287                    throw new InvalidParameterValueException( msg );
288                }
289            }
290    
291            // STARTPOSITION (deegree specific)
292            String startPosString = getParam( "STARTPOSITION", kvp, "1" );
293            int startPosition = 1;
294            try {
295                startPosition = Integer.parseInt( startPosString );
296                if ( startPosition < 1 ) {
297                    throw new NumberFormatException();
298                }
299            } catch ( NumberFormatException e ) {
300                LOG.logError( e.getMessage(), e );
301                String msg = Messages.getMessage( "WFS_PARAMETER_INVALID_INT", startPosString, "STARTPOSITION" );
302                throw new InvalidParameterValueException( msg );
303            }
304    
305            // SRSNAME
306            String srsName = kvp.get( "SRSNAME" );
307    
308            // SORTBY
309            SortProperty[] sortProperties = SortProperty.create( kvp.get( "SORTBY" ),
310                                                                 extractNamespaceParameter( kvp ).getNamespaceMap() );
311    
312            // TRAVERSEXLINKDEPTH
313            int traverseXLinkDepth = -1;
314    
315            // TRAVERSEXLINKEXPIRY
316            int traverseXLinkExpiry = -1;
317    
318            Map<QualifiedName, Filter> filterMap = null;
319    
320            // TYPENAME
321            QualifiedName[] typeNames = extractTypeNames( kvp );
322            if ( typeNames.length == 0 ) {
323                // check if FEATUREID is present
324                String featureId = kvp.get( "FEATUREID" );
325                if ( featureId != null ) {
326                    // no TYPENAME parameter -> request needs to be augmented later (with configuration)
327                    return new AugmentableGetFeature( version, id, null, resultType, outputFormat, maxFeatures,
328                                                      startPosition, traverseXLinkDepth, traverseXLinkExpiry, new Query[0],
329                                                      kvp );
330                }
331                String msg = Messages.getMessage( "WFS_TYPENAME+FID_PARAMS_MISSING" );
332                throw new InvalidParameterValueException( msg );
333            }
334    
335            // check if FEATUREID is present
336            String featureId = kvp.get( "FEATUREID" );
337            if ( featureId != null ) {
338                String[] featureIds = featureId.split( "," );
339                if ( typeNames.length != 1 && featureIds.length != typeNames.length ) {
340                    String msg = Messages.getMessage( "WFS_TYPENAME+FID_COUNT_MISMATCH", typeNames.length,
341                                                      featureIds.length );
342                    throw new InvalidParameterValueException( msg );
343                } else if ( typeNames.length == 1 ) {
344                    // build one filter
345                    ArrayList<FeatureId> fids = new ArrayList<FeatureId>( featureIds.length );
346                    for ( String fid : featureIds ) {
347                        fids.add( new FeatureId( fid ) );
348                    }
349                    Filter filter = new FeatureFilter( fids );
350                    filterMap = new HashMap<QualifiedName, Filter>();
351                    filterMap.put( typeNames[0], filter );
352                } else {
353                    throw new InvalidParameterValueException(
354                                                              "Usage of FEATUREID with multiple TYPENAME values is not supported yet." );
355                }
356            }
357    
358            // BBOX
359            Filter bboxFilter = extractBBOXFilter( kvp );
360    
361            // FILTER (mutually exclusive with FEATUREID or BBOX, prequisite: TYPENAME)
362            if ( filterMap != null || bboxFilter != null ) {
363                if ( kvp.containsKey( "FILTER" ) ) {
364                    String msg = Messages.getMessage( "WFS_GET_FEATURE_FEATUREID_BBOX_AND_FILTER" );
365                    throw new InvalidParameterValueException( msg );
366                }
367            } else {
368                filterMap = extractFilters( kvp, typeNames );
369            }
370    
371            // PROPERTYNAME
372            Map<QualifiedName, PropertyPath[]> propertyNameMap = extractPropNames( kvp, typeNames );
373    
374            // build a Query instance for each requested feature type (later also for each featureid...)
375            Query[] queries = new Query[typeNames.length];
376            for ( int i = 0; i < queries.length; i++ ) {
377                QualifiedName ftName = typeNames[i];
378                PropertyPath[] properties = propertyNameMap.get( ftName );
379                Filter filter;
380                if ( filterMap != null ) {
381                    filter = filterMap.get( ftName );
382                } else {
383                    filter = bboxFilter;
384                }
385                QualifiedName[] ftNames = new QualifiedName[] { ftName };
386                queries[i] = new Query( properties, null, sortProperties, null, featureVersion, ftNames, null, srsName,
387                                        filter, resultType, maxFeatures, startPosition );
388            }
389    
390            // build a GetFeature request that contains all queries
391            GetFeature request = new GetFeature( version, id, null, resultType, outputFormat, maxFeatures, startPosition,
392                                                 traverseXLinkDepth, traverseXLinkExpiry, queries, kvp );
393            return request;
394        }
395    
396        /**
397         * Extracts the PROPERTYNAME parameter and assigns them to the requested type names.
398         *
399         * @param kvp
400         * @param typeNames
401         * @return map with the assignments of type names to property names
402         * @throws InvalidParameterValueException
403         */
404        protected static Map<QualifiedName, PropertyPath[]> extractPropNames( Map<String, String> kvp,
405                                                                              QualifiedName[] typeNames )
406                                throws InvalidParameterValueException {
407            Map<QualifiedName, PropertyPath[]> propMap = new HashMap<QualifiedName, PropertyPath[]>();
408            String propNameString = kvp.get( "PROPERTYNAME" );
409            if ( propNameString != null ) {
410                String[] propNameLists = propNameString.split( "\\)" );
411                if ( propNameLists.length != typeNames.length ) {
412                    String msg = Messages.getMessage( "WFS_PROPNAME_PARAM_WRONG_COUNT",
413                                                      Integer.toString( propNameLists.length ),
414                                                      Integer.toString( typeNames.length ) );
415                    throw new InvalidParameterValueException( msg );
416                }
417                NamespaceContext nsContext = extractNamespaceParameter( kvp );
418                for ( int i = 0; i < propNameLists.length; i++ ) {
419                    String propNameList = propNameLists[i];
420                    if ( propNameList.startsWith( "(" ) ) {
421                        propNameList = propNameList.substring( 1 );
422                    }
423                    String[] propNames = propNameList.split( "," );
424                    PropertyPath[] paths = new PropertyPath[propNames.length];
425                    for ( int j = 0; j < propNames.length; j++ ) {
426                        PropertyPath path = transformToPropertyPath( propNames[j], nsContext );
427                        paths[j] = ( path );
428                    }
429                    propMap.put( typeNames[i], paths );
430                }
431            }
432            return propMap;
433        }
434    
435        /**
436         * Transforms the given property name to a (qualified) <code>PropertyPath</code> object by using the specified
437         * namespace bindings.
438         *
439         * @param propName
440         * @param nsContext
441         * @return (qualified) <code>PropertyPath</code> object
442         * @throws InvalidParameterValueException
443         */
444        private static PropertyPath transformToPropertyPath( String propName, NamespaceContext nsContext )
445                                throws InvalidParameterValueException {
446            String[] steps = propName.split( "/" );
447            List<PropertyPathStep> propertyPathSteps = new ArrayList<PropertyPathStep>( steps.length );
448    
449            for ( int i = 0; i < steps.length; i++ ) {
450                PropertyPathStep propertyStep = null;
451                QualifiedName propertyName = null;
452                String step = steps[i];
453                boolean isAttribute = false;
454                boolean isIndexed = false;
455                int selectedIndex = -1;
456    
457                // check if step begins with '@' -> must be the final step then
458                if ( step.startsWith( "@" ) ) {
459                    if ( i != steps.length - 1 ) {
460                        String msg = "PropertyName '" + propName + "' is illegal: the attribute specifier may only "
461                                     + "be used for the final step.";
462                        throw new InvalidParameterValueException( msg );
463                    }
464                    step = step.substring( 1 );
465                    isAttribute = true;
466                }
467    
468                // check if the step ends with brackets ([...])
469                if ( step.endsWith( "]" ) ) {
470                    if ( isAttribute ) {
471                        String msg = "PropertyName '" + propName
472                                     + "' is illegal: if the attribute specifier ('@') is used, "
473                                     + "index selection ('[...']) is not possible.";
474                        throw new InvalidParameterValueException( msg );
475                    }
476                    int bracketPos = step.indexOf( '[' );
477                    if ( bracketPos < 0 ) {
478                        String msg = "PropertyName '" + propName + "' is illegal. No opening brackets found for step '"
479                                     + step + "'.";
480                        throw new InvalidParameterValueException( msg );
481                    }
482                    try {
483                        selectedIndex = Integer.parseInt( step.substring( bracketPos + 1, step.length() - 1 ) );
484                    } catch ( NumberFormatException e ) {
485                        LOG.logError( e.getMessage(), e );
486                        String msg = "PropertyName '" + propName + "' is illegal. Specified index '"
487                                     + step.substring( bracketPos + 1, step.length() - 1 ) + "' is not a number.";
488                        throw new InvalidParameterValueException( msg );
489                    }
490                    step = step.substring( 0, bracketPos );
491                    isIndexed = true;
492                }
493    
494                // determine namespace prefix and binding (if any)
495                int colonPos = step.indexOf( ':' );
496                String prefix = "";
497                String localName = step;
498                if ( colonPos > 0 ) {
499                    prefix = step.substring( 0, colonPos );
500                    localName = step.substring( colonPos + 1 );
501                }
502                URI nsURI = nsContext.getURI( prefix );
503                propertyName = new QualifiedName( prefix, localName, nsURI );
504    
505                if ( isAttribute ) {
506                    propertyStep = PropertyPathFactory.createAttributePropertyPathStep( propertyName );
507                } else if ( isIndexed ) {
508                    propertyStep = PropertyPathFactory.createPropertyPathStep( propertyName, selectedIndex );
509                } else {
510                    propertyStep = PropertyPathFactory.createPropertyPathStep( propertyName );
511                }
512                propertyPathSteps.add( propertyStep );
513            }
514            return PropertyPathFactory.createPropertyPath( propertyPathSteps );
515        }
516    
517        /**
518         * Returns the output format.
519         * <p>
520         * The outputFormat attribute defines the format to use to generate the result set. Vendor specific formats,
521         * declared in the capabilities document are possible. The WFS-specs implies GML as default output format.
522         *
523         * @return the output format.
524         */
525        public String getOutputFormat() {
526            return this.outputFormat;
527        }
528    
529        /**
530         * The query defines which feature type to query, what properties to retrieve and what constraints (spatial and
531         * non-spatial) to apply to those properties.
532         * <p>
533         * only used for xml-coded requests
534         *
535         * @return contained queries
536         */
537        public Query[] getQuery() {
538            return queries.toArray( new Query[queries.size()] );
539        }
540    
541        /**
542         * sets the <Query>
543         *
544         * @param query
545         */
546        public void setQueries( Query[] query ) {
547            if ( query != null ) {
548                this.queries = new ArrayList<Query>( query.length );
549                for ( int i = 0; i < query.length; i++ ) {
550                    this.queries.add( query[i] );
551                }
552            } else {
553                this.queries = new ArrayList<Query>();
554            }
555        }
556    
557        /**
558         * The optional maxFeatures attribute can be used to limit the number of features that a GetFeature request
559         * retrieves. Once the maxFeatures limit is reached, the result set is truncated at that point. If not limit is set
560         * -1 will be returned.
561         *
562         * @return number of feature to fetch, -1 if no limit is set
563         */
564        public int getMaxFeatures() {
565            return maxFeatures;
566        }
567    
568        /**
569         * @see #getMaxFeatures()
570         * @param max
571         */
572        public void setMaxFeatures( int max ) {
573            this.maxFeatures = max;
574            for ( int i = 0; i < queries.size(); i++ ) {
575                queries.get( i ).setMaxFeatures( max );
576            }
577        }
578    
579        /**
580         * The startPosition parameter identifies the first result set entry to be returned specified the default is the
581         * first record. If not startposition is set 0 will be returned
582         *
583         * @return the first result set entry to be returned
584         */
585        public int getStartPosition() {
586            return startPosition;
587        }
588    
589        /**
590         * Returns the desired result type of the GetFeature operation. Possible values are 'results' and 'hits'.
591         *
592         * @return the desired result type
593         */
594        public RESULT_TYPE getResultType() {
595            return this.resultType;
596        }
597    
598        /**
599         * The optional traverseXLinkDepth attribute indicates the depth to which nested property XLink linking element
600         * locator attribute (href) XLinks in all properties of the selected feature(s) are traversed and resolved if
601         * possible. A value of "1" indicates that one linking element locator attribute (href) XLink will be traversed and
602         * the referenced element returned if possible, but nested property XLink linking element locator attribute (href)
603         * XLinks in the returned element are not traversed. A value of "*" indicates that all nested property XLink linking
604         * element locator attribute (href) XLinks will be traversed and the referenced elements returned if possible. The
605         * range of valid values for this attribute consists of positive integers plus "*".
606         *
607         * @return the depth to which nested property XLinks are traversed and resolved
608         */
609        public int getTraverseXLinkDepth() {
610            return traverseXLinkDepth;
611        }
612    
613        /**
614         * The traverseXLinkExpiry attribute is specified in minutes. It indicates how long a Web Feature Service should
615         * wait to receive a response to a nested GetGmlObject request. If no traverseXLinkExpiry attribute is present for a
616         * GetGmlObject request, the WFS wait time is implementation dependent.
617         *
618         * @return how long to wait to receive a response to a nested GetGmlObject request
619         */
620        public int getTraverseXLinkExpiry() {
621            return traverseXLinkExpiry;
622        }
623    
624        /**
625         * Adds missing namespaces in the names of requested feature types.
626         * <p>
627         * If the {@link QualifiedName} of a requested type has a null namespace, the first qualified feature type name of
628         * the given {@link WFService} with the same local name is used instead.
629         * <p>
630         * Note: The method changes this request (the feature type names) and should only be called by the
631         * <code>WFSHandler</code> class.
632         *
633         * @param wfs
634         *            {@link WFService} instance that is used for the lookup of proper (qualified) feature type names
635         */
636        public void guessMissingTypeNameNamespaces( WFService wfs ) {
637            for ( Query query : queries ) {
638                query.guessMissingTypeNameNamespace( wfs );
639            }
640        }
641    
642        /**
643         * Adds missing namespaces to requested feature type names, property names, filter properties and sort properties.
644         * <p>
645         * Note: The method changes the request and should only be called by the <code>WFSHandler</code> class.
646         *
647         * @param wfs
648         *            {@link WFService} instance that is used for the lookup of proper (qualified) feature and property
649         *            names
650         */
651        public void guessAllMissingNamespaces( WFService wfs ) {
652            for ( Query query : queries ) {
653                query.guessAllMissingNamespaces( wfs );
654            }
655        }
656    
657        @Override
658        public String toString() {
659            String ret = null;
660            ret = "WFSGetFeatureRequest: { \n ";
661            ret += "outputFormat = " + outputFormat + "\n";
662            ret += ( "handle = " + getHandle() + "\n" );
663            ret += ( "query = " + queries + "\n" );
664            ret += "}\n";
665            return ret;
666        }
667    }