001    //$Header: /deegreerepository/deegree/src/org/deegree/ogcwebservices/wfs/TransactionHandler.java,v 1.69 2007/03/14 14:41:43 mschneider 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    
037    package org.deegree.ogcwebservices.wfs;
038    
039    import static java.util.Collections.singletonList;
040    import static org.deegree.ogcwebservices.wfs.operation.transaction.Transaction.RELEASE_ACTION.ALL;
041    import static org.deegree.ogcwebservices.wfs.operation.transaction.Transaction.RELEASE_ACTION.SOME;
042    
043    import java.util.ArrayList;
044    import java.util.HashMap;
045    import java.util.Iterator;
046    import java.util.List;
047    import java.util.Map;
048    import java.util.Set;
049    import java.util.TreeSet;
050    
051    import org.deegree.datatypes.QualifiedName;
052    import org.deegree.framework.log.ILogger;
053    import org.deegree.framework.log.LoggerFactory;
054    import org.deegree.i18n.Messages;
055    import org.deegree.io.datastore.Datastore;
056    import org.deegree.io.datastore.DatastoreException;
057    import org.deegree.io.datastore.DatastoreTransaction;
058    import org.deegree.io.datastore.FeatureId;
059    import org.deegree.io.datastore.LockManager;
060    import org.deegree.io.datastore.MissingLockIdException;
061    import org.deegree.io.datastore.PropertyPathResolver;
062    import org.deegree.io.datastore.idgenerator.FeatureIdAssigner;
063    import org.deegree.io.datastore.schema.MappedFeaturePropertyType;
064    import org.deegree.io.datastore.schema.MappedFeatureType;
065    import org.deegree.io.datastore.schema.MappedGMLSchema;
066    import org.deegree.io.datastore.sql.transaction.SQLTransaction;
067    import org.deegree.model.feature.Feature;
068    import org.deegree.model.feature.FeatureCollection;
069    import org.deegree.model.feature.FeatureProperty;
070    import org.deegree.model.feature.GMLFeatureAdapter;
071    import org.deegree.model.feature.GMLFeatureCollectionDocument;
072    import org.deegree.model.feature.Validator;
073    import org.deegree.model.feature.schema.FeatureType;
074    import org.deegree.model.feature.schema.PropertyType;
075    import org.deegree.ogcbase.PropertyPath;
076    import org.deegree.ogcbase.PropertyPathStep;
077    import org.deegree.ogcwebservices.InvalidParameterValueException;
078    import org.deegree.ogcwebservices.MissingParameterValueException;
079    import org.deegree.ogcwebservices.OGCWebServiceException;
080    import org.deegree.ogcwebservices.wfs.operation.transaction.Delete;
081    import org.deegree.ogcwebservices.wfs.operation.transaction.Insert;
082    import org.deegree.ogcwebservices.wfs.operation.transaction.InsertResults;
083    import org.deegree.ogcwebservices.wfs.operation.transaction.Native;
084    import org.deegree.ogcwebservices.wfs.operation.transaction.Transaction;
085    import org.deegree.ogcwebservices.wfs.operation.transaction.TransactionOperation;
086    import org.deegree.ogcwebservices.wfs.operation.transaction.TransactionResponse;
087    import org.deegree.ogcwebservices.wfs.operation.transaction.Update;
088    import org.deegree.ogcwebservices.wfs.operation.transaction.Insert.ID_GEN;
089    
090    /**
091     * Handler for transaction requests to the {@link WFService}.
092     * <p>
093     * If the used backend does not support atomic transactions, it is possible that one part fails while another works
094     * well. Depending on definitions made in the OGC WFS 1.1.0 specification in this case it is possible that even if a sub
095     * part of the request fails no exception will be thrown. In this case the result objects contains informations on the
096     * parts of the request that worked and that did not.
097     * 
098     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
099     * @author <a href="mailto:deshmukh@lat-lon.de">Anup Deshmukh </a>
100     * @author last edited by: $Author: mschneider $
101     * 
102     * @version $Revision: 18544 $, $Date: 2009-07-20 16:14:38 +0200 (Mo, 20. Jul 2009) $
103     */
104    class TransactionHandler {
105    
106        private static final ILogger LOG = LoggerFactory.getLogger( TransactionHandler.class );
107    
108        private WFService service;
109    
110        private Transaction request;
111    
112        private Map<QualifiedName, MappedFeatureType> ftMap;
113    
114        // filled by #acquireDSTransactions()
115        private Map<QualifiedName, DatastoreTransaction> taMap = new HashMap<QualifiedName, DatastoreTransaction>();
116    
117        // filled by #acquireDSTransactions()
118        private Map<Datastore, DatastoreTransaction> dsToTaMap = new HashMap<Datastore, DatastoreTransaction>();
119    
120        private Set<FeatureId> changedFIDs = new TreeSet<FeatureId>();
121    
122        /**
123         * Creates a new <code>TransactionHandler</code> instance.
124         * 
125         * @param service
126         * @param request
127         */
128        TransactionHandler( WFService service, Transaction request ) {
129            this.service = service;
130            this.request = request;
131            this.ftMap = service.getMappedFeatureTypes();
132        }
133    
134        /**
135         * Performs the associated transaction.
136         * 
137         * @return transaction response
138         * @throws OGCWebServiceException
139         *             if an error occurred
140         */
141        synchronized TransactionResponse handleRequest()
142                                throws OGCWebServiceException {
143    
144            changedFIDs.clear();
145    
146            validate( this.request );
147    
148            TransactionResponse response = null;
149    
150            acquireDSTransactions();
151    
152            try {
153                try {
154                    response = performOperations();
155                } catch ( OGCWebServiceException e ) {
156                    abortDSTransactions();
157                    throw e;
158                }
159                commitDSTransactions();
160                if ( request.getLockId() != null && request.getReleaseAction() == ALL ) {
161                    try {
162                        LockManager.getInstance().releaseLock( request.getLockId() );
163                    } catch ( DatastoreException e ) {
164                        throw new InvalidParameterValueException( e.getMessage() );
165                    }
166                }
167                if ( request.getLockId() != null && request.getReleaseAction() == SOME ) {
168                    try {
169                        LockManager.getInstance().releaseLockPartly( request.getLockId(), changedFIDs );
170                    } catch ( DatastoreException e ) {
171                        throw new InvalidParameterValueException( e.getMessage() );
172                    }
173                }
174            } finally {
175                releaseDSTransactions();
176            }
177    
178            return response;
179        }
180    
181        /**
182         * Validates the feature instances in the given transaction against the WFS' application schemas.
183         * <p>
184         * The feature instances are assigned the corresponding <code>MappedFeatureType</code> in the process.
185         * 
186         * @param request
187         * @throws OGCWebServiceException
188         */
189        private void validate( Transaction request )
190                                throws OGCWebServiceException {
191    
192            List<TransactionOperation> operations = request.getOperations();
193    
194            Iterator<TransactionOperation> iter = operations.iterator();
195            while ( iter.hasNext() ) {
196                TransactionOperation operation = iter.next();
197                if ( operation instanceof Insert ) {
198                    try {
199                        validateInsert( (Insert) operation );
200                    } catch ( DatastoreException e ) {
201                        throw new OGCWebServiceException( this.getClass().getName(), e.getMessage() );
202                    }
203                } else if ( operation instanceof Delete ) {
204                    validateDelete( (Delete) operation );
205                } else if ( operation instanceof Update ) {
206                    validateUpdate( (Update) operation );
207                } else if ( operation instanceof Native ) {
208                    // nothing to do
209                } else {
210                    String msg = "Internal error. Unhandled transaction operation type '" + operation.getClass().getName()
211                                 + "'.";
212                    throw new OGCWebServiceException( this.getClass().getName(), msg );
213                }
214            }
215        }
216    
217        /**
218         * Validates all feature instances in the given insert operation against the WFS' application schemas.
219         * <p>
220         * The feature instances are assigned the corresponding <code>MappedFeatureType</code> in the process and are
221         * disambiguated, so every feature has a unique feature id afterwards.
222         * 
223         * @param operation
224         * @throws OGCWebServiceException
225         * @throws DatastoreException
226         */
227        @SuppressWarnings("unchecked")
228        private void validateInsert( Insert operation )
229                                throws OGCWebServiceException, DatastoreException {
230            FeatureCollection fc = operation.getFeatures();
231            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
232                try {
233                    GMLFeatureAdapter ada = new GMLFeatureAdapter( false );
234                    GMLFeatureCollectionDocument doc = ada.export( fc );
235                    LOG.logDebugXMLFile( "TransactionHandler_insert_incoming", doc );
236                } catch ( Exception e ) {
237                    LOG.logError( e.getMessage(), e );
238                }
239            }
240            Validator validator = new Validator( (Map) this.service.getMappedFeatureTypes() );
241            for ( int i = 0; i < fc.size(); i++ ) {
242                validator.validate( fc.getFeature( i ) );
243            }
244    
245            // merge all equal and anonymous features (without fid)
246            FeatureDisambiguator merger = new FeatureDisambiguator( fc );
247            if ( operation.getIdGen() == ID_GEN.USE_EXISTING ) {
248                if ( merger.checkForAnonymousFeatures() ) {
249                    String msg = Messages.getMessage( "WFS_INSERT_USE_EXISTING_AND_NO_FID" );
250                    throw new DatastoreException( msg );
251                }
252            }
253            fc = merger.mergeFeatures();
254        }
255    
256        /**
257         * Validates all feature instances in the given insert operation against the WFS' application schemas.
258         * <p>
259         * The feature instances are assigned the corresponding <code>MappedFeatureType</code> in the process.
260         * 
261         * @param operation
262         * @throws OGCWebServiceException
263         */
264        private void validateDelete( Delete operation )
265                                throws OGCWebServiceException {
266            QualifiedName ftName = operation.getTypeName();
267            MappedFeatureType ft = this.ftMap.get( ftName );
268            if ( ft == null ) {
269                String msg = Messages.getMessage( "WFS_DELETE_FEATURE_TYPE_UNKNOWN", ftName );
270                throw new OGCWebServiceException( this.getClass().getName(), msg );
271            }
272            if ( ft.isAbstract() ) {
273                String msg = Messages.getMessage( "WFS_DELETE_FEATURE_TYPE_ABSTRACT", ftName );
274                throw new OGCWebServiceException( this.getClass().getName(), msg );
275            }
276        }
277    
278        /**
279         * Validates any feature instance in the given update operation against the WFS' application schemas.
280         * <p>
281         * Feature instances are assigned the corresponding <code>MappedFeatureType</code> in the process, property names
282         * are normalized and their values are parsed into the respective objects.
283         * 
284         * @param operation
285         *            update operation
286         * @throws OGCWebServiceException
287         */
288        @SuppressWarnings("unchecked")
289        private void validateUpdate( Update operation )
290                                throws OGCWebServiceException {
291    
292            QualifiedName ftName = operation.getTypeName();
293            MappedFeatureType ft = this.ftMap.get( ftName );
294            if ( ft == null ) {
295                String msg = Messages.getMessage( "WFS_UPDATE_FEATURE_TYPE_UNKNOWN", ftName );
296                throw new OGCWebServiceException( this.getClass().getName(), msg );
297            }
298    
299            Feature feature = operation.getFeature();
300            if ( feature != null ) {
301                Validator validator = new Validator( (Map) this.service.getMappedFeatureTypes() );
302                validator.validate( feature );
303            } else {
304                validateProperties( ft, operation );
305            }
306        }
307    
308        /**
309         * Validates the properties and their replacement values that are specified in the given <code>Update</code>
310         * operation.
311         * <p>
312         * Property names are normalized and their values are parsed into the respective objects.
313         * 
314         * @param ft
315         *            feature type
316         * @param operation
317         *            update operation
318         * @throws OGCWebServiceException
319         */
320        private void validateProperties( MappedFeatureType ft, Update operation )
321                                throws OGCWebServiceException {
322    
323            Map<PropertyPath, FeatureProperty> replacementProps = operation.getReplacementProperties();
324            Map<PropertyPath, FeatureProperty> normalizedProps = new HashMap<PropertyPath, FeatureProperty>();
325    
326            for ( PropertyPath path : replacementProps.keySet() ) {
327                FeatureProperty property = replacementProps.get( path );
328                path = PropertyPathResolver.normalizePropertyPath( ft, null, path );
329                validateProperty( ft, path, property );
330                normalizedProps.put( path, property );
331            }
332    
333            // remove all mappings and add normalized ones
334            replacementProps.clear();
335            for ( PropertyPath path : normalizedProps.keySet() ) {
336                replacementProps.put( path, normalizedProps.get( path ) );
337            }
338        }
339    
340        /**
341         * Validates the property name and it's replacement value.
342         * <p>
343         * Values are parsed into the respective objects.
344         * 
345         * @param ft
346         *            feature type
347         * @param path
348         *            property name
349         * @param replacementProperty
350         *            replacement property value (as XML node)
351         * @throws OGCWebServiceException
352         */
353        private void validateProperty( MappedFeatureType ft, PropertyPath path, FeatureProperty replacementProperty )
354                                throws OGCWebServiceException {
355    
356            for ( int i = 0; i < path.getSteps(); i += 2 ) {
357                // check if feature step is valid
358                PropertyPathStep ftStep = path.getStep( i );
359                FeatureType stepFt = this.ftMap.get( ftStep.getPropertyName() );
360                if ( stepFt == null ) {
361                    String msg = Messages.getMessage( "WFS_UPDATE_FEATURE_STEP_UNKNOWN", path, "unknown" );
362                    throw new OGCWebServiceException( this.getClass().getName(), msg );
363                }
364                MappedGMLSchema schema = ft.getGMLSchema();
365                if ( !schema.isValidSubstitution( ft, stepFt ) ) {
366                    String msg = Messages.getMessage( "WFS_UPDATE_FEATURE_STEP_INVALID", path, stepFt.getName(),
367                                                      ft.getName() );
368                    throw new OGCWebServiceException( this.getClass().getName(), msg );
369                }
370    
371                // check if property step is valid
372                PropertyPathStep propertyStep = path.getStep( i + 1 );
373                QualifiedName propertyName = propertyStep.getPropertyName();
374                PropertyType pt = ft.getProperty( propertyName );
375                if ( pt == null ) {
376                    String msg = Messages.getMessage( "WFS_UPDATE_PROPERTY_STEP_UNKNOWN", path, propertyName, ft.getName() );
377                    throw new OGCWebServiceException( this.getClass().getName(), msg );
378                }
379                if ( i + 2 == path.getSteps() ) {
380                    if ( replacementProperty.getValue() == null && pt.getMinOccurs() > 0 ) {
381                        String msg = Messages.getMessage( "WFS_UPDATE_PROPERTY_NULL_INVALID", path, pt.getMinOccurs() );
382                        throw new InvalidParameterValueException( this.getClass().getName(), msg );
383                    }
384                    if ( replacementProperty.getValue() instanceof Feature ) {
385                        Validator validator = new Validator( (Map) this.service.getMappedFeatureTypes() );
386                        validator.validate( (Feature) replacementProperty.getValue() );
387                    }
388                } else {
389                    if ( !( pt instanceof MappedFeaturePropertyType ) ) {
390                        String msg = Messages.getMessage( "WFS_UPDATE_NOT_FEATURE_PROPERTY", path, propertyName );
391                        throw new OGCWebServiceException( this.getClass().getName(), msg );
392                    }
393                    MappedFeaturePropertyType fpt = (MappedFeaturePropertyType) pt;
394                    ft = fpt.getFeatureTypeReference().getFeatureType();
395                }
396            }
397        }
398    
399        /**
400         * Performs the operations contained in the transaction.
401         * 
402         * @throws OGCWebServiceException
403         */
404        private TransactionResponse performOperations()
405                                throws OGCWebServiceException {
406    
407            int inserts = 0;
408            int deletes = 0;
409            int updates = 0;
410    
411            List<InsertResults> insertResults = new ArrayList<InsertResults>();
412            List<TransactionOperation> operations = request.getOperations();
413    
414            Iterator<TransactionOperation> iter = operations.iterator();
415            while ( iter.hasNext() ) {
416                TransactionOperation operation = iter.next();
417                String handle = operation.getHandle();
418                try {
419                    if ( operation instanceof Insert ) {
420                        List<FeatureId> insertedFIDs = performInsert( (Insert) operation );
421                        inserts += insertedFIDs.size();
422    
423                        for ( FeatureId id : insertedFIDs ) {
424                            InsertResults results = new InsertResults( handle, singletonList( id ) );
425                            insertResults.add( results );
426                        }
427                    } else if ( operation instanceof Delete ) {
428                        // the intended behavior is different between versions here, so in #performDelete the exception
429                        // will be thrown now, and caught here if 1.1.0
430                        if ( request.getVersion().equals( "1.0.0" ) ) {
431                            deletes += performDelete( (Delete) operation );
432                        } else {
433                            try {
434                                deletes += performDelete( (Delete) operation );
435                            } catch ( MissingLockIdException e ) {
436                                throw new MissingParameterValueException( "LockId", e.getMessage() );
437                            }
438                        }
439                    } else if ( operation instanceof Update ) {
440                        updates += performUpdate( (Update) operation );
441                    } else if ( operation instanceof Native ) {
442                        String msg = Messages.getMessage( "WFS_NATIVE_OPERATIONS_UNSUPPORTED" );
443                        throw new OGCWebServiceException( this.getClass().getName(), msg );
444                    } else {
445                        String opType = operation.getClass().getName();
446                        String msg = Messages.getMessage( "WFS_UNHANDLED_OPERATION_TYPE", opType );
447                        throw new OGCWebServiceException( this.getClass().getName(), msg );
448                    }
449                } catch ( DatastoreException e ) {
450                    LOG.logError( e.getMessage(), e );
451                    String msg = "A datastore exception occured during the processing of operation with handle '" + handle
452                                 + "': " + e.getMessage();
453                    throw new InvalidParameterValueException( this.getClass().getName(), msg );
454                }
455            }
456            TransactionResponse response = new TransactionResponse( request, inserts, updates, deletes, insertResults );
457            return response;
458        }
459    
460        /**
461         * Performs the given insert operation.
462         * 
463         * @param insert
464         *            insert operation to be performed
465         * @throws DatastoreException
466         */
467        private List<FeatureId> performInsert( Insert insert )
468                                throws DatastoreException {
469    
470            List<FeatureId> fids = new ArrayList<FeatureId>();
471            FeatureCollection fc = insert.getFeatures();
472    
473            // clear reference to feature collection
474            // insert.setFeatureCollection( null );
475    
476            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
477                try {
478                    GMLFeatureAdapter ada = new GMLFeatureAdapter( false );
479                    GMLFeatureCollectionDocument doc = ada.export( fc );
480                    LOG.logDebugXMLFile( "TransactionHandler_insert_merged", doc );
481                } catch ( Exception e ) {
482                    LOG.logError( e.getMessage(), e );
483                }
484            }
485    
486            Map<DatastoreTransaction, List<Feature>> taFeaturesMap = new HashMap<DatastoreTransaction, List<Feature>>();
487            FeatureIdAssigner fidAssigner = new FeatureIdAssigner( insert.getIdGen() );
488    
489            // assign features to corresponding datastore transactions
490            for ( int i = 0; i < fc.size(); i++ ) {
491                Feature feature = fc.getFeature( i );
492                QualifiedName ftName = feature.getName();
493                DatastoreTransaction dsTa = this.taMap.get( ftName );
494                // reassign feature ids (if necessary)
495                fidAssigner.assignFID( feature, dsTa );
496                List<Feature> features = taFeaturesMap.get( dsTa );
497                if ( features == null ) {
498                    features = new ArrayList<Feature>();
499                    taFeaturesMap.put( dsTa, features );
500                }
501                features.add( feature );
502            }
503    
504            // TODO remove this hack
505            fidAssigner.markStoredFeatures();
506    
507            // clear reference to fidAssigner (implicitly to feature collection)
508            fidAssigner = null;
509    
510            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
511                try {
512                    GMLFeatureAdapter ada = new GMLFeatureAdapter( false );
513                    GMLFeatureCollectionDocument doc = ada.export( fc );
514                    LOG.logDebugXMLFile( "TransactionHandler_insert_marked", doc );
515                } catch ( Exception e ) {
516                    LOG.logError( e.getMessage(), e );
517                }
518            }
519    
520            // clear reference to feature collection
521            fc = null;
522    
523            for ( DatastoreTransaction ta : taFeaturesMap.keySet() ) {
524                List<Feature> features = taFeaturesMap.get( ta );
525                fids.addAll( ta.performInsert( features ) );
526            }
527            return fids;
528        }
529    
530        /**
531         * Performs the given delete operation.
532         * 
533         * @param delete
534         *            delete operation to be performed
535         * @throws DatastoreException
536         */
537        private int performDelete( Delete delete )
538                                throws DatastoreException {
539            QualifiedName ftName = delete.getTypeName();
540            MappedFeatureType ft = this.ftMap.get( ftName );
541            DatastoreTransaction dsTa = this.taMap.get( ftName );
542            int deleted = dsTa.performDelete( ft, delete.getFilter(), this.request.getLockId() );
543            return deleted;
544        }
545    
546        /**
547         * Performs the given update operation.
548         * <p>
549         * Assigning of FIDs to replacement features is performed in the {@link DatastoreTransaction}.
550         * 
551         * @param update
552         *            update operation to be perform
553         * @throws DatastoreException
554         */
555        private int performUpdate( Update update )
556                                throws DatastoreException {
557    
558            QualifiedName ftName = update.getTypeName();
559            MappedFeatureType ft = this.ftMap.get( ftName );
560            DatastoreTransaction dsTa = this.taMap.get( ftName );
561            int updated = 0;
562            if ( update.getFeature() == null ) {
563                updated = dsTa.performUpdate( ft, update.getReplacementProperties(), update.getFilter(),
564                                              this.request.getLockId() );
565            } else {
566                updated = dsTa.performUpdate( ft, update.getFeature(), update.getFilter(), this.request.getLockId() );
567            }
568    
569            // to work around API changes...
570            if ( dsTa instanceof SQLTransaction ) {
571                changedFIDs.addAll( ( (SQLTransaction) dsTa ).determineAffectedFIDs( ft, update.getFilter() ) );
572            }
573    
574            return updated;
575        }
576    
577        /**
578         * Acquires the necessary <code>DatastoreTransaction</code>s. For each participating <code>Datastore</code>, one
579         * transaction is needed.
580         * <p>
581         * Fills the <code>taMap</code> and <code>dsToTaMap</code> members of this class.
582         * 
583         * @throws OGCWebServiceException
584         *             if a feature type is unknown or a DatastoreTransaction could not be acquired
585         */
586        private void acquireDSTransactions()
587                                throws OGCWebServiceException {
588            Set<QualifiedName> ftNames = this.request.getAffectedFeatureTypes();
589            for ( QualifiedName ftName : ftNames ) {
590                MappedFeatureType ft = this.ftMap.get( ftName );
591                if ( ft == null ) {
592                    String msg = "FeatureType '" + ftName + "' is not known to the WFS.";
593                    throw new OGCWebServiceException( this.getClass().getName(), msg );
594                }
595                Datastore ds = ft.getGMLSchema().getDatastore();
596                DatastoreTransaction dsTa = this.dsToTaMap.get( ds );
597                if ( dsTa == null ) {
598                    try {
599                        dsTa = ds.acquireTransaction();
600                    } catch ( DatastoreException e ) {
601                        LOG.logError( e.getMessage(), e );
602                        String msg = "Could not acquire transaction for FeatureType '" + ftName + "'.";
603                        throw new OGCWebServiceException( this.getClass().getName(), msg );
604                    }
605                    this.dsToTaMap.put( ds, dsTa );
606                }
607                this.taMap.put( ftName, dsTa );
608            }
609        }
610    
611        /**
612         * Releases all acquired <code>DatastoreTransaction</code>s.
613         * 
614         * @throws OGCWebServiceException
615         *             if a DatastoreTransaction could not be released
616         */
617        private void releaseDSTransactions()
618                                throws OGCWebServiceException {
619            String msg = "";
620            for ( DatastoreTransaction dsTa : this.dsToTaMap.values() ) {
621                LOG.logDebug( "Releasing DatastoreTransaction " + dsTa );
622                try {
623                    dsTa.release();
624                } catch ( DatastoreException e ) {
625                    LOG.logError( "Error releasing DatastoreTransaction: " + e.getMessage(), e );
626                    msg += e.getMessage() + "\n";
627                }
628            }
629            if ( msg.length() != 0 ) {
630                msg = "Could not release one or more DatastoreTransactions: " + msg;
631                throw new OGCWebServiceException( this.getClass().getName(), msg );
632            }
633        }
634    
635        /**
636         * Commits all pending <code>DatastoreTransaction</code>s.
637         * 
638         * @throws OGCWebServiceException
639         *             if a DatastoreException could not be committed
640         */
641        private void commitDSTransactions()
642                                throws OGCWebServiceException {
643            String msg = "";
644            for ( DatastoreTransaction dsTa : this.dsToTaMap.values() ) {
645                LOG.logDebug( "Committing DatastoreTransaction " + dsTa );
646                try {
647                    dsTa.commit();
648                } catch ( DatastoreException e ) {
649                    LOG.logError( "Error committing DatastoreTransaction: " + e.getMessage(), e );
650                    msg += e.getMessage() + "\n";
651                }
652            }
653            if ( msg.length() != 0 ) {
654                msg = "Could not commit one or more DatastoreTransactions: " + msg;
655                throw new OGCWebServiceException( this.getClass().getName(), msg );
656            }
657        }
658    
659        /**
660         * Aborts all pending <code>DatastoreTransaction</code>s.
661         * 
662         * @throws OGCWebServiceException
663         *             if a DatastoreException could not be aborted
664         */
665        private void abortDSTransactions()
666                                throws OGCWebServiceException {
667            String msg = "";
668            for ( DatastoreTransaction dsTa : this.dsToTaMap.values() ) {
669                LOG.logDebug( "Aborting DatastoreTransaction " + dsTa );
670                try {
671                    dsTa.rollback();
672                } catch ( DatastoreException e ) {
673                    LOG.logError( "Error aborting DatastoreTransaction: " + e.getMessage(), e );
674                    msg += e.getMessage() + "\n";
675                }
676            }
677            if ( msg.length() != 0 ) {
678                msg = "Could not abort one or more DatastoreTransactions: " + msg;
679                throw new OGCWebServiceException( this.getClass().getName(), msg );
680            }
681        }
682    }