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