001    //$Header: /deegreerepository/deegree/src/org/deegree/ogcwebservices/wfs/operation/transaction/Transaction.java,v 1.11 2007/02/07 15:01:51 poth Exp $
002    /*----------------    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    package org.deegree.ogcwebservices.wfs.operation.transaction;
044    
045    import java.util.ArrayList;
046    import java.util.HashSet;
047    import java.util.Iterator;
048    import java.util.List;
049    import java.util.Map;
050    import java.util.Set;
051    
052    import org.deegree.datatypes.QualifiedName;
053    import org.deegree.framework.log.ILogger;
054    import org.deegree.framework.log.LoggerFactory;
055    import org.deegree.framework.util.KVP2Map;
056    import org.deegree.ogcwebservices.InconsistentRequestException;
057    import org.deegree.ogcwebservices.InvalidParameterValueException;
058    import org.deegree.ogcwebservices.OGCWebServiceException;
059    import org.deegree.ogcwebservices.wfs.operation.AbstractWFSRequest;
060    import org.w3c.dom.Element;
061    
062    /**
063     * Represents a <code>Transaction</code> request to a web feature service.
064     * <p>
065     * A <code>Transaction</code> consists of a sequence of {@link Insert}, {@link Update},
066     * {@link Delete} and {@link Native} operations.
067     * <p>
068     * From the WFS Specification 1.1.0 OGC 04-094 (#12, Pg.63):
069     * <p>
070     * A <code>Transaction</code> request is used to describe data transformation operations that are
071     * to be applied to web accessible feature instances. When the transaction has been completed, a web
072     * feature service will generate an XML response document indicating the completion status of the
073     * transaction.
074     * 
075     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
076     * @author last edited by: $Author: apoth $
077     * 
078     * @version $Revision: 9348 $, $Date: 2007-12-27 17:59:14 +0100 (Do, 27 Dez 2007) $
079     */
080    public class Transaction extends AbstractWFSRequest {
081    
082        private static final long serialVersionUID = 6904739857311368390L;
083    
084        private static final ILogger LOG = LoggerFactory.getLogger( Transaction.class );
085    
086        private List<TransactionOperation> operations;
087    
088        // request version
089        private String version;
090    
091        // transaction ID
092        private String id;
093    
094        // LockID associated with the request
095        private String lockId;
096    
097        /**
098         * Specifies if ALL records should be released or if SOME records, indicating only those records
099         * which have been modified will be released. The default is ALL.
100         */
101        private RELEASE_ACTION releaseAction = RELEASE_ACTION.ALL;
102    
103        private TransactionDocument sourceDocument;
104    
105        /** Controls how locked features are treated when a transaction request is completed. */
106        public static enum RELEASE_ACTION {
107    
108            /**
109             * Indicates that the locks on all feature instances locked using the associated lockId
110             * should be released when the transaction completes, regardless of whether or not a
111             * particular feature instance in the locked set was actually operated upon.
112             */
113            ALL,
114    
115            /**
116             * Indicates that only the locks on feature instances modified by the transaction should be
117             * released. The other, unmodified, feature instances should remain locked using the same
118             * lockId so that subsequent transactions can operate on those feature instances. If an
119             * expiry period was specified, the expiry counter must be reset to zero after each
120             * transaction unless all feature instances in the locked set have been operated upon.
121             */
122            SOME
123        }
124    
125        /**
126         * Creates a new <code>Transaction</code> instance.
127         * 
128         * @param version
129         *            WFS version
130         * @param id
131         *            Transaction id
132         * @param versionSpecificParameter
133         * @param lockID
134         *            Lock Id
135         * @param operations
136         *            List of operations to be carried out
137         * @param releaseAllFeatures
138         * @param sourceDocument
139         */
140        public Transaction( String id, String version, Map<String, String> versionSpecificParameter, String lockID,
141                            List<TransactionOperation> operations, boolean releaseAllFeatures,
142                            TransactionDocument sourceDocument ) {
143            super( version, id, null, versionSpecificParameter );
144            this.id = id;
145            this.version = version;
146            this.lockId = lockID;
147            this.operations = operations;
148            if ( !releaseAllFeatures ) {
149                this.releaseAction = RELEASE_ACTION.SOME;
150            }
151            this.sourceDocument = sourceDocument;
152        }
153    
154        /**
155         * Returns the source document that was used to create this <code>Transaction</code> instance.
156         * 
157         * @return the source document
158         */
159        public TransactionDocument getSourceDocument() {
160            return this.sourceDocument;
161        }
162    
163        /**
164         * Returns the {@link TransactionOperation}s that are contained in the transaction.
165         * 
166         * @return the contained operations
167         */
168        public List<TransactionOperation> getOperations() {
169            return this.operations;
170        }
171    
172        /**
173         * Returns the lock identifier associated with this transaction.
174         * 
175         * @return the lock identifier associated with this transaction if it exists, null otherwise
176         */
177        public String getLockId() {
178            return this.lockId;
179        }
180    
181        /**
182         * Returns the release action mode to be applied after the transaction finished successfully.
183         * 
184         * @see RELEASE_ACTION
185         * @return the release action mode to be applied after the transaction finished successfully
186         */
187        public RELEASE_ACTION getReleaseAction() {
188            return this.releaseAction;
189        }
190    
191        /**
192         * Returns the names of the feature types that are affected by the transaction.
193         * 
194         * @return the names of the affected feature types
195         */
196        public Set<QualifiedName> getAffectedFeatureTypes() {
197            Set<QualifiedName> featureTypeSet = new HashSet<QualifiedName>();
198    
199            Iterator<TransactionOperation> iter = this.operations.iterator();
200            while ( iter.hasNext() ) {
201                TransactionOperation operation = iter.next();
202                featureTypeSet.addAll( operation.getAffectedFeatureTypes() );
203            }
204            return featureTypeSet;
205        }
206    
207        /**
208         * Creates a <code>Transaction</code> request from a key-value-pair encoding of the parameters
209         * contained in the passed variable 'request'.
210         * 
211         * @param id
212         *            id of the request
213         * @param request
214         *            key-value-pair encoded GetFeature request
215         * @return new created Transaction instance
216         * @throws InconsistentRequestException
217         * @throws InvalidParameterValueException
218         */
219        public static Transaction create( String id, String request )
220                                throws InconsistentRequestException, InvalidParameterValueException {
221            
222            Map<String, String> model = KVP2Map.toMap( request );
223            model.put( "ID", id );
224            
225            return create( model );
226        }
227    
228        /**
229         * Creates a <code>Transaction</code> request from a key-value-pair encoding of the parameters
230         * contained in the given Map.
231         * 
232         * @param model
233         *            key-value-pair encoded Transaction request
234         * @return new Transaction instance
235         * @throws InconsistentRequestException
236         * @throws InvalidParameterValueException
237         */
238        public static Transaction create( Map<String, String> model )
239                                throws InconsistentRequestException, InvalidParameterValueException {
240    
241            Map<String, String> versionSpecificParameter = null;
242    
243            String id = model.get( "ID" );
244    
245            String version = checkVersionParameter( model );
246    
247            checkServiceParameter( model );
248    
249            String request = model.remove( "REQUEST" );
250            if ( request == null ) {
251                throw new InconsistentRequestException( "Request parameter for a transaction request must be set." );
252            }
253    
254            String lockID = model.remove( "LOCKID" );
255    
256            String releaseAction = model.remove( "RELEASEACTION" );
257            boolean releaseAllFeatures = true;
258            if ( releaseAction != null ) {
259                if ( "SOME".equals( releaseAction ) ) {
260                    releaseAllFeatures = false;
261                } else if ( "ALL".equals( releaseAction ) ) {
262                    releaseAllFeatures = true;
263                } else {
264                    throw new InvalidParameterValueException( "releaseAction", releaseAction );
265                }
266            }
267    
268            QualifiedName[] typeNames = extractTypeNames( model );
269    
270            String featureIdParameter = model.remove( "FEATUREID" );
271            if ( typeNames == null && featureIdParameter == null ) {
272                throw new InconsistentRequestException( "TypeName OR FeatureId parameter must be set." );
273            }
274    
275            // String[] featureIds = null;
276            // if ( featureIdParameter != null ) {
277            // // FEATUREID specified. Looking for featureId
278            // // declaration TYPENAME contained in featureId declaration (eg.
279            // // FEATUREID=InWaterA_1M.1013)
280            // featureIds = StringTools.toArray( featureIdParameter, ",", false );
281            // //typeNameSet = extractTypeNameFromFeatureId( featureIds, context, (HashSet) typeNameSet
282            // );
283            // }
284    
285            // Filters
286            // Map typeFilter = buildFilterMap( model, typeNames, featureIds, context );
287    
288            // // BBOX
289            // typeFilter = extractBBOXParameter( model, typeNames, typeFilter );
290            //
291            // if ( typeFilter == null || typeFilter.size() == 0 ) {
292            // for ( int i = 0; i < typeNames.length; i++ ) {
293            // typeFilter.put( typeNames[i], null );
294            // }
295            // }
296    
297            List<TransactionOperation> operations = extractOperations( model, null );
298    
299            return new Transaction( id, version, versionSpecificParameter, lockID, operations, releaseAllFeatures, null );
300        }
301    
302        /**
303         * Extracts the {@link TransactionOperation}s contained in the given kvp request.
304         * 
305         * @param model
306         * @param typeFilter
307         * @param typeNames
308         * @return List
309         * @throws InconsistentRequestException
310         */
311        private static List<TransactionOperation> extractOperations( Map model, Map typeFilter )
312                                throws InconsistentRequestException {
313            List<TransactionOperation> operation = new ArrayList<TransactionOperation>();
314            String op = (String) model.remove( "OPERATION" );
315            if ( op == null ) {
316                throw new InconsistentRequestException( "Operation parameter must be set" );
317            }
318            if ( op.equals( "Delete" ) ) {
319                List<Delete> deletes = Delete.create( typeFilter );
320                operation.addAll( deletes );
321            } else {
322                String msg = "Invalid OPERATION parameter '" + op
323                             + "'. KVP Transactions only support the 'Delete' operation.";
324                throw new InconsistentRequestException( msg );
325            }
326            return operation;
327        }
328    
329        /**
330         * Creates a <code>Transaction</code> instance from a document that contains the DOM
331         * representation of the request.
332         * 
333         * @param id
334         * @param root
335         *            element that contains the DOM representation of the request
336         * @return transaction instance
337         * @throws OGCWebServiceException
338         */
339        public static Transaction create( String id, Element root )
340                                throws OGCWebServiceException {
341            TransactionDocument doc = new TransactionDocument();
342            doc.setRootElement( root );
343            Transaction request;
344            try {
345                request = doc.parse( id );
346            } catch ( Exception e ) {
347                LOG.logError( e.getMessage(), e );
348                throw new OGCWebServiceException( "Transaction", e.getMessage() );
349            }
350            return request;
351        }
352    
353        @Override
354        public String toString() {
355            String ret = this.getClass().getName();
356            ret += "version: " + this.version + "\n";
357            ret += "id: " + this.id + "\n";
358            ret += "lockID: " + this.lockId + "\n";
359            ret += "operations: \n";
360            for ( int i = 0; i < operations.size(); i++ ) {
361                ret += ( i + ": " + operations.get( i ) + "\n " );
362            }
363            ret += "releaseAllFeatures: " + this.releaseAction;
364            return ret;
365        }
366    }