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 }