001    // $HeadURL: svn+ssh://mschneider@svn.wald.intevation.org/deegree/base/trunk/src/org/deegree/ogcwebservices/wfs/capabilities/WFSCapabilitiesDocument.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.ogcwebservices.wfs.capabilities;
037    
038    import java.io.IOException;
039    import java.net.MalformedURLException;
040    import java.net.URI;
041    import java.net.URL;
042    import java.security.InvalidParameterException;
043    import java.util.ArrayList;
044    import java.util.List;
045    
046    import org.deegree.datatypes.QualifiedName;
047    import org.deegree.framework.util.StringTools;
048    import org.deegree.framework.xml.XMLParsingException;
049    import org.deegree.framework.xml.XMLTools;
050    import org.deegree.model.filterencoding.capabilities.FilterCapabilities;
051    import org.deegree.model.filterencoding.capabilities.FilterCapabilities100Fragment;
052    import org.deegree.model.metadata.iso19115.Keywords;
053    import org.deegree.model.spatialschema.Envelope;
054    import org.deegree.model.spatialschema.GeometryFactory;
055    import org.deegree.ogcwebservices.getcapabilities.DCPType;
056    import org.deegree.ogcwebservices.getcapabilities.HTTP;
057    import org.deegree.ogcwebservices.getcapabilities.InvalidCapabilitiesException;
058    import org.deegree.ogcwebservices.getcapabilities.MetadataURL;
059    import org.deegree.ogcwebservices.getcapabilities.OGCCapabilities;
060    import org.deegree.ogcwebservices.getcapabilities.OGCCapabilitiesDocument;
061    import org.deegree.ogcwebservices.getcapabilities.Operation;
062    import org.deegree.ogcwebservices.getcapabilities.OperationsMetadata;
063    import org.deegree.ogcwebservices.getcapabilities.ServiceIdentification;
064    import org.deegree.ogcwebservices.getcapabilities.ServiceProvider;
065    import org.deegree.owscommon.OWSDomainType;
066    import org.w3c.dom.Document;
067    import org.w3c.dom.Element;
068    import org.xml.sax.SAXException;
069    
070    /**
071     * Represents a capabilities document for an OGC WFS 1.0.0 compliant web service.
072     * <p>
073     * NOTE: The parsing methods produces beans that are designed to match the WFS 1.1.0 specification. Needs testing! The
074     * following things are still TBD:
075     * <ul>
076     * <li>Respect value of wfs:Service/wfs:OnlineResource element. Should possible be stored in a {@link ServiceProvider}
077     * bean.</li>
078     * <li>Respect value of wfs:Capability/wfs:DescribeFeatureType/wfs:SchemaDescriptionLanguage element. Should possibly be
079     * stored as an {@link OWSDomainType} bean in the corresponding {@link Operation} object.</li>
080     * <li>Respect value of wfs:Capability/wfs:GetFeature/wfs:ResultFormat element. Should possibly be stored as an
081     * {@link OWSDomainType} bean in the corresponding {@link Operation} object.</li>
082     * <li>Respect value of wfs:Capability/wfs:GetFeatureWithLock/wfs:ResultFormat element. Should possibly be stored as an
083     * {@link OWSDomainType} bean in the corresponding {@link Operation} object.</li>
084     * </ul>
085     *
086     * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider </a>
087     * @author last edited by: $Author: rbezema $
088     *
089     * @version $Revision: 11377 $, $Date: 2008-04-23 07:55:34 +0000 (Mi, 23 Apr 2008) $
090     */
091    public class WFSCapabilitiesDocument_1_0_0 extends OGCCapabilitiesDocument {
092    
093        private static final long serialVersionUID = 4538469826043112486L;
094    
095        private static final String[] VALID_TYPES = { "TDC211", "FGDC" };
096    
097        private static final String[] VALID_FORMATS = { "XML", "SGML", "TXT" };
098    
099        /**
100         * Creates a skeleton capabilities document that contains the mandatory elements only.
101         *
102         * @throws IOException
103         * @throws SAXException
104         */
105        public void createEmptyDocument()
106                                throws IOException, SAXException {
107            // set up the root document.
108            Document doc = XMLTools.create();
109            Element root = doc.createElementNS( "http://www.opengis.net/wfs", "wfs:WFS_Capabilities" );
110            doc.importNode( root, false );
111    
112            setRootElement( root );
113            root.setAttribute( "version", "1.0.0" );
114            root.setAttribute( "updateSequence", "0" );
115        }
116    
117        /**
118         * Creates a class representation of the document.
119         *
120         * @return class representation of the capabilities document
121         */
122        @Override
123        public OGCCapabilities parseCapabilities()
124                                throws InvalidCapabilitiesException {
125    
126            WFSCapabilities wfsCapabilities = null;
127            try {
128    
129                wfsCapabilities = new WFSCapabilities( parseVersion(), parseUpdateSequence(), getService(), null,
130                                                       getCapability(), getFeatureTypeList(), null, null, null,
131                                                       getFilterCapabilities() );
132            } catch ( XMLParsingException e ) {
133                throw new InvalidCapabilitiesException( e.getMessage() + "\n" + StringTools.stackTraceToString( e ) );
134            }
135    
136            return wfsCapabilities;
137        }
138    
139        /**
140         * Returns the class representation for the <code>Service</code> section of the document.
141         *
142         * @return class representation for the <code>Service</code> section
143         * @throws XMLParsingException
144         */
145        public ServiceIdentification getService()
146                                throws XMLParsingException {
147    
148            Element element = XMLTools.getRequiredElement( getRootElement(), "wfs:Service", nsContext );
149    
150            String name = XMLTools.getRequiredNodeAsString( element, "wfs:Name/text()", nsContext );
151            String title = XMLTools.getRequiredNodeAsString( element, "wfs:Title/text()", nsContext );
152            String abstract_ = XMLTools.getNodeAsString( element, "wfs:Abstract/text()", nsContext, null );
153    
154            String keywordsValue = XMLTools.getNodeAsString( element, "wfs:Keywords/text()", nsContext, null );
155            Keywords[] keywords = null;
156            if ( keywordsValue != null ) {
157                keywords = new Keywords[] { new Keywords( new String[] { keywordsValue } ) };
158            }
159    
160            String[] serviceTypeVersions = new String[] { "1.0.0" };
161    
162            // String onlineResource = XMLTools.getRequiredNodeAsString( element, "wfs:OnlineResource/text()", nsContext );
163    
164            // 'Fees' element (optional)
165            String fees = XMLTools.getNodeAsString( element, "wfs:Fees/text()", nsContext, null );
166    
167            return new ServiceIdentification( name, null, serviceTypeVersions, title, abstract_, keywords, fees, null );
168        }
169    
170        /**
171         * Creates an object representation of the <code>wfs:Capability</code> section.
172         *
173         * @return object representation of the <code>wfs:Capability</code> section
174         * @throws XMLParsingException
175         */
176        public OperationsMetadata getCapability()
177                                throws XMLParsingException {
178    
179            Element requestElement = XMLTools.getRequiredElement( getRootElement(), "wfs:Capability/wfs:Request", nsContext );
180    
181            // wfs:GetCapabilities element
182            Operation getCapabilities = null;
183            Element getCapabilitiesElement = XMLTools.getElement( requestElement, "wfs:GetCapabilities", nsContext );
184            if ( getCapabilitiesElement != null ) {
185                List<Element> dcpTypeElements = XMLTools.getElements( getCapabilitiesElement, "wfs:DCPType", nsContext );
186                DCPType[] dcpTypes = new DCPType[dcpTypeElements.size()];
187                for ( int i = 0; i < dcpTypes.length; i++ ) {
188                    Element dcpTypeElement = dcpTypeElements.get( i );
189                    dcpTypes[i] = getDCPTypeType( dcpTypeElement );
190                }
191                getCapabilities = new Operation( "GetCapabilities", dcpTypes );
192            }
193    
194            // wfs:DescribeFeatureType element
195            Operation describeFeatureType = null;
196            Element describeFeatureTypeElement = XMLTools.getElement( requestElement, "wfs:DescribeFeatureType", nsContext );
197            if ( describeFeatureTypeElement != null ) {
198                List<Element> dcpTypeElements = XMLTools.getElements( describeFeatureTypeElement, "wfs:DCPType", nsContext );
199                DCPType[] dcpTypes = new DCPType[dcpTypeElements.size()];
200                for ( int i = 0; i < dcpTypes.length; i++ ) {
201                    Element dcpTypeElement = dcpTypeElements.get( i );
202                    dcpTypes[i] = getDCPTypeType( dcpTypeElement );
203                }
204    
205                // TODO evaluate SchemaDescriptionLanguage element
206    
207                describeFeatureType = new Operation( "DescribeFeatureType", dcpTypes );
208            }
209    
210            // wfs:GetFeature element
211            Operation getFeature = null;
212            Element getFeatureElement = XMLTools.getElement( requestElement, "wfs:GetFeature", nsContext );
213            if ( getFeatureElement != null ) {
214                List<Element> dcpTypeElements = XMLTools.getElements( getFeatureElement, "wfs:DCPType", nsContext );
215                DCPType[] dcpTypes = new DCPType[dcpTypeElements.size()];
216                for ( int i = 0; i < dcpTypes.length; i++ ) {
217                    Element dcpTypeElement = dcpTypeElements.get( i );
218                    dcpTypes[i] = getDCPTypeType( dcpTypeElement );
219                }
220    
221                // TODO evaluate ResultFormat element
222    
223                getFeature = new Operation( "GetFeature", dcpTypes );
224            }
225    
226            // wfs:GetFeature element
227            Operation getFeatureWithLock = null;
228            Element getFeatureWithLockElement = XMLTools.getElement( requestElement, "wfs:GetFeatureWithLock", nsContext );
229            if ( getFeatureWithLockElement != null ) {
230                List<Element> dcpTypeElements = XMLTools.getElements( getFeatureWithLockElement, "wfs:DCPType", nsContext );
231                DCPType[] dcpTypes = new DCPType[dcpTypeElements.size()];
232                for ( int i = 0; i < dcpTypes.length; i++ ) {
233                    Element dcpTypeElement = dcpTypeElements.get( i );
234                    dcpTypes[i] = getDCPTypeType( dcpTypeElement );
235                }
236    
237                // TODO evaluate ResultFormat element
238    
239                getFeatureWithLock = new Operation( "GetFeatureWithLock", dcpTypes );
240            }
241    
242            // wfs:LockFeature element
243            Operation lockFeature = null;
244            Element lockFeatureElement = XMLTools.getElement( requestElement, "wfs:LockFeature", nsContext );
245            if ( lockFeatureElement != null ) {
246                List<Element> dcpTypeElements = XMLTools.getElements( lockFeatureElement, "wfs:DCPType", nsContext );
247                DCPType[] dcpTypes = new DCPType[dcpTypeElements.size()];
248                for ( int i = 0; i < dcpTypes.length; i++ ) {
249                    Element dcpTypeElement = dcpTypeElements.get( i );
250                    dcpTypes[i] = getDCPTypeType( dcpTypeElement );
251                }
252                lockFeature = new Operation( "LockFeature", dcpTypes );
253            }
254    
255            // wfs:Transaction element
256            Operation transaction = null;
257            Element transactionElement = XMLTools.getElement( requestElement, "wfs:Transaction", nsContext );
258            if ( transactionElement != null ) {
259                List<Element> dcpTypeElements = XMLTools.getElements( transactionElement, "wfs:DCPType", nsContext );
260                DCPType[] dcpTypes = new DCPType[dcpTypeElements.size()];
261                for ( int i = 0; i < dcpTypes.length; i++ ) {
262                    Element dcpTypeElement = dcpTypeElements.get( i );
263                    dcpTypes[i] = getDCPTypeType( dcpTypeElement );
264                }
265                transaction = new Operation( "Transaction", dcpTypes );
266            }
267    
268            return new WFSOperationsMetadata( getCapabilities, describeFeatureType, getFeature, getFeatureWithLock, null,
269                                              lockFeature, transaction, null, null );
270        }
271    
272        /**
273         * Creates an object representation of the given <code>wfs:DCPTypeType</code> element.
274         *
275         * @return object representation of the given <code>wfs:DCPTypeType</code> element.
276         * @throws XMLParsingException
277         */
278        private DCPType getDCPTypeType( Element element )
279                                throws XMLParsingException {
280    
281            Element httpElement = XMLTools.getRequiredElement( element, "wfs:HTTP", nsContext );
282            String[] gets = XMLTools.getNodesAsStrings( httpElement, "wfs:Get/@onlineResource", nsContext );
283            URL[] getURLs = new URL[gets.length];
284            for ( int j = 0; j < gets.length; j++ ) {
285                try {
286                    getURLs[j] = new URL( gets[j] );
287                } catch ( MalformedURLException e ) {
288                    throw new XMLParsingException( "OnlineResource '" + gets[j] + "' is not a valid URL." );
289                }
290            }
291            String[] posts = XMLTools.getNodesAsStrings( httpElement, "wfs:Post/@onlineResource", nsContext );
292            URL[] postURLs = new URL[posts.length];
293            for ( int j = 0; j < posts.length; j++ ) {
294                try {
295                    postURLs[j] = new URL( posts[j] );
296                } catch ( MalformedURLException e ) {
297                    throw new XMLParsingException( "OnlineResource '" + posts[j] + "' is not a valid URL." );
298                }
299            }
300            return new DCPType( new HTTP( getURLs, postURLs ) );
301        }
302    
303        /**
304         * Returns the object representation of the <code>wfs:FeatureTypeList</code>- section.
305         *
306         * @return object representation of the <code>wfs:FeatureTypeList</code> section, may be empty (if missing)
307         * @throws XMLParsingException
308         */
309        public FeatureTypeList getFeatureTypeList()
310                                throws XMLParsingException {
311    
312            List<WFSFeatureType> wfsFeatureTypes = new ArrayList<WFSFeatureType>();
313    
314            FeatureTypeList featureTypeList = new FeatureTypeList(
315                                                                   new org.deegree.ogcwebservices.wfs.capabilities.Operation[0],
316                                                                   wfsFeatureTypes );
317    
318            Element element = (Element) XMLTools.getNode( getRootElement(), "wfs:FeatureTypeList", nsContext );
319            if ( element != null ) {
320                org.deegree.ogcwebservices.wfs.capabilities.Operation[] globalOperations = null;
321                Element operationsTypeElement = (Element) XMLTools.getNode( element, "wfs:Operations", nsContext );
322                if ( operationsTypeElement != null ) {
323                    globalOperations = getOperationsType( operationsTypeElement );
324                }
325                List<Element> featureTypeElementList = XMLTools.getElements( element, "wfs:FeatureType", nsContext );
326                if ( featureTypeElementList.size() < 1 ) {
327                    throw new XMLParsingException(
328                                                   "A wfs:FeatureTypeListType must contain at least one wfs:FeatureType-element." );
329                }
330                for ( int i = 0; i < featureTypeElementList.size(); i++ ) {
331                    WFSFeatureType wfsFT = getFeatureTypeType( featureTypeElementList.get( i ) );
332                    wfsFeatureTypes.add( wfsFT );
333                }
334    
335                featureTypeList = new FeatureTypeList( globalOperations, wfsFeatureTypes );
336            }
337    
338            return featureTypeList;
339        }
340    
341        /**
342         * Returns the object representation for an element of type <code>wfs:FeatureTypeType</code>.
343         *
344         * @param element
345         * @return object representation for the element of type <code>wfs:OperationsType</code>
346         * @throws XMLParsingException
347         */
348        public WFSFeatureType getFeatureTypeType( Element element )
349                                throws XMLParsingException {
350    
351            QualifiedName name = parseQualifiedName( XMLTools.getRequiredNode( element, "wfs:Name/text()", nsContext ) );
352            String title = XMLTools.getNodeAsString( element, "wfs:Title/text()", nsContext, null );
353            String abstract_ = XMLTools.getNodeAsString( element, "wfs:Abstract/text()", nsContext, null );
354    
355            String keywordsValue = XMLTools.getNodeAsString( element, "wfs:Keywords/text()", nsContext, null );
356            Keywords[] keywords = null;
357            if ( keywordsValue != null ) {
358                keywords = new Keywords[] { new Keywords( new String[] { keywordsValue } ) };
359            }
360    
361            URI defaultSrs = XMLTools.getRequiredNodeAsURI( element, "wfs:SRS", nsContext );
362    
363            org.deegree.ogcwebservices.wfs.capabilities.Operation[] operations = null;
364            Element operationsTypeElement = (Element) XMLTools.getNode( element, "wfs:Operations", nsContext );
365            if ( operationsTypeElement != null ) {
366                operations = getOperationsType( operationsTypeElement );
367            }
368    
369            List<Element> latLongBoundingBoxElements = XMLTools.getElements( element, "wfs:LatLongBoundingBox", nsContext );
370            Envelope[] latLongBoundingBoxes = new Envelope[latLongBoundingBoxElements.size()];
371            for ( int i = 0; i < latLongBoundingBoxes.length; i++ ) {
372                latLongBoundingBoxes[i] = getLatLongBoundingBoxType( latLongBoundingBoxElements.get( i ) );
373            }
374    
375            List<Element> metadataURLElementList = XMLTools.getElements( element, "wfs:MetadataURL", nsContext );
376            MetadataURL[] metadataUrls = new MetadataURL[metadataURLElementList.size()];
377            for ( int i = 0; i < metadataUrls.length; i++ ) {
378                metadataUrls[i] = getMetadataURL( metadataURLElementList.get( i ) );
379            }
380    
381            return new WFSFeatureType( name, title, abstract_, keywords, defaultSrs, null, operations, null,
382                                       latLongBoundingBoxes, metadataUrls );
383        }
384    
385        /**
386         * Creates an <code>Envelope</code> object from the given element of type <code>wfs:LatLongBoundingBoxType</code>.
387         *
388         * @param element
389         * @return corresponsing <code>Envelope</code> object
390         * @throws XMLParsingException
391         */
392        private Envelope getLatLongBoundingBoxType( Element element )
393                                throws XMLParsingException {
394            double minX = XMLTools.getRequiredNodeAsDouble( element, "@minx", nsContext );
395            double minY = XMLTools.getRequiredNodeAsDouble( element, "@miny", nsContext );
396            double maxX = XMLTools.getRequiredNodeAsDouble( element, "@maxx", nsContext );
397            double maxY = XMLTools.getRequiredNodeAsDouble( element, "@maxy", nsContext );
398            return GeometryFactory.createEnvelope( minX, minY, maxX, maxY, null );
399        }
400    
401        /**
402         * Returns the object representation for an element node of type <code>wfs:MetadataURLType</code>.
403         *
404         * TODO: Schema says base type is String, not URL!
405         *
406         * @param element
407         * @return object representation for the element of type <code>wfs:MetadataURLType</code>
408         * @throws XMLParsingException
409         */
410        public MetadataURL getMetadataURL( Element element )
411                                throws XMLParsingException {
412    
413            String type = XMLTools.getRequiredNodeAsString( element, "@type", nsContext, VALID_TYPES );
414            String format = XMLTools.getRequiredNodeAsString( element, "@format", nsContext, VALID_FORMATS );
415            String url = XMLTools.getRequiredNodeAsString( element, "text()", nsContext );
416            URL onlineResource;
417            try {
418                onlineResource = new URL( url );
419            } catch ( MalformedURLException e ) {
420                throw new XMLParsingException( "A wfs:MetadataURLType must contain a valid URL: " + e.getMessage() );
421            }
422    
423            return new MetadataURL( type, format, onlineResource );
424        }
425    
426        /**
427         * Returns the object representation for an element node of type <code>wfs:OperationsType</code>.
428         *
429         * @param element
430         * @return object representation for the element of type <code>wfs:OperationsType</code>
431         * @throws XMLParsingException
432         */
433        public org.deegree.ogcwebservices.wfs.capabilities.Operation[] getOperationsType( Element element )
434                                throws XMLParsingException {
435    
436            String[] operationCodes = XMLTools.getNodesAsStrings( element, "wfs:Operation/text()", nsContext );
437            org.deegree.ogcwebservices.wfs.capabilities.Operation[] operations = new org.deegree.ogcwebservices.wfs.capabilities.Operation[operationCodes.length];
438            for ( int i = 0; i < operations.length; i++ ) {
439                if ( org.deegree.ogcwebservices.wfs.capabilities.Operation.GET_GML_OBJECT.equals( operationCodes[i] ) ) {
440                    String msg = ( "Invalid WFS capabilities document. WFS 1.0.0 does not specify operation '"
441                                   + org.deegree.ogcwebservices.wfs.capabilities.Operation.GET_GML_OBJECT + ".'" );
442                    throw new XMLParsingException( msg );
443                }
444                try {
445                    operations[i] = new org.deegree.ogcwebservices.wfs.capabilities.Operation( operationCodes[i] );
446                } catch ( InvalidParameterException e ) {
447                    throw new XMLParsingException( e.getMessage() );
448                }
449            }
450    
451            return operations;
452        }
453    
454        /**
455         * Returns the object representation of the <code>Filter_Capabilities</code> section of the document.
456         *
457         * @return class representation of the <code>Filter_Capabilities</code> section
458         * @throws XMLParsingException
459         */
460        public FilterCapabilities getFilterCapabilities()
461                                throws XMLParsingException {
462    
463            FilterCapabilities filterCapabilities = null;
464            Element filterCapabilitiesElement = (Element) XMLTools.getNode( getRootElement(), "ogc:Filter_Capabilities",
465                                                                            nsContext );
466            if ( filterCapabilitiesElement != null ) {
467                filterCapabilities = new FilterCapabilities100Fragment( filterCapabilitiesElement, getSystemId() ).parseFilterCapabilities();
468            }
469            return filterCapabilities;
470        }
471    }