001    // $HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/ogcbase/OGCDocument.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2007 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/deegree/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     This library is free software; you can redistribute it and/or
012     modify it under the terms of the GNU Lesser General Public
013     License as published by the Free Software Foundation; either
014     version 2.1 of the License, or (at your option) any later version.
015    
016     This library is distributed in the hope that it will be useful,
017     but WITHOUT ANY WARRANTY; without even the implied warranty of
018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     Lesser General Public License for more details.
020    
021     You should have received a copy of the GNU Lesser General Public
022     License along with this library; if not, write to the Free Software
023     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024    
025     Contact:
026    
027     Andreas Poth
028     lat/lon GmbH
029     Aennchenstr. 19
030     53115 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Prof. Dr. Klaus Greve
035     Department of Geography
036     University of Bonn
037     Meckenheimer Allee 166
038     53115 Bonn
039     Germany
040     E-Mail: greve@giub.uni-bonn.de
041    
042     
043     ---------------------------------------------------------------------------*/
044    package org.deegree.ogcbase;
045    
046    import java.net.MalformedURLException;
047    import java.net.URI;
048    import java.net.URISyntaxException;
049    import java.net.URL;
050    import java.util.ArrayList;
051    import java.util.List;
052    
053    import org.deegree.datatypes.CodeList;
054    import org.deegree.datatypes.QualifiedName;
055    import org.deegree.datatypes.time.TimeDuration;
056    import org.deegree.datatypes.time.TimePeriod;
057    import org.deegree.datatypes.time.TimePosition;
058    import org.deegree.datatypes.time.TimeSequence;
059    import org.deegree.datatypes.values.Closure;
060    import org.deegree.datatypes.values.Interval;
061    import org.deegree.datatypes.values.TypedLiteral;
062    import org.deegree.datatypes.values.Values;
063    import org.deegree.framework.util.StringTools;
064    import org.deegree.framework.xml.ElementList;
065    import org.deegree.framework.xml.XMLFragment;
066    import org.deegree.framework.xml.XMLParsingException;
067    import org.deegree.framework.xml.XMLTools;
068    import org.deegree.model.metadata.iso19115.Keywords;
069    import org.deegree.model.metadata.iso19115.Linkage;
070    import org.deegree.model.metadata.iso19115.OnlineResource;
071    import org.deegree.model.spatialschema.Point;
072    import org.deegree.ogcwebservices.LonLatEnvelope;
073    import org.deegree.ogcwebservices.OGCWebServiceException;
074    import org.deegree.ogcwebservices.wcs.describecoverage.InvalidCoverageDescriptionExcpetion;
075    import org.w3c.dom.Element;
076    import org.w3c.dom.Text;
077    
078    /**
079     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a>
080     * @author last edited by: $Author: apoth $
081     * 
082     * @version 1.0. $Revision: 6818 $, $Date: 2007-05-04 15:54:56 +0200 (Fr, 04 Mai 2007) $
083     * 
084     * @since 1.1
085     */
086    public abstract class OGCDocument extends XMLFragment {
087    
088        protected static final URI GMLNS = CommonNamespaces.GMLNS;
089    
090        /**
091         * creates a <tt>LonLatEnvelope</tt> object from the passed element
092         * 
093         * @param element
094         * @return created <tt>LonLatEnvelope</tt>
095         * @throws XMLParsingException
096         * @throws InvalidCoverageDescriptionExcpetion
097         */
098        protected LonLatEnvelope parseLonLatEnvelope( Element element )
099                                throws XMLParsingException, OGCWebServiceException {
100    
101            String srs = XMLTools.getRequiredAttrValue( "srsName", null, element );
102            if ( !"WGS84(DD)".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, "WGS84(DD)" );
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 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>
139         * -elements.
140         * 
141         * This appears to be pretty superfluous (as one <code>keywords</code>- element may contain
142         * several <code>keyword</code> -elements). However, the schema in the OGC document "Web
143         * Coverage Service (WCS), Version 1.0.0", contains the following line (in the definition of the
144         * CoverageOfferingBriefType):
145         * 
146         * <code>&lt;xs:element ref="keywords" minOccurs="0" maxOccurs="unbounded"/&gt;</code>
147         * 
148         * @param el
149         * @return created array of <tt>Keywords</tt>
150         * @throws XMLParsingException
151         */
152        protected Keywords[] parseKeywords( ElementList el, URI namespaceURI ) {
153            Keywords[] kws = new Keywords[el.getLength()];
154            for ( int i = 0; i < kws.length; i++ ) {
155                kws[i] = parseKeywords( el.item( i ), namespaceURI );
156            }
157            return kws;
158        }
159    
160        /**
161         * Creates a <code>Keywords</code> instance from the given <code>keywords</code> -element.
162         * 
163         * @param element
164         * @param namespaceURI
165         * @return created <code>Keywords</code>
166         * @throws XMLParsingException
167         */
168        protected Keywords parseKeywords( Element element, URI namespaceURI ) {
169            ElementList el = XMLTools.getChildElements( "keyword", namespaceURI, element );
170            String[] kws = new String[el.getLength()];
171            for ( int i = 0; i < kws.length; i++ ) {
172                kws[i] = XMLTools.getStringValue( el.item( i ) );
173            }
174            return new Keywords( kws );
175        }
176    
177        /**
178         * creates an <tt>TimeSequence</tt> from the passed element
179         * 
180         * @param element
181         * @return created <tt>TimeSequence</tt>
182         * @throws XMLParsingException
183         * @throws InvalidCoverageDescriptionExcpetion
184         */
185        protected TimeSequence parseTimeSequence( Element element, URI namespaceURI )
186                                throws XMLParsingException, OGCWebServiceException {
187            ElementList el = XMLTools.getChildElements( "timePerdiod", namespaceURI, element );
188            TimePeriod[] timePerdiods = parseTimePeriods( el, namespaceURI );
189            el = XMLTools.getChildElements( "timePosition", GMLNS, element );
190            TimePosition[] timePositions = parseTimePositions( el );
191    
192            return new TimeSequence( timePerdiods, timePositions );
193        }
194    
195        /**
196         * creates an array of <tt>TimePeriod</tt> s from the passed element
197         * 
198         * @param el
199         * @return created array of <tt>TimePeriod</tt> s
200         * @throws XMLParsingException
201         * @throws InvalidCoverageDescriptionExcpetion
202         */
203        protected TimePeriod[] parseTimePeriods( ElementList el, URI namespaceURI )
204                                throws XMLParsingException, OGCWebServiceException {
205            TimePeriod[] timePeriods = new TimePeriod[el.getLength()];
206            for ( int i = 0; i < timePeriods.length; i++ ) {
207                timePeriods[i] = parseTimePeriod( el.item( i ), namespaceURI );
208            }
209            return timePeriods;
210        }
211    
212        /**
213         * creates a <tt>TimePeriod</tt> from the passed element
214         * 
215         * @param element
216         * @return created <tt>TimePeriod</tt>
217         * @throws XMLParsingException
218         * @throws InvalidCoverageDescriptionExcpetion
219         */
220        protected TimePeriod parseTimePeriod( Element element, URI namespaceURI )
221                                throws XMLParsingException, OGCWebServiceException {
222            try {
223                Element begin = XMLTools.getRequiredChildElement( "beginPosition", namespaceURI, element );
224                TimePosition beginPosition = GMLDocument.parseTimePosition( begin );
225                Element end = XMLTools.getRequiredChildElement( "endPosition", namespaceURI, element );
226                TimePosition endPosition = GMLDocument.parseTimePosition( end );
227                String dur = XMLTools.getRequiredStringValue( "timeResolution", namespaceURI, element );
228                TimeDuration resolution = TimeDuration.createTimeDuration( dur );
229    
230                return new TimePeriod( beginPosition, endPosition, resolution );
231            } catch ( InvalidGMLException e ) {
232                LOG.logError( e.getMessage(), e );
233                String s = e.getMessage() + "\n" + StringTools.stackTraceToString( e );
234                throw new OGCWebServiceException( s );
235            }
236    
237        }
238    
239        /**
240         * creates a <tt>Values</tt> object from the passed element
241         * 
242         * @param element
243         * @return created <tt>Values</tt>
244         * @throws XMLParsingException
245         */
246        protected Values parseValues( Element element, URI namespaceURI )
247                                throws XMLParsingException {
248    
249            String type = XMLTools.getAttrValue( element, namespaceURI, "type", null );
250            String semantic = XMLTools.getAttrValue( element, namespaceURI, "semantic", null );
251    
252            ElementList el = XMLTools.getChildElements( "interval", namespaceURI, element );
253            Interval[] intervals = new Interval[el.getLength()];
254            for ( int i = 0; i < intervals.length; i++ ) {
255                intervals[i] = parseInterval( el.item( i ), namespaceURI );
256            }
257    
258            el = XMLTools.getChildElements( "singleValue", namespaceURI, element );
259            TypedLiteral[] singleValues = new TypedLiteral[el.getLength()];
260            for ( int i = 0; i < singleValues.length; i++ ) {
261                singleValues[i] = parseTypedLiteral( el.item( i ) );
262            }
263    
264            Element elem = XMLTools.getChildElement( "default", namespaceURI, element );
265            TypedLiteral def = null;
266            if ( elem != null ) {
267                def = parseTypedLiteral( elem );
268            }
269    
270            try {
271                URI sem = null;
272                if ( semantic != null )
273                    sem = new URI( semantic );
274                URI tp = null;
275                if ( type != null )
276                    tp = new URI( type );
277                return new Values( intervals, singleValues, tp, sem, def );
278            } catch ( URISyntaxException e ) {
279                LOG.logError( e.getMessage(), e );
280                throw new XMLParsingException( "couldn't parse URI from valuesl\n" + StringTools.stackTraceToString( e ) );
281            }
282        }
283    
284        /**
285         * creates an <tt>Interval</tt> object from the passed element
286         * 
287         * @param element
288         * @return created <tt>Interval</tt>
289         * @throws XMLParsingException
290         */
291        protected Interval parseInterval( Element element, URI namespaceURI )
292                                throws XMLParsingException {
293    
294            try {
295                String tmp = XMLTools.getAttrValue( element, namespaceURI, "type", null );
296                URI type = null;
297                if ( tmp != null )
298                    type = new URI( tmp );
299                String semantic = XMLTools.getAttrValue( element, namespaceURI, "semantic", null );
300                tmp = XMLTools.getAttrValue( element, null, "atomic", null );
301                boolean atomic = "true".equals( tmp ) || "1".equals( tmp );
302                String clos = XMLTools.getAttrValue( element, namespaceURI, "closure", null );
303    
304                Closure closure = new Closure( clos );
305    
306                Element elem = XMLTools.getRequiredChildElement( "min", namespaceURI, element );
307                TypedLiteral min = parseTypedLiteral( elem );
308    
309                elem = XMLTools.getRequiredChildElement( "min", namespaceURI, element );
310                TypedLiteral max = parseTypedLiteral( elem );
311    
312                elem = XMLTools.getRequiredChildElement( "res", namespaceURI, element );
313                TypedLiteral res = parseTypedLiteral( elem );
314    
315                URI sem = null;
316                if ( semantic != null )
317                    sem = new URI( semantic );
318    
319                return new Interval( min, max, type, sem, atomic, closure, res );
320            } catch ( URISyntaxException e ) {
321                LOG.logError( e.getMessage(), e );
322                throw new XMLParsingException( "couldn't parse URI from interval\n" + StringTools.stackTraceToString( e ) );
323            }
324    
325        }
326    
327        /**
328         * creates a <tt>TypedLiteral</tt> from the passed element
329         * 
330         * @param element
331         * @return created <tt>TypedLiteral</tt>
332         * @throws XMLParsingException
333         */
334        protected TypedLiteral parseTypedLiteral( Element element )
335                                throws XMLParsingException {
336            try {
337                String tmp = XMLTools.getStringValue( element );
338                String mtype = XMLTools.getAttrValue( element, null, "type", null );
339                URI mt = null;
340                if ( mtype != null )
341                    mt = new URI( mtype );
342                return new TypedLiteral( tmp, mt );
343            } catch ( URISyntaxException e ) {
344                LOG.logError( e.getMessage(), e );
345                throw new XMLParsingException( "couldn't parse URI from typedLiteral\n"
346                                               + StringTools.stackTraceToString( e ) );
347            }
348        }
349    
350        /**
351         * creates an array of <tt>CodeList</tt> objects from the passed element list
352         * 
353         * @param el
354         * @return created array of <tt>CodeList</tt>
355         * @throws XMLParsingException
356         */
357        protected CodeList[] parseCodeListArray( ElementList el )
358                                throws XMLParsingException {
359            CodeList[] cl = new CodeList[el.getLength()];
360            for ( int i = 0; i < cl.length; i++ ) {
361                cl[i] = parseCodeList( el.item( i ) );
362            }
363            return cl;
364        }
365    
366        /**
367         * creates a <tt>CodeList</tt> object from the passed element
368         * 
369         * @param element
370         * @return created <tt>CodeList</tt>
371         * @throws XMLParsingException
372         */
373        protected CodeList parseCodeList( Element element )
374                                throws XMLParsingException {
375            try {
376                String tmp = XMLTools.getAttrValue( element, null, "codeSpace", null );
377                URI codeSpace = null;
378                if ( tmp != null ) {
379                    codeSpace = new URI( tmp );
380                }
381                tmp = XMLTools.getStringValue( element );
382                String[] ar = StringTools.toArray( tmp, " ,;", true );
383                return new CodeList( element.getNodeName(), ar, codeSpace );
384            } catch ( URISyntaxException e ) {
385                LOG.logError( e.getMessage(), e );
386                throw new XMLParsingException( "couldn't parse URI from CodeList\n" + StringTools.stackTraceToString( e ) );
387            }
388        }
389    
390        /**
391         * Creates an <tt>OnLineResource</tt> instance from the passed element. The element contains
392         * an OnlineResourse as it is used in the OGC Web XXX CapabilitiesService specifications.
393         * 
394         * TODO Compare with XMLFragment#parseSimpleLink
395         * 
396         * @param element
397         * @return
398         * @throws XMLParsingException
399         */
400        protected OnlineResource parseOnLineResource( Element element )
401                                throws XMLParsingException {
402    
403            OnlineResource olr = null;
404            String attrValue = XMLTools.getRequiredAttrValue( "href", XLNNS, element );
405            URL href = null;
406            try {
407                href = resolve( attrValue );
408            } catch ( MalformedURLException e ) {
409                LOG.logError( e.getMessage(), e );
410                throw new XMLParsingException( "Given value '" + attrValue + "' in attribute 'href' " + "(namespace: "
411                                               + XLNNS + ") of element '" + element.getLocalName() + "' (namespace: "
412                                               + element.getNamespaceURI() + ") is not a valid URL." );
413            }
414            Linkage linkage = new Linkage( href, Linkage.SIMPLE );
415            String title = XMLTools.getAttrValue( element, XLNNS, "title", null );
416            olr = new OnlineResource( null, null, linkage, null, title, href.getProtocol() );
417            return olr;
418        }
419    
420        /**
421         * Creates a new instance of <code>PropertyPath</code> from the given text node.
422         * <p>
423         * NOTE: Namespace prefices used in the property path must be bound using XML namespace
424         * mechanisms (i.e. using xmlns attributes in the document).
425         * 
426         * @param textNode
427         *            string representation of the property path
428         * @return new PropertyPath instance
429         * @see PropertyPath
430         */
431        public static PropertyPath parsePropertyPath( Text textNode )
432                                throws XMLParsingException {
433    
434            String path = XMLTools.getStringValue( textNode );
435            String[] steps = StringTools.toArray( path, "/", false );
436            List<PropertyPathStep> propertyPathSteps = new ArrayList<PropertyPathStep>( steps.length );
437    
438            for ( int i = 0; i < steps.length; i++ ) {
439                PropertyPathStep propertyStep = null;
440                QualifiedName propertyName = null;
441                String step = steps[i];
442                boolean isAttribute = false;
443                boolean isIndexed = false;
444                int selectedIndex = -1;
445    
446                // check if step begins with '@' -> must be the final step then
447                if ( step.startsWith( "@" ) ) {
448                    if ( i != steps.length - 1 ) {
449                        String msg = "PropertyName '" + path + "' is illegal: the attribute specifier may only "
450                                     + "be used for the final step.";
451                        throw new XMLParsingException( msg );
452                    }
453                    step = step.substring( 1 );
454                    isAttribute = true;
455                }
456    
457                // check if the step ends with brackets ([...])
458                if ( step.endsWith( "]" ) ) {
459                    if ( isAttribute ) {
460                        String msg = "PropertyName '" + path + "' is illegal: if the attribute specifier ('@') is used, "
461                                     + "index selection ('[...']) is not possible.";
462                        throw new XMLParsingException( msg );
463                    }
464                    int bracketPos = step.indexOf( '[' );
465                    if ( bracketPos < 0 ) {
466                        String msg = "PropertyName '" + path + "' is illegal. No opening brackets found for step '" + step
467                                     + "'.";
468                        throw new XMLParsingException( msg );
469                    }
470                    try {
471                        selectedIndex = Integer.parseInt( step.substring( bracketPos + 1, step.length() - 1 ) );
472                    } catch ( NumberFormatException e ) {
473                        String msg = "PropertyName '" + path + "' is illegal. Specified index '"
474                                     + step.substring( bracketPos + 1, step.length() - 1 ) + "' is not a number.";
475                        throw new XMLParsingException( msg );
476                    }
477                    step = step.substring( 0, bracketPos );
478                    isIndexed = true;
479                }
480    
481                // determine namespace prefix and binding (if any)
482                int colonPos = step.indexOf( ':' );
483                if ( colonPos < 0 ) {
484                    propertyName = new QualifiedName( step );
485                } else {
486                    String prefix = step.substring( 0, colonPos );
487                    step = step.substring( colonPos + 1 );
488                    URI namespace = null;
489                    try {
490                        namespace = XMLTools.getNamespaceForPrefix( prefix, textNode );
491                    } catch ( URISyntaxException e ) {
492                        throw new XMLParsingException( "Error parsing PropertyName: " + e.getMessage() );
493                    }
494                    if ( namespace == null ) {
495                        throw new XMLParsingException( "PropertyName '" + path + "' uses an unbound namespace prefix: "
496                                                       + prefix );
497                    }
498                    propertyName = new QualifiedName( prefix, step, namespace );
499                }
500    
501                if ( isAttribute ) {
502                    propertyStep = PropertyPathFactory.createAttributePropertyPathStep( propertyName );
503                } else if ( isIndexed ) {
504                    propertyStep = PropertyPathFactory.createPropertyPathStep( propertyName, selectedIndex );
505                } else {
506                    propertyStep = PropertyPathFactory.createPropertyPathStep( propertyName );
507                }
508                propertyPathSteps.add( propertyStep );
509            }
510            return PropertyPathFactory.createPropertyPath( propertyPathSteps );
511        }
512    }