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>&lt;xs:element ref="keywords" minOccurs="0" maxOccurs="unbounded"/&gt;</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    }