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 }