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