001 //$HeadURL: http://svn.wald.intevation.org/svn/deegree/base/trunk/src/org/deegree/model/feature/GMLFeatureAdapter.java $
002 /*----------------------------------------------------------------------------
003 This file is part of deegree, http://deegree.org/
004 Copyright (C) 2001-2009 by:
005 Department of Geography, University of Bonn
006 and
007 lat/lon GmbH
008
009 This library is free software; you can redistribute it and/or modify it under
010 the terms of the GNU Lesser General Public License as published by the Free
011 Software Foundation; either version 2.1 of the License, or (at your option)
012 any later version.
013 This library is distributed in the hope that it will be useful, but WITHOUT
014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016 details.
017 You should have received a copy of the GNU Lesser General Public License
018 along with this library; if not, write to the Free Software Foundation, Inc.,
019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020
021 Contact information:
022
023 lat/lon GmbH
024 Aennchenstr. 19, 53177 Bonn
025 Germany
026 http://lat-lon.de/
027
028 Department of Geography, University of Bonn
029 Prof. Dr. Klaus Greve
030 Postfach 1147, 53001 Bonn
031 Germany
032 http://www.geographie.uni-bonn.de/deegree/
033
034 e-mail: info@deegree.org
035 ----------------------------------------------------------------------------*/
036 package org.deegree.model.feature;
037
038 import static org.deegree.datatypes.Types.DATE;
039 import static org.deegree.datatypes.Types.FEATURE;
040 import static org.deegree.datatypes.Types.TIME;
041 import static org.deegree.datatypes.Types.TIMESTAMP;
042 import static org.deegree.framework.util.TimeTools.getISOFormattedTime;
043 import static org.deegree.framework.xml.XMLTools.escape;
044 import static org.deegree.model.spatialschema.GMLGeometryAdapter.swap;
045 import static org.deegree.ogcbase.CommonNamespaces.GMLNS;
046
047 import java.io.ByteArrayInputStream;
048 import java.io.ByteArrayOutputStream;
049 import java.io.IOException;
050 import java.io.OutputStream;
051 import java.io.OutputStreamWriter;
052 import java.io.PrintWriter;
053 import java.math.BigDecimal;
054 import java.net.URI;
055 import java.net.URL;
056 import java.net.URLConnection;
057 import java.sql.Timestamp;
058 import java.text.DateFormat;
059 import java.text.SimpleDateFormat;
060 import java.util.Calendar;
061 import java.util.Collection;
062 import java.util.Date;
063 import java.util.HashMap;
064 import java.util.HashSet;
065 import java.util.Iterator;
066 import java.util.Map;
067 import java.util.Set;
068
069 import org.deegree.datatypes.QualifiedName;
070 import org.deegree.datatypes.Types;
071 import org.deegree.framework.log.ILogger;
072 import org.deegree.framework.log.LoggerFactory;
073 import org.deegree.framework.util.CharsetUtils;
074 import org.deegree.framework.util.StringTools;
075 import org.deegree.framework.util.TimeTools;
076 import org.deegree.framework.xml.DOMPrinter;
077 import org.deegree.framework.xml.XMLException;
078 import org.deegree.framework.xml.XMLFragment;
079 import org.deegree.framework.xml.XMLParsingException;
080 import org.deegree.framework.xml.XMLTools;
081 import org.deegree.io.datastore.schema.MappedFeaturePropertyType;
082 import org.deegree.io.datastore.schema.MappedFeatureType;
083 import org.deegree.model.crs.UnknownCRSException;
084 import org.deegree.model.feature.schema.FeatureType;
085 import org.deegree.model.feature.schema.PropertyType;
086 import org.deegree.model.spatialschema.Envelope;
087 import org.deegree.model.spatialschema.GMLGeometryAdapter;
088 import org.deegree.model.spatialschema.Geometry;
089 import org.deegree.model.spatialschema.GeometryException;
090 import org.deegree.ogcbase.CommonNamespaces;
091 import org.deegree.ogcbase.PropertyPath;
092 import org.deegree.ogcbase.XLinkPropertyPath;
093 import org.w3c.dom.Element;
094 import org.xml.sax.SAXException;
095
096 /**
097 * Exports feature instances to their GML representation.
098 * <p>
099 * Has support for XLink output and to disable XLink output (which is generally not feasible).
100 * <p>
101 * Also responsible for "xlinking" features: if a feature occurs several times in a feature collection, it must be
102 * exported only once - all other occurences must use xlink-attributes in the surrounding property element to reference
103 * the feature.
104 *
105 * TODO Handle FeatureCollections like ordinary Features (needs changes in feature model).
106 *
107 * TODO Separate cycle check (for suppressXLinkOutput).
108 *
109 * TODO Use a more straight-forward approach to export DOM representations.
110 *
111 * TODO Handle multiple application schemas (in xsi:schemaLocation attribute).
112 *
113 * TODO Handle WFS-schema-binding in a subclass in the WFS package?
114 *
115 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
116 * @author last edited by: $Author: apoth $
117 *
118 * @version $Revision: 30885 $, $Date: 2011-05-23 10:12:46 +0200 (Mo, 23 Mai 2011) $
119 */
120 public class GMLFeatureAdapter {
121
122 private static final ILogger LOG = LoggerFactory.getLogger( GMLFeatureAdapter.class );
123
124 private String wfsSchemaBinding = "http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd";
125
126 // values: feature ids of already exported features (for XLinks)
127 private Set<String> exportedFeatures = new HashSet<String>();
128
129 // values: feature ids of all (sub-) features in a feature (to find cyclic features)
130 private Set<String> localFeatures = new HashSet<String>();
131
132 private boolean suppressXLinkOutput;
133
134 private String schemaURL;
135
136 // marks if namespace bindings have been appended already
137 private boolean nsBindingsExported;
138
139 private final DateFormat dateFormatter = new SimpleDateFormat( "yyyy-MM-dd" );
140
141 private final DateFormat timeFormatter = new SimpleDateFormat( "HH:mm:ss" );
142
143 private boolean printGeometryIds;
144
145 private int xlinkDepth = -1;
146
147 private HashMap<String, Integer> xlinkPropertyNames;
148
149 private String baseUrl = "http://localhost:8080/wfs/services";
150
151 /**
152 * Creates a new <code>GMLFeatureAdapter</code> instance with enabled XLink output.
153 */
154 public GMLFeatureAdapter() {
155 this.suppressXLinkOutput = false;
156 this.xlinkPropertyNames = new HashMap<String, Integer>();
157 }
158
159 /**
160 * @param xlinkdepth
161 */
162 public GMLFeatureAdapter( int xlinkdepth ) {
163 xlinkDepth = xlinkdepth;
164 this.xlinkPropertyNames = new HashMap<String, Integer>();
165 }
166
167 /**
168 * Creates a new <code>GMLFeatureAdapter</code> instance with enabled XLink output and schema reference.
169 *
170 * @param schemaURL
171 * URL of schema document (used as xsi:schemaLocation attribute in XML output)
172 */
173 public GMLFeatureAdapter( String schemaURL ) {
174 this.suppressXLinkOutput = false;
175 if ( schemaURL != null ) {
176 this.schemaURL = StringTools.replace( schemaURL, "&", "&", 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 if ( attributes != null ) {
456 for ( Iterator<String> iterator = attributes.keySet().iterator(); iterator.hasNext(); ) {
457 String name = iterator.next();
458 String value = attributes.get( name );
459 pw.print( ' ' );
460 pw.print( name );
461 pw.print( "='" );
462 pw.print( value );
463 pw.print( "'" );
464 }
465 }
466
467 // determine and add namespace bindings
468 Map<String, URI> nsBindings = determineUsedNSBindings( fc );
469 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
470 LOG.logDebug( nsBindings.toString() );
471 }
472 nsBindings.put( "gml", CommonNamespaces.GMLNS );
473 nsBindings.put( "xlink", CommonNamespaces.XLNNS );
474 if ( this.schemaURL != null ) {
475 nsBindings.put( "xsi", CommonNamespaces.XSINS );
476 }
477 appendNSBindings( nsBindings, pw );
478
479 // add schema reference (if available)
480 if ( this.schemaURL != null && fc.size() > 0 ) {
481 pw.print( " xsi:schemaLocation=\"" + fc.getFeature( 0 ).getName().getNamespace() + " " );
482 pw.print( this.schemaURL + " " );
483 pw.print( wfsSchemaBinding + "\"" );
484 }
485 pw.print( '>' );
486
487 Envelope env = null;
488 try {
489 env = fc.getBoundedBy();
490 } catch ( GeometryException e ) {
491 // omit gml:boundedBy-element if featureCollection contains features
492 // with different SRS (and their envelopes cannot be merged)
493 }
494 if ( env != null ) {
495 pw.print( "<gml:boundedBy><gml:Envelope" );
496 if ( env.getCoordinateSystem() != null ) {
497 pw.print( " srsName='" + env.getCoordinateSystem().getPrefixedName() + "'" );
498 }
499
500 boolean swap = swap( env );
501
502 pw.print( "><gml:lowerCorner>" );
503 pw.print( swap ? env.getMin().getY() : env.getMin().getX() );
504 pw.print( ' ' );
505 pw.print( swap ? env.getMin().getX() : env.getMin().getY() );
506 pw.print( "</gml:lowerCorner><gml:upperCorner>" );
507 pw.print( swap ? env.getMax().getY() : env.getMax().getX() );
508 pw.print( ' ' );
509 pw.print( swap ? env.getMax().getX() : env.getMax().getY() );
510 pw.print( "</gml:upperCorner></gml:Envelope></gml:boundedBy>" );
511 }
512
513 // export all contained features
514 for ( int i = 0; i < fc.size(); i++ ) {
515 Feature feature = fc.getFeature( i );
516 String fid = feature.getId();
517 if ( fid != null && !fid.equals( "" ) && this.exportedFeatures.contains( fid ) && !this.suppressXLinkOutput ) {
518 pw.print( "<gml:featureMember xlink:href=\"#" );
519 pw.print( fid );
520 pw.print( "\"/>" );
521 } else {
522 pw.print( "<gml:featureMember>" );
523 export( feature, pw, xlinkDepth );
524 pw.print( "</gml:featureMember>" );
525 }
526 }
527
528 // export all additional root level features
529 for ( Feature feature : additionalRootLevelFeatures ) {
530 String fid = feature.getId();
531 if ( fid != null && !fid.equals( "" ) && this.exportedFeatures.contains( fid ) ) {
532 throw new RuntimeException();
533 }
534 pw.print( "<gml:featureMember>" );
535 export( feature, pw, xlinkDepth );
536 pw.print( "</gml:featureMember>" );
537 }
538
539 // close the feature collection element
540 pw.print( "</" );
541 pw.print( fc.getName().getPrefixedName() );
542 pw.print( '>' );
543 }
544
545 /**
546 * Determines features that don't originally belong to the root level of the given <code>FeatureCollection</code>,
547 * but which need to be put there in the output, because they are subfeatures inside of properties that have the
548 * content type "gml:ReferenceType" and wouldn't otherwise be exported anywhere.
549 *
550 * @param fc
551 * @return features to be added to the root level
552 */
553 private Set<Feature> determineAdditionalRootLevelFeatures( FeatureCollection fc ) {
554
555 Set<Feature> embeddedFeatures = determineEmbeddedFeatures( fc );
556 Set<Feature> additionalRootFeatures = new HashSet<Feature>();
557 Set<Feature> checkedFeatures = new HashSet<Feature>();
558 for ( int i = 0; i < fc.size(); i++ ) {
559 Feature feature = fc.getFeature( i );
560 determineAdditionalRootLevelFeatures( feature, additionalRootFeatures, embeddedFeatures, checkedFeatures );
561 }
562 return additionalRootFeatures;
563 }
564
565 /**
566 * Determines the features that are embedded in the given feature collection on all levels.
567 * <p>
568 * NOTE: This *excludes* all subfeatures which are only values of "gml:ReferenceType" properties.
569 * </p>
570 *
571 * @param fc
572 * @return all features that are embedded in the GML representation of the given collection
573 */
574 private Set<Feature> determineEmbeddedFeatures( FeatureCollection fc ) {
575 Set<Feature> features = new HashSet<Feature>( fc.size() );
576 for ( int i = 0; i < fc.size(); i++ ) {
577 determineEmbeddedFeatures( features, fc.getFeature( i ) );
578 }
579 return features;
580 }
581
582 private void determineEmbeddedFeatures( Set<Feature> exportedFeatures, Feature feature ) {
583 if ( !exportedFeatures.contains( feature ) ) {
584 exportedFeatures.add( feature );
585 for ( FeatureProperty property : feature.getProperties() ) {
586 Object value = property.getValue();
587 if ( value instanceof Feature ) {
588 Feature subFeature = (Feature) value;
589 if ( feature.getFeatureType() != null && feature.getFeatureType() instanceof MappedFeatureType ) {
590 MappedFeatureType ft = (MappedFeatureType) feature.getFeatureType();
591 MappedFeaturePropertyType pt = (MappedFeaturePropertyType) ft.getProperty( property.getName() );
592 assert pt != null;
593 if ( !pt.isReferenceType() ) {
594 determineEmbeddedFeatures( exportedFeatures, subFeature );
595 }
596 }
597 }
598 }
599 }
600 }
601
602 /**
603 * Determines features that don't originally belong to the root level of the given <code>FeatureCollection</code>,
604 * but which need to be put there in the output, because they are subfeatures inside of properties that have the
605 * content type "gml:ReferenceType".
606 *
607 * @param feature
608 * @param additionalFeatures
609 * to be added to the root level
610 * @param embeddedFeatures
611 * to be added to the root level
612 * @param checkedFeatures
613 * features that have already been checked
614 */
615 private void determineAdditionalRootLevelFeatures( Feature feature, Set<Feature> additionalFeatures,
616 Set<Feature> embeddedFeatures, Set<Feature> checkedFeatures ) {
617 for ( FeatureProperty property : feature.getProperties() ) {
618 Object value = property.getValue();
619 if ( value instanceof Feature ) {
620 Feature subFeature = (Feature) value;
621 if ( !checkedFeatures.contains( subFeature ) ) {
622 if ( feature.getFeatureType() != null && feature.getFeatureType() instanceof MappedFeatureType ) {
623 MappedFeatureType ft = (MappedFeatureType) feature.getFeatureType();
624 MappedFeaturePropertyType pt = (MappedFeaturePropertyType) ft.getProperty( property.getName() );
625 assert pt != null;
626 if ( pt.isReferenceType() && !embeddedFeatures.contains( subFeature ) ) {
627 additionalFeatures.add( (Feature) value );
628 }
629 }
630 checkedFeatures.add( subFeature );
631 determineAdditionalRootLevelFeatures( subFeature, additionalFeatures, embeddedFeatures,
632 checkedFeatures );
633 }
634 }
635 }
636 }
637
638 /**
639 * Exports a {@link FeatureTupleCollection} instance to the passed {@link OutputStream} formatted as GML.
640 *
641 * @param fc
642 * feature tuple collection to print/export
643 * @param pw
644 * target of the printing/export
645 * @throws FeatureException
646 */
647 private void exportTupleCollection( FeatureTupleCollection fc, PrintWriter pw )
648 throws FeatureException {
649
650 if ( fc.getId() != null && !"".equals( fc.getId() ) ) {
651 this.exportedFeatures.add( fc.getId() );
652 }
653
654 // open the feature collection element
655 pw.print( "<" );
656 pw.print( fc.getName().getPrefixedName() );
657
658 Map<String, String> attributes = fc.getAttributes();
659 if ( attributes != null ) {
660 for ( Iterator<String> iterator = attributes.keySet().iterator(); iterator.hasNext(); ) {
661 String name = iterator.next();
662 String value = attributes.get( name );
663 pw.print( ' ' );
664 pw.print( name );
665 pw.print( "='" );
666 pw.print( value );
667 pw.print( "'" );
668 }
669 }
670
671 // determine and add namespace bindings
672 Map<String, URI> nsBindings = determineUsedNSBindings( fc );
673 nsBindings.put( "gml", CommonNamespaces.GMLNS );
674 nsBindings.put( "xlink", CommonNamespaces.XLNNS );
675 if ( this.schemaURL != null ) {
676 nsBindings.put( "xsi", CommonNamespaces.XSINS );
677 }
678 appendNSBindings( nsBindings, pw );
679
680 // add schema reference (if available)
681 if ( this.schemaURL != null && fc.size() > 0 ) {
682 pw.print( " xsi:schemaLocation=\"" + fc.getTuple( 0 )[0].getName().getNamespace() + " " );
683 pw.print( this.schemaURL + " " );
684 pw.print( wfsSchemaBinding + "\"" );
685 }
686 pw.print( '>' );
687
688 Envelope env = null;
689 try {
690 env = fc.getBoundedBy();
691 } catch ( GeometryException e ) {
692 // omit gml:boundedBy-element if featureCollection contains features
693 // with different SRS (and their envelopes cannot be merged)
694 }
695 if ( env != null ) {
696 pw.print( "<gml:boundedBy><gml:Envelope" );
697 if ( env.getCoordinateSystem() != null ) {
698 pw.print( " srsName='" + env.getCoordinateSystem().getPrefixedName() + "'" );
699 }
700
701 boolean swap = swap( env );
702
703 pw.print( "><gml:lowerCorner>" );
704 pw.print( swap ? env.getMin().getY() : env.getMin().getX() );
705 pw.print( ' ' );
706 pw.print( swap ? env.getMin().getX() : env.getMin().getY() );
707 pw.print( "</gml:lowerCorner><gml:upperCorner>" );
708 pw.print( swap ? env.getMax().getY() : env.getMax().getX() );
709 pw.print( ' ' );
710 pw.print( swap ? env.getMax().getX() : env.getMax().getY() );
711 pw.print( "</gml:upperCorner></gml:Envelope></gml:boundedBy>" );
712 }
713
714 // export all contained feature tuples
715 for ( int i = 0; i < fc.numTuples(); i++ ) {
716 Feature[] features = fc.getTuple( i );
717 pw.print( "<gml:featureTuple>" );
718 for ( Feature feature : features ) {
719 export( feature, pw, xlinkDepth );
720 }
721 pw.print( "</gml:featureTuple>" );
722 }
723
724 // close the feature collection element
725 pw.print( "</" );
726 pw.print( fc.getName().getPrefixedName() );
727 pw.print( '>' );
728 }
729
730 /**
731 * Determines the namespace bindings that are used in the feature collection.
732 * <p>
733 * NOTE: Currently only the bindings for the feature collection's root element and the contained features are
734 * considered. If a subfeature uses another bindings, this binding will be missing in the XML.
735 *
736 * @param fc
737 * feature collection
738 * @return the namespace bindings.
739 */
740 private Map<String, URI> determineUsedNSBindings( FeatureCollection fc ) {
741
742 Map<String, URI> nsBindings = new HashMap<String, URI>();
743
744 // process feature collection element
745 QualifiedName name = fc.getName();
746 nsBindings.put( name.getPrefix(), name.getNamespace() );
747
748 if ( fc instanceof FeatureTupleCollection ) {
749 // process contained feature tuples
750 FeatureTupleCollection ftc = (FeatureTupleCollection) fc;
751 for ( int i = 0; i < ftc.numTuples(); i++ ) {
752 Feature[] features = ftc.getTuple( i );
753 for ( Feature feature : features ) {
754 name = feature.getName();
755 nsBindings.put( name.getPrefix(), name.getNamespace() );
756 }
757 }
758 } else {
759 // process contained features
760 // for ( int i = 0; i < fc.size(); i++ ) {
761 // name = fc.getFeature( i ).getName();
762 // nsBindings.put( name.getPrefix(), name.getNamespace() );
763 // }
764 for ( int i = 0; i < fc.size(); i++ ) {
765 Feature feature = fc.getFeature( i );
766 if ( feature != null ) {
767 nsBindings = determineUsedNSBindings( feature, nsBindings );
768 }
769 }
770 }
771
772 return nsBindings;
773 }
774
775 /**
776 * Determines the namespace bindings that are used in the feature and it's properties
777 *
778 * @param feature
779 * feature
780 * @param nsBindings
781 * to add to.
782 * @return the namespace bindings
783 */
784 private Map<String, URI> determineUsedNSBindings( Feature feature, Map<String, URI> nsBindings ) {
785 if ( nsBindings == null ) {
786 nsBindings = new HashMap<String, URI>();
787 }
788 if ( feature != null ) {
789 // process feature element
790 QualifiedName qName = feature.getName();
791 if ( qName != null ) {
792 String prefix = qName.getPrefix();
793 URI ns = qName.getNamespace();
794 if ( ns != null && !"".equals( ns.toASCIIString().trim() ) ) {
795 LOG.logDebug( "Adding qName: " + qName );
796 nsBindings.put( prefix, ns );
797 }
798 }
799
800 // now check for properties which use a namespace
801 FeatureProperty[] featureProperties = feature.getProperties();
802 if ( featureProperties != null ) {
803 for ( FeatureProperty fp : featureProperties ) {
804 if ( fp != null ) {
805 QualifiedName fpName = fp.getName();
806 if ( fpName != null ) {
807 String prefix = fpName.getPrefix();
808 if ( prefix != null && !"".equals( prefix.trim() ) ) {
809 if ( nsBindings.get( prefix ) == null ) {
810 URI ns = fpName.getNamespace();
811 if ( ns != null && !"".equals( ns.toASCIIString().trim() ) ) {
812 LOG.logDebug( "Adding qname: " + fpName );
813 nsBindings.put( prefix, ns );
814 }
815 }
816 }
817 }
818 // Object value = fp.getValue();
819 // if ( value instanceof Feature ) {
820 // determineUsedNSBindings( (Feature) value, nsBindings );
821 // }
822 }
823 }
824 }
825 }
826
827 return nsBindings;
828 }
829
830 /**
831 * Determines the namespace bindings that are used in the feature.
832 * <p>
833 * NOTE: Currently only the bindings for the feature's root element and the contained features are considered. If a
834 * subfeature uses another bindings, this binding will be missing in the XML.
835 *
836 * @param feature
837 * feature
838 * @return the namespace bindings
839 */
840 private Map<String, URI> determineUsedNSBindings( Feature feature ) {
841
842 // reset the counter.
843 Map<String, URI> nsBindings = new HashMap<String, URI>();
844
845 return determineUsedNSBindings( feature, nsBindings );
846 // // process feature element
847 // QualifiedName name = feature.getName();
848 // nsBindings.put( name.getPrefix(), name.getNamespace() );
849
850 }
851
852 /**
853 * Appends the given namespace bindings to the PrintWriter.
854 *
855 * @param bindings
856 * namespace bindings to append
857 * @param pw
858 * PrintWriter to write to
859 */
860 private void appendNSBindings( Map<String, URI> bindings, PrintWriter pw ) {
861
862 Iterator<String> prefixIter = bindings.keySet().iterator();
863 while ( prefixIter.hasNext() ) {
864 String prefix = prefixIter.next();
865 URI nsURI = bindings.get( prefix );
866 if ( prefix == null ) {
867 pw.print( " xmlns=\"" );
868 pw.print( nsURI );
869 pw.print( '\"' );
870 } else {
871 pw.print( " xmlns:" );
872 pw.print( prefix );
873 pw.print( "=\"" );
874 pw.print( nsURI );
875 pw.print( '\"' );
876 }
877 }
878 // if more then one default namespaces were defined, each feature must (re)-determine the
879 // default ns.
880 this.nsBindingsExported = true;// ( defaultNamespaceCounter == 0 );
881 }
882
883 /**
884 * Exports an instance of a <code>Feature</code> to the passed <code>OutputStream</code> formatted as GML. Uses the
885 * deegree system character set for the XML header encoding information.
886 *
887 * @param feature
888 * feature to export
889 * @param os
890 * output stream to write to
891 *
892 * @throws IOException
893 * @throws FeatureException
894 */
895 public void export( Feature feature, OutputStream os )
896 throws IOException, FeatureException {
897 export( feature, os, CharsetUtils.getSystemCharset() );
898 }
899
900 /**
901 * Exports a <code>Feature</code> instance to the passed <code>OutputStream</code> formatted as GML.
902 *
903 * @param feature
904 * feature to export
905 * @param os
906 * output stream to write to
907 * @param charsetName
908 * name of the used charset/encoding (for the XML header)
909 * @throws IOException
910 * @throws FeatureException
911 */
912 public void export( Feature feature, OutputStream os, String charsetName )
913 throws IOException, FeatureException {
914
915 PrintWriter pw = new PrintWriter( new OutputStreamWriter( os, charsetName ) );
916 pw.println( "<?xml version=\"1.0\" encoding=\"" + charsetName + "\"?>" );
917 export( feature, pw, xlinkDepth );
918 pw.close();
919 }
920
921 /**
922 * Exports a <code>Feature</code> instance to the passed <code>PrintWriter</code> as GML.
923 *
924 * @param feature
925 * feature to export
926 * @param pw
927 * PrintWriter to write to
928 * @throws FeatureException
929 */
930 private void export( Feature feature, PrintWriter pw, int currentDepth )
931 throws FeatureException {
932
933 boolean isPseudoFt = false;
934 if ( feature.getFeatureType() != null && feature.getFeatureType() instanceof MappedFeatureType ) {
935 isPseudoFt = ( (MappedFeatureType) feature.getFeatureType() ).isPseudoFeatureType();
936 }
937
938 QualifiedName ftName = feature.getName();
939 String fid = isPseudoFt ? null : feature.getId();
940
941 if ( this.suppressXLinkOutput && fid != null && !"".equals( fid ) ) {
942 if ( this.localFeatures.contains( fid ) ) {
943 String msg = Messages.format( "ERROR_CYLIC_FEATURE", fid );
944 throw new FeatureException( msg );
945 }
946 this.localFeatures.add( fid );
947 }
948
949 // open feature element (add gml:id attribute if feature has an id)
950 pw.print( '<' );
951 pw.print( ftName.getPrefixedName() );
952 if ( fid != null && !fid.equals( "" ) ) {
953 this.exportedFeatures.add( fid );
954 pw.print( " gml:id=\"" );
955 pw.print( fid );
956 pw.print( '\"' );
957 }
958
959 // determine and add namespace bindings
960
961 if ( !this.nsBindingsExported ) {
962
963 Map<String, URI> nsBindings = determineUsedNSBindings( feature );
964 nsBindings.put( "gml", CommonNamespaces.GMLNS );
965 nsBindings.put( "xlink", CommonNamespaces.XLNNS );
966 if ( this.schemaURL != null ) {
967 nsBindings.put( "xsi", CommonNamespaces.XSINS );
968 }
969 appendNSBindings( nsBindings, pw );
970 }
971
972 pw.print( '>' );
973
974 // to get the order right (gml default attributes come BEFORE the envelope, stupid...)
975 boolean boundedByExported = false;
976
977 // export all properties of the feature
978 FeatureProperty[] properties = feature.getProperties();
979
980 int geomProperties = -1; // this counter is actually counted up where geometries are exported (and it's used for
981 // outputting geometry ids)
982 // that's why the methods now all take the counter and return it also
983
984 for ( int i = 0; i < properties.length; i++ ) {
985 boolean exportEnv = !isPseudoFt && !boundedByExported;
986 QualifiedName qn = properties[i].getName();
987 String ln = qn.getLocalName();
988 exportEnv = exportEnv
989 && !( qn.getNamespace().equals( GMLNS ) && ( ln.equals( "description" ) || ln.equals( "name" ) ) );
990
991 if ( exportEnv ) {
992 exportBoundedBy( feature, pw );
993 boundedByExported = true;
994 }
995
996 if ( properties[i] != null && properties[i].getValue() != null ) {
997 geomProperties = exportProperty( feature, properties[i], pw, geomProperties, currentDepth );
998 }
999 }
1000
1001 if ( properties.length == 0 ) {
1002 boolean exportEnv = !isPseudoFt && !boundedByExported;
1003 if ( exportEnv ) {
1004 exportBoundedBy( feature, pw );
1005 boundedByExported = true;
1006 }
1007 }
1008
1009 // close feature element
1010 pw.print( "</" );
1011 pw.print( ftName.getPrefixedName() );
1012 pw.println( '>' );
1013
1014 if ( this.suppressXLinkOutput || fid != null ) {
1015 this.localFeatures.remove( fid );
1016 }
1017 }
1018
1019 private static void exportBoundedBy( Feature feature, PrintWriter pw ) {
1020 try {
1021 Envelope env = null;
1022 if ( ( env = feature.getBoundedBy() ) != null ) {
1023 pw.print( "<gml:boundedBy><gml:Envelope" );
1024 if ( env.getCoordinateSystem() != null ) {
1025 pw.print( " srsName='" + env.getCoordinateSystem().getPrefixedName() + "'" );
1026 }
1027
1028 boolean swap = swap( env );
1029
1030 pw.print( "><gml:lowerCorner>" );
1031 pw.print( swap ? env.getMin().getY() : env.getMin().getX() );
1032 pw.print( ' ' );
1033 pw.print( swap ? env.getMin().getX() : env.getMin().getY() );
1034 pw.print( "</gml:lowerCorner><gml:upperCorner>" );
1035 pw.print( swap ? env.getMax().getY() : env.getMax().getX() );
1036 pw.print( ' ' );
1037 pw.print( swap ? env.getMax().getX() : env.getMax().getY() );
1038 pw.print( "</gml:upperCorner></gml:Envelope></gml:boundedBy>" );
1039 }
1040 } catch ( GeometryException e ) {
1041 LOG.logError( e.getMessage(), e );
1042 }
1043
1044 }
1045
1046 /**
1047 * Exports a <code>FeatureProperty</code> instance to the passed <code>PrintWriter</code> as GML.
1048 *
1049 * @param feature
1050 * feature that the property belongs to
1051 * @param property
1052 * property to export
1053 * @param pw
1054 * PrintWriter to write to
1055 * @param geomProperties
1056 * counter to indicate the number of the current geometry property
1057 * @param currentDepth
1058 * counter to indicate how many levels of xlinks have already been resolved
1059 * @throws FeatureException
1060 */
1061 private int exportProperty( Feature feature, FeatureProperty property, PrintWriter pw, int geomProperties,
1062 int currentDepth )
1063 throws FeatureException {
1064
1065 QualifiedName propertyName = property.getName();
1066 Object value = property.getValue();
1067 Integer d = xlinkPropertyNames.get( property.getName().getPrefixedName() );
1068 int customDepth = d == null ? currentDepth : d;
1069 boolean isReferenceType = false;
1070 if ( feature.getFeatureType() != null && feature.getFeatureType() instanceof MappedFeatureType ) {
1071 MappedFeatureType ft = (MappedFeatureType) feature.getFeatureType();
1072 if ( ft.getProperty( property.getName() ) instanceof MappedFeaturePropertyType ) {
1073 MappedFeaturePropertyType pt = (MappedFeaturePropertyType) ft.getProperty( property.getName() );
1074 isReferenceType = pt.isReferenceType();
1075 }
1076 }
1077
1078 if ( value instanceof Feature ) {
1079 Feature subfeature = (Feature) value;
1080
1081 if ( isReferenceType
1082 || ( subfeature.getId() != null && !subfeature.getId().equals( "" )
1083 && exportedFeatures.contains( subfeature.getId() ) && !this.suppressXLinkOutput ) ) {
1084 if ( exportedFeatures.contains( subfeature.getId() ) ) {
1085 pw.print( "<!-- xlink:href=\"#" );
1086 pw.print( subfeature.getId() );
1087 pw.print( "\" -->" );
1088 pw.print( '<' );
1089 pw.print( propertyName.getPrefixedName() );
1090 pw.print( " xlink:href=\"#" );
1091 pw.print( subfeature.getId() );
1092 pw.print( "\"/>" );
1093 } else {
1094 pw.print( '<' );
1095 pw.print( propertyName.getPrefixedName() );
1096 if ( isReferenceType || currentDepth == 0 || customDepth == 0 ) {
1097 pw.print( " xlink:href=\"#" );
1098 pw.print( subfeature.getId() );
1099 pw.print( "\"" );
1100 }
1101 pw.print( ">" );
1102 if ( !isReferenceType && ( currentDepth != 0 || customDepth != 0 ) ) {
1103 pw.print( "<!-- gml:id=\"" );
1104 pw.print( subfeature.getId() );
1105 pw.print( "\" -->" );
1106 export( subfeature, pw, currentDepth - 1 );
1107 }
1108 pw.print( "</" );
1109 pw.print( propertyName.getPrefixedName() );
1110 pw.print( ">" );
1111 }
1112 } else {
1113 pw.print( '<' );
1114 pw.print( propertyName.getPrefixedName() );
1115 if ( currentDepth == 0 && customDepth == 0 ) {
1116 pw.print( " xlink:href=\"" );
1117 pw.print( baseUrl );
1118 pw.print( "?request=GetGmlObject&version=1.1.0&service=WFS&objectid=" );
1119 pw.print( subfeature.getId() );
1120 pw.print( "\"/>" );
1121 } else {
1122 pw.print( '>' );
1123 pw.print( "<!-- xlink:href=\"#" );
1124 pw.print( subfeature.getId() );
1125 pw.print( "\" -->" );
1126 exportPropertyValue( subfeature, pw, FEATURE, null, 0, currentDepth - 1 );
1127 pw.print( "</" );
1128 pw.print( propertyName.getPrefixedName() );
1129 pw.print( '>' );
1130 }
1131 }
1132 } else if ( value instanceof URL ) {
1133 if ( currentDepth == 0 || isReferenceType || customDepth == 0 ) {
1134 pw.print( '<' );
1135 pw.print( propertyName.getPrefixedName() );
1136 pw.print( " xlink:href=\"" );
1137 pw.print( escape( value.toString() ) );
1138 pw.print( "\"/>" );
1139 } else {
1140 pw.print( '<' );
1141 pw.print( propertyName.getPrefixedName() );
1142 pw.print( ">" );
1143 pw.print( "<!-- xlink:href=\"" );
1144 pw.print( value );
1145 pw.print( "\" -->" );
1146
1147 try {
1148 GMLFeatureDocument doc = new GMLFeatureDocument();
1149 URLConnection conn = ( (URL) value ).openConnection();
1150 conn.setReadTimeout( 5000 );
1151 conn.setConnectTimeout( 1000 );
1152 doc.load( conn.getInputStream(), ( (URL) value ).toExternalForm() );
1153 export( doc.parseFeature(), pw, currentDepth - 1 );
1154 pw.print( "</" );
1155 pw.print( propertyName.getPrefixedName() );
1156 pw.print( ">" );
1157 } catch ( IOException e ) {
1158 e.printStackTrace();
1159 LOG.logDebug( "Stack trace:", e );
1160 throw new FeatureException( org.deegree.i18n.Messages.get( "DATASTORE_XLINK_IO_RETRIEVE",
1161 e.getLocalizedMessage() ) );
1162 } catch ( SAXException e ) {
1163 e.printStackTrace();
1164 LOG.logDebug( "Stack trace:", e );
1165 throw new FeatureException( org.deegree.i18n.Messages.get( "DATASTORE_XLINK_PARSE_ERROR",
1166 e.getLocalizedMessage() ) );
1167 } catch ( XMLParsingException e ) {
1168 e.printStackTrace();
1169 LOG.logDebug( "Stack trace:", e );
1170 throw new FeatureException( org.deegree.i18n.Messages.get( "DATASTORE_XLINK_PARSE_ERROR",
1171 e.getLocalizedMessage() ) );
1172 } catch ( UnknownCRSException e ) {
1173 e.printStackTrace();
1174 LOG.logDebug( "Stack trace:", e );
1175 throw new FeatureException( org.deegree.i18n.Messages.get( "DATASTORE_XLINK_PARSE_ERROR",
1176 e.getLocalizedMessage() ) );
1177 }
1178
1179 }
1180 } else {
1181 pw.print( '<' );
1182 pw.print( propertyName.getPrefixedName() );
1183 pw.print( '>' );
1184 if ( value != null ) {
1185 FeatureType ft = feature.getFeatureType();
1186 PropertyType pt = ft.getProperty( property.getName() );
1187 if ( pt == null || pt.getType() == Types.ANYTYPE ) {
1188 pw.print( value );
1189 } else {
1190 geomProperties = exportPropertyValue( value, pw, pt.getType(), printGeometryIds ? feature.getId()
1191 : null,
1192 geomProperties, currentDepth - 1 );
1193 }
1194 }
1195 pw.print( "</" );
1196 pw.print( propertyName.getPrefixedName() );
1197 pw.print( '>' );
1198 }
1199
1200 return geomProperties;
1201 }
1202
1203 /**
1204 * Exports the value of a property to the passed <code>PrintWriter</code> as GML.
1205 *
1206 * TODO make available and use property type information to determine correct output format (e.g. xs:date, xs:time,
1207 * xs:dateTime are all represented using Date objects and cannot be differentiated at the moment)
1208 *
1209 * @param value
1210 * property value to export
1211 * @param pw
1212 * PrintWriter to write to
1213 * @param type
1214 * the Types.java type code
1215 * @param geomProperties
1216 * counter to indicate the number of the current geometry property
1217 * @param currentDepth
1218 * counter to indicate how many levels of xlinks have already been resolved
1219 * @throws FeatureException
1220 */
1221 private int exportPropertyValue( Object value, PrintWriter pw, int type, String id, int geomProperties,
1222 int currentDepth )
1223 throws FeatureException {
1224 if ( value instanceof Feature ) {
1225 export( (Feature) value, pw, currentDepth );
1226 } else if ( value instanceof Feature[] ) {
1227 Feature[] features = (Feature[]) value;
1228 for ( int i = 0; i < features.length; i++ ) {
1229 export( features[i], pw, currentDepth );
1230 }
1231 } else if ( value instanceof Envelope ) {
1232 exportEnvelope( (Envelope) value, pw );
1233 } else if ( value instanceof FeatureCollection ) {
1234 export( (FeatureCollection) value, pw, currentDepth );
1235 } else if ( value instanceof Geometry ) {
1236 id = id == null ? null : ( id + "_GEOM_" + ++geomProperties );
1237 exportGeometry( (Geometry) value, pw, id );
1238 } else if ( value instanceof Date ) {
1239
1240 switch ( type ) {
1241 case DATE: {
1242 pw.print( dateFormatter.format( (Date) value ) );
1243 break;
1244 }
1245 case TIME: {
1246 pw.print( timeFormatter.format( (Date) value ) );
1247 break;
1248 }
1249 case TIMESTAMP: {
1250 pw.print( getISOFormattedTime( (Date) value ) );
1251 break;
1252 }
1253 }
1254
1255 } else if ( value instanceof Calendar ) {
1256 pw.print( TimeTools.getISOFormattedTime( (Calendar) value ) );
1257 } else if ( value instanceof Timestamp ) {
1258 pw.print( TimeTools.getISOFormattedTime( (Timestamp) value ) );
1259 } else if ( value instanceof java.sql.Date ) {
1260 pw.print( TimeTools.getISOFormattedTime( (java.sql.Date) value ) );
1261 } else if ( value instanceof Number || value instanceof BigDecimal ) {
1262 pw.print( value.toString() );
1263 } else if ( value instanceof String ) {
1264 StringBuffer sb = DOMPrinter.validateCDATA( (String) value );
1265 pw.print( sb );
1266 } else if ( value instanceof Boolean ) {
1267 pw.print( value );
1268 } else {
1269 LOG.logInfo( "Unhandled property class '" + value.getClass() + "' in GMLFeatureAdapter." );
1270 StringBuffer sb = DOMPrinter.validateCDATA( value.toString() );
1271 pw.print( sb );
1272 }
1273
1274 return geomProperties;
1275 }
1276
1277 /**
1278 * prints the passed geometry to the also passed PrintWriter formatted as GML
1279 *
1280 * @param geo
1281 * geometry to print/extport
1282 * @param pw
1283 * target of the printing/export
1284 * @throws FeatureException
1285 */
1286 private void exportGeometry( Geometry geo, PrintWriter pw, String id )
1287 throws FeatureException {
1288 try {
1289 GMLGeometryAdapter.export( geo, pw, id );
1290 } catch ( Exception e ) {
1291 LOG.logError( "", e );
1292 throw new FeatureException( "Could not export geometry to GML: " + e.getMessage(), e );
1293 }
1294 }
1295
1296 /**
1297 * prints the passed geometry to the also passed PrintWriter formatted as GML
1298 *
1299 * @param geo
1300 * geometry to print/extport
1301 * @param pw
1302 * target of the printing/export
1303 * @throws FeatureException
1304 */
1305 private void exportEnvelope( Envelope geo, PrintWriter pw )
1306 throws FeatureException {
1307 try {
1308 pw.print( GMLGeometryAdapter.exportAsBox( geo ) );
1309 } catch ( Exception e ) {
1310 throw new FeatureException( "Could not export envelope to GML: " + e.getMessage(), e );
1311 }
1312 }
1313 }