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