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 }