001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/model/feature/GMLFeatureAdapter.java $
002 /*---------------- FILE HEADER ------------------------------------------
003
004 This file is part of deegree.
005 Copyright (C) 2001-2008 by:
006 EXSE, Department of Geography, University of Bonn
007 http://www.giub.uni-bonn.de/deegree/
008 lat/lon GmbH
009 http://www.lat-lon.de
010
011 This library is free software; you can redistribute it and/or
012 modify it under the terms of the GNU Lesser General Public
013 License as published by the Free Software Foundation; either
014 version 2.1 of the License, or (at your option) any later version.
015
016 This library is distributed in the hope that it will be useful,
017 but WITHOUT ANY WARRANTY; without even the implied warranty of
018 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019 Lesser General Public License for more details.
020
021 You should have received a copy of the GNU Lesser General Public
022 License along with this library; if not, write to the Free Software
023 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024
025 Contact:
026
027 Andreas Poth
028 lat/lon GmbH
029 Aennchenstraße 19
030 53177 Bonn
031 Germany
032 E-Mail: poth@lat-lon.de
033
034 Prof. Dr. Klaus Greve
035 Department of Geography
036 University of Bonn
037 Meckenheimer Allee 166
038 53115 Bonn
039 Germany
040 E-Mail: greve@giub.uni-bonn.de
041
042 ---------------------------------------------------------------------------*/
043 package org.deegree.model.feature;
044
045 import java.io.ByteArrayInputStream;
046 import java.io.ByteArrayOutputStream;
047 import java.io.IOException;
048 import java.io.OutputStream;
049 import java.io.OutputStreamWriter;
050 import java.io.PrintWriter;
051 import java.math.BigDecimal;
052 import java.net.URI;
053 import java.sql.Timestamp;
054 import java.util.Calendar;
055 import java.util.Date;
056 import java.util.HashMap;
057 import java.util.HashSet;
058 import java.util.Iterator;
059 import java.util.Map;
060 import java.util.Set;
061
062 import org.deegree.datatypes.QualifiedName;
063 import org.deegree.datatypes.Types;
064 import org.deegree.framework.log.ILogger;
065 import org.deegree.framework.log.LoggerFactory;
066 import org.deegree.framework.util.CharsetUtils;
067 import org.deegree.framework.util.StringTools;
068 import org.deegree.framework.util.TimeTools;
069 import org.deegree.framework.xml.DOMPrinter;
070 import org.deegree.framework.xml.XMLException;
071 import org.deegree.framework.xml.XMLFragment;
072 import org.deegree.framework.xml.XMLTools;
073 import org.deegree.io.datastore.schema.MappedFeaturePropertyType;
074 import org.deegree.io.datastore.schema.MappedFeatureType;
075 import org.deegree.model.feature.schema.FeatureType;
076 import org.deegree.model.feature.schema.PropertyType;
077 import org.deegree.model.spatialschema.Envelope;
078 import org.deegree.model.spatialschema.GMLGeometryAdapter;
079 import org.deegree.model.spatialschema.Geometry;
080 import org.deegree.model.spatialschema.GeometryException;
081 import org.deegree.ogcbase.CommonNamespaces;
082 import org.w3c.dom.Element;
083 import org.xml.sax.SAXException;
084
085 /**
086 * Exports feature instances to their GML representation.
087 * <p>
088 * Has support for XLink output and to disable XLink output (which is generally not feasible).
089 * <p>
090 * Also responsible for "xlinking" features: if a feature occurs several times in a feature
091 * collection, it must be exported only once - all other occurences must use xlink-attributes in the
092 * surrounding property element to reference the feature.
093 *
094 * TODO Handle FeatureCollections like ordinary Features (needs changes in feature model).
095 *
096 * TODO Separate cycle check (for suppressXLinkOutput).
097 *
098 * TODO Use a more straight-forward approach to export DOM representations.
099 *
100 * TODO Handle multiple application schemas (in xsi:schemaLocation attribute).
101 *
102 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
103 * @author last edited by: $Author: apoth $
104 *
105 * @version $Revision: 9343 $, $Date: 2007-12-27 14:30:32 +0100 (Do, 27 Dez 2007) $
106 */
107 public class GMLFeatureAdapter {
108
109 private static final ILogger LOG = LoggerFactory.getLogger( GMLFeatureAdapter.class );
110
111 private static final String WFS_SCHEMA_BINDING = "http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd";
112
113 // values: feature ids of already exported features (for XLinks)
114 private Set<String> exportedFeatures = new HashSet<String>();
115
116 // values: feature ids of all (sub-) features in a feature (to find cyclic features)
117 private Set<String> localFeatures = new HashSet<String>();
118
119 private boolean suppressXLinkOutput;
120
121 private String schemaURL;
122
123 // marks if namespace bindings have been appended already
124 private boolean nsBindingsExported;
125
126 /**
127 * Creates a new <code>GMLFeatureAdapter</code> instance with enabled XLink output.
128 */
129 public GMLFeatureAdapter() {
130 this.suppressXLinkOutput = false;
131 }
132
133 /**
134 * Creates a new <code>GMLFeatureAdapter</code> instance with enabled XLink output and schema
135 * reference.
136 *
137 * @param schemaURL
138 * URL of schema document (used as xsi:schemaLocation attribute in XML output)
139 */
140 public GMLFeatureAdapter( String schemaURL ) {
141 this.suppressXLinkOutput = false;
142 if ( schemaURL != null ) {
143 this.schemaURL = StringTools.replace( schemaURL, "&", "&", true );
144 }
145 }
146
147 /**
148 * Creates a new instance <code>GMLFeatureAdapter</code> with configurable XLink output.
149 *
150 * @param suppressXLinkOutput
151 * set to true, if no XLinks shall be used
152 */
153 public GMLFeatureAdapter( boolean suppressXLinkOutput ) {
154 this.suppressXLinkOutput = suppressXLinkOutput;
155 }
156
157 /**
158 * Creates a new instance <code>GMLFeatureAdapter</code> with configurable XLink output.
159 *
160 * @param suppressXLinkOutput
161 * set to true, if no XLinks shall be used
162 * @param schemaURL
163 * URL of schema document (used as xsi:schemaLocation attribute in XML output)
164 */
165 public GMLFeatureAdapter( boolean suppressXLinkOutput, String schemaURL ) {
166 this.suppressXLinkOutput = suppressXLinkOutput;
167 if ( schemaURL != null ) {
168 this.schemaURL = StringTools.replace( schemaURL, "&", "&", true );
169 }
170 }
171
172 /**
173 * Appends the DOM representation of the given feature to the also given <code>Node</code>.
174 * <p>
175 * TODO do this a better way (append nodes directly without serializing to string and parsing it
176 * again)
177 *
178 * @param root
179 * @param feature
180 * @throws FeatureException
181 * @throws IOException
182 * @throws SAXException
183 */
184 public void append( Element root, Feature feature )
185 throws FeatureException, IOException, SAXException {
186
187 GMLFeatureDocument doc = export( feature );
188 XMLTools.insertNodeInto( doc.getRootElement(), root );
189 }
190
191 /**
192 * Export a <code>Feature</code> to it's XML representation.
193 *
194 * @param feature
195 * feature to export
196 * @return XML representation of feature
197 * @throws IOException
198 * @throws FeatureException
199 * @throws XMLException
200 * @throws SAXException
201 */
202 public GMLFeatureDocument export( Feature feature )
203 throws IOException, FeatureException, XMLException, SAXException {
204
205 ByteArrayOutputStream bos = new ByteArrayOutputStream( 20000 );
206 export( feature, bos );
207 ByteArrayInputStream bis = new ByteArrayInputStream( bos.toByteArray() );
208 bos.close();
209
210 GMLFeatureDocument doc = new GMLFeatureDocument();
211 doc.load( bis, XMLFragment.DEFAULT_URL );
212 return doc;
213 }
214
215 /**
216 * Appends the DOM representation of the given <code>FeatureCollection</code> to the also
217 * given <code>Node</code>.
218 * <p>
219 * TODO do this a better way (append nodes directly without serializing to string and parsing it
220 * again)
221 *
222 * @param root
223 * @param fc
224 * @throws FeatureException
225 * @throws IOException
226 * @throws SAXException
227 */
228 public void append( Element root, FeatureCollection fc )
229 throws FeatureException, IOException, SAXException {
230
231 GMLFeatureCollectionDocument doc = export( fc );
232 XMLTools.insertNodeInto( doc.getRootElement(), root );
233 }
234
235 /**
236 * Export a <code>FeatureCollection</code> to it's XML representation.
237 *
238 * @param fc
239 * feature collection
240 * @return XML representation of feature collection
241 * @throws IOException
242 * @throws FeatureException
243 * @throws XMLException
244 * @throws SAXException
245 */
246 public GMLFeatureCollectionDocument export( FeatureCollection fc )
247 throws IOException, FeatureException, XMLException, SAXException {
248
249 ByteArrayOutputStream bos = new ByteArrayOutputStream( 20000 );
250 export( fc, bos );
251 ByteArrayInputStream bis = new ByteArrayInputStream( bos.toByteArray() );
252 bos.close();
253
254 GMLFeatureCollectionDocument doc = new GMLFeatureCollectionDocument();
255 doc.load( bis, XMLFragment.DEFAULT_URL );
256 return doc;
257 }
258
259 /**
260 * Exports an instance of a <code>FeatureCollection</code> to the passed
261 * <code>OutputStream</code> formatted as GML. Uses the deegree system character set for the
262 * XML header encoding information.
263 *
264 * @param fc
265 * feature collection to export
266 * @param os
267 * output stream to write to
268 *
269 * @throws IOException
270 * @throws FeatureException
271 */
272 public void export( FeatureCollection fc, OutputStream os )
273 throws IOException, FeatureException {
274 export( fc, os, CharsetUtils.getSystemCharset() );
275 }
276
277 /**
278 * Exports a <code>FeatureCollection</code> instance to the passed <code>OutputStream</code>
279 * formatted as GML.
280 *
281 * @param fc
282 * feature collection to export
283 * @param os
284 * output stream to write to
285 * @param charsetName
286 * name of the used charset/encoding (for the XML header)
287 *
288 * @throws IOException
289 * @throws FeatureException
290 */
291 public void export( FeatureCollection fc, OutputStream os, String charsetName )
292 throws IOException, FeatureException {
293
294 PrintWriter pw = new PrintWriter( new OutputStreamWriter( os, charsetName ) );
295 pw.println( "<?xml version=\"1.0\" encoding=\"" + charsetName + "\"?>" );
296 if ( fc instanceof FeatureTupleCollection ) {
297 exportTupleCollection( (FeatureTupleCollection) fc, pw );
298 } else {
299 exportRootCollection( fc, pw );
300 }
301 pw.close();
302 }
303
304 /**
305 * Exports a <code>FeatureCollection</code> instance to the passed <code>OutputStream</code>
306 * formatted as GML.
307 *
308 * @param fc
309 * feature collection to print/export
310 * @param pw
311 * target of the printing/export
312 * @throws FeatureException
313 */
314 private void exportRootCollection( FeatureCollection fc, PrintWriter pw )
315 throws FeatureException {
316
317 Set<Feature> additionalRootLevelFeatures = determineAdditionalRootLevelFeatures( fc );
318 if ( this.suppressXLinkOutput && additionalRootLevelFeatures.size() > 0 ) {
319 String msg = Messages.getString( "ERROR_REFERENCE_TYPE" );
320 throw new FeatureException( msg );
321 }
322
323 if ( fc.getId() != null && !"".equals( fc.getId() ) ) {
324 this.exportedFeatures.add( fc.getId() );
325 }
326
327 // open the feature collection element
328 pw.print( "<" );
329 pw.print( fc.getName().getPrefixedName() );
330
331 // hack to correct the "numberOfFeatures" attribute (can not be set in any case, because
332 // sometimes (resultType="hits") the collection contains no features at all -- but only the
333 // attribute "numberOfFeatures"
334 if ( fc.size() > 0 ) {
335 int hackedFeatureCount = fc.size() + additionalRootLevelFeatures.size();
336 fc.setAttribute( "numberOfFeatures", "" + hackedFeatureCount );
337 }
338
339 Map<String, String> attributes = fc.getAttributes();
340 for ( Iterator iterator = attributes.keySet().iterator(); iterator.hasNext(); ) {
341 String name = (String) iterator.next();
342 String value = attributes.get( name );
343 pw.print( ' ' );
344 pw.print( name );
345 pw.print( "='" );
346 pw.print( value );
347 pw.print( "'" );
348 }
349
350 // determine and add namespace bindings
351 Map<String, URI> nsBindings = determineUsedNSBindings( fc );
352 nsBindings.put( "gml", CommonNamespaces.GMLNS );
353 nsBindings.put( "xlink", CommonNamespaces.XLNNS );
354 if ( this.schemaURL != null ) {
355 nsBindings.put( "xsi", CommonNamespaces.XSINS );
356 }
357 appendNSBindings( nsBindings, pw );
358
359 // add schema reference (if available)
360 if ( this.schemaURL != null && fc.size() > 0 ) {
361 pw.print( " xsi:schemaLocation=\"" + fc.getFeature( 0 ).getName().getNamespace() + " " );
362 pw.print( this.schemaURL + " " );
363 pw.print( WFS_SCHEMA_BINDING + "\"" );
364 }
365 pw.print( '>' );
366
367 Envelope env = null;
368 try {
369 env = fc.getBoundedBy();
370 } catch ( GeometryException e ) {
371 // omit gml:boundedBy-element if featureCollection contains features
372 // with different SRS (and their envelopes cannot be merged)
373 }
374 if ( env != null ) {
375 pw.print( "<gml:boundedBy><gml:Envelope" );
376 if ( env.getCoordinateSystem() != null ) {
377 pw.print( " srsName='" + env.getCoordinateSystem().getPrefixedName() + "'" );
378 }
379 pw.print( "><gml:pos srsDimension='2'>" );
380 pw.print( env.getMin().getX() );
381 pw.print( ' ' );
382 pw.print( env.getMin().getY() );
383 pw.print( "</gml:pos><gml:pos srsDimension='2'>" );
384 pw.print( env.getMax().getX() );
385 pw.print( ' ' );
386 pw.print( env.getMax().getY() );
387 pw.print( "</gml:pos></gml:Envelope></gml:boundedBy>" );
388 }
389
390 // export all contained features
391 for ( int i = 0; i < fc.size(); i++ ) {
392 Feature feature = fc.getFeature( i );
393 String fid = feature.getId();
394 if ( fid != null && !fid.equals( "" ) && this.exportedFeatures.contains( fid ) && !this.suppressXLinkOutput ) {
395 pw.print( "<gml:featureMember xlink:href=\"#" );
396 pw.print( fid );
397 pw.print( "\"/>" );
398 } else {
399 pw.print( "<gml:featureMember>" );
400 export( feature, pw );
401 pw.print( "</gml:featureMember>" );
402 }
403 }
404
405 // export all additional root level features
406 for ( Feature feature : additionalRootLevelFeatures ) {
407 String fid = feature.getId();
408 if ( fid != null && !fid.equals( "" ) && this.exportedFeatures.contains( fid ) ) {
409 pw.print( "<gml:featureMember xlink:href=\"#" );
410 pw.print( fid );
411 pw.print( "\"/>" );
412 } else {
413 pw.print( "<gml:featureMember>" );
414 export( feature, pw );
415 pw.print( "</gml:featureMember>" );
416 }
417 }
418
419 // close the feature collection element
420 pw.print( "</" );
421 pw.print( fc.getName().getPrefixedName() );
422 pw.print( '>' );
423 }
424
425 /**
426 * Determines features that don't originally belong to the root level of the given
427 * <code>FeatureCollection</code>, but which need to be put there in the output, because they
428 * are subfeatures inside of properties that have the content type "gml:ReferenceType".
429 *
430 * @param fc
431 * @return features to be added to the root level
432 */
433 private Set<Feature> determineAdditionalRootLevelFeatures( FeatureCollection fc ) {
434
435 Set<Feature> rootFeatures = new HashSet<Feature>( fc.size() );
436 for ( int i = 0; i < fc.size(); i++ ) {
437 rootFeatures.add( fc.getFeature( i ) );
438 }
439 Set<Feature> additionalRootFeatures = new HashSet<Feature>();
440 Set<Feature> checkedFeatures = new HashSet<Feature>();
441 for ( int i = 0; i < fc.size(); i++ ) {
442 Feature feature = fc.getFeature( i );
443 determineAdditionalRootLevelFeatures( feature, additionalRootFeatures, rootFeatures, checkedFeatures );
444 }
445 return additionalRootFeatures;
446 }
447
448 /**
449 * Determines features that don't originally belong to the root level of the given
450 * <code>FeatureCollection</code>, but which need to be put there in the output, because they
451 * are subfeatures inside of properties that have the content type "gml:ReferenceType".
452 *
453 * @param fc
454 * @param additionalFeatures
455 * to be added to the root level
456 * @param rootFeatures
457 * to be added to the root level
458 * @param checkedFeatures
459 * features that have already been checked
460 */
461 private void determineAdditionalRootLevelFeatures( Feature feature, Set<Feature> additionalFeatures,
462 Set<Feature> rootFeatures, Set<Feature> checkedFeatures ) {
463 for ( FeatureProperty property : feature.getProperties() ) {
464 Object value = property.getValue();
465 if ( value instanceof Feature ) {
466 Feature subFeature = (Feature) value;
467 if ( !checkedFeatures.contains( subFeature ) ) {
468 if ( feature.getFeatureType() != null && feature.getFeatureType() instanceof MappedFeatureType ) {
469 MappedFeatureType ft = (MappedFeatureType) feature.getFeatureType();
470 MappedFeaturePropertyType pt = (MappedFeaturePropertyType) ft.getProperty( property.getName() );
471 assert pt != null;
472 if ( pt.isReferenceType() && !rootFeatures.contains( subFeature ) ) {
473 additionalFeatures.add( (Feature) value );
474 }
475 }
476 checkedFeatures.add( subFeature );
477 determineAdditionalRootLevelFeatures( subFeature, additionalFeatures, rootFeatures, checkedFeatures );
478 }
479 }
480 }
481 }
482
483 /**
484 * Exports a {@link FeatureTupleCollection} instance to the passed {@link OutputStream}
485 * formatted as GML.
486 *
487 * @param fc
488 * feature tuple collection to print/export
489 * @param pw
490 * target of the printing/export
491 * @throws FeatureException
492 */
493 private void exportTupleCollection( FeatureTupleCollection fc, PrintWriter pw )
494 throws FeatureException {
495
496 if ( fc.getId() != null && !"".equals( fc.getId() ) ) {
497 this.exportedFeatures.add( fc.getId() );
498 }
499
500 // open the feature collection element
501 pw.print( "<" );
502 pw.print( fc.getName().getPrefixedName() );
503
504 Map<String, String> attributes = fc.getAttributes();
505 for ( Iterator iterator = attributes.keySet().iterator(); iterator.hasNext(); ) {
506 String name = (String) iterator.next();
507 String value = attributes.get( name );
508 pw.print( ' ' );
509 pw.print( name );
510 pw.print( "='" );
511 pw.print( value );
512 pw.print( "'" );
513 }
514
515 // determine and add namespace bindings
516 Map<String, URI> nsBindings = determineUsedNSBindings( fc );
517 nsBindings.put( "gml", CommonNamespaces.GMLNS );
518 nsBindings.put( "xlink", CommonNamespaces.XLNNS );
519 if ( this.schemaURL != null ) {
520 nsBindings.put( "xsi", CommonNamespaces.XSINS );
521 }
522 appendNSBindings( nsBindings, pw );
523
524 // add schema reference (if available)
525 if ( this.schemaURL != null && fc.size() > 0 ) {
526 pw.print( " xsi:schemaLocation=\"" + fc.getTuple( 0 )[0].getName().getNamespace() + " " );
527 pw.print( this.schemaURL + " " );
528 pw.print( WFS_SCHEMA_BINDING + "\"" );
529 }
530 pw.print( '>' );
531
532 Envelope env = null;
533 try {
534 env = fc.getBoundedBy();
535 } catch ( GeometryException e ) {
536 // omit gml:boundedBy-element if featureCollection contains features
537 // with different SRS (and their envelopes cannot be merged)
538 }
539 if ( env != null ) {
540 pw.print( "<gml:boundedBy><gml:Envelope" );
541 if ( env.getCoordinateSystem() != null ) {
542 pw.print( " srsName='" + env.getCoordinateSystem().getPrefixedName() + "'" );
543 }
544 pw.print( "><gml:pos srsDimension='2'>" );
545 pw.print( env.getMin().getX() );
546 pw.print( ' ' );
547 pw.print( env.getMin().getY() );
548 pw.print( "</gml:pos><gml:pos srsDimension='2'>" );
549 pw.print( env.getMax().getX() );
550 pw.print( ' ' );
551 pw.print( env.getMax().getY() );
552 pw.print( "</gml:pos></gml:Envelope></gml:boundedBy>" );
553 }
554
555 // export all contained feature tuples
556 for ( int i = 0; i < fc.numTuples(); i++ ) {
557 Feature[] features = fc.getTuple( i );
558 pw.print( "<gml:featureTuple>" );
559 for ( Feature feature : features ) {
560 export( feature, pw );
561 }
562 pw.print( "</gml:featureTuple>" );
563 }
564
565 // close the feature collection element
566 pw.print( "</" );
567 pw.print( fc.getName().getPrefixedName() );
568 pw.print( '>' );
569 }
570
571 /**
572 * Determines the namespace bindings that are used in the feature collection.
573 * <p>
574 * NOTE: Currently only the bindings for the feature collection's root element and the contained
575 * features are considered. If a subfeature uses another bindings, this binding will be missing
576 * in the XML.
577 *
578 * @param fc
579 * feature collection
580 * @return the namespace bindings.
581 */
582 private Map<String, URI> determineUsedNSBindings( FeatureCollection fc ) {
583
584 Map<String, URI> nsBindings = new HashMap<String, URI>();
585
586 // process feature collection element
587 QualifiedName name = fc.getName();
588 nsBindings.put( name.getPrefix(), name.getNamespace() );
589
590 if ( fc instanceof FeatureTupleCollection ) {
591 // process contained feature tuples
592 FeatureTupleCollection ftc = (FeatureTupleCollection) fc;
593 for ( int i = 0; i < ftc.numTuples(); i++ ) {
594 Feature[] features = ftc.getTuple( i );
595 for ( Feature feature : features ) {
596 name = feature.getName();
597 nsBindings.put( name.getPrefix(), name.getNamespace() );
598 }
599 }
600 } else {
601 // process contained features
602 for ( int i = 0; i < fc.size(); i++ ) {
603 name = fc.getFeature( i ).getName();
604 nsBindings.put( name.getPrefix(), name.getNamespace() );
605 }
606 }
607
608 return nsBindings;
609 }
610
611 /**
612 * Determines the namespace bindings that are used in the feature.
613 * <p>
614 * NOTE: Currently only the bindings for the feature's root element and the contained features
615 * are considered. If a subfeature uses another bindings, this binding will be missing in the
616 * XML.
617 *
618 * @param fc
619 * feature
620 * @return the namespace bindings
621 */
622 private Map<String, URI> determineUsedNSBindings( Feature feature ) {
623
624 Map<String, URI> nsBindings = new HashMap<String, URI>();
625
626 // process feature element
627 QualifiedName name = feature.getName();
628 nsBindings.put( name.getPrefix(), name.getNamespace() );
629
630 return nsBindings;
631 }
632
633 /**
634 * Appends the given namespace bindings to the PrintWriter.
635 *
636 * @param bindings
637 * namespace bindings to append
638 * @param pw
639 * PrintWriter to write to
640 */
641 private void appendNSBindings( Map<String, URI> bindings, PrintWriter pw ) {
642
643 Iterator<String> prefixIter = bindings.keySet().iterator();
644 while ( prefixIter.hasNext() ) {
645 String prefix = prefixIter.next();
646 URI nsURI = bindings.get( prefix );
647 pw.print( " xmlns:" );
648 pw.print( prefix );
649 pw.print( "=\"" );
650 pw.print( nsURI );
651 pw.print( '\"' );
652 }
653 this.nsBindingsExported = true;
654 }
655
656 /**
657 * Exports an instance of a <code>Feature</code> to the passed <code>OutputStream</code>
658 * formatted as GML. Uses the deegree system character set for the XML header encoding
659 * information.
660 *
661 * @param feature
662 * feature to export
663 * @param os
664 * output stream to write to
665 *
666 * @throws IOException
667 * @throws FeatureException
668 */
669 public void export( Feature feature, OutputStream os )
670 throws IOException, FeatureException {
671 export( feature, os, CharsetUtils.getSystemCharset() );
672 }
673
674 /**
675 * Exports a <code>Feature</code> instance to the passed <code>OutputStream</code> formatted
676 * as GML.
677 *
678 * @param feature
679 * feature to export
680 * @param os
681 * output stream to write to
682 * @param charsetName
683 * name of the used charset/encoding (for the XML header)
684 * @throws IOException
685 * @throws FeatureException
686 */
687 public void export( Feature feature, OutputStream os, String charsetName )
688 throws IOException, FeatureException {
689
690 PrintWriter pw = new PrintWriter( new OutputStreamWriter( os, charsetName ) );
691 pw.println( "<?xml version=\"1.0\" encoding=\"" + charsetName + "\"?>" );
692 export( feature, pw );
693 pw.close();
694 }
695
696 /**
697 * Exports a <code>Feature</code> instance to the passed <code>PrintWriter</code> as GML.
698 *
699 * @param feature
700 * feature to export
701 * @param pw
702 * PrintWriter to write to
703 * @throws FeatureException
704 */
705 private void export( Feature feature, PrintWriter pw )
706 throws FeatureException {
707
708 QualifiedName ftName = feature.getName();
709 String fid = feature.getId();
710
711 if ( this.suppressXLinkOutput && fid != null && !"".equals( fid ) ) {
712 if ( this.localFeatures.contains( fid ) ) {
713 String msg = Messages.format( "ERROR_CYLIC_FEATURE", fid );
714 throw new FeatureException( msg );
715 }
716 this.localFeatures.add( fid );
717 }
718
719 // open feature element (add gml:id attribute if feature has an id)
720 pw.print( '<' );
721 pw.print( ftName.getPrefixedName() );
722 if ( fid != null ) {
723 this.exportedFeatures.add( fid );
724 pw.print( " gml:id=\"" );
725 pw.print( fid );
726 pw.print( '\"' );
727 }
728
729 // determine and add namespace bindings
730 if ( !this.nsBindingsExported ) {
731 Map<String, URI> nsBindings = determineUsedNSBindings( feature );
732 nsBindings.put( "gml", CommonNamespaces.GMLNS );
733 nsBindings.put( "xlink", CommonNamespaces.XLNNS );
734 if ( this.schemaURL != null ) {
735 nsBindings.put( "xsi", CommonNamespaces.XSINS );
736 }
737 appendNSBindings( nsBindings, pw );
738 }
739
740 pw.print( '>' );
741
742 try {
743 Envelope env = null;
744 if ( ( env = feature.getBoundedBy() ) != null ) {
745 pw.print( "<gml:boundedBy><gml:Envelope" );
746 if ( env.getCoordinateSystem() != null ) {
747 pw.print( " srsName='" + env.getCoordinateSystem().getPrefixedName() + "'" );
748 }
749 pw.print( "><gml:pos srsDimension='2'>" );
750 pw.print( env.getMin().getX() );
751 pw.print( ' ' );
752 pw.print( env.getMin().getY() );
753 pw.print( "</gml:pos><gml:pos srsDimension=\"2\">" );
754 pw.print( env.getMax().getX() );
755 pw.print( ' ' );
756 pw.print( env.getMax().getY() );
757 pw.print( "</gml:pos></gml:Envelope></gml:boundedBy>" );
758 }
759 } catch ( GeometryException e ) {
760 LOG.logError( e.getMessage(), e );
761 }
762
763 // export all properties of the feature
764 FeatureProperty[] properties = feature.getProperties();
765 for ( int i = 0; i < properties.length; i++ ) {
766 if ( properties[i] != null && properties[i].getValue() != null ) {
767 exportProperty( feature, properties[i], pw );
768 }
769 }
770
771 // close feature element
772 pw.print( "</" );
773 pw.print( ftName.getPrefixedName() );
774 pw.println( '>' );
775
776 if ( this.suppressXLinkOutput || fid != null ) {
777 this.localFeatures.remove( fid );
778 }
779 }
780
781 /**
782 * Exports a <code>FeatureProperty</code> instance to the passed <code>PrintWriter</code> as
783 * GML.
784 *
785 * @param feature
786 * feature that the property belongs to
787 * @param property
788 * property to export
789 * @param pw
790 * PrintWriter to write to
791 * @throws FeatureException
792 */
793 private void exportProperty( Feature feature, FeatureProperty property, PrintWriter pw )
794 throws FeatureException {
795
796 QualifiedName propertyName = property.getName();
797 Object value = property.getValue();
798
799 if ( value instanceof Feature ) {
800 Feature subfeature = (Feature) value;
801
802 boolean isReferenceType = false;
803 if ( feature.getFeatureType() != null && feature.getFeatureType() instanceof MappedFeatureType ) {
804 MappedFeatureType ft = (MappedFeatureType) feature.getFeatureType();
805 MappedFeaturePropertyType pt = (MappedFeaturePropertyType) ft.getProperty( property.getName() );
806 assert pt != null;
807 isReferenceType = pt.isReferenceType();
808 }
809
810 if ( isReferenceType || ( exportedFeatures.contains( subfeature.getId() ) && !this.suppressXLinkOutput ) ) {
811 pw.print( '<' );
812 pw.print( propertyName.getPrefixedName() );
813 pw.print( " xlink:href=\"#" );
814 pw.print( subfeature.getId() );
815 pw.print( "\"/>" );
816 } else {
817 pw.print( '<' );
818 pw.print( propertyName.getPrefixedName() );
819 pw.print( '>' );
820 exportPropertyValue( subfeature, pw );
821 pw.print( "</" );
822 pw.print( propertyName.getPrefixedName() );
823 pw.print( '>' );
824 }
825 } else {
826 pw.print( '<' );
827 pw.print( propertyName.getPrefixedName() );
828 pw.print( '>' );
829 if ( value != null ) {
830 FeatureType ft = feature.getFeatureType();
831 PropertyType pt = ft.getProperty( property.getName() );
832 if ( pt.getType() == Types.ANYTYPE ) {
833 pw.print( value );
834 } else {
835 exportPropertyValue( value, pw );
836 }
837 }
838 pw.print( "</" );
839 pw.print( propertyName.getPrefixedName() );
840 pw.print( '>' );
841 }
842 }
843
844 /**
845 * Exports the value of a property to the passed <code>PrintWriter</code> as GML.
846 *
847 * TODO make available and use property type information to determine correct output format
848 * (e.g. xs:date, xs:time, xs:dateTime are all represented using Date objects and cannot be
849 * differentiated at the moment)
850 *
851 * @param value
852 * property value to export
853 * @param pw
854 * PrintWriter to write to
855 * @throws FeatureException
856 */
857 private void exportPropertyValue( Object value, PrintWriter pw )
858 throws FeatureException {
859 if ( value instanceof Feature ) {
860 export( (Feature) value, pw );
861 } else if ( value instanceof Feature[] ) {
862 Feature[] features = (Feature[]) value;
863 for ( int i = 0; i < features.length; i++ ) {
864 export( features[i], pw );
865 }
866 } else if ( value instanceof Envelope ) {
867 exportEnvelope( (Envelope) value, pw );
868 } else if ( value instanceof FeatureCollection ) {
869 export( (FeatureCollection) value, pw );
870 } else if ( value instanceof Geometry ) {
871 exportGeometry( (Geometry) value, pw );
872 } else if ( value instanceof Date ) {
873
874 // TODO: use (currently unavailable) property type information to determine correct
875 // output format (e.g. xs:date, xs:time, xs:dateTime are all represented using Date
876 // objects and cannot be differentiated at the moment)
877
878 // pw.print( ( (Date) value ).toString() );
879 pw.print( TimeTools.getISOFormattedTime( (Date) value ) );
880 } else if ( value instanceof Calendar ) {
881 pw.print( TimeTools.getISOFormattedTime( (Calendar) value ) );
882 } else if ( value instanceof Timestamp ) {
883 pw.print( TimeTools.getISOFormattedTime( (Timestamp) value ) );
884 } else if ( value instanceof java.sql.Date ) {
885 pw.print( TimeTools.getISOFormattedTime( (java.sql.Date) value ) );
886 } else if ( value instanceof Integer || value instanceof Long || value instanceof Float
887 || value instanceof Double || value instanceof BigDecimal ) {
888 pw.print( value.toString() );
889 } else if ( value instanceof String ) {
890 StringBuffer sb = DOMPrinter.validateCDATA( (String) value );
891 pw.print( sb );
892 } else if ( value instanceof Boolean ) {
893 pw.print( value );
894 } else {
895 LOG.logInfo( "Unhandled property class '" + value.getClass() + "' in GMLFeatureAdapter." );
896 StringBuffer sb = DOMPrinter.validateCDATA( value.toString() );
897 pw.print( sb );
898 }
899 }
900
901 /**
902 * prints the passed geometry to the also passed PrintWriter formatted as GML
903 *
904 * @param geo
905 * geometry to print/extport
906 * @param pw
907 * target of the printing/export
908 * @throws FeatureException
909 */
910 private void exportGeometry( Geometry geo, PrintWriter pw )
911 throws FeatureException {
912 try {
913 pw.print( GMLGeometryAdapter.export( geo ) );
914 } catch ( Exception e ) {
915 LOG.logError( "", e );
916 throw new FeatureException( "Could not export geometry to GML: " + e.getMessage(), e );
917 }
918 }
919
920 /**
921 * prints the passed geometry to the also passed PrintWriter formatted as GML
922 *
923 * @param geo
924 * geometry to print/extport
925 * @param pw
926 * target of the printing/export
927 * @throws FeatureException
928 */
929 private void exportEnvelope( Envelope geo, PrintWriter pw )
930 throws FeatureException {
931 try {
932 pw.print( GMLGeometryAdapter.exportAsBox( geo ) );
933 } catch ( Exception e ) {
934 throw new FeatureException( "Could not export envelope to GML: " + e.getMessage(), e );
935 }
936 }
937 }