001 //$Header: /deegreerepository/deegree/src/org/deegree/ogcwebservices/wfs/operation/transaction/Transaction.java,v 1.11 2007/02/07 15:01:51 poth Exp $ 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 java.net.URI; 039 import java.util.ArrayList; 040 import java.util.HashSet; 041 import java.util.Iterator; 042 import java.util.List; 043 import java.util.Map; 044 import java.util.Set; 045 046 import org.deegree.datatypes.QualifiedName; 047 import org.deegree.framework.log.ILogger; 048 import org.deegree.framework.log.LoggerFactory; 049 import org.deegree.framework.util.KVP2Map; 050 import org.deegree.framework.xml.XMLParsingException; 051 import org.deegree.model.feature.FeatureFactory; 052 import org.deegree.model.feature.FeatureProperty; 053 import org.deegree.model.filterencoding.Filter; 054 import org.deegree.ogcbase.PropertyPath; 055 import org.deegree.ogcbase.PropertyPathStep; 056 import org.deegree.ogcwebservices.InconsistentRequestException; 057 import org.deegree.ogcwebservices.InvalidParameterValueException; 058 import org.deegree.ogcwebservices.MissingParameterValueException; 059 import org.deegree.ogcwebservices.OGCWebServiceException; 060 import org.deegree.ogcwebservices.wfs.WFService; 061 import org.deegree.ogcwebservices.wfs.operation.AbstractWFSRequest; 062 import org.w3c.dom.Element; 063 064 /** 065 * Represents a <code>Transaction</code> request to a web feature service. 066 * <p> 067 * A <code>Transaction</code> consists of a sequence of {@link Insert}, {@link Update}, {@link Delete} and 068 * {@link Native} operations. 069 * <p> 070 * From the WFS Specification 1.1.0 OGC 04-094 (#12, Pg.63): 071 * <p> 072 * A <code>Transaction</code> request is used to describe data transformation operations that are to be applied to web 073 * accessible feature instances. When the transaction has been completed, a web feature service will generate an XML 074 * response document indicating the completion status of the transaction. 075 * 076 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a> 077 * @author last edited by: $Author: aionita $ 078 * 079 * @version $Revision: 23794 $, $Date: 2010-04-23 15:05:33 +0200 (Fr, 23 Apr 2010) $ 080 */ 081 public class Transaction extends AbstractWFSRequest { 082 083 private static final long serialVersionUID = 6904739857311368390L; 084 085 private static final ILogger LOG = LoggerFactory.getLogger( Transaction.class ); 086 087 private List<TransactionOperation> operations; 088 089 // request version 090 private String version; 091 092 // transaction ID 093 private String id; 094 095 // LockID associated with the request 096 private String lockId; 097 098 /** 099 * Specifies if ALL records should be released or if SOME records, indicating only those records which have been 100 * modified will be released. The default is ALL. 101 */ 102 private RELEASE_ACTION releaseAction = RELEASE_ACTION.ALL; 103 104 private TransactionDocument sourceDocument; 105 106 /** Controls how locked features are treated when a transaction request is completed. */ 107 public static enum RELEASE_ACTION { 108 109 /** 110 * Indicates that the locks on all feature instances locked using the associated lockId should be released when 111 * the transaction completes, regardless of whether or not a particular feature instance in the locked set was 112 * actually operated upon. 113 */ 114 ALL, 115 116 /** 117 * Indicates that only the locks on feature instances modified by the transaction should be released. The other, 118 * unmodified, feature instances should remain locked using the same lockId so that subsequent transactions can 119 * operate on those feature instances. If an expiry period was specified, the expiry counter must be reset to 120 * zero after each transaction unless all feature instances in the locked set have been operated upon. 121 */ 122 SOME 123 } 124 125 /** 126 * Creates a new <code>Transaction</code> instance. 127 * 128 * @param version 129 * WFS version 130 * @param id 131 * Transaction id 132 * @param versionSpecificParameter 133 * @param lockID 134 * Lock Id 135 * @param operations 136 * List of operations to be carried out 137 * @param releaseAllFeatures 138 * @param sourceDocument 139 */ 140 public Transaction( String id, String version, Map<String, String> versionSpecificParameter, String lockID, 141 List<TransactionOperation> operations, boolean releaseAllFeatures, 142 TransactionDocument sourceDocument ) { 143 super( version, id, null, versionSpecificParameter ); 144 this.id = id; 145 this.version = version; 146 this.lockId = lockID; 147 this.operations = operations; 148 if ( !releaseAllFeatures ) { 149 this.releaseAction = RELEASE_ACTION.SOME; 150 } 151 this.sourceDocument = sourceDocument; 152 } 153 154 /** 155 * Returns the source document that was used to create this <code>Transaction</code> instance. 156 * 157 * @return the source document 158 */ 159 public TransactionDocument getSourceDocument() { 160 return this.sourceDocument; 161 } 162 163 /** 164 * Returns the {@link TransactionOperation}s that are contained in the transaction. 165 * 166 * @return the contained operations 167 */ 168 public List<TransactionOperation> getOperations() { 169 return this.operations; 170 } 171 172 /** 173 * Returns the lock identifier associated with this transaction. 174 * 175 * @return the lock identifier associated with this transaction if it exists, null otherwise 176 */ 177 public String getLockId() { 178 return this.lockId; 179 } 180 181 /** 182 * Returns the release action mode to be applied after the transaction finished successfully. 183 * 184 * @see RELEASE_ACTION 185 * @return the release action mode to be applied after the transaction finished successfully 186 */ 187 public RELEASE_ACTION getReleaseAction() { 188 return this.releaseAction; 189 } 190 191 /** 192 * Returns the names of the feature types that are affected by the transaction. 193 * 194 * @return the names of the affected feature types 195 */ 196 public Set<QualifiedName> getAffectedFeatureTypes() { 197 Set<QualifiedName> featureTypeSet = new HashSet<QualifiedName>(); 198 199 Iterator<TransactionOperation> iter = this.operations.iterator(); 200 while ( iter.hasNext() ) { 201 TransactionOperation operation = iter.next(); 202 featureTypeSet.addAll( operation.getAffectedFeatureTypes() ); 203 } 204 return featureTypeSet; 205 } 206 207 /** 208 * Creates a <code>Transaction</code> request from a key-value-pair encoding of the parameters contained in the 209 * passed variable 'request'. 210 * 211 * @param id 212 * id of the request 213 * @param request 214 * key-value-pair encoded GetFeature request 215 * @return new created Transaction instance 216 * @throws InconsistentRequestException 217 * @throws InvalidParameterValueException 218 * @throws MissingParameterValueException 219 */ 220 public static Transaction create( String id, String request ) 221 throws InconsistentRequestException, InvalidParameterValueException, 222 MissingParameterValueException { 223 224 Map<String, String> model = KVP2Map.toMap( request ); 225 model.put( "ID", id ); 226 227 return create( model ); 228 } 229 230 /** 231 * Creates a <code>Transaction</code> request from a key-value-pair encoding of the parameters contained in the 232 * given Map. 233 * 234 * @param model 235 * key-value-pair encoded Transaction request 236 * @return new Transaction instance 237 * @throws InconsistentRequestException 238 * @throws InvalidParameterValueException 239 * @throws MissingParameterValueException 240 */ 241 public static Transaction create( Map<String, String> model ) 242 throws InconsistentRequestException, InvalidParameterValueException, 243 MissingParameterValueException { 244 245 Map<String, String> versionSpecificParameter = null; 246 247 String id = model.get( "ID" ); 248 249 String version = checkVersionParameter( model ); 250 251 checkServiceParameter( model ); 252 253 String request = model.remove( "REQUEST" ); 254 if ( request == null ) { 255 throw new InconsistentRequestException( "Request parameter for a transaction request must be set." ); 256 } 257 258 String lockID = model.remove( "LOCKID" ); 259 260 String releaseAction = model.remove( "RELEASEACTION" ); 261 boolean releaseAllFeatures = true; 262 if ( releaseAction != null ) { 263 if ( "SOME".equals( releaseAction ) ) { 264 releaseAllFeatures = false; 265 } else if ( "ALL".equals( releaseAction ) ) { 266 releaseAllFeatures = true; 267 } else { 268 throw new InvalidParameterValueException( "releaseAction", releaseAction ); 269 } 270 } 271 272 QualifiedName[] typeNames = extractTypeNames( model ); 273 274 String featureIdParameter = model.remove( "FEATUREID" ); 275 if ( typeNames == null && featureIdParameter == null ) { 276 throw new InconsistentRequestException( "TypeName OR FeatureId parameter must be set." ); 277 } 278 279 // String[] featureIds = null; 280 // if ( featureIdParameter != null ) { 281 // // FEATUREID specified. Looking for featureId 282 // // declaration TYPENAME contained in featureId declaration (eg. 283 // // FEATUREID=InWaterA_1M.1013) 284 // featureIds = StringTools.toArray( featureIdParameter, ",", false ); 285 // //typeNameSet = extractTypeNameFromFeatureId( featureIds, context, (HashSet) typeNameSet 286 // ); 287 // } 288 289 // Filters 290 // Map typeFilter = buildFilterMap( model, typeNames, featureIds, context ); 291 292 // // BBOX 293 // typeFilter = extractBBOXParameter( model, typeNames, typeFilter ); 294 // 295 // if ( typeFilter == null || typeFilter.size() == 0 ) { 296 // for ( int i = 0; i < typeNames.length; i++ ) { 297 // typeFilter.put( typeNames[i], null ); 298 // } 299 // } 300 301 List<TransactionOperation> operations = extractOperations( model, null ); 302 303 return new Transaction( id, version, versionSpecificParameter, lockID, operations, releaseAllFeatures, null ); 304 } 305 306 /** 307 * Extracts the {@link TransactionOperation}s contained in the given kvp request. 308 * 309 * @param model 310 * @param typeFilter 311 * @return List 312 * @throws InconsistentRequestException 313 */ 314 private static List<TransactionOperation> extractOperations( Map<String, String> model, 315 Map<QualifiedName, Filter> typeFilter ) 316 throws InconsistentRequestException { 317 List<TransactionOperation> operation = new ArrayList<TransactionOperation>(); 318 String op = model.remove( "OPERATION" ); 319 if ( op == null ) { 320 throw new InconsistentRequestException( "Operation parameter must be set" ); 321 } 322 if ( op.equals( "Delete" ) ) { 323 List<Delete> deletes = Delete.create( typeFilter ); 324 operation.addAll( deletes ); 325 } else { 326 String msg = "Invalid OPERATION parameter '" + op 327 + "'. KVP Transactions only support the 'Delete' operation."; 328 throw new InconsistentRequestException( msg ); 329 } 330 return operation; 331 } 332 333 /** 334 * Creates a <code>Transaction</code> instance from a document that contains the DOM representation of the request. 335 * 336 * @param id 337 * @param root 338 * element that contains the DOM representation of the request 339 * @return transaction instance 340 * @throws OGCWebServiceException 341 */ 342 public static Transaction create( String id, Element root ) 343 throws OGCWebServiceException { 344 TransactionDocument doc = new TransactionDocument(); 345 doc.setRootElement( root ); 346 Transaction request; 347 try { 348 request = doc.parse( id ); 349 } catch ( XMLParsingException e ) { 350 if ( e.getWrapped() != null ) { 351 throw e.getWrapped(); 352 } 353 LOG.logError( e.getMessage(), e ); 354 throw new OGCWebServiceException( "Transaction", e.getMessage() ); 355 } 356 return request; 357 } 358 359 @Override 360 public String toString() { 361 String ret = this.getClass().getName(); 362 ret += "version: " + this.version + "\n"; 363 ret += "id: " + this.id + "\n"; 364 ret += "lockID: " + this.lockId + "\n"; 365 ret += "operations: \n"; 366 for ( int i = 0; i < operations.size(); i++ ) { 367 ret += ( i + ": " + operations.get( i ) + "\n " ); 368 } 369 ret += "releaseAllFeatures: " + this.releaseAction; 370 return ret; 371 } 372 373 /** 374 * Adds missing namespaces in the names of requested feature types. 375 * <p> 376 * If the {@link QualifiedName} of a requested type has a null namespace, the first qualified feature type name of 377 * the given {@link WFService} with the same local name is used instead. 378 * <p> 379 * Note: The method changes this request (the feature type names) and should only be called by the 380 * <code>WFSHandler</code> class. 381 * 382 * @param wfs 383 * {@link WFService} instance that is used for the lookup of proper (qualified) feature type names 384 */ 385 public void guessMissingNamespaces( WFService wfs ) { 386 387 Set<QualifiedName> featureNames = wfs.getMappedFeatureTypes().keySet(); 388 for ( int j = 0; j < operations.size(); j++ ) { 389 TransactionOperation op = operations.get( j ); 390 if ( op instanceof Update ) { 391 Update update = (Update) op; 392 QualifiedName tn = update.getTypeName(); 393 if ( tn.getNamespace() == null ) { 394 QualifiedName newTn = guessTypeNameNamespace( tn, featureNames ); 395 update.setTypeName( newTn ); 396 } 397 398 if ( update.getReplacementProperties() != null ) { 399 Set<PropertyPath> propPaths = update.getReplacementProperties().keySet(); 400 String defaultPrefix = update.getTypeName().getPrefix(); 401 URI defaultNamespace = update.getTypeName().getNamespace(); 402 guessMissingPropertyNamespace( propPaths, defaultPrefix, defaultNamespace, update, j ); 403 } 404 405 } else if ( op instanceof Delete ) { 406 Delete delete = (Delete) op; 407 QualifiedName newTn = guessTypeNameNamespace( delete.getTypeName(), featureNames ); 408 delete.setTypeName( newTn ); 409 } 410 } 411 } 412 413 private void guessMissingPropertyNamespace( Set<PropertyPath> propPaths, String defaultPrefix, 414 URI defaultNamespace, Update update, int j ) { 415 for ( PropertyPath propPath : propPaths ) { 416 for ( int i = 0; i < propPath.getAllSteps().size(); i++ ) { 417 PropertyPathStep step = propPath.getStep( i ); 418 QualifiedName prop = step.getPropertyName(); 419 if ( prop.getNamespace() == null ) { 420 // the following retrieves the old values the object involved 421 Map<PropertyPath, FeatureProperty> oldMap = update.getReplacementProperties(); 422 List<PropertyPathStep> steps = propPath.getAllSteps(); 423 FeatureProperty oldValue = oldMap.get( propPath ); 424 oldMap.remove( propPath ); 425 QualifiedName newQName = new QualifiedName( defaultPrefix, prop.getLocalName(), defaultNamespace ); 426 427 // replaces old values in the objects involved 428 step.setPropertyName( newQName ); 429 FeatureProperty newValue = FeatureFactory.createFeatureProperty( newQName, oldValue.getValue() ); 430 steps.set( i, step ); 431 propPath.setSteps( steps ); 432 oldMap.put( propPath, newValue ); 433 update.setReplacementProperties( oldMap ); 434 operations.set( j, update ); 435 } 436 } 437 } 438 } 439 440 private QualifiedName guessTypeNameNamespace( QualifiedName candidate, Set<QualifiedName> featureNames ) { 441 if ( candidate.getNamespace() == null ) { 442 for ( QualifiedName ftName : featureNames ) { 443 if ( ftName.getLocalName().equals( candidate.getLocalName() ) ) { 444 return ftName; 445 } 446 } 447 } 448 return candidate; 449 } 450 451 }