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 }