001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/ogcwebservices/wfs/XMLFactory.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2007 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: rbezema $
112     * 
113     * @version $Revision: 7889 $, $Date: 2007-08-02 11:51:48 +0200 (Do, 02 Aug 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 {@link Element}.
204         * 
205         * @param root
206         * @param serviceIdentification
207         */
208        protected static void appendServiceIdentification( Element root, ServiceIdentification serviceIdentification ) {
209    
210            // 'ServiceIdentification'-element
211            Element serviceIdentificationNode = XMLTools.appendElement( root, OWSNS, "ows:ServiceIdentification" );
212    
213            // 'ServiceType'-element
214            XMLTools.appendElement( serviceIdentificationNode, OWSNS, "ows:ServiceType",
215                                    serviceIdentification.getServiceType().getCode() );
216    
217            // 'ServiceTypeVersion'-elements
218            String[] versions = serviceIdentification.getServiceTypeVersions();
219            for ( int i = 0; i < versions.length; i++ ) {
220                XMLTools.appendElement( serviceIdentificationNode, OWSNS, "ows:ServiceTypeVersion", versions[i] );
221            }
222    
223            // 'Fees'-element
224            XMLTools.appendElement( serviceIdentificationNode, OWSNS, "ows:Fees", serviceIdentification.getFees() );
225    
226            // 'AccessConstraints'-element
227            String[] constraints = serviceIdentification.getAccessConstraints();
228            if ( constraints != null ) {
229                for ( int i = 0; i < constraints.length; i++ ) {
230                    XMLTools.appendElement( serviceIdentificationNode, OWSNS, "ows:AccessConstraints", constraints[i] );
231                }
232            }
233        }
234    
235        /**
236         * Exports a <code>GetFeature</code> instance to a <code>GetFeatureDocument</code>.
237         * 
238         * @param getFeature
239         *            request to be exported
240         * @return XML representation of the <code>GetFeature</code> request
241         * @throws IOException
242         * @throws XMLParsingException
243         */
244        public static GetFeatureDocument export( GetFeature getFeature )
245                                throws IOException, XMLParsingException {
246    
247            GetFeatureDocument xml = new GetFeatureDocument();
248            try {
249                xml.load( XMLFactory.class.getResource( "GetFeatureTemplate.xml" ) );
250            } catch ( SAXException e ) {
251                throw new XMLParsingException( "could not parse GetFeatureTemplate.xml", e );
252            }
253            Element root = xml.getRootElement();
254            root.setAttribute( "outputFormat", getFeature.getOutputFormat() );
255            root.setAttribute( "service", "WFS" );
256            root.setAttribute( "version", getFeature.getVersion() );
257            if ( getFeature.getHandle() != null ) {
258                root.setAttribute( "handle", getFeature.getHandle() );
259            }
260            if ( getFeature.getResultType() == RESULT_TYPE.HITS ) {
261                root.setAttribute( "resultType", "hits" );
262            } else {
263                root.setAttribute( "resultType", "results" );
264            }
265            root.setAttribute( "maxFeatures", "" + getFeature.getMaxFeatures() );
266            if ( getFeature.getStartPosition() > 0 ) {
267                root.setAttribute( "startPosition", "" + getFeature.getStartPosition() );
268            }
269            if ( getFeature.getTraverseXLinkDepth() > 0 ) {
270                root.setAttribute( "traverseXLinkDepth", "" + getFeature.getTraverseXLinkDepth() );
271            }
272            if ( getFeature.getTraverseXLinkExpiry() > 0 ) {
273                root.setAttribute( "traverseXLinkExpiry", "" + getFeature.getTraverseXLinkExpiry() );
274            }
275            Query[] queries = getFeature.getQuery();
276            for ( int i = 0; i < queries.length; i++ ) {
277                appendQuery( root, queries[i] );
278            }
279            return xml;
280        }
281    
282        /**
283         * Exports a {@link LockFeature} request instance to a {@link LockFeatureDocument}.
284         * 
285         * @param request
286         *            request to be exported
287         * @return XML representation of the <code>LockFeature</code> request
288         * @throws IOException
289         * @throws XMLParsingException
290         * @throws SAXException
291         */
292        public static LockFeatureDocument export( LockFeature request )
293                                throws IOException, XMLParsingException, SAXException {
294    
295            LockFeatureDocument doc = new LockFeatureDocument();
296            doc.createEmptyDocument();
297    
298            Element root = doc.getRootElement();
299            root.setAttribute( "version", request.getVersion() );
300            root.setAttribute( "service", "WFS" );
301            if ( request.getHandle() != null ) {
302                root.setAttribute( "handle", request.getHandle() );
303            }
304            root.setAttribute( "expiry", "" + request.getExpiry() );
305            root.setAttribute( "lockAction", "" + request.getLockAction() );
306    
307            List<Lock> locks = request.getLocks();
308            for ( Lock lock : locks ) {
309                appendLock( root, lock );
310            }
311            return doc;
312        }
313    
314        /**
315         * Appends the XML representation of the given {@link Lock} to the given element.
316         * 
317         * @param root
318         * @param lock
319         */
320        private static void appendLock( Element root, Lock lock )
321                                throws IOException, XMLParsingException {
322    
323            Element lockElement = XMLTools.appendElement( root, WFS, PRE_WFS + "Lock" );
324            if ( lock.getHandle() != null ) {
325                lockElement.setAttribute( "handle", lock.getHandle() );
326            }
327            QualifiedName typeName = lock.getTypeName();
328            if ( typeName.getPrefix() != null ) {
329                lockElement.setAttribute( "xmlns:" + typeName.getPrefix(), typeName.getNamespace().toASCIIString() );
330            }
331            lockElement.setAttribute( "typeName", typeName.getPrefixedName() );
332    
333            // copy filter into Lock element
334            if ( lock.getFilter() != null ) {
335                StringReader sr = new StringReader( lock.getFilter().toXML().toString() );
336                Document doc;
337                try {
338                    doc = XMLTools.parse( sr );
339                } catch ( SAXException e ) {
340                    throw new XMLParsingException( "Could not parse filter.", e );
341                }
342                Element elem = XMLTools.appendElement( lockElement, OGCNS, "ogc:Filter" );
343                XMLTools.copyNode( doc.getDocumentElement(), elem );
344            }
345        }
346    
347        /**
348         * Exports a {@link LockFeatureResponse} instance to its XML representation.
349         * 
350         * @param response
351         *            response to be exported
352         * @return XML representation of the <code>LockFeatureResponse</code>
353         * @throws IOException
354         * @throws SAXException
355         */
356        public static LockFeatureResponseDocument export( LockFeatureResponse response )
357                                throws IOException, SAXException {
358    
359            LockFeatureResponseDocument doc = new LockFeatureResponseDocument();
360            doc.createEmptyDocument();
361    
362            Element root = doc.getRootElement();
363            XMLTools.appendElement( root, WFS, PRE_WFS + "LockId", response.getLockId() );
364            String[] fids = response.getFeaturesLocked();
365            if ( fids.length != 0 ) {
366                Element featuresLockedElement = XMLTools.appendElement( root, WFS, PRE_WFS + "FeaturesLocked" );
367                for ( String fid : fids ) {
368                    appendFeatureId( featuresLockedElement, fid );
369                }
370            }
371            fids = response.getFeaturesNotLocked();
372            if ( fids.length != 0 ) {
373                Element featuresNotLockedElement = XMLTools.appendElement( root, WFS, PRE_WFS + "FeaturesNotLocked" );
374                for ( String fid : fids ) {
375                    appendFeatureId( featuresNotLockedElement, fid );
376                }
377            }
378            return doc;
379        }
380    
381        /**
382         * Exports a {@link Transaction} instance to its XML representation.
383         * 
384         * @param transaction
385         *            transaction to export
386         * @return XML representation of transaction
387         * @throws IOException
388         * @throws XMLParsingException
389         */
390        public static TransactionDocument export( Transaction transaction )
391                                throws IOException, XMLParsingException {
392    
393            TransactionDocument xml = new TransactionDocument();
394            try {
395                xml.createEmptyDocument();
396            } catch ( SAXException e ) {
397                throw new IOException( e.getMessage() );
398            }
399            Element root = xml.getRootElement();
400            List ops = transaction.getOperations();
401            for ( int i = 0; i < ops.size(); i++ ) {
402                try {
403                    if ( ops.get( i ) instanceof Insert ) {
404                        appendInsert( root, (Insert) ops.get( i ) );
405                    } else if ( ops.get( i ) instanceof Update ) {
406                        appendUpdate( root, (Update) ops.get( i ) );
407                    } else if ( ops.get( i ) instanceof Delete ) {
408                        appendDelete( root, (Delete) ops.get( i ) );
409                    }
410                } catch ( Exception e ) {
411                    LOG.logError( e.getMessage(), e );
412                    throw new XMLParsingException( e.getMessage() );
413                }
414            }
415            return xml;
416        }
417    
418        /**
419         * Adds the XML representation of a <code>Delete</code> operation to the given element.
420         * 
421         * @param root
422         * @param delete
423         */
424        private static void appendDelete( Element root, Delete delete ) {
425            Element el = XMLTools.appendElement( root, WFS, "Delete" );
426            if ( delete.getHandle() != null ) {
427                el.setAttribute( "handle", delete.getHandle() );
428            }
429            // TODO What about the namespace binding here?
430            el.setAttribute( "typeName", delete.getTypeName().getPrefixedName() );
431    
432            Filter filter = delete.getFilter();
433            if ( filter != null ) {
434                org.deegree.model.filterencoding.XMLFactory.appendFilter( el, filter );
435            }
436            root.appendChild( el );
437        }
438    
439        /**
440         * Adds the XML representation of an <code>Update</code> operation to the given element.
441         * <p>
442         * Respects the deegree-specific extension to the Update operation: instead of specifying properties and their
443         * values, it's also possible to only specify just one feature that replaces the matched feature.
444         * 
445         * @param root
446         * @param update
447         * @throws SAXException
448         * @throws IOException
449         * @throws FeatureException
450         * @throws GeometryException
451         */
452        private static void appendUpdate( Element root, Update update )
453                                throws FeatureException, IOException, SAXException, GeometryException {
454    
455            Element el = XMLTools.appendElement( root, WFS, "Update" );
456            if ( update.getHandle() != null ) {
457                el.setAttribute( "handle", update.getHandle() );
458            }
459            // TODO What about the namespace binding here?
460            el.setAttribute( "typeName", update.getTypeName().getPrefixedName() );
461    
462            Feature replacement = update.getFeature();
463            if ( replacement != null ) {
464                GMLFeatureAdapter adapter = new GMLFeatureAdapter();
465                adapter.append( root, replacement );
466            } else {
467                Map<PropertyPath, FeatureProperty> replaces = update.getReplacementProperties();
468                for ( PropertyPath propertyName : replaces.keySet() ) {
469                    Element propElement = XMLTools.appendElement( el, WFS, "Property" );
470                    Element nameElement = XMLTools.appendElement( propElement, WFS, "Name" );
471                    org.deegree.ogcbase.XMLFactory.appendPropertyPath( nameElement, propertyName );
472    
473                    // append property value
474                    Object propValue = replaces.get( propertyName ).getValue();
475                    if ( propValue != null ) {
476                        Element valueElement = XMLTools.appendElement( propElement, WFS, "Value" );
477                        if ( propValue instanceof Feature ) {
478                            GMLFeatureAdapter adapter = new GMLFeatureAdapter();
479                            adapter.append( valueElement, (Feature) propValue );
480                        } else if ( propValue instanceof Geometry ) {
481                            appendGeometry( valueElement, (Geometry) propValue );
482                        } else {
483                            XMLTools.setNodeValue( valueElement, propValue.toString() );
484                        }
485                    }
486                }
487            }
488    
489            Filter filter = update.getFilter();
490            if ( filter != null ) {
491                org.deegree.model.filterencoding.XMLFactory.appendFilter( el, filter );
492            }
493            root.appendChild( el );
494        }
495    
496        /**
497         * Adds the XML representation of an <code>Insert</code> operation to the given element.
498         * 
499         * @param root
500         * @param insert
501         * @throws SAXException
502         * @throws IOException
503         * @throws FeatureException
504         */
505        private static void appendInsert( Element root, Insert insert )
506                                throws IOException, FeatureException, XMLException, SAXException {
507    
508            Element el = XMLTools.appendElement( root, WFS, "Insert" );
509            if ( insert.getHandle() != null ) {
510                el.setAttribute( "handle", insert.getHandle() );
511            }
512            if ( insert.getIdGen() != null ) {
513                el.setAttribute( "idgen", insert.getIdGen().name() );
514            }
515    
516            GMLFeatureAdapter adapter = new GMLFeatureAdapter();
517            adapter.append( el, insert.getFeatures() );
518        }
519    
520        /**
521         * Exports an instance of {@link TransactionResponse} to its XML representation.
522         * 
523         * @param response
524         *            TransactionResponse to export
525         * @return XML representation of TransactionResponse
526         * @throws IOException
527         */
528        public static TransactionResponseDocument export( TransactionResponse response )
529                                throws IOException {
530    
531            TransactionResponseDocument xml = new TransactionResponseDocument();
532            try {
533                xml.createEmptyDocument();
534            } catch ( SAXException e ) {
535                throw new IOException( e.getMessage() );
536            }
537    
538            Element root = xml.getRootElement();
539            appendTransactionSummary( root, response.getTotalInserted(), response.getTotalUpdated(),
540                                      response.getTotalDeleted() );
541            appendInsertResults( root, response.getInsertResults() );
542            return xml;
543        }
544    
545        /**
546         * Appends a 'wfs:TransactionSummary' element to the given element.
547         * 
548         * @param root
549         * @param totalInserted
550         * @param totalUpdated
551         * @param totalDeleted
552         */
553        private static void appendTransactionSummary( Element root, int totalInserted, int totalUpdated, int totalDeleted ) {
554            Element taSummary = XMLTools.appendElement( root, WFS, PRE_WFS + "TransactionSummary" );
555            XMLTools.appendElement( taSummary, WFS, PRE_WFS + "totalInserted", "" + totalInserted );
556            XMLTools.appendElement( taSummary, WFS, PRE_WFS + "totalUpdated", "" + totalUpdated );
557            XMLTools.appendElement( taSummary, WFS, PRE_WFS + "totalDeleted", "" + totalDeleted );
558        }
559    
560        /**
561         * Appends an 'wfs:InsertResults' element to the given element (only if necessary).
562         * 
563         * @param root
564         * @param insertResults
565         */
566        private static void appendInsertResults( Element root, Collection<InsertResults> insertResults ) {
567            if ( insertResults.size() > 0 ) {
568                Element insertResultsElement = XMLTools.appendElement( root, WFS, PRE_WFS + "InsertResults" );
569                Iterator<InsertResults> iter = insertResults.iterator();
570                while ( iter.hasNext() ) {
571                    appendFeatureIds( insertResultsElement, iter.next() );
572                }
573            }
574        }
575    
576        /**
577         * Appends a 'wfs:Feature' element to the given element.
578         * 
579         * @param root
580         * @param results
581         */
582        private static void appendFeatureIds( Element root, InsertResults results ) {
583            Element featureElement = XMLTools.appendElement( root, WFS, PRE_WFS + "Feature" );
584            String handle = results.getHandle();
585            if ( handle != null ) {
586                featureElement.setAttribute( "handle", handle );
587            }
588            Iterator<FeatureId> iter = results.getFeatureIDs().iterator();
589            while ( iter.hasNext() ) {
590                Element featureIdElement = XMLTools.appendElement( featureElement, OGCNS, "ogc:FeatureId" );
591                featureIdElement.setAttribute( "fid", iter.next().getAsString() );
592            }
593        }
594    
595        /**
596         * Appends the XML representation of the given {@link Query} instance to an element.
597         * 
598         * @param query
599         */
600        private static void appendQuery( Element root, Query query )
601                                throws IOException, XMLParsingException {
602    
603            Element queryElem = XMLTools.appendElement( root, WFS, PRE_WFS + "Query" );
604            if ( query.getHandle() != null ) {
605                queryElem.setAttribute( "handle", query.getHandle() );
606            }
607            if ( query.getFeatureVersion() != null ) {
608                queryElem.setAttribute( "featureVersion", query.getFeatureVersion() );
609            }
610            QualifiedName[] qn = query.getTypeNames();
611            String[] na = new String[qn.length];
612            for ( int i = 0; i < na.length; i++ ) {
613                na[i] = qn[i].getPrefixedName();
614                queryElem.setAttribute( "xmlns:" + qn[i].getPrefix(), qn[i].getNamespace().toASCIIString() );
615            }
616            String tn = StringTools.arrayToString( na, ',' );
617            queryElem.setAttribute( "typeName", tn );
618    
619            if (query.getSrsName() != null) {
620                queryElem.setAttribute( "srsName", query.getSrsName() );            
621            }
622            
623            String[] aliases = query.getAliases();
624            if ( aliases != null && aliases.length != 0 ) {
625                StringBuffer aliasesList = new StringBuffer( aliases[0] );
626                for ( int i = 1; i < aliases.length; i++ ) {
627                    aliasesList.append( ' ' );
628                    aliasesList.append( aliases[i] );
629                }
630                queryElem.setAttribute( "aliases", aliasesList.toString() );
631            }
632    
633            PropertyPath[] propertyNames = query.getPropertyNames();
634            for ( int i = 0; i < propertyNames.length; i++ ) {
635                Element propertyNameElement = XMLTools.appendElement( queryElem, WFS, PRE_WFS + "PropertyName" );
636                appendPropertyPath( propertyNameElement, propertyNames[i] );
637            }
638            Function[] fn = query.getFunctions();
639            // copy function definitions into query node
640            if ( fn != null ) {
641                for ( int i = 0; i < fn.length; i++ ) {
642                    StringReader sr = new StringReader( fn[i].toXML().toString() );
643                    Document doc;
644                    try {
645                        doc = XMLTools.parse( sr );
646                    } catch ( SAXException e ) {
647                        throw new XMLParsingException( "could not parse filter function", e );
648                    }
649                    XMLTools.copyNode( doc.getDocumentElement(), queryElem );
650                }
651            }
652            // copy filter into query node
653            if ( query.getFilter() != null ) {
654                StringReader sr = new StringReader( query.getFilter().toXML().toString() );
655                Document doc;
656                try {
657                    doc = XMLTools.parse( sr );
658                } catch ( SAXException e ) {
659                    throw new XMLParsingException( "could not parse filter", e );
660                }
661                Element elem = XMLTools.appendElement( queryElem, OGCNS, "ogc:Filter" );
662                XMLTools.copyNode( doc.getDocumentElement(), elem );
663            }
664    
665            SortProperty[] sp = query.getSortProperties();
666            if ( sp != null ) {
667                Element sortBy = XMLTools.appendElement( queryElem, OGCNS, "ogc:SortBy" );
668                for ( int i = 0; i < sp.length; i++ ) {
669                    Element sortProp = XMLTools.appendElement( sortBy, OGCNS, "ogc:SortProperty" );
670                    XMLTools.appendElement( sortProp, OGCNS, "ogc:PropertyName", sp[i].getSortProperty().getAsString() );
671                    if ( !sp[i].getSortOrder() ) {
672                        XMLTools.appendElement( sortBy, OGCNS, "ogc:SortOrder", "DESC" );
673                    }
674                }
675            }
676        }
677    
678        /**
679         * Appends the XML representation of the <code>wfs:FeatureTypeList</code>- section to the passed
680         * <code>Element</code>.
681         * 
682         * @param root
683         * @param featureTypeList
684         */
685        public static void appendFeatureTypeList( Element root, FeatureTypeList featureTypeList ) {
686    
687            Element featureTypeListNode = XMLTools.appendElement( root, WFS, PRE_WFS + "FeatureTypeList", null );
688            Operation[] operations = featureTypeList.getGlobalOperations();
689            if ( operations != null ) {
690                Element operationsNode = XMLTools.appendElement( featureTypeListNode, WFS, PRE_WFS + "Operations" );
691                for ( int i = 0; i < operations.length; i++ ) {
692                    XMLTools.appendElement( operationsNode, WFS, PRE_WFS + "Operation", operations[i].getOperation() );
693                }
694            }
695            WFSFeatureType[] featureTypes = featureTypeList.getFeatureTypes();
696            if ( featureTypes != null ) {
697                for ( int i = 0; i < featureTypes.length; i++ ) {
698                    appendWFSFeatureType( featureTypeListNode, featureTypes[i] );
699                }
700            }
701    
702            LOG.exiting();
703        }
704    
705        /**
706         * Appends the XML representation of the <code>WFSFeatureType</code> instance to the passed <code>Element</code>.
707         * 
708         * @param root
709         * @param featureType
710         */
711        public static void appendWFSFeatureType( Element root, WFSFeatureType featureType ) {
712    
713            Element featureTypeNode = XMLTools.appendElement( root, WFS, PRE_WFS + "FeatureType" );
714    
715            if ( featureType.getName().getPrefix() != null ) {
716                XMLTools.appendNSBinding( featureTypeNode, featureType.getName().getPrefix(),
717                                          featureType.getName().getNamespace() );
718            }
719            XMLTools.appendElement( featureTypeNode, WFS, PRE_WFS + "Name", featureType.getName().getPrefixedName() );
720            XMLTools.appendElement( featureTypeNode, WFS, PRE_WFS + "Title", featureType.getTitle() );
721            String abstract_ = featureType.getAbstract();
722            if ( abstract_ != null ) {
723                XMLTools.appendElement( featureTypeNode, WFS, PRE_WFS + "Abstract", featureType.getAbstract() );
724            }
725            Keywords[] keywords = featureType.getKeywords();
726            if ( keywords != null ) {
727                appendOWSKeywords( featureTypeNode, keywords );
728            }
729            URI defaultSrs = featureType.getDefaultSRS();
730            if ( defaultSrs != null ) {
731                XMLTools.appendElement( featureTypeNode, WFS, PRE_WFS + "DefaultSRS", defaultSrs.toString() );
732                URI[] otherSrs = featureType.getOtherSrs();
733                if ( otherSrs != null ) {
734                    for ( int i = 0; i < otherSrs.length; i++ ) {
735                        XMLTools.appendElement( featureTypeNode, WFS, PRE_WFS + "OtherSRS", otherSrs[i].toString() );
736                    }
737                }
738            } else {
739                XMLTools.appendElement( featureTypeNode, WFS, PRE_WFS + "Title" );
740            }
741            Operation[] operations = featureType.getOperations();
742            if ( operations != null ) {
743                Element operationsNode = XMLTools.appendElement( featureTypeNode, WFS, PRE_WFS + "Operations" );
744                for ( int i = 0; i < operations.length; i++ ) {
745                    XMLTools.appendElement( operationsNode, WFS, PRE_WFS + "Operation", operations[i].getOperation() );
746                }
747            }
748            FormatType[] formats = featureType.getOutputFormats();
749            if ( formats != null ) {
750                appendOutputFormats( featureTypeNode, formats );
751            }
752            Envelope[] wgs84BoundingBoxes = featureType.getWgs84BoundingBoxes();
753            for ( int i = 0; i < wgs84BoundingBoxes.length; i++ ) {
754                appendWgs84BoundingBox( featureTypeNode, wgs84BoundingBoxes[i] );
755            }
756        }
757    
758        /**
759         * Appends the XML representation of the <code>wfs:ServesGMLObjectTypeList</code>- section to the passed
760         * <code>Element</code> as a new element with the given qualified name.
761         * 
762         * @param root
763         * @param elementNS
764         * @param elementName
765         * @param gmlObjectTypes
766         */
767        public static void appendGMLObjectTypeList( Element root, URI elementNS, String elementName,
768                                                    GMLObject[] gmlObjectTypes ) {
769    
770            Element gmlObjectTypeListNode = XMLTools.appendElement( root, elementNS, elementName );
771            for ( int i = 0; i < gmlObjectTypes.length; i++ ) {
772                appendGMLObjectTypeType( gmlObjectTypeListNode, gmlObjectTypes[i] );
773            }
774        }
775    
776        /**
777         * Appends the XML representation of the given {@link GMLObject} (as a <code>wfs:GMLObjectType</code> element) to
778         * the passed <code>Element</code>.
779         * 
780         * @param root
781         * @param gmlObjectType
782         */
783        public static void appendGMLObjectTypeType( Element root, GMLObject gmlObjectType ) {
784    
785            Element gmlObjectTypeNode = XMLTools.appendElement( root, WFS, PRE_WFS + "GMLObjectType" );
786    
787            if ( gmlObjectType.getName().getPrefix() != null ) {
788                XMLTools.appendNSBinding( gmlObjectTypeNode, gmlObjectType.getName().getPrefix(),
789                                          gmlObjectType.getName().getNamespace() );
790            }
791            XMLTools.appendElement( gmlObjectTypeNode, WFS, PRE_WFS + "Name", gmlObjectType.getName().getPrefixedName() );
792            if ( gmlObjectType.getTitle() != null ) {
793                XMLTools.appendElement( gmlObjectTypeNode, WFS, PRE_WFS + "Title", gmlObjectType.getTitle() );
794            }
795            String abstract_ = gmlObjectType.getAbstract();
796            if ( abstract_ != null ) {
797                XMLTools.appendElement( gmlObjectTypeNode, WFS, PRE_WFS + "Abstract", gmlObjectType.getAbstract() );
798            }
799            Keywords[] keywords = gmlObjectType.getKeywords();
800            if ( keywords != null ) {
801                appendOWSKeywords( gmlObjectTypeNode, keywords );
802            }
803            FormatType[] formats = gmlObjectType.getOutputFormats();
804            if ( formats != null ) {
805                appendOutputFormats( gmlObjectTypeNode, formats );
806            }
807        }
808    
809        /**
810         * Appends the XML representation of the given {@link Envelope} (as an <code>ows:WGS84BoundingBoxType</code>
811         * element) to the passed <code>Element</code>.
812         * 
813         * @param root
814         * @param envelope
815         */
816        public static void appendWgs84BoundingBox( Element root, Envelope envelope ) {
817            Element wgs84BoundingBoxElement = XMLTools.appendElement( root, OWSNS, "ows:WGS84BoundingBox" );
818            XMLTools.appendElement( wgs84BoundingBoxElement, OWSNS, "ows:LowerCorner", envelope.getMin().getX() + " "
819                                                                                     + envelope.getMin().getY() );
820            XMLTools.appendElement( wgs84BoundingBoxElement, OWSNS, "ows:UpperCorner", envelope.getMax().getX() + " "
821                                                                                     + envelope.getMax().getY() );
822        }
823    
824        /**
825         * Appends the XML representation of the given {@link FormatType}s as (as a <code>wfs:OutputFormats</code>
826         * element) to the passed <code>Element</code>.
827         * 
828         * @param root
829         * @param formats
830         */
831        public static void appendOutputFormats( Element root, FormatType[] formats ) {
832    
833            Element outputFormatsNode = XMLTools.appendElement( root, WFS, PRE_WFS + "OutputFormats" );
834            for ( int i = 0; i < formats.length; i++ ) {
835                Element formatNode = XMLTools.appendElement( outputFormatsNode, WFS, PRE_WFS + "Format", formats[i].getValue() );
836                if ( formats[i].getInFilter() != null ) {
837                    formatNode.setAttributeNS( DEEGREEWFS.toString(), "deegree:inFilter",
838                                               formats[i].getInFilter().toString() );
839                }
840                if ( formats[i].getOutFilter() != null ) {
841                    formatNode.setAttributeNS( DEEGREEWFS.toString(), "deegree:outFilter",
842                                               formats[i].getOutFilter().toString() );
843                }
844                if ( formats[i].getSchemaLocation() != null ) {
845                    formatNode.setAttributeNS( DEEGREEWFS.toString(), "deegree:schemaLocation",
846                                               formats[i].getSchemaLocation().toString() );
847                }
848            }
849        }
850    }