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 }