001 // $HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/ogcbase/OGCDocument.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.ogcbase; 037 038 import java.net.MalformedURLException; 039 import java.net.URI; 040 import java.net.URISyntaxException; 041 import java.net.URL; 042 import java.util.ArrayList; 043 import java.util.List; 044 045 import org.deegree.datatypes.CodeList; 046 import org.deegree.datatypes.QualifiedName; 047 import org.deegree.datatypes.time.TimeDuration; 048 import org.deegree.datatypes.time.TimePeriod; 049 import org.deegree.datatypes.time.TimePosition; 050 import org.deegree.datatypes.time.TimeSequence; 051 import org.deegree.datatypes.values.Closure; 052 import org.deegree.datatypes.values.Interval; 053 import org.deegree.datatypes.values.TypedLiteral; 054 import org.deegree.datatypes.values.Values; 055 import org.deegree.framework.log.ILogger; 056 import org.deegree.framework.log.LoggerFactory; 057 import org.deegree.framework.util.StringTools; 058 import org.deegree.framework.xml.ElementList; 059 import org.deegree.framework.xml.XMLFragment; 060 import org.deegree.framework.xml.XMLParsingException; 061 import org.deegree.framework.xml.XMLTools; 062 import org.deegree.model.metadata.iso19115.Keywords; 063 import org.deegree.model.metadata.iso19115.Linkage; 064 import org.deegree.model.metadata.iso19115.OnlineResource; 065 import org.deegree.model.spatialschema.Point; 066 import org.deegree.ogcwebservices.LonLatEnvelope; 067 import org.deegree.ogcwebservices.OGCWebServiceException; 068 import org.deegree.ogcwebservices.wcs.describecoverage.InvalidCoverageDescriptionExcpetion; 069 import org.w3c.dom.Element; 070 import org.w3c.dom.Text; 071 072 /** 073 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a> 074 * @author last edited by: $Author: mschneider $ 075 * 076 * @version 1.0. $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $ 077 * 078 * @since 1.1 079 */ 080 public abstract class OGCDocument extends XMLFragment { 081 082 private static final long serialVersionUID = 6474662629175208201L; 083 084 protected static final URI GMLNS = CommonNamespaces.GMLNS; 085 086 private static ILogger LOG = LoggerFactory.getLogger( OGCDocument.class ); 087 088 /** 089 * creates a <tt>LonLatEnvelope</tt> object from the passed element 090 * 091 * @param element 092 * @return created <tt>LonLatEnvelope</tt> 093 * @throws XMLParsingException 094 * @throws OGCWebServiceException 095 * @throws InvalidCoverageDescriptionExcpetion 096 */ 097 public static LonLatEnvelope parseLonLatEnvelope( Element element ) 098 throws XMLParsingException, OGCWebServiceException { 099 100 String srs = XMLTools.getRequiredAttrValue( "srsName", null, element ); 101 if ( !( "WGS84(DD)".equals( srs ) ) ) { 102 // || "urn:ogc:def:crs:OGC:1.3:CRS84".equals( srs ) ) ) { 103 throw new OGCWebServiceException( "srsName must be WGS84(DD) for lonLatEnvelope." ); 104 } 105 106 ElementList el = XMLTools.getChildElements( "pos", GMLNS, element ); 107 if ( el == null || el.getLength() != 2 ) { 108 throw new OGCWebServiceException( "A lonLatEnvelope must contain two gml:pos elements" ); 109 } 110 111 Point min = GMLDocument.parsePos( el.item( 0 ) ); 112 Point max = GMLDocument.parsePos( el.item( 1 ) ); 113 114 el = XMLTools.getChildElements( "timePosition", GMLNS, element ); 115 TimePosition[] timePositions = parseTimePositions( el ); 116 117 return new LonLatEnvelope( min, max, timePositions, srs ); 118 } 119 120 /** 121 * creates an array of <tt>TimePosition</tt> s from the passed element 122 * 123 * @param el 124 * @return created array of <tt>TimePosition</tt> s 125 * @throws XMLParsingException 126 * @throws InvalidCoverageDescriptionExcpetion 127 */ 128 protected static TimePosition[] parseTimePositions( ElementList el ) 129 throws XMLParsingException, OGCWebServiceException { 130 TimePosition[] timePos = new TimePosition[el.getLength()]; 131 for ( int i = 0; i < timePos.length; i++ ) { 132 timePos[i] = GMLDocument.parseTimePosition( el.item( i ) ); 133 } 134 return timePos; 135 } 136 137 /** 138 * Creates an array of <code>Keywords</code> from the passed list of <code>keyword</code> -elements. 139 * 140 * This appears to be pretty superfluous (as one <code>keywords</code>- element may contain several 141 * <code>keyword</code> -elements). However, the schema in the OGC document "Web Coverage Service (WCS), Version 142 * 1.0.0", contains the following line (in the definition of the CoverageOfferingBriefType): 143 * 144 * <code><xs:element ref="keywords" minOccurs="0" maxOccurs="unbounded"/></code> 145 * 146 * @param el 147 * @return created array of <tt>Keywords</tt> 148 */ 149 protected Keywords[] parseKeywords( ElementList el, URI namespaceURI ) { 150 Keywords[] kws = new Keywords[el.getLength()]; 151 for ( int i = 0; i < kws.length; i++ ) { 152 kws[i] = parseKeywords( el.item( i ), namespaceURI ); 153 } 154 return kws; 155 } 156 157 /** 158 * Creates a <code>Keywords</code> instance from the given <code>keywords</code> -element. 159 * 160 * @param element 161 * @param namespaceURI 162 * @return created <code>Keywords</code> 163 */ 164 protected Keywords parseKeywords( Element element, URI namespaceURI ) { 165 ElementList el = XMLTools.getChildElements( "keyword", namespaceURI, element ); 166 String[] kws = new String[el.getLength()]; 167 for ( int i = 0; i < kws.length; i++ ) { 168 kws[i] = XMLTools.getStringValue( el.item( i ) ); 169 } 170 return new Keywords( kws ); 171 } 172 173 /** 174 * creates an <tt>TimeSequence</tt> from the passed element 175 * 176 * @param element 177 * @return created <tt>TimeSequence</tt> 178 * @throws XMLParsingException 179 * @throws InvalidCoverageDescriptionExcpetion 180 */ 181 protected TimeSequence parseTimeSequence( Element element, URI namespaceURI ) 182 throws XMLParsingException, OGCWebServiceException { 183 ElementList el = XMLTools.getChildElements( "timePerdiod", namespaceURI, element ); 184 TimePeriod[] timePerdiods = parseTimePeriods( el, namespaceURI ); 185 el = XMLTools.getChildElements( "timePosition", GMLNS, element ); 186 TimePosition[] timePositions = parseTimePositions( el ); 187 188 return new TimeSequence( timePerdiods, timePositions ); 189 } 190 191 /** 192 * creates an array of <tt>TimePeriod</tt> s from the passed element 193 * 194 * @param el 195 * @return created array of <tt>TimePeriod</tt> s 196 * @throws XMLParsingException 197 * @throws InvalidCoverageDescriptionExcpetion 198 */ 199 protected TimePeriod[] parseTimePeriods( ElementList el, URI namespaceURI ) 200 throws XMLParsingException, OGCWebServiceException { 201 TimePeriod[] timePeriods = new TimePeriod[el.getLength()]; 202 for ( int i = 0; i < timePeriods.length; i++ ) { 203 timePeriods[i] = parseTimePeriod( el.item( i ), namespaceURI ); 204 } 205 return timePeriods; 206 } 207 208 /** 209 * creates a <tt>TimePeriod</tt> from the passed element 210 * 211 * @param element 212 * @return created <tt>TimePeriod</tt> 213 * @throws XMLParsingException 214 * @throws InvalidCoverageDescriptionExcpetion 215 */ 216 protected TimePeriod parseTimePeriod( Element element, URI namespaceURI ) 217 throws XMLParsingException, OGCWebServiceException { 218 try { 219 Element begin = XMLTools.getRequiredChildElement( "beginPosition", namespaceURI, element ); 220 TimePosition beginPosition = GMLDocument.parseTimePosition( begin ); 221 Element end = XMLTools.getRequiredChildElement( "endPosition", namespaceURI, element ); 222 TimePosition endPosition = GMLDocument.parseTimePosition( end ); 223 String dur = XMLTools.getRequiredStringValue( "timeResolution", namespaceURI, element ); 224 TimeDuration resolution = TimeDuration.createTimeDuration( dur ); 225 226 return new TimePeriod( beginPosition, endPosition, resolution ); 227 } catch ( InvalidGMLException e ) { 228 LOG.logError( e.getMessage(), e ); 229 String s = e.getMessage() + "\n" + StringTools.stackTraceToString( e ); 230 throw new OGCWebServiceException( s ); 231 } 232 233 } 234 235 /** 236 * creates a <tt>Values</tt> object from the passed element 237 * 238 * @param element 239 * @return created <tt>Values</tt> 240 * @throws XMLParsingException 241 */ 242 protected Values parseValues( Element element, URI namespaceURI ) 243 throws XMLParsingException { 244 245 String type = XMLTools.getAttrValue( element, namespaceURI, "type", null ); 246 String semantic = XMLTools.getAttrValue( element, namespaceURI, "semantic", null ); 247 248 ElementList el = XMLTools.getChildElements( "interval", namespaceURI, element ); 249 Interval[] intervals = new Interval[el.getLength()]; 250 for ( int i = 0; i < intervals.length; i++ ) { 251 intervals[i] = parseInterval( el.item( i ), namespaceURI ); 252 } 253 254 el = XMLTools.getChildElements( "singleValue", namespaceURI, element ); 255 TypedLiteral[] singleValues = new TypedLiteral[el.getLength()]; 256 for ( int i = 0; i < singleValues.length; i++ ) { 257 singleValues[i] = parseTypedLiteral( el.item( i ) ); 258 } 259 260 Element elem = XMLTools.getChildElement( "default", namespaceURI, element ); 261 TypedLiteral def = null; 262 if ( elem != null ) { 263 def = parseTypedLiteral( elem ); 264 } 265 266 try { 267 URI sem = null; 268 if ( semantic != null ) 269 sem = new URI( semantic ); 270 URI tp = null; 271 if ( type != null ) 272 tp = new URI( type ); 273 return new Values( intervals, singleValues, tp, sem, def ); 274 } catch ( URISyntaxException e ) { 275 LOG.logError( e.getMessage(), e ); 276 throw new XMLParsingException( "couldn't parse URI from valuesl\n" + StringTools.stackTraceToString( e ) ); 277 } 278 } 279 280 /** 281 * creates an <tt>Interval</tt> object from the passed element 282 * 283 * @param element 284 * @return created <tt>Interval</tt> 285 * @throws XMLParsingException 286 */ 287 protected Interval parseInterval( Element element, URI namespaceURI ) 288 throws XMLParsingException { 289 290 try { 291 String tmp = XMLTools.getAttrValue( element, namespaceURI, "type", null ); 292 URI type = null; 293 if ( tmp != null ) 294 type = new URI( tmp ); 295 String semantic = XMLTools.getAttrValue( element, namespaceURI, "semantic", null ); 296 tmp = XMLTools.getAttrValue( element, null, "atomic", null ); 297 boolean atomic = "true".equals( tmp ) || "1".equals( tmp ); 298 String clos = XMLTools.getAttrValue( element, namespaceURI, "closure", null ); 299 300 Closure closure = new Closure( clos ); 301 302 Element elem = XMLTools.getRequiredChildElement( "min", namespaceURI, element ); 303 TypedLiteral min = parseTypedLiteral( elem ); 304 305 elem = XMLTools.getRequiredChildElement( "min", namespaceURI, element ); 306 TypedLiteral max = parseTypedLiteral( elem ); 307 308 elem = XMLTools.getRequiredChildElement( "res", namespaceURI, element ); 309 TypedLiteral res = parseTypedLiteral( elem ); 310 311 URI sem = null; 312 if ( semantic != null ) 313 sem = new URI( semantic ); 314 315 return new Interval( min, max, type, sem, atomic, closure, res ); 316 } catch ( URISyntaxException e ) { 317 LOG.logError( e.getMessage(), e ); 318 throw new XMLParsingException( "couldn't parse URI from interval\n" + StringTools.stackTraceToString( e ) ); 319 } 320 321 } 322 323 /** 324 * creates a <tt>TypedLiteral</tt> from the passed element 325 * 326 * @param element 327 * @return created <tt>TypedLiteral</tt> 328 * @throws XMLParsingException 329 */ 330 protected TypedLiteral parseTypedLiteral( Element element ) 331 throws XMLParsingException { 332 try { 333 String tmp = XMLTools.getStringValue( element ); 334 String mtype = XMLTools.getAttrValue( element, null, "type", null ); 335 URI mt = null; 336 if ( mtype != null ) 337 mt = new URI( mtype ); 338 return new TypedLiteral( tmp, mt ); 339 } catch ( URISyntaxException e ) { 340 LOG.logError( e.getMessage(), e ); 341 throw new XMLParsingException( "couldn't parse URI from typedLiteral\n" 342 + StringTools.stackTraceToString( e ) ); 343 } 344 } 345 346 /** 347 * creates an array of <tt>CodeList</tt> objects from the passed element list 348 * 349 * @param el 350 * @return created array of <tt>CodeList</tt> 351 * @throws XMLParsingException 352 */ 353 protected CodeList[] parseCodeListArray( ElementList el ) 354 throws XMLParsingException { 355 CodeList[] cl = new CodeList[el.getLength()]; 356 for ( int i = 0; i < cl.length; i++ ) { 357 cl[i] = parseCodeList( el.item( i ) ); 358 } 359 return cl; 360 } 361 362 /** 363 * creates a <tt>CodeList</tt> object from the passed element 364 * 365 * @param element 366 * @return created <tt>CodeList</tt> 367 * @throws XMLParsingException 368 */ 369 protected CodeList parseCodeList( Element element ) 370 throws XMLParsingException { 371 if ( element == null ) { 372 return null; 373 } 374 try { 375 String tmp = XMLTools.getAttrValue( element, null, "codeSpace", null ); 376 URI codeSpace = null; 377 if ( tmp != null ) { 378 codeSpace = new URI( tmp ); 379 } 380 tmp = XMLTools.getStringValue( element ); 381 String[] ar = StringTools.toArray( tmp, " ,;", true ); 382 return new CodeList( element.getNodeName(), ar, codeSpace ); 383 } catch ( URISyntaxException e ) { 384 LOG.logError( e.getMessage(), e ); 385 throw new XMLParsingException( "couldn't parse URI from CodeList\n" + StringTools.stackTraceToString( e ) ); 386 } 387 } 388 389 /** 390 * Creates an <tt>OnLineResource</tt> instance from the passed element. The element contains an OnlineResourse as it 391 * is used in the OGC Web XXX CapabilitiesService specifications. 392 * 393 * TODO Compare with XMLFragment#parseSimpleLink 394 * 395 * @param element 396 * @return the link 397 * @throws XMLParsingException 398 */ 399 protected OnlineResource parseOnLineResource( Element element ) 400 throws XMLParsingException { 401 402 OnlineResource olr = null; 403 String attrValue = XMLTools.getRequiredAttrValue( "href", XLNNS, element ); 404 URL href = null; 405 try { 406 href = resolve( attrValue ); 407 } catch ( MalformedURLException e ) { 408 LOG.logError( e.getMessage(), e ); 409 throw new XMLParsingException( "Given value '" + attrValue + "' in attribute 'href' " + "(namespace: " 410 + XLNNS + ") of element '" + element.getLocalName() + "' (namespace: " 411 + element.getNamespaceURI() + ") is not a valid URL." ); 412 } 413 Linkage linkage = new Linkage( href, Linkage.SIMPLE ); 414 String title = XMLTools.getAttrValue( element, XLNNS, "title", null ); 415 olr = new OnlineResource( null, null, linkage, null, title, href.getProtocol() ); 416 return olr; 417 } 418 419 /** 420 * @param e 421 * @return a new property path, possibly an xlinked one 422 * @throws XMLParsingException 423 */ 424 public static PropertyPath parseExtendedPropertyPath( Element e ) 425 throws XMLParsingException { 426 PropertyPath path = parsePropertyPath( (Text) XMLTools.getNode( e, "text()", null ) ); 427 428 if ( e.getLocalName().equals( "PropertyName" ) ) { 429 return path; 430 } 431 432 String depth = e.getAttribute( "traverseXlinkDepth" ); 433 int idepth = depth == null ? -1 : ( depth.equals( "*" ) ? -1 : Integer.parseInt( depth ) ); 434 // TODO do this for expiry as well 435 436 return new XLinkPropertyPath( path.getAllSteps(), idepth ); 437 } 438 439 /** 440 * Creates a new instance of <code>PropertyPath</code> from the given text node. 441 * <p> 442 * NOTE: Namespace prefices used in the property path should be bound using XML namespace mechanisms (i.e. using 443 * xmlns attributes in the document). However, to enable processing of partly broken requests, unbound prefices are 444 * accepted as well. 445 * 446 * @param textNode 447 * string representation of the property path 448 * @return new PropertyPath instance 449 * @throws XMLParsingException 450 * @see PropertyPath 451 */ 452 public static PropertyPath parsePropertyPath( Text textNode ) 453 throws XMLParsingException { 454 455 String path = XMLTools.getStringValue( textNode ); 456 String[] steps = StringTools.toArray( path, "/", false ); 457 List<PropertyPathStep> propertyPathSteps = new ArrayList<PropertyPathStep>( steps.length ); 458 459 for ( int i = 0; i < steps.length; i++ ) { 460 PropertyPathStep propertyStep = null; 461 QualifiedName propertyName = null; 462 String step = steps[i]; 463 boolean isAttribute = false; 464 boolean isIndexed = false; 465 int selectedIndex = -1; 466 467 // check if step begins with '@' -> must be the final step then 468 if ( step.startsWith( "@" ) ) { 469 if ( i != steps.length - 1 ) { 470 String msg = "PropertyName '" + path + "' is illegal: the attribute specifier may only " 471 + "be used for the final step."; 472 throw new XMLParsingException( msg ); 473 } 474 step = step.substring( 1 ); 475 isAttribute = true; 476 } 477 478 // check if the step ends with brackets ([...]) 479 if ( step.endsWith( "]" ) ) { 480 if ( isAttribute ) { 481 String msg = "PropertyName '" + path + "' is illegal: if the attribute specifier ('@') is used, " 482 + "index selection ('[...']) is not possible."; 483 throw new XMLParsingException( msg ); 484 } 485 int bracketPos = step.indexOf( '[' ); 486 if ( bracketPos < 0 ) { 487 String msg = "PropertyName '" + path + "' is illegal. No opening brackets found for step '" + step 488 + "'."; 489 throw new XMLParsingException( msg ); 490 } 491 492 // workaround for really silly compliance tests as we're not supporting XPath anyway 493 String inBrackets = step.substring( bracketPos + 1, step.length() - 1 ); 494 if ( inBrackets.indexOf( "position()" ) != -1 ) { 495 inBrackets = inBrackets.replace( "position()=", "" ); 496 inBrackets = inBrackets.replace( "=position()", "" ); 497 } 498 499 try { 500 selectedIndex = Integer.parseInt( inBrackets ); 501 } catch ( NumberFormatException e ) { 502 String msg = "PropertyName '" + path + "' is illegal. Specified index '" 503 + step.substring( bracketPos + 1, step.length() - 1 ) + "' is not a number."; 504 throw new XMLParsingException( msg ); 505 } 506 step = step.substring( 0, bracketPos ); 507 isIndexed = true; 508 } 509 510 // determine namespace prefix and binding (if any) 511 int colonPos = step.indexOf( ':' ); 512 if ( colonPos < 0 ) { 513 propertyName = new QualifiedName( step ); 514 } else { 515 String prefix = step.substring( 0, colonPos ); 516 step = step.substring( colonPos + 1 ); 517 URI namespace = null; 518 try { 519 namespace = XMLTools.getNamespaceForPrefix( prefix, textNode ); 520 } catch ( URISyntaxException e ) { 521 throw new XMLParsingException( "Error parsing PropertyName: " + e.getMessage() ); 522 } 523 if ( namespace == null ) { 524 LOG.logWarning( "PropertyName '" + path + "' uses an unbound namespace prefix: " + prefix ); 525 } 526 propertyName = new QualifiedName( prefix, step, namespace ); 527 } 528 529 // hack for "*" is here (AnyStep with or without index) 530 // this probably only works wor SQL datastores! 531 if ( step.equals( "*" ) ) { 532 if ( isIndexed ) { 533 propertyStep = PropertyPathFactory.createAnyStep( selectedIndex ); 534 } else { 535 propertyStep = PropertyPathFactory.createAnyStep(); 536 } 537 } else { 538 if ( isAttribute ) { 539 propertyStep = PropertyPathFactory.createAttributePropertyPathStep( propertyName ); 540 } else if ( isIndexed ) { 541 propertyStep = PropertyPathFactory.createPropertyPathStep( propertyName, selectedIndex ); 542 } else { 543 propertyStep = PropertyPathFactory.createPropertyPathStep( propertyName ); 544 } 545 } 546 propertyPathSteps.add( propertyStep ); 547 } 548 return PropertyPathFactory.createPropertyPath( propertyPathSteps ); 549 } 550 }