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