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