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.capabilities; 038 039 import static org.deegree.framework.xml.XMLTools.getElement; 040 import static org.deegree.framework.xml.XMLTools.getElements; 041 import static org.deegree.framework.xml.XMLTools.getNodeAsBoolean; 042 import static org.deegree.framework.xml.XMLTools.getNodesAsStringList; 043 import static org.deegree.framework.xml.XMLTools.getRequiredElements; 044 import static org.deegree.framework.xml.XMLTools.getRequiredNodeAsBoolean; 045 import static org.deegree.framework.xml.XMLTools.getStringValue; 046 import static org.deegree.ogcbase.CommonNamespaces.DEEGREEWCTS_PREFIX; 047 import static org.deegree.ogcbase.CommonNamespaces.WCS_1_2_0_PREFIX; 048 import static org.deegree.ogcbase.CommonNamespaces.WCTS_PREFIX; 049 050 import java.util.ArrayList; 051 import java.util.HashMap; 052 import java.util.List; 053 import java.util.Map; 054 055 import org.deegree.crs.transformations.Transformation; 056 import org.deegree.crs.transformations.helmert.Helmert; 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.XMLParsingException; 062 import org.deegree.framework.xml.XMLTools; 063 import org.deegree.i18n.Messages; 064 import org.deegree.model.crs.CRSFactory; 065 import org.deegree.model.crs.CoordinateSystem; 066 import org.deegree.model.crs.UnknownCRSException; 067 import org.deegree.ogcbase.CommonNamespaces; 068 import org.deegree.ogcbase.ExceptionCode; 069 import org.deegree.ogcwebservices.getcapabilities.InvalidCapabilitiesException; 070 import org.deegree.ogcwebservices.getcapabilities.OGCCapabilities; 071 import org.deegree.ogcwebservices.wcts.WCTService; 072 import org.deegree.ogcwebservices.wcts.capabilities.mdprofiles.MetadataProfile; 073 import org.deegree.ogcwebservices.wcts.capabilities.mdprofiles.TransformationMetadata; 074 import org.deegree.owscommon_1_1_0.Metadata; 075 import org.deegree.owscommon_1_1_0.OWSCommonCapabilitiesDocument; 076 import org.w3c.dom.Element; 077 078 /** 079 * <code>WCTSCapabilitiesDocument</code> parses a given wcts:Capabilities document version 0.4.0, with ows:Common 080 * 1.1.0 and csw 1.2.0. 081 * 082 * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a> 083 * 084 * @author last edited by: $Author:$ 085 * 086 * @version $Revision:$, $Date:$ 087 * 088 */ 089 public class WCTSCapabilitiesDocument extends OWSCommonCapabilitiesDocument { 090 091 /** 092 * 093 */ 094 private static final long serialVersionUID = -2378224055294207801L; 095 096 private static ILogger LOG = LoggerFactory.getLogger( WCTSCapabilitiesDocument.class ); 097 098 private static String PRE = WCTS_PREFIX + ":"; 099 100 // Default value of codeType attribute, from wctsCommon.xsd. 101 private static String DEFAULT_COV_URL = "http://schemas.opengis.net/wcts/0.0.0/coverageType.xml"; 102 103 private static String DEFAULT_GEOM_URL = "http://schemas.opengis.net/wcts/0.0.0/geometryType.xml"; 104 105 /** 106 * Creates a wcts 0.4.0 capabilities document with the rootnode set to wcts:Capabilities. 107 */ 108 public void createEmptyDocument() { 109 setRootElement( XMLTools.create().createElementNS( CommonNamespaces.WCTSNS.toASCIIString(), 110 PRE + "Capabilities" ) ); 111 } 112 113 /** 114 * @param configuredProvider 115 * the crs provider to be used for creation of CRS's (found in the deegreeparams section of the 116 * configuration). If <code>null</code> the default configured provider will be used. 117 * @return the OGCCapabilities parsed from the root node. 118 * @throws InvalidCapabilitiesException 119 */ 120 public OGCCapabilities parseCapabilities( String configuredProvider ) 121 throws InvalidCapabilitiesException { 122 WCTSCapabilities caps = null; 123 try { 124 caps = new WCTSCapabilities( parseVersion(), parseUpdateSequence(), parseServiceIdentification(), 125 parseServiceProvider(), parseOperationsMetadata(), 126 parseContents( configuredProvider ) ); 127 } catch ( XMLParsingException e ) { 128 LOG.logError( e.getMessage(), e ); 129 throw new InvalidCapabilitiesException( e.getMessage(), ExceptionCode.INVALID_FORMAT ); 130 } catch ( UnknownCRSException ucrse ) { 131 LOG.logError( ucrse.getMessage(), ucrse ); 132 throw new InvalidCapabilitiesException( ucrse.getMessage(), ExceptionCode.INVALID_FORMAT ); 133 } 134 return caps; 135 } 136 137 // /** 138 // * @return the OGCCapabilities parsed from the root node. 139 // * @throws InvalidCapabilitiesException 140 // */ 141 // public OGCCapabilities parseCapabilities() 142 // throws InvalidCapabilitiesException { 143 // return this.parseCapabilities( null ); 144 // } 145 146 /** 147 * @return the mandatory version string. 148 * @throws InvalidCapabilitiesException 149 * with code INVALIDPARAMETERVALUE, if the attribute was not given. 150 */ 151 public String parseVersion() 152 throws InvalidCapabilitiesException { 153 Element root = getRootElement(); 154 String result = new String(); 155 if ( root != null ) { 156 result = root.getAttribute( "version" ); 157 if ( result == null || "".equals( result.trim() ) ) { 158 throw new InvalidCapabilitiesException( 159 Messages.getMessage( "WCTS_ILLEGAL_VERSION", WCTService.version ), 160 ExceptionCode.INVALIDPARAMETERVALUE ); 161 } 162 } 163 164 if ( !WCTService.version.equalsIgnoreCase( result ) ) { 165 throw new InvalidCapabilitiesException( Messages.getMessage( "WCTS_ILLEGAL_VERSION", WCTService.version ), 166 ExceptionCode.INVALIDPARAMETERVALUE ); 167 } 168 return result; 169 170 } 171 172 /** 173 * @return the optional updateSeqequence String. 174 */ 175 public String parseUpdateSequence() { 176 Element root = getRootElement(); 177 String result = new String(); 178 if ( root != null ) { 179 result = root.getAttribute( "updateSequence" ); 180 } 181 return result; 182 } 183 184 /** 185 * Parses the optional wcts:Content element of the wcts:Capabilities element. 186 * 187 * @param configuredProvider 188 * the crs provider to be used for creation of CRS's (found in the deegreeparams section of the 189 * configuration). If <code>null</code> the default configured provider will be used. 190 * @return the content bean representation 191 * @throws XMLParsingException 192 * @throws UnknownCRSException 193 */ 194 protected Content parseContents( String configuredProvider ) 195 throws XMLParsingException, UnknownCRSException { 196 Element contents = getElement( getRootElement(), PRE + "Contents", nsContext ); 197 if ( contents == null ) { 198 return null; 199 } 200 201 List<String> transformations = getNodesAsStringList( contents, PRE + "Transformation", nsContext ); 202 Map<String, Transformation> configuredTransforms = new HashMap<String, Transformation>( transformations.size() ); 203 for ( String trans : transformations ) { 204 Transformation t = CRSFactory.getTransformation( configuredProvider, trans ); 205 if ( t == null ) { 206 LOG.logWarning( "The transformation with id : " + trans 207 + " could not be loaded from the crs configuration ignoring it. " ); 208 } else { 209 configuredTransforms.put( trans, t ); 210 } 211 212 } 213 214 List<String> methods = getNodesAsStringList( contents, PRE + "Method", nsContext ); 215 List<String> sCRSs = getNodesAsStringList( contents, PRE + "SourceCRS", nsContext ); 216 if ( sCRSs == null || sCRSs.size() == 0 ) { 217 throw new XMLParsingException( "The " + PRE + "Content node of the " + PRE 218 + "Capabilites must have at least one SourceCRS element." ); 219 } 220 List<CoordinateSystem> sourceCRSs = new ArrayList<CoordinateSystem>( sCRSs.size() ); 221 for ( String crs : sCRSs ) { 222 sourceCRSs.add( CRSFactory.create( configuredProvider, crs ) ); 223 } 224 225 List<String> tCRSs = getNodesAsStringList( contents, PRE + "TargetCRS", nsContext ); 226 if ( tCRSs == null || tCRSs.size() == 0 ) { 227 throw new XMLParsingException( "The " + PRE + "Content node of the " + PRE 228 + "Capabilites must have at least one TargetCRS element." ); 229 } 230 231 List<CoordinateSystem> targetCRSs = new ArrayList<CoordinateSystem>( tCRSs.size() ); 232 for ( String crs : tCRSs ) { 233 targetCRSs.add( CRSFactory.create( configuredProvider, crs ) ); 234 } 235 CoverageAbilities coverageAbilities = parseCoverageAbilities( getElement( contents, PRE + "CoverageAbilities", 236 nsContext ) ); 237 FeatureAbilities featureAbilities = parseFeatureAbilities( getElement( contents, PRE + "FeatureAbilities", 238 nsContext ) ); 239 if ( coverageAbilities == null && featureAbilities == null ) { 240 throw new XMLParsingException( "Both the " + PRE + "CoverageAbilities and " + PRE 241 + "FeatureAbilities elements are null, at least one must be present in the " 242 + PRE + "Content element of the WCTS-capabilities document." ); 243 } 244 245 List<Metadata> metadata = parseMetadatas( getElements( contents, PRE_OWS + "Metadata", nsContext ) ); 246 247 // the parseAbstractMetadata will remove the metadatas from the metadata list if it could be parsed. 248 List<MetadataProfile<?>> metadataProfiles = parseAbstractMetadata( metadata, sourceCRSs, targetCRSs, 249 configuredTransforms ); 250 251 boolean userDefinedCRS = getRequiredNodeAsBoolean( contents, "@userDefinedCRSs", nsContext ); 252 253 return new Content( configuredTransforms, methods, sourceCRSs, targetCRSs, coverageAbilities, featureAbilities, 254 metadata, userDefinedCRS, metadataProfiles ); 255 } 256 257 /** 258 * @param metadata 259 * @param targetCRSs 260 * @param sourceCRSs 261 * @param configuredTransforms 262 * @return the list of metadata profiles, may be emtpy but never <code>null</code> 263 */ 264 private List<MetadataProfile<?>> parseAbstractMetadata( List<Metadata> metadata, List<CoordinateSystem> sourceCRSs, 265 List<CoordinateSystem> targetCRSs, 266 Map<String, Transformation> configuredTransforms ) { 267 if ( metadata == null ) { 268 return new ArrayList<MetadataProfile<?>>(); 269 } 270 List<MetadataProfile<?>> result = new ArrayList<MetadataProfile<?>>(); 271 List<Metadata> toBeRemoved = new ArrayList<Metadata>(); 272 for ( Metadata md : metadata ) { 273 if ( md != null ) { 274 Element abst = md.getAbstractElement(); 275 if ( abst != null ) { 276 LOG.logDebug( "Found following abstract element: " + abst.getLocalName() ); 277 if ( "transformationMetadata".equals( abst.getLocalName() ) ) { 278 try { 279 result.add( parseTransformationMetadata( abst, sourceCRSs, targetCRSs, configuredTransforms ) ); 280 } catch ( XMLParsingException e ) { 281 LOG.logError( e.getMessage() ); 282 } 283 // remove all transformation metadatas, they will be added to capabilities in their transform 284 // form. 285 toBeRemoved.add( md ); 286 } else { 287 LOG.logError( "The type: " 288 + abst.getLocalName() 289 + " is not recognized by the wcts, currently just TransformationMetadata elements are supported." ); 290 } 291 292 } 293 } 294 } 295 metadata.removeAll( toBeRemoved ); 296 return result; 297 } 298 299 /** 300 * Parse the transformation metadata elements from the abstractMetadata element of the the content element. 301 * 302 * @param transformationMD 303 * @param sourceCRSs 304 * @param targetCRSs 305 * @param configuredTransforms 306 * @return the metadata bean. 307 * @throws XMLParsingException 308 */ 309 private TransformationMetadata parseTransformationMetadata( Element transformationMD, 310 List<CoordinateSystem> sourceCRSs, 311 List<CoordinateSystem> targetCRSs, 312 Map<String, Transformation> configuredTransforms ) 313 throws XMLParsingException { 314 if ( transformationMD == null || !"transformationMetadata".equals( transformationMD.getLocalName() ) ) { 315 return null; 316 } 317 if ( LOG.isDebug() ) { 318 XMLFragment doc = new XMLFragment( transformationMD ); 319 LOG.logDebug( "Parsing transformationMD from following xml fragment." ); 320 LOG.logDebug( doc.getAsPrettyString() ); 321 } 322 String sCRS = transformationMD.getAttribute( "sourceCRS" ); 323 CoordinateSystem sourceCRS = null; 324 if ( "".equals( sCRS.trim() ) ) { 325 throw new XMLParsingException( "The sourceCRS attribute may not be empty." ); 326 } 327 for ( int i = 0; i < sourceCRSs.size() && sourceCRS == null; ++i ) { 328 if ( sourceCRSs.get( i ) != null ) { 329 if ( sourceCRSs.get( i ).getCRS().hasID( sCRS ) ) { 330 sourceCRS = sourceCRSs.get( i ); 331 } 332 } 333 } 334 if ( sourceCRS == null ) { 335 throw new XMLParsingException( "The sourceCRS attribute:" + sCRS 336 + " denotes a CRS which is not defined as a sourceCRS." ); 337 } 338 339 String tCRS = transformationMD.getAttribute( "targetCRS" ); 340 CoordinateSystem targetCRS = null; 341 if ( "".equals( tCRS.trim() ) ) { 342 throw new XMLParsingException( "The targetCRS attribute may not be empty." ); 343 } 344 for ( int i = 0; i < targetCRSs.size() && targetCRS == null; ++i ) { 345 if ( targetCRSs.get( i ) != null ) { 346 if ( targetCRSs.get( i ).getCRS().hasID( tCRS ) ) { 347 targetCRS = targetCRSs.get( i ); 348 } 349 } 350 } 351 if ( targetCRS == null ) { 352 throw new XMLParsingException( "The targetCRS attribute: " + tCRS 353 + " denotes a CRS which is not defined as a targetCRS." ); 354 } 355 356 String tID = transformationMD.getAttribute( "transformationID" ); 357 if ( "".equals( tID.trim() ) ) { 358 throw new XMLParsingException( "The transformationID attribute may not be empty." ); 359 } 360 if ( !configuredTransforms.containsKey( tID ) || configuredTransforms.get( tID ) == null ) { 361 configuredTransforms.remove( tID ); 362 throw new XMLParsingException( "The transformationID: " + tID 363 + " was not found in the configured transformations, discarding this id." ); 364 } 365 Transformation transform = configuredTransforms.get( tID ); 366 if ( transform == null ) { 367 throw new XMLParsingException( 368 "The transformationID: " 369 + tID 370 + " does not reference a known Transformation, discarding this metadata element." ); 371 } 372 String description = XMLTools.getRequiredNodeAsString( transformationMD, DEEGREEWCTS_PREFIX + ":" 373 + "description", nsContext ); 374 375 // if a helmert transformation, just use the default transformation chain. 376 return new TransformationMetadata( ( transform instanceof Helmert ) ? null : transform, tID, sourceCRS, 377 targetCRS, description ); 378 379 } 380 381 /** 382 * @param element 383 * @return the FeatureAbilities 384 * @throws XMLParsingException 385 */ 386 private FeatureAbilities parseFeatureAbilities( Element element ) 387 throws XMLParsingException { 388 if ( element == null ) { 389 return null; 390 } 391 List<Element> requiredElements = getRequiredElements( element, PRE + "GeometryType", nsContext ); 392 List<Pair<String, String>> geometryTypes = new ArrayList<Pair<String, String>>( requiredElements.size() ); 393 for ( Element geomElement : requiredElements ) { 394 Pair<String, String> t = parseCodeType( geomElement, DEFAULT_GEOM_URL ); 395 if ( t != null ) { 396 geometryTypes.add( t ); 397 } 398 } 399 400 requiredElements = getRequiredElements( element, PRE + "FeatureFormat", nsContext ); 401 List<InputOutputFormat> featureFormats = new ArrayList<InputOutputFormat>( requiredElements.size() ); 402 for ( Element featureFormElement : requiredElements ) { 403 InputOutputFormat t = parseInputOutputFormatType( featureFormElement ); 404 if ( t != null ) { 405 featureFormats.add( t ); 406 } 407 } 408 409 boolean remoteProperties = getRequiredNodeAsBoolean( element, "@remoteProperties", nsContext ); 410 411 return new FeatureAbilities( geometryTypes, featureFormats, remoteProperties ); 412 } 413 414 /** 415 * Parses given element for coverageType, CoverageFormat and InterpolatinMethods, the latter is not evaluated yet 416 * though. 417 * 418 * @param caElement 419 * @return the CoverageAbilities 420 * @throws XMLParsingException 421 */ 422 private CoverageAbilities parseCoverageAbilities( Element caElement ) 423 throws XMLParsingException { 424 if ( caElement == null ) { 425 return null; 426 } 427 List<Element> ctElements = getRequiredElements( caElement, PRE + "CoverageType", nsContext ); 428 List<Pair<String, String>> coverageTypes = new ArrayList<Pair<String, String>>( ctElements.size() ); 429 for ( Element ct : ctElements ) { 430 Pair<String, String> t = parseCodeType( ct, DEFAULT_COV_URL ); 431 if ( t != null ) { 432 coverageTypes.add( t ); 433 } 434 } 435 ctElements = getRequiredElements( caElement, PRE + "CoverageFormat", nsContext ); 436 List<InputOutputFormat> coverageFormats = new ArrayList<InputOutputFormat>( ctElements.size() ); 437 for ( Element ct : ctElements ) { 438 InputOutputFormat t = parseInputOutputFormatType( ct ); 439 if ( t != null ) { 440 coverageFormats.add( t ); 441 } 442 } 443 List<Element> interPolationMethods = getRequiredElements( caElement, 444 WCS_1_2_0_PREFIX + ":InterpolationMethods", nsContext ); 445 LOG.logWarning( "The " + WCS_1_2_0_PREFIX + ":InterpolationMethods are not evaluated yet." ); 446 return new CoverageAbilities( coverageTypes, coverageFormats, interPolationMethods ); 447 } 448 449 /** 450 * return the <value,codetype > pair of a CodeType element. 451 * 452 * @param elem 453 * to parse from 454 * @param defaultString 455 * the String to use as a default value. 456 * @return the <text(), codeType-attribute> pair or <code>null</code> if the element is <code>null</code> 457 */ 458 private Pair<String, String> parseCodeType( Element elem, String defaultString ) { 459 String value = getStringValue( elem ); 460 String attrib = elem.getAttribute( "codeSpace" ); 461 if ( attrib == null || "".equals( attrib ) ) { 462 attrib = defaultString; 463 } 464 return new Pair<String, String>( value, attrib ); 465 } 466 467 /** 468 * return the <mimetype,<input,output>> pair of a CodeType element. 469 * 470 * @param elem 471 * to parse from 472 * @return the <text(), <input,output>> pair or <code>null</code> if the element is <code>null</code> 473 * @throws XMLParsingException 474 */ 475 private InputOutputFormat parseInputOutputFormatType( Element elem ) 476 throws XMLParsingException { 477 String value = elem.getTextContent(); 478 boolean input = getNodeAsBoolean( elem, "@input", nsContext, true ); 479 boolean output = getNodeAsBoolean( elem, "@output", nsContext, true ); 480 return new InputOutputFormat( value, input, output ); 481 } 482 483 }