001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/model/feature/GMLFeatureAdapter.java $
002 /*----------------------------------------------------------------------------
003 This file is part of deegree, http://deegree.org/
004 Copyright (C) 2001-2009 by:
005 Department of Geography, University of Bonn
006 and
007 lat/lon GmbH
008
009 This library is free software; you can redistribute it and/or modify it under
010 the terms of the GNU Lesser General Public License as published by the Free
011 Software Foundation; either version 2.1 of the License, or (at your option)
012 any later version.
013 This library is distributed in the hope that it will be useful, but WITHOUT
014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016 details.
017 You should have received a copy of the GNU Lesser General Public License
018 along with this library; if not, write to the Free Software Foundation, Inc.,
019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020
021 Contact information:
022
023 lat/lon GmbH
024 Aennchenstr. 19, 53177 Bonn
025 Germany
026 http://lat-lon.de/
027
028 Department of Geography, University of Bonn
029 Prof. Dr. Klaus Greve
030 Postfach 1147, 53001 Bonn
031 Germany
032 http://www.geographie.uni-bonn.de/deegree/
033
034 e-mail: info@deegree.org
035 ----------------------------------------------------------------------------*/
036 package org.deegree.model.feature;
037
038 import static org.deegree.datatypes.Types.DATE;
039 import static org.deegree.datatypes.Types.FEATURE;
040 import static org.deegree.datatypes.Types.TIME;
041 import static org.deegree.datatypes.Types.TIMESTAMP;
042 import static org.deegree.framework.util.TimeTools.getISOFormattedTime;
043 import static org.deegree.framework.xml.XMLTools.escape;
044 import static org.deegree.model.spatialschema.GMLGeometryAdapter.swap;
045 import static org.deegree.ogcbase.CommonNamespaces.GMLNS;
046
047 import java.io.ByteArrayInputStream;
048 import java.io.ByteArrayOutputStream;
049 import java.io.IOException;
050 import java.io.OutputStream;
051 import java.io.OutputStreamWriter;
052 import java.io.PrintWriter;
053 import java.math.BigDecimal;
054 import java.net.URI;
055 import java.net.URL;
056 import java.net.URLConnection;
057 import java.sql.Timestamp;
058 import java.text.DateFormat;
059 import java.text.SimpleDateFormat;
060 import java.util.Calendar;
061 import java.util.Collection;
062 import java.util.Date;
063 import java.util.HashMap;
064 import java.util.HashSet;
065 import java.util.Iterator;
066 import java.util.Map;
067 import java.util.Set;
068
069 import org.deegree.datatypes.QualifiedName;
070 import org.deegree.datatypes.Types;
071 import org.deegree.framework.log.ILogger;
072 import org.deegree.framework.log.LoggerFactory;
073 import org.deegree.framework.util.CharsetUtils;
074 import org.deegree.framework.util.StringTools;
075 import org.deegree.framework.util.TimeTools;
076 import org.deegree.framework.xml.DOMPrinter;
077 import org.deegree.framework.xml.XMLException;
078 import org.deegree.framework.xml.XMLFragment;
079 import org.deegree.framework.xml.XMLParsingException;
080 import org.deegree.framework.xml.XMLTools;
081 import org.deegree.io.datastore.schema.MappedFeaturePropertyType;
082 import org.deegree.io.datastore.schema.MappedFeatureType;
083 import org.deegree.model.crs.UnknownCRSException;
084 import org.deegree.model.feature.schema.FeatureType;
085 import org.deegree.model.feature.schema.PropertyType;
086 import org.deegree.model.spatialschema.Envelope;
087 import org.deegree.model.spatialschema.GMLGeometryAdapter;
088 import org.deegree.model.spatialschema.Geometry;
089 import org.deegree.model.spatialschema.GeometryException;
090 import org.deegree.ogcbase.CommonNamespaces;
091 import org.deegree.ogcbase.PropertyPath;
092 import org.deegree.ogcbase.XLinkPropertyPath;
093 import org.w3c.dom.Element;
094 import org.xml.sax.SAXException;
095
096 /**
097 * Exports feature instances to their GML representation.
098 * <p>
099 * Has support for XLink output and to disable XLink output (which is generally not feasible).
100 * <p>
101 * Also responsible for "xlinking" features: if a feature occurs several times in a feature collection, it must be
102 * exported only once - all other occurences must use xlink-attributes in the surrounding property element to reference
103 * the feature.
104 *
105 * TODO Handle FeatureCollections like ordinary Features (needs changes in feature model).
106 *
107 * TODO Separate cycle check (for suppressXLinkOutput).
108 *
109 * TODO Use a more straight-forward approach to export DOM representations.
110 *
111 * TODO Handle multiple application schemas (in xsi:schemaLocation attribute).
112 *
113 * TODO Handle WFS-schema-binding in a subclass in the WFS package?
114 *
115 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
116 * @author last edited by: $Author: mschneider $
117 *
118 * @version $Revision: 20808 $, $Date: 2009-11-16 14:03:13 +0100 (Mo, 16. Nov 2009) $
119 */
120 public class GMLFeatureAdapter {
121
122 private static final ILogger LOG = LoggerFactory.getLogger( GMLFeatureAdapter.class );
123
124 private String wfsSchemaBinding = "http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd";
125
126 // values: feature ids of already exported features (for XLinks)
127 private Set<String> exportedFeatures = new HashSet<String>();
128
129 // values: feature ids of all (sub-) features in a feature (to find cyclic features)
130 private Set<String> localFeatures = new HashSet<String>();
131
132 private boolean suppressXLinkOutput;
133
134 private String schemaURL;
135
136 // marks if namespace bindings have been appended already
137 private boolean nsBindingsExported;
138
139 private final DateFormat dateFormatter = new SimpleDateFormat( "yyyy-MM-dd" );
140
141 private final DateFormat timeFormatter = new SimpleDateFormat( "HH:mm:ss" );
142
143 private boolean printGeometryIds;
144
145 private int xlinkDepth = -1;
146
147 private HashMap<String, Integer> xlinkPropertyNames;
148
149 private String baseUrl = "http://localhost:8080/wfs/services";
150
151 /**
152 * Creates a new <code>GMLFeatureAdapter</code> instance with enabled XLink output.
153 */
154 public GMLFeatureAdapter() {
155 this.suppressXLinkOutput = false;
156 this.xlinkPropertyNames = new HashMap<String, Integer>();
157 }
158
159 /**
160 * @param xlinkdepth
161 */
162 public GMLFeatureAdapter( int xlinkdepth ) {
163 xlinkDepth = xlinkdepth;
164 this.xlinkPropertyNames = new HashMap<String, Integer>();
165 }
166
167 /**
168 * Creates a new <code>GMLFeatureAdapter</code> instance with enabled XLink output and schema reference.
169 *
170 * @param schemaURL
171 * URL of schema document (used as xsi:schemaLocation attribute in XML output)
172 */
173 public GMLFeatureAdapter( String schemaURL ) {
174 this.suppressXLinkOutput = false;
175 if ( schemaURL != null ) {
176 this.schemaURL = StringTools.replace( schemaURL, "&", "&", 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, "&", "&", 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, "&", "&", 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&version=1.1.0&service=WFS&objectid=" );
1114 pw.print( subfeature.getId() );
1115 pw.print( "\"/>" );
1116 } else {
1117 pw.print( '>' );
1118 pw.print( "<!-- xlink:href=\"#" );
1119 pw.print( subfeature.getId() );
1120 pw.print( "\" -->" );
1121 exportPropertyValue( subfeature, pw, FEATURE, null, 0, currentDepth - 1 );
1122 pw.print( "</" );
1123 pw.print( propertyName.getPrefixedName() );
1124 pw.print( '>' );
1125 }
1126 }
1127 } else if ( value instanceof URL ) {
1128 if ( currentDepth == 0 || isReferenceType || customDepth == 0 ) {
1129 pw.print( '<' );
1130 pw.print( propertyName.getPrefixedName() );
1131 pw.print( " xlink:href=\"" );
1132 pw.print( escape( value.toString() ) );
1133 pw.print( "\"/>" );
1134 } else {
1135 pw.print( '<' );
1136 pw.print( propertyName.getPrefixedName() );
1137 pw.print( ">" );
1138 pw.print( "<!-- xlink:href=\"" );
1139 pw.print( value );
1140 pw.print( "\" -->" );
1141
1142 try {
1143 GMLFeatureDocument doc = new GMLFeatureDocument();
1144 URLConnection conn = ( (URL) value ).openConnection();
1145 conn.setReadTimeout( 5000 );
1146 conn.setConnectTimeout( 1000 );
1147 doc.load( conn.getInputStream(), ( (URL) value ).toExternalForm() );
1148 export( doc.parseFeature(), pw, currentDepth - 1 );
1149 pw.print( "</" );
1150 pw.print( propertyName.getPrefixedName() );
1151 pw.print( ">" );
1152 } catch ( IOException e ) {
1153 e.printStackTrace();
1154 LOG.logDebug( "Stack trace:", e );
1155 throw new FeatureException( org.deegree.i18n.Messages.get( "DATASTORE_XLINK_IO_RETRIEVE",
1156 e.getLocalizedMessage() ) );
1157 } catch ( SAXException e ) {
1158 e.printStackTrace();
1159 LOG.logDebug( "Stack trace:", e );
1160 throw new FeatureException( org.deegree.i18n.Messages.get( "DATASTORE_XLINK_PARSE_ERROR",
1161 e.getLocalizedMessage() ) );
1162 } catch ( XMLParsingException e ) {
1163 e.printStackTrace();
1164 LOG.logDebug( "Stack trace:", e );
1165 throw new FeatureException( org.deegree.i18n.Messages.get( "DATASTORE_XLINK_PARSE_ERROR",
1166 e.getLocalizedMessage() ) );
1167 } catch ( UnknownCRSException e ) {
1168 e.printStackTrace();
1169 LOG.logDebug( "Stack trace:", e );
1170 throw new FeatureException( org.deegree.i18n.Messages.get( "DATASTORE_XLINK_PARSE_ERROR",
1171 e.getLocalizedMessage() ) );
1172 }
1173
1174 }
1175 } else {
1176 pw.print( '<' );
1177 pw.print( propertyName.getPrefixedName() );
1178 pw.print( '>' );
1179 if ( value != null ) {
1180 FeatureType ft = feature.getFeatureType();
1181 PropertyType pt = ft.getProperty( property.getName() );
1182 if ( pt.getType() == Types.ANYTYPE ) {
1183 pw.print( value );
1184 } else {
1185 geomProperties = exportPropertyValue( value, pw, pt.getType(), printGeometryIds ? feature.getId()
1186 : null,
1187 geomProperties, currentDepth - 1 );
1188 }
1189 }
1190 pw.print( "</" );
1191 pw.print( propertyName.getPrefixedName() );
1192 pw.print( '>' );
1193 }
1194
1195 return geomProperties;
1196 }
1197
1198 /**
1199 * Exports the value of a property to the passed <code>PrintWriter</code> as GML.
1200 *
1201 * TODO make available and use property type information to determine correct output format (e.g. xs:date, xs:time,
1202 * xs:dateTime are all represented using Date objects and cannot be differentiated at the moment)
1203 *
1204 * @param value
1205 * property value to export
1206 * @param pw
1207 * PrintWriter to write to
1208 * @param type
1209 * the Types.java type code
1210 * @param geomProperties
1211 * counter to indicate the number of the current geometry property
1212 * @param currentDepth
1213 * counter to indicate how many levels of xlinks have already been resolved
1214 * @throws FeatureException
1215 */
1216 private int exportPropertyValue( Object value, PrintWriter pw, int type, String id, int geomProperties,
1217 int currentDepth )
1218 throws FeatureException {
1219 if ( value instanceof Feature ) {
1220 export( (Feature) value, pw, currentDepth );
1221 } else if ( value instanceof Feature[] ) {
1222 Feature[] features = (Feature[]) value;
1223 for ( int i = 0; i < features.length; i++ ) {
1224 export( features[i], pw, currentDepth );
1225 }
1226 } else if ( value instanceof Envelope ) {
1227 exportEnvelope( (Envelope) value, pw );
1228 } else if ( value instanceof FeatureCollection ) {
1229 export( (FeatureCollection) value, pw, currentDepth );
1230 } else if ( value instanceof Geometry ) {
1231 id = id == null ? null : ( id + "_GEOM_" + ++geomProperties );
1232 exportGeometry( (Geometry) value, pw, id );
1233 } else if ( value instanceof Date ) {
1234
1235 switch ( type ) {
1236 case DATE: {
1237 pw.print( dateFormatter.format( (Date) value ) );
1238 break;
1239 }
1240 case TIME: {
1241 pw.print( timeFormatter.format( (Date) value ) );
1242 break;
1243 }
1244 case TIMESTAMP: {
1245 pw.print( getISOFormattedTime( (Date) value ) );
1246 break;
1247 }
1248 }
1249
1250 } else if ( value instanceof Calendar ) {
1251 pw.print( TimeTools.getISOFormattedTime( (Calendar) value ) );
1252 } else if ( value instanceof Timestamp ) {
1253 pw.print( TimeTools.getISOFormattedTime( (Timestamp) value ) );
1254 } else if ( value instanceof java.sql.Date ) {
1255 pw.print( TimeTools.getISOFormattedTime( (java.sql.Date) value ) );
1256 } else if ( value instanceof Integer || value instanceof Long || value instanceof Float
1257 || value instanceof Double || value instanceof BigDecimal ) {
1258 pw.print( value.toString() );
1259 } else if ( value instanceof String ) {
1260 StringBuffer sb = DOMPrinter.validateCDATA( (String) value );
1261 pw.print( sb );
1262 } else if ( value instanceof Boolean ) {
1263 pw.print( value );
1264 } else {
1265 LOG.logInfo( "Unhandled property class '" + value.getClass() + "' in GMLFeatureAdapter." );
1266 StringBuffer sb = DOMPrinter.validateCDATA( value.toString() );
1267 pw.print( sb );
1268 }
1269
1270 return geomProperties;
1271 }
1272
1273 /**
1274 * prints the passed geometry to the also passed PrintWriter formatted as GML
1275 *
1276 * @param geo
1277 * geometry to print/extport
1278 * @param pw
1279 * target of the printing/export
1280 * @throws FeatureException
1281 */
1282 private void exportGeometry( Geometry geo, PrintWriter pw, String id )
1283 throws FeatureException {
1284 try {
1285 GMLGeometryAdapter.export( geo, pw, id );
1286 } catch ( Exception e ) {
1287 LOG.logError( "", e );
1288 throw new FeatureException( "Could not export geometry to GML: " + e.getMessage(), e );
1289 }
1290 }
1291
1292 /**
1293 * prints the passed geometry to the also passed PrintWriter formatted as GML
1294 *
1295 * @param geo
1296 * geometry to print/extport
1297 * @param pw
1298 * target of the printing/export
1299 * @throws FeatureException
1300 */
1301 private void exportEnvelope( Envelope geo, PrintWriter pw )
1302 throws FeatureException {
1303 try {
1304 pw.print( GMLGeometryAdapter.exportAsBox( geo ) );
1305 } catch ( Exception e ) {
1306 throw new FeatureException( "Could not export envelope to GML: " + e.getMessage(), e );
1307 }
1308 }
1309 }