001    //$HeadURL: http://svn.wald.intevation.org/svn/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.Address;
061    import org.deegree.model.metadata.iso19115.CitedResponsibleParty;
062    import org.deegree.model.metadata.iso19115.Constraints;
063    import org.deegree.model.metadata.iso19115.ContactInfo;
064    import org.deegree.model.metadata.iso19115.Keywords;
065    import org.deegree.model.metadata.iso19115.Linkage;
066    import org.deegree.model.metadata.iso19115.OnlineResource;
067    import org.deegree.model.metadata.iso19115.Phone;
068    import org.deegree.model.spatialschema.Envelope;
069    import org.deegree.model.spatialschema.GeometryFactory;
070    import org.deegree.model.spatialschema.Position;
071    import org.deegree.ogcwebservices.getcapabilities.InvalidCapabilitiesException;
072    import org.deegree.ogcwebservices.getcapabilities.MetadataURL;
073    import org.deegree.ogcwebservices.getcapabilities.OGCCapabilities;
074    import org.deegree.ogcwebservices.getcapabilities.OGCCapabilitiesDocument;
075    import org.deegree.owscommon_new.DCP;
076    import org.deegree.owscommon_new.DomainType;
077    import org.deegree.owscommon_new.HTTP;
078    import org.deegree.owscommon_new.Operation;
079    import org.deegree.owscommon_new.OperationsMetadata;
080    import org.deegree.owscommon_new.Parameter;
081    import org.deegree.owscommon_new.ServiceIdentification;
082    import org.deegree.owscommon_new.ServiceProvider;
083    import org.w3c.dom.Element;
084    import org.w3c.dom.Node;
085    import org.xml.sax.SAXException;
086    
087    /**
088     * <code>WMSCapabilitiesDocument</code> is the parser class for WMS capabilities documents that uses the new OWS common
089     * classes to encapsulate the data.
090     * 
091     * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a>
092     * @author last edited by: $Author: apoth $
093     * 
094     * @version 2.0, $Revision: 29867 $, $Date: 2011-03-04 11:40:16 +0100 (Fri, 04 Mar 2011) $
095     * 
096     * @since 2.0
097     */
098    
099    public class WMSCapabilitiesDocument extends OGCCapabilitiesDocument {
100    
101        private static final long serialVersionUID = -4165017479515126226L;
102    
103        private static final String XML_TEMPLATE = "WMSCapabilitiesTemplate.xml";
104    
105        private static final ILogger LOG = LoggerFactory.getLogger( WMSCapabilitiesDocument.class );
106    
107        /**
108         * Creates a skeleton capabilities document that contains the mandatory elements only.
109         * 
110         * @throws IOException
111         * @throws SAXException
112         */
113        public void createEmptyDocument()
114                                throws IOException, SAXException {
115    
116            URL url = WMSCapabilitiesDocument.class.getResource( XML_TEMPLATE );
117            if ( url == null ) {
118                throw new IOException( "The resource '" + XML_TEMPLATE + " could not be found." );
119            }
120            load( url );
121        }
122    
123        protected List<String> parseExceptionFormats( Element elem )
124                                throws XMLParsingException {
125            String[] formats = XMLTools.getRequiredNodesAsStrings( elem, "Format", nsContext );
126            return Arrays.asList( formats );
127        }
128    
129        /**
130         * Creates a class representation of the document.
131         * 
132         * @return class representation of the configuration document
133         * @throws InvalidCapabilitiesException
134         */
135        @Override
136        public OGCCapabilities parseCapabilities()
137                                throws InvalidCapabilitiesException {
138            ServiceIdentification serviceIdentification = null;
139            ServiceProvider serviceProvider = null;
140            OperationsMetadata metadata = null;
141            Layer layer = null;
142            UserDefinedSymbolization uds = null;
143            String version = parseVersion();
144            List<String> exceptions;
145    
146            String updateSeq = parseUpdateSequence();
147            try {
148                serviceIdentification = parseServiceIdentification();
149                serviceProvider = parseServiceProvider();
150                metadata = parseOperationsMetadata();
151    
152                Element exceptionElement = XMLTools.getRequiredElement( getRootElement(), "Capability/Exception", nsContext );
153                exceptions = parseExceptionFormats( exceptionElement );
154    
155                uds = parseUserDefinedSymbolization();
156                Element layerElem = XMLTools.getRequiredElement( getRootElement(), "./Capability/Layer", nsContext );
157                layer = parseLayers( layerElem, null, null );
158            } catch ( XMLParsingException e ) {
159                e.printStackTrace();
160                throw new InvalidCapabilitiesException( e.getMessage() + StringTools.stackTraceToString( e ) );
161            } catch ( UnknownCRSException e ) {
162                e.printStackTrace();
163                throw new InvalidCapabilitiesException( e.getMessage() + StringTools.stackTraceToString( e ) );
164            }
165    
166            WMSCapabilities wmsCapabilities = new WMSCapabilities( version, updateSeq, serviceIdentification,
167                                                                   serviceProvider, uds, metadata, layer, exceptions );
168    
169            return wmsCapabilities;
170        }
171    
172        /**
173         * @return the services indentification read from the WMS capabilities service section
174         * 
175         * @throws XMLParsingException
176         */
177        protected ServiceIdentification parseServiceIdentification()
178                                throws XMLParsingException {
179            String name = XMLTools.getNodeAsString( getRootElement(), "./Service/Name", nsContext, null );
180            String title = XMLTools.getNodeAsString( getRootElement(), "./Service/Title", nsContext, name );
181            String serviceAbstract = XMLTools.getNodeAsString( getRootElement(), "./Service/Abstract", nsContext, null );
182    
183            String[] kw = XMLTools.getNodesAsStrings( getRootElement(), "./Service/KeywordList/Keyword", nsContext );
184    
185            Keywords[] keywordArray = new Keywords[] { new Keywords( kw ) };
186            List<Keywords> keywords = Arrays.asList( keywordArray );
187    
188            String fees = XMLTools.getNodeAsString( getRootElement(), "./Service/Fees", nsContext, null );
189    
190            List<Constraints> accessConstraints = new ArrayList<Constraints>();
191    
192            String[] constraints = XMLTools.getNodesAsStrings( getRootElement(), "./Service/AccessConstraints", nsContext );
193    
194            for ( String constraint : constraints ) {
195                List<String> limits = new ArrayList<String>();
196                limits.add( constraint );
197                accessConstraints.add( new Constraints( fees, null, null, null, limits, null, null, null ) );
198            }
199    
200            List<String> versions = new ArrayList<String>();
201            versions.add( "1.0.0" );
202            versions.add( "1.1.0" );
203            versions.add( "1.1.1" );
204            versions.add( "1.2.0" );
205            versions.add( "1.3.0" );
206    
207            ServiceIdentification serviceIdentification = new ServiceIdentification(
208                                                                                     new Code( "OGC:WMS" ),
209                                                                                     versions,
210                                                                                     title,
211                                                                                     null,
212                                                                                     new Date( System.currentTimeMillis() ),
213                                                                                     title, serviceAbstract, keywords,
214                                                                                     accessConstraints );
215    
216            return serviceIdentification;
217        }
218    
219        /**
220         * returns WMS contact informaion encapsulated within a <code>ServiceProvider</code> object
221         * 
222         * @return the service provider data
223         * @throws XMLParsingException
224         */
225        protected ServiceProvider parseServiceProvider()
226                                throws XMLParsingException {
227            Node ci = XMLTools.getNode( getRootElement(), "./Service/ContactInformation", nsContext );
228            // according to WMS 1.1.1 specification this element is mandatory
229            // but there are several services online which does not contain
230            // this element in its capabilities :-(
231            String s = XMLTools.getNodeAsString( getRootElement(), "./Service/OnlineResource/@xlink:href", nsContext, null );
232    
233            OnlineResource providerSite = null;
234    
235            if ( s != null ) {
236                try {
237                    providerSite = new OnlineResource( new Linkage( new URL( s ) ) );
238                } catch ( MalformedURLException e ) {
239                    LOG.logError(
240                                  "could not parse service online resource; use default URL: http://www.opengeospatial.org instead",
241                                  e );
242                    try {
243                        providerSite = new OnlineResource( new Linkage( new URL( "http://www.opengeospatial.org" ) ) );
244                    } catch ( MalformedURLException neverHappens ) {
245                        // useless exception
246                    }
247                }
248            } else {
249                // use default if no online resource is contained in the
250                // capabilities (see comment above)
251                try {
252                    providerSite = new OnlineResource( new Linkage( new URL( "http://www.opengeospatial.org/" ) ) );
253                } catch ( MalformedURLException neverHappens ) {
254                    // useless exception
255                }
256            }
257    
258            String person = null;
259            String orga = null;
260            String position = null;
261            if ( ci != null ) {
262                person = XMLTools.getNodeAsString( ci, "./ContactPersonPrimary/ContactPerson", nsContext, null );
263                orga = XMLTools.getNodeAsString( ci, "./ContactPersonPrimary/ContactOrganization", nsContext, null );
264                position = XMLTools.getNodeAsString( ci, "./ContactPosition", nsContext, null );
265            }
266            ContactInfo contact = parseContactInfo();
267            // ServiceProvider sp = new ServiceProvider( orga, sLink, person, position, contact, null );
268            CitedResponsibleParty party = new CitedResponsibleParty(
269                                                                     contact == null ? new ContactInfo[] {}
270                                                                                    : new ContactInfo[] { contact },
271                                                                     person == null ? new String[] {}
272                                                                                   : new String[] { person },
273                                                                     orga == null ? new String[] {} : new String[] { orga },
274                                                                     position == null ? new String[] {}
275                                                                                     : new String[] { position }, null );
276            ServiceProvider sp = new ServiceProvider( person, providerSite, party );
277    
278            return sp;
279        }
280    
281        /**
282         * 
283         * @return the contact information
284         * @throws XMLParsingException
285         */
286        protected ContactInfo parseContactInfo()
287                                throws XMLParsingException {
288            Node ci = XMLTools.getNode( getRootElement(), "./Service/ContactInformation", nsContext );
289            ContactInfo cont = null;
290            if ( ci != null ) {
291                String[] addr = XMLTools.getNodesAsStrings( ci, "./ContactAddress/Address", nsContext );
292                // String addrType =
293                // XMLTools.getNodeAsString( ci, "./ContactAddress/AddressType", nsContext, null );
294                String city = XMLTools.getNodeAsString( ci, "./ContactAddress/City", nsContext, null );
295                String state = XMLTools.getNodeAsString( ci, "./ContactAddress/StateOrProvince", nsContext, null );
296                String pc = XMLTools.getNodeAsString( ci, "./ContactAddress/PostCode", nsContext, null );
297                String country = XMLTools.getNodeAsString( ci, "./ContactAddress/Country", nsContext, null );
298                String[] mail = XMLTools.getNodesAsStrings( ci, "./ContactElectronicMailAddress", nsContext );
299                Address address = new Address( state, city, country, addr, mail, pc );
300    
301                String[] phone = XMLTools.getNodesAsStrings( ci, "./ContactVoiceTelephone", nsContext );
302                String[] fax = XMLTools.getNodesAsStrings( ci, "./ContactFacsimileTelephone", nsContext );
303    
304                Phone ph = new Phone( fax, phone );
305    
306                cont = new ContactInfo( address, null, null, null, ph );
307            }
308    
309            return cont;
310        }
311    
312        /**
313         * returns the services capabilitiy read from the WMS capabilities file
314         * 
315         * @return the operations metadata
316         * @throws XMLParsingException
317         */
318        protected OperationsMetadata parseOperationsMetadata()
319                                throws XMLParsingException {
320    
321            Node opNode = XMLTools.getNode( getRootElement(), "./Capability/Request/GetCapabilities", nsContext );
322    
323            if ( opNode == null ) {
324                // may it is a WMS 1.0.0 capabilities document
325                opNode = XMLTools.getRequiredNode( getRootElement(), "./Capability/Request/Capabilities", nsContext );
326            }
327            Operation getCapa = parseOperation( opNode );
328    
329            opNode = XMLTools.getNode( getRootElement(), "./Capability/Request/GetMap", nsContext );
330            if ( opNode == null ) {
331                // may it is a WMS 1.0.0 capabilities document
332                opNode = XMLTools.getRequiredNode( getRootElement(), "./Capability/Request/Map", nsContext );
333            }
334            Operation getMap = parseOperation( opNode );
335    
336            opNode = XMLTools.getNode( getRootElement(), "./Capability/Request/GetFeatureInfo", nsContext );
337            Operation getFI = null;
338            if ( opNode != null ) {
339                getFI = parseOperation( opNode );
340            } else {
341                // maybe its WMS 1.0.0
342                opNode = XMLTools.getNode( getRootElement(), "./Capability/Request/FeatureInfo", nsContext );
343                if ( opNode != null ) {
344                    getFI = parseOperation( opNode );
345                }
346            }
347    
348            opNode = XMLTools.getNode( getRootElement(), "./Capability/Request/GetLegendGraphic", nsContext );
349            Operation getLG = null;
350            if ( opNode != null ) {
351                getLG = parseOperation( opNode );
352            }
353    
354            opNode = XMLTools.getNode( getRootElement(), "./Capability/Request/DescribeLayer", nsContext );
355            Operation descL = null;
356            if ( opNode != null ) {
357                descL = parseOperation( opNode );
358            }
359    
360            opNode = XMLTools.getNode( getRootElement(), "./Capability/Request/GetStyles", nsContext );
361            Operation getStyles = null;
362            if ( opNode != null ) {
363                getStyles = parseOperation( opNode );
364            }
365    
366            opNode = XMLTools.getNode( getRootElement(), "./Capability/Request/PutStyles", nsContext );
367            Operation putStyles = null;
368            if ( opNode != null ) {
369                putStyles = parseOperation( opNode );
370            }
371    
372            List<Operation> operations = new ArrayList<Operation>();
373            if ( getCapa != null )
374                operations.add( getCapa );
375            if ( getMap != null )
376                operations.add( getMap );
377            if ( getFI != null )
378                operations.add( getFI );
379            if ( getLG != null )
380                operations.add( getLG );
381            if ( descL != null )
382                operations.add( descL );
383            if ( getStyles != null )
384                operations.add( getStyles );
385            if ( putStyles != null )
386                operations.add( putStyles );
387    
388            OperationsMetadata metadata = new OperationsMetadata( null, null, operations, null );
389    
390            return metadata;
391        }
392    
393        /**
394         * Creates an <tt>Operation</tt>-instance according to the contents of the DOM-subtree starting at the given
395         * <tt>Node</tt>.
396         * <p>
397         * Notice: operation to be parsed must be operations in sense of WMS 1.0.0 - 1.3.0 and not as defined in OWSCommons.
398         * But the method will return an OWSCommon Operation which encapsulates parsed WMS operation
399         * <p>
400         * 
401         * @param node
402         *            the <tt>Element</tt> that describes an <tt>Operation</tt>
403         * @throws XMLParsingException
404         *             if a syntactic or semantic error in the DOM-subtree is encountered
405         * @return the constructed <tt>Operation</tt>-instance
406         */
407        protected Operation parseOperation( Node node )
408                                throws XMLParsingException {
409            // use node name as name of the Operation to be defined
410            String name = node.getNodeName();
411            if ( name.equals( "Capabilities" ) ) {
412                name = "GetCapabilities";
413            } else if ( name.equals( "Map" ) ) {
414                name = "GetMap";
415            } else if ( name.equals( "FeatureInfo" ) ) {
416                name = "GetFeatureInfo";
417            }
418    
419            String[] tmp = XMLTools.getRequiredNodesAsStrings( node, "./Format", nsContext );
420            List<TypedLiteral> values = new ArrayList<TypedLiteral>();
421    
422            URI stringURI = null;
423            try {
424                stringURI = new URI( null, "String", null );
425            } catch ( URISyntaxException e ) {
426                // cannot happen, why do I have to catch this?
427            }
428    
429            for ( String str : tmp )
430                values.add( new TypedLiteral( str, stringURI ) );
431    
432            DomainType owsDomainType = new DomainType( false, true, null, 0, new QualifiedName( "Format" ), values, null,
433                                                       null, false, null, false, null, null, null, null );
434            List<Parameter> parameters = new ArrayList<Parameter>();
435            parameters.add( owsDomainType );
436    
437            List<Element> nl = XMLTools.getRequiredElements( node, "./DCPType", nsContext );
438            List<DCP> dcps = new ArrayList<DCP>();
439    
440            for ( Element element : nl ) {
441                dcps.add( parseDCP( element ) );
442            }
443    
444            return new Operation( new QualifiedName( name ), dcps, parameters, null, null, null );
445        }
446    
447        /**
448         * Parses a DCPType element. Does not override the method defined in the base class any more.
449         * 
450         * @param element
451         * @return created <code>DCPType</code>
452         * @throws XMLParsingException
453         * @see org.deegree.ogcwebservices.getcapabilities.OGCStandardCapabilities
454         */
455        protected DCP parseDCP( Element element )
456                                throws XMLParsingException {
457    
458            List<HTTP.Type> types = new ArrayList<HTTP.Type>();
459            List<OnlineResource> links = new ArrayList<OnlineResource>();
460    
461            Element elem = XMLTools.getRequiredElement( element, "HTTP", nsContext );
462            String s = null;
463            try {
464                List<Node> nl = XMLTools.getNodes( elem, "Get", nsContext );
465    
466                for ( int i = 0; i < nl.size(); i++ ) {
467                    s = XMLTools.getNodeAsString( nl.get( i ), "./@xlink:href", nsContext, null );
468                    if ( s == null ) {
469                        s = XMLTools.getRequiredNodeAsString( nl.get( i ), "./OnlineResource/@xlink:href", nsContext );
470                    }
471                    types.add( HTTP.Type.Get );
472                    links.add( new OnlineResource( new Linkage( new URL( s ) ) ) );
473                }
474            } catch ( Exception e ) {
475                LOG.logError( e.getMessage(), e );
476                throw new XMLParsingException( Messages.getMessage( "WMS_DCPGET", s ) );
477            }
478            try {
479                List<Node> nl = XMLTools.getNodes( elem, "Post", nsContext );
480    
481                for ( int i = 0; i < nl.size(); i++ ) {
482                    s = XMLTools.getNodeAsString( nl.get( i ), "./@xlink:href", nsContext, null );
483                    if ( s == null ) {
484                        s = XMLTools.getRequiredNodeAsString( nl.get( i ), "./OnlineResource/@xlink:href", nsContext );
485                    }
486                    types.add( HTTP.Type.Post );
487                    links.add( new OnlineResource( new Linkage( new URL( s ) ) ) );
488                }
489    
490            } catch ( MalformedURLException e ) {
491                throw new XMLParsingException( Messages.getMessage( "WMS_DCPPOST", s ) );
492            }
493            HTTP http = new HTTP( links, null, types );
494    
495            return http;
496        }
497    
498        /**
499         * 
500         * @return the parsed data
501         * @throws XMLParsingException
502         */
503        protected UserDefinedSymbolization parseUserDefinedSymbolization()
504                                throws XMLParsingException {
505    
506            boolean supportSLD = XMLTools.getNodeAsBoolean( getRootElement(),
507                                                            "./Capability/UserDefinedSymbolization/@SupportSLD", nsContext,
508                                                            false );
509    
510            boolean userLayer = XMLTools.getNodeAsBoolean( getRootElement(),
511                                                           "./Capability/UserDefinedSymbolization/@UserLayer", nsContext,
512                                                           false );
513    
514            boolean userStyle = XMLTools.getNodeAsBoolean( getRootElement(),
515                                                           "./Capability/UserDefinedSymbolization/@UserStyle", nsContext,
516                                                           false );
517    
518            boolean remoteWFS = XMLTools.getNodeAsBoolean( getRootElement(),
519                                                           "./Capability/UserDefinedSymbolization/@RemoteWFS", nsContext,
520                                                           false );
521    
522            UserDefinedSymbolization uds = new UserDefinedSymbolization( supportSLD, userLayer, remoteWFS, userStyle );
523    
524            return uds;
525        }
526    
527        /**
528         * @param layerElem
529         * @param parent
530         * @param scaleHint
531         * 
532         * @return the layer created from the given element
533         * @throws XMLParsingException
534         * @throws UnknownCRSException
535         */
536        protected Layer parseLayers( Element layerElem, Layer parent, ScaleHint scaleHint )
537                                throws XMLParsingException, UnknownCRSException {
538    
539            boolean queryable = XMLTools.getNodeAsBoolean( layerElem, "./@queryable", nsContext, false );
540    
541            int cascaded = XMLTools.getNodeAsInt( layerElem, "./@cascaded", nsContext, 0 );
542            boolean opaque = XMLTools.getNodeAsBoolean( layerElem, "./@opaque", nsContext, false );
543            boolean noSubsets = XMLTools.getNodeAsBoolean( layerElem, "./@noSubsets", nsContext, false );
544            int fixedWidth = XMLTools.getNodeAsInt( layerElem, "./@fixedWidth", nsContext, 0 );
545            int fixedHeight = XMLTools.getNodeAsInt( layerElem, "./@fixedHeight", nsContext, 0 );
546            String name = XMLTools.getNodeAsString( layerElem, "./Name", nsContext, null );
547            String title = XMLTools.getRequiredNodeAsString( layerElem, "./Title", nsContext );
548            String layerAbstract = XMLTools.getNodeAsString( layerElem, "./Abstract", nsContext, null );
549            String[] keywords = XMLTools.getNodesAsStrings( layerElem, "./KeywordList/Keyword", nsContext );
550            String[] srs = XMLTools.getNodesAsStrings( layerElem, "./SRS", nsContext );
551    
552            List<Element> nl = XMLTools.getElements( layerElem, "./BoundingBox", nsContext );
553            // TODO
554            // substitue with Envelope
555            LayerBoundingBox[] bboxes = null;
556            if ( nl.size() == 0 && parent != null ) {
557                // inherit BoundingBoxes from parent layer
558                bboxes = parent.getBoundingBoxes();
559            } else {
560                bboxes = parseLayerBoundingBoxes( nl );
561            }
562    
563            Element llBox = XMLTools.getElement( layerElem, "./LatLonBoundingBox", nsContext );
564            Envelope llBoundingBox = null;
565    
566            if ( llBox == null && parent != null ) {
567                // inherit LatLonBoundingBox parent layer
568                llBoundingBox = parent.getLatLonBoundingBox();
569            } else if ( llBox != null ) {
570                llBoundingBox = parseLatLonBoundingBox( llBox );
571            } else {
572                llBoundingBox = GeometryFactory.createEnvelope( -180, -90, 180, 90, CRSFactory.create( "EPSG:4326" ) );
573            }
574    
575            Dimension[] dimensions = parseDimensions( layerElem );
576            Extent[] extents = parseExtents( layerElem );
577    
578            Attribution attribution = parseAttribution( layerElem );
579    
580            AuthorityURL[] authorityURLs = parseAuthorityURLs( layerElem );
581    
582            MetadataURL[] metadataURLs = parseMetadataURLs( layerElem );
583    
584            DataURL[] dataURLs = parseDataURL( layerElem );
585    
586            Identifier[] identifiers = parseIdentifiers( layerElem );
587    
588            FeatureListURL[] featureListURLs = parseFeatureListURL( layerElem );
589    
590            Style[] styles = parseStyles( layerElem );
591    
592            scaleHint = parseScaleHint( layerElem, scaleHint );
593    
594            Layer layer = new Layer( queryable, cascaded, opaque, noSubsets, fixedWidth, fixedHeight, name, title,
595                                     layerAbstract, llBoundingBox, attribution, scaleHint, keywords, srs, bboxes,
596                                     dimensions, extents, authorityURLs, identifiers, metadataURLs, dataURLs,
597                                     featureListURLs, styles, null, null, parent );
598    
599            // get Child layers
600            nl = XMLTools.getElements( layerElem, "./Layer", nsContext );
601            Layer[] layers = new Layer[nl.size()];
602            for ( int i = 0; i < layers.length; i++ ) {
603                layers[i] = parseLayers( nl.get( i ), layer, scaleHint );
604            }
605    
606            // set child layers
607            layer.setLayer( layers );
608    
609            return layer;
610        }
611    
612        /**
613         * 
614         * @param layerElem
615         * @return the dimensions
616         * @throws XMLParsingException
617         */
618        protected Dimension[] parseDimensions( Element layerElem )
619                                throws XMLParsingException {
620    
621            List<Node> nl = XMLTools.getNodes( layerElem, "./Dimension", nsContext );
622            Dimension[] dimensions = new Dimension[nl.size()];
623            for ( int i = 0; i < dimensions.length; i++ ) {
624                String name = XMLTools.getNodeAsString( nl.get( i ), "./@name", nsContext, null );
625                String units = XMLTools.getNodeAsString( nl.get( i ), "./@units", nsContext, null );
626                String unitSymbol = XMLTools.getNodeAsString( nl.get( i ), "./@unitSymbol", nsContext, null );
627                dimensions[i] = new Dimension( name, units, unitSymbol );
628            }
629    
630            return dimensions;
631        }
632    
633        /**
634         * 
635         * @param layerElem
636         * @return the extent
637         * @throws XMLParsingException
638         */
639        protected Extent[] parseExtents( Element layerElem )
640                                throws XMLParsingException {
641    
642            List<Node> nl = XMLTools.getNodes( layerElem, "./Extent", nsContext );
643            Extent[] extents = new Extent[nl.size()];
644            for ( int i = 0; i < extents.length; i++ ) {
645                String name = XMLTools.getNodeAsString( nl.get( i ), "./@name", nsContext, null );
646                String deflt = XMLTools.getNodeAsString( nl.get( i ), "./@default", nsContext, null );
647                boolean nearestValue = XMLTools.getNodeAsBoolean( nl.get( i ), "./@nearestValue", nsContext, false );
648                String value = XMLTools.getNodeAsString( nl.get( i ), ".", nsContext, "" );
649                extents[i] = new Extent( name, deflt, nearestValue, value );
650            }
651    
652            return extents;
653        }
654    
655        /**
656         * 
657         * @param layerElem
658         * @return the attribution
659         * @throws XMLParsingException
660         */
661        protected Attribution parseAttribution( Element layerElem )
662                                throws XMLParsingException {
663    
664            Attribution attribution = null;
665            Node node = XMLTools.getNode( layerElem, "./Attribution", nsContext );
666            if ( node != null ) {
667                String title = XMLTools.getRequiredNodeAsString( layerElem, "./Attribution/Title", nsContext );
668                OnlineResource onLineResource = parseOnLineResource( XMLTools.getRequiredElement( node, "./OnlineResource",
669                                                                                                  nsContext ) );
670                node = XMLTools.getNode( node, "./LogoURL", nsContext );
671                LogoURL logoURL = null;
672                if ( node != null ) {
673                    int width = XMLTools.getRequiredNodeAsInt( node, "./@width", nsContext );
674                    int height = XMLTools.getRequiredNodeAsInt( node, "./@height", nsContext );
675                    String format = XMLTools.getRequiredNodeAsString( node, "./Format", nsContext );
676                    OnlineResource logoOR = parseOnLineResource( XMLTools.getRequiredElement( node, "./OnlineResource",
677                                                                                              nsContext ) );
678                    logoURL = new LogoURL( width, height, format, logoOR.getLinkage().getHref() );
679                }
680                attribution = new Attribution( title, onLineResource.getLinkage().getHref(), logoURL );
681            }
682    
683            return attribution;
684        }
685    
686        /**
687         * 
688         * @param layerElem
689         * @return the URLs
690         * @throws XMLParsingException
691         */
692        protected AuthorityURL[] parseAuthorityURLs( Element layerElem )
693                                throws XMLParsingException {
694    
695            List<Node> nl = XMLTools.getNodes( layerElem, "./AuthorityURL", nsContext );
696            AuthorityURL[] authorityURLs = new AuthorityURL[nl.size()];
697            for ( int i = 0; i < authorityURLs.length; i++ ) {
698                String name = XMLTools.getRequiredNodeAsString( nl.get( i ), "./@name", nsContext );
699                Element tmp = XMLTools.getRequiredElement( nl.get( i ), "./OnlineResource", nsContext );
700                OnlineResource olr = parseOnLineResource( tmp );
701                authorityURLs[i] = new AuthorityURL( name, olr.getLinkage().getHref() );
702            }
703    
704            return authorityURLs;
705        }
706    
707        /**
708         * 
709         * @param layerElem
710         * @return the URLs
711         * @throws XMLParsingException
712         */
713        protected MetadataURL[] parseMetadataURLs( Element layerElem )
714                                throws XMLParsingException {
715    
716            List<Node> nl = XMLTools.getNodes( layerElem, "./MetadataURL", nsContext );
717            MetadataURL[] metadataURL = new MetadataURL[nl.size()];
718            for ( int i = 0; i < metadataURL.length; i++ ) {
719                String type = XMLTools.getRequiredNodeAsString( nl.get( i ), "./@type", nsContext );
720                String format = XMLTools.getRequiredNodeAsString( nl.get( i ), "./Format", nsContext );
721                Element tmp = XMLTools.getRequiredElement( nl.get( i ), "./OnlineResource", nsContext );
722                OnlineResource olr = parseOnLineResource( tmp );
723                metadataURL[i] = new MetadataURL( type, format, olr.getLinkage().getHref() );
724    
725            }
726    
727            return metadataURL;
728        }
729    
730        /**
731         * 
732         * @param layerElem
733         * @return the URLs
734         * @throws XMLParsingException
735         */
736        protected DataURL[] parseDataURL( Element layerElem )
737                                throws XMLParsingException {
738    
739            List<Node> nl = XMLTools.getNodes( layerElem, "./DataURL", nsContext );
740            DataURL[] dataURL = new DataURL[nl.size()];
741            for ( int i = 0; i < dataURL.length; i++ ) {
742    
743                String format = XMLTools.getRequiredNodeAsString( nl.get( i ), "./Format", nsContext );
744                Element tmp = XMLTools.getRequiredElement( nl.get( i ), "./OnlineResource", nsContext );
745                OnlineResource olr = parseOnLineResource( tmp );
746                dataURL[i] = new DataURL( format, olr.getLinkage().getHref() );
747    
748            }
749    
750            return dataURL;
751        }
752    
753        /**
754         * 
755         * @param layerElem
756         * @return the URLs
757         * @throws XMLParsingException
758         */
759        protected FeatureListURL[] parseFeatureListURL( Element layerElem )
760                                throws XMLParsingException {
761    
762            List<Node> nl = XMLTools.getNodes( layerElem, "./FeatureListURL", nsContext );
763            FeatureListURL[] flURL = new FeatureListURL[nl.size()];
764            for ( int i = 0; i < flURL.length; i++ ) {
765    
766                String format = XMLTools.getRequiredNodeAsString( nl.get( i ), "./Format", nsContext );
767                Element tmp = XMLTools.getRequiredElement( nl.get( i ), "./OnlineResource", nsContext );
768                OnlineResource olr = parseOnLineResource( tmp );
769                flURL[i] = new FeatureListURL( format, olr.getLinkage().getHref() );
770    
771            }
772    
773            return flURL;
774        }
775    
776        /**
777         * 
778         * @param layerElem
779         * @return the styles
780         * @throws XMLParsingException
781         */
782        protected Style[] parseStyles( Element layerElem )
783                                throws XMLParsingException {
784    
785            List<Node> nl = XMLTools.getNodes( layerElem, "./Style", nsContext );
786            Style[] styles = new Style[nl.size()];
787            for ( int i = 0; i < styles.length; i++ ) {
788                String name = XMLTools.getRequiredNodeAsString( nl.get( i ), "./Name", nsContext );
789    
790                if ( name == null ) {
791                    throw new XMLParsingException( Messages.getMessage( "WMS_STYLENAME" ) );
792                }
793                String title = XMLTools.getNodeAsString( nl.get( i ), "./Title", nsContext, null );
794                if ( title == null ) {
795                    throw new XMLParsingException( Messages.getMessage( "WMS_STYLETITLE" ) );
796                }
797                String styleAbstract = XMLTools.getNodeAsString( nl.get( i ), "./Abstract", nsContext, null );
798                LegendURL[] legendURLs = parseLegendURL( nl.get( i ) );
799                StyleURL styleURL = parseStyleURL( nl.get( i ) );
800                StyleSheetURL styleSheetURL = parseStyleSheetURL( nl.get( i ) );
801    
802                styles[i] = new Style( name, title, styleAbstract, legendURLs, styleSheetURL, styleURL, null );
803            }
804    
805            return styles;
806        }
807    
808        /**
809         * 
810         * @param node
811         * @return the URLs
812         * @throws XMLParsingException
813         */
814        protected LegendURL[] parseLegendURL( Node node )
815                                throws XMLParsingException {
816    
817            List<Node> nl = XMLTools.getNodes( node, "./LegendURL", nsContext );
818            LegendURL[] lURL = new LegendURL[nl.size()];
819            for ( int i = 0; i < lURL.length; i++ ) {
820                int width = XMLTools.getRequiredNodeAsInt( nl.get( i ), "./@width", nsContext );
821                int height = XMLTools.getRequiredNodeAsInt( nl.get( i ), "./@height", nsContext );
822                String format = XMLTools.getRequiredNodeAsString( nl.get( i ), "./Format", nsContext );
823                Element tmp = XMLTools.getRequiredElement( nl.get( i ), "./OnlineResource", nsContext );
824                OnlineResource olr = parseOnLineResource( tmp );
825                lURL[i] = new LegendURL( width, height, format, olr.getLinkage().getHref() );
826    
827            }
828    
829            return lURL;
830        }
831    
832        /**
833         * 
834         * @param node
835         * @return the URL
836         * @throws XMLParsingException
837         */
838        protected StyleURL parseStyleURL( Node node )
839                                throws XMLParsingException {
840    
841            StyleURL styleURL = null;
842            Node styleNode = XMLTools.getNode( node, "./StyleURL", nsContext );
843    
844            if ( styleNode != null ) {
845                String format = XMLTools.getRequiredNodeAsString( styleNode, "./Format", nsContext );
846                Element tmp = XMLTools.getRequiredElement( styleNode, "./OnlineResource", nsContext );
847                OnlineResource olr = parseOnLineResource( tmp );
848                styleURL = new StyleURL( format, olr.getLinkage().getHref() );
849    
850            }
851    
852            return styleURL;
853        }
854    
855        /**
856         * 
857         * @param node
858         * @return the URL
859         * @throws XMLParsingException
860         */
861        protected StyleSheetURL parseStyleSheetURL( Node node )
862                                throws XMLParsingException {
863    
864            StyleSheetURL styleSheetURL = null;
865            Node styleNode = XMLTools.getNode( node, "./StyleSheetURL", nsContext );
866    
867            if ( styleNode != null ) {
868                String format = XMLTools.getRequiredNodeAsString( styleNode, "./Format", nsContext );
869                Element tmp = XMLTools.getRequiredElement( styleNode, "./OnlineResource", nsContext );
870                OnlineResource olr = parseOnLineResource( tmp );
871                styleSheetURL = new StyleSheetURL( format, olr.getLinkage().getHref() );
872    
873            }
874    
875            return styleSheetURL;
876        }
877    
878        /**
879         * 
880         * @param layerElem
881         * @param scaleHint
882         *            the default scale hint
883         * @return the scale hint
884         * @throws XMLParsingException
885         */
886        protected ScaleHint parseScaleHint( Element layerElem, ScaleHint scaleHint )
887                                throws XMLParsingException {
888    
889            Node scNode = XMLTools.getNode( layerElem, "./ScaleHint", nsContext );
890            if ( scNode != null ) {
891                double mn = XMLTools.getNodeAsDouble( scNode, "./@min", nsContext, 0 );
892                double mx = XMLTools.getNodeAsDouble( scNode, "./@max", nsContext, Double.MAX_VALUE );
893                scaleHint = new ScaleHint( mn, mx );
894            }
895    
896            if ( scaleHint == null ) {
897                // set default value to avoid NullPointerException
898                // when accessing a layers scalehint
899                scaleHint = new ScaleHint( 0, Double.MAX_VALUE );
900            }
901    
902            return scaleHint;
903        }
904    
905        /**
906         * 
907         * @param layerElem
908         * @return the identifiers
909         * @throws XMLParsingException
910         */
911        protected Identifier[] parseIdentifiers( Element layerElem )
912                                throws XMLParsingException {
913    
914            List<Node> nl = XMLTools.getNodes( layerElem, "./Identifier", nsContext );
915            Identifier[] identifiers = new Identifier[nl.size()];
916            for ( int i = 0; i < identifiers.length; i++ ) {
917                String value = XMLTools.getStringValue( nl.get( i ) );
918                String authority = XMLTools.getNodeAsString( layerElem, "./@authority", nsContext, null );
919                identifiers[i] = new Identifier( value, authority );
920            }
921    
922            return identifiers;
923        }
924    
925        /**
926         * 
927         * @param nl
928         * @return the bboxes
929         * @throws XMLParsingException
930         */
931        protected LayerBoundingBox[] parseLayerBoundingBoxes( List<Element> nl )
932                                throws XMLParsingException {
933    
934            LayerBoundingBox[] llBoxes = new LayerBoundingBox[nl.size()];
935            for ( int i = 0; i < llBoxes.length; i++ ) {
936                double minx = XMLTools.getRequiredNodeAsDouble( nl.get( i ), "./@minx", nsContext );
937                double maxx = XMLTools.getRequiredNodeAsDouble( nl.get( i ), "./@maxx", nsContext );
938                double miny = XMLTools.getRequiredNodeAsDouble( nl.get( i ), "./@miny", nsContext );
939                double maxy = XMLTools.getRequiredNodeAsDouble( nl.get( i ), "./@maxy", nsContext );
940                double resx = XMLTools.getNodeAsDouble( nl.get( i ), "./@resx", nsContext, -1 );
941                double resy = XMLTools.getNodeAsDouble( nl.get( i ), "./@resx", nsContext, -1 );
942                String srs = XMLTools.getRequiredNodeAsString( nl.get( i ), "./@SRS", nsContext );
943                Position min = GeometryFactory.createPosition( minx, miny );
944                Position max = GeometryFactory.createPosition( maxx, maxy );
945                llBoxes[i] = new LayerBoundingBox( min, max, srs, resx, resy );
946            }
947    
948            return llBoxes;
949        }
950    
951        /**
952         * 
953         * @param llBox
954         * @return the envelope
955         * @throws XMLParsingException
956         * @throws UnknownCRSException
957         */
958        protected Envelope parseLatLonBoundingBox( Element llBox )
959                                throws XMLParsingException, UnknownCRSException {
960    
961            double minx = XMLTools.getRequiredNodeAsDouble( llBox, "./@minx", nsContext );
962            double maxx = XMLTools.getRequiredNodeAsDouble( llBox, "./@maxx", nsContext );
963            double miny = XMLTools.getRequiredNodeAsDouble( llBox, "./@miny", nsContext );
964            double maxy = XMLTools.getRequiredNodeAsDouble( llBox, "./@maxy", nsContext );
965            CoordinateSystem crs = CRSFactory.create( "EPSG:4326" );
966    
967            Envelope env = GeometryFactory.createEnvelope( minx, miny, maxx, maxy, crs );
968    
969            return env;
970        }
971    
972    }