001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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 }