001    //$HeadURL: http://svn.wald.intevation.org/svn/deegree/base/trunk/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: apoth $
117     * 
118     * @version $Revision: 30885 $, $Date: 2011-05-23 10:12:46 +0200 (Mo, 23 Mai 2011) $
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            if ( attributes != null ) {
456                for ( Iterator<String> iterator = attributes.keySet().iterator(); iterator.hasNext(); ) {
457                    String name = iterator.next();
458                    String value = attributes.get( name );
459                    pw.print( ' ' );
460                    pw.print( name );
461                    pw.print( "='" );
462                    pw.print( value );
463                    pw.print( "'" );
464                }
465            }
466    
467            // determine and add namespace bindings
468            Map<String, URI> nsBindings = determineUsedNSBindings( fc );
469            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
470                LOG.logDebug( nsBindings.toString() );
471            }
472            nsBindings.put( "gml", CommonNamespaces.GMLNS );
473            nsBindings.put( "xlink", CommonNamespaces.XLNNS );
474            if ( this.schemaURL != null ) {
475                nsBindings.put( "xsi", CommonNamespaces.XSINS );
476            }
477            appendNSBindings( nsBindings, pw );
478    
479            // add schema reference (if available)
480            if ( this.schemaURL != null && fc.size() > 0 ) {
481                pw.print( " xsi:schemaLocation=\"" + fc.getFeature( 0 ).getName().getNamespace() + " " );
482                pw.print( this.schemaURL + " " );
483                pw.print( wfsSchemaBinding + "\"" );
484            }
485            pw.print( '>' );
486    
487            Envelope env = null;
488            try {
489                env = fc.getBoundedBy();
490            } catch ( GeometryException e ) {
491                // omit gml:boundedBy-element if featureCollection contains features
492                // with different SRS (and their envelopes cannot be merged)
493            }
494            if ( env != null ) {
495                pw.print( "<gml:boundedBy><gml:Envelope" );
496                if ( env.getCoordinateSystem() != null ) {
497                    pw.print( " srsName='" + env.getCoordinateSystem().getPrefixedName() + "'" );
498                }
499    
500                boolean swap = swap( env );
501    
502                pw.print( "><gml:lowerCorner>" );
503                pw.print( swap ? env.getMin().getY() : env.getMin().getX() );
504                pw.print( ' ' );
505                pw.print( swap ? env.getMin().getX() : env.getMin().getY() );
506                pw.print( "</gml:lowerCorner><gml:upperCorner>" );
507                pw.print( swap ? env.getMax().getY() : env.getMax().getX() );
508                pw.print( ' ' );
509                pw.print( swap ? env.getMax().getX() : env.getMax().getY() );
510                pw.print( "</gml:upperCorner></gml:Envelope></gml:boundedBy>" );
511            }
512    
513            // export all contained features
514            for ( int i = 0; i < fc.size(); i++ ) {
515                Feature feature = fc.getFeature( i );
516                String fid = feature.getId();
517                if ( fid != null && !fid.equals( "" ) && this.exportedFeatures.contains( fid ) && !this.suppressXLinkOutput ) {
518                    pw.print( "<gml:featureMember xlink:href=\"#" );
519                    pw.print( fid );
520                    pw.print( "\"/>" );
521                } else {
522                    pw.print( "<gml:featureMember>" );
523                    export( feature, pw, xlinkDepth );
524                    pw.print( "</gml:featureMember>" );
525                }
526            }
527    
528            // export all additional root level features
529            for ( Feature feature : additionalRootLevelFeatures ) {
530                String fid = feature.getId();
531                if ( fid != null && !fid.equals( "" ) && this.exportedFeatures.contains( fid ) ) {
532                    throw new RuntimeException();
533                }
534                pw.print( "<gml:featureMember>" );
535                export( feature, pw, xlinkDepth );
536                pw.print( "</gml:featureMember>" );
537            }
538    
539            // close the feature collection element
540            pw.print( "</" );
541            pw.print( fc.getName().getPrefixedName() );
542            pw.print( '>' );
543        }
544    
545        /**
546         * Determines features that don't originally belong to the root level of the given <code>FeatureCollection</code>,
547         * but which need to be put there in the output, because they are subfeatures inside of properties that have the
548         * content type "gml:ReferenceType" and wouldn't otherwise be exported anywhere.
549         * 
550         * @param fc
551         * @return features to be added to the root level
552         */
553        private Set<Feature> determineAdditionalRootLevelFeatures( FeatureCollection fc ) {
554    
555            Set<Feature> embeddedFeatures = determineEmbeddedFeatures( fc );
556            Set<Feature> additionalRootFeatures = new HashSet<Feature>();
557            Set<Feature> checkedFeatures = new HashSet<Feature>();
558            for ( int i = 0; i < fc.size(); i++ ) {
559                Feature feature = fc.getFeature( i );
560                determineAdditionalRootLevelFeatures( feature, additionalRootFeatures, embeddedFeatures, checkedFeatures );
561            }
562            return additionalRootFeatures;
563        }
564    
565        /**
566         * Determines the features that are embedded in the given feature collection on all levels.
567         * <p>
568         * NOTE: This *excludes* all subfeatures which are only values of "gml:ReferenceType" properties.
569         * </p>
570         * 
571         * @param fc
572         * @return all features that are embedded in the GML representation of the given collection
573         */
574        private Set<Feature> determineEmbeddedFeatures( FeatureCollection fc ) {
575            Set<Feature> features = new HashSet<Feature>( fc.size() );
576            for ( int i = 0; i < fc.size(); i++ ) {
577                determineEmbeddedFeatures( features, fc.getFeature( i ) );
578            }
579            return features;
580        }
581    
582        private void determineEmbeddedFeatures( Set<Feature> exportedFeatures, Feature feature ) {
583            if ( !exportedFeatures.contains( feature ) ) {
584                exportedFeatures.add( feature );
585                for ( FeatureProperty property : feature.getProperties() ) {
586                    Object value = property.getValue();
587                    if ( value instanceof Feature ) {
588                        Feature subFeature = (Feature) value;
589                        if ( feature.getFeatureType() != null && feature.getFeatureType() instanceof MappedFeatureType ) {
590                            MappedFeatureType ft = (MappedFeatureType) feature.getFeatureType();
591                            MappedFeaturePropertyType pt = (MappedFeaturePropertyType) ft.getProperty( property.getName() );
592                            assert pt != null;
593                            if ( !pt.isReferenceType() ) {
594                                determineEmbeddedFeatures( exportedFeatures, subFeature );
595                            }
596                        }
597                    }
598                }
599            }
600        }
601    
602        /**
603         * Determines features that don't originally belong to the root level of the given <code>FeatureCollection</code>,
604         * but which need to be put there in the output, because they are subfeatures inside of properties that have the
605         * content type "gml:ReferenceType".
606         * 
607         * @param feature
608         * @param additionalFeatures
609         *            to be added to the root level
610         * @param embeddedFeatures
611         *            to be added to the root level
612         * @param checkedFeatures
613         *            features that have already been checked
614         */
615        private void determineAdditionalRootLevelFeatures( Feature feature, Set<Feature> additionalFeatures,
616                                                           Set<Feature> embeddedFeatures, Set<Feature> checkedFeatures ) {
617            for ( FeatureProperty property : feature.getProperties() ) {
618                Object value = property.getValue();
619                if ( value instanceof Feature ) {
620                    Feature subFeature = (Feature) value;
621                    if ( !checkedFeatures.contains( subFeature ) ) {
622                        if ( feature.getFeatureType() != null && feature.getFeatureType() instanceof MappedFeatureType ) {
623                            MappedFeatureType ft = (MappedFeatureType) feature.getFeatureType();
624                            MappedFeaturePropertyType pt = (MappedFeaturePropertyType) ft.getProperty( property.getName() );
625                            assert pt != null;
626                            if ( pt.isReferenceType() && !embeddedFeatures.contains( subFeature ) ) {
627                                additionalFeatures.add( (Feature) value );
628                            }
629                        }
630                        checkedFeatures.add( subFeature );
631                        determineAdditionalRootLevelFeatures( subFeature, additionalFeatures, embeddedFeatures,
632                                                              checkedFeatures );
633                    }
634                }
635            }
636        }
637    
638        /**
639         * Exports a {@link FeatureTupleCollection} instance to the passed {@link OutputStream} formatted as GML.
640         * 
641         * @param fc
642         *            feature tuple collection to print/export
643         * @param pw
644         *            target of the printing/export
645         * @throws FeatureException
646         */
647        private void exportTupleCollection( FeatureTupleCollection fc, PrintWriter pw )
648                                throws FeatureException {
649    
650            if ( fc.getId() != null && !"".equals( fc.getId() ) ) {
651                this.exportedFeatures.add( fc.getId() );
652            }
653    
654            // open the feature collection element
655            pw.print( "<" );
656            pw.print( fc.getName().getPrefixedName() );
657    
658            Map<String, String> attributes = fc.getAttributes();
659            if ( attributes != null ) {
660                for ( Iterator<String> iterator = attributes.keySet().iterator(); iterator.hasNext(); ) {
661                    String name = iterator.next();
662                    String value = attributes.get( name );
663                    pw.print( ' ' );
664                    pw.print( name );
665                    pw.print( "='" );
666                    pw.print( value );
667                    pw.print( "'" );
668                }
669            }
670    
671            // determine and add namespace bindings
672            Map<String, URI> nsBindings = determineUsedNSBindings( fc );
673            nsBindings.put( "gml", CommonNamespaces.GMLNS );
674            nsBindings.put( "xlink", CommonNamespaces.XLNNS );
675            if ( this.schemaURL != null ) {
676                nsBindings.put( "xsi", CommonNamespaces.XSINS );
677            }
678            appendNSBindings( nsBindings, pw );
679    
680            // add schema reference (if available)
681            if ( this.schemaURL != null && fc.size() > 0 ) {
682                pw.print( " xsi:schemaLocation=\"" + fc.getTuple( 0 )[0].getName().getNamespace() + " " );
683                pw.print( this.schemaURL + " " );
684                pw.print( wfsSchemaBinding + "\"" );
685            }
686            pw.print( '>' );
687    
688            Envelope env = null;
689            try {
690                env = fc.getBoundedBy();
691            } catch ( GeometryException e ) {
692                // omit gml:boundedBy-element if featureCollection contains features
693                // with different SRS (and their envelopes cannot be merged)
694            }
695            if ( env != null ) {
696                pw.print( "<gml:boundedBy><gml:Envelope" );
697                if ( env.getCoordinateSystem() != null ) {
698                    pw.print( " srsName='" + env.getCoordinateSystem().getPrefixedName() + "'" );
699                }
700    
701                boolean swap = swap( env );
702    
703                pw.print( "><gml:lowerCorner>" );
704                pw.print( swap ? env.getMin().getY() : env.getMin().getX() );
705                pw.print( ' ' );
706                pw.print( swap ? env.getMin().getX() : env.getMin().getY() );
707                pw.print( "</gml:lowerCorner><gml:upperCorner>" );
708                pw.print( swap ? env.getMax().getY() : env.getMax().getX() );
709                pw.print( ' ' );
710                pw.print( swap ? env.getMax().getX() : env.getMax().getY() );
711                pw.print( "</gml:upperCorner></gml:Envelope></gml:boundedBy>" );
712            }
713    
714            // export all contained feature tuples
715            for ( int i = 0; i < fc.numTuples(); i++ ) {
716                Feature[] features = fc.getTuple( i );
717                pw.print( "<gml:featureTuple>" );
718                for ( Feature feature : features ) {
719                    export( feature, pw, xlinkDepth );
720                }
721                pw.print( "</gml:featureTuple>" );
722            }
723    
724            // close the feature collection element
725            pw.print( "</" );
726            pw.print( fc.getName().getPrefixedName() );
727            pw.print( '>' );
728        }
729    
730        /**
731         * Determines the namespace bindings that are used in the feature collection.
732         * <p>
733         * NOTE: Currently only the bindings for the feature collection's root element and the contained features are
734         * considered. If a subfeature uses another bindings, this binding will be missing in the XML.
735         * 
736         * @param fc
737         *            feature collection
738         * @return the namespace bindings.
739         */
740        private Map<String, URI> determineUsedNSBindings( FeatureCollection fc ) {
741    
742            Map<String, URI> nsBindings = new HashMap<String, URI>();
743    
744            // process feature collection element
745            QualifiedName name = fc.getName();
746            nsBindings.put( name.getPrefix(), name.getNamespace() );
747    
748            if ( fc instanceof FeatureTupleCollection ) {
749                // process contained feature tuples
750                FeatureTupleCollection ftc = (FeatureTupleCollection) fc;
751                for ( int i = 0; i < ftc.numTuples(); i++ ) {
752                    Feature[] features = ftc.getTuple( i );
753                    for ( Feature feature : features ) {
754                        name = feature.getName();
755                        nsBindings.put( name.getPrefix(), name.getNamespace() );
756                    }
757                }
758            } else {
759                // process contained features
760                // for ( int i = 0; i < fc.size(); i++ ) {
761                // name = fc.getFeature( i ).getName();
762                // nsBindings.put( name.getPrefix(), name.getNamespace() );
763                // }
764                for ( int i = 0; i < fc.size(); i++ ) {
765                    Feature feature = fc.getFeature( i );
766                    if ( feature != null ) {
767                        nsBindings = determineUsedNSBindings( feature, nsBindings );
768                    }
769                }
770            }
771    
772            return nsBindings;
773        }
774    
775        /**
776         * Determines the namespace bindings that are used in the feature and it's properties
777         * 
778         * @param feature
779         *            feature
780         * @param nsBindings
781         *            to add to.
782         * @return the namespace bindings
783         */
784        private Map<String, URI> determineUsedNSBindings( Feature feature, Map<String, URI> nsBindings ) {
785            if ( nsBindings == null ) {
786                nsBindings = new HashMap<String, URI>();
787            }
788            if ( feature != null ) {
789                // process feature element
790                QualifiedName qName = feature.getName();
791                if ( qName != null ) {
792                    String prefix = qName.getPrefix();
793                    URI ns = qName.getNamespace();
794                    if ( ns != null && !"".equals( ns.toASCIIString().trim() ) ) {
795                        LOG.logDebug( "Adding qName: " + qName );
796                        nsBindings.put( prefix, ns );
797                    }
798                }
799    
800                // now check for properties which use a namespace
801                FeatureProperty[] featureProperties = feature.getProperties();
802                if ( featureProperties != null ) {
803                    for ( FeatureProperty fp : featureProperties ) {
804                        if ( fp != null ) {
805                            QualifiedName fpName = fp.getName();
806                            if ( fpName != null ) {
807                                String prefix = fpName.getPrefix();
808                                if ( prefix != null && !"".equals( prefix.trim() ) ) {
809                                    if ( nsBindings.get( prefix ) == null ) {
810                                        URI ns = fpName.getNamespace();
811                                        if ( ns != null && !"".equals( ns.toASCIIString().trim() ) ) {
812                                            LOG.logDebug( "Adding qname: " + fpName );
813                                            nsBindings.put( prefix, ns );
814                                        }
815                                    }
816                                }
817                            }
818                            // Object value = fp.getValue();
819                            // if ( value instanceof Feature ) {
820                            // determineUsedNSBindings( (Feature) value, nsBindings );
821                            // }
822                        }
823                    }
824                }
825            }
826    
827            return nsBindings;
828        }
829    
830        /**
831         * Determines the namespace bindings that are used in the feature.
832         * <p>
833         * NOTE: Currently only the bindings for the feature's root element and the contained features are considered. If a
834         * subfeature uses another bindings, this binding will be missing in the XML.
835         * 
836         * @param feature
837         *            feature
838         * @return the namespace bindings
839         */
840        private Map<String, URI> determineUsedNSBindings( Feature feature ) {
841    
842            // reset the counter.
843            Map<String, URI> nsBindings = new HashMap<String, URI>();
844    
845            return determineUsedNSBindings( feature, nsBindings );
846            // // process feature element
847            // QualifiedName name = feature.getName();
848            // nsBindings.put( name.getPrefix(), name.getNamespace() );
849    
850        }
851    
852        /**
853         * Appends the given namespace bindings to the PrintWriter.
854         * 
855         * @param bindings
856         *            namespace bindings to append
857         * @param pw
858         *            PrintWriter to write to
859         */
860        private void appendNSBindings( Map<String, URI> bindings, PrintWriter pw ) {
861    
862            Iterator<String> prefixIter = bindings.keySet().iterator();
863            while ( prefixIter.hasNext() ) {
864                String prefix = prefixIter.next();
865                URI nsURI = bindings.get( prefix );
866                if ( prefix == null ) {
867                    pw.print( " xmlns=\"" );
868                    pw.print( nsURI );
869                    pw.print( '\"' );
870                } else {
871                    pw.print( " xmlns:" );
872                    pw.print( prefix );
873                    pw.print( "=\"" );
874                    pw.print( nsURI );
875                    pw.print( '\"' );
876                }
877            }
878            // if more then one default namespaces were defined, each feature must (re)-determine the
879            // default ns.
880            this.nsBindingsExported = true;// ( defaultNamespaceCounter == 0 );
881        }
882    
883        /**
884         * Exports an instance of a <code>Feature</code> to the passed <code>OutputStream</code> formatted as GML. Uses the
885         * deegree system character set for the XML header encoding information.
886         * 
887         * @param feature
888         *            feature to export
889         * @param os
890         *            output stream to write to
891         * 
892         * @throws IOException
893         * @throws FeatureException
894         */
895        public void export( Feature feature, OutputStream os )
896                                throws IOException, FeatureException {
897            export( feature, os, CharsetUtils.getSystemCharset() );
898        }
899    
900        /**
901         * Exports a <code>Feature</code> instance to the passed <code>OutputStream</code> formatted as GML.
902         * 
903         * @param feature
904         *            feature to export
905         * @param os
906         *            output stream to write to
907         * @param charsetName
908         *            name of the used charset/encoding (for the XML header)
909         * @throws IOException
910         * @throws FeatureException
911         */
912        public void export( Feature feature, OutputStream os, String charsetName )
913                                throws IOException, FeatureException {
914    
915            PrintWriter pw = new PrintWriter( new OutputStreamWriter( os, charsetName ) );
916            pw.println( "<?xml version=\"1.0\" encoding=\"" + charsetName + "\"?>" );
917            export( feature, pw, xlinkDepth );
918            pw.close();
919        }
920    
921        /**
922         * Exports a <code>Feature</code> instance to the passed <code>PrintWriter</code> as GML.
923         * 
924         * @param feature
925         *            feature to export
926         * @param pw
927         *            PrintWriter to write to
928         * @throws FeatureException
929         */
930        private void export( Feature feature, PrintWriter pw, int currentDepth )
931                                throws FeatureException {
932    
933            boolean isPseudoFt = false;
934            if ( feature.getFeatureType() != null && feature.getFeatureType() instanceof MappedFeatureType ) {
935                isPseudoFt = ( (MappedFeatureType) feature.getFeatureType() ).isPseudoFeatureType();
936            }
937    
938            QualifiedName ftName = feature.getName();
939            String fid = isPseudoFt ? null : feature.getId();
940    
941            if ( this.suppressXLinkOutput && fid != null && !"".equals( fid ) ) {
942                if ( this.localFeatures.contains( fid ) ) {
943                    String msg = Messages.format( "ERROR_CYLIC_FEATURE", fid );
944                    throw new FeatureException( msg );
945                }
946                this.localFeatures.add( fid );
947            }
948    
949            // open feature element (add gml:id attribute if feature has an id)
950            pw.print( '<' );
951            pw.print( ftName.getPrefixedName() );
952            if ( fid != null && !fid.equals( "" ) ) {
953                this.exportedFeatures.add( fid );
954                pw.print( " gml:id=\"" );
955                pw.print( fid );
956                pw.print( '\"' );
957            }
958    
959            // determine and add namespace bindings
960    
961            if ( !this.nsBindingsExported ) {
962    
963                Map<String, URI> nsBindings = determineUsedNSBindings( feature );
964                nsBindings.put( "gml", CommonNamespaces.GMLNS );
965                nsBindings.put( "xlink", CommonNamespaces.XLNNS );
966                if ( this.schemaURL != null ) {
967                    nsBindings.put( "xsi", CommonNamespaces.XSINS );
968                }
969                appendNSBindings( nsBindings, pw );
970            }
971    
972            pw.print( '>' );
973    
974            // to get the order right (gml default attributes come BEFORE the envelope, stupid...)
975            boolean boundedByExported = false;
976    
977            // export all properties of the feature
978            FeatureProperty[] properties = feature.getProperties();
979    
980            int geomProperties = -1; // this counter is actually counted up where geometries are exported (and it's used for
981            // outputting geometry ids)
982            // that's why the methods now all take the counter and return it also
983    
984            for ( int i = 0; i < properties.length; i++ ) {
985                boolean exportEnv = !isPseudoFt && !boundedByExported;
986                QualifiedName qn = properties[i].getName();
987                String ln = qn.getLocalName();
988                exportEnv = exportEnv
989                            && !( qn.getNamespace().equals( GMLNS ) && ( ln.equals( "description" ) || ln.equals( "name" ) ) );
990    
991                if ( exportEnv ) {
992                    exportBoundedBy( feature, pw );
993                    boundedByExported = true;
994                }
995    
996                if ( properties[i] != null && properties[i].getValue() != null ) {
997                    geomProperties = exportProperty( feature, properties[i], pw, geomProperties, currentDepth );
998                }
999            }
1000    
1001            if ( properties.length == 0 ) {
1002                boolean exportEnv = !isPseudoFt && !boundedByExported;
1003                if ( exportEnv ) {
1004                    exportBoundedBy( feature, pw );
1005                    boundedByExported = true;
1006                }
1007            }
1008    
1009            // close feature element
1010            pw.print( "</" );
1011            pw.print( ftName.getPrefixedName() );
1012            pw.println( '>' );
1013    
1014            if ( this.suppressXLinkOutput || fid != null ) {
1015                this.localFeatures.remove( fid );
1016            }
1017        }
1018    
1019        private static void exportBoundedBy( Feature feature, PrintWriter pw ) {
1020            try {
1021                Envelope env = null;
1022                if ( ( env = feature.getBoundedBy() ) != null ) {
1023                    pw.print( "<gml:boundedBy><gml:Envelope" );
1024                    if ( env.getCoordinateSystem() != null ) {
1025                        pw.print( " srsName='" + env.getCoordinateSystem().getPrefixedName() + "'" );
1026                    }
1027    
1028                    boolean swap = swap( env );
1029    
1030                    pw.print( "><gml:lowerCorner>" );
1031                    pw.print( swap ? env.getMin().getY() : env.getMin().getX() );
1032                    pw.print( ' ' );
1033                    pw.print( swap ? env.getMin().getX() : env.getMin().getY() );
1034                    pw.print( "</gml:lowerCorner><gml:upperCorner>" );
1035                    pw.print( swap ? env.getMax().getY() : env.getMax().getX() );
1036                    pw.print( ' ' );
1037                    pw.print( swap ? env.getMax().getX() : env.getMax().getY() );
1038                    pw.print( "</gml:upperCorner></gml:Envelope></gml:boundedBy>" );
1039                }
1040            } catch ( GeometryException e ) {
1041                LOG.logError( e.getMessage(), e );
1042            }
1043    
1044        }
1045    
1046        /**
1047         * Exports a <code>FeatureProperty</code> instance to the passed <code>PrintWriter</code> as GML.
1048         * 
1049         * @param feature
1050         *            feature that the property belongs to
1051         * @param property
1052         *            property to export
1053         * @param pw
1054         *            PrintWriter to write to
1055         * @param geomProperties
1056         *            counter to indicate the number of the current geometry property
1057         * @param currentDepth
1058         *            counter to indicate how many levels of xlinks have already been resolved
1059         * @throws FeatureException
1060         */
1061        private int exportProperty( Feature feature, FeatureProperty property, PrintWriter pw, int geomProperties,
1062                                    int currentDepth )
1063                                throws FeatureException {
1064    
1065            QualifiedName propertyName = property.getName();
1066            Object value = property.getValue();
1067            Integer d = xlinkPropertyNames.get( property.getName().getPrefixedName() );
1068            int customDepth = d == null ? currentDepth : d;
1069            boolean isReferenceType = false;
1070            if ( feature.getFeatureType() != null && feature.getFeatureType() instanceof MappedFeatureType ) {
1071                MappedFeatureType ft = (MappedFeatureType) feature.getFeatureType();
1072                if ( ft.getProperty( property.getName() ) instanceof MappedFeaturePropertyType ) {
1073                    MappedFeaturePropertyType pt = (MappedFeaturePropertyType) ft.getProperty( property.getName() );
1074                    isReferenceType = pt.isReferenceType();
1075                }
1076            }
1077    
1078            if ( value instanceof Feature ) {
1079                Feature subfeature = (Feature) value;
1080    
1081                if ( isReferenceType
1082                     || ( subfeature.getId() != null && !subfeature.getId().equals( "" )
1083                          && exportedFeatures.contains( subfeature.getId() ) && !this.suppressXLinkOutput ) ) {
1084                    if ( exportedFeatures.contains( subfeature.getId() ) ) {
1085                        pw.print( "<!-- xlink:href=\"#" );
1086                        pw.print( subfeature.getId() );
1087                        pw.print( "\" -->" );
1088                        pw.print( '<' );
1089                        pw.print( propertyName.getPrefixedName() );
1090                        pw.print( " xlink:href=\"#" );
1091                        pw.print( subfeature.getId() );
1092                        pw.print( "\"/>" );
1093                    } else {
1094                        pw.print( '<' );
1095                        pw.print( propertyName.getPrefixedName() );
1096                        if ( isReferenceType || currentDepth == 0 || customDepth == 0 ) {
1097                            pw.print( " xlink:href=\"#" );
1098                            pw.print( subfeature.getId() );
1099                            pw.print( "\"" );
1100                        }
1101                        pw.print( ">" );
1102                        if ( !isReferenceType && ( currentDepth != 0 || customDepth != 0 ) ) {
1103                            pw.print( "<!-- gml:id=\"" );
1104                            pw.print( subfeature.getId() );
1105                            pw.print( "\" -->" );
1106                            export( subfeature, pw, currentDepth - 1 );
1107                        }
1108                        pw.print( "</" );
1109                        pw.print( propertyName.getPrefixedName() );
1110                        pw.print( ">" );
1111                    }
1112                } else {
1113                    pw.print( '<' );
1114                    pw.print( propertyName.getPrefixedName() );
1115                    if ( currentDepth == 0 && customDepth == 0 ) {
1116                        pw.print( " xlink:href=\"" );
1117                        pw.print( baseUrl );
1118                        pw.print( "?request=GetGmlObject&amp;version=1.1.0&amp;service=WFS&amp;objectid=" );
1119                        pw.print( subfeature.getId() );
1120                        pw.print( "\"/>" );
1121                    } else {
1122                        pw.print( '>' );
1123                        pw.print( "<!-- xlink:href=\"#" );
1124                        pw.print( subfeature.getId() );
1125                        pw.print( "\" -->" );
1126                        exportPropertyValue( subfeature, pw, FEATURE, null, 0, currentDepth - 1 );
1127                        pw.print( "</" );
1128                        pw.print( propertyName.getPrefixedName() );
1129                        pw.print( '>' );
1130                    }
1131                }
1132            } else if ( value instanceof URL ) {
1133                if ( currentDepth == 0 || isReferenceType || customDepth == 0 ) {
1134                    pw.print( '<' );
1135                    pw.print( propertyName.getPrefixedName() );
1136                    pw.print( " xlink:href=\"" );
1137                    pw.print( escape( value.toString() ) );
1138                    pw.print( "\"/>" );
1139                } else {
1140                    pw.print( '<' );
1141                    pw.print( propertyName.getPrefixedName() );
1142                    pw.print( ">" );
1143                    pw.print( "<!-- xlink:href=\"" );
1144                    pw.print( value );
1145                    pw.print( "\" -->" );
1146    
1147                    try {
1148                        GMLFeatureDocument doc = new GMLFeatureDocument();
1149                        URLConnection conn = ( (URL) value ).openConnection();
1150                        conn.setReadTimeout( 5000 );
1151                        conn.setConnectTimeout( 1000 );
1152                        doc.load( conn.getInputStream(), ( (URL) value ).toExternalForm() );
1153                        export( doc.parseFeature(), pw, currentDepth - 1 );
1154                        pw.print( "</" );
1155                        pw.print( propertyName.getPrefixedName() );
1156                        pw.print( ">" );
1157                    } catch ( IOException e ) {
1158                        e.printStackTrace();
1159                        LOG.logDebug( "Stack trace:", e );
1160                        throw new FeatureException( org.deegree.i18n.Messages.get( "DATASTORE_XLINK_IO_RETRIEVE",
1161                                                                                   e.getLocalizedMessage() ) );
1162                    } catch ( SAXException 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 ( XMLParsingException 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                    } catch ( UnknownCRSException e ) {
1173                        e.printStackTrace();
1174                        LOG.logDebug( "Stack trace:", e );
1175                        throw new FeatureException( org.deegree.i18n.Messages.get( "DATASTORE_XLINK_PARSE_ERROR",
1176                                                                                   e.getLocalizedMessage() ) );
1177                    }
1178    
1179                }
1180            } else {
1181                pw.print( '<' );
1182                pw.print( propertyName.getPrefixedName() );
1183                pw.print( '>' );
1184                if ( value != null ) {
1185                    FeatureType ft = feature.getFeatureType();
1186                    PropertyType pt = ft.getProperty( property.getName() );
1187                    if ( pt == null || pt.getType() == Types.ANYTYPE ) {
1188                        pw.print( value );
1189                    } else {
1190                        geomProperties = exportPropertyValue( value, pw, pt.getType(), printGeometryIds ? feature.getId()
1191                                                                                                       : null,
1192                                                              geomProperties, currentDepth - 1 );
1193                    }
1194                }
1195                pw.print( "</" );
1196                pw.print( propertyName.getPrefixedName() );
1197                pw.print( '>' );
1198            }
1199    
1200            return geomProperties;
1201        }
1202    
1203        /**
1204         * Exports the value of a property to the passed <code>PrintWriter</code> as GML.
1205         * 
1206         * TODO make available and use property type information to determine correct output format (e.g. xs:date, xs:time,
1207         * xs:dateTime are all represented using Date objects and cannot be differentiated at the moment)
1208         * 
1209         * @param value
1210         *            property value to export
1211         * @param pw
1212         *            PrintWriter to write to
1213         * @param type
1214         *            the Types.java type code
1215         * @param geomProperties
1216         *            counter to indicate the number of the current geometry property
1217         * @param currentDepth
1218         *            counter to indicate how many levels of xlinks have already been resolved
1219         * @throws FeatureException
1220         */
1221        private int exportPropertyValue( Object value, PrintWriter pw, int type, String id, int geomProperties,
1222                                         int currentDepth )
1223                                throws FeatureException {
1224            if ( value instanceof Feature ) {
1225                export( (Feature) value, pw, currentDepth );
1226            } else if ( value instanceof Feature[] ) {
1227                Feature[] features = (Feature[]) value;
1228                for ( int i = 0; i < features.length; i++ ) {
1229                    export( features[i], pw, currentDepth );
1230                }
1231            } else if ( value instanceof Envelope ) {
1232                exportEnvelope( (Envelope) value, pw );
1233            } else if ( value instanceof FeatureCollection ) {
1234                export( (FeatureCollection) value, pw, currentDepth );
1235            } else if ( value instanceof Geometry ) {
1236                id = id == null ? null : ( id + "_GEOM_" + ++geomProperties );
1237                exportGeometry( (Geometry) value, pw, id );
1238            } else if ( value instanceof Date ) {
1239    
1240                switch ( type ) {
1241                case DATE: {
1242                    pw.print( dateFormatter.format( (Date) value ) );
1243                    break;
1244                }
1245                case TIME: {
1246                    pw.print( timeFormatter.format( (Date) value ) );
1247                    break;
1248                }
1249                case TIMESTAMP: {
1250                    pw.print( getISOFormattedTime( (Date) value ) );
1251                    break;
1252                }
1253                }
1254    
1255            } else if ( value instanceof Calendar ) {
1256                pw.print( TimeTools.getISOFormattedTime( (Calendar) value ) );
1257            } else if ( value instanceof Timestamp ) {
1258                pw.print( TimeTools.getISOFormattedTime( (Timestamp) value ) );
1259            } else if ( value instanceof java.sql.Date ) {
1260                pw.print( TimeTools.getISOFormattedTime( (java.sql.Date) value ) );
1261            } else if ( value instanceof Number || value instanceof BigDecimal ) {
1262                pw.print( value.toString() );
1263            } else if ( value instanceof String ) {
1264                StringBuffer sb = DOMPrinter.validateCDATA( (String) value );
1265                pw.print( sb );
1266            } else if ( value instanceof Boolean ) {
1267                pw.print( value );
1268            } else {
1269                LOG.logInfo( "Unhandled property class '" + value.getClass() + "' in GMLFeatureAdapter." );
1270                StringBuffer sb = DOMPrinter.validateCDATA( value.toString() );
1271                pw.print( sb );
1272            }
1273    
1274            return geomProperties;
1275        }
1276    
1277        /**
1278         * prints the passed geometry to the also passed PrintWriter formatted as GML
1279         * 
1280         * @param geo
1281         *            geometry to print/extport
1282         * @param pw
1283         *            target of the printing/export
1284         * @throws FeatureException
1285         */
1286        private void exportGeometry( Geometry geo, PrintWriter pw, String id )
1287                                throws FeatureException {
1288            try {
1289                GMLGeometryAdapter.export( geo, pw, id );
1290            } catch ( Exception e ) {
1291                LOG.logError( "", e );
1292                throw new FeatureException( "Could not export geometry to GML: " + e.getMessage(), e );
1293            }
1294        }
1295    
1296        /**
1297         * prints the passed geometry to the also passed PrintWriter formatted as GML
1298         * 
1299         * @param geo
1300         *            geometry to print/extport
1301         * @param pw
1302         *            target of the printing/export
1303         * @throws FeatureException
1304         */
1305        private void exportEnvelope( Envelope geo, PrintWriter pw )
1306                                throws FeatureException {
1307            try {
1308                pw.print( GMLGeometryAdapter.exportAsBox( geo ) );
1309            } catch ( Exception e ) {
1310                throw new FeatureException( "Could not export envelope to GML: " + e.getMessage(), e );
1311            }
1312        }
1313    }