001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/ogcwebservices/wfs/operation/GetFeatureDocument.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 static java.lang.Integer.parseInt;
039    import static java.lang.Math.toDegrees;
040    import static org.deegree.crs.coordinatesystems.GeographicCRS.WGS84;
041    import static org.deegree.framework.log.LoggerFactory.getLogger;
042    import static org.deegree.framework.xml.XMLTools.getElements;
043    import static org.deegree.framework.xml.XMLTools.getNodeAsString;
044    import static org.deegree.framework.xml.XMLTools.getNodes;
045    import static org.deegree.framework.xml.XMLTools.getRequiredNodeAsString;
046    import static org.deegree.i18n.Messages.get;
047    import static org.deegree.i18n.Messages.getMessage;
048    import static org.deegree.model.crs.CRSFactory.create;
049    import static org.deegree.model.filterencoding.OperationDefines.BBOX;
050    import static org.deegree.model.filterencoding.OperationDefines.TYPE_LOGICAL;
051    import static org.deegree.model.filterencoding.OperationDefines.getTypeById;
052    import static org.deegree.ogcwebservices.wfs.configuration.WFSDeegreeParams.getSwitchAxes;
053    import static org.deegree.ogcwebservices.wfs.operation.AbstractWFSRequest.FORMAT_GML2_WFS100;
054    import static org.deegree.ogcwebservices.wfs.operation.AbstractWFSRequest.FORMAT_GML3;
055    
056    import java.security.InvalidParameterException;
057    import java.util.Arrays;
058    import java.util.HashSet;
059    import java.util.List;
060    import java.util.Map;
061    import java.util.Set;
062    
063    import javax.vecmath.Point2d;
064    
065    import org.deegree.crs.coordinatesystems.ProjectedCRS;
066    import org.deegree.datatypes.QualifiedName;
067    import org.deegree.framework.log.ILogger;
068    import org.deegree.framework.xml.XMLParsingException;
069    import org.deegree.framework.xml.XMLTools;
070    import org.deegree.i18n.Messages;
071    import org.deegree.model.crs.CRSTransformationException;
072    import org.deegree.model.crs.CoordinateSystem;
073    import org.deegree.model.crs.GeoTransformer;
074    import org.deegree.model.crs.UnknownCRSException;
075    import org.deegree.model.filterencoding.AbstractFilter;
076    import org.deegree.model.filterencoding.ComplexFilter;
077    import org.deegree.model.filterencoding.Filter;
078    import org.deegree.model.filterencoding.Function;
079    import org.deegree.model.filterencoding.LogicalOperation;
080    import org.deegree.model.filterencoding.Operation;
081    import org.deegree.model.filterencoding.SpatialOperation;
082    import org.deegree.model.spatialschema.Envelope;
083    import org.deegree.model.spatialschema.GeometryFactory;
084    import org.deegree.ogcbase.PropertyPath;
085    import org.deegree.ogcbase.SortProperty;
086    import org.deegree.ogcwebservices.InvalidParameterValueException;
087    import org.deegree.ogcwebservices.wfs.operation.GetFeature.RESULT_TYPE;
088    import org.jaxen.JaxenException;
089    import org.jaxen.XPath;
090    import org.jaxen.dom.DOMXPath;
091    import org.w3c.dom.Element;
092    import org.w3c.dom.Node;
093    
094    /**
095     * Parser for "wfs:GetFeature" requests.
096     *
097     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
098     * @author last edited by: $Author: mschneider $
099     *
100     * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
101     */
102    public class GetFeatureDocument extends AbstractWFSRequestDocument {
103    
104        private static final ILogger LOG = getLogger( GetFeatureDocument.class );
105    
106        private static final long serialVersionUID = -3411186861123355322L;
107    
108        /**
109         * Parses the underlying document into a <code>GetFeature</code> request object.
110         *
111         * @param id
112         * @return corresponding <code>GetFeature</code> object
113         * @throws XMLParsingException
114         * @throws InvalidParameterValueException
115         */
116        public GetFeature parse( String id )
117                                throws InvalidParameterValueException, XMLParsingException {
118    
119            checkServiceAttribute();
120            String version = checkVersionAttribute();
121            boolean useVersion_1_0_0 = "1.0.0".equals( version );
122    
123            Element root = getRootElement();
124            String handle = XMLTools.getNodeAsString( root, "@handle", nsContext, null );
125            String outputFormat = XMLTools.getNodeAsString( root, "@outputFormat", nsContext,
126                                                            useVersion_1_0_0 ? FORMAT_GML2_WFS100 : FORMAT_GML3 );
127    
128            int maxFeatures = XMLTools.getNodeAsInt( root, "@maxFeatures", nsContext, -1 );
129            int startPosition = XMLTools.getNodeAsInt( root, "@startPosition", nsContext, 1 );
130            if ( startPosition < 1 ) {
131                String msg = Messages.getMessage( "WFS_INVALID_STARTPOSITION", Integer.toString( startPosition ) );
132                throw new XMLParsingException( msg );
133            }
134    
135            String depth = XMLTools.getNodeAsString( root, "@traverseXlinkDepth", nsContext, "*" );
136            int traverseXLinkDepth = depth.equals( "*" ) ? -1 : parseInt( depth );
137            int traverseXLinkExpiry = XMLTools.getNodeAsInt( root, "@traverseXlinkExpiry", nsContext, -1 );
138    
139            String resultTypeString = XMLTools.getNodeAsString( root, "@resultType", nsContext, "results" );
140            RESULT_TYPE resultType;
141            if ( "results".equals( resultTypeString ) ) {
142                resultType = RESULT_TYPE.RESULTS;
143            } else if ( "hits".equals( resultTypeString ) ) {
144                resultType = RESULT_TYPE.HITS;
145            } else {
146                String msg = Messages.getMessage( "WFS_INVALID_RESULT_TYPE", resultTypeString );
147                throw new XMLParsingException( msg );
148            }
149    
150            // next time, perhaps we'll use real validation instead of ad hoc "validation" like this
151            List<Element> nl = getElements( root, "wfs:Query", nsContext );
152            try {
153                XPath xpath = new DOMXPath( "count(*)" );
154                xpath.numberValueOf( root );
155    
156                int cnt = xpath.numberValueOf( root ).intValue();
157    
158                if ( cnt != nl.size() ) {
159                    throw new InvalidParameterValueException( getMessage( "WFS_ONLY_QUERY_ELEMENTS_PERMITTED" ) );
160                }
161    
162            } catch ( JaxenException e ) {
163                // the xpath was tested...
164            }
165    
166            if ( nl.size() == 0 ) {
167                throw new InvalidParameterValueException( getMessage( "WFS_QUERY_ELEMENT_MISSING" ) );
168            }
169            Query[] queries = new Query[nl.size()];
170            for ( int i = 0; i < queries.length; i++ ) {
171                queries[i] = parseQuery( nl.get( i ), useVersion_1_0_0 );
172            }
173    
174            // vendorspecific attributes; required by deegree rights management
175            Map<String, String> vendorSpecificParams = parseDRMParams( root );
176    
177            GetFeature req = new GetFeature( version, id, handle, resultType, outputFormat, maxFeatures, startPosition,
178                                             traverseXLinkDepth, traverseXLinkExpiry, queries, vendorSpecificParams );
179            return req;
180        }
181    
182        /**
183         * Parses the given query element into a {@link Query} object with filter encoding 1.1.0.
184         * <p>
185         * Note that the following attributes from the surrounding element are also considered (if it is present):
186         * <ul>
187         * <li>resultType</li>
188         * <li>maxFeatures</li>
189         * <li>startPosition</li>
190         * </ul>
191         *
192         * @param element
193         *            query element
194         * @return corresponding <code>Query</code> object
195         * @throws XMLParsingException
196         */
197        Query parseQuery( Element element )
198                                throws XMLParsingException {
199            return parseQuery( element, false );
200    
201        }
202    
203        /**
204         * Parses the given query element into a {@link Query} object.
205         * <p>
206         * Note that the following attributes from the surrounding element are also considered (if it is present):
207         * <ul>
208         * <li>resultType</li>
209         * <li>maxFeatures</li>
210         * <li>startPosition</li>
211         * </ul>
212         *
213         * @param element
214         *            query element
215         * @param useVersion_1_0_0
216         *            true, if the query is part of a 1.0.0 GetFeature request, otherwise false
217         * @return corresponding <code>Query</code> object
218         * @throws XMLParsingException
219         */
220        Query parseQuery( Element element, boolean useVersion_1_0_0 )
221                                throws XMLParsingException {
222    
223            String handle = getNodeAsString( element, "@handle", nsContext, null );
224            String typeNameList = getRequiredNodeAsString( element, "@typeName", nsContext );
225            // handle both 1.1.0 and 1.2.0 delimiters
226            String[] values = typeNameList.split( "[,\\s]" );
227            QualifiedName[] typeNames = transformToQualifiedNames( values, element );
228            String[] aliases = null;
229            String aliasesList = getNodeAsString( element, "@aliases", nsContext, null );
230            if ( aliasesList != null ) {
231                aliases = aliasesList.split( "\\s" );
232                if ( LOG.isDebug() ) {
233                    LOG.logDebug( "Found following aliases:" + Arrays.toString( aliases ) );
234                }
235    
236                if ( aliases.length != typeNames.length ) {
237                    String msg = getMessage( "WFS_QUERY_ALIAS_WRONG_COUNT", Integer.toString( typeNames.length ),
238                                             Integer.toString( aliases.length ) );
239                    throw new XMLParsingException( msg );
240                }
241                Set<String> tempSet = new HashSet<String>();
242                for ( String alias : aliases ) {
243                    if ( tempSet.contains( alias ) ) {
244                        String msg = getMessage( "WFS_QUERY_ALIAS_NOT_UNIQUE", alias );
245                        throw new XMLParsingException( msg );
246                    }
247                    tempSet.add( alias );
248                }
249            }
250    
251            String featureVersion = getNodeAsString( element, "@featureVersion", nsContext, null );
252            String srsName = getNodeAsString( element, "@srsName", nsContext, null );
253    
254            List<Node> nl = null;
255            if ( useVersion_1_0_0 ) {
256                nl = getNodes( element, "ogc:PropertyName", nsContext );
257            } else {
258                nl = getNodes( element, "wfs:PropertyName | wfs:XlinkPropertyName", nsContext );
259            }
260            PropertyPath[] propertyNames = new PropertyPath[nl.size()];
261            for ( int i = 0; i < propertyNames.length; i++ ) {
262                propertyNames[i] = parseExtendedPropertyPath( (Element) nl.get( i ) );
263            }
264    
265            nl = XMLTools.getNodes( element, "ogc:Function", nsContext );
266            Function[] functions = new Function[nl.size()];
267            for ( int i = 0; i < functions.length; i++ ) {
268                functions[i] = (Function) Function.buildFromDOM( (Element) nl.get( i ) );
269            }
270    
271            Filter filter = null;
272            Element filterElement = (Element) XMLTools.getNode( element, "ogc:Filter", nsContext );
273            if ( filterElement != null ) {
274                filter = AbstractFilter.buildFromDOM( filterElement, useVersion_1_0_0 );
275            }
276    
277            SortProperty[] sortProps = null;
278            Element sortByElement = (Element) XMLTools.getNode( element, "ogc:SortBy", nsContext );
279            if ( sortByElement != null ) {
280                sortProps = parseSortBy( sortByElement );
281            }
282    
283            // ----------------------------------------------------------------------------------------
284            // parse "inherited" attributes from GetFeature element (but very kindly)
285            // ----------------------------------------------------------------------------------------
286    
287            String resultTypeString = "results";
288            RESULT_TYPE resultType;
289            try {
290                resultTypeString = XMLTools.getNodeAsString( element, "../@resultType", nsContext, "results" );
291            } catch ( XMLParsingException doNothing ) {
292                // it's o.k. - let's be really kind here
293            }
294    
295            if ( "results".equals( resultTypeString ) ) {
296                resultType = RESULT_TYPE.RESULTS;
297            } else if ( "hits".equals( resultTypeString ) ) {
298                resultType = RESULT_TYPE.HITS;
299            } else {
300                String msg = Messages.getMessage( "WFS_INVALID_RESULT_TYPE", resultTypeString );
301                throw new XMLParsingException( msg );
302            }
303    
304            int maxFeatures = -1;
305            try {
306                maxFeatures = XMLTools.getNodeAsInt( element, "../@maxFeatures", nsContext, -1 );
307            } catch ( XMLParsingException doNothing ) {
308                // it's o.k. - let's be really kind here
309            }
310    
311            int startPosition = -1;
312            try {
313                startPosition = XMLTools.getNodeAsInt( element, "../@startPosition", nsContext, 0 );
314            } catch ( XMLParsingException doNothing ) {
315                // it's o.k. - let's be really kind here
316            }
317    
318            BBoxTest test = new BBoxTest( srsName, filter );
319    
320            return new Query( propertyNames, functions, sortProps, handle, featureVersion, typeNames, aliases, srsName,
321                              filter, resultType, maxFeatures, startPosition, test );
322        }
323    
324        /**
325         * <code>BBoxTest</code> is a helper class that encapsulates the check for bounding boxes.
326         *
327         * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a>
328         * @author last edited by: $Author: mschneider $
329         *
330         * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
331         */
332        public static class BBoxTest {
333            private String srsName;
334    
335            private Filter filter;
336    
337            /**
338             * @param srsName
339             * @param filter
340             */
341            public BBoxTest( String srsName, Filter filter ) {
342                this.srsName = srsName;
343                this.filter = filter;
344            }
345    
346            /**
347             * @throws InvalidParameterValueException
348             */
349            public void performTest()
350                                    throws InvalidParameterValueException {
351                if ( srsName == null ) {
352                    return;
353                }
354                isBoundingBoxValid( srsName, filter );
355            }
356        }
357    
358        private static SpatialOperation extractFirstBBOX( Operation operation ) {
359            if ( operation.getOperatorId() == BBOX ) {
360                return (SpatialOperation) operation;
361            }
362            if ( getTypeById( operation.getOperatorId() ) == TYPE_LOGICAL ) {
363                for ( Operation op : ( (LogicalOperation) operation ).getArguments() ) {
364                    SpatialOperation bbox = extractFirstBBOX( op );
365                    if ( bbox != null ) {
366                        return bbox;
367                    }
368                }
369            }
370    
371            return null;
372        }
373    
374        static void isBoundingBoxValid( String srsName, Filter filter )
375                                throws InvalidParameterValueException {
376            SpatialOperation bbox = null;
377    
378            if ( filter instanceof ComplexFilter ) {
379                ComplexFilter cf = (ComplexFilter) filter;
380                bbox = extractFirstBBOX( cf.getOperation() );
381            }
382    
383            if ( bbox == null ) {
384                return;
385            }
386    
387            try {
388                CoordinateSystem crs = create( srsName );
389    
390                if ( !( crs.getCRS() instanceof ProjectedCRS ) ) {
391                    return;
392                }
393    
394                String code = crs.getCRS().getIdentifier().split( ":" )[1];
395    
396                if ( !( code.compareTo( "26901" ) > 0 && code.compareTo( "26929" ) < 0 )
397                     && !( code.compareTo( "32601" ) > 0 && code.compareTo( "32660" ) < 0 )
398                     && !( code.compareTo( "32701" ) > 0 && code.compareTo( "32760" ) < 0 ) ) {
399                    return;
400                }
401    
402                Envelope bb = bbox.getGeometry().getEnvelope();
403                bb = GeometryFactory.createEnvelope( bb.getMin(), bb.getMax(), bb.getCoordinateSystem() );
404                if ( !bb.getCoordinateSystem().getCRS().equals( WGS84 ) ) {
405                    bb = new GeoTransformer( create( WGS84 ) ).transform( bb, bb.getCoordinateSystem() );
406                }
407    
408                Point2d naturalOrigin = ( (ProjectedCRS) crs.getCRS() ).getProjection().getNaturalOrigin();
409    
410                boolean swap = getSwitchAxes();
411    
412                double degx = toDegrees( naturalOrigin.x );
413                double left = swap ? bb.getMin().getX() : bb.getMin().getY();
414                double right = swap ? bb.getMax().getX() : bb.getMin().getY();
415                if ( !( degx > left && degx < right ) ) {
416                    throw new InvalidParameterValueException( get( "WFS_WRONG_UTM_STRIPE", left, degx, right ) );
417                }
418    
419            } catch ( UnknownCRSException e ) {
420                LOG.logError( "A problem occurred while parsing the request. Please report the stack trace.", e );
421            } catch ( ClassCastException e ) {
422                LOG.logError( "A problem occurred while parsing the request. Please report the stack trace.", e );
423            } catch ( InvalidParameterException e ) {
424                LOG.logError( "A problem occurred while parsing the request. Please report the stack trace.", e );
425            } catch ( CRSTransformationException e ) {
426                LOG.logError( "A problem occurred while parsing the request. Please report the stack trace.", e );
427            }
428        }
429    
430        /**
431         * Parses the given "ogc:SortBy" element.
432         *
433         * @param root
434         *            "ogc:SortBy" element
435         * @return corresponding <code>SortProperty</code> instances (in original order)
436         */
437        private SortProperty[] parseSortBy( Element root )
438                                throws XMLParsingException {
439    
440            List<Node> nl = XMLTools.getRequiredNodes( root, "ogc:SortProperty", nsContext );
441            SortProperty[] sortProps = new SortProperty[nl.size()];
442            for ( int i = 0; i < nl.size(); i++ ) {
443                sortProps[i] = SortProperty.create( (Element) nl.get( i ) );
444            }
445            return sortProps;
446        }
447    }