001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/ogcwebservices/wfs/operation/GetFeatureDocument.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.Integer.parseInt; 039 import static java.lang.Math.toDegrees; 040 import static org.deegree.crs.coordinatesystems.GeographicCRS.WGS84; 041 import static org.deegree.framework.log.LoggerFactory.getLogger; 042 import static org.deegree.framework.xml.XMLTools.getElements; 043 import static org.deegree.framework.xml.XMLTools.getNodeAsString; 044 import static org.deegree.framework.xml.XMLTools.getNodes; 045 import static org.deegree.framework.xml.XMLTools.getRequiredNodeAsString; 046 import static org.deegree.i18n.Messages.get; 047 import static org.deegree.i18n.Messages.getMessage; 048 import static org.deegree.model.crs.CRSFactory.create; 049 import static org.deegree.model.filterencoding.OperationDefines.BBOX; 050 import static org.deegree.model.filterencoding.OperationDefines.TYPE_LOGICAL; 051 import static org.deegree.model.filterencoding.OperationDefines.getTypeById; 052 import static org.deegree.ogcwebservices.wfs.configuration.WFSDeegreeParams.getSwitchAxes; 053 import static org.deegree.ogcwebservices.wfs.operation.AbstractWFSRequest.FORMAT_GML2_WFS100; 054 import static org.deegree.ogcwebservices.wfs.operation.AbstractWFSRequest.FORMAT_GML3; 055 056 import java.security.InvalidParameterException; 057 import java.util.Arrays; 058 import java.util.HashSet; 059 import java.util.List; 060 import java.util.Map; 061 import java.util.Set; 062 063 import javax.vecmath.Point2d; 064 065 import org.deegree.crs.coordinatesystems.ProjectedCRS; 066 import org.deegree.datatypes.QualifiedName; 067 import org.deegree.framework.log.ILogger; 068 import org.deegree.framework.xml.XMLParsingException; 069 import org.deegree.framework.xml.XMLTools; 070 import org.deegree.i18n.Messages; 071 import org.deegree.model.crs.CRSTransformationException; 072 import org.deegree.model.crs.CoordinateSystem; 073 import org.deegree.model.crs.GeoTransformer; 074 import org.deegree.model.crs.UnknownCRSException; 075 import org.deegree.model.filterencoding.AbstractFilter; 076 import org.deegree.model.filterencoding.ComplexFilter; 077 import org.deegree.model.filterencoding.Filter; 078 import org.deegree.model.filterencoding.Function; 079 import org.deegree.model.filterencoding.LogicalOperation; 080 import org.deegree.model.filterencoding.Operation; 081 import org.deegree.model.filterencoding.SpatialOperation; 082 import org.deegree.model.spatialschema.Envelope; 083 import org.deegree.model.spatialschema.GeometryFactory; 084 import org.deegree.ogcbase.PropertyPath; 085 import org.deegree.ogcbase.SortProperty; 086 import org.deegree.ogcwebservices.InvalidParameterValueException; 087 import org.deegree.ogcwebservices.wfs.operation.GetFeature.RESULT_TYPE; 088 import org.jaxen.JaxenException; 089 import org.jaxen.XPath; 090 import org.jaxen.dom.DOMXPath; 091 import org.w3c.dom.Element; 092 import org.w3c.dom.Node; 093 094 /** 095 * Parser for "wfs:GetFeature" requests. 096 * 097 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a> 098 * @author last edited by: $Author: mschneider $ 099 * 100 * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18 Jun 2009) $ 101 */ 102 public class GetFeatureDocument extends AbstractWFSRequestDocument { 103 104 private static final ILogger LOG = getLogger( GetFeatureDocument.class ); 105 106 private static final long serialVersionUID = -3411186861123355322L; 107 108 /** 109 * Parses the underlying document into a <code>GetFeature</code> request object. 110 * 111 * @param id 112 * @return corresponding <code>GetFeature</code> object 113 * @throws XMLParsingException 114 * @throws InvalidParameterValueException 115 */ 116 public GetFeature parse( String id ) 117 throws InvalidParameterValueException, XMLParsingException { 118 119 checkServiceAttribute(); 120 String version = checkVersionAttribute(); 121 boolean useVersion_1_0_0 = "1.0.0".equals( version ); 122 123 Element root = getRootElement(); 124 String handle = XMLTools.getNodeAsString( root, "@handle", nsContext, null ); 125 String outputFormat = XMLTools.getNodeAsString( root, "@outputFormat", nsContext, 126 useVersion_1_0_0 ? FORMAT_GML2_WFS100 : FORMAT_GML3 ); 127 128 int maxFeatures = XMLTools.getNodeAsInt( root, "@maxFeatures", nsContext, -1 ); 129 int startPosition = XMLTools.getNodeAsInt( root, "@startPosition", nsContext, 1 ); 130 if ( startPosition < 1 ) { 131 String msg = Messages.getMessage( "WFS_INVALID_STARTPOSITION", Integer.toString( startPosition ) ); 132 throw new XMLParsingException( msg ); 133 } 134 135 String depth = XMLTools.getNodeAsString( root, "@traverseXlinkDepth", nsContext, "*" ); 136 int traverseXLinkDepth = depth.equals( "*" ) ? -1 : parseInt( depth ); 137 int traverseXLinkExpiry = XMLTools.getNodeAsInt( root, "@traverseXlinkExpiry", nsContext, -1 ); 138 139 String resultTypeString = XMLTools.getNodeAsString( root, "@resultType", nsContext, "results" ); 140 RESULT_TYPE resultType; 141 if ( "results".equals( resultTypeString ) ) { 142 resultType = RESULT_TYPE.RESULTS; 143 } else if ( "hits".equals( resultTypeString ) ) { 144 resultType = RESULT_TYPE.HITS; 145 } else { 146 String msg = Messages.getMessage( "WFS_INVALID_RESULT_TYPE", resultTypeString ); 147 throw new XMLParsingException( msg ); 148 } 149 150 // next time, perhaps we'll use real validation instead of ad hoc "validation" like this 151 List<Element> nl = getElements( root, "wfs:Query", nsContext ); 152 try { 153 XPath xpath = new DOMXPath( "count(*)" ); 154 xpath.numberValueOf( root ); 155 156 int cnt = xpath.numberValueOf( root ).intValue(); 157 158 if ( cnt != nl.size() ) { 159 throw new InvalidParameterValueException( getMessage( "WFS_ONLY_QUERY_ELEMENTS_PERMITTED" ) ); 160 } 161 162 } catch ( JaxenException e ) { 163 // the xpath was tested... 164 } 165 166 if ( nl.size() == 0 ) { 167 throw new InvalidParameterValueException( getMessage( "WFS_QUERY_ELEMENT_MISSING" ) ); 168 } 169 Query[] queries = new Query[nl.size()]; 170 for ( int i = 0; i < queries.length; i++ ) { 171 queries[i] = parseQuery( nl.get( i ), useVersion_1_0_0 ); 172 } 173 174 // vendorspecific attributes; required by deegree rights management 175 Map<String, String> vendorSpecificParams = parseDRMParams( root ); 176 177 GetFeature req = new GetFeature( version, id, handle, resultType, outputFormat, maxFeatures, startPosition, 178 traverseXLinkDepth, traverseXLinkExpiry, queries, vendorSpecificParams ); 179 return req; 180 } 181 182 /** 183 * Parses the given query element into a {@link Query} object with filter encoding 1.1.0. 184 * <p> 185 * Note that the following attributes from the surrounding element are also considered (if it is present): 186 * <ul> 187 * <li>resultType</li> 188 * <li>maxFeatures</li> 189 * <li>startPosition</li> 190 * </ul> 191 * 192 * @param element 193 * query element 194 * @return corresponding <code>Query</code> object 195 * @throws XMLParsingException 196 */ 197 Query parseQuery( Element element ) 198 throws XMLParsingException { 199 return parseQuery( element, false ); 200 201 } 202 203 /** 204 * Parses the given query element into a {@link Query} object. 205 * <p> 206 * Note that the following attributes from the surrounding element are also considered (if it is present): 207 * <ul> 208 * <li>resultType</li> 209 * <li>maxFeatures</li> 210 * <li>startPosition</li> 211 * </ul> 212 * 213 * @param element 214 * query element 215 * @param useVersion_1_0_0 216 * true, if the query is part of a 1.0.0 GetFeature request, otherwise false 217 * @return corresponding <code>Query</code> object 218 * @throws XMLParsingException 219 */ 220 Query parseQuery( Element element, boolean useVersion_1_0_0 ) 221 throws XMLParsingException { 222 223 String handle = getNodeAsString( element, "@handle", nsContext, null ); 224 String typeNameList = getRequiredNodeAsString( element, "@typeName", nsContext ); 225 // handle both 1.1.0 and 1.2.0 delimiters 226 String[] values = typeNameList.split( "[,\\s]" ); 227 QualifiedName[] typeNames = transformToQualifiedNames( values, element ); 228 String[] aliases = null; 229 String aliasesList = getNodeAsString( element, "@aliases", nsContext, null ); 230 if ( aliasesList != null ) { 231 aliases = aliasesList.split( "\\s" ); 232 if ( LOG.isDebug() ) { 233 LOG.logDebug( "Found following aliases:" + Arrays.toString( aliases ) ); 234 } 235 236 if ( aliases.length != typeNames.length ) { 237 String msg = getMessage( "WFS_QUERY_ALIAS_WRONG_COUNT", Integer.toString( typeNames.length ), 238 Integer.toString( aliases.length ) ); 239 throw new XMLParsingException( msg ); 240 } 241 Set<String> tempSet = new HashSet<String>(); 242 for ( String alias : aliases ) { 243 if ( tempSet.contains( alias ) ) { 244 String msg = getMessage( "WFS_QUERY_ALIAS_NOT_UNIQUE", alias ); 245 throw new XMLParsingException( msg ); 246 } 247 tempSet.add( alias ); 248 } 249 } 250 251 String featureVersion = getNodeAsString( element, "@featureVersion", nsContext, null ); 252 String srsName = getNodeAsString( element, "@srsName", nsContext, null ); 253 254 List<Node> nl = null; 255 if ( useVersion_1_0_0 ) { 256 nl = getNodes( element, "ogc:PropertyName", nsContext ); 257 } else { 258 nl = getNodes( element, "wfs:PropertyName | wfs:XlinkPropertyName", nsContext ); 259 } 260 PropertyPath[] propertyNames = new PropertyPath[nl.size()]; 261 for ( int i = 0; i < propertyNames.length; i++ ) { 262 propertyNames[i] = parseExtendedPropertyPath( (Element) nl.get( i ) ); 263 } 264 265 nl = XMLTools.getNodes( element, "ogc:Function", nsContext ); 266 Function[] functions = new Function[nl.size()]; 267 for ( int i = 0; i < functions.length; i++ ) { 268 functions[i] = (Function) Function.buildFromDOM( (Element) nl.get( i ) ); 269 } 270 271 Filter filter = null; 272 Element filterElement = (Element) XMLTools.getNode( element, "ogc:Filter", nsContext ); 273 if ( filterElement != null ) { 274 filter = AbstractFilter.buildFromDOM( filterElement, useVersion_1_0_0 ); 275 } 276 277 SortProperty[] sortProps = null; 278 Element sortByElement = (Element) XMLTools.getNode( element, "ogc:SortBy", nsContext ); 279 if ( sortByElement != null ) { 280 sortProps = parseSortBy( sortByElement ); 281 } 282 283 // ---------------------------------------------------------------------------------------- 284 // parse "inherited" attributes from GetFeature element (but very kindly) 285 // ---------------------------------------------------------------------------------------- 286 287 String resultTypeString = "results"; 288 RESULT_TYPE resultType; 289 try { 290 resultTypeString = XMLTools.getNodeAsString( element, "../@resultType", nsContext, "results" ); 291 } catch ( XMLParsingException doNothing ) { 292 // it's o.k. - let's be really kind here 293 } 294 295 if ( "results".equals( resultTypeString ) ) { 296 resultType = RESULT_TYPE.RESULTS; 297 } else if ( "hits".equals( resultTypeString ) ) { 298 resultType = RESULT_TYPE.HITS; 299 } else { 300 String msg = Messages.getMessage( "WFS_INVALID_RESULT_TYPE", resultTypeString ); 301 throw new XMLParsingException( msg ); 302 } 303 304 int maxFeatures = -1; 305 try { 306 maxFeatures = XMLTools.getNodeAsInt( element, "../@maxFeatures", nsContext, -1 ); 307 } catch ( XMLParsingException doNothing ) { 308 // it's o.k. - let's be really kind here 309 } 310 311 int startPosition = -1; 312 try { 313 startPosition = XMLTools.getNodeAsInt( element, "../@startPosition", nsContext, 0 ); 314 } catch ( XMLParsingException doNothing ) { 315 // it's o.k. - let's be really kind here 316 } 317 318 BBoxTest test = new BBoxTest( srsName, filter ); 319 320 return new Query( propertyNames, functions, sortProps, handle, featureVersion, typeNames, aliases, srsName, 321 filter, resultType, maxFeatures, startPosition, test ); 322 } 323 324 /** 325 * <code>BBoxTest</code> is a helper class that encapsulates the check for bounding boxes. 326 * 327 * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a> 328 * @author last edited by: $Author: mschneider $ 329 * 330 * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18 Jun 2009) $ 331 */ 332 public static class BBoxTest { 333 private String srsName; 334 335 private Filter filter; 336 337 /** 338 * @param srsName 339 * @param filter 340 */ 341 public BBoxTest( String srsName, Filter filter ) { 342 this.srsName = srsName; 343 this.filter = filter; 344 } 345 346 /** 347 * @throws InvalidParameterValueException 348 */ 349 public void performTest() 350 throws InvalidParameterValueException { 351 if ( srsName == null ) { 352 return; 353 } 354 isBoundingBoxValid( srsName, filter ); 355 } 356 } 357 358 private static SpatialOperation extractFirstBBOX( Operation operation ) { 359 if ( operation.getOperatorId() == BBOX ) { 360 return (SpatialOperation) operation; 361 } 362 if ( getTypeById( operation.getOperatorId() ) == TYPE_LOGICAL ) { 363 for ( Operation op : ( (LogicalOperation) operation ).getArguments() ) { 364 SpatialOperation bbox = extractFirstBBOX( op ); 365 if ( bbox != null ) { 366 return bbox; 367 } 368 } 369 } 370 371 return null; 372 } 373 374 static void isBoundingBoxValid( String srsName, Filter filter ) 375 throws InvalidParameterValueException { 376 SpatialOperation bbox = null; 377 378 if ( filter instanceof ComplexFilter ) { 379 ComplexFilter cf = (ComplexFilter) filter; 380 bbox = extractFirstBBOX( cf.getOperation() ); 381 } 382 383 if ( bbox == null ) { 384 return; 385 } 386 387 try { 388 CoordinateSystem crs = create( srsName ); 389 390 if ( !( crs.getCRS() instanceof ProjectedCRS ) ) { 391 return; 392 } 393 394 String code = crs.getCRS().getIdentifier().split( ":" )[1]; 395 396 if ( !( code.compareTo( "26901" ) > 0 && code.compareTo( "26929" ) < 0 ) 397 && !( code.compareTo( "32601" ) > 0 && code.compareTo( "32660" ) < 0 ) 398 && !( code.compareTo( "32701" ) > 0 && code.compareTo( "32760" ) < 0 ) ) { 399 return; 400 } 401 402 Envelope bb = bbox.getGeometry().getEnvelope(); 403 bb = GeometryFactory.createEnvelope( bb.getMin(), bb.getMax(), bb.getCoordinateSystem() ); 404 if ( !bb.getCoordinateSystem().getCRS().equals( WGS84 ) ) { 405 bb = new GeoTransformer( create( WGS84 ) ).transform( bb, bb.getCoordinateSystem() ); 406 } 407 408 Point2d naturalOrigin = ( (ProjectedCRS) crs.getCRS() ).getProjection().getNaturalOrigin(); 409 410 boolean swap = getSwitchAxes(); 411 412 double degx = toDegrees( naturalOrigin.x ); 413 double left = swap ? bb.getMin().getX() : bb.getMin().getY(); 414 double right = swap ? bb.getMax().getX() : bb.getMin().getY(); 415 if ( !( degx > left && degx < right ) ) { 416 throw new InvalidParameterValueException( get( "WFS_WRONG_UTM_STRIPE", left, degx, right ) ); 417 } 418 419 } catch ( UnknownCRSException e ) { 420 LOG.logError( "A problem occurred while parsing the request. Please report the stack trace.", e ); 421 } catch ( ClassCastException e ) { 422 LOG.logError( "A problem occurred while parsing the request. Please report the stack trace.", e ); 423 } catch ( InvalidParameterException e ) { 424 LOG.logError( "A problem occurred while parsing the request. Please report the stack trace.", e ); 425 } catch ( CRSTransformationException e ) { 426 LOG.logError( "A problem occurred while parsing the request. Please report the stack trace.", e ); 427 } 428 } 429 430 /** 431 * Parses the given "ogc:SortBy" element. 432 * 433 * @param root 434 * "ogc:SortBy" element 435 * @return corresponding <code>SortProperty</code> instances (in original order) 436 */ 437 private SortProperty[] parseSortBy( Element root ) 438 throws XMLParsingException { 439 440 List<Node> nl = XMLTools.getRequiredNodes( root, "ogc:SortProperty", nsContext ); 441 SortProperty[] sortProps = new SortProperty[nl.size()]; 442 for ( int i = 0; i < nl.size(); i++ ) { 443 sortProps[i] = SortProperty.create( (Element) nl.get( i ) ); 444 } 445 return sortProps; 446 } 447 }