    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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    }