001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/ogcwebservices/wfs/XMLFactory.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2008 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/deegree/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     This library is free software; you can redistribute it and/or
012     modify it under the terms of the GNU Lesser General Public
013     License as published by the Free Software Foundation; either
014     version 2.1 of the License, or (at your option) any later version.
015    
016     This library is distributed in the hope that it will be useful,
017     but WITHOUT ANY WARRANTY; without even the implied warranty of
018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     Lesser General Public License for more details.
020    
021     You should have received a copy of the GNU Lesser General Public
022     License along with this library; if not, write to the Free Software
023     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024    
025     Contact:
026    
027     Andreas Poth
028     lat/lon GmbH
029     Aennchenstr. 19
030     53115 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Prof. Dr. Klaus Greve
035     Department of Geography
036     University of Bonn
037     Meckenheimer Allee 166
038     53115 Bonn
039     Germany
040     E-Mail: greve@giub.uni-bonn.de
041     
042     ---------------------------------------------------------------------------*/
043    package org.deegree.ogcwebservices.wfs;
044    
045    import java.io.IOException;
046    import java.io.StringReader;
047    import java.net.URI;
048    import java.util.Collection;
049    import java.util.Iterator;
050    import java.util.List;
051    import java.util.Map;
052    
053    import org.deegree.datatypes.QualifiedName;
054    import org.deegree.framework.log.ILogger;
055    import org.deegree.framework.log.LoggerFactory;
056    import org.deegree.framework.util.StringTools;
057    import org.deegree.framework.xml.XMLException;
058    import org.deegree.framework.xml.XMLParsingException;
059    import org.deegree.framework.xml.XMLTools;
060    import org.deegree.io.datastore.FeatureId;
061    import org.deegree.model.feature.Feature;
062    import org.deegree.model.feature.FeatureException;
063    import org.deegree.model.feature.FeatureProperty;
064    import org.deegree.model.feature.GMLFeatureAdapter;
065    import org.deegree.model.filterencoding.Filter;
066    import org.deegree.model.filterencoding.Function;
067    import org.deegree.model.filterencoding.capabilities.FilterCapabilities;
068    import org.deegree.model.metadata.iso19115.Keywords;
069    import org.deegree.model.spatialschema.Envelope;
070    import org.deegree.model.spatialschema.Geometry;
071    import org.deegree.model.spatialschema.GeometryException;
072    import org.deegree.ogcbase.CommonNamespaces;
073    import org.deegree.ogcbase.PropertyPath;
074    import org.deegree.ogcbase.SortProperty;
075    import org.deegree.ogcwebservices.getcapabilities.Contents;
076    import org.deegree.ogcwebservices.getcapabilities.OperationsMetadata;
077    import org.deegree.ogcwebservices.getcapabilities.ServiceIdentification;
078    import org.deegree.ogcwebservices.getcapabilities.ServiceProvider;
079    import org.deegree.ogcwebservices.wfs.capabilities.FeatureTypeList;
080    import org.deegree.ogcwebservices.wfs.capabilities.FormatType;
081    import org.deegree.ogcwebservices.wfs.capabilities.GMLObject;
082    import org.deegree.ogcwebservices.wfs.capabilities.Operation;
083    import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities;
084    import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilitiesDocument;
085    import org.deegree.ogcwebservices.wfs.capabilities.WFSFeatureType;
086    import org.deegree.ogcwebservices.wfs.operation.GetFeature;
087    import org.deegree.ogcwebservices.wfs.operation.GetFeatureDocument;
088    import org.deegree.ogcwebservices.wfs.operation.Lock;
089    import org.deegree.ogcwebservices.wfs.operation.LockFeature;
090    import org.deegree.ogcwebservices.wfs.operation.LockFeatureDocument;
091    import org.deegree.ogcwebservices.wfs.operation.LockFeatureResponse;
092    import org.deegree.ogcwebservices.wfs.operation.LockFeatureResponseDocument;
093    import org.deegree.ogcwebservices.wfs.operation.Query;
094    import org.deegree.ogcwebservices.wfs.operation.GetFeature.RESULT_TYPE;
095    import org.deegree.ogcwebservices.wfs.operation.transaction.Delete;
096    import org.deegree.ogcwebservices.wfs.operation.transaction.Insert;
097    import org.deegree.ogcwebservices.wfs.operation.transaction.InsertResults;
098    import org.deegree.ogcwebservices.wfs.operation.transaction.Transaction;
099    import org.deegree.ogcwebservices.wfs.operation.transaction.TransactionDocument;
100    import org.deegree.ogcwebservices.wfs.operation.transaction.TransactionResponse;
101    import org.deegree.ogcwebservices.wfs.operation.transaction.TransactionResponseDocument;
102    import org.deegree.ogcwebservices.wfs.operation.transaction.Update;
103    import org.w3c.dom.Document;
104    import org.w3c.dom.Element;
105    import org.xml.sax.SAXException;
106    
107    /**
108     * Responsible for the generation of XML representations of objects from the WFS context.
109     * 
110     * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider </a>
111     * @author last edited by: $Author: apoth $
112     * 
113     * @version $Revision: 9348 $, $Date: 2007-12-27 17:59:14 +0100 (Do, 27 Dez 2007) $
114     */
115    public class XMLFactory extends org.deegree.owscommon.XMLFactory {
116    
117        private static final URI WFS = CommonNamespaces.WFSNS;
118    
119        private static final URI OGCNS = CommonNamespaces.OGCNS;
120    
121        private static final String PRE_WFS = CommonNamespaces.WFS_PREFIX + ":";
122    
123        private static final URI DEEGREEWFS = CommonNamespaces.DEEGREEWFS;
124    
125        private static final ILogger LOG = LoggerFactory.getLogger( XMLFactory.class );
126    
127        /**
128         * Exports a <code>WFSCapabilities</code> instance to a <code>WFSCapabilitiesDocument</code>.
129         * 
130         * @param capabilities
131         * @return DOM representation of the <code>WFSCapabilities</code>
132         * @throws IOException
133         *             if XML template could not be loaded
134         */
135        public static WFSCapabilitiesDocument export( WFSCapabilities capabilities )
136                                throws IOException {
137    
138            WFSCapabilitiesDocument capabilitiesDocument = new WFSCapabilitiesDocument();
139    
140            try {
141                capabilitiesDocument.createEmptyDocument();
142                Element root = capabilitiesDocument.getRootElement();
143    
144                ServiceIdentification serviceIdentification = capabilities.getServiceIdentification();
145                if ( serviceIdentification != null ) {
146                    appendServiceIdentification( root, serviceIdentification );
147                }
148    
149                ServiceProvider serviceProvider = capabilities.getServiceProvider();
150                if ( serviceProvider != null ) {
151                    appendServiceProvider( root, capabilities.getServiceProvider() );
152                }
153    
154                OperationsMetadata operationsMetadata = capabilities.getOperationsMetadata();
155                if ( operationsMetadata != null ) {
156                    appendOperationsMetadata( root, operationsMetadata );
157                }
158                FeatureTypeList featureTypeList = capabilities.getFeatureTypeList();
159                if ( featureTypeList != null ) {
160                    appendFeatureTypeList( root, featureTypeList );
161                }
162                GMLObject[] servesGMLObjectTypes = capabilities.getServesGMLObjectTypeList();
163                if ( servesGMLObjectTypes != null ) {
164                    appendGMLObjectTypeList( root, WFS, PRE_WFS + "ServesGMLObjectTypeList", servesGMLObjectTypes );
165                }
166                GMLObject[] supportsGMLObjectTypes = capabilities.getSupportsGMLObjectTypeList();
167                if ( supportsGMLObjectTypes != null ) {
168                    appendGMLObjectTypeList( root, WFS, PRE_WFS + "SupportsGMLObjectTypeList", supportsGMLObjectTypes );
169                }
170                Contents contents = capabilities.getContents();
171                if ( contents != null ) {
172                    // appendContents(root, contents);
173                }
174    
175                FilterCapabilities filterCapabilities = capabilities.getFilterCapabilities();
176                if ( filterCapabilities != null ) {
177                    org.deegree.model.filterencoding.XMLFactory.appendFilterCapabilities110( root, filterCapabilities );
178                }
179            } catch ( SAXException e ) {
180                LOG.logError( e.getMessage(), e );
181            }
182            return capabilitiesDocument;
183        }
184    
185        /**
186         * Exports a <code>WFSCapabilities</code> instance to a <code>WFSCapabilitiesDocument</code>.
187         * 
188         * @param capabilities
189         * @param sections
190         *            names of sections to be exported, may contain 'All'
191         * @return DOM representation of the <code>WFSCapabilities</code>
192         * @throws IOException
193         *             if XML template could not be loaded
194         */
195        public static WFSCapabilitiesDocument export( WFSCapabilities capabilities, String[] sections )
196                                throws IOException {
197    
198            // TODO only export requested sections
199            return export( capabilities );
200        }
201    
202        /**
203         * Appends the DOM representation of the {@link ServiceIdentification} section to the passed
204         * {@link Element}.
205         * 
206         * @param root
207         * @param serviceIdentification
208         */
209        protected static void appendServiceIdentification( Element root, ServiceIdentification serviceIdentification ) {
210    
211            // 'ServiceIdentification'-element
212            Element serviceIdentificationNode = XMLTools.appendElement( root, OWSNS, "ows:ServiceIdentification" );
213    
214            // the optional title element
215            String tmp = serviceIdentification.getTitle();
216            if ( tmp != null && !"".equals( tmp ) ) {
217                XMLTools.appendElement( serviceIdentificationNode, OWSNS, "ows:Title", tmp );
218            }
219    
220            // the optional abstract element
221            tmp = serviceIdentification.getAbstract();
222            if ( tmp != null && !"".equals( tmp ) ) {
223                XMLTools.appendElement( serviceIdentificationNode, OWSNS, "ows:Abstract", tmp );
224            }
225    
226            // the optional keywords element
227            appendKeywords( serviceIdentificationNode, serviceIdentification.getKeywords(), OWSNS );
228    
229            // 'ServiceType'-element
230            XMLTools.appendElement( serviceIdentificationNode, OWSNS, "ows:ServiceType",
231                                    serviceIdentification.getServiceType().getCode() );
232    
233            // 'ServiceTypeVersion'-elements
234            String[] versions = serviceIdentification.getServiceTypeVersions();
235            for ( int i = 0; i < versions.length; i++ ) {
236                XMLTools.appendElement( serviceIdentificationNode, OWSNS, "ows:ServiceTypeVersion", versions[i] );
237            }
238    
239            // 'Fees'-element
240            XMLTools.appendElement( serviceIdentificationNode, OWSNS, "ows:Fees", serviceIdentification.getFees() );
241    
242            // 'AccessConstraints'-element
243            String[] constraints = serviceIdentification.getAccessConstraints();
244            if ( constraints != null ) {
245                for ( int i = 0; i < constraints.length; i++ ) {
246                    XMLTools.appendElement( serviceIdentificationNode, OWSNS, "ows:AccessConstraints", constraints[i] );
247                }
248            }
249        }
250    
251        /**
252         * Appends a <code>ows:Keywords</code> -element for each <code>Keywords</code> object of the
253         * passed array to the passed <code>Element</code>.
254         * 
255         * @param xmlNode
256         * @param keywords
257         * @param namespaceURI
258         */
259        protected static void appendKeywords( Element xmlNode, Keywords[] keywords, URI namespaceURI ) {
260            if ( keywords != null ) {
261                for ( int i = 0; i < keywords.length; i++ ) {
262                    Element node = XMLTools.appendElement( xmlNode, namespaceURI, "ows:Keywords" );
263                    appendKeywords( node, keywords[i], namespaceURI );
264                }
265            }
266        }
267    
268        /**
269         * Appends a <code>Keyword</code> -element to the passed <code>Element</code> and fills it
270         * with the available keywords.
271         * 
272         * @param xmlNode
273         * @param keywords
274         * @param namespaceURI
275         */
276        protected static void appendKeywords( Element xmlNode, Keywords keywords, URI namespaceURI ) {
277            if ( keywords != null ) {
278                String[] kw = keywords.getKeywords();
279                for ( int i = 0; i < kw.length; i++ ) {
280                    XMLTools.appendElement( xmlNode, namespaceURI, "ows:Keyword", kw[i] );
281                }
282                if ( keywords.getThesaurusName() != null ) {
283                    XMLTools.appendElement( xmlNode, namespaceURI, "ows:Type", keywords.getThesaurusName() );
284                }
285            }
286        }
287    
288        /**
289         * Exports a <code>GetFeature</code> instance to a <code>GetFeatureDocument</code>.
290         * 
291         * @param getFeature
292         *            request to be exported
293         * @return XML representation of the <code>GetFeature</code> request
294         * @throws IOException
295         * @throws XMLParsingException
296         */
297        public static GetFeatureDocument export( GetFeature getFeature )
298                                throws IOException, XMLParsingException {
299    
300            GetFeatureDocument xml = new GetFeatureDocument();
301            try {
302                xml.load( XMLFactory.class.getResource( "GetFeatureTemplate.xml" ) );
303            } catch ( SAXException e ) {
304                throw new XMLParsingException( "could not parse GetFeatureTemplate.xml", e );
305            }
306            Element root = xml.getRootElement();
307            root.setAttribute( "outputFormat", getFeature.getOutputFormat() );
308            root.setAttribute( "service", "WFS" );
309            root.setAttribute( "version", getFeature.getVersion() );
310            if ( getFeature.getHandle() != null ) {
311                root.setAttribute( "handle", getFeature.getHandle() );
312            }
313            if ( getFeature.getResultType() == RESULT_TYPE.HITS ) {
314                root.setAttribute( "resultType", "hits" );
315            } else {
316                root.setAttribute( "resultType", "results" );
317            }
318            root.setAttribute( "maxFeatures", "" + getFeature.getMaxFeatures() );
319            if ( getFeature.getStartPosition() > 0 ) {
320                root.setAttribute( "startPosition", "" + getFeature.getStartPosition() );
321            }
322            if ( getFeature.getTraverseXLinkDepth() > 0 ) {
323                root.setAttribute( "traverseXLinkDepth", "" + getFeature.getTraverseXLinkDepth() );
324            }
325            if ( getFeature.getTraverseXLinkExpiry() > 0 ) {
326                root.setAttribute( "traverseXLinkExpiry", "" + getFeature.getTraverseXLinkExpiry() );
327            }
328            Query[] queries = getFeature.getQuery();
329            for ( int i = 0; i < queries.length; i++ ) {
330                appendQuery( root, queries[i] );
331            }
332            return xml;
333        }
334    
335        /**
336         * Exports a {@link LockFeature} request instance to a {@link LockFeatureDocument}.
337         * 
338         * @param request
339         *            request to be exported
340         * @return XML representation of the <code>LockFeature</code> request
341         * @throws IOException
342         * @throws XMLParsingException
343         * @throws SAXException
344         */
345        public static LockFeatureDocument export( LockFeature request )
346                                throws IOException, XMLParsingException, SAXException {
347    
348            LockFeatureDocument doc = new LockFeatureDocument();
349            doc.createEmptyDocument();
350    
351            Element root = doc.getRootElement();
352            root.setAttribute( "version", request.getVersion() );
353            root.setAttribute( "service", "WFS" );
354            if ( request.getHandle() != null ) {
355                root.setAttribute( "handle", request.getHandle() );
356            }
357            root.setAttribute( "expiry", "" + request.getExpiry() );
358            root.setAttribute( "lockAction", "" + request.getLockAction() );
359    
360            List<Lock> locks = request.getLocks();
361            for ( Lock lock : locks ) {
362                appendLock( root, lock );
363            }
364            return doc;
365        }
366    
367        /**
368         * Appends the XML representation of the given {@link Lock} to the given element.
369         * 
370         * @param root
371         * @param lock
372         */
373        private static void appendLock( Element root, Lock lock )
374                                throws IOException, XMLParsingException {
375    
376            Element lockElement = XMLTools.appendElement( root, WFS, PRE_WFS + "Lock" );
377            if ( lock.getHandle() != null ) {
378                lockElement.setAttribute( "handle", lock.getHandle() );
379            }
380            QualifiedName typeName = lock.getTypeName();
381            if ( typeName.getPrefix() != null ) {
382                lockElement.setAttribute( "xmlns:" + typeName.getPrefix(), typeName.getNamespace().toASCIIString() );
383            }
384            lockElement.setAttribute( "typeName", typeName.getPrefixedName() );
385    
386            // copy filter into Lock element
387            if ( lock.getFilter() != null ) {
388                StringReader sr = new StringReader( lock.getFilter().toXML().toString() );
389                Document doc;
390                try {
391                    doc = XMLTools.parse( sr );
392                } catch ( SAXException e ) {
393                    throw new XMLParsingException( "Could not parse filter.", e );
394                }
395                Element elem = XMLTools.appendElement( lockElement, OGCNS, "ogc:Filter" );
396                XMLTools.copyNode( doc.getDocumentElement(), elem );
397            }
398        }
399    
400        /**
401         * Exports a {@link LockFeatureResponse} instance to its XML representation.
402         * 
403         * @param response
404         *            response to be exported
405         * @return XML representation of the <code>LockFeatureResponse</code>
406         * @throws IOException
407         * @throws SAXException
408         */
409        public static LockFeatureResponseDocument export( LockFeatureResponse response )
410                                throws IOException, SAXException {
411    
412            LockFeatureResponseDocument doc = new LockFeatureResponseDocument();
413            doc.createEmptyDocument();
414    
415            Element root = doc.getRootElement();
416            XMLTools.appendElement( root, WFS, PRE_WFS + "LockId", response.getLockId() );
417            String[] fids = response.getFeaturesLocked();
418            if ( fids.length != 0 ) {
419                Element featuresLockedElement = XMLTools.appendElement( root, WFS, PRE_WFS + "FeaturesLocked" );
420                for ( String fid : fids ) {
421                    appendFeatureId( featuresLockedElement, fid );
422                }
423            }
424            fids = response.getFeaturesNotLocked();
425            if ( fids.length != 0 ) {
426                Element featuresNotLockedElement = XMLTools.appendElement( root, WFS, PRE_WFS + "FeaturesNotLocked" );
427                for ( String fid : fids ) {
428                    appendFeatureId( featuresNotLockedElement, fid );
429                }
430            }
431            return doc;
432        }
433    
434        /**
435         * Exports a {@link Transaction} instance to its XML representation.
436         * 
437         * @param transaction
438         *            transaction to export
439         * @return XML representation of transaction
440         * @throws IOException
441         * @throws XMLParsingException
442         */
443        public static TransactionDocument export( Transaction transaction )
444                                throws IOException, XMLParsingException {
445    
446            TransactionDocument xml = new TransactionDocument();
447            try {
448                xml.createEmptyDocument();
449            } catch ( SAXException e ) {
450                throw new IOException( e.getMessage() );
451            }
452            Element root = xml.getRootElement();
453            List ops = transaction.getOperations();
454            for ( int i = 0; i < ops.size(); i++ ) {
455                try {
456                    if ( ops.get( i ) instanceof Insert ) {
457                        appendInsert( root, (Insert) ops.get( i ) );
458                    } else if ( ops.get( i ) instanceof Update ) {
459                        appendUpdate( root, (Update) ops.get( i ) );
460                    } else if ( ops.get( i ) instanceof Delete ) {
461                        appendDelete( root, (Delete) ops.get( i ) );
462                    }
463                } catch ( Exception e ) {
464                    LOG.logError( e.getMessage(), e );
465                    throw new XMLParsingException( e.getMessage() );
466                }
467            }
468            return xml;
469        }
470    
471        /**
472         * Adds the XML representation of a <code>Delete</code> operation to the given element.
473         * 
474         * @param root
475         * @param delete
476         */
477        private static void appendDelete( Element root, Delete delete ) {
478            Element el = XMLTools.appendElement( root, WFS, "Delete" );
479            if ( delete.getHandle() != null ) {
480                el.setAttribute( "handle", delete.getHandle() );
481            }
482            // TODO What about the namespace binding here?
483            el.setAttribute( "typeName", delete.getTypeName().getPrefixedName() );
484    
485            Filter filter = delete.getFilter();
486            if ( filter != null ) {
487                org.deegree.model.filterencoding.XMLFactory.appendFilter( el, filter );
488            }
489            root.appendChild( el );
490        }
491    
492        /**
493         * Adds the XML representation of an <code>Update</code> operation to the given element.
494         * <p>
495         * Respects the deegree-specific extension to the Update operation: instead of specifying
496         * properties and their values, it's also possible to only specify just one feature that
497         * replaces the matched feature.
498         * 
499         * @param root
500         * @param update
501         * @throws SAXException
502         * @throws IOException
503         * @throws FeatureException
504         * @throws GeometryException
505         */
506        private static void appendUpdate( Element root, Update update )
507                                throws FeatureException, IOException, SAXException, GeometryException {
508    
509            Element el = XMLTools.appendElement( root, WFS, "Update" );
510            if ( update.getHandle() != null ) {
511                el.setAttribute( "handle", update.getHandle() );
512            }
513            // TODO What about the namespace binding here?
514            el.setAttribute( "typeName", update.getTypeName().getPrefixedName() );
515    
516            Feature replacement = update.getFeature();
517            if ( replacement != null ) {
518                GMLFeatureAdapter adapter = new GMLFeatureAdapter();
519                adapter.append( root, replacement );
520            } else {
521                Map<PropertyPath, FeatureProperty> replaces = update.getReplacementProperties();
522                for ( PropertyPath propertyName : replaces.keySet() ) {
523                    Element propElement = XMLTools.appendElement( el, WFS, "Property" );
524                    Element nameElement = XMLTools.appendElement( propElement, WFS, "Name" );
525                    org.deegree.ogcbase.XMLFactory.appendPropertyPath( nameElement, propertyName );
526    
527                    // append property value
528                    Object propValue = replaces.get( propertyName ).getValue();
529                    if ( propValue != null ) {
530                        Element valueElement = XMLTools.appendElement( propElement, WFS, "Value" );
531                        if ( propValue instanceof Feature ) {
532                            GMLFeatureAdapter adapter = new GMLFeatureAdapter();
533                            adapter.append( valueElement, (Feature) propValue );
534                        } else if ( propValue instanceof Geometry ) {
535                            appendGeometry( valueElement, (Geometry) propValue );
536                        } else {
537                            XMLTools.setNodeValue( valueElement, propValue.toString() );
538                        }
539                    }
540                }
541            }
542    
543            Filter filter = update.getFilter();
544            if ( filter != null ) {
545                org.deegree.model.filterencoding.XMLFactory.appendFilter( el, filter );
546            }
547            root.appendChild( el );
548        }
549    
550        /**
551         * Adds the XML representation of an <code>Insert</code> operation to the given element.
552         * 
553         * @param root
554         * @param insert
555         * @throws SAXException
556         * @throws IOException
557         * @throws FeatureException
558         */
559        private static void appendInsert( Element root, Insert insert )
560                                throws IOException, FeatureException, XMLException, SAXException {
561    
562            Element el = XMLTools.appendElement( root, WFS, "Insert" );
563            if ( insert.getHandle() != null ) {
564                el.setAttribute( "handle", insert.getHandle() );
565            }
566            if ( insert.getIdGen() != null ) {
567                el.setAttribute( "idgen", insert.getIdGen().name() );
568            }
569    
570            GMLFeatureAdapter adapter = new GMLFeatureAdapter();
571            adapter.append( el, insert.getFeatures() );
572        }
573    
574        /**
575         * Exports an instance of {@link TransactionResponse} to its XML representation.
576         * 
577         * @param response
578         *            TransactionResponse to export
579         * @return XML representation of TransactionResponse
580         * @throws IOException
581         */
582        public static TransactionResponseDocument export( TransactionResponse response )
583                                throws IOException {
584    
585            TransactionResponseDocument xml = new TransactionResponseDocument();
586            try {
587                xml.createEmptyDocument();
588            } catch ( SAXException e ) {
589                throw new IOException( e.getMessage() );
590            }
591    
592            Element root = xml.getRootElement();
593            appendTransactionSummary( root, response.getTotalInserted(), response.getTotalUpdated(),
594                                      response.getTotalDeleted() );
595            appendInsertResults( root, response.getInsertResults() );
596            return xml;
597        }
598    
599        /**
600         * Appends a 'wfs:TransactionSummary' element to the given element.
601         * 
602         * @param root
603         * @param totalInserted
604         * @param totalUpdated
605         * @param totalDeleted
606         */
607        private static void appendTransactionSummary( Element root, int totalInserted, int totalUpdated, int totalDeleted ) {
608            Element taSummary = XMLTools.appendElement( root, WFS, PRE_WFS + "TransactionSummary" );
609            XMLTools.appendElement( taSummary, WFS, PRE_WFS + "totalInserted", "" + totalInserted );
610            XMLTools.appendElement( taSummary, WFS, PRE_WFS + "totalUpdated", "" + totalUpdated );
611            XMLTools.appendElement( taSummary, WFS, PRE_WFS + "totalDeleted", "" + totalDeleted );
612        }
613    
614        /**
615         * Appends an 'wfs:InsertResults' element to the given element (only if necessary).
616         * 
617         * @param root
618         * @param insertResults
619         */
620        private static void appendInsertResults( Element root, Collection<InsertResults> insertResults ) {
621            if ( insertResults.size() > 0 ) {
622                Element insertResultsElement = XMLTools.appendElement( root, WFS, PRE_WFS + "InsertResults" );
623                Iterator<InsertResults> iter = insertResults.iterator();
624                while ( iter.hasNext() ) {
625                    appendFeatureIds( insertResultsElement, iter.next() );
626                }
627            }
628        }
629    
630        /**
631         * Appends a 'wfs:Feature' element to the given element.
632         * 
633         * @param root
634         * @param results
635         */
636        private static void appendFeatureIds( Element root, InsertResults results ) {
637            Element featureElement = XMLTools.appendElement( root, WFS, PRE_WFS + "Feature" );
638            String handle = results.getHandle();
639            if ( handle != null ) {
640                featureElement.setAttribute( "handle", handle );
641            }
642            Iterator<FeatureId> iter = results.getFeatureIDs().iterator();
643            while ( iter.hasNext() ) {
644                Element featureIdElement = XMLTools.appendElement( featureElement, OGCNS, "ogc:FeatureId" );
645                featureIdElement.setAttribute( "fid", iter.next().getAsString() );
646            }
647        }
648    
649        /**
650         * Appends the XML representation of the given {@link Query} instance to an element.
651         * 
652         * @param query
653         */
654        private static void appendQuery( Element root, Query query )
655                                throws IOException, XMLParsingException {
656    
657            Element queryElem = XMLTools.appendElement( root, WFS, PRE_WFS + "Query" );
658            if ( query.getHandle() != null ) {
659                queryElem.setAttribute( "handle", query.getHandle() );
660            }
661            if ( query.getFeatureVersion() != null ) {
662                queryElem.setAttribute( "featureVersion", query.getFeatureVersion() );
663            }
664            QualifiedName[] qn = query.getTypeNames();
665            String[] na = new String[qn.length];
666            for ( int i = 0; i < na.length; i++ ) {
667                na[i] = qn[i].getPrefixedName();
668                queryElem.setAttribute( "xmlns:" + qn[i].getPrefix(), qn[i].getNamespace().toASCIIString() );
669            }
670            String tn = StringTools.arrayToString( na, ',' );
671            queryElem.setAttribute( "typeName", tn );
672    
673            if ( query.getSrsName() != null ) {
674                queryElem.setAttribute( "srsName", query.getSrsName() );
675            }
676    
677            String[] aliases = query.getAliases();
678            if ( aliases != null && aliases.length != 0 ) {
679                StringBuffer aliasesList = new StringBuffer( aliases[0] );
680                for ( int i = 1; i < aliases.length; i++ ) {
681                    aliasesList.append( ' ' );
682                    aliasesList.append( aliases[i] );
683                }
684                queryElem.setAttribute( "aliases", aliasesList.toString() );
685            }
686    
687            PropertyPath[] propertyNames = query.getPropertyNames();
688            for ( int i = 0; i < propertyNames.length; i++ ) {
689                Element propertyNameElement = XMLTools.appendElement( queryElem, WFS, PRE_WFS + "PropertyName" );
690                appendPropertyPath( propertyNameElement, propertyNames[i] );
691            }
692            Function[] fn = query.getFunctions();
693            // copy function definitions into query node
694            if ( fn != null ) {
695                for ( int i = 0; i < fn.length; i++ ) {
696                    StringReader sr = new StringReader( fn[i].toXML().toString() );
697                    Document doc;
698                    try {
699                        doc = XMLTools.parse( sr );
700                    } catch ( SAXException e ) {
701                        throw new XMLParsingException( "could not parse filter function", e );
702                    }
703                    XMLTools.copyNode( doc.getDocumentElement(), queryElem );
704                }
705            }
706            // copy filter into query node
707            if ( query.getFilter() != null ) {
708                StringReader sr = new StringReader( query.getFilter().toXML().toString() );
709                Document doc;
710                try {
711                    doc = XMLTools.parse( sr );
712                } catch ( SAXException e ) {
713                    throw new XMLParsingException( "could not parse filter", e );
714                }
715                Element elem = XMLTools.appendElement( queryElem, OGCNS, "ogc:Filter" );
716                XMLTools.copyNode( doc.getDocumentElement(), elem );
717            }
718    
719            SortProperty[] sp = query.getSortProperties();
720            if ( sp != null ) {
721                Element sortBy = XMLTools.appendElement( queryElem, OGCNS, "ogc:SortBy" );
722                for ( int i = 0; i < sp.length; i++ ) {
723                    Element sortProp = XMLTools.appendElement( sortBy, OGCNS, "ogc:SortProperty" );
724                    XMLTools.appendElement( sortProp, OGCNS, "ogc:PropertyName", sp[i].getSortProperty().getAsString() );
725                    if ( !sp[i].getSortOrder() ) {
726                        XMLTools.appendElement( sortBy, OGCNS, "ogc:SortOrder", "DESC" );
727                    }
728                }
729            }
730        }
731    
732        /**
733         * Appends the XML representation of the <code>wfs:FeatureTypeList</code>- section to the
734         * passed <code>Element</code>.
735         * 
736         * @param root
737         * @param featureTypeList
738         */
739        public static void appendFeatureTypeList( Element root, FeatureTypeList featureTypeList ) {
740    
741            Element featureTypeListNode = XMLTools.appendElement( root, WFS, PRE_WFS + "FeatureTypeList", null );
742            Operation[] operations = featureTypeList.getGlobalOperations();
743            if ( operations != null ) {
744                Element operationsNode = XMLTools.appendElement( featureTypeListNode, WFS, PRE_WFS + "Operations" );
745                for ( int i = 0; i < operations.length; i++ ) {
746                    XMLTools.appendElement( operationsNode, WFS, PRE_WFS + "Operation", operations[i].getOperation() );
747                }
748            }
749            WFSFeatureType[] featureTypes = featureTypeList.getFeatureTypes();
750            if ( featureTypes != null ) {
751                for ( int i = 0; i < featureTypes.length; i++ ) {
752                    appendWFSFeatureType( featureTypeListNode, featureTypes[i] );
753                }
754            }
755    
756            
757        }
758    
759        /**
760         * Appends the XML representation of the <code>WFSFeatureType</code> instance to the passed
761         * <code>Element</code>.
762         * 
763         * @param root
764         * @param featureType
765         */
766        public static void appendWFSFeatureType( Element root, WFSFeatureType featureType ) {
767    
768            Element featureTypeNode = XMLTools.appendElement( root, WFS, PRE_WFS + "FeatureType" );
769    
770            if ( featureType.getName().getPrefix() != null ) {
771                XMLTools.appendNSBinding( featureTypeNode, featureType.getName().getPrefix(),
772                                          featureType.getName().getNamespace() );
773            }
774            XMLTools.appendElement( featureTypeNode, WFS, PRE_WFS + "Name", featureType.getName().getPrefixedName() );
775            XMLTools.appendElement( featureTypeNode, WFS, PRE_WFS + "Title", featureType.getTitle() );
776            String abstract_ = featureType.getAbstract();
777            if ( abstract_ != null ) {
778                XMLTools.appendElement( featureTypeNode, WFS, PRE_WFS + "Abstract", featureType.getAbstract() );
779            }
780            Keywords[] keywords = featureType.getKeywords();
781            if ( keywords != null ) {
782                appendOWSKeywords( featureTypeNode, keywords );
783            }
784            URI defaultSrs = featureType.getDefaultSRS();
785            if ( defaultSrs != null ) {
786                XMLTools.appendElement( featureTypeNode, WFS, PRE_WFS + "DefaultSRS", defaultSrs.toString() );
787                URI[] otherSrs = featureType.getOtherSrs();
788                if ( otherSrs != null ) {
789                    for ( int i = 0; i < otherSrs.length; i++ ) {
790                        XMLTools.appendElement( featureTypeNode, WFS, PRE_WFS + "OtherSRS", otherSrs[i].toString() );
791                    }
792                }
793            } else {
794                XMLTools.appendElement( featureTypeNode, WFS, PRE_WFS + "Title" );
795            }
796            Operation[] operations = featureType.getOperations();
797            if ( operations != null ) {
798                Element operationsNode = XMLTools.appendElement( featureTypeNode, WFS, PRE_WFS + "Operations" );
799                for ( int i = 0; i < operations.length; i++ ) {
800                    XMLTools.appendElement( operationsNode, WFS, PRE_WFS + "Operation", operations[i].getOperation() );
801                }
802            }
803            FormatType[] formats = featureType.getOutputFormats();
804            if ( formats != null ) {
805                appendOutputFormats( featureTypeNode, formats );
806            }
807            Envelope[] wgs84BoundingBoxes = featureType.getWgs84BoundingBoxes();
808            for ( int i = 0; i < wgs84BoundingBoxes.length; i++ ) {
809                appendWgs84BoundingBox( featureTypeNode, wgs84BoundingBoxes[i] );
810            }
811        }
812    
813        /**
814         * Appends the XML representation of the <code>wfs:ServesGMLObjectTypeList</code>- section to
815         * the passed <code>Element</code> as a new element with the given qualified name.
816         * 
817         * @param root
818         * @param elementNS
819         * @param elementName
820         * @param gmlObjectTypes
821         */
822        public static void appendGMLObjectTypeList( Element root, URI elementNS, String elementName,
823                                                    GMLObject[] gmlObjectTypes ) {
824    
825            Element gmlObjectTypeListNode = XMLTools.appendElement( root, elementNS, elementName );
826            for ( int i = 0; i < gmlObjectTypes.length; i++ ) {
827                appendGMLObjectTypeType( gmlObjectTypeListNode, gmlObjectTypes[i] );
828            }
829        }
830    
831        /**
832         * Appends the XML representation of the given {@link GMLObject} (as a
833         * <code>wfs:GMLObjectType</code> element) to the passed <code>Element</code>.
834         * 
835         * @param root
836         * @param gmlObjectType
837         */
838        public static void appendGMLObjectTypeType( Element root, GMLObject gmlObjectType ) {
839    
840            Element gmlObjectTypeNode = XMLTools.appendElement( root, WFS, PRE_WFS + "GMLObjectType" );
841    
842            if ( gmlObjectType.getName().getPrefix() != null ) {
843                XMLTools.appendNSBinding( gmlObjectTypeNode, gmlObjectType.getName().getPrefix(),
844                                          gmlObjectType.getName().getNamespace() );
845            }
846            XMLTools.appendElement( gmlObjectTypeNode, WFS, PRE_WFS + "Name", gmlObjectType.getName().getPrefixedName() );
847            if ( gmlObjectType.getTitle() != null ) {
848                XMLTools.appendElement( gmlObjectTypeNode, WFS, PRE_WFS + "Title", gmlObjectType.getTitle() );
849            }
850            String abstract_ = gmlObjectType.getAbstract();
851            if ( abstract_ != null ) {
852                XMLTools.appendElement( gmlObjectTypeNode, WFS, PRE_WFS + "Abstract", gmlObjectType.getAbstract() );
853            }
854            Keywords[] keywords = gmlObjectType.getKeywords();
855            if ( keywords != null ) {
856                appendOWSKeywords( gmlObjectTypeNode, keywords );
857            }
858            FormatType[] formats = gmlObjectType.getOutputFormats();
859            if ( formats != null ) {
860                appendOutputFormats( gmlObjectTypeNode, formats );
861            }
862        }
863    
864        /**
865         * Appends the XML representation of the given {@link Envelope} (as an
866         * <code>ows:WGS84BoundingBoxType</code> element) to the passed <code>Element</code>.
867         * 
868         * @param root
869         * @param envelope
870         */
871        public static void appendWgs84BoundingBox( Element root, Envelope envelope ) {
872            Element wgs84BoundingBoxElement = XMLTools.appendElement( root, OWSNS, "ows:WGS84BoundingBox" );
873            XMLTools.appendElement( wgs84BoundingBoxElement, OWSNS, "ows:LowerCorner", envelope.getMin().getX() + " "
874                                                                                       + envelope.getMin().getY() );
875            XMLTools.appendElement( wgs84BoundingBoxElement, OWSNS, "ows:UpperCorner", envelope.getMax().getX() + " "
876                                                                                       + envelope.getMax().getY() );
877        }
878    
879        /**
880         * Appends the XML representation of the given {@link FormatType}s as (as a
881         * <code>wfs:OutputFormats</code> element) to the passed <code>Element</code>.
882         * 
883         * @param root
884         * @param formats
885         */
886        public static void appendOutputFormats( Element root, FormatType[] formats ) {
887    
888            Element outputFormatsNode = XMLTools.appendElement( root, WFS, PRE_WFS + "OutputFormats" );
889            for ( int i = 0; i < formats.length; i++ ) {
890                Element formatNode = XMLTools.appendElement( outputFormatsNode, WFS, PRE_WFS + "Format",
891                                                             formats[i].getValue() );
892                if ( formats[i].getInFilter() != null ) {
893                    formatNode.setAttributeNS( DEEGREEWFS.toString(), "deegree:inFilter",
894                                               formats[i].getInFilter().toString() );
895                }
896                if ( formats[i].getOutFilter() != null ) {
897                    formatNode.setAttributeNS( DEEGREEWFS.toString(), "deegree:outFilter",
898                                               formats[i].getOutFilter().toString() );
899                }
900                if ( formats[i].getSchemaLocation() != null ) {
901                    formatNode.setAttributeNS( DEEGREEWFS.toString(), "deegree:schemaLocation",
902                                               formats[i].getSchemaLocation().toString() );
903                }
904            }
905        }
906    }