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