001 /*---------------------------------------------------------------------------- 002 This file is part of deegree, http://deegree.org/ 003 Copyright (C) 2001-2009 by: 004 Department of Geography, University of Bonn 005 and 006 lat/lon GmbH 007 008 This library is free software; you can redistribute it and/or modify it under 009 the terms of the GNU Lesser General Public License as published by the Free 010 Software Foundation; either version 2.1 of the License, or (at your option) 011 any later version. 012 This library is distributed in the hope that it will be useful, but WITHOUT 013 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 014 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 015 details. 016 You should have received a copy of the GNU Lesser General Public License 017 along with this library; if not, write to the Free Software Foundation, Inc., 018 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 019 020 Contact information: 021 022 lat/lon GmbH 023 Aennchenstr. 19, 53177 Bonn 024 Germany 025 http://lat-lon.de/ 026 027 Department of Geography, University of Bonn 028 Prof. Dr. Klaus Greve 029 Postfach 1147, 53001 Bonn 030 Germany 031 http://www.geographie.uni-bonn.de/deegree/ 032 033 e-mail: info@deegree.org 034 ----------------------------------------------------------------------------*/ 035 036 package org.deegree.ogcwebservices.wcts.operation; 037 038 import static org.deegree.framework.xml.XMLTools.getElement; 039 import static org.deegree.framework.xml.XMLTools.getElements; 040 import static org.deegree.framework.xml.XMLTools.getNodeAsBoolean; 041 import static org.deegree.framework.xml.XMLTools.getNodeAsString; 042 import static org.deegree.framework.xml.XMLTools.getStringValue; 043 import static org.deegree.ogcbase.CommonNamespaces.DEEGREEWCTS; 044 import static org.deegree.ogcbase.CommonNamespaces.DEEGREEWCTS_PREFIX; 045 import static org.deegree.ogcbase.CommonNamespaces.GML_PREFIX; 046 import static org.deegree.ogcbase.CommonNamespaces.OWS_1_1_0PREFIX; 047 import static org.deegree.ogcbase.CommonNamespaces.WCS_1_2_0_PREFIX; 048 049 import java.util.ArrayList; 050 import java.util.List; 051 052 import javax.vecmath.Point3d; 053 054 import org.deegree.crs.transformations.Transformation; 055 import org.deegree.framework.log.ILogger; 056 import org.deegree.framework.log.LoggerFactory; 057 import org.deegree.framework.xml.XMLParsingException; 058 import org.deegree.framework.xml.XMLTools; 059 import org.deegree.i18n.Messages; 060 import org.deegree.model.crs.CRSFactory; 061 import org.deegree.model.crs.CoordinateSystem; 062 import org.deegree.model.crs.UnknownCRSException; 063 import org.deegree.model.feature.FeatureCollection; 064 import org.deegree.model.feature.GMLFeatureCollectionDocument; 065 import org.deegree.model.spatialschema.GMLGeometryAdapter; 066 import org.deegree.model.spatialschema.Geometry; 067 import org.deegree.model.spatialschema.GeometryException; 068 import org.deegree.ogcbase.CommonNamespaces; 069 import org.deegree.ogcbase.ExceptionCode; 070 import org.deegree.ogcwebservices.OGCWebServiceException; 071 import org.deegree.ogcwebservices.wcts.WCTSExceptionCode; 072 import org.deegree.ogcwebservices.wcts.WCTService; 073 import org.deegree.ogcwebservices.wcts.WCTServiceFactory; 074 import org.deegree.ogcwebservices.wcts.capabilities.Content; 075 import org.deegree.ogcwebservices.wcts.capabilities.FeatureAbilities; 076 import org.deegree.ogcwebservices.wcts.capabilities.InputOutputFormat; 077 import org.deegree.ogcwebservices.wcts.configuration.WCTSConfiguration; 078 import org.deegree.ogcwebservices.wcts.data.FeatureCollectionData; 079 import org.deegree.ogcwebservices.wcts.data.GeometryData; 080 import org.deegree.ogcwebservices.wcts.data.SimpleData; 081 import org.deegree.ogcwebservices.wcts.data.TransformableData; 082 import org.deegree.owscommon_1_1_0.Manifest; 083 import org.deegree.owscommon_1_1_0.ManifestDocument; 084 import org.w3c.dom.Element; 085 import org.w3c.dom.Node; 086 087 /** 088 * <code>WCTSTransformDocument</code> is a helper class which supplies a constructor to parse wcts Transform requests 089 * version 0.4.0. 090 * <p> 091 * Following elements are currently not supported: 092 * <ul> 093 * <li>wcts:transformation</li> 094 * <li>wcs:GridCRS</li> 095 * <li>wcts:InterpolationType, which is of type wcs:InterpolationMethodBaseType</li> 096 * </ul> 097 * </p> 098 * 099 * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a> 100 * 101 * @author last edited by: $Author:$ 102 * 103 * @version $Revision:$, $Date:$ 104 * 105 */ 106 public class TransformDocument extends WCTSRequestBaseDocument { 107 private static ILogger LOG = LoggerFactory.getLogger( TransformDocument.class ); 108 109 private static final long serialVersionUID = 1343985893563449983L; 110 111 private final Transform transformRequest; 112 113 /** 114 * @param requestId 115 * @param rootElement 116 * should not be <code>null</code> 117 * @throws OGCWebServiceException 118 * if an {@link XMLParsingException} occurred or a mandatory element/attribute is missing. 119 */ 120 public TransformDocument( String requestId, Element rootElement ) throws OGCWebServiceException { 121 super( rootElement ); 122 String version = parseVersion(); 123 124 // check for valid request. 125 parseService(); 126 127 try { 128 String sCRS = getNodeAsString( getRootElement(), PRE + "SourceCRS", nsContext, null ); 129 String tCRS = getNodeAsString( getRootElement(), PRE + "TargetCRS", nsContext, null ); 130 CoordinateSystem sourceCRS = null; 131 CoordinateSystem targetCRS = null; 132 TransformationReference transformationReference = null; 133 134 /** 135 * Try to parse the xml choice. 136 */ 137 if ( ( sCRS != null && tCRS == null ) || ( sCRS == null && tCRS != null ) ) { 138 throw new OGCWebServiceException( 139 Messages.getMessage( 140 "WCTS_ISTRANSFORMABLE_MISSING_CRS", 141 ( ( sCRS == null ) ? "TargetCRS" : "SourceCRS" ), 142 ( ( sCRS == null ) ? "SourceCRS" : "TargetCRS" ) ), 143 ExceptionCode.INVALIDPARAMETERVALUE ); 144 } 145 if ( sCRS != null && tCRS != null ) { 146 sourceCRS = CRSFactory.create( WCTService.CRS_PROVIDER, sCRS ); 147 targetCRS = CRSFactory.create( WCTService.CRS_PROVIDER, tCRS ); 148 } else { 149 // Check for supported transformation element. 150 Element transformation = getElement( getRootElement(), PRE + "Transformation", nsContext ); 151 if ( transformation != null ) { 152 transformationReference = handleTransformation( transformation ); 153 } 154 if ( transformationReference == null ) { 155 throw new OGCWebServiceException( 156 Messages.getMessage( "WCTS_NOT_VALID_XML_CHOICE", 157 PRE + "SourceCRS/TargetCRS and " + PRE 158 + "Transformation" ), 159 ExceptionCode.MISSINGPARAMETERVALUE ); 160 } 161 } 162 // Check for not supported gridcrs. 163 Element gridCRS = getElement( getRootElement(), WCS_1_2_0_PREFIX + ":GridCRS", nsContext ); 164 if ( gridCRS != null ) { 165 throw new OGCWebServiceException( Messages.getMessage( "WCTS_OPERATION_NOT_SUPPORTED", 166 "Definition of output GridCRS (" 167 + WCS_1_2_0_PREFIX 168 + ":GridCRS element)" ), 169 ExceptionCode.OPERATIONNOTSUPPORTED ); 170 } 171 172 Element inputDataElement = getElement( getRootElement(), OWS_1_1_0PREFIX + ":InputData", nsContext ); 173 Manifest inputData = null; 174 TransformableData<?> transformableData = null; 175 int dataPresentation = Transform.MULTIPART; 176 if ( inputDataElement != null ) { 177 ManifestDocument doc = new ManifestDocument(); 178 inputData = doc.parseManifestType( inputDataElement ); 179 /** 180 * get deegree specific elements. The featurecollections provided by the mime/multiparts were put 181 * beneath the d_wcts:InsertedMultiparts. 182 */ 183 Element multiParts = getElement( inputDataElement, DEEGREEWCTS_PREFIX + ":MultiParts", nsContext ); 184 if ( multiParts != null ) { 185 List<FeatureCollection> allData = parseFeatureCollectionData( multiParts ); 186 transformableData = new FeatureCollectionData( allData ); 187 } 188 } else { 189 LOG.logDebug( "Found no " + OWS_1_1_0PREFIX 190 + ":InputData element, now checking for the deegree element" ); 191 inputDataElement = getElement( getRootElement(), DEEGREEWCTS_PREFIX + ":InputData", nsContext ); 192 if ( inputDataElement != null ) { 193 ManifestDocument doc = new ManifestDocument(); 194 inputData = doc.parseManifestType( inputDataElement ); 195 Element inlineData = getElement( inputDataElement, DEEGREEWCTS_PREFIX + ":InlineData", nsContext ); 196 if ( inlineData != null ) { 197 if ( sourceCRS == null ) { 198 WCTSConfiguration config = WCTServiceFactory.getConfiguration(); 199 Content cont = config.getContents(); 200 if ( transformationReference == null ) { 201 throw new OGCWebServiceException( 202 Messages.getMessage( "WCTS_OPERATION_NOT_SUPPORTED", 203 " transforming of simple data without a transformation or source CRS " ), 204 ExceptionCode.OPERATIONNOTSUPPORTED ); 205 } 206 Transformation trans = cont.getTransformations().get( 207 transformationReference.gettransformationId() ); 208 if ( trans == null || trans.getSourceCRS() == null ) { 209 throw new OGCWebServiceException( 210 Messages.getMessage( "WCTS_OPERATION_NOT_SUPPORTED", 211 " transforming of simple data without a transformation or source CRS " ), 212 ExceptionCode.OPERATIONNOTSUPPORTED ); 213 } 214 transformableData = parseInlineData( CRSFactory.create( trans.getSourceCRS() ), inlineData ); 215 } else { 216 transformableData = parseInlineData( sourceCRS, inlineData ); 217 } 218 dataPresentation = Transform.INLINE; 219 } else { 220 // Handle the xlink:href attributes of the inputdata/referencegroup/reference@xlink:href. 221 } 222 } 223 } 224 if ( inputData == null || transformableData == null ) { 225 throw new OGCWebServiceException( Messages.getMessage( "WCTS_TRANSFORM_MISSING_DATA" ), 226 WCTSExceptionCode.NO_INPUT_DATA ); 227 } 228 229 // Check for not supported interpolationType. 230 Element interpolationType = getElement( getRootElement(), PRE + "InterpolationType", nsContext ); 231 if ( interpolationType != null ) { 232 throw new OGCWebServiceException( 233 Messages.getMessage( 234 "WCTS_OPERATION_NOT_SUPPORTED", 235 "Defining an InterpolationType (" 236 + PRE 237 + "InterpolationType element)" ), 238 ExceptionCode.OPERATIONNOTSUPPORTED ); 239 } 240 241 String outputFormat = getNodeAsString( getRootElement(), PRE + "OutputFormat", nsContext, null ); 242 if ( outputFormat != null && !"".equals( outputFormat.trim() ) 243 && !"text/xml; gmlVersion=3.1.1".equalsIgnoreCase( outputFormat.trim() ) ) { 244 WCTSConfiguration config = WCTServiceFactory.getConfiguration(); 245 boolean outputFormatDefined = false; 246 if ( config != null ) { 247 Content content = config.getContents(); 248 if ( content != null ) { 249 FeatureAbilities fa = content.getFeatureAbilities(); 250 if ( fa != null ) { 251 List<InputOutputFormat> formats = fa.getFeatureFormats(); 252 if ( formats != null ) { 253 for ( InputOutputFormat format : formats ) { 254 if ( outputFormatDefined && format != null && format.canOutput() ) { 255 outputFormat.equals( format.getValue() ); 256 outputFormatDefined = true; 257 } 258 } 259 } 260 } 261 } 262 } 263 if ( !outputFormatDefined ) { 264 throw new OGCWebServiceException( Messages.getMessage( "WCTS_REQUESTED_OUTPUTFORMAT_NOT_KNOWN", 265 outputFormat, "Transform" ), 266 ExceptionCode.INVALIDPARAMETERVALUE ); 267 } 268 269 } else { 270 outputFormat = "text/xml"; 271 } 272 boolean store = getNodeAsBoolean( getRootElement(), "@store", nsContext, true ); 273 this.transformRequest = new Transform( version, requestId, store, sourceCRS, targetCRS, 274 transformationReference, inputData, transformableData, outputFormat, 275 dataPresentation ); 276 277 } catch ( XMLParsingException e ) { 278 LOG.logError( e.getMessage(), e ); 279 throw new OGCWebServiceException( e.getMessage(), ExceptionCode.NOAPPLICABLECODE ); 280 } catch ( UnknownCRSException e ) { 281 LOG.logError( e.getMessage(), e ); 282 throw new OGCWebServiceException( e.getMessage(), ExceptionCode.NOAPPLICABLECODE ); 283 } 284 } 285 286 /** 287 * @param rootElement 288 * @return the transform reference. 289 * 290 * @throws XMLParsingException 291 * if the xlink:href was not given. 292 */ 293 private TransformationReference handleTransformation( Element rootElement ) 294 throws XMLParsingException { 295 if ( rootElement == null ) { 296 LOG.logDebug( "No transformation element given, using standard transformation type." ); 297 return null; 298 } 299 String id = rootElement.getAttributeNS( CommonNamespaces.XLNNS.toASCIIString(), "href" ); 300 String sourceID = null; 301 String targetID = null; 302 if ( id == null || "".equals( id.trim() ) ) { 303 Element sCRSNode = XMLTools.getElement( rootElement, PRE + "sourceCRS", nsContext ); 304 Element tCRSNode = XMLTools.getElement( rootElement, PRE + "targetCRS", nsContext ); 305 if ( ( sCRSNode == null && tCRSNode != null ) || ( sCRSNode != null && tCRSNode == null ) ) { 306 throw new XMLParsingException( 307 Messages.getMessage( 308 "WCTS_ISTRANSFORMABLE_MISSING_CRS", 309 ( ( sCRSNode == null ) ? "TargetCRS" : "SourceCRS" ), 310 ( ( tCRSNode == null ) ? "SourceCRS" : "TargetCRS" ) ) ); 311 } 312 if ( sCRSNode != null ) { 313 sourceID = sCRSNode.getAttributeNS( CommonNamespaces.XLNNS.toASCIIString(), "href" ); 314 } 315 if ( tCRSNode != null ) { 316 targetID = tCRSNode.getAttributeNS( CommonNamespaces.XLNNS.toASCIIString(), "href" ); 317 } 318 LOG.logDebug( "The evaluation os supplied sourceID: ", sourceID, " and/or targetID: ", targetID, 319 " are currently not supported." ); 320 throw new XMLParsingException( "Currently only referencing of transformations is supported." ); 321 } 322 323 return new TransformationReference( id ); 324 325 } 326 327 /** 328 * Parses the deegree inlinedata element. 329 * 330 * @param sourceCRS 331 * of the data. 332 * @param targetCRS 333 * in which the data is to be transformed. 334 * @param inlineData 335 * element to extract the data from. 336 * @return a {@link TransformableData} element instantiated with the right type. 337 * @throws OGCWebServiceException 338 * if for any reason the data could not be parsed or processed. 339 */ 340 private TransformableData<?> parseInlineData( CoordinateSystem sourceCRS, Node inlineData ) 341 throws OGCWebServiceException { 342 Node firstChild = null; 343 try { 344 firstChild = getElement( inlineData, "*[1]", nsContext ); 345 } catch ( XMLParsingException e ) { 346 LOG.logError( e.getMessage(), e ); 347 } 348 if ( firstChild != null ) { 349 LOG.logDebug( "Incoming inlineData has localname: " + inlineData.getLocalName() 350 + " has a firstchild with localname: " + firstChild.getLocalName() ); 351 String prefix = firstChild.getPrefix(); 352 String nameSpace = firstChild.getNamespaceURI(); 353 if ( prefix != null ) { 354 String tmp = firstChild.lookupNamespaceURI( prefix ); 355 if ( tmp != null && !"".equals( tmp ) ) { 356 nameSpace = tmp; 357 } 358 } 359 if ( nameSpace == null ) { 360 nameSpace = ""; 361 } 362 LOG.logDebug( "Firstchild is bound to namespace: " + nameSpace ); 363 if ( !DEEGREEWCTS.toASCIIString().equalsIgnoreCase( nameSpace.trim() ) ) { 364 LOG.logError( "The node beneath an " + DEEGREEWCTS_PREFIX 365 + ":inlineData element must be bound to the deegree-wcts (" + DEEGREEWCTS.toASCIIString() 366 + ") name space, found following namespace: " + nameSpace ); 367 } else { 368 String localName = firstChild.getLocalName(); 369 if ( localName != null ) { 370 localName = localName.trim(); 371 if ( "SimpleData".equals( localName ) ) { 372 SimpleData result = parseSimpleData( sourceCRS, (Element) firstChild ); 373 if ( result == null ) { 374 result = new SimpleData(); 375 } 376 return result; 377 } else if ( "GeometryData".equals( localName ) ) { 378 return new GeometryData( parseGeometryData( sourceCRS.getIdentifier(), (Element) firstChild ) ); 379 } else if ( "FeatureCollectionData".equals( localName ) ) { 380 return new FeatureCollectionData( parseFeatureCollectionData( (Element) firstChild ) ); 381 } else { 382 throw new OGCWebServiceException( 383 Messages.getMessage( "WCTS_TRANSFORM_UNKNOWN_INLINE_DATA", 384 localName, 385 "SimpleData, GeometryData or FeatureCollectionData" ), 386 ExceptionCode.INVALIDPARAMETERVALUE ); 387 } 388 389 } 390 } 391 } 392 throw new OGCWebServiceException( Messages.getMessage( "WCTS_TRANSFORM_MISSING_DATA" ), 393 WCTSExceptionCode.NO_INPUT_DATA ); 394 } 395 396 /** 397 * @param simpleData 398 * the dom-xml element to be parsed. 399 * @return a list of point3d's or <code>null</code> if the given parameter <code>null</code>. 400 * @throws OGCWebServiceException 401 * if the number of points is not congruent with the dimension. 402 */ 403 private SimpleData parseSimpleData( CoordinateSystem sourceCRS, Element simpleData ) 404 throws OGCWebServiceException { 405 if ( simpleData == null ) { 406 return null; 407 } 408 409 String cs = simpleData.getAttribute( "cs" ); 410 if ( cs == null || "".equals( cs ) ) { 411 cs = ","; 412 } 413 String ts = simpleData.getAttribute( "ts" ); 414 if ( ts == null || "".equals( ts ) ) { 415 ts = " "; 416 } 417 String values = getStringValue( simpleData ); 418 List<Point3d> points = SimpleData.parseData( values, sourceCRS.getDimension(), cs, ts, "." ); 419 if ( points.size() == 0 ) { 420 throw new OGCWebServiceException( Messages.getMessage( "WCTS_TRANSFORM_MISSING_DATA" ), 421 WCTSExceptionCode.NO_INPUT_DATA ); 422 } 423 return new SimpleData( points, cs, ts ); 424 } 425 426 /** 427 * Parse the featurecollections from the given featureCollectionsData element. 428 * 429 * @param featureCollectionData 430 * (a deegreewcts:inlineElement/deegreewcts:FeatureCollectionData or a deegreewcts:mulipart element). 431 * @return the list of feature collections. 432 * @throws OGCWebServiceException 433 * if no feature collections were found or an xml parsing exception occurred. 434 */ 435 private List<FeatureCollection> parseFeatureCollectionData( Element featureCollectionData ) 436 throws OGCWebServiceException { 437 if ( featureCollectionData == null ) { 438 return null; 439 } 440 List<FeatureCollection> transformableData = new ArrayList<FeatureCollection>(); 441 try { 442 List<Element> fcElements = getElements( featureCollectionData, GML_PREFIX + ":FeatureCollection", nsContext ); 443 if ( fcElements == null || fcElements.size() == 0 ) { 444 LOG.logError( "Could not find any feature collections, this is strange!" ); 445 throw new OGCWebServiceException( Messages.getMessage( "WCTS_TRANSFORM_NO_DATA_FOUND", 446 "gml:FeatureCollection" ), 447 WCTSExceptionCode.NO_INPUT_DATA ); 448 } 449 GMLFeatureCollectionDocument fd = new GMLFeatureCollectionDocument( true, true ); 450 for ( Element fc : fcElements ) { 451 fd.setRootElement( fc ); 452 FeatureCollection data = fd.parse(); 453 if ( data != null ) { 454 transformableData.add( data ); 455 } 456 } 457 } catch ( XMLParsingException e ) { 458 LOG.logError( e.getMessage(), e ); 459 throw new OGCWebServiceException( e.getMessage(), ExceptionCode.NOAPPLICABLECODE ); 460 } 461 return transformableData; 462 } 463 464 /** 465 * Parse the geometries from the given geometries data element. 466 * 467 * @param sourceCRSID 468 * needed for the wrap function of the GMLGeometrieAdapter. 469 * @param geometryData 470 * (an deegreewcts:inlineElement/deegreewcts:GeometryData or a deegreewcts:mulipart element). 471 * @return the list of geometries. 472 * @throws OGCWebServiceException 473 * if no feature collections were found or an xml parsing exception occurred. 474 */ 475 private List<Geometry> parseGeometryData( String sourceCRSID, Element geometryData ) 476 throws OGCWebServiceException { 477 if ( geometryData == null ) { 478 return null; 479 } 480 List<Geometry> transformableData = new ArrayList<Geometry>(); 481 try { 482 List<Element> geomElements = getElements( geometryData, "*", nsContext ); 483 if ( geomElements == null || geomElements.size() == 0 ) { 484 LOG.logError( "Could not find any geometries, this is strange!" ); 485 throw new OGCWebServiceException( 486 Messages.getMessage( "WCTS_TRANSFORM_NO_DATA_FOUND", "gml:Geometries" ), 487 WCTSExceptionCode.NO_INPUT_DATA ); 488 } 489 for ( Element fc : geomElements ) { 490 try { 491 Geometry data = GMLGeometryAdapter.wrap( fc, sourceCRSID ); 492 if ( data != null ) { 493 transformableData.add( data ); 494 } 495 } catch ( GeometryException e ) { 496 LOG.logError( e.getMessage(), e ); 497 } 498 } 499 } catch ( XMLParsingException e ) { 500 LOG.logError( e.getMessage(), e ); 501 throw new OGCWebServiceException( e.getMessage(), ExceptionCode.NOAPPLICABLECODE ); 502 } 503 return transformableData; 504 } 505 506 /** 507 * @return the transformRequest may be <code>null</code>. 508 */ 509 public final Transform getTransformRequest() { 510 return transformRequest; 511 } 512 513 }