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