001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/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: 24539 $, $Date: 2010-05-25 11:04:40 +0200 (Di, 25 Mai 2010) $ 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 for ( Iterator<String> iterator = attributes.keySet().iterator(); iterator.hasNext(); ) { 456 String name = iterator.next(); 457 String value = attributes.get( name ); 458 pw.print( ' ' ); 459 pw.print( name ); 460 pw.print( "='" ); 461 pw.print( value ); 462 pw.print( "'" ); 463 } 464 465 // determine and add namespace bindings 466 Map<String, URI> nsBindings = determineUsedNSBindings( fc ); 467 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 468 LOG.logDebug( nsBindings.toString() ); 469 } 470 nsBindings.put( "gml", CommonNamespaces.GMLNS ); 471 nsBindings.put( "xlink", CommonNamespaces.XLNNS ); 472 if ( this.schemaURL != null ) { 473 nsBindings.put( "xsi", CommonNamespaces.XSINS ); 474 } 475 appendNSBindings( nsBindings, pw ); 476 477 // add schema reference (if available) 478 if ( this.schemaURL != null && fc.size() > 0 ) { 479 pw.print( " xsi:schemaLocation=\"" + fc.getFeature( 0 ).getName().getNamespace() + " " ); 480 pw.print( this.schemaURL + " " ); 481 pw.print( wfsSchemaBinding + "\"" ); 482 } 483 pw.print( '>' ); 484 485 Envelope env = null; 486 try { 487 env = fc.getBoundedBy(); 488 } catch ( GeometryException e ) { 489 // omit gml:boundedBy-element if featureCollection contains features 490 // with different SRS (and their envelopes cannot be merged) 491 } 492 if ( env != null ) { 493 pw.print( "<gml:boundedBy><gml:Envelope" ); 494 if ( env.getCoordinateSystem() != null ) { 495 pw.print( " srsName='" + env.getCoordinateSystem().getPrefixedName() + "'" ); 496 } 497 498 boolean swap = swap( env ); 499 500 pw.print( "><gml:lowerCorner>" ); 501 pw.print( swap ? env.getMin().getY() : env.getMin().getX() ); 502 pw.print( ' ' ); 503 pw.print( swap ? env.getMin().getX() : env.getMin().getY() ); 504 pw.print( "</gml:lowerCorner><gml:upperCorner>" ); 505 pw.print( swap ? env.getMax().getY() : env.getMax().getX() ); 506 pw.print( ' ' ); 507 pw.print( swap ? env.getMax().getX() : env.getMax().getY() ); 508 pw.print( "</gml:upperCorner></gml:Envelope></gml:boundedBy>" ); 509 } 510 511 // export all contained features 512 for ( int i = 0; i < fc.size(); i++ ) { 513 Feature feature = fc.getFeature( i ); 514 String fid = feature.getId(); 515 if ( fid != null && !fid.equals( "" ) && this.exportedFeatures.contains( fid ) && !this.suppressXLinkOutput ) { 516 pw.print( "<gml:featureMember xlink:href=\"#" ); 517 pw.print( fid ); 518 pw.print( "\"/>" ); 519 } else { 520 pw.print( "<gml:featureMember>" ); 521 export( feature, pw, xlinkDepth ); 522 pw.print( "</gml:featureMember>" ); 523 } 524 } 525 526 // export all additional root level features 527 for ( Feature feature : additionalRootLevelFeatures ) { 528 String fid = feature.getId(); 529 if ( fid != null && !fid.equals( "" ) && this.exportedFeatures.contains( fid ) ) { 530 throw new RuntimeException (); 531 } 532 pw.print( "<gml:featureMember>" ); 533 export( feature, pw, xlinkDepth ); 534 pw.print( "</gml:featureMember>" ); 535 } 536 537 // close the feature collection element 538 pw.print( "</" ); 539 pw.print( fc.getName().getPrefixedName() ); 540 pw.print( '>' ); 541 } 542 543 /** 544 * Determines features that don't originally belong to the root level of the given <code>FeatureCollection</code>, 545 * but which need to be put there in the output, because they are subfeatures inside of properties that have the 546 * content type "gml:ReferenceType" and wouldn't otherwise be exported anywhere. 547 * 548 * @param fc 549 * @return features to be added to the root level 550 */ 551 private Set<Feature> determineAdditionalRootLevelFeatures( FeatureCollection fc ) { 552 553 Set<Feature> embeddedFeatures = determineEmbeddedFeatures( fc ); 554 Set<Feature> additionalRootFeatures = new HashSet<Feature>(); 555 Set<Feature> checkedFeatures = new HashSet<Feature>(); 556 for ( int i = 0; i < fc.size(); i++ ) { 557 Feature feature = fc.getFeature( i ); 558 determineAdditionalRootLevelFeatures( feature, additionalRootFeatures, embeddedFeatures, checkedFeatures ); 559 } 560 return additionalRootFeatures; 561 } 562 563 /** 564 * Determines the features that are embedded in the given feature collection on all levels. 565 * <p> 566 * NOTE: This *excludes* all subfeatures which are only values of "gml:ReferenceType" properties. 567 * </p> 568 * 569 * @param fc 570 * @return all features that are embedded in the GML representation of the given collection 571 */ 572 private Set<Feature> determineEmbeddedFeatures( FeatureCollection fc ) { 573 Set<Feature> features = new HashSet<Feature>( fc.size() ); 574 for ( int i = 0; i < fc.size(); i++ ) { 575 determineEmbeddedFeatures( features, fc.getFeature( i ) ); 576 } 577 return features; 578 } 579 580 private void determineEmbeddedFeatures( Set<Feature> exportedFeatures, Feature feature ) { 581 if ( !exportedFeatures.contains( feature ) ) { 582 exportedFeatures.add( feature ); 583 for ( FeatureProperty property : feature.getProperties() ) { 584 Object value = property.getValue(); 585 if ( value instanceof Feature ) { 586 Feature subFeature = (Feature) value; 587 if ( feature.getFeatureType() != null && feature.getFeatureType() instanceof MappedFeatureType ) { 588 MappedFeatureType ft = (MappedFeatureType) feature.getFeatureType(); 589 MappedFeaturePropertyType pt = (MappedFeaturePropertyType) ft.getProperty( property.getName() ); 590 assert pt != null; 591 if ( !pt.isReferenceType() ) { 592 determineEmbeddedFeatures( exportedFeatures, subFeature ); 593 } 594 } 595 } 596 } 597 } 598 } 599 600 /** 601 * Determines features that don't originally belong to the root level of the given <code>FeatureCollection</code>, 602 * but which need to be put there in the output, because they are subfeatures inside of properties that have the 603 * content type "gml:ReferenceType". 604 * 605 * @param feature 606 * @param additionalFeatures 607 * to be added to the root level 608 * @param embeddedFeatures 609 * to be added to the root level 610 * @param checkedFeatures 611 * features that have already been checked 612 */ 613 private void determineAdditionalRootLevelFeatures( Feature feature, Set<Feature> additionalFeatures, 614 Set<Feature> embeddedFeatures, Set<Feature> checkedFeatures ) { 615 for ( FeatureProperty property : feature.getProperties() ) { 616 Object value = property.getValue(); 617 if ( value instanceof Feature ) { 618 Feature subFeature = (Feature) value; 619 if ( !checkedFeatures.contains( subFeature ) ) { 620 if ( feature.getFeatureType() != null && feature.getFeatureType() instanceof MappedFeatureType ) { 621 MappedFeatureType ft = (MappedFeatureType) feature.getFeatureType(); 622 MappedFeaturePropertyType pt = (MappedFeaturePropertyType) ft.getProperty( property.getName() ); 623 assert pt != null; 624 if ( pt.isReferenceType() && !embeddedFeatures.contains( subFeature ) ) { 625 additionalFeatures.add( (Feature) value ); 626 } 627 } 628 checkedFeatures.add( subFeature ); 629 determineAdditionalRootLevelFeatures( subFeature, additionalFeatures, embeddedFeatures, checkedFeatures ); 630 } 631 } 632 } 633 } 634 635 /** 636 * Exports a {@link FeatureTupleCollection} instance to the passed {@link OutputStream} formatted as GML. 637 * 638 * @param fc 639 * feature tuple collection to print/export 640 * @param pw 641 * target of the printing/export 642 * @throws FeatureException 643 */ 644 private void exportTupleCollection( FeatureTupleCollection fc, PrintWriter pw ) 645 throws FeatureException { 646 647 if ( fc.getId() != null && !"".equals( fc.getId() ) ) { 648 this.exportedFeatures.add( fc.getId() ); 649 } 650 651 // open the feature collection element 652 pw.print( "<" ); 653 pw.print( fc.getName().getPrefixedName() ); 654 655 Map<String, String> attributes = fc.getAttributes(); 656 for ( Iterator<String> iterator = attributes.keySet().iterator(); iterator.hasNext(); ) { 657 String name = iterator.next(); 658 String value = attributes.get( name ); 659 pw.print( ' ' ); 660 pw.print( name ); 661 pw.print( "='" ); 662 pw.print( value ); 663 pw.print( "'" ); 664 } 665 666 // determine and add namespace bindings 667 Map<String, URI> nsBindings = determineUsedNSBindings( fc ); 668 nsBindings.put( "gml", CommonNamespaces.GMLNS ); 669 nsBindings.put( "xlink", CommonNamespaces.XLNNS ); 670 if ( this.schemaURL != null ) { 671 nsBindings.put( "xsi", CommonNamespaces.XSINS ); 672 } 673 appendNSBindings( nsBindings, pw ); 674 675 // add schema reference (if available) 676 if ( this.schemaURL != null && fc.size() > 0 ) { 677 pw.print( " xsi:schemaLocation=\"" + fc.getTuple( 0 )[0].getName().getNamespace() + " " ); 678 pw.print( this.schemaURL + " " ); 679 pw.print( wfsSchemaBinding + "\"" ); 680 } 681 pw.print( '>' ); 682 683 Envelope env = null; 684 try { 685 env = fc.getBoundedBy(); 686 } catch ( GeometryException e ) { 687 // omit gml:boundedBy-element if featureCollection contains features 688 // with different SRS (and their envelopes cannot be merged) 689 } 690 if ( env != null ) { 691 pw.print( "<gml:boundedBy><gml:Envelope" ); 692 if ( env.getCoordinateSystem() != null ) { 693 pw.print( " srsName='" + env.getCoordinateSystem().getPrefixedName() + "'" ); 694 } 695 696 boolean swap = swap( env ); 697 698 pw.print( "><gml:lowerCorner>" ); 699 pw.print( swap ? env.getMin().getY() : env.getMin().getX() ); 700 pw.print( ' ' ); 701 pw.print( swap ? env.getMin().getX() : env.getMin().getY() ); 702 pw.print( "</gml:lowerCorner><gml:upperCorner>" ); 703 pw.print( swap ? env.getMax().getY() : env.getMax().getX() ); 704 pw.print( ' ' ); 705 pw.print( swap ? env.getMax().getX() : env.getMax().getY() ); 706 pw.print( "</gml:upperCorner></gml:Envelope></gml:boundedBy>" ); 707 } 708 709 // export all contained feature tuples 710 for ( int i = 0; i < fc.numTuples(); i++ ) { 711 Feature[] features = fc.getTuple( i ); 712 pw.print( "<gml:featureTuple>" ); 713 for ( Feature feature : features ) { 714 export( feature, pw, xlinkDepth ); 715 } 716 pw.print( "</gml:featureTuple>" ); 717 } 718 719 // close the feature collection element 720 pw.print( "</" ); 721 pw.print( fc.getName().getPrefixedName() ); 722 pw.print( '>' ); 723 } 724 725 /** 726 * Determines the namespace bindings that are used in the feature collection. 727 * <p> 728 * NOTE: Currently only the bindings for the feature collection's root element and the contained features are 729 * considered. If a subfeature uses another bindings, this binding will be missing in the XML. 730 * 731 * @param fc 732 * feature collection 733 * @return the namespace bindings. 734 */ 735 private Map<String, URI> determineUsedNSBindings( FeatureCollection fc ) { 736 737 Map<String, URI> nsBindings = new HashMap<String, URI>(); 738 739 // process feature collection element 740 QualifiedName name = fc.getName(); 741 nsBindings.put( name.getPrefix(), name.getNamespace() ); 742 743 if ( fc instanceof FeatureTupleCollection ) { 744 // process contained feature tuples 745 FeatureTupleCollection ftc = (FeatureTupleCollection) fc; 746 for ( int i = 0; i < ftc.numTuples(); i++ ) { 747 Feature[] features = ftc.getTuple( i ); 748 for ( Feature feature : features ) { 749 name = feature.getName(); 750 nsBindings.put( name.getPrefix(), name.getNamespace() ); 751 } 752 } 753 } else { 754 // process contained features 755 // for ( int i = 0; i < fc.size(); i++ ) { 756 // name = fc.getFeature( i ).getName(); 757 // nsBindings.put( name.getPrefix(), name.getNamespace() ); 758 // } 759 for ( int i = 0; i < fc.size(); i++ ) { 760 Feature feature = fc.getFeature( i ); 761 if ( feature != null ) { 762 nsBindings = determineUsedNSBindings( feature, nsBindings ); 763 } 764 } 765 } 766 767 return nsBindings; 768 } 769 770 /** 771 * Determines the namespace bindings that are used in the feature and it's properties 772 * 773 * @param feature 774 * feature 775 * @param nsBindings 776 * to add to. 777 * @return the namespace bindings 778 */ 779 private Map<String, URI> determineUsedNSBindings( Feature feature, Map<String, URI> nsBindings ) { 780 if ( nsBindings == null ) { 781 nsBindings = new HashMap<String, URI>(); 782 } 783 if ( feature != null ) { 784 // process feature element 785 QualifiedName qName = feature.getName(); 786 if ( qName != null ) { 787 String prefix = qName.getPrefix(); 788 URI ns = qName.getNamespace(); 789 if ( ns != null && !"".equals( ns.toASCIIString().trim() ) ) { 790 LOG.logDebug( "Adding qName: " + qName ); 791 nsBindings.put( prefix, ns ); 792 } 793 } 794 795 // now check for properties which use a namespace 796 FeatureProperty[] featureProperties = feature.getProperties(); 797 if ( featureProperties != null ) { 798 for ( FeatureProperty fp : featureProperties ) { 799 if ( fp != null ) { 800 QualifiedName fpName = fp.getName(); 801 if ( fpName != null ) { 802 String prefix = fpName.getPrefix(); 803 if ( prefix != null && !"".equals( prefix.trim() ) ) { 804 if ( nsBindings.get( prefix ) == null ) { 805 URI ns = fpName.getNamespace(); 806 if ( ns != null && !"".equals( ns.toASCIIString().trim() ) ) { 807 LOG.logDebug( "Adding qname: " + fpName ); 808 nsBindings.put( prefix, ns ); 809 } 810 } 811 } 812 } 813 // Object value = fp.getValue(); 814 // if ( value instanceof Feature ) { 815 // determineUsedNSBindings( (Feature) value, nsBindings ); 816 // } 817 } 818 } 819 } 820 } 821 822 return nsBindings; 823 } 824 825 /** 826 * Determines the namespace bindings that are used in the feature. 827 * <p> 828 * NOTE: Currently only the bindings for the feature's root element and the contained features are considered. If a 829 * subfeature uses another bindings, this binding will be missing in the XML. 830 * 831 * @param feature 832 * feature 833 * @return the namespace bindings 834 */ 835 private Map<String, URI> determineUsedNSBindings( Feature feature ) { 836 837 // reset the counter. 838 Map<String, URI> nsBindings = new HashMap<String, URI>(); 839 840 return determineUsedNSBindings( feature, nsBindings ); 841 // // process feature element 842 // QualifiedName name = feature.getName(); 843 // nsBindings.put( name.getPrefix(), name.getNamespace() ); 844 845 } 846 847 /** 848 * Appends the given namespace bindings to the PrintWriter. 849 * 850 * @param bindings 851 * namespace bindings to append 852 * @param pw 853 * PrintWriter to write to 854 */ 855 private void appendNSBindings( Map<String, URI> bindings, PrintWriter pw ) { 856 857 Iterator<String> prefixIter = bindings.keySet().iterator(); 858 while ( prefixIter.hasNext() ) { 859 String prefix = prefixIter.next(); 860 URI nsURI = bindings.get( prefix ); 861 if ( prefix == null ) { 862 pw.print( " xmlns=\"" ); 863 pw.print( nsURI ); 864 pw.print( '\"' ); 865 } else { 866 pw.print( " xmlns:" ); 867 pw.print( prefix ); 868 pw.print( "=\"" ); 869 pw.print( nsURI ); 870 pw.print( '\"' ); 871 } 872 } 873 // if more then one default namespaces were defined, each feature must (re)-determine the 874 // default ns. 875 this.nsBindingsExported = true;// ( defaultNamespaceCounter == 0 ); 876 } 877 878 /** 879 * Exports an instance of a <code>Feature</code> to the passed <code>OutputStream</code> formatted as GML. Uses the 880 * deegree system character set for the XML header encoding information. 881 * 882 * @param feature 883 * feature to export 884 * @param os 885 * output stream to write to 886 * 887 * @throws IOException 888 * @throws FeatureException 889 */ 890 public void export( Feature feature, OutputStream os ) 891 throws IOException, FeatureException { 892 export( feature, os, CharsetUtils.getSystemCharset() ); 893 } 894 895 /** 896 * Exports a <code>Feature</code> instance to the passed <code>OutputStream</code> formatted as GML. 897 * 898 * @param feature 899 * feature to export 900 * @param os 901 * output stream to write to 902 * @param charsetName 903 * name of the used charset/encoding (for the XML header) 904 * @throws IOException 905 * @throws FeatureException 906 */ 907 public void export( Feature feature, OutputStream os, String charsetName ) 908 throws IOException, FeatureException { 909 910 PrintWriter pw = new PrintWriter( new OutputStreamWriter( os, charsetName ) ); 911 pw.println( "<?xml version=\"1.0\" encoding=\"" + charsetName + "\"?>" ); 912 export( feature, pw, xlinkDepth ); 913 pw.close(); 914 } 915 916 /** 917 * Exports a <code>Feature</code> instance to the passed <code>PrintWriter</code> as GML. 918 * 919 * @param feature 920 * feature to export 921 * @param pw 922 * PrintWriter to write to 923 * @throws FeatureException 924 */ 925 private void export( Feature feature, PrintWriter pw, int currentDepth ) 926 throws FeatureException { 927 928 boolean isPseudoFt = false; 929 if ( feature.getFeatureType() != null && feature.getFeatureType() instanceof MappedFeatureType ) { 930 isPseudoFt = ( (MappedFeatureType) feature.getFeatureType() ).isPseudoFeatureType(); 931 } 932 933 QualifiedName ftName = feature.getName(); 934 String fid = isPseudoFt ? null : feature.getId(); 935 936 if ( this.suppressXLinkOutput && fid != null && !"".equals( fid ) ) { 937 if ( this.localFeatures.contains( fid ) ) { 938 String msg = Messages.format( "ERROR_CYLIC_FEATURE", fid ); 939 throw new FeatureException( msg ); 940 } 941 this.localFeatures.add( fid ); 942 } 943 944 // open feature element (add gml:id attribute if feature has an id) 945 pw.print( '<' ); 946 pw.print( ftName.getPrefixedName() ); 947 if ( fid != null && !fid.equals( "" ) ) { 948 this.exportedFeatures.add( fid ); 949 pw.print( " gml:id=\"" ); 950 pw.print( fid ); 951 pw.print( '\"' ); 952 } 953 954 // determine and add namespace bindings 955 956 if ( !this.nsBindingsExported ) { 957 958 Map<String, URI> nsBindings = determineUsedNSBindings( feature ); 959 nsBindings.put( "gml", CommonNamespaces.GMLNS ); 960 nsBindings.put( "xlink", CommonNamespaces.XLNNS ); 961 if ( this.schemaURL != null ) { 962 nsBindings.put( "xsi", CommonNamespaces.XSINS ); 963 } 964 appendNSBindings( nsBindings, pw ); 965 } 966 967 pw.print( '>' ); 968 969 // to get the order right (gml default attributes come BEFORE the envelope, stupid...) 970 boolean boundedByExported = false; 971 972 // export all properties of the feature 973 FeatureProperty[] properties = feature.getProperties(); 974 975 int geomProperties = -1; // this counter is actually counted up where geometries are exported (and it's used for 976 // outputting geometry ids) 977 // that's why the methods now all take the counter and return it also 978 979 for ( int i = 0; i < properties.length; i++ ) { 980 boolean exportEnv = !isPseudoFt && !boundedByExported; 981 QualifiedName qn = properties[i].getName(); 982 String ln = qn.getLocalName(); 983 exportEnv = exportEnv 984 && !( qn.getNamespace().equals( GMLNS ) && ( ln.equals( "description" ) || ln.equals( "name" ) ) ); 985 986 if ( exportEnv ) { 987 exportBoundedBy( feature, pw ); 988 boundedByExported = true; 989 } 990 991 if ( properties[i] != null && properties[i].getValue() != null ) { 992 geomProperties = exportProperty( feature, properties[i], pw, geomProperties, currentDepth ); 993 } 994 } 995 996 if (properties.length == 0) { 997 boolean exportEnv = !isPseudoFt && !boundedByExported; 998 if ( exportEnv ) { 999 exportBoundedBy( feature, pw ); 1000 boundedByExported = true; 1001 } 1002 } 1003 1004 // close feature element 1005 pw.print( "</" ); 1006 pw.print( ftName.getPrefixedName() ); 1007 pw.println( '>' ); 1008 1009 if ( this.suppressXLinkOutput || fid != null ) { 1010 this.localFeatures.remove( fid ); 1011 } 1012 } 1013 1014 private static void exportBoundedBy( Feature feature, PrintWriter pw ) { 1015 try { 1016 Envelope env = null; 1017 if ( ( env = feature.getBoundedBy() ) != null ) { 1018 pw.print( "<gml:boundedBy><gml:Envelope" ); 1019 if ( env.getCoordinateSystem() != null ) { 1020 pw.print( " srsName='" + env.getCoordinateSystem().getPrefixedName() + "'" ); 1021 } 1022 1023 boolean swap = swap( env ); 1024 1025 pw.print( "><gml:lowerCorner>" ); 1026 pw.print( swap ? env.getMin().getY() : env.getMin().getX() ); 1027 pw.print( ' ' ); 1028 pw.print( swap ? env.getMin().getX() : env.getMin().getY() ); 1029 pw.print( "</gml:lowerCorner><gml:upperCorner>" ); 1030 pw.print( swap ? env.getMax().getY() : env.getMax().getX() ); 1031 pw.print( ' ' ); 1032 pw.print( swap ? env.getMax().getX() : env.getMax().getY() ); 1033 pw.print( "</gml:upperCorner></gml:Envelope></gml:boundedBy>" ); 1034 } 1035 } catch ( GeometryException e ) { 1036 LOG.logError( e.getMessage(), e ); 1037 } 1038 1039 } 1040 1041 /** 1042 * Exports a <code>FeatureProperty</code> instance to the passed <code>PrintWriter</code> as GML. 1043 * 1044 * @param feature 1045 * feature that the property belongs to 1046 * @param property 1047 * property to export 1048 * @param pw 1049 * PrintWriter to write to 1050 * @param geomProperties 1051 * counter to indicate the number of the current geometry property 1052 * @param currentDepth 1053 * counter to indicate how many levels of xlinks have already been resolved 1054 * @throws FeatureException 1055 */ 1056 private int exportProperty( Feature feature, FeatureProperty property, PrintWriter pw, int geomProperties, 1057 int currentDepth ) 1058 throws FeatureException { 1059 1060 QualifiedName propertyName = property.getName(); 1061 Object value = property.getValue(); 1062 Integer d = xlinkPropertyNames.get( property.getName().getPrefixedName() ); 1063 int customDepth = d == null ? currentDepth : d; 1064 boolean isReferenceType = false; 1065 if ( feature.getFeatureType() != null && feature.getFeatureType() instanceof MappedFeatureType ) { 1066 MappedFeatureType ft = (MappedFeatureType) feature.getFeatureType(); 1067 if ( ft.getProperty( property.getName() ) instanceof MappedFeaturePropertyType ) { 1068 MappedFeaturePropertyType pt = (MappedFeaturePropertyType) ft.getProperty( property.getName() ); 1069 isReferenceType = pt.isReferenceType(); 1070 } 1071 } 1072 1073 if ( value instanceof Feature ) { 1074 Feature subfeature = (Feature) value; 1075 1076 if ( isReferenceType 1077 || ( subfeature.getId() != null && !subfeature.getId().equals( "" ) 1078 && exportedFeatures.contains( subfeature.getId() ) && !this.suppressXLinkOutput ) ) { 1079 if ( exportedFeatures.contains( subfeature.getId() ) ) { 1080 pw.print( "<!-- xlink:href=\"#" ); 1081 pw.print( subfeature.getId() ); 1082 pw.print( "\" -->" ); 1083 pw.print( '<' ); 1084 pw.print( propertyName.getPrefixedName() ); 1085 pw.print( " xlink:href=\"#" ); 1086 pw.print( subfeature.getId() ); 1087 pw.print( "\"/>" ); 1088 } else { 1089 pw.print( '<' ); 1090 pw.print( propertyName.getPrefixedName() ); 1091 if ( isReferenceType || currentDepth == 0 || customDepth == 0 ) { 1092 pw.print( " xlink:href=\"#" ); 1093 pw.print( subfeature.getId() ); 1094 pw.print( "\"" ); 1095 } 1096 pw.print( ">" ); 1097 if ( !isReferenceType && ( currentDepth != 0 || customDepth != 0 ) ) { 1098 pw.print( "<!-- gml:id=\"" ); 1099 pw.print( subfeature.getId() ); 1100 pw.print( "\" -->" ); 1101 export( subfeature, pw, currentDepth - 1 ); 1102 } 1103 pw.print( "</" ); 1104 pw.print( propertyName.getPrefixedName() ); 1105 pw.print( ">" ); 1106 } 1107 } else { 1108 pw.print( '<' ); 1109 pw.print( propertyName.getPrefixedName() ); 1110 if ( currentDepth == 0 && customDepth == 0 ) { 1111 pw.print( " xlink:href=\"" ); 1112 pw.print( baseUrl ); 1113 pw.print( "?request=GetGmlObject&version=1.1.0&service=WFS&objectid=" ); 1114 pw.print( subfeature.getId() ); 1115 pw.print( "\"/>" ); 1116 } else { 1117 pw.print( '>' ); 1118 pw.print( "<!-- xlink:href=\"#" ); 1119 pw.print( subfeature.getId() ); 1120 pw.print( "\" -->" ); 1121 exportPropertyValue( subfeature, pw, FEATURE, null, 0, currentDepth - 1 ); 1122 pw.print( "</" ); 1123 pw.print( propertyName.getPrefixedName() ); 1124 pw.print( '>' ); 1125 } 1126 } 1127 } else if ( value instanceof URL ) { 1128 if ( currentDepth == 0 || isReferenceType || customDepth == 0 ) { 1129 pw.print( '<' ); 1130 pw.print( propertyName.getPrefixedName() ); 1131 pw.print( " xlink:href=\"" ); 1132 pw.print( escape( value.toString() ) ); 1133 pw.print( "\"/>" ); 1134 } else { 1135 pw.print( '<' ); 1136 pw.print( propertyName.getPrefixedName() ); 1137 pw.print( ">" ); 1138 pw.print( "<!-- xlink:href=\"" ); 1139 pw.print( value ); 1140 pw.print( "\" -->" ); 1141 1142 try { 1143 GMLFeatureDocument doc = new GMLFeatureDocument(); 1144 URLConnection conn = ( (URL) value ).openConnection(); 1145 conn.setReadTimeout( 5000 ); 1146 conn.setConnectTimeout( 1000 ); 1147 doc.load( conn.getInputStream(), ( (URL) value ).toExternalForm() ); 1148 export( doc.parseFeature(), pw, currentDepth - 1 ); 1149 pw.print( "</" ); 1150 pw.print( propertyName.getPrefixedName() ); 1151 pw.print( ">" ); 1152 } catch ( IOException e ) { 1153 e.printStackTrace(); 1154 LOG.logDebug( "Stack trace:", e ); 1155 throw new FeatureException( org.deegree.i18n.Messages.get( "DATASTORE_XLINK_IO_RETRIEVE", 1156 e.getLocalizedMessage() ) ); 1157 } catch ( SAXException e ) { 1158 e.printStackTrace(); 1159 LOG.logDebug( "Stack trace:", e ); 1160 throw new FeatureException( org.deegree.i18n.Messages.get( "DATASTORE_XLINK_PARSE_ERROR", 1161 e.getLocalizedMessage() ) ); 1162 } catch ( XMLParsingException 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 ( UnknownCRSException 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 } 1173 1174 } 1175 } else { 1176 pw.print( '<' ); 1177 pw.print( propertyName.getPrefixedName() ); 1178 pw.print( '>' ); 1179 if ( value != null ) { 1180 FeatureType ft = feature.getFeatureType(); 1181 PropertyType pt = ft.getProperty( property.getName() ); 1182 if ( pt == null || pt.getType() == Types.ANYTYPE ) { 1183 pw.print( value ); 1184 } else { 1185 geomProperties = exportPropertyValue( value, pw, pt.getType(), printGeometryIds ? feature.getId() 1186 : null, 1187 geomProperties, currentDepth - 1 ); 1188 } 1189 } 1190 pw.print( "</" ); 1191 pw.print( propertyName.getPrefixedName() ); 1192 pw.print( '>' ); 1193 } 1194 1195 return geomProperties; 1196 } 1197 1198 /** 1199 * Exports the value of a property to the passed <code>PrintWriter</code> as GML. 1200 * 1201 * TODO make available and use property type information to determine correct output format (e.g. xs:date, xs:time, 1202 * xs:dateTime are all represented using Date objects and cannot be differentiated at the moment) 1203 * 1204 * @param value 1205 * property value to export 1206 * @param pw 1207 * PrintWriter to write to 1208 * @param type 1209 * the Types.java type code 1210 * @param geomProperties 1211 * counter to indicate the number of the current geometry property 1212 * @param currentDepth 1213 * counter to indicate how many levels of xlinks have already been resolved 1214 * @throws FeatureException 1215 */ 1216 private int exportPropertyValue( Object value, PrintWriter pw, int type, String id, int geomProperties, 1217 int currentDepth ) 1218 throws FeatureException { 1219 if ( value instanceof Feature ) { 1220 export( (Feature) value, pw, currentDepth ); 1221 } else if ( value instanceof Feature[] ) { 1222 Feature[] features = (Feature[]) value; 1223 for ( int i = 0; i < features.length; i++ ) { 1224 export( features[i], pw, currentDepth ); 1225 } 1226 } else if ( value instanceof Envelope ) { 1227 exportEnvelope( (Envelope) value, pw ); 1228 } else if ( value instanceof FeatureCollection ) { 1229 export( (FeatureCollection) value, pw, currentDepth ); 1230 } else if ( value instanceof Geometry ) { 1231 id = id == null ? null : ( id + "_GEOM_" + ++geomProperties ); 1232 exportGeometry( (Geometry) value, pw, id ); 1233 } else if ( value instanceof Date ) { 1234 1235 switch ( type ) { 1236 case DATE: { 1237 pw.print( dateFormatter.format( (Date) value ) ); 1238 break; 1239 } 1240 case TIME: { 1241 pw.print( timeFormatter.format( (Date) value ) ); 1242 break; 1243 } 1244 case TIMESTAMP: { 1245 pw.print( getISOFormattedTime( (Date) value ) ); 1246 break; 1247 } 1248 } 1249 1250 } else if ( value instanceof Calendar ) { 1251 pw.print( TimeTools.getISOFormattedTime( (Calendar) value ) ); 1252 } else if ( value instanceof Timestamp ) { 1253 pw.print( TimeTools.getISOFormattedTime( (Timestamp) value ) ); 1254 } else if ( value instanceof java.sql.Date ) { 1255 pw.print( TimeTools.getISOFormattedTime( (java.sql.Date) value ) ); 1256 } else if ( value instanceof Integer || value instanceof Long || value instanceof Float 1257 || value instanceof Double || value instanceof BigDecimal ) { 1258 pw.print( value.toString() ); 1259 } else if ( value instanceof String ) { 1260 StringBuffer sb = DOMPrinter.validateCDATA( (String) value ); 1261 pw.print( sb ); 1262 } else if ( value instanceof Boolean ) { 1263 pw.print( value ); 1264 } else { 1265 LOG.logInfo( "Unhandled property class '" + value.getClass() + "' in GMLFeatureAdapter." ); 1266 StringBuffer sb = DOMPrinter.validateCDATA( value.toString() ); 1267 pw.print( sb ); 1268 } 1269 1270 return geomProperties; 1271 } 1272 1273 /** 1274 * prints the passed geometry to the also passed PrintWriter formatted as GML 1275 * 1276 * @param geo 1277 * geometry to print/extport 1278 * @param pw 1279 * target of the printing/export 1280 * @throws FeatureException 1281 */ 1282 private void exportGeometry( Geometry geo, PrintWriter pw, String id ) 1283 throws FeatureException { 1284 try { 1285 GMLGeometryAdapter.export( geo, pw, id ); 1286 } catch ( Exception e ) { 1287 LOG.logError( "", e ); 1288 throw new FeatureException( "Could not export geometry to GML: " + e.getMessage(), e ); 1289 } 1290 } 1291 1292 /** 1293 * prints the passed geometry to the also passed PrintWriter formatted as GML 1294 * 1295 * @param geo 1296 * geometry to print/extport 1297 * @param pw 1298 * target of the printing/export 1299 * @throws FeatureException 1300 */ 1301 private void exportEnvelope( Envelope geo, PrintWriter pw ) 1302 throws FeatureException { 1303 try { 1304 pw.print( GMLGeometryAdapter.exportAsBox( geo ) ); 1305 } catch ( Exception e ) { 1306 throw new FeatureException( "Could not export envelope to GML: " + e.getMessage(), e ); 1307 } 1308 } 1309 }