001    //$HeadURL: svn+ssh://developername@svn.wald.intevation.org/deegree/base/trunk/src/org/deegree/ogcwebservices/wms/capabilities/WMSCapabilitiesDocument.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.wms.capabilities;
037    
038    import java.io.IOException;
039    import java.net.MalformedURLException;
040    import java.net.URI;
041    import java.net.URISyntaxException;
042    import java.net.URL;
043    import java.util.ArrayList;
044    import java.util.Arrays;
045    import java.util.Date;
046    import java.util.List;
047    
048    import org.deegree.datatypes.Code;
049    import org.deegree.datatypes.QualifiedName;
050    import org.deegree.datatypes.values.TypedLiteral;
051    import org.deegree.framework.log.ILogger;
052    import org.deegree.framework.log.LoggerFactory;
053    import org.deegree.framework.util.StringTools;
054    import org.deegree.framework.xml.XMLParsingException;
055    import org.deegree.framework.xml.XMLTools;
056    import org.deegree.i18n.Messages;
057    import org.deegree.model.crs.CRSFactory;
058    import org.deegree.model.crs.CoordinateSystem;
059    import org.deegree.model.crs.UnknownCRSException;
060    import org.deegree.model.metadata.iso19115.Constraints;
061    import org.deegree.model.metadata.iso19115.Keywords;
062    import org.deegree.model.metadata.iso19115.Linkage;
063    import org.deegree.model.metadata.iso19115.OnlineResource;
064    import org.deegree.model.spatialschema.Envelope;
065    import org.deegree.model.spatialschema.GeometryFactory;
066    import org.deegree.model.spatialschema.Position;
067    import org.deegree.ogcwebservices.getcapabilities.InvalidCapabilitiesException;
068    import org.deegree.ogcwebservices.getcapabilities.OGCCapabilities;
069    import org.deegree.owscommon_new.DCP;
070    import org.deegree.owscommon_new.DomainType;
071    import org.deegree.owscommon_new.HTTP;
072    import org.deegree.owscommon_new.Operation;
073    import org.deegree.owscommon_new.OperationsMetadata;
074    import org.deegree.owscommon_new.Parameter;
075    import org.deegree.owscommon_new.ServiceIdentification;
076    import org.deegree.owscommon_new.ServiceProvider;
077    import org.w3c.dom.Element;
078    import org.w3c.dom.Node;
079    import org.xml.sax.SAXException;
080    
081    /**
082     * <code>WMSCapabilitiesDocument</code> is the parser class for WMS capabilities documents that
083     * uses the new OWS common classes to encapsulate the data.
084     *
085     * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a>
086     * @author last edited by: $Author: aschmitz $
087     *
088     * @version 2.0, $Revision: 8220 $, $Date: 2007-09-28 15:26:08 +0200 (Fr, 28 Sep 2007) $
089     *
090     * @since 2.0
091     */
092    
093    public class WMSCapabilitiesDocument_1_0_0 extends WMSCapabilitiesDocument {
094    
095        private static final long serialVersionUID = 4689978960047737035L;
096    
097        private static final String XML_TEMPLATE = "WMSCapabilitiesTemplate.xml";
098    
099        private static final ILogger LOG = LoggerFactory.getLogger( WMSCapabilitiesDocument_1_0_0.class );
100    
101        /**
102         * Creates a skeleton capabilities document that contains the mandatory elements only.
103         *
104         * @throws IOException
105         * @throws SAXException
106         */
107        @Override
108        public void createEmptyDocument()
109                                throws IOException, SAXException {
110    
111            URL url = WMSCapabilitiesDocument_1_0_0.class.getResource( XML_TEMPLATE );
112            if ( url == null ) {
113                throw new IOException( "The resource '" + XML_TEMPLATE + " could not be found." );
114            }
115            load( url );
116        }
117    
118        /**
119         *
120         * @param elem
121         * @return array of supported exception formats
122         * @throws XMLParsingException
123         */
124        @Override
125        protected List<String> parseExceptionFormats( Element elem )
126                                throws XMLParsingException {
127            List<Node> nodes = XMLTools.getRequiredNodes( elem, "Format", nsContext );
128            String[] formats = new String[nodes.size()];
129            for ( int i = 0; i < formats.length; i++ ) {
130                formats[i] = nodes.get( i ).getLocalName();
131            }
132            return Arrays.asList( formats );
133        }
134    
135        /**
136         * Creates a class representation of the document.
137         *
138         * @return class representation of the configuration document
139         * @throws InvalidCapabilitiesException
140         */
141        @Override
142        public OGCCapabilities parseCapabilities()
143                                throws InvalidCapabilitiesException {
144            ServiceIdentification serviceIdentification = null;
145            ServiceProvider serviceProvider = null;
146            OperationsMetadata metadata = null;
147            Layer layer = null;
148            List<String> exceptions;
149    
150            String updateSeq = parseUpdateSequence();
151            try {
152                serviceIdentification = parseServiceIdentification();
153                metadata = parseOperationsMetadata();
154    
155                Element exceptionElement = XMLTools.getRequiredElement( getRootElement(), "Capability/Exception", nsContext );
156                exceptions = parseExceptionFormats( exceptionElement );
157    
158                Element layerElem = XMLTools.getRequiredElement( getRootElement(), "./Capability/Layer", nsContext );
159                layer = parseLayers( layerElem, null, null );
160            } catch ( XMLParsingException e ) {
161                LOG.logError( e.getMessage(), e );
162                throw new InvalidCapabilitiesException( e.getMessage() );
163            } catch ( UnknownCRSException e ) {
164                LOG.logError( e.getMessage(), e );
165                throw new InvalidCapabilitiesException( e.getMessage() );
166            }
167    
168            return new WMSCapabilities_1_0_0( updateSeq, serviceIdentification, serviceProvider, metadata, layer,
169                                              exceptions );
170    
171        }
172    
173        /**
174         * returns the services indentification read from the WMS capabilities service section
175         *
176         * @throws XMLParsingException
177         */
178        @Override
179        protected ServiceIdentification parseServiceIdentification()
180                                throws XMLParsingException {
181            String name = XMLTools.getNodeAsString( getRootElement(), "./Service/Name", nsContext, null );
182            String title = XMLTools.getNodeAsString( getRootElement(), "./Service/Title", nsContext, name );
183            String serviceAbstract = XMLTools.getNodeAsString( getRootElement(), "./Service/Abstract", nsContext, null );
184    
185            String tmp = XMLTools.getNodeAsString( getRootElement(), "./Service/Keywords", nsContext, "" );
186            String[] kw = StringTools.toArray( tmp, " ", false );
187    
188            Keywords[] keywordArray = new Keywords[] { new Keywords( kw ) };
189            List<Keywords> keywords = Arrays.asList( keywordArray );
190    
191            String fees = XMLTools.getNodeAsString( getRootElement(), "./Service/Fees", nsContext, null );
192    
193            List<Constraints> accessConstraints = new ArrayList<Constraints>();
194    
195            String constraints = XMLTools.getNodeAsString( getRootElement(), "./Service/AccessConstraints", nsContext, null );
196    
197            if ( constraints != null ) {
198                List<String> limits = new ArrayList<String>();
199                limits.add( constraints );
200                accessConstraints.add( new Constraints( fees, null, null, null, limits, null, null, null ) );
201            }
202    
203            List<String> versions = new ArrayList<String>();
204            versions.add( "1.0.0" );
205    
206            return new ServiceIdentification( new Code( "OGC:WMS" ), versions, title, null,
207                                              new Date( System.currentTimeMillis() ), title, serviceAbstract, keywords,
208                                              accessConstraints );
209    
210        }
211    
212        /**
213         * returns the services capabilitiy read from the WMS capabilities file
214         *
215         * @return the operations metadata
216         * @throws XMLParsingException
217         */
218        @Override
219        protected OperationsMetadata parseOperationsMetadata()
220                                throws XMLParsingException {
221    
222            Node opNode = XMLTools.getRequiredNode( getRootElement(), "./Capability/Request/Capabilities", nsContext );
223    
224            Operation getCapa = parseOperation( opNode );
225    
226            opNode = XMLTools.getRequiredNode( getRootElement(), "./Capability/Request/Map", nsContext );
227    
228            Operation getMap = parseOperation( opNode );
229    
230            Operation getFI = null;
231            opNode = XMLTools.getNode( getRootElement(), "./Capability/Request/FeatureInfo", nsContext );
232            if ( opNode != null ) {
233                getFI = parseOperation( opNode );
234            }
235    
236            List<Operation> operations = new ArrayList<Operation>();
237            if ( getCapa != null )
238                operations.add( getCapa );
239            if ( getMap != null )
240                operations.add( getMap );
241            if ( getFI != null )
242                operations.add( getFI );
243    
244            return new OperationsMetadata( null, null, operations, null );
245    
246        }
247    
248        /**
249         * Creates an <tt>Operation</tt>-instance according to the contents of the DOM-subtree
250         * starting at the given <tt>Node</tt>.
251         * <p>
252         * Notice: operation to be parsed must be operations in sense of WMS 1.0.0 - 1.3.0 and not as
253         * defined in OWSCommons. But the method will return an OWSCommon Operation which encapsulates
254         * parsed WMS operation
255         * <p>
256         *
257         * @param node
258         *            the <tt>Element</tt> that describes an <tt>Operation</tt>
259         * @throws XMLParsingException
260         *             if a syntactic or semantic error in the DOM-subtree is encountered
261         * @return the constructed <tt>Operation</tt>-instance
262         */
263        @Override
264        protected Operation parseOperation( Node node )
265                                throws XMLParsingException {
266            // use node name as name of the Operation to be defined
267            String name = node.getNodeName();
268            if ( name.equals( "Capabilities" ) ) {
269                name = "GetCapabilities";
270            } else if ( name.equals( "Map" ) ) {
271                name = "GetMap";
272            } else if ( name.equals( "FeatureInfo" ) ) {
273                name = "GetFeatureInfo";
274            }
275    
276            List<Node> nodes = XMLTools.getRequiredNodes( node, "./Format", nsContext );
277    
278            List<TypedLiteral> values = new ArrayList<TypedLiteral>();
279    
280            URI stringURI = null;
281            try {
282                stringURI = new URI( null, "String", null );
283            } catch ( URISyntaxException e ) {
284                // cannot happen, why do I have to catch this?
285            }
286    
287            for ( Node str : nodes )
288                values.add( new TypedLiteral( str.getLocalName(), stringURI ) );
289    
290            DomainType owsDomainType = new DomainType( false, true, null, 0, new QualifiedName( "Format" ), values, null,
291                                                       null, false, null, false, null, null, null, null );
292            List<Parameter> parameters = new ArrayList<Parameter>();
293            parameters.add( owsDomainType );
294    
295            List<Element> nl = XMLTools.getRequiredElements( node, "./DCPType", nsContext );
296            List<DCP> dcps = new ArrayList<DCP>();
297    
298            for ( Element element : nl ) {
299                dcps.add( parseDCP( element ) );
300            }
301    
302            return new Operation( new QualifiedName( name ), dcps, parameters, null, null, null );
303        }
304    
305        /**
306         * Parses a DCPType element. Does not override the method defined in the base class any more.
307         *
308         * @param element
309         * @return created <code>DCPType</code>
310         * @throws XMLParsingException
311         * @see org.deegree.ogcwebservices.getcapabilities.OGCStandardCapabilities
312         */
313        @Override
314        protected DCP parseDCP( Element element )
315                                throws XMLParsingException {
316    
317            List<HTTP.Type> types = new ArrayList<HTTP.Type>();
318            List<OnlineResource> links = new ArrayList<OnlineResource>();
319    
320            Element elem = XMLTools.getRequiredElement( element, "HTTP", nsContext );
321            String s = null;
322            try {
323                List<Node> nl = XMLTools.getNodes( elem, "Get", nsContext );
324                for ( int i = 0; i < nl.size(); i++ ) {
325                    s = XMLTools.getNodeAsString( nl.get( i ), "./@onlineResource", nsContext, null );
326                    types.add( HTTP.Type.Get );
327                    links.add( new OnlineResource( new Linkage( new URL( s ) ) ) );
328                }
329            } catch ( Exception e ) {
330                LOG.logError( e.getMessage(), e );
331                throw new XMLParsingException( Messages.getMessage( "WMS_DCPGET", s ) );
332            }
333            try {
334                List<Node> nl = XMLTools.getNodes( elem, "Post", nsContext );
335    
336                for ( int i = 0; i < nl.size(); i++ ) {
337                    s = XMLTools.getNodeAsString( nl.get( i ), "./@onlineResource", nsContext, null );
338                    types.add( HTTP.Type.Post );
339                    links.add( new OnlineResource( new Linkage( new URL( s ) ) ) );
340                }
341    
342            } catch ( MalformedURLException e ) {
343                throw new XMLParsingException( Messages.getMessage( "WMS_DCPPOST", s ) );
344            }
345            return new HTTP( links, null, types );
346    
347        }
348    
349        /**
350         * returns the layers offered by the WMS
351         *
352         * @return the layer
353         * @throws XMLParsingException
354         * @throws UnknownCRSException
355         */
356        @Override
357        protected Layer parseLayers( Element layerElem, Layer parent, ScaleHint scaleHint )
358                                throws XMLParsingException, UnknownCRSException {
359    
360            boolean queryable = XMLTools.getNodeAsBoolean( layerElem, "./@queryable", nsContext, false );
361    
362            int cascaded = 0;
363            boolean opaque = false;
364            boolean noSubsets = false;
365            int fixedWidth = 0;
366            int fixedHeight = 0;
367            String name = XMLTools.getNodeAsString( layerElem, "./Name", nsContext, null );
368            String title = XMLTools.getRequiredNodeAsString( layerElem, "./Title", nsContext );
369            String layerAbstract = XMLTools.getNodeAsString( layerElem, "./Abstract", nsContext, null );
370            String[] keywords = XMLTools.getNodesAsStrings( layerElem, "./Keywords", nsContext );
371            String[] srs = XMLTools.getNodesAsStrings( layerElem, "./SRS", nsContext );
372    
373            List<Element> nl = XMLTools.getElements( layerElem, "./BoundingBox", nsContext );
374            // TODO
375            // substitue with Envelope
376            LayerBoundingBox[] bboxes = null;
377            if ( nl.size() == 0 && parent != null ) {
378                // inherit BoundingBoxes from parent layer
379                bboxes = parent.getBoundingBoxes();
380            } else {
381                bboxes = parseLayerBoundingBoxes( nl );
382            }
383    
384            Element llBox = XMLTools.getElement( layerElem, "./LatLonBoundingBox", nsContext );
385            Envelope llBoundingBox = null;
386    
387            if ( llBox == null && parent != null ) {
388                // inherit LatLonBoundingBox parent layer
389                llBoundingBox = parent.getLatLonBoundingBox();
390            } else if ( llBox != null ) {
391                llBoundingBox = parseLatLonBoundingBox( llBox );
392            } else {
393                llBoundingBox = GeometryFactory.createEnvelope( -180, -90, 180, 90, CRSFactory.create( "EPSG:4326" ) );
394            }
395    
396            DataURL[] dataURLs = parseDataURL( layerElem );
397    
398            Style[] styles = parseStyles( layerElem );
399    
400            scaleHint = parseScaleHint( layerElem, scaleHint );
401    
402            Layer layer = new Layer( queryable, cascaded, opaque, noSubsets, fixedWidth, fixedHeight, name, title,
403                                     layerAbstract, llBoundingBox, null, scaleHint, keywords, srs, bboxes, null, null,
404                                     null, null, null, dataURLs, null, styles, null, null, parent );
405    
406            // get Child layers
407            nl = XMLTools.getElements( layerElem, "./Layer", nsContext );
408            Layer[] layers = new Layer[nl.size()];
409            for ( int i = 0; i < layers.length; i++ ) {
410                layers[i] = parseLayers( nl.get( i ), layer, scaleHint );
411            }
412    
413            // set child layers
414            layer.setLayer( layers );
415    
416            return layer;
417        }
418    
419        /**
420         *
421         * @param layerElem
422         * @return the URLs
423         * @throws XMLParsingException
424         */
425        @Override
426        protected DataURL[] parseDataURL( Element layerElem )
427                                throws XMLParsingException {
428    
429            List<Node> nl = XMLTools.getNodes( layerElem, "./DataURL", nsContext );
430            DataURL[] dataURL = new DataURL[nl.size()];
431            for ( int i = 0; i < dataURL.length; i++ ) {
432                URL url;
433                try {
434                    url = new URL( XMLTools.getStringValue( nl.get( i ) ) );
435                } catch ( MalformedURLException e ) {
436                    throw new XMLParsingException( XMLTools.getStringValue( nl.get( i ) ) + " is not an URL" );
437                }
438                dataURL[i] = new DataURL( null, url );
439    
440            }
441    
442            return dataURL;
443        }
444    
445        /**
446         *
447         * @param layerElem
448         * @return the styles
449         * @throws XMLParsingException
450         */
451        @Override
452        protected Style[] parseStyles( Element layerElem )
453                                throws XMLParsingException {
454    
455            List<Node> nl = XMLTools.getNodes( layerElem, "./Style", nsContext );
456            Style[] styles = new Style[nl.size()];
457            for ( int i = 0; i < styles.length; i++ ) {
458                String name = XMLTools.getRequiredNodeAsString( nl.get( i ), "./Name", nsContext );
459    
460                if ( name == null ) {
461                    throw new XMLParsingException( Messages.getMessage( "WMS_STYLENAME" ) );
462                }
463                String title = XMLTools.getNodeAsString( nl.get( i ), "./Title", nsContext, null );
464                if ( title == null ) {
465                    throw new XMLParsingException( Messages.getMessage( "WMS_STYLETITLE" ) );
466                }
467                String styleAbstract = XMLTools.getNodeAsString( nl.get( i ), "./Abstract", nsContext, null );
468                StyleURL styleURL = parseStyleURL( nl.get( i ) );
469    
470                styles[i] = new Style( name, title, styleAbstract, null, null, styleURL, null );
471            }
472    
473            return styles;
474        }
475    
476        /**
477         *
478         * @param node
479         * @return the URL
480         * @throws XMLParsingException
481         */
482        @Override
483        protected StyleURL parseStyleURL( Node node )
484                                throws XMLParsingException {
485    
486            StyleURL styleURL = null;
487            Node styleNode = XMLTools.getNode( node, "./StyleURL", nsContext );
488    
489            if ( styleNode != null ) {
490                URL url;
491                try {
492                    url = new URL( XMLTools.getStringValue( styleNode ) );
493                } catch ( MalformedURLException e ) {
494                    throw new XMLParsingException( XMLTools.getStringValue( styleNode ) + " is not an URL" );
495                }
496                styleURL = new StyleURL( null, url );
497            }
498    
499            return styleURL;
500        }
501    
502        /**
503         *
504         * @param layerElem
505         * @param scaleHint
506         *            the default scale hint
507         * @return the scale hint
508         * @throws XMLParsingException
509         */
510        @Override
511        protected ScaleHint parseScaleHint( Element layerElem, ScaleHint scaleHint )
512                                throws XMLParsingException {
513    
514            Node scNode = XMLTools.getNode( layerElem, "./ScaleHint", nsContext );
515            if ( scNode != null ) {
516                double mn = XMLTools.getNodeAsDouble( scNode, "./@min", nsContext, 0 );
517                double mx = XMLTools.getNodeAsDouble( scNode, "./@max", nsContext, Double.MAX_VALUE );
518                scaleHint = new ScaleHint( mn, mx );
519            }
520    
521            if ( scaleHint == null ) {
522                // set default value to avoid NullPointerException
523                // when accessing a layers scalehint
524                scaleHint = new ScaleHint( 0, Double.MAX_VALUE );
525            }
526    
527            return scaleHint;
528        }
529    
530        /**
531         *
532         * @param nl
533         * @return the bboxes
534         * @throws XMLParsingException
535         */
536        @Override
537        protected LayerBoundingBox[] parseLayerBoundingBoxes( List<Element> nl )
538                                throws XMLParsingException {
539    
540            LayerBoundingBox[] llBoxes = new LayerBoundingBox[nl.size()];
541            for ( int i = 0; i < llBoxes.length; i++ ) {
542                double minx = XMLTools.getRequiredNodeAsDouble( nl.get( i ), "./@minx", nsContext );
543                double maxx = XMLTools.getRequiredNodeAsDouble( nl.get( i ), "./@maxx", nsContext );
544                double miny = XMLTools.getRequiredNodeAsDouble( nl.get( i ), "./@miny", nsContext );
545                double maxy = XMLTools.getRequiredNodeAsDouble( nl.get( i ), "./@maxy", nsContext );
546                String srs = XMLTools.getRequiredNodeAsString( nl.get( i ), "./@SRS", nsContext );
547                Position min = GeometryFactory.createPosition( minx, miny );
548                Position max = GeometryFactory.createPosition( maxx, maxy );
549                llBoxes[i] = new LayerBoundingBox( min, max, srs, -1, -1 );
550            }
551    
552            return llBoxes;
553        }
554    
555        /**
556         *
557         * @param llBox
558         * @return the envelope
559         * @throws XMLParsingException
560         * @throws UnknownCRSException
561         */
562        @Override
563        protected Envelope parseLatLonBoundingBox( Element llBox )
564                                throws XMLParsingException, UnknownCRSException {
565    
566            double minx = XMLTools.getRequiredNodeAsDouble( llBox, "./@minx", nsContext );
567            double maxx = XMLTools.getRequiredNodeAsDouble( llBox, "./@maxx", nsContext );
568            double miny = XMLTools.getRequiredNodeAsDouble( llBox, "./@miny", nsContext );
569            double maxy = XMLTools.getRequiredNodeAsDouble( llBox, "./@maxy", nsContext );
570            CoordinateSystem crs = CRSFactory.create( "EPSG:4326" );
571    
572            Envelope env = GeometryFactory.createEnvelope( minx, miny, maxx, maxy, crs );
573    
574            return env;
575        }
576    
577    }