001    // $HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/owscommon/OWSCommonCapabilitiesDocument.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.owscommon;
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.List;
051    import java.util.Map;
052    
053    import org.deegree.datatypes.Code;
054    import org.deegree.datatypes.xlink.SimpleLink;
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.XMLParsingException;
060    import org.deegree.framework.xml.XMLTools;
061    import org.deegree.model.metadata.iso19115.Address;
062    import org.deegree.model.metadata.iso19115.ContactInfo;
063    import org.deegree.model.metadata.iso19115.Keywords;
064    import org.deegree.model.metadata.iso19115.OnlineResource;
065    import org.deegree.model.metadata.iso19115.Phone;
066    import org.deegree.model.metadata.iso19115.TypeCode;
067    import org.deegree.model.spatialschema.Envelope;
068    import org.deegree.model.spatialschema.GeometryFactory;
069    import org.deegree.ogcbase.CommonNamespaces;
070    import org.deegree.ogcwebservices.getcapabilities.DCPType;
071    import org.deegree.ogcwebservices.getcapabilities.HTTP;
072    import org.deegree.ogcwebservices.getcapabilities.OGCCapabilitiesDocument;
073    import org.deegree.ogcwebservices.getcapabilities.Operation;
074    import org.deegree.ogcwebservices.getcapabilities.Protocol;
075    import org.deegree.ogcwebservices.getcapabilities.ServiceIdentification;
076    import org.deegree.ogcwebservices.getcapabilities.ServiceProvider;
077    import org.w3c.dom.Element;
078    import org.w3c.dom.Node;
079    
080    /**
081     * Represents a configuration document for an OGC-Webservice according to the
082     * <code>OWS Common Implementation Specification 0.3</code>.
083     * <p>
084     * It consists of the following elements: <table border="1">
085     * <tr>
086     * <th>Name</th>
087     * <th>Function</th>
088     * </tr>
089     * <tr>
090     * <td>ServiceIdentification</td>
091     * <td>corresponds to and expands the SV_ServiceIdentification class in ISO 19119</td>
092     * </tr>
093     * <tr>
094     * <td>ServiceProvider</td>
095     * <td>corresponds to and expands the SV_ServiceProvider class in ISO 19119 </td>
096     * </tr>
097     * <tr>
098     * <td>OperationsMetadata</td>
099     * <td>contains set of Operation elements that each corresponds to and expand the
100     * SV_OperationsMetadata class in ISO 19119</td>
101     * </tr>
102     * <tr>
103     * <td>Contents</td>
104     * <td>whenever relevant, contains set of elements that each corresponds to the
105     * MD_DataIdentification class in ISO 19119 and 19115</td>
106     * </tr>
107     * </table>
108     * 
109     * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider </a>
110     * @author last edited by: $Author: apoth $
111     * 
112     * @version $Revision: 6953 $, $Date: 2007-05-08 20:52:18 +0200 (Di, 08 Mai 2007) $
113     */
114    public abstract class OWSCommonCapabilitiesDocument extends OGCCapabilitiesDocument {
115    
116        private static final ILogger LOG = LoggerFactory.getLogger( OWSCommonCapabilitiesDocument.class );
117    
118        public final static String ALL_NAME = "All";
119    
120        public final static String SERVICE_IDENTIFICATION_NAME = "ServiceIdentification";
121    
122        public final static String SERVICE_PROVIDER_NAME = "ServiceProvider";
123    
124        public final static String OPERATIONS_METADATA_NAME = "OperationsMetadata";
125    
126        public final static String CONTENTS_NAME = "Contents";
127    
128        protected static final URI OWSNS = CommonNamespaces.OWSNS;
129    
130        protected static final URI OGCNS = CommonNamespaces.OGCNS;
131    
132        /**
133         * Returns the class representation for the <code>ServiceProvider</code> section of the
134         * document.
135         * 
136         * @return class representation for the <code>ServiceProvider</code> section
137         * @throws XMLParsingException
138         */
139        public ServiceProvider getServiceProvider()
140                                throws XMLParsingException {
141    
142            Element element = XMLTools.getRequiredChildElement( "ServiceProvider", OWSNS, getRootElement() );
143    
144            // 'ProviderName' element (optional, default value: 'deegree')
145            String providerName = XMLTools.getStringValue( "ProviderName", OWSNS, element, "deegree" );
146    
147            // 'ProviderSite' element (optional)
148            Element providerSiteElement = XMLTools.getChildElement( "ProviderSite", OWSNS, element );
149            SimpleLink providerSite = null;
150            if ( providerSiteElement != null ) {
151                providerSite = parseSimpleLink( providerSiteElement );
152            }
153    
154            // 'ServiceContact' element (mandatory)
155            Element serviceContactElement = XMLTools.getRequiredChildElement( "ServiceContact", OWSNS, element );
156    
157            // 'IndividualName' element (optional)
158            String individualName = XMLTools.getStringValue( "IndividualName", OWSNS, serviceContactElement, null );
159    
160            // 'PositionName' element (optional)
161            String positionName = XMLTools.getStringValue( "PositionName", OWSNS, serviceContactElement, null );
162    
163            // 'ContactInfo' element (optional)
164            ContactInfo contactInfo = null;
165            Element contactInfoElement = XMLTools.getChildElement( "ContactInfo", OWSNS, serviceContactElement );
166            if ( contactInfoElement != null ) {
167                contactInfo = getContactInfo( contactInfoElement );
168            }
169            TypeCode role = null;
170            Element roleElement = (Element) XMLTools.getNode( serviceContactElement, "ows:Role", nsContext );
171            if ( roleElement != null ) {
172                role = getCodeType( roleElement );
173            }
174            ServiceProvider serviceProvider = new ServiceProvider( providerName, providerSite, individualName,
175                                                                   positionName, contactInfo, role );
176    
177            return serviceProvider;
178        }
179    
180        /**
181         * Returns the class representation for the <code>ServiceIdentification</code> section of the
182         * document.
183         * 
184         * @return class representation for the <code>ServiceIdentification</code> section
185         * @throws XMLParsingException
186         */
187        public ServiceIdentification getServiceIdentification()
188                                throws XMLParsingException {
189    
190            Element element = XMLTools.getRequiredChildElement( "ServiceIdentification", OWSNS, getRootElement() );
191    
192            // 'ServiceType' element (mandatory)
193            Element serviceTypeElement = XMLTools.getRequiredChildElement( "ServiceType", OWSNS, element );
194            Code serviceType = null;
195            try {
196                String codeSpace = XMLTools.getAttrValue( serviceTypeElement, OWSNS, "codeSpace", null );
197                URI uri = codeSpace != null ? new URI( codeSpace ) : null;
198                serviceType = new Code( XMLTools.getStringValue( serviceTypeElement ), uri );
199            } catch ( URISyntaxException e ) {
200                throw new XMLParsingException( "Given value '"
201                                               + XMLTools.getAttrValue( serviceTypeElement, OWSNS, "codeSpace", null )
202                                               + "' in attribute 'codeSpace' of element 'ServiceType' " + "(namespace: '"
203                                               + OWSNS + "') is not a valid URI." );
204            }
205    
206            // 'ServiceTypeVersion' elements (mandatory)
207            String[] serviceTypeVersions = XMLTools.getRequiredNodeAsStrings( element, "ows:ServiceTypeVersion", nsContext,
208                                                                              ",;" );
209    
210            // 'Title' element (mandatory)
211            String title = XMLTools.getRequiredStringValue( "Title", OWSNS, element );
212    
213            // 'Abstract' element (optional)
214            String serviceAbstract = XMLTools.getRequiredStringValue( "Abstract", OWSNS, element );
215    
216            // 'Keywords' elements (optional)
217            List keywordsList = XMLTools.getNodes( element, "ows:Keywords", nsContext );
218            Keywords[] keywords = getKeywords( keywordsList );
219    
220            // 'Fees' element (optional)
221            String fees = XMLTools.getStringValue( "Fees", OWSNS, element, null );
222    
223            // 'AccessConstraints' elements (optional)
224            String accessConstraints[] = XMLTools.getNodesAsStrings( element, "ows:AccessConstraints", nsContext );
225    
226            ServiceIdentification serviceIdentification = new ServiceIdentification( serviceType, serviceTypeVersions,
227                                                                                     title, serviceAbstract, keywords,
228                                                                                     fees, accessConstraints );
229    
230            return serviceIdentification;
231        }
232    
233        /**
234         * Creates a <code>Keywords</code> instance from the given element of type
235         * <code>ows:KeywordsType</code>.
236         * 
237         * NOTE: This method is redefined here (it is already defined in <code>OGCDocument</code>),
238         * because the spelling of the first letter ('K') changed in the OWS Common Implementation
239         * Specification 0.2 from lowercase to uppercase.
240         * 
241         * @param element
242         * @return created <code>Keywords</code>
243         * @throws XMLParsingException
244         */
245        protected Keywords getKeywords( Element element )
246                                throws XMLParsingException {
247            TypeCode codeType = null;
248            Element codeTypeElement = (Element) XMLTools.getNode( element, "ows:Type", nsContext );
249            if ( codeTypeElement != null ) {
250                codeType = getCodeType( codeTypeElement );
251            }
252            Keywords keywords = new Keywords( XMLTools.getNodesAsStrings( element, "ows:Keyword/text()", nsContext ), null,
253                                              codeType );
254            return keywords;
255        }
256    
257        /**
258         * Creates an array of <code> Keywords </code> instances from the passed list of elements of
259         * type <code> ows:KeywordsType </code>.
260         * 
261         * This may appear to be pretty superfluous (as one <code> ows:KeywordsType
262         * </code> can hold
263         * several elements of type <code> ows:Keyword
264         * </code>.
265         * 
266         * @param nl
267         *            may be null
268         * @return created array of <code> Keywords </code>, null if <code>NodeList</code> constains
269         *         zero elements
270         * @throws XMLParsingException
271         */
272        protected Keywords[] getKeywords( List nl )
273                                throws XMLParsingException {
274            Keywords[] kws = null;
275            if ( nl.size() > 0 ) {
276                kws = new Keywords[nl.size()];
277                for ( int i = 0; i < kws.length; i++ ) {
278                    kws[i] = getKeywords( (Element) nl.get( i ) );
279                }
280            }
281            return kws;
282        }
283    
284        /**
285         * Creates a <code>DCPType</code> object from the passed <code>DCP</code> element.
286         * <p>
287         * NOTE: Currently the <code>OnlineResources</code> included in the <code>DCPType</code> are
288         * just stored as simple <code>URLs</code> (not as <code>OnLineResource</code> instances)!
289         * <p>
290         * NOTE: In an <code>OGCStandardCapabilitiesDocument</code> the <code>XLinks</code> (the
291         * <code>URLs</code>) are stored in separate elements (<code>OnlineResource</code>), in
292         * an <code>OGCCommonCapabilitiesDocument</code> they are the
293         * <code>Get<code>/<code>Post</code> elements themselves.
294         * 
295         * @param element
296         * @return created <code>DCPType</code>
297         * @throws XMLParsingException
298         * @see org.deegree.ogcwebservices.getcapabilities.OGCStandardCapabilities
299         */
300        protected DCPType getDCP( Element element )
301                                throws XMLParsingException {
302    
303            DCPType dcpType = null;
304            try {
305                Element elem = (Element) XMLTools.getRequiredNode( element, "ows:HTTP", nsContext );
306                List nl = XMLTools.getNodes( elem, "ows:Get", nsContext );
307    
308                URL[] get = new URL[nl.size()];
309                for ( int i = 0; i < get.length; i++ ) {
310                    String s = XMLTools.getNodeAsString( (Node) nl.get( i ), "./@xlink:href", nsContext, null );
311                    if ( s == null ) {
312                        s = XMLTools.getRequiredNodeAsString( (Node) nl.get( i ), "./ows:OnlineResource/@xlink:href",
313                                                              nsContext );
314                    }
315                    get[i] = new URL( s );
316                }
317                nl = XMLTools.getNodes( elem, "ows:Post", nsContext );
318    
319                URL[] post = new URL[nl.size()];
320                for ( int i = 0; i < post.length; i++ ) {
321                    String s = XMLTools.getNodeAsString( (Node) nl.get( i ), "./@xlink:href", nsContext, null );
322                    if ( s == null ) {
323                        s = XMLTools.getRequiredNodeAsString( (Node) nl.get( i ), "./ows:OnlineResource/@xlink:href",
324                                                              nsContext );
325                    }
326                    post[i] = new URL( s );
327                }
328                Protocol protocol = new HTTP( get, post );
329                dcpType = new DCPType( protocol );
330            } catch ( MalformedURLException e ) {
331                throw new XMLParsingException( "Couldn't parse DCPType onlineresource URL about: "
332                                               + StringTools.stackTraceToString( e ) );
333            }
334    
335            return dcpType;
336        }
337    
338        /**
339         * Creates an array of <code>DCPType</code> objects from the passed element list.
340         * <p>
341         * NOTE: Currently the <code>OnlineResources</code> included in the <code>DCPType</code> are
342         * just stored as simple <code>URLs</code> (not as <code>OnLineResource</code> instances)!
343         * 
344         * @param el
345         * @return array of <code>DCPType</code>
346         * @throws XMLParsingException
347         */
348        protected DCPType[] getDCPs( List el )
349                                throws XMLParsingException {
350    
351            DCPType[] dcpTypes = new DCPType[el.size()];
352            for ( int i = 0; i < dcpTypes.length; i++ ) {
353                dcpTypes[i] = getDCP( (Element) el.get( i ) );
354            }
355    
356            return dcpTypes;
357        }
358    
359        /**
360         * Creates a class representation of an <code>ows:Operation</code>- element.
361         * 
362         * @param name
363         * @param isMandatory
364         * @param operations
365         * @return operation
366         * @throws XMLParsingException
367         */
368        protected Operation getOperation( String name, boolean isMandatory, Map operations )
369                                throws XMLParsingException {
370    
371            Operation operation = null;
372            Element operationElement = (Element) operations.get( name );
373            if ( operationElement == null ) {
374                if ( isMandatory ) {
375                    throw new XMLParsingException( "Mandatory operation '" + name + "' not defined in "
376                                                   + "'OperationsMetadata'-section." );
377                }
378            } else {
379                // "ows:Parameter"-elements
380                ElementList parameterElements = XMLTools.getChildElements( "Parameter", OWSNS, operationElement );
381                OWSDomainType[] parameters = new OWSDomainType[parameterElements.getLength()];
382                for ( int i = 0; i < parameters.length; i++ ) {
383                    parameters[i] = getOWSDomainType( name, parameterElements.item( i ) );
384                }
385                DCPType[] dcps = getDCPs( XMLTools.getRequiredNodes( operationElement, "ows:DCP", nsContext ) );
386                operation = new Operation( name, dcps, parameters );
387    
388            }
389    
390            return operation;
391        }
392    
393        /**
394         * Creates a class representation of an element of type <code>ows:DomainType</code>.
395         * 
396         * @param operation
397         * @param element
398         * @return domainType
399         * @throws XMLParsingException
400         */
401        protected OWSDomainType getOWSDomainType( String operation, Element element )
402                                throws XMLParsingException {
403    
404            // "name"-attribute
405            String name = XMLTools.getRequiredNodeAsString( element, "@name", nsContext );
406    
407            // "ows:Value"-elements
408            String[] values = XMLTools.getNodesAsStrings( element, "ows:Value/text()", nsContext );
409            if ( values.length < 1 ) {
410                throw new XMLParsingException( "At least one 'ows:Value'-element must be defined in each "
411                                               + "element of type 'ows:DomainType'." );
412            }
413    
414            // TODO: "ows:Metadata"-elements
415            OWSDomainType domainType = new OWSDomainType( name, values, null );
416    
417            return domainType;
418        }
419    
420        /**
421         * Creates a class representation of an element of type <code>ows:CodeType</code>.
422         * 
423         * @param element
424         *            an ows:CodeType element
425         * @return the TypeCode (which is defined as something like a dictionary, thesaurus etc.)
426         * @throws XMLParsingException
427         */
428        protected TypeCode getCodeType( Element element )
429                                throws XMLParsingException {
430    
431            String code = XMLTools.getRequiredNodeAsString( element, "text()", nsContext );
432    
433            URI codeSpace = null;
434            String codeSpaceString = XMLTools.getNodeAsString( element, "@codeSpace", nsContext, null );
435            if ( codeSpaceString != null ) {
436                try {
437                    codeSpace = new URI( codeSpaceString );
438                } catch ( URISyntaxException e ) {
439                    throw new XMLParsingException( "'" + codeSpaceString + "' does not denote a valid URI in: "
440                                                   + e.getMessage() );
441                }
442            }
443            return new TypeCode( code, codeSpace );
444        }
445    
446        /**
447         * Creates a <code>ContactInfo</code> object from the given element of type
448         * <code>ows:ContactInfoType</code>.
449         * 
450         * @param element
451         * @return ContactInfo
452         * @throws XMLParsingException
453         */
454        private ContactInfo getContactInfo( Element element )
455                                throws XMLParsingException {
456    
457            // 'Phone' element (optional)
458            Phone phone = null;
459            Element phoneElement = XMLTools.getChildElement( "Phone", OWSNS, element );
460            if ( phoneElement != null ) {
461                phone = parsePhone( phoneElement, OWSNS );
462            }
463    
464            // 'Address' element (optional)
465            Address address = null;
466            Element addressElement = XMLTools.getChildElement( "Address", OWSNS, element );
467            if ( addressElement != null ) {
468                address = parseAddress( addressElement, OWSNS );
469            }
470    
471            // 'OnlineResource' element (optional)
472            OnlineResource onlineResource = null;
473            Element onlineResourceElement = XMLTools.getChildElement( "OnlineResource", OWSNS, element );
474            if ( onlineResourceElement != null ) {
475                onlineResource = parseOnLineResource( onlineResourceElement );
476            }
477    
478            String hoursOfService = XMLTools.getNodeAsString( element, "ows:HoursOfService/text()", nsContext, null );
479            String contactInstructions = XMLTools.getNodeAsString( element, "ows:ContactInstructions/text()", nsContext,
480                                                                   null );
481    
482            return new ContactInfo( address, contactInstructions, hoursOfService, onlineResource, phone );
483        }
484    
485        /**
486         * Creates an <code>Envelope</code> object from the given element of type
487         * <code>ows:WGS84BoundingBoxType</code>.
488         * 
489         * @param element
490         * @return an <code>Envelope</code> object
491         * @throws XMLParsingException
492         */
493        protected Envelope getWGS84BoundingBoxType( Element element )
494                                throws XMLParsingException {
495            double[] lowerCorner = XMLTools.getRequiredNodeAsDoubles( element, "ows:LowerCorner/text()", nsContext, " " );
496            if ( lowerCorner.length != 2 ) {
497                throw new XMLParsingException( "Element 'ows:LowerCorner' must contain exactly two double values." );
498            }
499            double[] upperCorner = XMLTools.getRequiredNodeAsDoubles( element, "ows:UpperCorner/text()", nsContext, " " );
500            if ( upperCorner.length != 2 ) {
501                throw new XMLParsingException( "Element 'ows:UpperCorner' must contain exactly two double values." );
502            }
503            return GeometryFactory.createEnvelope( lowerCorner[0], lowerCorner[1], upperCorner[0], upperCorner[1], null );
504        }
505    }