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