    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/ogcwebservices/wfs/XMLFactory.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;
    037
    038    import static org.deegree.framework.xml.XMLTools.appendElement;
    039    import static org.deegree.ogcbase.CommonNamespaces.OGC_PREFIX;
    040    import static org.deegree.ogcbase.CommonNamespaces.WFSNS;
    041    import static org.deegree.ogcbase.CommonNamespaces.WFS_PREFIX;
    042
    043    import java.io.IOException;
    044    import java.io.StringReader;
    045    import java.net.URI;
    046    import java.util.Collection;
    047    import java.util.HashSet;
    048    import java.util.Iterator;
    049    import java.util.List;
    050    import java.util.Map;
    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.StringTools;
    056    import org.deegree.framework.xml.XMLException;
    057    import org.deegree.framework.xml.XMLParsingException;
    058    import org.deegree.framework.xml.XMLTools;
    059    import org.deegree.io.datastore.FeatureId;
    060    import org.deegree.model.feature.Feature;
    061    import org.deegree.model.feature.FeatureException;
    062    import org.deegree.model.feature.FeatureProperty;
    063    import org.deegree.model.feature.GMLFeatureAdapter;
    064    import org.deegree.model.filterencoding.Filter;
    065    import org.deegree.model.filterencoding.Function;
    066    import org.deegree.model.filterencoding.capabilities.FilterCapabilities;
    067    import org.deegree.model.metadata.iso19115.Keywords;
    068    import org.deegree.model.spatialschema.Envelope;
    069    import org.deegree.model.spatialschema.Geometry;
    070    import org.deegree.model.spatialschema.GeometryException;
    071    import org.deegree.ogcbase.CommonNamespaces;
    072    import org.deegree.ogcbase.PropertyPath;
    073    import org.deegree.ogcbase.SortProperty;
    074    import org.deegree.ogcbase.XLinkPropertyPath;
    075    import org.deegree.ogcwebservices.getcapabilities.Contents;
    076    import org.deegree.ogcwebservices.getcapabilities.MetadataURL;
    077    import org.deegree.ogcwebservices.getcapabilities.OperationsMetadata;
    078    import org.deegree.ogcwebservices.getcapabilities.ServiceIdentification;
    079    import org.deegree.ogcwebservices.getcapabilities.ServiceProvider;
    080    import org.deegree.ogcwebservices.wfs.capabilities.FeatureTypeList;
    081    import org.deegree.ogcwebservices.wfs.capabilities.FormatType;
    082    import org.deegree.ogcwebservices.wfs.capabilities.GMLObject;
    083    import org.deegree.ogcwebservices.wfs.capabilities.Operation;
    084    import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities;
    085    import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilitiesDocument;
    086    import org.deegree.ogcwebservices.wfs.capabilities.WFSFeatureType;
    087    import org.deegree.ogcwebservices.wfs.operation.GetFeature;
    088    import org.deegree.ogcwebservices.wfs.operation.GetFeatureDocument;
    089    import org.deegree.ogcwebservices.wfs.operation.Lock;
    090    import org.deegree.ogcwebservices.wfs.operation.LockFeature;
    091    import org.deegree.ogcwebservices.wfs.operation.LockFeatureDocument;
    092    import org.deegree.ogcwebservices.wfs.operation.LockFeatureResponse;
    093    import org.deegree.ogcwebservices.wfs.operation.LockFeatureResponseDocument;
    094    import org.deegree.ogcwebservices.wfs.operation.Query;
    095    import org.deegree.ogcwebservices.wfs.operation.GetFeature.RESULT_TYPE;
    096    import org.deegree.ogcwebservices.wfs.operation.transaction.Delete;
    097    import org.deegree.ogcwebservices.wfs.operation.transaction.Insert;
    098    import org.deegree.ogcwebservices.wfs.operation.transaction.InsertResults;
    099    import org.deegree.ogcwebservices.wfs.operation.transaction.Transaction;
    100    import org.deegree.ogcwebservices.wfs.operation.transaction.TransactionDocument;
    101    import org.deegree.ogcwebservices.wfs.operation.transaction.TransactionOperation;
    102    import org.deegree.ogcwebservices.wfs.operation.transaction.TransactionResponse;
    103    import org.deegree.ogcwebservices.wfs.operation.transaction.TransactionResponseDocument;
    104    import org.deegree.ogcwebservices.wfs.operation.transaction.Update;
    105    import org.w3c.dom.Comment;
    106    import org.w3c.dom.Document;
    107    import org.w3c.dom.Element;
    108    import org.xml.sax.SAXException;
    109
    110    /**
    111     * Responsible for the generation of XML representations of objects from the WFS context.
    112     *
    113     * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider </a>
    114     * @author last edited by: $Author: mschneider $
    115     *
    116     * @version $Revision: 18281 $, $Date: 2009-06-29 15:07:08 +0200 (Mo, 29. Jun 2009) $
    117     */
    118    public class XMLFactory extends org.deegree.owscommon.XMLFactory {
    119
    120        private static final URI WFS = CommonNamespaces.WFSNS;
    121
    122        private static final URI OGCNS = CommonNamespaces.OGCNS;
    123
    124        // private static final String PRE_WFS = CommonNamespaces.WFS_PREFIX + ":";
    125
    126        private static final ILogger LOG = LoggerFactory.getLogger( XMLFactory.class );
    127
    128        /**
    129         * Exports a <code>WFSCapabilities</code> instance to a <code>WFSCapabilitiesDocument</code>.
    130         *
    131         * @param capabilities
    132         * @return DOM representation of the <code>WFSCapabilities</code>
    133         * @throws IOException
    134         *             if XML template could not be loaded
    135         */
    136        public static WFSCapabilitiesDocument export( WFSCapabilities capabilities )
    137                                throws IOException {
    138            return export( capabilities, true, true, true, true );
    139        }
    140
    141        /**
    142         * Exports a <code>WFSCapabilities</code> instance to a <code>WFSCapabilitiesDocument</code>.
    143         *
    144         * @param capabilities
    145         * @param sections
    146         *            names of sections to be exported, may contain 'All'
    147         * @return DOM representation of the <code>WFSCapabilities</code>
    148         * @throws IOException
    149         *             if XML template could not be loaded
    150         */
    151        public static WFSCapabilitiesDocument export( WFSCapabilities capabilities, String[] sections )
    152                                throws IOException {
    153
    154            if ( sections == null || sections.length == 0 ) {
    155                return export( capabilities );
    156            }
    157
    158            if ( sections.length == 1 && sections[0].equalsIgnoreCase( "all" ) ) {
    159                return export( capabilities );
    160            }
    161
    162            boolean ident = false, provider = false, md = false, ftlist = false;
    163
    164            HashSet<String> set = new HashSet<String>();
    165            for ( String s : sections ) {
    166                set.add( s.toLowerCase() );
    167            }
    168
    169            LOG.logDebug( "The set of requested sections was", set );
    170
    171            if ( set.contains( "serviceidentification" ) ) {
    172                ident = true;
    173            }
    174            if ( set.contains( "serviceprovider" ) ) {
    175                provider = true;
    176            }
    177            if ( set.contains( "operationsmetadata" ) ) {
    178                md = true;
    179            }
    180            if ( set.contains( "featuretypelist" ) ) {
    181                ftlist = true;
    182            }
    183
    184            return export( capabilities, ident, provider, md, ftlist );
    185        }
    186
    187        /**
    188         * @param capabilities
    189         * @param serviceIdentification
    190         * @param serviceProvider
    191         * @param operationsMetadata
    192         * @param featureTypeList
    193         * @return the exported capabilities document (possibly missing some sections if one of the flags was set to false)
    194         * @throws IOException
    195         */
    196        public static WFSCapabilitiesDocument export( WFSCapabilities capabilities, boolean serviceIdentification,
    197                                                      boolean serviceProvider, boolean operationsMetadata,
    198                                                      boolean featureTypeList )
    199                                throws IOException {
    200            WFSCapabilitiesDocument capabilitiesDocument = new WFSCapabilitiesDocument();
    201
    202            try {
    203                capabilitiesDocument.createEmptyDocument();
    204                Element root = capabilitiesDocument.getRootElement();
    205
    206                root.setAttribute( "updateSequence", capabilities.getUpdateSequence() );
    207
    208                if ( serviceIdentification ) {
    209                    ServiceIdentification si = capabilities.getServiceIdentification();
    210                    if ( si != null ) {
    211                        appendServiceIdentification( root, si );
    212                    }
    213                }
    214
    215                if ( serviceProvider ) {
    216                    ServiceProvider sp = capabilities.getServiceProvider();
    217                    if ( sp != null ) {
    218                        appendServiceProvider( root, sp );
    219                    }
    220                }
    221
    222                if ( operationsMetadata ) {
    223                    OperationsMetadata om = capabilities.getOperationsMetadata();
    224                    if ( om != null ) {
    225                        appendOperationsMetadata( root, om );
    226                    }
    227                }
    228
    229                if ( featureTypeList ) {
    230                    FeatureTypeList ftl = capabilities.getFeatureTypeList();
    231                    if ( ftl != null ) {
    232                        appendFeatureTypeList( root, ftl );
    233                    }
    234                }
    235
    236                GMLObject[] servesGMLObjectTypes = capabilities.getServesGMLObjectTypeList();
    237                if ( servesGMLObjectTypes != null ) {
    238                    appendGMLObjectTypeList( root, WFS, "ServesGMLObjectTypeList", servesGMLObjectTypes );
    239                }
    240                GMLObject[] supportsGMLObjectTypes = capabilities.getSupportsGMLObjectTypeList();
    241                if ( supportsGMLObjectTypes != null ) {
    242                    appendGMLObjectTypeList( root, WFS, "SupportsGMLObjectTypeList", supportsGMLObjectTypes );
    243                }
    244                Contents contents = capabilities.getContents();
    245                if ( contents != null ) {
    246                    // appendContents(root, contents);
    247                }
    248
    249                FilterCapabilities fc = capabilities.getFilterCapabilities();
    250                if ( fc != null ) {
    251                    org.deegree.model.filterencoding.XMLFactory.appendFilterCapabilities110( root, fc );
    252                }
    253
    254            } catch ( SAXException e ) {
    255                LOG.logError( e.getMessage(), e );
    256            }
    257            return capabilitiesDocument;
    258        }
    259
    260        /**
    261         * Appends the DOM representation of the {@link ServiceIdentification} section to the passed {@link Element}.
    262         *
    263         * @param root
    264         * @param serviceIdentification
    265         */
    266        protected static void appendServiceIdentification( Element root, ServiceIdentification serviceIdentification ) {
    267
    268            // 'ServiceIdentification'-element
    269            Element serviceIdentificationNode = XMLTools.appendElement( root, OWSNS, "ows:ServiceIdentification" );
    270
    271            // the optional title element
    272            String tmp = serviceIdentification.getTitle();
    273            if ( tmp != null && !"".equals( tmp ) ) {
    274                XMLTools.appendElement( serviceIdentificationNode, OWSNS, "ows:Title", tmp );
    275            }
    276
    277            // the optional abstract element
    278            tmp = serviceIdentification.getAbstract();
    279            if ( tmp != null && !"".equals( tmp ) ) {
    280                XMLTools.appendElement( serviceIdentificationNode, OWSNS, "ows:Abstract", tmp );
    281            }
    282
    283            // the optional keywords element
    284            appendKeywords( serviceIdentificationNode, serviceIdentification.getKeywords(), OWSNS );
    285
    286            // 'ServiceType'-element
    287            XMLTools.appendElement( serviceIdentificationNode, OWSNS, "ows:ServiceType",
    288                                    serviceIdentification.getServiceType().getCode() );
    289
    290            // 'ServiceTypeVersion'-elements
    291            String[] versions = serviceIdentification.getServiceTypeVersions();
    292            for ( int i = 0; i < versions.length; i++ ) {
    293                XMLTools.appendElement( serviceIdentificationNode, OWSNS, "ows:ServiceTypeVersion", versions[i] );
    294            }
    295
    296            // 'Fees'-element
    297            XMLTools.appendElement( serviceIdentificationNode, OWSNS, "ows:Fees", serviceIdentification.getFees() );
    298
    299            // 'AccessConstraints'-element
    300            String[] constraints = serviceIdentification.getAccessConstraints();
    301            if ( constraints != null ) {
    302                for ( int i = 0; i < constraints.length; i++ ) {
    303                    XMLTools.appendElement( serviceIdentificationNode, OWSNS, "ows:AccessConstraints", constraints[i] );
    304                }
    305            }
    306        }
    307
    308        /**
    309         * Appends a <code>ows:Keywords</code> -element for each <code>Keywords</code> object of the passed array to the
    310         * passed <code>Element</code>.
    311         *
    312         * @param xmlNode
    313         * @param keywords
    314         * @param namespaceURI
    315         */
    316        protected static void appendKeywords( Element xmlNode, Keywords[] keywords, URI namespaceURI ) {
    317            if ( keywords != null ) {
    318                for ( int i = 0; i < keywords.length; i++ ) {
    319                    Element node = XMLTools.appendElement( xmlNode, namespaceURI, "ows:Keywords" );
    320                    appendKeywords( node, keywords[i], namespaceURI );
    321                }
    322            }
    323        }
    324
    325        /**
    326         * Appends a <code>Keyword</code> -element to the passed <code>Element</code> and fills it with the available
    327         * keywords.
    328         *
    329         * @param xmlNode
    330         * @param keywords
    331         * @param namespaceURI
    332         */
    333        protected static void appendKeywords( Element xmlNode, Keywords keywords, URI namespaceURI ) {
    334            if ( keywords != null ) {
    335                String[] kw = keywords.getKeywords();
    336                for ( int i = 0; i < kw.length; i++ ) {
    337                    XMLTools.appendElement( xmlNode, namespaceURI, "ows:Keyword", kw[i] );
    338                }
    339                if ( keywords.getThesaurusName() != null ) {
    340                    XMLTools.appendElement( xmlNode, namespaceURI, "ows:Type", keywords.getThesaurusName() );
    341                }
    342            }
    343        }
    344
    345        /**
    346         * Exports a <code>GetFeature</code> instance to a <code>GetFeatureDocument</code>.
    347         *
    348         * @param getFeature
    349         *            request to be exported
    350         * @return XML representation of the <code>GetFeature</code> request
    351         * @throws IOException
    352         * @throws XMLParsingException
    353         */
    354        public static GetFeatureDocument export( GetFeature getFeature )
    355                                throws IOException, XMLParsingException {
    356
    357            GetFeatureDocument xml = new GetFeatureDocument();
    358            try {
    359                xml.load( XMLFactory.class.getResource( "GetFeatureTemplate.xml" ) );
    360            } catch ( SAXException e ) {
    361                throw new XMLParsingException( "could not parse GetFeatureTemplate.xml", e );
    362            }
    363            Element root = xml.getRootElement();
    364            root.setAttribute( "outputFormat", getFeature.getOutputFormat() );
    365            root.setAttribute( "service", "WFS" );
    366            root.setAttribute( "version", getFeature.getVersion() );
    367            if ( getFeature.getHandle() != null ) {
    368                root.setAttribute( "handle", getFeature.getHandle() );
    369            }
    370            if ( getFeature.getResultType() == RESULT_TYPE.HITS ) {
    371                root.setAttribute( "resultType", "hits" );
    372            } else {
    373                root.setAttribute( "resultType", "results" );
    374            }
    375            if ( getFeature.getMaxFeatures() > 0 ) {
    376                root.setAttribute( "maxFeatures", "" + getFeature.getMaxFeatures() );
    377            }
    378            if ( getFeature.getStartPosition() > 0 ) {
    379                root.setAttribute( "startPosition", "" + getFeature.getStartPosition() );
    380            }
    381            if ( getFeature.getTraverseXLinkDepth() >= 0 ) {
    382                root.setAttribute( "traverseXlinkDepth", "" + getFeature.getTraverseXLinkDepth() );
    383            }
    384            if ( getFeature.getTraverseXLinkExpiry() >= 0 ) {
    385                root.setAttribute( "traverseXlinkExpiry", "" + getFeature.getTraverseXLinkExpiry() );
    386            }
    387            Query[] queries = getFeature.getQuery();
    388            for ( int i = 0; i < queries.length; i++ ) {
    389                appendQuery( root, queries[i] );
    390            }
    391            return xml;
    392        }
    393
    394        /**
    395         * Exports a {@link LockFeature} request instance to a {@link LockFeatureDocument}.
    396         *
    397         * @param request
    398         *            request to be exported
    399         * @return XML representation of the <code>LockFeature</code> request
    400         * @throws IOException
    401         * @throws XMLParsingException
    402         * @throws SAXException
    403         */
    404        public static LockFeatureDocument export( LockFeature request )
    405                                throws IOException, XMLParsingException, SAXException {
    406
    407            LockFeatureDocument doc = new LockFeatureDocument();
    408            doc.createEmptyDocument();
    409
    410            Element root = doc.getRootElement();
    411            root.setAttribute( "version", request.getVersion() );
    412            root.setAttribute( "service", "WFS" );
    413            if ( request.getHandle() != null ) {
    414                root.setAttribute( "handle", request.getHandle() );
    415            }
    416            root.setAttribute( "expiry", "" + request.getExpiry() );
    417            root.setAttribute( "lockAction", "" + request.getLockAction() );
    418
    419            List<Lock> locks = request.getLocks();
    420            for ( Lock lock : locks ) {
    421                appendLock( root, lock );
    422            }
    423            return doc;
    424        }
    425
    426        /**
    427         * Appends the XML representation of the given {@link Lock} to the given element.
    428         *
    429         * @param root
    430         * @param lock
    431         */
    432        private static void appendLock( Element root, Lock lock )
    433                                throws IOException, XMLParsingException {
    434
    435            Element lockElement = XMLTools.appendElement( root, WFS, "Lock" );
    436            if ( lock.getHandle() != null ) {
    437                lockElement.setAttribute( "handle", lock.getHandle() );
    438            }
    439            QualifiedName typeName = lock.getTypeName();
    440            if ( typeName.getPrefix() != null ) {
    441                lockElement.setAttribute( "xmlns:" + typeName.getPrefix(), typeName.getNamespace().toASCIIString() );
    442            }
    443            lockElement.setAttribute( "typeName", typeName.getPrefixedName() );
    444
    445            // copy filter into Lock element
    446            if ( lock.getFilter() != null ) {
    447                StringReader sr = new StringReader( lock.getFilter().to110XML().toString() );
    448                Document doc;
    449                try {
    450                    doc = XMLTools.parse( sr );
    451                } catch ( SAXException e ) {
    452                    throw new XMLParsingException( "Could not parse filter.", e );
    453                }
    454                Element elem = XMLTools.appendElement( lockElement, OGCNS, "ogc:Filter" );
    455                XMLTools.copyNode( doc.getDocumentElement(), elem );
    456            }
    457        }
    458
    459        /**
    460         * Exports a {@link LockFeatureResponse} instance to its XML representation.
    461         *
    462         * @param response
    463         *            response to be exported
    464         * @return XML representation of the <code>LockFeatureResponse</code>
    465         * @throws IOException
    466         * @throws SAXException
    467         */
    468        public static LockFeatureResponseDocument export( LockFeatureResponse response )
    469                                throws IOException, SAXException {
    470
    471            LockFeatureResponseDocument doc = new LockFeatureResponseDocument();
    472            doc.createEmptyDocument();
    473
    474            Element root = doc.getRootElement();
    475            XMLTools.appendElement( root, WFS, "LockId", response.getLockId() );
    476            String[] fids = response.getFeaturesLocked();
    477            if ( fids.length != 0 ) {
    478                Element featuresLockedElement = XMLTools.appendElement( root, WFS, "FeaturesLocked" );
    479                for ( String fid : fids ) {
    480                    appendFeatureId( featuresLockedElement, fid );
    481                }
    482            }
    483            fids = response.getFeaturesNotLocked();
    484            if ( fids.length != 0 ) {
    485                Element featuresNotLockedElement = XMLTools.appendElement( root, WFS, "FeaturesNotLocked" );
    486                for ( String fid : fids ) {
    487                    appendFeatureId( featuresNotLockedElement, fid );
    488                }
    489            }
    490            return doc;
    491        }
    492
    493        /**
    494         * Exports a {@link Transaction} instance to its XML representation.
    495         *
    496         * @param transaction
    497         *            transaction to export
    498         * @return XML representation of transaction
    499         * @throws IOException
    500         * @throws XMLParsingException
    501         */
    502        public static TransactionDocument export( Transaction transaction )
    503                                throws IOException, XMLParsingException {
    504
    505            TransactionDocument xml = new TransactionDocument();
    506            try {
    507                xml.createEmptyDocument();
    508            } catch ( SAXException e ) {
    509                throw new IOException( e.getMessage() );
    510            }
    511            Element root = xml.getRootElement();
    512            List<TransactionOperation> ops = transaction.getOperations();
    513            for ( int i = 0; i < ops.size(); i++ ) {
    514                try {
    515                    if ( ops.get( i ) instanceof Insert ) {
    516                        appendInsert( root, (Insert) ops.get( i ) );
    517                    } else if ( ops.get( i ) instanceof Update ) {
    518                        appendUpdate( root, (Update) ops.get( i ) );
    519                    } else if ( ops.get( i ) instanceof Delete ) {
    520                        appendDelete( root, (Delete) ops.get( i ) );
    521                    }
    522                } catch ( Exception e ) {
    523                    LOG.logError( e.getMessage(), e );
    524                    throw new XMLParsingException( e.getMessage() );
    525                }
    526            }
    527            return xml;
    528        }
    529
    530        /**
    531         * Adds the XML representation of a <code>Delete</code> operation to the given element.
    532         *
    533         * @param root
    534         * @param delete
    535         */
    536        private static void appendDelete( Element root, Delete delete ) {
    537            Element el = XMLTools.appendElement( root, WFS, "Delete" );
    538            if ( delete.getHandle() != null ) {
    539                el.setAttribute( "handle", delete.getHandle() );
    540            }
    541            el.setAttribute( "typeName", delete.getTypeName().getPrefixedName() );
    542            // ensure that the type's namespace is declared
    543            el.setAttribute( "xmlns:" + delete.getTypeName().getPrefix(),
    544                             delete.getTypeName().getNamespace().toASCIIString() );
    545
    546            Filter filter = delete.getFilter();
    547            if ( filter != null ) {
    548                org.deegree.model.filterencoding.XMLFactory.appendFilter( el, filter );
    549            }
    550            root.appendChild( el );
    551        }
    552
    553        /**
    554         * Adds the XML representation of an <code>Update</code> operation to the given element.
    555         * <p>
    556         * Respects the deegree-specific extension to the Update operation: instead of specifying properties and their
    557         * values, it's also possible to only specify just one feature that replaces the matched feature.
    558         *
    559         * @param root
    560         * @param update
    561         * @throws SAXException
    562         * @throws IOException
    563         * @throws FeatureException
    564         * @throws GeometryException
    565         */
    566        private static void appendUpdate( Element root, Update update )
    567                                throws FeatureException, IOException, SAXException, GeometryException {
    568
    569            Element el = XMLTools.appendElement( root, WFS, "Update" );
    570            if ( update.getHandle() != null ) {
    571                el.setAttribute( "handle", update.getHandle() );
    572            }
    573
    574            el.setAttribute( "typeName", update.getTypeName().getPrefixedName() );
    575
    576            // ensure that the type's namespace is declared
    577            el.setAttribute( "xmlns:" + update.getTypeName().getPrefix(),
    578                             update.getTypeName().getNamespace().toASCIIString() );
    579
    580            Feature replacement = update.getFeature();
    581            if ( replacement != null ) {
    582                GMLFeatureAdapter adapter = new GMLFeatureAdapter();
    583                adapter.append( el, replacement );
    584            } else {
    585                Map<PropertyPath, FeatureProperty> replaces = update.getReplacementProperties();
    586                for ( PropertyPath propertyName : replaces.keySet() ) {
    587                    Element propElement = XMLTools.appendElement( el, WFS, "Property" );
    588                    Element nameElement = XMLTools.appendElement( propElement, WFS, "Name" );
    589                    org.deegree.ogcbase.XMLFactory.appendPropertyPath( nameElement, propertyName );
    590
    591                    // append property value
    592                    Object propValue = replaces.get( propertyName ).getValue();
    593                    if ( propValue != null ) {
    594                        Element valueElement = XMLTools.appendElement( propElement, WFS, "Value" );
    595                        if ( propValue instanceof Feature ) {
    596                            GMLFeatureAdapter adapter = new GMLFeatureAdapter();
    597                            adapter.append( valueElement, (Feature) propValue );
    598                        } else if ( propValue instanceof Geometry ) {
    599                            appendGeometry( valueElement, (Geometry) propValue );
    600                        } else {
    601                            XMLTools.setNodeValue( valueElement, propValue.toString() );
    602                        }
    603                    }
    604                }
    605            }
    606
    607            Filter filter = update.getFilter();
    608            if ( filter != null ) {
    609                org.deegree.model.filterencoding.XMLFactory.appendFilter( el, filter );
    610            }
    611            root.appendChild( el );
    612        }
    613
    614        /**
    615         * Adds the XML representation of an <code>Insert</code> operation to the given element.
    616         *
    617         * @param root
    618         * @param insert
    619         * @throws SAXException
    620         * @throws IOException
    621         * @throws FeatureException
    622         */
    623        private static void appendInsert( Element root, Insert insert )
    624                                throws IOException, FeatureException, XMLException, SAXException {
    625
    626            Element el = XMLTools.appendElement( root, WFS, "Insert" );
    627            if ( insert.getHandle() != null ) {
    628                el.setAttribute( "handle", insert.getHandle() );
    629            }
    630            if ( insert.getIdGen() != null ) {
    631                switch ( insert.getIdGen() ) {
    632                case USE_EXISTING:
    633                    el.setAttribute( "idgen", "UseExisting" );
    634                    break;
    635                case GENERATE_NEW:
    636                    el.setAttribute( "idgen", "GenerateNew" );
    637                    break;
    638                case REPLACE_DUPLICATE:
    639                    el.setAttribute( "idgen", "ReplaceDuplicate" );
    640                    break;
    641                }
    642            }
    643
    644            GMLFeatureAdapter adapter = new GMLFeatureAdapter();
    645            adapter.append( el, insert.getFeatures() );
    646        }
    647
    648        /**
    649         * Exports an instance of {@link TransactionResponse} to its XML representation.
    650         *
    651         * @param response
    652         *            TransactionResponse to export
    653         * @return XML representation of TransactionResponse
    654         * @throws IOException
    655         */
    656        public static TransactionResponseDocument export( TransactionResponse response )
    657                                throws IOException {
    658
    659            TransactionResponseDocument xml = new TransactionResponseDocument();
    660            try {
    661                xml.createEmptyDocument();
    662            } catch ( SAXException e ) {
    663                throw new IOException( e.getMessage() );
    664            }
    665
    666            Element root = xml.getRootElement();
    667            appendTransactionSummary( root, response.getTotalInserted(), response.getTotalUpdated(),
    668                                      response.getTotalDeleted() );
    669            appendInsertResults( root, response.getInsertResults() );
    670            return xml;
    671        }
    672
    673        /**
    674         * Appends a 'wfs:TransactionSummary' element to the given element.
    675         *
    676         * @param root
    677         * @param totalInserted
    678         * @param totalUpdated
    679         * @param totalDeleted
    680         */
    681        private static void appendTransactionSummary( Element root, int totalInserted, int totalUpdated, int totalDeleted ) {
    682            Element taSummary = XMLTools.appendElement( root, WFS, "TransactionSummary" );
    683            XMLTools.appendElement( taSummary, WFS, "totalInserted", "" + totalInserted );
    684            XMLTools.appendElement( taSummary, WFS, "totalUpdated", "" + totalUpdated );
    685            XMLTools.appendElement( taSummary, WFS, "totalDeleted", "" + totalDeleted );
    686        }
    687
    688        /**
    689         * Appends an 'wfs:InsertResults' element to the given element (only if necessary).
    690         *
    691         * @param root
    692         * @param insertResults
    693         */
    694        private static void appendInsertResults( Element root, Collection<InsertResults> insertResults ) {
    695            Element insertResultsElement = appendElement( root, WFS, "InsertResults" );
    696
    697            // append synthetic ones because of the faulty schema
    698            if ( insertResults.size() == 0 ) {
    699                Document d = root.getOwnerDocument();
    700                Comment comment = d.createComment( "Dummy InsertResults element for compliance with (faulty?) WFS schema" );
    701                root.insertBefore( comment, insertResultsElement );
    702                Element elem = appendElement( insertResultsElement, WFS, "Feature" );
    703                appendElement( elem, OGCNS, OGC_PREFIX + ":FeatureId" ).setAttribute( "fid", "bogus" );
    704            } else {
    705                Iterator<InsertResults> iter = insertResults.iterator();
    706                while ( iter.hasNext() ) {
    707                    appendFeatureIds( insertResultsElement, iter.next() );
    708                }
    709            }
    710        }
    711
    712        /**
    713         * Appends a 'wfs:Feature' element to the given element.
    714         *
    715         * @param root
    716         * @param results
    717         */
    718        private static void appendFeatureIds( Element root, InsertResults results ) {
    719            Element featureElement = XMLTools.appendElement( root, WFS, "Feature" );
    720            String handle = results.getHandle();
    721            if ( handle != null ) {
    722                featureElement.setAttribute( "handle", handle );
    723            }
    724            Iterator<FeatureId> iter = results.getFeatureIDs().iterator();
    725            while ( iter.hasNext() ) {
    726                Element featureIdElement = XMLTools.appendElement( featureElement, OGCNS, "ogc:FeatureId" );
    727                featureIdElement.setAttribute( "fid", iter.next().getAsString() );
    728            }
    729        }
    730
    731        /**
    732         * Appends the XML representation of the given {@link Query} instance to an element.
    733         *
    734         * @param query
    735         */
    736        private static void appendQuery( Element root, Query query )
    737                                throws IOException, XMLParsingException {
    738
    739            Element queryElem = XMLTools.appendElement( root, WFS, "Query" );
    740            if ( query.getHandle() != null ) {
    741                queryElem.setAttribute( "handle", query.getHandle() );
    742            }
    743            if ( query.getFeatureVersion() != null ) {
    744                queryElem.setAttribute( "featureVersion", query.getFeatureVersion() );
    745            }
    746            QualifiedName[] qn = query.getTypeNames();
    747            String[] na = new String[qn.length];
    748            for ( int i = 0; i < na.length; i++ ) {
    749                na[i] = qn[i].getPrefixedName();
    750                if ( qn[i].getNamespace() != null ) {
    751                    queryElem.setAttribute( "xmlns:" + qn[i].getPrefix(), qn[i].getNamespace().toASCIIString() );
    752                }
    753            }
    754            String tn = StringTools.arrayToString( na, ',' );
    755            queryElem.setAttribute( "typeName", tn );
    756
    757            if ( query.getSrsName() != null ) {
    758                queryElem.setAttribute( "srsName", query.getSrsName() );
    759            }
    760
    761            String[] aliases = query.getAliases();
    762            if ( aliases != null && aliases.length != 0 ) {
    763                StringBuffer aliasesList = new StringBuffer( aliases[0] );
    764                for ( int i = 1; i < aliases.length; i++ ) {
    765                    aliasesList.append( ' ' );
    766                    aliasesList.append( aliases[i] );
    767                }
    768                queryElem.setAttribute( "aliases", aliasesList.toString() );
    769            }
    770
    771            PropertyPath[] propertyNames = query.getPropertyNames();
    772            for ( int i = 0; i < propertyNames.length; i++ ) {
    773                if ( propertyNames[i] instanceof XLinkPropertyPath ) {
    774                    Element propertyNameElement = appendElement( queryElem, WFSNS, WFS_PREFIX + ":XlinkPropertyName" );
    775                    String depth = Integer.toString( ( (XLinkPropertyPath) propertyNames[i] ).getXlinkDepth() );
    776                    propertyNameElement.setAttribute( "traverseXlinkDepth", depth );
    777                    appendPropertyPath( propertyNameElement, propertyNames[i] );
    778                } else {
    779                    Element propertyNameElement = appendElement( queryElem, WFSNS, WFS_PREFIX + ":PropertyName" );
    780                    appendPropertyPath( propertyNameElement, propertyNames[i] );
    781                }
    782            }
    783            Function[] fn = query.getFunctions();
    784            // copy function definitions into query node
    785            if ( fn != null ) {
    786                for ( int i = 0; i < fn.length; i++ ) {
    787                    StringReader sr = new StringReader( fn[i].toXML().toString() );
    788                    Document doc;
    789                    try {
    790                        doc = XMLTools.parse( sr );
    791                    } catch ( SAXException e ) {
    792                        throw new XMLParsingException( "could not parse filter function", e );
    793                    }
    794                    XMLTools.copyNode( doc.getDocumentElement(), queryElem );
    795                }
    796            }
    797            // copy filter into query node
    798            if ( query.getFilter() != null ) {
    799                StringReader sr = new StringReader( query.getFilter().to110XML().toString() );
    800                Document doc;
    801                try {
    802                    doc = XMLTools.parse( sr );
    803                } catch ( SAXException e ) {
    804                    throw new XMLParsingException( "could not parse filter", e );
    805                }
    806                Element elem = XMLTools.appendElement( queryElem, OGCNS, "ogc:Filter" );
    807                XMLTools.copyNode( doc.getDocumentElement(), elem );
    808            }
    809
    810            SortProperty[] sp = query.getSortProperties();
    811            if ( sp != null ) {
    812                Element sortBy = XMLTools.appendElement( queryElem, OGCNS, "ogc:SortBy" );
    813                for ( int i = 0; i < sp.length; i++ ) {
    814                    Element sortProp = XMLTools.appendElement( sortBy, OGCNS, "ogc:SortProperty" );
    815                    XMLTools.appendElement( sortProp, OGCNS, "ogc:PropertyName", sp[i].getSortProperty().getAsString() );
    816                    if ( !sp[i].getSortOrder() ) {
    817                        XMLTools.appendElement( sortProp, OGCNS, "ogc:SortOrder", "DESC" );
    818                    }
    819                }
    820            }
    821        }
    822
    823        /**
    824         * Appends the XML representation of the <code>wfs:FeatureTypeList</code>- section to the passed
    825         * <code>Element</code>.
    826         *
    827         * @param root
    828         * @param featureTypeList
    829         */
    830        public static void appendFeatureTypeList( Element root, FeatureTypeList featureTypeList ) {
    831
    832            Element featureTypeListNode = XMLTools.appendElement( root, WFS, "FeatureTypeList", null );
    833            Operation[] operations = featureTypeList.getGlobalOperations();
    834            if ( operations != null ) {
    835                Element operationsNode = XMLTools.appendElement( featureTypeListNode, WFS, "Operations" );
    836                for ( int i = 0; i < operations.length; i++ ) {
    837                    XMLTools.appendElement( operationsNode, WFS, "Operation", operations[i].getOperation() );
    838                }
    839            }
    840            WFSFeatureType[] featureTypes = featureTypeList.getFeatureTypes();
    841            if ( featureTypes != null ) {
    842                for ( int i = 0; i < featureTypes.length; i++ ) {
    843                    appendWFSFeatureType( featureTypeListNode, featureTypes[i] );
    844                }
    845            }
    846
    847        }
    848
    849        /**
    850         * Appends the XML representation of the <code>WFSFeatureType</code> instance to the passed <code>Element</code>.
    851         *
    852         * @param root
    853         * @param featureType
    854         */
    855        public static void appendWFSFeatureType( Element root, WFSFeatureType featureType ) {
    856
    857            Element featureTypeNode = XMLTools.appendElement( root, WFS, "FeatureType" );
    858
    859            if ( featureType.getName().getPrefix() != null ) {
    860                XMLTools.appendNSBinding( featureTypeNode, featureType.getName().getPrefix(),
    861                                          featureType.getName().getNamespace() );
    862            }
    863            XMLTools.appendElement( featureTypeNode, WFS, "Name", featureType.getName().getPrefixedName() );
    864            XMLTools.appendElement( featureTypeNode, WFS, "Title", featureType.getTitle() );
    865            String abstract_ = featureType.getAbstract();
    866            if ( abstract_ != null ) {
    867                XMLTools.appendElement( featureTypeNode, WFS, "Abstract", featureType.getAbstract() );
    868            }
    869            Keywords[] keywords = featureType.getKeywords();
    870            if ( keywords != null ) {
    871                appendOWSKeywords( featureTypeNode, keywords );
    872            }
    873            URI defaultSrs = featureType.getDefaultSRS();
    874            if ( defaultSrs != null ) {
    875                XMLTools.appendElement( featureTypeNode, WFS, "DefaultSRS", defaultSrs.toString() );
    876                URI[] otherSrs = featureType.getOtherSrs();
    877                if ( otherSrs != null ) {
    878                    for ( int i = 0; i < otherSrs.length; i++ ) {
    879                        XMLTools.appendElement( featureTypeNode, WFS, "OtherSRS", otherSrs[i].toString() );
    880                    }
    881                }
    882            } else {
    883                XMLTools.appendElement( featureTypeNode, WFS, "Title" );
    884            }
    885            Operation[] operations = featureType.getOperations();
    886            if ( operations != null ) {
    887                Element operationsNode = XMLTools.appendElement( featureTypeNode, WFS, "Operations" );
    888                for ( int i = 0; i < operations.length; i++ ) {
    889                    XMLTools.appendElement( operationsNode, WFS, "Operation", operations[i].getOperation() );
    890                }
    891            }
    892            FormatType[] formats = featureType.getOutputFormats();
    893            if ( formats != null ) {
    894                appendOutputFormats( featureTypeNode, formats );
    895            }
    896            Envelope[] wgs84BoundingBoxes = featureType.getWgs84BoundingBoxes();
    897            for ( int i = 0; i < wgs84BoundingBoxes.length; i++ ) {
    898                appendWgs84BoundingBox( featureTypeNode, wgs84BoundingBoxes[i] );
    899            }
    900            if ( featureType.getMetadataUrls() != null ) {
    901                for ( MetadataURL metadataURL : featureType.getMetadataUrls() ) {
    902                    appendMetadataURL( featureTypeNode, metadataURL );
    903                }
    904            }
    905        }
    906
    907        /**
    908         * Appends the XML representation of the <code>wfs:ServesGMLObjectTypeList</code>- section to the passed
    909         * <code>Element</code> as a new element with the given qualified name.
    910         *
    911         * @param root
    912         * @param elementNS
    913         * @param elementName
    914         * @param gmlObjectTypes
    915         */
    916        public static void appendGMLObjectTypeList( Element root, URI elementNS, String elementName,
    917                                                    GMLObject[] gmlObjectTypes ) {
    918
    919            Element gmlObjectTypeListNode = XMLTools.appendElement( root, elementNS, elementName );
    920            for ( int i = 0; i < gmlObjectTypes.length; i++ ) {
    921                appendGMLObjectTypeType( gmlObjectTypeListNode, gmlObjectTypes[i] );
    922            }
    923        }
    924
    925        /**
    926         * Appends the XML representation of the given {@link GMLObject} (as a <code>wfs:GMLObjectType</code> element) to
    927         * the passed <code>Element</code>.
    928         *
    929         * @param root
    930         * @param gmlObjectType
    931         */
    932        public static void appendGMLObjectTypeType( Element root, GMLObject gmlObjectType ) {
    933
    934            Element gmlObjectTypeNode = XMLTools.appendElement( root, WFS, "GMLObjectType" );
    935
    936            if ( gmlObjectType.getName().getPrefix() != null ) {
    937                XMLTools.appendNSBinding( gmlObjectTypeNode, gmlObjectType.getName().getPrefix(),
    938                                          gmlObjectType.getName().getNamespace() );
    939            }
    940            XMLTools.appendElement( gmlObjectTypeNode, WFS, "Name", gmlObjectType.getName().getPrefixedName() );
    941            if ( gmlObjectType.getTitle() != null ) {
    942                XMLTools.appendElement( gmlObjectTypeNode, WFS, "Title", gmlObjectType.getTitle() );
    943            }
    944            String abstract_ = gmlObjectType.getAbstract();
    945            if ( abstract_ != null ) {
    946                XMLTools.appendElement( gmlObjectTypeNode, WFS, "Abstract", gmlObjectType.getAbstract() );
    947            }
    948            Keywords[] keywords = gmlObjectType.getKeywords();
    949            if ( keywords != null ) {
    950                appendOWSKeywords( gmlObjectTypeNode, keywords );
    951            }
    952            FormatType[] formats = gmlObjectType.getOutputFormats();
    953            if ( formats != null ) {
    954                appendOutputFormats( gmlObjectTypeNode, formats );
    955            }
    956        }
    957
    958        /**
    959         * Appends the XML representation of the given {@link Envelope} (as an <code>ows:WGS84BoundingBoxType</code>
    960         * element) to the passed <code>Element</code>.
    961         *
    962         * @param root
    963         * @param envelope
    964         */
    965        public static void appendWgs84BoundingBox( Element root, Envelope envelope ) {
    966            Element wgs84BoundingBoxElement = XMLTools.appendElement( root, OWSNS, "ows:WGS84BoundingBox" );
    967            XMLTools.appendElement( wgs84BoundingBoxElement, OWSNS, "ows:LowerCorner", envelope.getMin().getX() + " "
    968                                                                                       + envelope.getMin().getY() );
    969            XMLTools.appendElement( wgs84BoundingBoxElement, OWSNS, "ows:UpperCorner", envelope.getMax().getX() + " "
    970                                                                                       + envelope.getMax().getY() );
    971        }
    972
    973        private static void appendMetadataURL( Element root, MetadataURL metadataURL ) {
    974            Element metadataURLElement = XMLTools.appendElement( root, WFSNS, "wfs:MetadataURL",
    975                                                                 metadataURL.getOnlineResource().toString() );
    976            metadataURLElement.setAttribute( "format", metadataURL.getFormat() );
    977            metadataURLElement.setAttribute( "type", metadataURL.getType() );
    978        }
    979
    980        /**
    981         * Appends the XML representation of the given {@link FormatType}s as (as a <code>wfs:OutputFormats</code> element)
    982         * to the passed <code>Element</code>.
    983         *
    984         * @param root
    985         * @param formats
    986         */
    987        public static void appendOutputFormats( Element root, FormatType[] formats ) {
    988
    989            Element outputFormatsNode = XMLTools.appendElement( root, WFS, "OutputFormats" );
    990            for ( int i = 0; i < formats.length; i++ ) {
    991                appendElement( outputFormatsNode, WFS, "Format", formats[i].getValue() );
    992            }
    993        }
    994    }