001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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    }