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