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