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 }