    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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: apoth $
    117     *
    118     * @version $Revision: 24539 $, $Date: 2010-05-25 11:04:40 +0200 (Di, 25. Mai 2010) $
    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 == null || 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    }