001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/model/feature/GMLFeatureAdapter.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.model.feature;
037    
038    import static org.deegree.datatypes.Types.DATE;
039    import static org.deegree.datatypes.Types.FEATURE;
040    import static org.deegree.datatypes.Types.TIME;
041    import static org.deegree.datatypes.Types.TIMESTAMP;
042    import static org.deegree.framework.util.TimeTools.getISOFormattedTime;
043    import static org.deegree.framework.xml.XMLTools.escape;
044    import static org.deegree.model.spatialschema.GMLGeometryAdapter.swap;
045    import static org.deegree.ogcbase.CommonNamespaces.GMLNS;
046    
047    import java.io.ByteArrayInputStream;
048    import java.io.ByteArrayOutputStream;
049    import java.io.IOException;
050    import java.io.OutputStream;
051    import java.io.OutputStreamWriter;
052    import java.io.PrintWriter;
053    import java.math.BigDecimal;
054    import java.net.URI;
055    import java.net.URL;
056    import java.net.URLConnection;
057    import java.sql.Timestamp;
058    import java.text.DateFormat;
059    import java.text.SimpleDateFormat;
060    import java.util.Calendar;
061    import java.util.Collection;
062    import java.util.Date;
063    import java.util.HashMap;
064    import java.util.HashSet;
065    import java.util.Iterator;
066    import java.util.Map;
067    import java.util.Set;
068    
069    import org.deegree.datatypes.QualifiedName;
070    import org.deegree.datatypes.Types;
071    import org.deegree.framework.log.ILogger;
072    import org.deegree.framework.log.LoggerFactory;
073    import org.deegree.framework.util.CharsetUtils;
074    import org.deegree.framework.util.StringTools;
075    import org.deegree.framework.util.TimeTools;
076    import org.deegree.framework.xml.DOMPrinter;
077    import org.deegree.framework.xml.XMLException;
078    import org.deegree.framework.xml.XMLFragment;
079    import org.deegree.framework.xml.XMLParsingException;
080    import org.deegree.framework.xml.XMLTools;
081    import org.deegree.io.datastore.schema.MappedFeaturePropertyType;
082    import org.deegree.io.datastore.schema.MappedFeatureType;
083    import org.deegree.model.crs.UnknownCRSException;
084    import org.deegree.model.feature.schema.FeatureType;
085    import org.deegree.model.feature.schema.PropertyType;
086    import org.deegree.model.spatialschema.Envelope;
087    import org.deegree.model.spatialschema.GMLGeometryAdapter;
088    import org.deegree.model.spatialschema.Geometry;
089    import org.deegree.model.spatialschema.GeometryException;
090    import org.deegree.ogcbase.CommonNamespaces;
091    import org.deegree.ogcbase.PropertyPath;
092    import org.deegree.ogcbase.XLinkPropertyPath;
093    import org.w3c.dom.Element;
094    import org.xml.sax.SAXException;
095    
096    /**
097     * Exports feature instances to their GML representation.
098     * <p>
099     * Has support for XLink output and to disable XLink output (which is generally not feasible).
100     * <p>
101     * Also responsible for "xlinking" features: if a feature occurs several times in a feature collection, it must be
102     * exported only once - all other occurences must use xlink-attributes in the surrounding property element to reference
103     * the feature.
104     * 
105     * TODO Handle FeatureCollections like ordinary Features (needs changes in feature model).
106     * 
107     * TODO Separate cycle check (for suppressXLinkOutput).
108     * 
109     * TODO Use a more straight-forward approach to export DOM representations.
110     * 
111     * TODO Handle multiple application schemas (in xsi:schemaLocation attribute).
112     * 
113     * TODO Handle WFS-schema-binding in a subclass in the WFS package?
114     * 
115     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
116     * @author last edited by: $Author: mschneider $
117     * 
118     * @version $Revision: 20808 $, $Date: 2009-11-16 14:03:13 +0100 (Mo, 16. Nov 2009) $
119     */
120    public class GMLFeatureAdapter {
121    
122        private static final ILogger LOG = LoggerFactory.getLogger( GMLFeatureAdapter.class );
123    
124        private String wfsSchemaBinding = "http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd";
125    
126        // values: feature ids of already exported features (for XLinks)
127        private Set<String> exportedFeatures = new HashSet<String>();
128    
129        // values: feature ids of all (sub-) features in a feature (to find cyclic features)
130        private Set<String> localFeatures = new HashSet<String>();
131    
132        private boolean suppressXLinkOutput;
133    
134        private String schemaURL;
135    
136        // marks if namespace bindings have been appended already
137        private boolean nsBindingsExported;
138    
139        private final DateFormat dateFormatter = new SimpleDateFormat( "yyyy-MM-dd" );
140    
141        private final DateFormat timeFormatter = new SimpleDateFormat( "HH:mm:ss" );
142    
143        private boolean printGeometryIds;
144    
145        private int xlinkDepth = -1;
146    
147        private HashMap<String, Integer> xlinkPropertyNames;
148    
149        private String baseUrl = "http://localhost:8080/wfs/services";
150    
151        /**
152         * Creates a new <code>GMLFeatureAdapter</code> instance with enabled XLink output.
153         */
154        public GMLFeatureAdapter() {
155            this.suppressXLinkOutput = false;
156            this.xlinkPropertyNames = new HashMap<String, Integer>();
157        }
158    
159        /**
160         * @param xlinkdepth
161         */
162        public GMLFeatureAdapter( int xlinkdepth ) {
163            xlinkDepth = xlinkdepth;
164            this.xlinkPropertyNames = new HashMap<String, Integer>();
165        }
166    
167        /**
168         * Creates a new <code>GMLFeatureAdapter</code> instance with enabled XLink output and schema reference.
169         * 
170         * @param schemaURL
171         *            URL of schema document (used as xsi:schemaLocation attribute in XML output)
172         */
173        public GMLFeatureAdapter( String schemaURL ) {
174            this.suppressXLinkOutput = false;
175            if ( schemaURL != null ) {
176                this.schemaURL = StringTools.replace( schemaURL, "&", "&amp;", true );
177            }
178            this.xlinkPropertyNames = new HashMap<String, Integer>();
179        }
180    
181        /**
182         * @param suppressXLinkOutput
183         * @param schemaURL
184         * @param printGeometryIds
185         */
186        public GMLFeatureAdapter( boolean suppressXLinkOutput, String schemaURL, boolean printGeometryIds ) {
187            this( suppressXLinkOutput, schemaURL );
188            this.printGeometryIds = printGeometryIds;
189        }
190    
191        /**
192         * @param suppressXLinkOutput
193         * @param schemaURL
194         * @param printGeometryIds
195         * @param depth
196         *            the depth of xlinks to resolve
197         */
198        public GMLFeatureAdapter( boolean suppressXLinkOutput, String schemaURL, boolean printGeometryIds, int depth ) {
199            this( suppressXLinkOutput, schemaURL, printGeometryIds );
200            xlinkDepth = depth;
201        }
202    
203        /**
204         * Creates a new instance <code>GMLFeatureAdapter</code> with configurable XLink output.
205         * 
206         * @param suppressXLinkOutput
207         *            set to true, if no XLinks shall be used
208         */
209        public GMLFeatureAdapter( boolean suppressXLinkOutput ) {
210            this.suppressXLinkOutput = suppressXLinkOutput;
211            this.xlinkPropertyNames = new HashMap<String, Integer>();
212        }
213    
214        /**
215         * Creates a new instance <code>GMLFeatureAdapter</code> with configurable XLink output.
216         * 
217         * @param suppressXLinkOutput
218         *            set to true, if no XLinks shall be used
219         * @param schemaURL
220         *            URL of schema document (used as xsi:schemaLocation attribute in XML output)
221         */
222        public GMLFeatureAdapter( boolean suppressXLinkOutput, String schemaURL ) {
223            this.suppressXLinkOutput = suppressXLinkOutput;
224            if ( schemaURL != null ) {
225                this.schemaURL = StringTools.replace( schemaURL, "&", "&amp;", true );
226            }
227            this.xlinkPropertyNames = new HashMap<String, Integer>();
228        }
229    
230        /**
231         * Creates a new instance <code>GMLFeatureAdapter</code> with configurable XLink output.
232         * 
233         * @param suppressXLinkOutput
234         *            set to true, if no XLinks shall be used
235         * @param schemaURL
236         *            URL of schema document (used for "xsi:schemaLocation" attribute in XML output)
237         * @param wfsSchemaBinding
238         *            fragment for the "xsi:schemaLocation" attribute that binds the wfs namespace
239         * @param printGeometryIds
240         */
241        public GMLFeatureAdapter( boolean suppressXLinkOutput, String schemaURL, String wfsSchemaBinding,
242                                  boolean printGeometryIds ) {
243            this.printGeometryIds = printGeometryIds;
244            this.suppressXLinkOutput = suppressXLinkOutput;
245            if ( schemaURL != null ) {
246                this.schemaURL = StringTools.replace( schemaURL, "&", "&amp;", true );
247            }
248            this.wfsSchemaBinding = wfsSchemaBinding;
249            this.xlinkPropertyNames = new HashMap<String, Integer>();
250        }
251    
252        /**
253         * @param suppressXLinkOutput
254         * @param schemaURL
255         * @param wfsSchemaBinding
256         * @param printGeometryIds
257         * @param depth
258         */
259        public GMLFeatureAdapter( boolean suppressXLinkOutput, String schemaURL, String wfsSchemaBinding,
260                                  boolean printGeometryIds, int depth ) {
261            this( suppressXLinkOutput, schemaURL, wfsSchemaBinding, printGeometryIds );
262            xlinkDepth = depth;
263        }
264    
265        /**
266         * Sets the property paths for which special xlink traversal will be done.
267         * 
268         * @param paths
269         */
270        public void setPropertyPaths( Collection<PropertyPath[]> paths ) {
271            this.xlinkPropertyNames = new HashMap<String, Integer>();
272    
273            if ( paths != null ) {
274                for ( PropertyPath[] ps : paths ) {
275                    for ( PropertyPath p : ps ) {
276                        if ( p instanceof XLinkPropertyPath ) {
277                            xlinkPropertyNames.put( p.getStep( p.getSteps() - 1 ).getPropertyName().getPrefixedName(),
278                                                    ( (XLinkPropertyPath) p ).getXlinkDepth() );
279                        }
280                    }
281                }
282            }
283        }
284    
285        /**
286         * @param url
287         *            the base url to use for GetGmlObject references
288         */
289        public void setBaseURL( String url ) {
290            this.baseUrl = url;
291        }
292    
293        /**
294         * Appends the DOM representation of the given feature to the also given <code>Node</code>.
295         * <p>
296         * TODO do this a better way (append nodes directly without serializing to string and parsing it again)
297         * 
298         * @param root
299         * @param feature
300         * @throws FeatureException
301         * @throws IOException
302         * @throws SAXException
303         */
304        public void append( Element root, Feature feature )
305                                throws FeatureException, IOException, SAXException {
306    
307            GMLFeatureDocument doc = export( feature );
308            XMLTools.insertNodeInto( doc.getRootElement(), root );
309        }
310    
311        /**
312         * Export a <code>Feature</code> to it's XML representation.
313         * 
314         * @param feature
315         *            feature to export
316         * @return XML representation of feature
317         * @throws IOException
318         * @throws FeatureException
319         * @throws XMLException
320         * @throws SAXException
321         */
322        public GMLFeatureDocument export( Feature feature )
323                                throws IOException, FeatureException, XMLException, SAXException {
324    
325            ByteArrayOutputStream bos = new ByteArrayOutputStream( 20000 );
326            export( feature, bos );
327            ByteArrayInputStream bis = new ByteArrayInputStream( bos.toByteArray() );
328            bos.close();
329    
330            GMLFeatureDocument doc = new GMLFeatureDocument();
331            doc.load( bis, XMLFragment.DEFAULT_URL );
332            return doc;
333        }
334    
335        /**
336         * Appends the DOM representation of the given <code>FeatureCollection</code> to the also given <code>Node</code>.
337         * <p>
338         * TODO do this a better way (append nodes directly without serializing to string and parsing it again)
339         * 
340         * @param root
341         * @param fc
342         * @throws FeatureException
343         * @throws IOException
344         * @throws SAXException
345         */
346        public void append( Element root, FeatureCollection fc )
347                                throws FeatureException, IOException, SAXException {
348    
349            GMLFeatureCollectionDocument doc = export( fc );
350            XMLTools.insertNodeInto( doc.getRootElement(), root );
351        }
352    
353        /**
354         * Export a <code>FeatureCollection</code> to it's XML representation.
355         * 
356         * @param fc
357         *            feature collection
358         * @return XML representation of feature collection
359         * @throws IOException
360         * @throws FeatureException
361         * @throws XMLException
362         * @throws SAXException
363         */
364        public GMLFeatureCollectionDocument export( FeatureCollection fc )
365                                throws IOException, FeatureException, XMLException, SAXException {
366    
367            ByteArrayOutputStream bos = new ByteArrayOutputStream( 20000 );
368            export( fc, bos );
369            ByteArrayInputStream bis = new ByteArrayInputStream( bos.toByteArray() );
370            bos.close();
371    
372            GMLFeatureCollectionDocument doc = new GMLFeatureCollectionDocument();
373            doc.load( bis, XMLFragment.DEFAULT_URL );
374            return doc;
375        }
376    
377        /**
378         * Exports an instance of a <code>FeatureCollection</code> to the passed <code>OutputStream</code> formatted as GML.
379         * Uses the deegree system character set for the XML header encoding information.
380         * 
381         * @param fc
382         *            feature collection to export
383         * @param os
384         *            output stream to write to
385         * 
386         * @throws IOException
387         * @throws FeatureException
388         */
389        public void export( FeatureCollection fc, OutputStream os )
390                                throws IOException, FeatureException {
391            export( fc, os, CharsetUtils.getSystemCharset() );
392        }
393    
394        /**
395         * Exports a <code>FeatureCollection</code> instance to the passed <code>OutputStream</code> formatted as GML.
396         * 
397         * @param fc
398         *            feature collection to export
399         * @param os
400         *            output stream to write to
401         * @param charsetName
402         *            name of the used charset/encoding (for the XML header)
403         * 
404         * @throws IOException
405         * @throws FeatureException
406         */
407        public void export( FeatureCollection fc, OutputStream os, String charsetName )
408                                throws IOException, FeatureException {
409    
410            PrintWriter pw = new PrintWriter( new OutputStreamWriter( os, charsetName ) );
411            pw.println( "<?xml version=\"1.0\" encoding=\"" + charsetName + "\"?>" );
412            if ( fc instanceof FeatureTupleCollection ) {
413                exportTupleCollection( (FeatureTupleCollection) fc, pw );
414            } else {
415                exportRootCollection( fc, pw );
416            }
417            pw.close();
418        }
419    
420        /**
421         * Exports a <code>FeatureCollection</code> instance to the passed <code>OutputStream</code> formatted as GML.
422         * 
423         * @param fc
424         *            feature collection to print/export
425         * @param pw
426         *            target of the printing/export
427         * @throws FeatureException
428         */
429        private void exportRootCollection( FeatureCollection fc, PrintWriter pw )
430                                throws FeatureException {
431    
432            Set<Feature> additionalRootLevelFeatures = determineAdditionalRootLevelFeatures( fc );
433            if ( this.suppressXLinkOutput && additionalRootLevelFeatures.size() > 0 ) {
434                String msg = Messages.getString( "ERROR_REFERENCE_TYPE" );
435                throw new FeatureException( msg );
436            }
437    
438            if ( fc.getId() != null && !"".equals( fc.getId() ) ) {
439                this.exportedFeatures.add( fc.getId() );
440            }
441    
442            // open the feature collection element
443            pw.print( "<" );
444            pw.print( fc.getName().getPrefixedName() );
445    
446            // hack to correct the "numberOfFeatures" attribute (can not be set in any case, because
447            // sometimes (resultType="hits") the collection contains no features at all -- but only the
448            // attribute "numberOfFeatures"
449            if ( fc.size() > 0 ) {
450                int hackedFeatureCount = fc.size() + additionalRootLevelFeatures.size();
451                fc.setAttribute( "numberOfFeatures", "" + hackedFeatureCount );
452            }
453    
454            Map<String, String> attributes = fc.getAttributes();
455            for ( Iterator<String> iterator = attributes.keySet().iterator(); iterator.hasNext(); ) {
456                String name = iterator.next();
457                String value = attributes.get( name );
458                pw.print( ' ' );
459                pw.print( name );
460                pw.print( "='" );
461                pw.print( value );
462                pw.print( "'" );
463            }
464    
465            // determine and add namespace bindings
466            Map<String, URI> nsBindings = determineUsedNSBindings( fc );
467            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
468                LOG.logDebug( nsBindings.toString() );
469            }
470            nsBindings.put( "gml", CommonNamespaces.GMLNS );
471            nsBindings.put( "xlink", CommonNamespaces.XLNNS );
472            if ( this.schemaURL != null ) {
473                nsBindings.put( "xsi", CommonNamespaces.XSINS );
474            }
475            appendNSBindings( nsBindings, pw );
476    
477            // add schema reference (if available)
478            if ( this.schemaURL != null && fc.size() > 0 ) {
479                pw.print( " xsi:schemaLocation=\"" + fc.getFeature( 0 ).getName().getNamespace() + " " );
480                pw.print( this.schemaURL + " " );
481                pw.print( wfsSchemaBinding + "\"" );
482            }
483            pw.print( '>' );
484    
485            Envelope env = null;
486            try {
487                env = fc.getBoundedBy();
488            } catch ( GeometryException e ) {
489                // omit gml:boundedBy-element if featureCollection contains features
490                // with different SRS (and their envelopes cannot be merged)
491            }
492            if ( env != null ) {
493                pw.print( "<gml:boundedBy><gml:Envelope" );
494                if ( env.getCoordinateSystem() != null ) {
495                    pw.print( " srsName='" + env.getCoordinateSystem().getPrefixedName() + "'" );
496                }
497    
498                boolean swap = swap( env );
499    
500                pw.print( "><gml:lowerCorner>" );
501                pw.print( swap ? env.getMin().getY() : env.getMin().getX() );
502                pw.print( ' ' );
503                pw.print( swap ? env.getMin().getX() : env.getMin().getY() );
504                pw.print( "</gml:lowerCorner><gml:upperCorner>" );
505                pw.print( swap ? env.getMax().getY() : env.getMax().getX() );
506                pw.print( ' ' );
507                pw.print( swap ? env.getMax().getX() : env.getMax().getY() );
508                pw.print( "</gml:upperCorner></gml:Envelope></gml:boundedBy>" );
509            }
510    
511            // export all contained features
512            for ( int i = 0; i < fc.size(); i++ ) {
513                Feature feature = fc.getFeature( i );
514                String fid = feature.getId();
515                if ( fid != null && !fid.equals( "" ) && this.exportedFeatures.contains( fid ) && !this.suppressXLinkOutput ) {
516                    pw.print( "<gml:featureMember xlink:href=\"#" );
517                    pw.print( fid );
518                    pw.print( "\"/>" );
519                } else {
520                    pw.print( "<gml:featureMember>" );
521                    export( feature, pw, xlinkDepth );
522                    pw.print( "</gml:featureMember>" );
523                }
524            }
525    
526            // export all additional root level features
527            for ( Feature feature : additionalRootLevelFeatures ) {
528                String fid = feature.getId();
529                if ( fid != null && !fid.equals( "" ) && this.exportedFeatures.contains( fid ) ) {
530                    throw new RuntimeException ();
531                }
532                pw.print( "<gml:featureMember>" );
533                export( feature, pw, xlinkDepth );
534                pw.print( "</gml:featureMember>" );
535            }
536    
537            // close the feature collection element
538            pw.print( "</" );
539            pw.print( fc.getName().getPrefixedName() );
540            pw.print( '>' );
541        }
542    
543        /**
544         * Determines features that don't originally belong to the root level of the given <code>FeatureCollection</code>,
545         * but which need to be put there in the output, because they are subfeatures inside of properties that have the
546         * content type "gml:ReferenceType" and wouldn't otherwise be exported anywhere.
547         * 
548         * @param fc
549         * @return features to be added to the root level
550         */
551        private Set<Feature> determineAdditionalRootLevelFeatures( FeatureCollection fc ) {
552    
553            Set<Feature> embeddedFeatures = determineEmbeddedFeatures( fc );
554            Set<Feature> additionalRootFeatures = new HashSet<Feature>();
555            Set<Feature> checkedFeatures = new HashSet<Feature>();
556            for ( int i = 0; i < fc.size(); i++ ) {
557                Feature feature = fc.getFeature( i );
558                determineAdditionalRootLevelFeatures( feature, additionalRootFeatures, embeddedFeatures, checkedFeatures );
559            }
560            return additionalRootFeatures;
561        }
562    
563        /**
564         * Determines the features that are embedded in the given feature collection on all levels.
565         * <p>
566         * NOTE: This *excludes* all subfeatures which are only values of "gml:ReferenceType" properties.
567         * </p>
568         *
569         * @param fc
570         * @return all features that are embedded in the GML representation of the given collection
571         */
572        private Set<Feature> determineEmbeddedFeatures( FeatureCollection fc ) {
573            Set<Feature> features = new HashSet<Feature>( fc.size() );
574            for ( int i = 0; i < fc.size(); i++ ) {
575                determineEmbeddedFeatures( features, fc.getFeature( i ) );
576            }
577            return features;
578        }
579    
580        private void determineEmbeddedFeatures( Set<Feature> exportedFeatures, Feature feature ) {
581            if ( !exportedFeatures.contains( feature ) ) {
582                exportedFeatures.add( feature );
583                for ( FeatureProperty property : feature.getProperties() ) {
584                    Object value = property.getValue();
585                    if ( value instanceof Feature ) {
586                        Feature subFeature = (Feature) value;
587                        if ( feature.getFeatureType() != null && feature.getFeatureType() instanceof MappedFeatureType ) {
588                            MappedFeatureType ft = (MappedFeatureType) feature.getFeatureType();
589                            MappedFeaturePropertyType pt = (MappedFeaturePropertyType) ft.getProperty( property.getName() );
590                            assert pt != null;
591                            if ( !pt.isReferenceType() ) {
592                                determineEmbeddedFeatures( exportedFeatures, subFeature );
593                            }
594                        }
595                    }
596                }
597            }
598        }
599    
600        /**
601         * Determines features that don't originally belong to the root level of the given <code>FeatureCollection</code>,
602         * but which need to be put there in the output, because they are subfeatures inside of properties that have the
603         * content type "gml:ReferenceType".
604         * 
605         * @param feature
606         * @param additionalFeatures
607         *            to be added to the root level
608         * @param embeddedFeatures
609         *            to be added to the root level
610         * @param checkedFeatures
611         *            features that have already been checked
612         */
613        private void determineAdditionalRootLevelFeatures( Feature feature, Set<Feature> additionalFeatures,
614                                                           Set<Feature> embeddedFeatures, Set<Feature> checkedFeatures ) {
615            for ( FeatureProperty property : feature.getProperties() ) {
616                Object value = property.getValue();
617                if ( value instanceof Feature ) {
618                    Feature subFeature = (Feature) value;
619                    if ( !checkedFeatures.contains( subFeature ) ) {
620                        if ( feature.getFeatureType() != null && feature.getFeatureType() instanceof MappedFeatureType ) {
621                            MappedFeatureType ft = (MappedFeatureType) feature.getFeatureType();
622                            MappedFeaturePropertyType pt = (MappedFeaturePropertyType) ft.getProperty( property.getName() );
623                            assert pt != null;
624                            if ( pt.isReferenceType() && !embeddedFeatures.contains( subFeature ) ) {
625                                additionalFeatures.add( (Feature) value );
626                            }
627                        }
628                        checkedFeatures.add( subFeature );
629                        determineAdditionalRootLevelFeatures( subFeature, additionalFeatures, embeddedFeatures, checkedFeatures );
630                    }
631                }
632            }
633        }
634    
635        /**
636         * Exports a {@link FeatureTupleCollection} instance to the passed {@link OutputStream} formatted as GML.
637         * 
638         * @param fc
639         *            feature tuple collection to print/export
640         * @param pw
641         *            target of the printing/export
642         * @throws FeatureException
643         */
644        private void exportTupleCollection( FeatureTupleCollection fc, PrintWriter pw )
645                                throws FeatureException {
646    
647            if ( fc.getId() != null && !"".equals( fc.getId() ) ) {
648                this.exportedFeatures.add( fc.getId() );
649            }
650    
651            // open the feature collection element
652            pw.print( "<" );
653            pw.print( fc.getName().getPrefixedName() );
654    
655            Map<String, String> attributes = fc.getAttributes();
656            for ( Iterator<String> iterator = attributes.keySet().iterator(); iterator.hasNext(); ) {
657                String name = iterator.next();
658                String value = attributes.get( name );
659                pw.print( ' ' );
660                pw.print( name );
661                pw.print( "='" );
662                pw.print( value );
663                pw.print( "'" );
664            }
665    
666            // determine and add namespace bindings
667            Map<String, URI> nsBindings = determineUsedNSBindings( fc );
668            nsBindings.put( "gml", CommonNamespaces.GMLNS );
669            nsBindings.put( "xlink", CommonNamespaces.XLNNS );
670            if ( this.schemaURL != null ) {
671                nsBindings.put( "xsi", CommonNamespaces.XSINS );
672            }
673            appendNSBindings( nsBindings, pw );
674    
675            // add schema reference (if available)
676            if ( this.schemaURL != null && fc.size() > 0 ) {
677                pw.print( " xsi:schemaLocation=\"" + fc.getTuple( 0 )[0].getName().getNamespace() + " " );
678                pw.print( this.schemaURL + " " );
679                pw.print( wfsSchemaBinding + "\"" );
680            }
681            pw.print( '>' );
682    
683            Envelope env = null;
684            try {
685                env = fc.getBoundedBy();
686            } catch ( GeometryException e ) {
687                // omit gml:boundedBy-element if featureCollection contains features
688                // with different SRS (and their envelopes cannot be merged)
689            }
690            if ( env != null ) {
691                pw.print( "<gml:boundedBy><gml:Envelope" );
692                if ( env.getCoordinateSystem() != null ) {
693                    pw.print( " srsName='" + env.getCoordinateSystem().getPrefixedName() + "'" );
694                }
695    
696                boolean swap = swap( env );
697    
698                pw.print( "><gml:lowerCorner>" );
699                pw.print( swap ? env.getMin().getY() : env.getMin().getX() );
700                pw.print( ' ' );
701                pw.print( swap ? env.getMin().getX() : env.getMin().getY() );
702                pw.print( "</gml:lowerCorner><gml:upperCorner>" );
703                pw.print( swap ? env.getMax().getY() : env.getMax().getX() );
704                pw.print( ' ' );
705                pw.print( swap ? env.getMax().getX() : env.getMax().getY() );
706                pw.print( "</gml:upperCorner></gml:Envelope></gml:boundedBy>" );
707            }
708    
709            // export all contained feature tuples
710            for ( int i = 0; i < fc.numTuples(); i++ ) {
711                Feature[] features = fc.getTuple( i );
712                pw.print( "<gml:featureTuple>" );
713                for ( Feature feature : features ) {
714                    export( feature, pw, xlinkDepth );
715                }
716                pw.print( "</gml:featureTuple>" );
717            }
718    
719            // close the feature collection element
720            pw.print( "</" );
721            pw.print( fc.getName().getPrefixedName() );
722            pw.print( '>' );
723        }
724    
725        /**
726         * Determines the namespace bindings that are used in the feature collection.
727         * <p>
728         * NOTE: Currently only the bindings for the feature collection's root element and the contained features are
729         * considered. If a subfeature uses another bindings, this binding will be missing in the XML.
730         * 
731         * @param fc
732         *            feature collection
733         * @return the namespace bindings.
734         */
735        private Map<String, URI> determineUsedNSBindings( FeatureCollection fc ) {
736    
737            Map<String, URI> nsBindings = new HashMap<String, URI>();
738    
739            // process feature collection element
740            QualifiedName name = fc.getName();
741            nsBindings.put( name.getPrefix(), name.getNamespace() );
742    
743            if ( fc instanceof FeatureTupleCollection ) {
744                // process contained feature tuples
745                FeatureTupleCollection ftc = (FeatureTupleCollection) fc;
746                for ( int i = 0; i < ftc.numTuples(); i++ ) {
747                    Feature[] features = ftc.getTuple( i );
748                    for ( Feature feature : features ) {
749                        name = feature.getName();
750                        nsBindings.put( name.getPrefix(), name.getNamespace() );
751                    }
752                }
753            } else {
754                // process contained features
755                // for ( int i = 0; i < fc.size(); i++ ) {
756                // name = fc.getFeature( i ).getName();
757                // nsBindings.put( name.getPrefix(), name.getNamespace() );
758                // }
759                for ( int i = 0; i < fc.size(); i++ ) {
760                    Feature feature = fc.getFeature( i );
761                    if ( feature != null ) {
762                        nsBindings = determineUsedNSBindings( feature, nsBindings );
763                    }
764                }
765            }
766    
767            return nsBindings;
768        }
769    
770        /**
771         * Determines the namespace bindings that are used in the feature and it's properties
772         * 
773         * @param feature
774         *            feature
775         * @param nsBindings
776         *            to add to.
777         * @return the namespace bindings
778         */
779        private Map<String, URI> determineUsedNSBindings( Feature feature, Map<String, URI> nsBindings ) {
780            if ( nsBindings == null ) {
781                nsBindings = new HashMap<String, URI>();
782            }
783            if ( feature != null ) {
784                // process feature element
785                QualifiedName qName = feature.getName();
786                if ( qName != null ) {
787                    String prefix = qName.getPrefix();
788                    URI ns = qName.getNamespace();
789                    if ( ns != null && !"".equals( ns.toASCIIString().trim() ) ) {
790                        LOG.logDebug( "Adding qName: " + qName );
791                        nsBindings.put( prefix, ns );
792                    }
793                }
794    
795                // now check for properties which use a namespace
796                FeatureProperty[] featureProperties = feature.getProperties();
797                if ( featureProperties != null ) {
798                    for ( FeatureProperty fp : featureProperties ) {
799                        if ( fp != null ) {
800                            QualifiedName fpName = fp.getName();
801                            if ( fpName != null ) {
802                                String prefix = fpName.getPrefix();
803                                if ( prefix != null && !"".equals( prefix.trim() ) ) {
804                                    if ( nsBindings.get( prefix ) == null ) {
805                                        URI ns = fpName.getNamespace();
806                                        if ( ns != null && !"".equals( ns.toASCIIString().trim() ) ) {
807                                            LOG.logDebug( "Adding qname: " + fpName );
808                                            nsBindings.put( prefix, ns );
809                                        }
810                                    }
811                                }
812                            }
813                            // Object value = fp.getValue();
814                            // if ( value instanceof Feature ) {
815                            // determineUsedNSBindings( (Feature) value, nsBindings );
816                            // }
817                        }
818                    }
819                }
820            }
821    
822            return nsBindings;
823        }
824    
825        /**
826         * Determines the namespace bindings that are used in the feature.
827         * <p>
828         * NOTE: Currently only the bindings for the feature's root element and the contained features are considered. If a
829         * subfeature uses another bindings, this binding will be missing in the XML.
830         * 
831         * @param feature
832         *            feature
833         * @return the namespace bindings
834         */
835        private Map<String, URI> determineUsedNSBindings( Feature feature ) {
836    
837            // reset the counter.
838            Map<String, URI> nsBindings = new HashMap<String, URI>();
839    
840            return determineUsedNSBindings( feature, nsBindings );
841            // // process feature element
842            // QualifiedName name = feature.getName();
843            // nsBindings.put( name.getPrefix(), name.getNamespace() );
844    
845        }
846    
847        /**
848         * Appends the given namespace bindings to the PrintWriter.
849         * 
850         * @param bindings
851         *            namespace bindings to append
852         * @param pw
853         *            PrintWriter to write to
854         */
855        private void appendNSBindings( Map<String, URI> bindings, PrintWriter pw ) {
856    
857            Iterator<String> prefixIter = bindings.keySet().iterator();
858            while ( prefixIter.hasNext() ) {
859                String prefix = prefixIter.next();
860                URI nsURI = bindings.get( prefix );
861                if ( prefix == null ) {
862                    pw.print( " xmlns=\"" );
863                    pw.print( nsURI );
864                    pw.print( '\"' );
865                } else {
866                    pw.print( " xmlns:" );
867                    pw.print( prefix );
868                    pw.print( "=\"" );
869                    pw.print( nsURI );
870                    pw.print( '\"' );
871                }
872            }
873            // if more then one default namespaces were defined, each feature must (re)-determine the
874            // default ns.
875            this.nsBindingsExported = true;// ( defaultNamespaceCounter == 0 );
876        }
877    
878        /**
879         * Exports an instance of a <code>Feature</code> to the passed <code>OutputStream</code> formatted as GML. Uses the
880         * deegree system character set for the XML header encoding information.
881         * 
882         * @param feature
883         *            feature to export
884         * @param os
885         *            output stream to write to
886         * 
887         * @throws IOException
888         * @throws FeatureException
889         */
890        public void export( Feature feature, OutputStream os )
891                                throws IOException, FeatureException {
892            export( feature, os, CharsetUtils.getSystemCharset() );
893        }
894    
895        /**
896         * Exports a <code>Feature</code> instance to the passed <code>OutputStream</code> formatted as GML.
897         * 
898         * @param feature
899         *            feature to export
900         * @param os
901         *            output stream to write to
902         * @param charsetName
903         *            name of the used charset/encoding (for the XML header)
904         * @throws IOException
905         * @throws FeatureException
906         */
907        public void export( Feature feature, OutputStream os, String charsetName )
908                                throws IOException, FeatureException {
909    
910            PrintWriter pw = new PrintWriter( new OutputStreamWriter( os, charsetName ) );
911            pw.println( "<?xml version=\"1.0\" encoding=\"" + charsetName + "\"?>" );
912            export( feature, pw, xlinkDepth );
913            pw.close();
914        }
915    
916        /**
917         * Exports a <code>Feature</code> instance to the passed <code>PrintWriter</code> as GML.
918         * 
919         * @param feature
920         *            feature to export
921         * @param pw
922         *            PrintWriter to write to
923         * @throws FeatureException
924         */
925        private void export( Feature feature, PrintWriter pw, int currentDepth )
926                                throws FeatureException {
927    
928            boolean isPseudoFt = false;
929            if ( feature.getFeatureType() != null && feature.getFeatureType() instanceof MappedFeatureType ) {
930                isPseudoFt = ( (MappedFeatureType) feature.getFeatureType() ).isPseudoFeatureType();
931            }
932    
933            QualifiedName ftName = feature.getName();
934            String fid = isPseudoFt ? null : feature.getId();
935    
936            if ( this.suppressXLinkOutput && fid != null && !"".equals( fid ) ) {
937                if ( this.localFeatures.contains( fid ) ) {
938                    String msg = Messages.format( "ERROR_CYLIC_FEATURE", fid );
939                    throw new FeatureException( msg );
940                }
941                this.localFeatures.add( fid );
942            }
943    
944            // open feature element (add gml:id attribute if feature has an id)
945            pw.print( '<' );
946            pw.print( ftName.getPrefixedName() );
947            if ( fid != null && !fid.equals( "" ) ) {
948                this.exportedFeatures.add( fid );
949                pw.print( " gml:id=\"" );
950                pw.print( fid );
951                pw.print( '\"' );
952            }
953    
954            // determine and add namespace bindings
955    
956            if ( !this.nsBindingsExported ) {
957    
958                Map<String, URI> nsBindings = determineUsedNSBindings( feature );
959                nsBindings.put( "gml", CommonNamespaces.GMLNS );
960                nsBindings.put( "xlink", CommonNamespaces.XLNNS );
961                if ( this.schemaURL != null ) {
962                    nsBindings.put( "xsi", CommonNamespaces.XSINS );
963                }
964                appendNSBindings( nsBindings, pw );
965            }
966    
967            pw.print( '>' );
968    
969            // to get the order right (gml default attributes come BEFORE the envelope, stupid...)
970            boolean boundedByExported = false;
971    
972            // export all properties of the feature
973            FeatureProperty[] properties = feature.getProperties();
974    
975            int geomProperties = -1; // this counter is actually counted up where geometries are exported (and it's used for
976            // outputting geometry ids)
977            // that's why the methods now all take the counter and return it also
978    
979            for ( int i = 0; i < properties.length; i++ ) {
980                boolean exportEnv = !isPseudoFt && !boundedByExported;
981                QualifiedName qn = properties[i].getName();
982                String ln = qn.getLocalName();
983                exportEnv = exportEnv
984                            && !( qn.getNamespace().equals( GMLNS ) && ( ln.equals( "description" ) || ln.equals( "name" ) ) );
985    
986                if ( exportEnv ) {
987                    exportBoundedBy( feature, pw );
988                    boundedByExported = true;
989                }
990    
991                if ( properties[i] != null && properties[i].getValue() != null ) {
992                    geomProperties = exportProperty( feature, properties[i], pw, geomProperties, currentDepth );
993                }
994            }
995            
996            if (properties.length == 0) {
997                boolean exportEnv = !isPseudoFt && !boundedByExported;
998                if ( exportEnv ) {
999                    exportBoundedBy( feature, pw );
1000                    boundedByExported = true;
1001                }
1002            }
1003    
1004            // close feature element
1005            pw.print( "</" );
1006            pw.print( ftName.getPrefixedName() );
1007            pw.println( '>' );
1008    
1009            if ( this.suppressXLinkOutput || fid != null ) {
1010                this.localFeatures.remove( fid );
1011            }
1012        }
1013    
1014        private static void exportBoundedBy( Feature feature, PrintWriter pw ) {
1015            try {
1016                Envelope env = null;
1017                if ( ( env = feature.getBoundedBy() ) != null ) {
1018                    pw.print( "<gml:boundedBy><gml:Envelope" );
1019                    if ( env.getCoordinateSystem() != null ) {
1020                        pw.print( " srsName='" + env.getCoordinateSystem().getPrefixedName() + "'" );
1021                    }
1022    
1023                    boolean swap = swap( env );
1024    
1025                    pw.print( "><gml:lowerCorner>" );
1026                    pw.print( swap ? env.getMin().getY() : env.getMin().getX() );
1027                    pw.print( ' ' );
1028                    pw.print( swap ? env.getMin().getX() : env.getMin().getY() );
1029                    pw.print( "</gml:lowerCorner><gml:upperCorner>" );
1030                    pw.print( swap ? env.getMax().getY() : env.getMax().getX() );
1031                    pw.print( ' ' );
1032                    pw.print( swap ? env.getMax().getX() : env.getMax().getY() );
1033                    pw.print( "</gml:upperCorner></gml:Envelope></gml:boundedBy>" );
1034                }
1035            } catch ( GeometryException e ) {
1036                LOG.logError( e.getMessage(), e );
1037            }
1038    
1039        }
1040    
1041        /**
1042         * Exports a <code>FeatureProperty</code> instance to the passed <code>PrintWriter</code> as GML.
1043         * 
1044         * @param feature
1045         *            feature that the property belongs to
1046         * @param property
1047         *            property to export
1048         * @param pw
1049         *            PrintWriter to write to
1050         * @param geomProperties
1051         *            counter to indicate the number of the current geometry property
1052         * @param currentDepth
1053         *            counter to indicate how many levels of xlinks have already been resolved
1054         * @throws FeatureException
1055         */
1056        private int exportProperty( Feature feature, FeatureProperty property, PrintWriter pw, int geomProperties,
1057                                    int currentDepth )
1058                                throws FeatureException {
1059    
1060            QualifiedName propertyName = property.getName();
1061            Object value = property.getValue();
1062            Integer d = xlinkPropertyNames.get( property.getName().getPrefixedName() );
1063            int customDepth = d == null ? currentDepth : d;
1064            boolean isReferenceType = false;
1065            if ( feature.getFeatureType() != null && feature.getFeatureType() instanceof MappedFeatureType ) {
1066                MappedFeatureType ft = (MappedFeatureType) feature.getFeatureType();
1067                if ( ft.getProperty( property.getName() ) instanceof MappedFeaturePropertyType ) {
1068                    MappedFeaturePropertyType pt = (MappedFeaturePropertyType) ft.getProperty( property.getName() );
1069                    isReferenceType = pt.isReferenceType();
1070                }
1071            }
1072    
1073            if ( value instanceof Feature ) {
1074                Feature subfeature = (Feature) value;
1075    
1076                if ( isReferenceType
1077                     || ( subfeature.getId() != null && !subfeature.getId().equals( "" )
1078                          && exportedFeatures.contains( subfeature.getId() ) && !this.suppressXLinkOutput ) ) {
1079                    if ( exportedFeatures.contains( subfeature.getId() ) ) {
1080                        pw.print( "<!-- xlink:href=\"#" );
1081                        pw.print( subfeature.getId() );
1082                        pw.print( "\" -->" );
1083                        pw.print( '<' );
1084                        pw.print( propertyName.getPrefixedName() );
1085                        pw.print( " xlink:href=\"#" );
1086                        pw.print( subfeature.getId() );
1087                        pw.print( "\"/>" );
1088                    } else {
1089                        pw.print( '<' );
1090                        pw.print( propertyName.getPrefixedName() );
1091                        if ( isReferenceType || currentDepth == 0 || customDepth == 0 ) {
1092                            pw.print( " xlink:href=\"#" );
1093                            pw.print( subfeature.getId() );
1094                            pw.print( "\"" );
1095                        }
1096                        pw.print( ">" );
1097                        if ( !isReferenceType && ( currentDepth != 0 || customDepth != 0 ) ) {
1098                            pw.print( "<!-- gml:id=\"" );
1099                            pw.print( subfeature.getId() );
1100                            pw.print( "\" -->" );
1101                            export( subfeature, pw, currentDepth - 1 );
1102                        }
1103                        pw.print( "</" );
1104                        pw.print( propertyName.getPrefixedName() );
1105                        pw.print( ">" );
1106                    }
1107                } else {
1108                    pw.print( '<' );
1109                    pw.print( propertyName.getPrefixedName() );
1110                    if ( currentDepth == 0 && customDepth == 0 ) {
1111                        pw.print( " xlink:href=\"" );
1112                        pw.print( baseUrl );
1113                        pw.print( "?request=GetGmlObject&amp;version=1.1.0&amp;service=WFS&amp;objectid=" );
1114                        pw.print( subfeature.getId() );
1115                        pw.print( "\"/>" );
1116                    } else {
1117                        pw.print( '>' );
1118                        pw.print( "<!-- xlink:href=\"#" );
1119                        pw.print( subfeature.getId() );
1120                        pw.print( "\" -->" );
1121                        exportPropertyValue( subfeature, pw, FEATURE, null, 0, currentDepth - 1 );
1122                        pw.print( "</" );
1123                        pw.print( propertyName.getPrefixedName() );
1124                        pw.print( '>' );
1125                    }
1126                }
1127            } else if ( value instanceof URL ) {
1128                if ( currentDepth == 0 || isReferenceType || customDepth == 0 ) {
1129                    pw.print( '<' );
1130                    pw.print( propertyName.getPrefixedName() );
1131                    pw.print( " xlink:href=\"" );
1132                    pw.print( escape( value.toString() ) );
1133                    pw.print( "\"/>" );
1134                } else {
1135                    pw.print( '<' );
1136                    pw.print( propertyName.getPrefixedName() );
1137                    pw.print( ">" );
1138                    pw.print( "<!-- xlink:href=\"" );
1139                    pw.print( value );
1140                    pw.print( "\" -->" );
1141    
1142                    try {
1143                        GMLFeatureDocument doc = new GMLFeatureDocument();
1144                        URLConnection conn = ( (URL) value ).openConnection();
1145                        conn.setReadTimeout( 5000 );
1146                        conn.setConnectTimeout( 1000 );
1147                        doc.load( conn.getInputStream(), ( (URL) value ).toExternalForm() );
1148                        export( doc.parseFeature(), pw, currentDepth - 1 );
1149                        pw.print( "</" );
1150                        pw.print( propertyName.getPrefixedName() );
1151                        pw.print( ">" );
1152                    } catch ( IOException e ) {
1153                        e.printStackTrace();
1154                        LOG.logDebug( "Stack trace:", e );
1155                        throw new FeatureException( org.deegree.i18n.Messages.get( "DATASTORE_XLINK_IO_RETRIEVE",
1156                                                                                   e.getLocalizedMessage() ) );
1157                    } catch ( SAXException e ) {
1158                        e.printStackTrace();
1159                        LOG.logDebug( "Stack trace:", e );
1160                        throw new FeatureException( org.deegree.i18n.Messages.get( "DATASTORE_XLINK_PARSE_ERROR",
1161                                                                                   e.getLocalizedMessage() ) );
1162                    } catch ( XMLParsingException e ) {
1163                        e.printStackTrace();
1164                        LOG.logDebug( "Stack trace:", e );
1165                        throw new FeatureException( org.deegree.i18n.Messages.get( "DATASTORE_XLINK_PARSE_ERROR",
1166                                                                                   e.getLocalizedMessage() ) );
1167                    } catch ( UnknownCRSException e ) {
1168                        e.printStackTrace();
1169                        LOG.logDebug( "Stack trace:", e );
1170                        throw new FeatureException( org.deegree.i18n.Messages.get( "DATASTORE_XLINK_PARSE_ERROR",
1171                                                                                   e.getLocalizedMessage() ) );
1172                    }
1173    
1174                }
1175            } else {
1176                pw.print( '<' );
1177                pw.print( propertyName.getPrefixedName() );
1178                pw.print( '>' );
1179                if ( value != null ) {
1180                    FeatureType ft = feature.getFeatureType();
1181                    PropertyType pt = ft.getProperty( property.getName() );
1182                    if ( pt.getType() == Types.ANYTYPE ) {
1183                        pw.print( value );
1184                    } else {
1185                        geomProperties = exportPropertyValue( value, pw, pt.getType(), printGeometryIds ? feature.getId()
1186                                                                                                       : null,
1187                                                              geomProperties, currentDepth - 1 );
1188                    }
1189                }
1190                pw.print( "</" );
1191                pw.print( propertyName.getPrefixedName() );
1192                pw.print( '>' );
1193            }
1194    
1195            return geomProperties;
1196        }
1197    
1198        /**
1199         * Exports the value of a property to the passed <code>PrintWriter</code> as GML.
1200         * 
1201         * TODO make available and use property type information to determine correct output format (e.g. xs:date, xs:time,
1202         * xs:dateTime are all represented using Date objects and cannot be differentiated at the moment)
1203         * 
1204         * @param value
1205         *            property value to export
1206         * @param pw
1207         *            PrintWriter to write to
1208         * @param type
1209         *            the Types.java type code
1210         * @param geomProperties
1211         *            counter to indicate the number of the current geometry property
1212         * @param currentDepth
1213         *            counter to indicate how many levels of xlinks have already been resolved
1214         * @throws FeatureException
1215         */
1216        private int exportPropertyValue( Object value, PrintWriter pw, int type, String id, int geomProperties,
1217                                         int currentDepth )
1218                                throws FeatureException {
1219            if ( value instanceof Feature ) {
1220                export( (Feature) value, pw, currentDepth );
1221            } else if ( value instanceof Feature[] ) {
1222                Feature[] features = (Feature[]) value;
1223                for ( int i = 0; i < features.length; i++ ) {
1224                    export( features[i], pw, currentDepth );
1225                }
1226            } else if ( value instanceof Envelope ) {
1227                exportEnvelope( (Envelope) value, pw );
1228            } else if ( value instanceof FeatureCollection ) {
1229                export( (FeatureCollection) value, pw, currentDepth );
1230            } else if ( value instanceof Geometry ) {
1231                id = id == null ? null : ( id + "_GEOM_" + ++geomProperties );
1232                exportGeometry( (Geometry) value, pw, id );
1233            } else if ( value instanceof Date ) {
1234    
1235                switch ( type ) {
1236                case DATE: {
1237                    pw.print( dateFormatter.format( (Date) value ) );
1238                    break;
1239                }
1240                case TIME: {
1241                    pw.print( timeFormatter.format( (Date) value ) );
1242                    break;
1243                }
1244                case TIMESTAMP: {
1245                    pw.print( getISOFormattedTime( (Date) value ) );
1246                    break;
1247                }
1248                }
1249    
1250            } else if ( value instanceof Calendar ) {
1251                pw.print( TimeTools.getISOFormattedTime( (Calendar) value ) );
1252            } else if ( value instanceof Timestamp ) {
1253                pw.print( TimeTools.getISOFormattedTime( (Timestamp) value ) );
1254            } else if ( value instanceof java.sql.Date ) {
1255                pw.print( TimeTools.getISOFormattedTime( (java.sql.Date) value ) );
1256            } else if ( value instanceof Integer || value instanceof Long || value instanceof Float
1257                        || value instanceof Double || value instanceof BigDecimal ) {
1258                pw.print( value.toString() );
1259            } else if ( value instanceof String ) {
1260                StringBuffer sb = DOMPrinter.validateCDATA( (String) value );
1261                pw.print( sb );
1262            } else if ( value instanceof Boolean ) {
1263                pw.print( value );
1264            } else {
1265                LOG.logInfo( "Unhandled property class '" + value.getClass() + "' in GMLFeatureAdapter." );
1266                StringBuffer sb = DOMPrinter.validateCDATA( value.toString() );
1267                pw.print( sb );
1268            }
1269    
1270            return geomProperties;
1271        }
1272    
1273        /**
1274         * prints the passed geometry to the also passed PrintWriter formatted as GML
1275         * 
1276         * @param geo
1277         *            geometry to print/extport
1278         * @param pw
1279         *            target of the printing/export
1280         * @throws FeatureException
1281         */
1282        private void exportGeometry( Geometry geo, PrintWriter pw, String id )
1283                                throws FeatureException {
1284            try {
1285                GMLGeometryAdapter.export( geo, pw, id );
1286            } catch ( Exception e ) {
1287                LOG.logError( "", e );
1288                throw new FeatureException( "Could not export geometry to GML: " + e.getMessage(), e );
1289            }
1290        }
1291    
1292        /**
1293         * prints the passed geometry to the also passed PrintWriter formatted as GML
1294         * 
1295         * @param geo
1296         *            geometry to print/extport
1297         * @param pw
1298         *            target of the printing/export
1299         * @throws FeatureException
1300         */
1301        private void exportEnvelope( Envelope geo, PrintWriter pw )
1302                                throws FeatureException {
1303            try {
1304                pw.print( GMLGeometryAdapter.exportAsBox( geo ) );
1305            } catch ( Exception e ) {
1306                throw new FeatureException( "Could not export envelope to GML: " + e.getMessage(), e );
1307            }
1308        }
1309    }