    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.net.URI;
    039    import java.util.ArrayList;
    040    import java.util.HashSet;
    041    import java.util.Iterator;
    042    import java.util.List;
    043    import java.util.Map;
    044    import java.util.Set;
    045
    046    import org.deegree.datatypes.QualifiedName;
    047    import org.deegree.framework.log.ILogger;
    048    import org.deegree.framework.log.LoggerFactory;
    049    import org.deegree.framework.util.KVP2Map;
    050    import org.deegree.framework.xml.XMLParsingException;
    051    import org.deegree.model.feature.FeatureFactory;
    052    import org.deegree.model.feature.FeatureProperty;
    053    import org.deegree.model.filterencoding.Filter;
    054    import org.deegree.ogcbase.PropertyPath;
    055    import org.deegree.ogcbase.PropertyPathStep;
    056    import org.deegree.ogcwebservices.InconsistentRequestException;
    057    import org.deegree.ogcwebservices.InvalidParameterValueException;
    058    import org.deegree.ogcwebservices.MissingParameterValueException;
    059    import org.deegree.ogcwebservices.OGCWebServiceException;
    060    import org.deegree.ogcwebservices.wfs.WFService;
    061    import org.deegree.ogcwebservices.wfs.operation.AbstractWFSRequest;
    062    import org.w3c.dom.Element;
    063
    064    /**
    065     * Represents a <code>Transaction</code> request to a web feature service.
    066     * <p>
    067     * A <code>Transaction</code> consists of a sequence of {@link Insert}, {@link Update}, {@link Delete} and
    068     * {@link Native} operations.
    069     * <p>
    070     * From the WFS Specification 1.1.0 OGC 04-094 (#12, Pg.63):
    071     * <p>
    072     * A <code>Transaction</code> request is used to describe data transformation operations that are to be applied to web
    073     * accessible feature instances. When the transaction has been completed, a web feature service will generate an XML
    074     * response document indicating the completion status of the transaction.
    075     *
    076     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
    077     * @author last edited by: $Author: aionita $
    078     *
    079     * @version $Revision: 23794 $, $Date: 2010-04-23 15:05:33 +0200 (Fr, 23. Apr 2010) $
    080     */
    081    public class Transaction extends AbstractWFSRequest {
    082
    083        private static final long serialVersionUID = 6904739857311368390L;
    084
    085        private static final ILogger LOG = LoggerFactory.getLogger( Transaction.class );
    086
    087        private List<TransactionOperation> operations;
    088
    089        // request version
    090        private String version;
    091
    092        // transaction ID
    093        private String id;
    094
    095        // LockID associated with the request
    096        private String lockId;
    097
    098        /**
    099         * Specifies if ALL records should be released or if SOME records, indicating only those records which have been
    100         * modified will be released. The default is ALL.
    101         */
    102        private RELEASE_ACTION releaseAction = RELEASE_ACTION.ALL;
    103
    104        private TransactionDocument sourceDocument;
    105
    106        /** Controls how locked features are treated when a transaction request is completed. */
    107        public static enum RELEASE_ACTION {
    108
    109            /**
    110             * Indicates that the locks on all feature instances locked using the associated lockId should be released when
    111             * the transaction completes, regardless of whether or not a particular feature instance in the locked set was
    112             * actually operated upon.
    113             */
    114            ALL,
    115
    116            /**
    117             * Indicates that only the locks on feature instances modified by the transaction should be released. The other,
    118             * unmodified, feature instances should remain locked using the same lockId so that subsequent transactions can
    119             * operate on those feature instances. If an expiry period was specified, the expiry counter must be reset to
    120             * zero after each 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 contained in the
    209         * 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         * @throws MissingParameterValueException
    219         */
    220        public static Transaction create( String id, String request )
    221                                throws InconsistentRequestException, InvalidParameterValueException,
    222                                MissingParameterValueException {
    223
    224            Map<String, String> model = KVP2Map.toMap( request );
    225            model.put( "ID", id );
    226
    227            return create( model );
    228        }
    229
    230        /**
    231         * Creates a <code>Transaction</code> request from a key-value-pair encoding of the parameters contained in the
    232         * given Map.
    233         *
    234         * @param model
    235         *            key-value-pair encoded Transaction request
    236         * @return new Transaction instance
    237         * @throws InconsistentRequestException
    238         * @throws InvalidParameterValueException
    239         * @throws MissingParameterValueException
    240         */
    241        public static Transaction create( Map<String, String> model )
    242                                throws InconsistentRequestException, InvalidParameterValueException,
    243                                MissingParameterValueException {
    244
    245            Map<String, String> versionSpecificParameter = null;
    246
    247            String id = model.get( "ID" );
    248
    249            String version = checkVersionParameter( model );
    250
    251            checkServiceParameter( model );
    252
    253            String request = model.remove( "REQUEST" );
    254            if ( request == null ) {
    255                throw new InconsistentRequestException( "Request parameter for a transaction request must be set." );
    256            }
    257
    258            String lockID = model.remove( "LOCKID" );
    259
    260            String releaseAction = model.remove( "RELEASEACTION" );
    261            boolean releaseAllFeatures = true;
    262            if ( releaseAction != null ) {
    263                if ( "SOME".equals( releaseAction ) ) {
    264                    releaseAllFeatures = false;
    265                } else if ( "ALL".equals( releaseAction ) ) {
    266                    releaseAllFeatures = true;
    267                } else {
    268                    throw new InvalidParameterValueException( "releaseAction", releaseAction );
    269                }
    270            }
    271
    272            QualifiedName[] typeNames = extractTypeNames( model );
    273
    274            String featureIdParameter = model.remove( "FEATUREID" );
    275            if ( typeNames == null && featureIdParameter == null ) {
    276                throw new InconsistentRequestException( "TypeName OR FeatureId parameter must be set." );
    277            }
    278
    279            // String[] featureIds = null;
    280            // if ( featureIdParameter != null ) {
    281            // // FEATUREID specified. Looking for featureId
    282            // // declaration TYPENAME contained in featureId declaration (eg.
    283            // // FEATUREID=InWaterA_1M.1013)
    284            // featureIds = StringTools.toArray( featureIdParameter, ",", false );
    285            // //typeNameSet = extractTypeNameFromFeatureId( featureIds, context, (HashSet) typeNameSet
    286            // );
    287            // }
    288
    289            // Filters
    290            // Map typeFilter = buildFilterMap( model, typeNames, featureIds, context );
    291
    292            // // BBOX
    293            // typeFilter = extractBBOXParameter( model, typeNames, typeFilter );
    294            //
    295            // if ( typeFilter == null || typeFilter.size() == 0 ) {
    296            // for ( int i = 0; i < typeNames.length; i++ ) {
    297            // typeFilter.put( typeNames[i], null );
    298            // }
    299            // }
    300
    301            List<TransactionOperation> operations = extractOperations( model, null );
    302
    303            return new Transaction( id, version, versionSpecificParameter, lockID, operations, releaseAllFeatures, null );
    304        }
    305
    306        /**
    307         * Extracts the {@link TransactionOperation}s contained in the given kvp request.
    308         *
    309         * @param model
    310         * @param typeFilter
    311         * @return List
    312         * @throws InconsistentRequestException
    313         */
    314        private static List<TransactionOperation> extractOperations( Map<String, String> model,
    315                                                                     Map<QualifiedName, Filter> typeFilter )
    316                                throws InconsistentRequestException {
    317            List<TransactionOperation> operation = new ArrayList<TransactionOperation>();
    318            String op = model.remove( "OPERATION" );
    319            if ( op == null ) {
    320                throw new InconsistentRequestException( "Operation parameter must be set" );
    321            }
    322            if ( op.equals( "Delete" ) ) {
    323                List<Delete> deletes = Delete.create( typeFilter );
    324                operation.addAll( deletes );
    325            } else {
    326                String msg = "Invalid OPERATION parameter '" + op
    327                             + "'. KVP Transactions only support the 'Delete' operation.";
    328                throw new InconsistentRequestException( msg );
    329            }
    330            return operation;
    331        }
    332
    333        /**
    334         * Creates a <code>Transaction</code> instance from a document that contains the DOM representation of the request.
    335         *
    336         * @param id
    337         * @param root
    338         *            element that contains the DOM representation of the request
    339         * @return transaction instance
    340         * @throws OGCWebServiceException
    341         */
    342        public static Transaction create( String id, Element root )
    343                                throws OGCWebServiceException {
    344            TransactionDocument doc = new TransactionDocument();
    345            doc.setRootElement( root );
    346            Transaction request;
    347            try {
    348                request = doc.parse( id );
    349            } catch ( XMLParsingException e ) {
    350                if ( e.getWrapped() != null ) {
    351                    throw e.getWrapped();
    352                }
    353                LOG.logError( e.getMessage(), e );
    354                throw new OGCWebServiceException( "Transaction", e.getMessage() );
    355            }
    356            return request;
    357        }
    358
    359        @Override
    360        public String toString() {
    361            String ret = this.getClass().getName();
    362            ret += "version: " + this.version + "\n";
    363            ret += "id: " + this.id + "\n";
    364            ret += "lockID: " + this.lockId + "\n";
    365            ret += "operations: \n";
    366            for ( int i = 0; i < operations.size(); i++ ) {
    367                ret += ( i + ": " + operations.get( i ) + "\n " );
    368            }
    369            ret += "releaseAllFeatures: " + this.releaseAction;
    370            return ret;
    371        }
    372
    373        /**
    374         * Adds missing namespaces in the names of requested feature types.
    375         * <p>
    376         * If the {@link QualifiedName} of a requested type has a null namespace, the first qualified feature type name of
    377         * the given {@link WFService} with the same local name is used instead.
    378         * <p>
    379         * Note: The method changes this request (the feature type names) and should only be called by the
    380         * <code>WFSHandler</code> class.
    381         *
    382         * @param wfs
    383         *            {@link WFService} instance that is used for the lookup of proper (qualified) feature type names
    384         */
    385        public void guessMissingNamespaces( WFService wfs ) {
    386
    387            Set<QualifiedName> featureNames = wfs.getMappedFeatureTypes().keySet();
    388            for ( int j = 0; j < operations.size(); j++ ) {
    389                TransactionOperation op = operations.get( j );
    390                if ( op instanceof Update ) {
    391                    Update update = (Update) op;
    392                    QualifiedName tn = update.getTypeName();
    393                    if ( tn.getNamespace() == null ) {
    394                        QualifiedName newTn = guessTypeNameNamespace( tn, featureNames );
    395                        update.setTypeName( newTn );
    396                    }
    397
    398                    if ( update.getReplacementProperties() != null ) {
    399                        Set<PropertyPath> propPaths = update.getReplacementProperties().keySet();
    400                        String defaultPrefix = update.getTypeName().getPrefix();
    401                        URI defaultNamespace = update.getTypeName().getNamespace();
    402                        guessMissingPropertyNamespace( propPaths, defaultPrefix, defaultNamespace, update, j );
    403                    }
    404
    405                } else if ( op instanceof Delete ) {
    406                    Delete delete = (Delete) op;
    407                    QualifiedName newTn = guessTypeNameNamespace( delete.getTypeName(), featureNames );
    408                    delete.setTypeName( newTn );
    409                }
    410            }
    411        }
    412
    413        private void guessMissingPropertyNamespace( Set<PropertyPath> propPaths, String defaultPrefix,
    414                                                    URI defaultNamespace, Update update, int j ) {
    415            for ( PropertyPath propPath : propPaths ) {
    416                for ( int i = 0; i < propPath.getAllSteps().size(); i++ ) {
    417                    PropertyPathStep step = propPath.getStep( i );
    418                    QualifiedName prop = step.getPropertyName();
    419                    if ( prop.getNamespace() == null ) {
    420                        // the following retrieves the old values the object involved
    421                        Map<PropertyPath, FeatureProperty> oldMap = update.getReplacementProperties();
    422                        List<PropertyPathStep> steps = propPath.getAllSteps();
    423                        FeatureProperty oldValue = oldMap.get( propPath );
    424                        oldMap.remove( propPath );
    425                        QualifiedName newQName = new QualifiedName( defaultPrefix, prop.getLocalName(), defaultNamespace );
    426
    427                        // replaces old values in the objects involved
    428                        step.setPropertyName( newQName );
    429                        FeatureProperty newValue = FeatureFactory.createFeatureProperty( newQName, oldValue.getValue() );
    430                        steps.set( i, step );
    431                        propPath.setSteps( steps );
    432                        oldMap.put( propPath, newValue );
    433                        update.setReplacementProperties( oldMap );
    434                        operations.set( j, update );
    435                    }
    436                }
    437            }
    438        }
    439
    440        private QualifiedName guessTypeNameNamespace( QualifiedName candidate, Set<QualifiedName> featureNames ) {
    441            if ( candidate.getNamespace() == null ) {
    442                for ( QualifiedName ftName : featureNames ) {
    443                    if ( ftName.getLocalName().equals( candidate.getLocalName() ) ) {
    444                        return ftName;
    445                    }
    446                }
    447            }
    448            return candidate;
    449        }
    450
    451    }