001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/ogcwebservices/wfs/operation/transaction/TransactionDocument.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.transaction; 037 038 import static org.deegree.framework.xml.XMLTools.appendElement; 039 import static org.deegree.framework.xml.XMLTools.getNodeAsString; 040 import static org.deegree.framework.xml.XMLTools.getNodeAsURI; 041 import static org.deegree.framework.xml.XMLTools.getRequiredElements; 042 import static org.deegree.i18n.Messages.get; 043 import static org.deegree.ogcbase.CommonNamespaces.GML_PREFIX; 044 import static org.deegree.ogcbase.CommonNamespaces.WFSNS; 045 import static org.deegree.ogcbase.CommonNamespaces.WFS_PREFIX; 046 047 import java.io.IOException; 048 import java.net.URI; 049 import java.net.URL; 050 import java.util.ArrayList; 051 import java.util.LinkedHashMap; 052 import java.util.List; 053 import java.util.Map; 054 055 import org.deegree.datatypes.QualifiedName; 056 import org.deegree.framework.log.ILogger; 057 import org.deegree.framework.log.LoggerFactory; 058 import org.deegree.framework.xml.XMLParsingException; 059 import org.deegree.framework.xml.XMLTools; 060 import org.deegree.i18n.Messages; 061 import org.deegree.model.crs.UnknownCRSException; 062 import org.deegree.model.feature.Feature; 063 import org.deegree.model.feature.FeatureCollection; 064 import org.deegree.model.feature.FeatureFactory; 065 import org.deegree.model.feature.FeatureProperty; 066 import org.deegree.model.feature.GMLFeatureCollectionDocument; 067 import org.deegree.model.feature.GMLFeatureDocument; 068 import org.deegree.model.filterencoding.AbstractFilter; 069 import org.deegree.model.filterencoding.Filter; 070 import org.deegree.ogcbase.CommonNamespaces; 071 import org.deegree.ogcbase.PropertyPath; 072 import org.deegree.ogcwebservices.InvalidParameterValueException; 073 import org.deegree.ogcwebservices.MissingParameterValueException; 074 import org.deegree.ogcwebservices.wfs.operation.AbstractWFSRequestDocument; 075 import org.deegree.ogcwebservices.wfs.operation.transaction.Insert.ID_GEN; 076 import org.w3c.dom.Element; 077 import org.w3c.dom.Node; 078 import org.w3c.dom.Text; 079 import org.xml.sax.SAXException; 080 081 /** 082 * Parser for "wfs:Transaction" requests and contained elements. 083 * 084 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a> 085 * @author last edited by: $Author: mschneider $ 086 * 087 * @version $Revision: 18544 $, $Date: 2009-07-20 16:14:38 +0200 (Mo, 20 Jul 2009) $ 088 */ 089 public class TransactionDocument extends AbstractWFSRequestDocument { 090 091 private static ILogger LOG = LoggerFactory.getLogger( TransactionDocument.class ); 092 093 private static final long serialVersionUID = -394478447170286393L; 094 095 private static final String XML_TEMPLATE = "TransactionTemplate.xml"; 096 097 private static final QualifiedName VALUE_ELEMENT_NAME = new QualifiedName( "wfs", "Value", CommonNamespaces.WFSNS ); 098 099 /** 100 * Creates a skeleton document that contains the root element and the namespace bindings only. 101 * 102 * @throws IOException 103 * @throws SAXException 104 */ 105 public void createEmptyDocument() 106 throws IOException, SAXException { 107 URL url = TransactionDocument.class.getResource( XML_TEMPLATE ); 108 if ( url == null ) { 109 throw new IOException( "The resource '" + XML_TEMPLATE + " could not be found." ); 110 } 111 load( url ); 112 } 113 114 /** 115 * Parses the underlying document into a <code>Transaction</code> request object. 116 * 117 * @param id 118 * @return corresponding <code>Transaction</code> object 119 * @throws XMLParsingException 120 * @throws InvalidParameterValueException 121 */ 122 public Transaction parse( String id ) 123 throws XMLParsingException, InvalidParameterValueException { 124 125 checkServiceAttribute(); 126 String version = checkVersionAttribute(); 127 128 Element root = this.getRootElement(); 129 String lockId = null; 130 boolean releaseAllFeatures = parseReleaseActionParameter(); 131 132 List<TransactionOperation> operations = new ArrayList<TransactionOperation>(); 133 List<Element> list = XMLTools.getElements( root, "*", nsContext ); 134 for ( int i = 0; i < list.size(); i++ ) { 135 Element element = list.get( i ); 136 if ( "LockId".equals( element.getLocalName() ) 137 && CommonNamespaces.WFSNS.toString().equals( element.getNamespaceURI() ) ) { 138 lockId = XMLTools.getNodeAsString( element, "text()", nsContext, null ); 139 } else { 140 TransactionOperation operation; 141 142 // workaround because API changes are not allowed 143 try { 144 operation = parseOperation( element ); 145 operations.add( operation ); 146 } catch ( MissingParameterValueException e ) { 147 throw new XMLParsingException( true, e ); 148 } 149 } 150 } 151 152 // vendorspecific attributes; required by deegree rights management 153 Map<String, String> vendorSpecificParams = parseDRMParams( root ); 154 155 return new Transaction( id, version, vendorSpecificParams, lockId, operations, releaseAllFeatures, this ); 156 } 157 158 /** 159 * Parses the optional "releaseAction" attribute of the root element. 160 * 161 * @return true, if releaseAction equals "ALL" (or is left out), false if it equals "SOME" 162 * @throws InvalidParameterValueException 163 * if parameter 164 * @throws XMLParsingException 165 */ 166 private boolean parseReleaseActionParameter() 167 throws InvalidParameterValueException, XMLParsingException { 168 169 String releaseAction = XMLTools.getNodeAsString( getRootElement(), "@releaseAction", nsContext, "ALL" ); 170 boolean releaseAllFeatures = true; 171 if ( releaseAction != null ) { 172 if ( "SOME".equals( releaseAction ) ) { 173 releaseAllFeatures = false; 174 } else if ( "ALL".equals( releaseAction ) ) { 175 releaseAllFeatures = true; 176 } else { 177 throw new InvalidParameterValueException( "releaseAction", releaseAction ); 178 } 179 } 180 return releaseAllFeatures; 181 } 182 183 /** 184 * Parses the given element as a <code>TransactionOperation</code>. 185 * <p> 186 * The given element must be one of the following: 187 * <ul> 188 * <li>wfs:Insert</li> 189 * <li>wfs:Update</li> 190 * <li>wfs:Delete</li> 191 * <li>wfs:Native</li> 192 * </ul> 193 * 194 * @param element 195 * operation element 196 * @return corresponding <code>TransactionOperation</code> object 197 * @throws XMLParsingException 198 * @throws InvalidParameterValueException 199 * @throws MissingParameterValueException 200 */ 201 private TransactionOperation parseOperation( Element element ) 202 throws XMLParsingException, InvalidParameterValueException, MissingParameterValueException { 203 204 TransactionOperation operation = null; 205 206 if ( !element.getNamespaceURI().equals( CommonNamespaces.WFSNS.toString() ) ) { 207 String msg = Messages.getMessage( "WFS_INVALID_OPERATION", element.getNodeName() ); 208 throw new XMLParsingException( msg ); 209 } 210 if ( element.getLocalName().equals( "Insert" ) ) { 211 operation = parseInsert( element ); 212 } else if ( element.getLocalName().equals( "Update" ) ) { 213 operation = parseUpdate( element ); 214 } else if ( element.getLocalName().equals( "Delete" ) ) { 215 operation = parseDelete( element ); 216 } else if ( element.getLocalName().equals( "Native" ) ) { 217 throw new InvalidParameterValueException( get( "WFS_NATIVE_OPERATIONS_UNSUPPORTED" ) ); 218 } else { 219 String msg = Messages.getMessage( "WFS_INVALID_OPERATION", element.getNodeName() ); 220 throw new XMLParsingException( msg ); 221 } 222 223 return operation; 224 } 225 226 /** 227 * Parses the given element as a "wfs:Insert" operation. 228 * 229 * @param element 230 * "wfs:Insert" operation 231 * @return corresponding Insert object 232 * @throws XMLParsingException 233 * @throws InvalidParameterValueException 234 */ 235 private Insert parseInsert( Element element ) 236 throws XMLParsingException, InvalidParameterValueException { 237 FeatureCollection fc = null; 238 ID_GEN mode = parseIdGen( element ); 239 String handle = getNodeAsString( element, "@handle", nsContext, null ); 240 URI srsName = getNodeAsURI( element, "@srsName", nsContext, null ); 241 List<Element> childElementList = getRequiredElements( element, "*", nsContext ); 242 243 // either one _gml:FeatureCollection element or any number of _gml:Feature elements 244 boolean isFeatureCollection = isFeatureCollection( childElementList.get( 0 ) ); 245 246 try { 247 GMLFeatureCollectionDocument doc = new GMLFeatureCollectionDocument( false ); 248 249 if ( isFeatureCollection ) { 250 LOG.logDebug( "Insert (FeatureCollection)" ); 251 doc.setRootElement( childElementList.get( 0 ) ); 252 } else { 253 LOG.logDebug( "Insert (Features)" ); 254 Element fcElem = appendElement( element, WFSNS, WFS_PREFIX + ":FeatureCollection" ); 255 256 for ( Element e : childElementList ) { 257 Node old = element.removeChild( e ); 258 Element elem = appendElement( fcElem, GMLNS, GML_PREFIX + ":featureMember" ); 259 elem.appendChild( old ); 260 } 261 262 doc.setRootElement( fcElem ); 263 } 264 265 doc.setSystemId( this.getSystemId() ); 266 fc = doc.parse(); 267 } catch ( XMLParsingException e ) { 268 LOG.logDebug( "Stack trace:", e ); 269 throw new InvalidParameterValueException( e.getMessage() ); 270 } catch ( Exception e ) { 271 throw new XMLParsingException( e.getMessage(), e ); 272 } 273 274 return new Insert( handle, mode, srsName, fc ); 275 } 276 277 /** 278 * Checks whether the given element is a (concrete) gml:_FeatureCollection element. 279 * <p> 280 * NOTE: This check is far from perfect. Instead of determining the type of the element by inspecting the schema, 281 * the decision is made by checking if the element name contains 'FeatureCollection'. Also, if child elements with 282 * name "gml:featureMember" are found, it is considered to be a feature collection. 283 * 284 * @param element 285 * potential gml:_FeatureCollection element 286 * @return true, if the given element appears to be a gml:_FeatureCollection element, false otherwise 287 * @throws XMLParsingException 288 */ 289 private boolean isFeatureCollection( Element element ) 290 throws XMLParsingException { 291 boolean containsFeatureCollection = false; 292 if ( element.getLocalName().contains( "FeatureCollection" ) ) { 293 containsFeatureCollection = true; 294 } else { 295 List<Node> nodeList = XMLTools.getNodes( element, "gml:featureMember", nsContext ); 296 if ( nodeList.size() > 0 ) { 297 containsFeatureCollection = true; 298 } 299 } 300 return containsFeatureCollection; 301 } 302 303 /** 304 * Parses the optional "idGen" attribute of the given "wfs:Insert" element. 305 * 306 * @param element 307 * "wfs:Insert" element 308 * @return "idGen" attribute code 309 * @throws XMLParsingException 310 */ 311 private ID_GEN parseIdGen( Element element ) 312 throws XMLParsingException { 313 ID_GEN mode; 314 String idGen = XMLTools.getNodeAsString( element, "@idgen", nsContext, Insert.ID_GEN_GENERATE_NEW_STRING ); 315 316 if ( Insert.ID_GEN_GENERATE_NEW_STRING.equals( idGen ) ) { 317 mode = Insert.ID_GEN.GENERATE_NEW; 318 } else if ( Insert.ID_GEN_USE_EXISTING_STRING.equals( idGen ) ) { 319 mode = Insert.ID_GEN.USE_EXISTING; 320 } else if ( Insert.ID_GEN_REPLACE_DUPLICATE_STRING.equals( idGen ) ) { 321 mode = Insert.ID_GEN.REPLACE_DUPLICATE; 322 } else { 323 String msg = Messages.getMessage( "WFS_INVALID_IDGEN_VALUE", idGen, Insert.ID_GEN_GENERATE_NEW_STRING, 324 Insert.ID_GEN_REPLACE_DUPLICATE_STRING, Insert.ID_GEN_USE_EXISTING_STRING ); 325 throw new XMLParsingException( msg ); 326 } 327 return mode; 328 } 329 330 /** 331 * Parses the given element as a "wfs:Update" operation. 332 * 333 * @param element 334 * "wfs:Update" operation 335 * @return corresponding Update object 336 * @throws XMLParsingException 337 */ 338 private Update parseUpdate( Element element ) 339 throws XMLParsingException { 340 341 Update update = null; 342 String handle = XMLTools.getNodeAsString( element, "@handle", nsContext, null ); 343 QualifiedName typeName = XMLTools.getRequiredNodeAsQualifiedName( element, "@typeName", nsContext ); 344 345 Element filterElement = (Element) XMLTools.getNode( element, "ogc:Filter", nsContext ); 346 Filter filter = null; 347 if ( filterElement != null ) { 348 filter = AbstractFilter.buildFromDOM( filterElement, false ); 349 } 350 351 List<Node> properties = XMLTools.getNodes( element, "wfs:Property", nsContext ); 352 if ( properties.size() > 0 ) { 353 // "standard" update (specifies properties + their replacement values) 354 LOG.logDebug( "Update (replacement properties)" ); 355 Map<PropertyPath, FeatureProperty> replacementProps = new LinkedHashMap<PropertyPath, FeatureProperty>(); 356 Map<PropertyPath, Node> rawProps = new LinkedHashMap<PropertyPath, Node>(); 357 for ( int i = 0; i < properties.size(); i++ ) { 358 Node propNode = properties.get( i ); 359 Text propNameNode = (Text) XMLTools.getRequiredNode( propNode, "wfs:Name/text()", nsContext ); 360 PropertyPath propPath = parsePropertyPath( propNameNode ); 361 362 // TODO remove this (only needed, because Update makes the DOM representation available) 363 Node valueNode = XMLTools.getNode( propNode, "wfs:Value/*", nsContext ); 364 if ( valueNode == null ) { 365 valueNode = XMLTools.getNode( propNode, "wfs:Value/text()", nsContext ); 366 } 367 rawProps.put( propPath, propNode ); 368 369 FeatureProperty replacementProp = null; 370 QualifiedName propName = propPath.getStep( propPath.getSteps() - 1 ).getPropertyName(); 371 372 if ( replacementProps.get( propPath ) != null ) { 373 String msg = Messages.getMessage( "WFS_UPDATE_DUPLICATE_PROPERTY", handle, propPath ); 374 throw new XMLParsingException( msg ); 375 } 376 377 // TODO improve generation of FeatureProperty from "wfs:Value" element 378 Object propValue = null; 379 Node node = XMLTools.getNode( propNode, "wfs:Value", nsContext ); 380 if ( node != null ) { 381 GMLFeatureDocument dummyDoc = new GMLFeatureDocument( false ); 382 dummyDoc.setRootElement( (Element) propNode ); 383 dummyDoc.setSystemId( this.getSystemId() ); 384 Feature dummyFeature; 385 try { 386 dummyFeature = dummyDoc.parseFeature(); 387 } catch ( UnknownCRSException e ) { 388 throw new XMLParsingException( e.getMessage(), e ); 389 } 390 propValue = dummyFeature.getProperties( VALUE_ELEMENT_NAME )[0].getValue(); 391 } 392 replacementProp = FeatureFactory.createFeatureProperty( propName, propValue ); 393 replacementProps.put( propPath, replacementProp ); 394 } 395 update = new Update( handle, typeName, replacementProps, rawProps, filter ); 396 } else { 397 // deegree specific update (specifies a single replacement feature) 398 LOG.logDebug( "Update (replacement feature)" ); 399 Feature replacementFeature = null; 400 List<Element> childElementList = XMLTools.getRequiredElements( element, "*", nsContext ); 401 if ( ( filter == null && childElementList.size() != 1 ) 402 || ( filter != null && childElementList.size() != 2 ) ) { 403 String msg = Messages.getMessage( "WFS_UPDATE_FEATURE_REPLACE", handle ); 404 throw new XMLParsingException( msg ); 405 } 406 try { 407 GMLFeatureDocument doc = new GMLFeatureDocument( false ); 408 doc.setRootElement( childElementList.get( 0 ) ); 409 doc.setSystemId( this.getSystemId() ); 410 replacementFeature = doc.parseFeature(); 411 } catch ( Exception e ) { 412 String msg = Messages.getMessage( "WFS_UPDATE_FEATURE_REPLACE", handle ); 413 throw new XMLParsingException( msg ); 414 } 415 update = new Update( handle, typeName, replacementFeature, filter ); 416 } 417 418 return update; 419 } 420 421 /** 422 * Parses the given element as a "wfs:Delete" operation. 423 * 424 * @param element 425 * "wfs:Delete" operation 426 * @return corresponding Delete object 427 * @throws MissingParameterValueException 428 */ 429 private Delete parseDelete( Element element ) 430 throws XMLParsingException, MissingParameterValueException { 431 432 String handle = XMLTools.getNodeAsString( element, "@handle", nsContext, null ); 433 QualifiedName typeName = XMLTools.getRequiredNodeAsQualifiedName( element, "@typeName", nsContext ); 434 435 Element filterElement = XMLTools.getElement( element, "ogc:Filter", nsContext ); 436 if ( filterElement == null ) { 437 throw new MissingParameterValueException( "filter", get( "WFS_MISSING_REQUIRED_ELEMENT", "ogc:Filter" ) ); 438 } 439 440 Filter filter = AbstractFilter.buildFromDOM( filterElement, false ); 441 return new Delete( handle, typeName, filter ); 442 } 443 }