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