001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/ogcwebservices/wfs/operation/AbstractWFSRequest.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2008 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/deegree/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     This library is free software; you can redistribute it and/or
012     modify it under the terms of the GNU Lesser General Public
013     License as published by the Free Software Foundation; either
014     version 2.1 of the License, or (at your option) any later version.
015    
016     This library is distributed in the hope that it will be useful,
017     but WITHOUT ANY WARRANTY; without even the implied warranty of
018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     Lesser General Public License for more details.
020    
021     You should have received a copy of the GNU Lesser General Public
022     License along with this library; if not, write to the Free Software
023     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024    
025     Contact:
026    
027     Andreas Poth
028     lat/lon GmbH
029     Aennchenstraße 19
030     53177 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Prof. Dr. Klaus Greve
035     Department of Geography
036     University of Bonn
037     Meckenheimer Allee 166
038     53115 Bonn
039     Germany
040     E-Mail: greve@giub.uni-bonn.de
041     
042     ---------------------------------------------------------------------------*/
043    package org.deegree.ogcwebservices.wfs.operation;
044    
045    import java.io.StringReader;
046    import java.net.URI;
047    import java.net.URISyntaxException;
048    import java.util.HashMap;
049    import java.util.Map;
050    
051    import org.deegree.datatypes.QualifiedName;
052    import org.deegree.framework.log.ILogger;
053    import org.deegree.framework.log.LoggerFactory;
054    import org.deegree.framework.xml.NamespaceContext;
055    import org.deegree.framework.xml.XMLTools;
056    import org.deegree.i18n.Messages;
057    import org.deegree.model.filterencoding.AbstractFilter;
058    import org.deegree.model.filterencoding.ComplexFilter;
059    import org.deegree.model.filterencoding.Filter;
060    import org.deegree.ogcwebservices.AbstractOGCWebServiceRequest;
061    import org.deegree.ogcwebservices.InconsistentRequestException;
062    import org.deegree.ogcwebservices.InvalidParameterValueException;
063    import org.deegree.ogcwebservices.wfs.WFService;
064    import org.w3c.dom.Document;
065    
066    /**
067     * Abstract base class for requests to web feature services.
068     * 
069     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a>
070     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
071     * @author last edited by: $Author: apoth $
072     * 
073     * @version $Revision: 9345 $, $Date: 2007-12-27 17:22:25 +0100 (Do, 27 Dez 2007) $
074     */
075    public class AbstractWFSRequest extends AbstractOGCWebServiceRequest {
076    
077        private static final ILogger LOG = LoggerFactory.getLogger( AbstractWFSRequest.class );
078    
079        private static final long serialVersionUID = 6691114984307038750L;
080    
081        /** GML2 format * */
082        public static String FORMAT_GML2 = "text/xml; subtype=gml/2.1.2";
083    
084        /** GML2 format (WFS 1.00 style) * */
085        public static String FORMAT_GML2_WFS100 = "GML2";
086    
087        /** GML3 format * */
088        public static String FORMAT_GML3 = "text/xml; subtype=gml/3.1.1";
089    
090        /** Generic XML format * */
091        public static String FORMAT_XML = "XML";
092    
093        private String handle = null;
094    
095        /**
096         * Creates a new <code>AbstractWFSRequest</code> instance.
097         * 
098         * @param version
099         * @param id
100         * @param handle
101         * @param vendorSpecificParameter
102         */
103        protected AbstractWFSRequest( String version, String id, String handle, Map<String, String> vendorSpecificParameter ) {
104            super( version, id, vendorSpecificParameter );
105            this.handle = handle;
106        }
107    
108        /**
109         * Returns the value of the service attribute (WFS).
110         * 
111         * @return the value of the service attribute (WFS)
112         */
113        public String getServiceName() {
114            return "WFS";
115        }
116    
117        /**
118         * Returns the value of the handle attribute.
119         * <p>
120         * The purpose of the <b>handle</b> attribute is to allow a client application to associate a
121         * mnemonic name with a request for error handling purposes. If a <b>handle</b> is specified,
122         * and an exception is encountered, a Web Feature Service may use the <b>handle</b> to identify
123         * the offending element.
124         * 
125         * @return the value of the handle attribute
126         */
127        public String getHandle() {
128            return this.handle;
129        }
130    
131        /**
132         * Checks that the "VERSION" parameter value equals a supported version.
133         * 
134         * @param model
135         *            contains the parameters of the request
136         * @return value for "VERSION" parameter, never null
137         * @throws InconsistentRequestException
138         *             if parameter is not present
139         * @throws InvalidParameterValueException
140         */
141        protected static String checkVersionParameter( Map<String, String> model )
142                                throws InconsistentRequestException, InvalidParameterValueException {
143            String version = model.get( "VERSION" );
144            if ( version == null ) {
145                throw new InconsistentRequestException( "'VERSION' parameter must be set." );
146            }
147            
148            if ( !WFService.VERSION.equals( version ) && !"1.0.0".equals( version ) ) {
149                String msg = Messages.getMessage( "WFS_REQUEST_UNSUPPORTED_VERSION", version,
150                                                  "1.0.0 and " + WFService.VERSION );
151                throw new InvalidParameterValueException( msg );
152            }
153            return version;
154        }
155    
156        /**
157         * Checks that the "SERVICE" parameter value equals the name of the service.
158         * 
159         * TODO move this to AbstractOGCWebServiceRequest
160         * 
161         * @param model
162         *            contains the parameters of the request
163         * @throws InconsistentRequestException
164         *             if parameter is not present or does not the service name
165         */
166        protected static void checkServiceParameter( Map<String, String> model )
167                                throws InconsistentRequestException {
168            String service = model.get( "SERVICE" );
169            if ( !"WFS".equals( service ) ) {
170                throw new InconsistentRequestException( "'SERVICE' parameter must be 'WFS', but is '" + service + "'." );
171            }
172        }
173    
174        /**
175         * Extracts the qualified type names from the TYPENAME parameter.
176         * 
177         * @param kvp
178         * @return qualified type names (empty array if TYPENAME parameter is not present)
179         * @throws InvalidParameterValueException
180         */
181        protected static QualifiedName[] extractTypeNames( Map<String, String> kvp )
182                                throws InvalidParameterValueException {
183            QualifiedName[] typeNames = new QualifiedName[0];
184            NamespaceContext nsContext = extractNamespaceParameter( kvp );
185            String typeNameString = kvp.get( "TYPENAME" );
186            if ( typeNameString != null ) {
187                String[] typeNameStrings = typeNameString.split( "," );
188                typeNames = new QualifiedName[typeNameStrings.length];
189                for ( int i = 0; i < typeNameStrings.length; i++ ) {
190                    typeNames[i] = transformToQualifiedName( typeNameStrings[i], nsContext );
191                }
192            }
193            return typeNames;
194        }
195    
196        /**
197         * Extracts the namespace bindings from the NAMESPACE parameter.
198         * <p>
199         * Example:
200         * <ul>
201         * <li><code>NAMESPACE=xmlns(myns=http://www.someserver.com),xmlns(yourns=http://www.someotherserver.com)</code></li>
202         * </ul>
203         * <p>
204         * The default namespace may also be bound (two variants are supported):
205         * <ul>
206         * <li><code>NAMESPACE=xmlns(=http://www.someserver.com)</code></li>
207         * <li><code>NAMESPACE=xmlns(http://www.someserver.com)</code></li>
208         * </ul>
209         * 
210         * @param model
211         *            the parameters of the request
212         * @return namespace context
213         * @throws InvalidParameterValueException
214         */
215        protected static NamespaceContext extractNamespaceParameter( Map<String, String> model )
216                                throws InvalidParameterValueException {
217    
218            String nsString = model.get( "NAMESPACE" );
219    
220            NamespaceContext nsContext = new NamespaceContext();
221            if ( nsString != null ) {
222                String nsDecls[] = nsString.split( "," );
223                for ( int i = 0; i < nsDecls.length; i++ ) {
224                    String nsDecl = nsDecls[i];
225                    if ( nsDecl.startsWith( "xmlns(" ) && nsDecl.endsWith( ")" ) ) {
226                        nsDecl = nsDecl.substring( 6, nsDecl.length() - 1 );
227                        int assignIdx = nsDecl.indexOf( '=' );
228                        String prefix = "";
229                        String nsURIString = null;
230                        if ( assignIdx != -1 ) {
231                            prefix = nsDecl.substring( 0, assignIdx );
232                            nsURIString = nsDecl.substring( assignIdx + 1 );
233                        } else {
234                            nsURIString = nsDecl;
235                        }
236                        try {
237                            URI nsURI = new URI( nsURIString );
238                            nsContext.addNamespace( prefix, nsURI );
239                        } catch ( URISyntaxException e ) {
240                            String msg = Messages.getMessage( "WFS_NAMESPACE_PARAM_INVALID_URI", nsURIString, prefix );
241                            throw new InvalidParameterValueException( msg );
242                        }
243                    } else {
244                        String msg = Messages.getMessage( "WFS_NAMESPACE_PARAM" );
245                        throw new InvalidParameterValueException( msg );
246                    }
247                }
248            }
249            return nsContext;
250        }
251    
252        /**
253         * Extracts a <code>Filter</code> from the BBOX parameter.
254         * 
255         * TODO handle other dimension count and crs
256         * 
257         * @param model
258         * @return filter representing the BBOX parameter (null, if no BBOX parameter specified)
259         * @throws InvalidParameterValueException
260         */
261        protected static Filter extractBBOXFilter( Map<String, String> model )
262                                throws InvalidParameterValueException {
263    
264            ComplexFilter filter = null;
265            String bboxString = model.get( "BBOX" );
266            if ( bboxString != null ) {
267                String msg = "Parameter 'BBOX' is currently not supported. Please use the 'FILTER' parameter instead.";
268                throw new InvalidParameterValueException( msg );
269                // String[] parts = bboxString.split( "," );
270                // double[] coords = new double[4];
271                //
272                // if ( parts.length > 5 ) {
273                // String msg = Messages.getString( "WFS_BBOX_PARAM_WRONG_COORD_COUNT" );
274                // throw new InvalidParameterValueException( msg );
275                // }
276                //
277                // for ( int i = 0; i < coords.length; i++ ) {
278                // try {
279                // coords[i] = Double.parseDouble( parts[i] );
280                // } catch ( NumberFormatException e ) {
281                // String msg = Messages.getMessage( "WFS_BBOX_PARAM_COORD_INVALID", coords[i] );
282                // throw new InvalidParameterValueException( msg );
283                // }
284                // }
285                //
286                // // build filter
287                // Envelope bbox = GeometryFactory.createEnvelope( coords[0], coords[1], coords[2],
288                // coords[3], null );
289                // Surface surface;
290                // try {
291                // surface = GeometryFactory.createSurface( bbox, null );
292                // } catch ( GeometryException e ) {
293                // String msg = Messages.getMessage( "WFS_BBOX_PARAM_BBOX_INVALID", e.getMessage() );
294                // throw new InvalidParameterValueException( msg );
295                // }
296                // Operation op = new SpatialOperation( OperationDefines.BBOX, null, surface );
297                // filter = new ComplexFilter( op );
298            }
299            return filter;
300        }
301    
302        /**
303         * Extracts the FILTER parameter and assigns them to the requested type names.
304         * <p>
305         * This is necessary, because it is allowed to specify a filter for each requested feature type.
306         * 
307         * @param kvp
308         * @param typeNames
309         * @return map with the assignments of type names to filters
310         * @throws InvalidParameterValueException
311         */
312        protected static Map<QualifiedName, Filter> extractFilters( Map<String, String> kvp, QualifiedName[] typeNames )
313                                throws InvalidParameterValueException {
314            Map<QualifiedName, Filter> filterMap = new HashMap<QualifiedName, Filter>();
315            String filterString = kvp.get( "FILTER" );
316            if ( filterString != null ) {
317                String[] filterStrings = filterString.split( "\\)" );
318                if ( filterStrings.length != typeNames.length ) {
319                    String msg = Messages.getMessage( "WFS_FILTER_PARAM_WRONG_COUNT", Integer.toString( filterStrings.length ),
320                                                      Integer.toString( typeNames.length ) );
321                    throw new InvalidParameterValueException( msg );
322                }
323                for ( int i = 0; i < filterStrings.length; i++ ) {
324                    // remove possible leading parenthesis
325                    if ( filterStrings[i].startsWith( "(" ) ) {
326                        filterStrings[i] = filterStrings[i].substring( 1 );
327                    }
328                    Document doc;
329                    try {
330                        doc = XMLTools.parse( new StringReader( filterStrings[i] ) );
331                        Filter filter = AbstractFilter.buildFromDOM( doc.getDocumentElement() );
332                        filterMap.put( typeNames[i], filter );
333                    } catch ( Exception e ) {
334                        LOG.logError( e.getMessage(), e );
335                        String msg = Messages.getMessage( "WFS_FILTER_PARAM_PARSING", e.getMessage() );
336                        throw new InvalidParameterValueException( msg );
337                    }
338                }
339            }
340            return filterMap;
341        }
342    
343        /**
344         * Transforms a type name to a qualified name using the given namespace bindings.
345         * 
346         * @param name
347         * @param nsContext
348         * @return QualifiedName
349         * @throws InvalidParameterValueException
350         */
351        private static QualifiedName transformToQualifiedName( String name, NamespaceContext nsContext )
352                                throws InvalidParameterValueException {
353            QualifiedName typeName;
354            String prefix = "";
355            int idx = name.indexOf( ':' );
356            if ( idx != -1 ) {
357                prefix = name.substring( 0, idx );
358                String localName = name.substring( idx + 1 );
359                URI nsURI = nsContext.getURI( prefix );
360                if ( nsURI == null ) {
361                    String msg = Messages.getMessage( "WFS_TYPENAME_PARAM_INVALID_URI", prefix );
362                    throw new InvalidParameterValueException( msg );
363                }
364                typeName = new QualifiedName( prefix, localName, nsURI );
365            } else {
366                // default namespace prefix ("")
367                URI nsURI = nsContext.getURI( "" );
368                typeName = new QualifiedName( name, nsURI );
369            }
370            return typeName;
371        }
372    }