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 }