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