    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/ogcwebservices/wfs/operation/transaction/TransactionDocument.java $
    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 static org.deegree.framework.xml.XMLTools.appendElement;
    039    import static org.deegree.framework.xml.XMLTools.getNodeAsString;
    040    import static org.deegree.framework.xml.XMLTools.getNodeAsURI;
    041    import static org.deegree.framework.xml.XMLTools.getRequiredElements;
    042    import static org.deegree.i18n.Messages.get;
    043    import static org.deegree.ogcbase.CommonNamespaces.GML_PREFIX;
    044    import static org.deegree.ogcbase.CommonNamespaces.WFSNS;
    045    import static org.deegree.ogcbase.CommonNamespaces.WFS_PREFIX;
    046
    047    import java.io.IOException;
    048    import java.net.URI;
    049    import java.net.URL;
    050    import java.util.ArrayList;
    051    import java.util.LinkedHashMap;
    052    import java.util.List;
    053    import java.util.Map;
    054
    055    import org.deegree.datatypes.QualifiedName;
    056    import org.deegree.framework.log.ILogger;
    057    import org.deegree.framework.log.LoggerFactory;
    058    import org.deegree.framework.xml.XMLParsingException;
    059    import org.deegree.framework.xml.XMLTools;
    060    import org.deegree.i18n.Messages;
    061    import org.deegree.model.crs.UnknownCRSException;
    062    import org.deegree.model.feature.Feature;
    063    import org.deegree.model.feature.FeatureCollection;
    064    import org.deegree.model.feature.FeatureFactory;
    065    import org.deegree.model.feature.FeatureProperty;
    066    import org.deegree.model.feature.GMLFeatureCollectionDocument;
    067    import org.deegree.model.feature.GMLFeatureDocument;
    068    import org.deegree.model.filterencoding.AbstractFilter;
    069    import org.deegree.model.filterencoding.Filter;
    070    import org.deegree.ogcbase.CommonNamespaces;
    071    import org.deegree.ogcbase.PropertyPath;
    072    import org.deegree.ogcwebservices.InvalidParameterValueException;
    073    import org.deegree.ogcwebservices.MissingParameterValueException;
    074    import org.deegree.ogcwebservices.wfs.operation.AbstractWFSRequestDocument;
    075    import org.deegree.ogcwebservices.wfs.operation.transaction.Insert.ID_GEN;
    076    import org.w3c.dom.Element;
    077    import org.w3c.dom.Node;
    078    import org.w3c.dom.Text;
    079    import org.xml.sax.SAXException;
    080
    081    /**
    082     * Parser for "wfs:Transaction" requests and contained elements.
    083     *
    084     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
    085     * @author last edited by: $Author: mschneider $
    086     *
    087     * @version $Revision: 18544 $, $Date: 2009-07-20 16:14:38 +0200 (Mo, 20. Jul 2009) $
    088     */
    089    public class TransactionDocument extends AbstractWFSRequestDocument {
    090
    091        private static ILogger LOG = LoggerFactory.getLogger( TransactionDocument.class );
    092
    093        private static final long serialVersionUID = -394478447170286393L;
    094
    095        private static final String XML_TEMPLATE = "TransactionTemplate.xml";
    096
    097        private static final QualifiedName VALUE_ELEMENT_NAME = new QualifiedName( "wfs", "Value", CommonNamespaces.WFSNS );
    098
    099        /**
    100         * Creates a skeleton document that contains the root element and the namespace bindings only.
    101         *
    102         * @throws IOException
    103         * @throws SAXException
    104         */
    105        public void createEmptyDocument()
    106                                throws IOException, SAXException {
    107            URL url = TransactionDocument.class.getResource( XML_TEMPLATE );
    108            if ( url == null ) {
    109                throw new IOException( "The resource '" + XML_TEMPLATE + " could not be found." );
    110            }
    111            load( url );
    112        }
    113
    114        /**
    115         * Parses the underlying document into a <code>Transaction</code> request object.
    116         *
    117         * @param id
    118         * @return corresponding <code>Transaction</code> object
    119         * @throws XMLParsingException
    120         * @throws InvalidParameterValueException
    121         */
    122        public Transaction parse( String id )
    123                                throws XMLParsingException, InvalidParameterValueException {
    124
    125            checkServiceAttribute();
    126            String version = checkVersionAttribute();
    127
    128            Element root = this.getRootElement();
    129            String lockId = null;
    130            boolean releaseAllFeatures = parseReleaseActionParameter();
    131
    132            List<TransactionOperation> operations = new ArrayList<TransactionOperation>();
    133            List<Element> list = XMLTools.getElements( root, "*", nsContext );
    134            for ( int i = 0; i < list.size(); i++ ) {
    135                Element element = list.get( i );
    136                if ( "LockId".equals( element.getLocalName() )
    137                     && CommonNamespaces.WFSNS.toString().equals( element.getNamespaceURI() ) ) {
    138                    lockId = XMLTools.getNodeAsString( element, "text()", nsContext, null );
    139                } else {
    140                    TransactionOperation operation;
    141
    142                    // workaround because API changes are not allowed
    143                    try {
    144                        operation = parseOperation( element );
    145                        operations.add( operation );
    146                    } catch ( MissingParameterValueException e ) {
    147                        throw new XMLParsingException( true, e );
    148                    }
    149                }
    150            }
    151
    152            // vendorspecific attributes; required by deegree rights management
    153            Map<String, String> vendorSpecificParams = parseDRMParams( root );
    154
    155            return new Transaction( id, version, vendorSpecificParams, lockId, operations, releaseAllFeatures, this );
    156        }
    157
    158        /**
    159         * Parses the optional "releaseAction" attribute of the root element.
    160         *
    161         * @return true, if releaseAction equals "ALL" (or is left out), false if it equals "SOME"
    162         * @throws InvalidParameterValueException
    163         *             if parameter
    164         * @throws XMLParsingException
    165         */
    166        private boolean parseReleaseActionParameter()
    167                                throws InvalidParameterValueException, XMLParsingException {
    168
    169            String releaseAction = XMLTools.getNodeAsString( getRootElement(), "@releaseAction", nsContext, "ALL" );
    170            boolean releaseAllFeatures = true;
    171            if ( releaseAction != null ) {
    172                if ( "SOME".equals( releaseAction ) ) {
    173                    releaseAllFeatures = false;
    174                } else if ( "ALL".equals( releaseAction ) ) {
    175                    releaseAllFeatures = true;
    176                } else {
    177                    throw new InvalidParameterValueException( "releaseAction", releaseAction );
    178                }
    179            }
    180            return releaseAllFeatures;
    181        }
    182
    183        /**
    184         * Parses the given element as a <code>TransactionOperation</code>.
    185         * <p>
    186         * The given element must be one of the following:
    187         * <ul>
    188         * <li>wfs:Insert</li>
    189         * <li>wfs:Update</li>
    190         * <li>wfs:Delete</li>
    191         * <li>wfs:Native</li>
    192         * </ul>
    193         *
    194         * @param element
    195         *            operation element
    196         * @return corresponding <code>TransactionOperation</code> object
    197         * @throws XMLParsingException
    198         * @throws InvalidParameterValueException
    199         * @throws MissingParameterValueException
    200         */
    201        private TransactionOperation parseOperation( Element element )
    202                                throws XMLParsingException, InvalidParameterValueException, MissingParameterValueException {
    203
    204            TransactionOperation operation = null;
    205
    206            if ( !element.getNamespaceURI().equals( CommonNamespaces.WFSNS.toString() ) ) {
    207                String msg = Messages.getMessage( "WFS_INVALID_OPERATION", element.getNodeName() );
    208                throw new XMLParsingException( msg );
    209            }
    210            if ( element.getLocalName().equals( "Insert" ) ) {
    211                operation = parseInsert( element );
    212            } else if ( element.getLocalName().equals( "Update" ) ) {
    213                operation = parseUpdate( element );
    214            } else if ( element.getLocalName().equals( "Delete" ) ) {
    215                operation = parseDelete( element );
    216            } else if ( element.getLocalName().equals( "Native" ) ) {
    217                throw new InvalidParameterValueException( get( "WFS_NATIVE_OPERATIONS_UNSUPPORTED" ) );
    218            } else {
    219                String msg = Messages.getMessage( "WFS_INVALID_OPERATION", element.getNodeName() );
    220                throw new XMLParsingException( msg );
    221            }
    222
    223            return operation;
    224        }
    225
    226        /**
    227         * Parses the given element as a "wfs:Insert" operation.
    228         *
    229         * @param element
    230         *            "wfs:Insert" operation
    231         * @return corresponding Insert object
    232         * @throws XMLParsingException
    233         * @throws InvalidParameterValueException
    234         */
    235        private Insert parseInsert( Element element )
    236                                throws XMLParsingException, InvalidParameterValueException {
    237            FeatureCollection fc = null;
    238            ID_GEN mode = parseIdGen( element );
    239            String handle = getNodeAsString( element, "@handle", nsContext, null );
    240            URI srsName = getNodeAsURI( element, "@srsName", nsContext, null );
    241            List<Element> childElementList = getRequiredElements( element, "*", nsContext );
    242
    243            // either one _gml:FeatureCollection element or any number of _gml:Feature elements
    244            boolean isFeatureCollection = isFeatureCollection( childElementList.get( 0 ) );
    245
    246            try {
    247                GMLFeatureCollectionDocument doc = new GMLFeatureCollectionDocument( false );
    248
    249                if ( isFeatureCollection ) {
    250                    LOG.logDebug( "Insert (FeatureCollection)" );
    251                    doc.setRootElement( childElementList.get( 0 ) );
    252                } else {
    253                    LOG.logDebug( "Insert (Features)" );
    254                    Element fcElem = appendElement( element, WFSNS, WFS_PREFIX + ":FeatureCollection" );
    255
    256                    for ( Element e : childElementList ) {
    257                        Node old = element.removeChild( e );
    258                        Element elem = appendElement( fcElem, GMLNS, GML_PREFIX + ":featureMember" );
    259                        elem.appendChild( old );
    260                    }
    261
    262                    doc.setRootElement( fcElem );
    263                }
    264
    265                doc.setSystemId( this.getSystemId() );
    266                fc = doc.parse();
    267            } catch ( XMLParsingException e ) {
    268                LOG.logDebug( "Stack trace:", e );
    269                throw new InvalidParameterValueException( e.getMessage() );
    270            } catch ( Exception e ) {
    271                throw new XMLParsingException( e.getMessage(), e );
    272            }
    273
    274            return new Insert( handle, mode, srsName, fc );
    275        }
    276
    277        /**
    278         * Checks whether the given element is a (concrete) gml:_FeatureCollection element.
    279         * <p>
    280         * NOTE: This check is far from perfect. Instead of determining the type of the element by inspecting the schema,
    281         * the decision is made by checking if the element name contains 'FeatureCollection'. Also, if child elements with
    282         * name "gml:featureMember" are found, it is considered to be a feature collection.
    283         *
    284         * @param element
    285         *            potential gml:_FeatureCollection element
    286         * @return true, if the given element appears to be a gml:_FeatureCollection element, false otherwise
    287         * @throws XMLParsingException
    288         */
    289        private boolean isFeatureCollection( Element element )
    290                                throws XMLParsingException {
    291            boolean containsFeatureCollection = false;
    292            if ( element.getLocalName().contains( "FeatureCollection" ) ) {
    293                containsFeatureCollection = true;
    294            } else {
    295                List<Node> nodeList = XMLTools.getNodes( element, "gml:featureMember", nsContext );
    296                if ( nodeList.size() > 0 ) {
    297                    containsFeatureCollection = true;
    298                }
    299            }
    300            return containsFeatureCollection;
    301        }
    302
    303        /**
    304         * Parses the optional "idGen" attribute of the given "wfs:Insert" element.
    305         *
    306         * @param element
    307         *            "wfs:Insert" element
    308         * @return "idGen" attribute code
    309         * @throws XMLParsingException
    310         */
    311        private ID_GEN parseIdGen( Element element )
    312                                throws XMLParsingException {
    313            ID_GEN mode;
    314            String idGen = XMLTools.getNodeAsString( element, "@idgen", nsContext, Insert.ID_GEN_GENERATE_NEW_STRING );
    315
    316            if ( Insert.ID_GEN_GENERATE_NEW_STRING.equals( idGen ) ) {
    317                mode = Insert.ID_GEN.GENERATE_NEW;
    318            } else if ( Insert.ID_GEN_USE_EXISTING_STRING.equals( idGen ) ) {
    319                mode = Insert.ID_GEN.USE_EXISTING;
    320            } else if ( Insert.ID_GEN_REPLACE_DUPLICATE_STRING.equals( idGen ) ) {
    321                mode = Insert.ID_GEN.REPLACE_DUPLICATE;
    322            } else {
    323                String msg = Messages.getMessage( "WFS_INVALID_IDGEN_VALUE", idGen, Insert.ID_GEN_GENERATE_NEW_STRING,
    324                                                  Insert.ID_GEN_REPLACE_DUPLICATE_STRING, Insert.ID_GEN_USE_EXISTING_STRING );
    325                throw new XMLParsingException( msg );
    326            }
    327            return mode;
    328        }
    329
    330        /**
    331         * Parses the given element as a "wfs:Update" operation.
    332         *
    333         * @param element
    334         *            "wfs:Update" operation
    335         * @return corresponding Update object
    336         * @throws XMLParsingException
    337         */
    338        private Update parseUpdate( Element element )
    339                                throws XMLParsingException {
    340
    341            Update update = null;
    342            String handle = XMLTools.getNodeAsString( element, "@handle", nsContext, null );
    343            QualifiedName typeName = XMLTools.getRequiredNodeAsQualifiedName( element, "@typeName", nsContext );
    344
    345            Element filterElement = (Element) XMLTools.getNode( element, "ogc:Filter", nsContext );
    346            Filter filter = null;
    347            if ( filterElement != null ) {
    348                filter = AbstractFilter.buildFromDOM( filterElement, false );
    349            }
    350
    351            List<Node> properties = XMLTools.getNodes( element, "wfs:Property", nsContext );
    352            if ( properties.size() > 0 ) {
    353                // "standard" update (specifies properties + their replacement values)
    354                LOG.logDebug( "Update (replacement properties)" );
    355                Map<PropertyPath, FeatureProperty> replacementProps = new LinkedHashMap<PropertyPath, FeatureProperty>();
    356                Map<PropertyPath, Node> rawProps = new LinkedHashMap<PropertyPath, Node>();
    357                for ( int i = 0; i < properties.size(); i++ ) {
    358                    Node propNode = properties.get( i );
    359                    Text propNameNode = (Text) XMLTools.getRequiredNode( propNode, "wfs:Name/text()", nsContext );
    360                    PropertyPath propPath = parsePropertyPath( propNameNode );
    361
    362                    // TODO remove this (only needed, because Update makes the DOM representation available)
    363                    Node valueNode = XMLTools.getNode( propNode, "wfs:Value/*", nsContext );
    364                    if ( valueNode == null ) {
    365                        valueNode = XMLTools.getNode( propNode, "wfs:Value/text()", nsContext );
    366                    }
    367                    rawProps.put( propPath, propNode );
    368
    369                    FeatureProperty replacementProp = null;
    370                    QualifiedName propName = propPath.getStep( propPath.getSteps() - 1 ).getPropertyName();
    371
    372                    if ( replacementProps.get( propPath ) != null ) {
    373                        String msg = Messages.getMessage( "WFS_UPDATE_DUPLICATE_PROPERTY", handle, propPath );
    374                        throw new XMLParsingException( msg );
    375                    }
    376
    377                    // TODO improve generation of FeatureProperty from "wfs:Value" element
    378                    Object propValue = null;
    379                    Node node = XMLTools.getNode( propNode, "wfs:Value", nsContext );
    380                    if ( node != null ) {
    381                        GMLFeatureDocument dummyDoc = new GMLFeatureDocument( false );
    382                        dummyDoc.setRootElement( (Element) propNode );
    383                        dummyDoc.setSystemId( this.getSystemId() );
    384                        Feature dummyFeature;
    385                        try {
    386                            dummyFeature = dummyDoc.parseFeature();
    387                        } catch ( UnknownCRSException e ) {
    388                            throw new XMLParsingException( e.getMessage(), e );
    389                        }
    390                        propValue = dummyFeature.getProperties( VALUE_ELEMENT_NAME )[0].getValue();
    391                    }
    392                    replacementProp = FeatureFactory.createFeatureProperty( propName, propValue );
    393                    replacementProps.put( propPath, replacementProp );
    394                }
    395                update = new Update( handle, typeName, replacementProps, rawProps, filter );
    396            } else {
    397                // deegree specific update (specifies a single replacement feature)
    398                LOG.logDebug( "Update (replacement feature)" );
    399                Feature replacementFeature = null;
    400                List<Element> childElementList = XMLTools.getRequiredElements( element, "*", nsContext );
    401                if ( ( filter == null && childElementList.size() != 1 )
    402                     || ( filter != null && childElementList.size() != 2 ) ) {
    403                    String msg = Messages.getMessage( "WFS_UPDATE_FEATURE_REPLACE", handle );
    404                    throw new XMLParsingException( msg );
    405                }
    406                try {
    407                    GMLFeatureDocument doc = new GMLFeatureDocument( false );
    408                    doc.setRootElement( childElementList.get( 0 ) );
    409                    doc.setSystemId( this.getSystemId() );
    410                    replacementFeature = doc.parseFeature();
    411                } catch ( Exception e ) {
    412                    String msg = Messages.getMessage( "WFS_UPDATE_FEATURE_REPLACE", handle );
    413                    throw new XMLParsingException( msg );
    414                }
    415                update = new Update( handle, typeName, replacementFeature, filter );
    416            }
    417
    418            return update;
    419        }
    420
    421        /**
    422         * Parses the given element as a "wfs:Delete" operation.
    423         *
    424         * @param element
    425         *            "wfs:Delete" operation
    426         * @return corresponding Delete object
    427         * @throws MissingParameterValueException
    428         */
    429        private Delete parseDelete( Element element )
    430                                throws XMLParsingException, MissingParameterValueException {
    431
    432            String handle = XMLTools.getNodeAsString( element, "@handle", nsContext, null );
    433            QualifiedName typeName = XMLTools.getRequiredNodeAsQualifiedName( element, "@typeName", nsContext );
    434
    435            Element filterElement = XMLTools.getElement( element, "ogc:Filter", nsContext );
    436            if ( filterElement == null ) {
    437                throw new MissingParameterValueException( "filter", get( "WFS_MISSING_REQUIRED_ELEMENT", "ogc:Filter" ) );
    438            }
    439
    440            Filter filter = AbstractFilter.buildFromDOM( filterElement, false );
    441            return new Delete( handle, typeName, filter );
    442        }
    443    }