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