001 //$HeadURL: $ 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 037 package org.deegree.ogcwebservices.wcts; 038 039 import static org.deegree.framework.xml.XMLTools.appendElement; 040 import static org.deegree.ogcbase.CommonNamespaces.DEEGREEWCTS; 041 import static org.deegree.ogcbase.CommonNamespaces.DEEGREEWCTS_PREFIX; 042 import static org.deegree.ogcbase.CommonNamespaces.OWSNS_1_1_0; 043 import static org.deegree.ogcbase.CommonNamespaces.WCTSNS; 044 import static org.deegree.ogcbase.CommonNamespaces.WCTS_PREFIX; 045 import static org.deegree.ogcbase.CommonNamespaces.XLINK_PREFIX; 046 import static org.deegree.ogcbase.CommonNamespaces.XLNNS; 047 import static org.deegree.ogcwebservices.wcts.operation.Transform.INLINE; 048 import static org.deegree.ogcwebservices.wcts.operation.Transform.MULTIPART; 049 050 import java.io.IOException; 051 import java.util.List; 052 import java.util.Map; 053 054 import javax.vecmath.Point3d; 055 056 import org.deegree.crs.transformations.Transformation; 057 import org.deegree.framework.log.ILogger; 058 import org.deegree.framework.log.LoggerFactory; 059 import org.deegree.framework.util.Pair; 060 import org.deegree.framework.xml.XMLFragment; 061 import org.deegree.framework.xml.XMLTools; 062 import org.deegree.model.crs.CoordinateSystem; 063 import org.deegree.model.feature.FeatureCollection; 064 import org.deegree.model.feature.FeatureException; 065 import org.deegree.model.feature.GMLFeatureAdapter; 066 import org.deegree.model.spatialschema.GMLGeometryAdapter; 067 import org.deegree.model.spatialschema.Geometry; 068 import org.deegree.model.spatialschema.GeometryException; 069 import org.deegree.ogcwebservices.wcts.capabilities.Content; 070 import org.deegree.ogcwebservices.wcts.capabilities.CoverageAbilities; 071 import org.deegree.ogcwebservices.wcts.capabilities.FeatureAbilities; 072 import org.deegree.ogcwebservices.wcts.capabilities.InputOutputFormat; 073 import org.deegree.ogcwebservices.wcts.capabilities.WCTSCapabilities; 074 import org.deegree.ogcwebservices.wcts.capabilities.mdprofiles.MetadataProfile; 075 import org.deegree.ogcwebservices.wcts.capabilities.mdprofiles.TransformationMetadata; 076 import org.deegree.ogcwebservices.wcts.data.FeatureCollectionData; 077 import org.deegree.ogcwebservices.wcts.data.GeometryData; 078 import org.deegree.ogcwebservices.wcts.data.SimpleData; 079 import org.deegree.ogcwebservices.wcts.data.TransformableData; 080 import org.deegree.ogcwebservices.wcts.operation.GetResourceByID; 081 import org.deegree.ogcwebservices.wcts.operation.TransformResponse; 082 import org.deegree.owscommon_1_1_0.Manifest; 083 import org.deegree.owscommon_1_1_0.Metadata; 084 import org.w3c.dom.Document; 085 import org.w3c.dom.Element; 086 import org.w3c.dom.Node; 087 import org.xml.sax.SAXException; 088 089 /** 090 * The <code>XMLFactory</code> provides helper methods to create xml-doc representations of bean encapsulations. 091 * 092 * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a> 093 * 094 * @author last edited by: $Author:$ 095 * 096 * @version $Revision:$, $Date:$ 097 * 098 */ 099 public class XMLFactory { 100 101 private static ILogger LOG = LoggerFactory.getLogger( XMLFactory.class ); 102 103 private static final String PRE = WCTS_PREFIX + ":"; 104 105 private static XMLFragment capabilitiesElement = null; 106 107 private static final String MUTEX = "EMPTY"; 108 109 /** 110 * Exports an GetResourceById bean to xml. 111 * 112 * @param resourceByID 113 * @return a dom-representation. 114 */ 115 public static XMLFragment create( GetResourceByID resourceByID ) { 116 return null; 117 } 118 119 /** 120 * Exports an WCTSCapabilies bean to xml. 121 * 122 * @param capabilities 123 * to be exported. 124 * @return an xml-dom-representation of the given bean or <code>null</code> if the given parameter is 125 * <code>null</code>. 126 * 127 */ 128 public static XMLFragment create( WCTSCapabilities capabilities ) { 129 if ( capabilities == null ) { 130 return null; 131 } 132 if ( capabilitiesElement == null ) { 133 synchronized ( MUTEX ) { 134 if ( capabilitiesElement == null ) { 135 org.deegree.owscommon_1_1_0.XMLFactory fac = new org.deegree.owscommon_1_1_0.XMLFactory(); 136 Document doc = XMLTools.create(); 137 Element root = doc.createElementNS( WCTSNS.toASCIIString(), PRE + "Capabilities" ); 138 capabilitiesElement = new XMLFragment( root ); 139 fac.exportCapabilities( root, capabilities ); 140 Content content = capabilities.getContents(); 141 if ( content != null ) { 142 appendCapabilitiesContent( root, content ); 143 } 144 } 145 try { 146 MUTEX.notifyAll(); 147 } catch ( IllegalMonitorStateException e ) { 148 // nottin 149 } 150 } 151 } 152 return capabilitiesElement; 153 } 154 155 /** 156 * Creates a response to a Transform request. The wcts spec defines it to be a ows_1_1_0:OperationResponse, this 157 * method appends the deegreewcts:MultiParts or the deegreewcts:InlineData element(s) to the root node. 158 * 159 * @param transformResponse 160 * to create. 161 * @param useDeegreeModel 162 * true if the transform response element should be embedded into inline/multipart elements. 163 * @return the ows_1_1_0:OperationResponse with deegreewcts:MultiPart element added or <code>null</code> if the 164 * given param is <code>null</code>. 165 */ 166 public static XMLFragment createResponse( TransformResponse transformResponse, boolean useDeegreeModel ) { 167 if ( transformResponse == null ) { 168 return null; 169 } 170 org.deegree.owscommon_1_1_0.XMLFactory owsFac = new org.deegree.owscommon_1_1_0.XMLFactory(); 171 Element dataElement = null; 172 LOG.logDebug( "Creating tranform operation response." ); 173 Document doc = XMLTools.create(); 174 Element root = doc.createElementNS( DEEGREEWCTS.toASCIIString(), PRE + "OperationResponse" ); 175 XMLFragment result = new XMLFragment( root ); 176 createOperationResponse( owsFac, result.getRootElement(), transformResponse.getInputData() ); 177 if ( useDeegreeModel ) { 178 switch ( transformResponse.getDataPresentation() ) { 179 case INLINE: 180 LOG.logDebug( "Creating tranform operation inline data response." ); 181 dataElement = appendElement( result.getRootElement(), DEEGREEWCTS, DEEGREEWCTS_PREFIX + ":InlineData" ); 182 break; 183 case MULTIPART: // fall through 184 default: 185 LOG.logDebug( "Creating tranform operation multipart data response." ); 186 // result = owsFac.createOperationResponse( transformResponse.getInputData() ); 187 dataElement = appendElement( result.getRootElement(), DEEGREEWCTS, DEEGREEWCTS_PREFIX + ":MultiParts" ); 188 break; 189 } 190 } else { 191 dataElement = root; 192 } 193 appendTransformableData( dataElement, transformResponse, useDeegreeModel ); 194 return result; 195 } 196 197 /** 198 * Will create an XMLFragment which holds the d_wcts:OperationResponse as the root element, values from the given 199 * manifest will be appended by the given ows_1_1 XMLFactory. 200 * 201 * @param owsFactory 202 * an instance of the ows_1-1 XMLFactory 203 * @param root 204 * to append the manifest to. 205 * 206 * @param operationResponse 207 * to create the dom-xml representation from. 208 */ 209 public static void createOperationResponse( org.deegree.owscommon_1_1_0.XMLFactory owsFactory, Element root, 210 Manifest operationResponse ) { 211 if ( operationResponse == null ) { 212 return; 213 } 214 // Document doc = XMLTools.create(); 215 // Element root = doc.createElementNS( DEEGREEWCTS.toASCIIString(), PRE + "OperationResponse" ); 216 owsFactory.appendManifest( root, operationResponse ); 217 // return new XMLFragment( root ); 218 } 219 220 /** 221 * Appends the TransformableData bean, as an xml-dom element to the given root. If either one of the parameters is 222 * <code>null</code>, this method just returns. 223 * 224 * @param root 225 * to append to. 226 * @param response 227 * to get the transformable data from. 228 * @param useDeegreeModel 229 * true if the transform response element should be embedded into inline/multipart elements. 230 */ 231 protected static void appendTransformableData( Element root, TransformResponse response, boolean useDeegreeModel ) { 232 if ( root == null || response == null ) { 233 return; 234 } 235 236 TransformableData<?> transformableData = response.getTransformableData(); 237 LOG.logDebug( "Appending transformable data. " ); 238 if ( transformableData instanceof SimpleData ) { 239 appendSimpleData( root, response.getTargetCRS().getDimension(), (SimpleData) transformableData, 240 useDeegreeModel ); 241 } else if ( transformableData instanceof GeometryData ) { 242 appendGeometryData( root, (GeometryData) transformableData, useDeegreeModel ); 243 } else if ( transformableData instanceof FeatureCollectionData ) { 244 appendFeatureCollectionData( root, (FeatureCollectionData) transformableData, useDeegreeModel ); 245 } 246 } 247 248 /** 249 * Appends a dom-xml document element with the name {http://www.deegree.org/wcts}:FeatureCollectionData. It will 250 * contain all transformed FeaturCollection as it's children. Or if no FeatureCollections were transformed this 251 * element will have no children at all. 252 * <p> 253 * If either one of the parameters is <code>null</code>, this method just returns. 254 * </p> 255 * 256 * @param root 257 * to append to. 258 * @param transformableData 259 * to append. 260 * @param useDeegreeModel 261 * true if the transform response element should be embedded into inline/multipart elements. 262 * 263 */ 264 protected static void appendFeatureCollectionData( Element root, FeatureCollectionData transformableData, 265 boolean useDeegreeModel ) { 266 if ( root == null || transformableData == null ) { 267 return; 268 } 269 LOG.logDebug( "Adding tranformed feature collection data." ); 270 Element featureCollectionElement = null; 271 if ( useDeegreeModel ) { 272 featureCollectionElement = appendElement( root, DEEGREEWCTS, DEEGREEWCTS_PREFIX + ":FeatureCollectionData" ); 273 } else { 274 featureCollectionElement = root; 275 } 276 GMLFeatureAdapter ad = new GMLFeatureAdapter(); 277 List<FeatureCollection> transformedData = transformableData.getTransformedData(); 278 if ( transformedData != null && transformedData.size() >= 0 ) { 279 for ( FeatureCollection featureCollection : transformedData ) { 280 if ( featureCollection != null ) { 281 try { 282 ad.append( featureCollectionElement, featureCollection ); 283 } catch ( FeatureException e ) { 284 LOG.logError( e.getMessage(), e ); 285 } catch ( IOException e ) { 286 LOG.logError( e.getMessage(), e ); 287 } catch ( SAXException e ) { 288 LOG.logError( e.getMessage(), e ); 289 } 290 } 291 } 292 } 293 } 294 295 /** 296 * Appends a dom-xml document element with the name {http://www.deegree.org/wcts}:GeometryData. It it will contain 297 * all transformed Geometries as it's children. Or if no Geometries were transformed this element will have no 298 * children at all. 299 * <p> 300 * If either one of the parameters is <code>null</code>, this method just returns. 301 * </p> 302 * 303 * @param root 304 * to append to. 305 * @param transformableData 306 * to append. 307 * @param useDeegreeModel 308 * true if the transform response element should be embedded into inline/multipart elements. 309 * 310 */ 311 protected static void appendGeometryData( Element root, GeometryData transformableData, boolean useDeegreeModel ) { 312 if ( root == null || transformableData == null ) { 313 return; 314 } 315 LOG.logDebug( "Adding tranformed geometry data." ); 316 Element geometryElement = null; 317 if ( useDeegreeModel ) { 318 geometryElement = appendElement( root, DEEGREEWCTS, DEEGREEWCTS_PREFIX + ":GeometryData" ); 319 } else { 320 geometryElement = root; 321 } 322 Document doc = geometryElement.getOwnerDocument(); 323 List<Geometry> transformedGeometries = transformableData.getTransformedData(); 324 if ( transformedGeometries.size() >= 0 ) { 325 for ( Geometry geom : transformedGeometries ) { 326 if ( geom != null ) { 327 try { 328 StringBuffer sb = GMLGeometryAdapter.export( geom ); 329 Element tmp = XMLTools.getStringFragmentAsElement( sb.toString() ); 330 if ( tmp != null ) { 331 tmp = (Element) doc.importNode( tmp, true ); 332 geometryElement.appendChild( tmp ); 333 } 334 } catch ( GeometryException e ) { 335 LOG.logError( e.getMessage(), e ); 336 } catch ( SAXException e ) { 337 LOG.logError( e.getMessage(), e ); 338 } catch ( IOException e ) { 339 LOG.logError( e.getMessage(), e ); 340 } 341 } 342 } 343 } 344 } 345 346 /** 347 * Appends a dom-xml document element with the name is {http://www.deegree.org/wcts}:SimpleData. It will contain the 348 * points as a separated list as defined by the 'cs' separator. The element has the attribute 'srsDimension'. The 349 * list elements can therefore be interpreted as a tuple of the value of 'srsDimension'. If no points were 350 * transformed the list will be empty. 351 * <p> 352 * If either one of the parameters is <code>null</code>, this method just returns. 353 * </p> 354 * 355 * @param root 356 * to append to. 357 * @param targetDimension 358 * of the target CRS 359 * @param transformableData 360 * to append. 361 * @param useDeegreeModel 362 * true if the transform response element should be embedded into inline/multipart elements. 363 * 364 */ 365 protected static void appendSimpleData( Element root, int targetDimension, SimpleData transformableData, 366 boolean useDeegreeModel ) { 367 if ( root == null || transformableData == null ) { 368 return; 369 } 370 LOG.logDebug( "Adding tranformed simple data." ); 371 Element simpleDataElement = null; 372 if ( useDeegreeModel ) { 373 simpleDataElement = appendElement( root, DEEGREEWCTS, DEEGREEWCTS_PREFIX + ":SimpleData" ); 374 } else { 375 simpleDataElement = root; 376 } 377 int dim = targetDimension; 378 final String ts = transformableData.getTupleSeparator(); 379 simpleDataElement.setAttribute( "ts", ts ); 380 final String cs = transformableData.getCoordinateSeparator(); 381 simpleDataElement.setAttribute( "cs", cs ); 382 List<Point3d> transformedPoints = transformableData.getTransformedData(); 383 StringBuilder sb = new StringBuilder( transformedPoints.size() * dim ); 384 for ( int i = 0; i < transformedPoints.size(); ++i ) { 385 Point3d point = transformedPoints.get( i ); 386 if ( point != null ) { 387 sb.append( point.x ); 388 sb.append( cs ); 389 sb.append( point.y ); 390 if ( dim == 3 ) { 391 sb.append( cs ); 392 sb.append( point.z ); 393 } 394 if ( ( i + 1 ) < transformedPoints.size() ) { 395 sb.append( ts ); 396 } 397 } 398 } 399 XMLTools.setNodeValue( simpleDataElement, sb.toString() ); 400 } 401 402 /** 403 * Appends the WCTSContent bean, as an xml-dom element to the given root. If either one of the parameters is 404 * <code>null</code>, this method just returns. 405 * 406 * @param root 407 * to append the values to. 408 * @param content 409 * to be appended. 410 */ 411 protected static void appendCapabilitiesContent( Element root, Content content ) { 412 if ( content == null || root == null ) { 413 return; 414 } 415 Element contentElement = appendElement( root, WCTSNS, PRE + "Contents" ); 416 Map<String, Transformation> transformations = content.getTransformations(); 417 if ( transformations != null && transformations.size() > 0 ) { 418 for ( String transform : transformations.keySet() ) { 419 if ( transform != null ) { 420 appendElement( contentElement, WCTSNS, PRE + "Transformation", transform ); 421 } 422 } 423 } 424 425 List<String> methods = content.getMethods(); 426 if ( methods != null && methods.size() > 0 ) { 427 for ( String s : methods ) { 428 if ( s != null ) { 429 appendElement( contentElement, WCTSNS, PRE + "Method", s ); 430 } 431 } 432 } 433 434 List<CoordinateSystem> sourceCRSs = content.getSourceCRSs(); 435 if ( sourceCRSs != null && sourceCRSs.size() > 0 ) { 436 for ( CoordinateSystem s : sourceCRSs ) { 437 if ( s != null ) { 438 appendElement( contentElement, WCTSNS, PRE + "SourceCRS", s.getIdentifier() ); 439 } 440 } 441 } 442 443 List<CoordinateSystem> targetCRSs = content.getTargetCRSs(); 444 if ( targetCRSs != null && targetCRSs.size() > 0 ) { 445 for ( CoordinateSystem s : targetCRSs ) { 446 if ( s != null ) { 447 appendElement( contentElement, WCTSNS, PRE + "TargetCRS", s.getIdentifier() ); 448 } 449 } 450 } 451 452 CoverageAbilities cAbilities = content.getCoverageAbilities(); 453 if ( cAbilities != null ) { 454 Element caElement = appendElement( contentElement, WCTSNS, PRE + "CoverageAbilities" ); 455 List<Pair<String, String>> coverageTypes = cAbilities.getCoverageTypes(); 456 if ( coverageTypes != null && coverageTypes.size() > 0 ) { 457 for ( Pair<String, String> values : coverageTypes ) { 458 if ( values != null ) { 459 Element ctElement = appendElement( caElement, WCTSNS, PRE + "CoverageType", values.first ); 460 ctElement.setAttribute( "codeSpace", values.second ); 461 } 462 } 463 } 464 List<InputOutputFormat> cFormats = cAbilities.getCoverageFormats(); 465 if ( cFormats != null && cFormats.size() > 0 ) { 466 for ( InputOutputFormat iof : cFormats ) { 467 appendInputOutput( caElement, iof, "CoverageFormat" ); 468 } 469 } 470 List<Element> interpolationMethods = cAbilities.getInterPolationMethods(); 471 if ( interpolationMethods != null && interpolationMethods.size() > 0 ) { 472 for ( Element element : interpolationMethods ) { 473 if ( element != null ) { 474 Element copyOf = (Element) caElement.getOwnerDocument().importNode( element, true ); 475 caElement.appendChild( copyOf ); 476 } 477 } 478 } 479 480 } 481 FeatureAbilities fAbilities = content.getFeatureAbilities(); 482 if ( fAbilities != null ) { 483 Element faElement = appendElement( contentElement, WCTSNS, PRE + "FeatureAbilities" ); 484 List<Pair<String, String>> geometryTypes = fAbilities.getGeometryTypes(); 485 if ( geometryTypes != null && geometryTypes.size() > 0 ) { 486 for ( Pair<String, String> values : geometryTypes ) { 487 if ( values != null ) { 488 Element ctElement = appendElement( faElement, WCTSNS, PRE + "GeometryType", values.first ); 489 ctElement.setAttribute( "codeSpace", values.second ); 490 } 491 } 492 } 493 List<InputOutputFormat> fFormats = fAbilities.getFeatureFormats(); 494 if ( fFormats != null && fFormats.size() > 0 ) { 495 for ( InputOutputFormat iof : fFormats ) { 496 appendInputOutput( faElement, iof, "FeatureFormat" ); 497 } 498 } 499 faElement.setAttribute( "remoteProperties", fAbilities.getRemoteProperties() ? "true" : "false" ); 500 } 501 502 List<Metadata> metadataList = content.getMetadata(); 503 if ( metadataList != null && metadataList.size() > 0 ) { 504 for ( Metadata attrib : metadataList ) { 505 if ( attrib != null ) { 506 Element mdElement = createMetadataRoot( contentElement, attrib ); 507 if ( mdElement != null ) { 508 Element abst = attrib.getAbstractElement(); 509 if ( abst != null ) { 510 Node n = mdElement.getOwnerDocument().importNode( abst, true ); 511 mdElement.appendChild( n ); 512 } 513 } 514 } 515 } 516 } 517 if ( content.getTransformMetadata() != null ) { 518 for ( MetadataProfile<?> mp : content.getTransformMetadata() ) { 519 if ( mp != null && ( mp instanceof TransformationMetadata ) ) { 520 Element metadata = appendElement( contentElement, OWSNS_1_1_0, PRE + "Metadata" ); 521 TransformationMetadata tmd = (TransformationMetadata) mp; 522 Element tmElem = appendElement( metadata, DEEGREEWCTS, DEEGREEWCTS_PREFIX 523 + ":transformationMetadata" ); 524 tmElem.setAttribute( "sourceCRS", tmd.getSourceCRS().getIdentifier() ); 525 tmElem.setAttribute( "targetCRS", tmd.getTargetCRS().getIdentifier() ); 526 tmElem.setAttribute( "transformationID", tmd.getTransformID() ); 527 String desc = tmd.getDescription(); 528 if ( desc == null || "".equals( desc ) ) { 529 desc = "No description"; 530 } 531 Element description = appendElement( tmElem, DEEGREEWCTS, DEEGREEWCTS_PREFIX + ":description" ); 532 XMLTools.setNodeValue( description, desc ); 533 } 534 } 535 } 536 537 contentElement.setAttribute( "userDefinedCRSs", content.supportsUserDefinedCRS() ? "true" : "false" ); 538 } 539 540 private static Element createMetadataRoot( Element root, Metadata md ) { 541 if ( root == null || md == null ) { 542 return null; 543 } 544 Element metadata = appendElement( root, OWSNS_1_1_0, PRE + "Metadata" ); 545 if ( md.getMetadataHref() != null ) { 546 metadata.setAttributeNS( XLNNS.toASCIIString(), XLINK_PREFIX + ":href", md.getMetadataHref() ); 547 } 548 if ( md.getMetadataAbout() != null ) { 549 metadata.setAttribute( "about", md.getMetadataAbout() ); 550 } 551 return metadata; 552 } 553 554 /** 555 * Appends the input output element 556 * 557 * @param root 558 * to append to 559 * @param inputOutput 560 * to append 561 * @param nodeName 562 * the element name, i.e. CoverageFormat or FeatureFormat 563 */ 564 private static void appendInputOutput( Element root, InputOutputFormat inputOutput, String nodeName ) { 565 if ( inputOutput != null && root != null ) { 566 Element cfElement = appendElement( root, WCTSNS, PRE + nodeName, inputOutput.getValue() ); 567 cfElement.setAttribute( "input", inputOutput.canInput() ? "true" : "false" ); 568 cfElement.setAttribute( "output", inputOutput.canOutput() ? "true" : "false" ); 569 570 } 571 } 572 }