    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/ogcwebservices/wfs/operation/AbstractWFSRequest.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.Double.parseDouble;
    039    import static org.deegree.i18n.Messages.get;
    040    import static org.deegree.i18n.Messages.getMessage;
    041    import static org.deegree.model.filterencoding.OperationDefines.BBOX;
    042    import static org.deegree.model.spatialschema.GMLGeometryAdapter.EPSG4326;
    043    import static org.deegree.model.spatialschema.GeometryFactory.createEnvelope;
    044    import static org.deegree.model.spatialschema.GeometryFactory.createSurface;
    045    import static org.deegree.ogcwebservices.wfs.configuration.WFSDeegreeParams.getSwitchAxes;
    046
    047    import java.io.StringReader;
    048    import java.net.URI;
    049    import java.net.URISyntaxException;
    050    import java.util.HashMap;
    051    import java.util.Map;
    052
    053    import org.deegree.datatypes.QualifiedName;
    054    import org.deegree.framework.log.ILogger;
    055    import org.deegree.framework.log.LoggerFactory;
    056    import org.deegree.framework.xml.NamespaceContext;
    057    import org.deegree.framework.xml.XMLTools;
    058    import org.deegree.i18n.Messages;
    059    import org.deegree.model.crs.CRSFactory;
    060    import org.deegree.model.crs.CoordinateSystem;
    061    import org.deegree.model.crs.UnknownCRSException;
    062    import org.deegree.model.filterencoding.AbstractFilter;
    063    import org.deegree.model.filterencoding.ComplexFilter;
    064    import org.deegree.model.filterencoding.Filter;
    065    import org.deegree.model.filterencoding.Operation;
    066    import org.deegree.model.filterencoding.SpatialOperation;
    067    import org.deegree.model.spatialschema.Envelope;
    068    import org.deegree.model.spatialschema.GeometryException;
    069    import org.deegree.model.spatialschema.Surface;
    070    import org.deegree.ogcwebservices.AbstractOGCWebServiceRequest;
    071    import org.deegree.ogcwebservices.InconsistentRequestException;
    072    import org.deegree.ogcwebservices.InvalidParameterValueException;
    073    import org.deegree.ogcwebservices.MissingParameterValueException;
    074    import org.deegree.ogcwebservices.wfs.WFService;
    075    import org.w3c.dom.Document;
    076
    077    /**
    078     * Abstract base class for requests to web feature services.
    079     *
    080     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a>
    081     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
    082     * @author last edited by: $Author: aschmitz $
    083     *
    084     * @version $Revision: 18323 $, $Date: 2009-07-06 14:25:03 +0200 (Mo, 06. Jul 2009) $
    085     */
    086    public class AbstractWFSRequest extends AbstractOGCWebServiceRequest {
    087
    088        private static final ILogger LOG = LoggerFactory.getLogger( AbstractWFSRequest.class );
    089
    090        private static final long serialVersionUID = 6691114984307038750L;
    091
    092        /** GML2 format * */
    093        public static String FORMAT_GML2 = "text/xml; subtype=gml/2.1.2";
    094
    095        /** GML2 format (WFS 1.00 style) * */
    096        public static String FORMAT_GML2_WFS100 = "GML2";
    097
    098        /** GML3 format * */
    099        public static String FORMAT_GML3 = "text/xml; subtype=gml/3.1.1";
    100
    101        /** Generic XML format * */
    102        public static String FORMAT_XML = "XML";
    103
    104        private String handle = null;
    105
    106        /**
    107         * Creates a new <code>AbstractWFSRequest</code> instance.
    108         *
    109         * @param version
    110         * @param id
    111         * @param handle
    112         * @param vendorSpecificParameter
    113         */
    114        protected AbstractWFSRequest( String version, String id, String handle, Map<String, String> vendorSpecificParameter ) {
    115            super( version, id, vendorSpecificParameter );
    116            this.handle = handle;
    117        }
    118
    119        /**
    120         * Returns the value of the service attribute (WFS).
    121         *
    122         * @return the value of the service attribute (WFS)
    123         */
    124        public String getServiceName() {
    125            return "WFS";
    126        }
    127
    128        /**
    129         * Returns the value of the handle attribute.
    130         * <p>
    131         * The purpose of the <b>handle</b> attribute is to allow a client application to associate a mnemonic name with a
    132         * request for error handling purposes. If a <b>handle</b> is specified, and an exception is encountered, a Web
    133         * Feature Service may use the <b>handle</b> to identify the offending element.
    134         *
    135         * @return the value of the handle attribute
    136         */
    137        public String getHandle() {
    138            return this.handle;
    139        }
    140
    141        /**
    142         * Checks that the "VERSION" parameter value equals a supported version.
    143         *
    144         * @param model
    145         *            contains the parameters of the request
    146         * @return value for "VERSION" parameter, never null
    147         * @throws InvalidParameterValueException
    148         * @throws MissingParameterValueException
    149         */
    150        protected static String checkVersionParameter( Map<String, String> model )
    151                                throws InvalidParameterValueException, MissingParameterValueException {
    152            String version = model.get( "VERSION" );
    153            if ( version == null || version.equals( "" ) ) {
    154                throw new MissingParameterValueException( "version", get( "WFS_MISSING_PARAMETER_VALUE", "VERSION" ) );
    155            }
    156
    157            if ( !WFService.VERSION.equals( version ) && !"1.0.0".equals( version ) ) {
    158                String msg = Messages.getMessage( "WFS_REQUEST_UNSUPPORTED_VERSION", version, "1.0.0 and "
    159                                                                                              + WFService.VERSION );
    160                throw new InvalidParameterValueException( "version", msg );
    161            }
    162            return version;
    163        }
    164
    165        /**
    166         * Checks that the "SERVICE" parameter value equals the name of the service.
    167         *
    168         * TODO move this to AbstractOGCWebServiceRequest
    169         *
    170         * @param model
    171         *            contains the parameters of the request
    172         * @throws InconsistentRequestException
    173         *             if parameter is not present or does not the service name
    174         * @throws MissingParameterValueException
    175         */
    176        protected static void checkServiceParameter( Map<String, String> model )
    177                                throws InconsistentRequestException, MissingParameterValueException {
    178            String service = model.get( "SERVICE" );
    179
    180            if ( service == null || service.equals( "" ) || service.equals( "unknown" ) ) {
    181                throw new MissingParameterValueException( "service", get( "WFS_MISSING_PARAMETER_VALUE", "SERVICE" ) );
    182            }
    183
    184            if ( !"WFS".equals( service ) ) {
    185                throw new InconsistentRequestException( "'SERVICE' parameter must be 'WFS', but is '" + service + "'." );
    186            }
    187        }
    188
    189        /**
    190         * Extracts the qualified type names from the TYPENAME parameter.
    191         *
    192         * @param kvp
    193         * @return qualified type names (empty array if TYPENAME parameter is not present)
    194         * @throws InvalidParameterValueException
    195         */
    196        protected static QualifiedName[] extractTypeNames( Map<String, String> kvp )
    197                                throws InvalidParameterValueException {
    198            QualifiedName[] typeNames = new QualifiedName[0];
    199            NamespaceContext nsContext = extractNamespaceParameter( kvp );
    200            String typeNameString = kvp.get( "TYPENAME" );
    201            if ( typeNameString != null ) {
    202                String[] typeNameStrings = typeNameString.split( "," );
    203                typeNames = new QualifiedName[typeNameStrings.length];
    204                for ( int i = 0; i < typeNameStrings.length; i++ ) {
    205                    typeNames[i] = transformToQualifiedName( typeNameStrings[i], nsContext );
    206                }
    207            }
    208            return typeNames;
    209        }
    210
    211        /**
    212         * Extracts the namespace bindings from the NAMESPACE parameter.
    213         * <p>
    214         * Example:
    215         * <ul>
    216         * <li><code>NAMESPACE=xmlns(myns=http://www.someserver.com),xmlns(yourns=http://www.someotherserver.com)</code></li>
    217         * </ul>
    218         * <p>
    219         * The default namespace may also be bound (two variants are supported):
    220         * <ul>
    221         * <li><code>NAMESPACE=xmlns(=http://www.someserver.com)</code></li>
    222         * <li><code>NAMESPACE=xmlns(http://www.someserver.com)</code></li>
    223         * </ul>
    224         *
    225         * @param model
    226         *            the parameters of the request
    227         * @return namespace context
    228         * @throws InvalidParameterValueException
    229         */
    230        protected static NamespaceContext extractNamespaceParameter( Map<String, String> model )
    231                                throws InvalidParameterValueException {
    232
    233            String nsString = model.get( "NAMESPACE" );
    234
    235            NamespaceContext nsContext = new NamespaceContext();
    236            if ( nsString != null ) {
    237                String nsDecls[] = nsString.split( "," );
    238                for ( int i = 0; i < nsDecls.length; i++ ) {
    239                    String nsDecl = nsDecls[i];
    240                    if ( nsDecl.startsWith( "xmlns(" ) && nsDecl.endsWith( ")" ) ) {
    241                        nsDecl = nsDecl.substring( 6, nsDecl.length() - 1 );
    242                        int assignIdx = nsDecl.indexOf( '=' );
    243                        String prefix = "";
    244                        String nsURIString = null;
    245                        if ( assignIdx != -1 ) {
    246                            prefix = nsDecl.substring( 0, assignIdx );
    247                            nsURIString = nsDecl.substring( assignIdx + 1 );
    248                        } else {
    249                            nsURIString = nsDecl;
    250                        }
    251                        try {
    252                            URI nsURI = new URI( nsURIString );
    253                            nsContext.addNamespace( prefix, nsURI );
    254                        } catch ( URISyntaxException e ) {
    255                            String msg = Messages.getMessage( "WFS_NAMESPACE_PARAM_INVALID_URI", nsURIString, prefix );
    256                            throw new InvalidParameterValueException( msg );
    257                        }
    258                    } else {
    259                        String msg = Messages.getMessage( "WFS_NAMESPACE_PARAM" );
    260                        throw new InvalidParameterValueException( msg );
    261                    }
    262                }
    263            }
    264            return nsContext;
    265        }
    266
    267        /**
    268         * Extracts a <code>Filter</code> from the BBOX parameter.
    269         *
    270         * TODO handle other dimension count and crs
    271         *
    272         * @param model
    273         * @return filter representing the BBOX parameter (null, if no BBOX parameter specified)
    274         * @throws InvalidParameterValueException
    275         */
    276        protected static Filter extractBBOXFilter( Map<String, String> model )
    277                                throws InvalidParameterValueException {
    278
    279            ComplexFilter filter = null;
    280            String bboxString = model.get( "BBOX" );
    281            if ( bboxString != null ) {
    282                String[] parts = bboxString.split( "," );
    283                double[] coords = new double[4];
    284
    285                for ( int i = 0; i < coords.length; i++ ) {
    286                    try {
    287                        coords[i] = parseDouble( parts[i] );
    288                    } catch ( NumberFormatException e ) {
    289                        String msg = getMessage( "WFS_BBOX_PARAM_COORD_INVALID", coords[i] );
    290                        throw new InvalidParameterValueException( msg );
    291                    }
    292                }
    293
    294                CoordinateSystem srs = null;
    295
    296                if ( parts.length > 4 ) {
    297                    try {
    298                        srs = CRSFactory.create( parts[4] );
    299                    } catch ( UnknownCRSException e ) {
    300                        throw new InvalidParameterValueException( get( "DATASTORE_SRS_UNKNOWN" ) );
    301                    }
    302                }
    303
    304                boolean swap = ( srs == null || srs.equals( EPSG4326 ) ) && getSwitchAxes();
    305
    306                // build filter
    307                Envelope bbox = createEnvelope( coords[swap ? 1 : 0], coords[swap ? 0 : 1], coords[swap ? 3 : 2],
    308                                                coords[swap ? 2 : 3], srs );
    309                Surface surface;
    310                try {
    311                    surface = createSurface( bbox, srs );
    312                } catch ( GeometryException e ) {
    313                    String msg = getMessage( "WFS_BBOX_PARAM_BBOX_INVALID", e.getMessage() );
    314                    throw new InvalidParameterValueException( msg );
    315                }
    316                Operation op = new SpatialOperation( BBOX, null, surface );
    317                filter = new ComplexFilter( op );
    318            }
    319            return filter;
    320        }
    321
    322        /**
    323         * Extracts the FILTER parameter and assigns them to the requested type names.
    324         * <p>
    325         * This is necessary, because it is allowed to specify a filter for each requested feature type.
    326         *
    327         * @param kvp
    328         * @param typeNames
    329         * @return map with the assignments of type names to filters
    330         * @throws InvalidParameterValueException
    331         */
    332        protected static Map<QualifiedName, Filter> extractFilters( Map<String, String> kvp, QualifiedName[] typeNames )
    333                                throws InvalidParameterValueException {
    334            Map<QualifiedName, Filter> filterMap = new HashMap<QualifiedName, Filter>();
    335            String filterString = kvp.get( "FILTER" );
    336            if ( filterString != null ) {
    337                String[] filterStrings = filterString.split( "\\)" );
    338                if ( filterStrings.length != typeNames.length ) {
    339                    String msg = Messages.getMessage( "WFS_FILTER_PARAM_WRONG_COUNT",
    340                                                      Integer.toString( filterStrings.length ),
    341                                                      Integer.toString( typeNames.length ) );
    342                    throw new InvalidParameterValueException( msg );
    343                }
    344                for ( int i = 0; i < filterStrings.length; i++ ) {
    345                    // remove possible leading parenthesis
    346                    if ( filterStrings[i].startsWith( "(" ) ) {
    347                        filterStrings[i] = filterStrings[i].substring( 1 );
    348                    }
    349                    Document doc;
    350                    try {
    351                        doc = XMLTools.parse( new StringReader( filterStrings[i] ) );
    352                        Filter filter = AbstractFilter.buildFromDOM( doc.getDocumentElement(), false );
    353                        filterMap.put( typeNames[i], filter );
    354                    } catch ( Exception e ) {
    355                        LOG.logError( e.getMessage(), e );
    356                        String msg = Messages.getMessage( "WFS_FILTER_PARAM_PARSING", e.getMessage() );
    357                        throw new InvalidParameterValueException( msg );
    358                    }
    359                }
    360            }
    361            return filterMap;
    362        }
    363
    364        /**
    365         * Transforms a type name to a qualified name using the given namespace bindings.
    366         *
    367         * @param name
    368         * @param nsContext
    369         * @return QualifiedName
    370         */
    371        private static QualifiedName transformToQualifiedName( String name, NamespaceContext nsContext ) {
    372            QualifiedName typeName;
    373            String prefix = "";
    374            int idx = name.indexOf( ':' );
    375            if ( idx != -1 ) {
    376                prefix = name.substring( 0, idx );
    377                String localName = name.substring( idx + 1 );
    378                URI nsURI = nsContext.getURI( prefix );
    379                if ( nsURI == null ) {
    380                    String msg = Messages.getMessage( "WFS_TYPENAME_PARAM_INVALID_URI", prefix, name, localName );
    381                    LOG.logWarning( msg );
    382                    typeName = new QualifiedName( localName );
    383                } else {
    384                    typeName = new QualifiedName( prefix, localName, nsURI );
    385                }
    386            } else {
    387                // default namespace prefix ("")
    388                URI nsURI = nsContext.getURI( "" );
    389                typeName = new QualifiedName( name, nsURI );
    390            }
    391            return typeName;
    392        }
    393    }