    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/ogcwebservices/wmps/configuration/WMPSConfigurationDocument.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.wmps.configuration;
    037
    038    import java.awt.Color;
    039    import java.io.IOException;
    040    import java.net.MalformedURLException;
    041    import java.net.URI;
    042    import java.net.URL;
    043    import java.util.ArrayList;
    044    import java.util.HashMap;
    045    import java.util.List;
    046    import java.util.Map;
    047
    048    import javax.xml.transform.TransformerException;
    049
    050    import org.deegree.datatypes.QualifiedName;
    051    import org.deegree.enterprise.Proxy;
    052    import org.deegree.framework.log.ILogger;
    053    import org.deegree.framework.log.LoggerFactory;
    054    import org.deegree.framework.util.BootLogger;
    055    import org.deegree.framework.util.IDGenerator;
    056    import org.deegree.framework.util.KVP2Map;
    057    import org.deegree.framework.util.StringTools;
    058    import org.deegree.framework.xml.InvalidConfigurationException;
    059    import org.deegree.framework.xml.XMLFragment;
    060    import org.deegree.framework.xml.XMLParsingException;
    061    import org.deegree.framework.xml.XMLTools;
    062    import org.deegree.framework.xml.XSLTDocument;
    063    import org.deegree.io.IODocument;
    064    import org.deegree.io.JDBCConnection;
    065    import org.deegree.model.crs.UnknownCRSException;
    066    import org.deegree.model.metadata.iso19115.OnlineResource;
    067    import org.deegree.model.spatialschema.Envelope;
    068    import org.deegree.model.spatialschema.GMLGeometryAdapter;
    069    import org.deegree.model.spatialschema.Geometry;
    070    import org.deegree.model.spatialschema.GeometryException;
    071    import org.deegree.ogcbase.CommonNamespaces;
    072    import org.deegree.ogcwebservices.OGCWebService;
    073    import org.deegree.ogcwebservices.getcapabilities.InvalidCapabilitiesException;
    074    import org.deegree.ogcwebservices.getcapabilities.MetadataURL;
    075    import org.deegree.ogcwebservices.getcapabilities.OperationsMetadata;
    076    import org.deegree.ogcwebservices.getcapabilities.ServiceIdentification;
    077    import org.deegree.ogcwebservices.getcapabilities.ServiceProvider;
    078    import org.deegree.ogcwebservices.wcs.RemoteWCService;
    079    import org.deegree.ogcwebservices.wcs.WCService;
    080    import org.deegree.ogcwebservices.wcs.configuration.WCSConfiguration;
    081    import org.deegree.ogcwebservices.wcs.getcapabilities.WCSCapabilities;
    082    import org.deegree.ogcwebservices.wcs.getcapabilities.WCSCapabilitiesDocument;
    083    import org.deegree.ogcwebservices.wcs.getcoverage.GetCoverage;
    084    import org.deegree.ogcwebservices.wfs.WFServiceFactory;
    085    import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities;
    086    import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilitiesDocument;
    087    import org.deegree.ogcwebservices.wfs.configuration.WFSConfiguration;
    088    import org.deegree.ogcwebservices.wfs.configuration.WFSConfigurationDocument;
    089    import org.deegree.ogcwebservices.wfs.operation.Query;
    090    import org.deegree.ogcwebservices.wmps.capabilities.WMPSCapabilitiesDocument;
    091    import org.deegree.ogcwebservices.wms.RemoteWMService;
    092    import org.deegree.ogcwebservices.wms.capabilities.Attribution;
    093    import org.deegree.ogcwebservices.wms.capabilities.AuthorityURL;
    094    import org.deegree.ogcwebservices.wms.capabilities.DataURL;
    095    import org.deegree.ogcwebservices.wms.capabilities.Dimension;
    096    import org.deegree.ogcwebservices.wms.capabilities.Extent;
    097    import org.deegree.ogcwebservices.wms.capabilities.FeatureListURL;
    098    import org.deegree.ogcwebservices.wms.capabilities.GazetteerParam;
    099    import org.deegree.ogcwebservices.wms.capabilities.Identifier;
    100    import org.deegree.ogcwebservices.wms.capabilities.Layer;
    101    import org.deegree.ogcwebservices.wms.capabilities.LayerBoundingBox;
    102    import org.deegree.ogcwebservices.wms.capabilities.LegendURL;
    103    import org.deegree.ogcwebservices.wms.capabilities.ScaleHint;
    104    import org.deegree.ogcwebservices.wms.capabilities.Style;
    105    import org.deegree.ogcwebservices.wms.capabilities.StyleSheetURL;
    106    import org.deegree.ogcwebservices.wms.capabilities.StyleURL;
    107    import org.deegree.ogcwebservices.wms.capabilities.UserDefinedSymbolization;
    108    import org.deegree.ogcwebservices.wms.capabilities.WMSCapabilities;
    109    import org.deegree.ogcwebservices.wms.capabilities.WMSCapabilitiesDocument;
    110    import org.deegree.ogcwebservices.wms.capabilities.WMSCapabilitiesDocumentFactory;
    111    import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource;
    112    import org.deegree.ogcwebservices.wms.configuration.LocalWCSDataSource;
    113    import org.deegree.ogcwebservices.wms.configuration.LocalWFSDataSource;
    114    import org.deegree.ogcwebservices.wms.configuration.RemoteWCSDataSource;
    115    import org.deegree.ogcwebservices.wms.configuration.RemoteWFSDataSource;
    116    import org.deegree.ogcwebservices.wms.configuration.RemoteWMSDataSource;
    117    import org.deegree.ogcwebservices.wms.operation.GetMap;
    118    import org.w3c.dom.Element;
    119    import org.w3c.dom.Node;
    120    import org.xml.sax.SAXException;
    121
    122    /**
    123     * Represents an XML configuration document for a deegree WMPS 1.0 instance, i.e. it consists of all sections common to
    124     * an OGC WMS 1.1.1 capabilities document plus a deegree specific section named <code>deegreeParams</code> and... TODO
    125     *
    126     * @author <a href="mailto:deshmukh@lat-lon.de">Anup Deshmukh</a>
    127     * @author last edited by: $Author: apoth $
    128     *
    129     * @version 2.0, $Revision: 24061 $, $Date: 2010-05-04 10:51:44 +0200 (Di, 04. Mai 2010) $
    130     */
    131    public class WMPSConfigurationDocument extends WMPSCapabilitiesDocument {
    132
    133        private static final long serialVersionUID = -7940857863171829848L;
    134
    135        private static final ILogger LOG = LoggerFactory.getLogger( WMPSConfigurationDocument.class );
    136
    137        protected static final URI DEEGREEWPSNS = CommonNamespaces.DEEGREEWMPS;
    138
    139        private static final String XML_TEMPLATE = "WMPSConfigurationTemplate.xml";
    140
    141        private static final String XSLT_TEMPLATE_NAME = "WMPSConfigurationTransform.xsl";
    142
    143        private static XSLTDocument XSLT_TEMPLATE;
    144
    145        private static Map<URL, Object> capaCache = new HashMap<URL, Object>();
    146
    147        static {
    148            XSLT_TEMPLATE = new XSLTDocument();
    149            try {
    150                XSLT_TEMPLATE.load( WMPSConfigurationDocument.class.getResource( XSLT_TEMPLATE_NAME ) );
    151            } catch ( Exception e ) {
    152                BootLogger.logError( "Error loading XSLT sheet in WMPSConfigurationDocument.", e );
    153            }
    154        }
    155
    156        /**
    157         *
    158         */
    159        public static void resetCapabilitiesCache() {
    160            capaCache.clear();
    161        }
    162
    163        /**
    164         * Creates a skeleton capabilities document that contains the mandatory elements only.
    165         *
    166         * @throws IOException
    167         * @throws SAXException
    168         */
    169        @Override
    170        public void createEmptyDocument()
    171                                throws IOException, SAXException {
    172            URL url = WMPSConfigurationDocument.class.getResource( XML_TEMPLATE );
    173            if ( url == null ) {
    174                throw new IOException( "The resource '" + XML_TEMPLATE + " could not be found." );
    175            }
    176            load( url );
    177        }
    178
    179        /**
    180         * Creates a class representation of the document.
    181         *
    182         * @return class representation of the configuration document
    183         * @throws InvalidConfigurationException
    184         */
    185        public WMPSConfiguration parseConfiguration()
    186                                throws InvalidConfigurationException {
    187
    188            try {
    189                // transform document to fill missing elements and attributes with default values
    190                XMLFragment frag = XSLT_TEMPLATE.transform( this );
    191                this.setRootElement( frag.getRootElement() );
    192            } catch ( TransformerException e ) {
    193                String msg = "Error transforming WMPS configuration document (in order to fill " + "in default value). "
    194                             + e.getMessage();
    195                LOG.logError( msg, e );
    196                throw new InvalidConfigurationException( msg, e );
    197            }
    198
    199            ServiceIdentification serviceIdentification = null;
    200            ServiceProvider serviceProvider = null;
    201            UserDefinedSymbolization uds = null;
    202            OperationsMetadata metadata = null;
    203            Layer layer = null;
    204            WMPSDeegreeParams params = null;
    205            String version = parseVersion();
    206            try {
    207                Node node = XMLTools.getRequiredNode( getRootElement(), "deegreewmps:DeegreeParam", nsContext );
    208                params = parseDeegreeParams( node );
    209                serviceIdentification = parseServiceIdentification();
    210                serviceProvider = parseServiceProvider();
    211                uds = parseUserDefinedSymbolization();
    212                metadata = parseOperationsMetadata();
    213                Element layerElem = (Element) XMLTools.getRequiredNode( getRootElement(), "./Capability/Layer", nsContext );
    214                layer = parseLayers( layerElem, null );
    215            } catch ( XMLParsingException e ) {
    216                e.printStackTrace();
    217                throw new InvalidConfigurationException( e.getMessage() + StringTools.stackTraceToString( e ) );
    218            } catch ( MalformedURLException e ) {
    219                throw new InvalidConfigurationException( e.getMessage() + " - " + StringTools.stackTraceToString( e ) );
    220            } catch ( UnknownCRSException e ) {
    221                throw new InvalidConfigurationException( getClass().getName(), e.getMessage() );
    222            }
    223            WMPSConfiguration wmpsConfiguration = new WMPSConfiguration( version, serviceIdentification, serviceProvider,
    224                                                                         uds, metadata, layer, params, getSystemId() );
    225
    226            return wmpsConfiguration;
    227        }
    228
    229        /**
    230         * Creates a class representation of the <code>deegreeParams</code>- section.
    231         *
    232         * @param root
    233         * @return WMPSDeegreeParams
    234         * @throws XMLParsingException
    235         * @throws MalformedURLException
    236         */
    237        public WMPSDeegreeParams parseDeegreeParams( Node root )
    238                                throws XMLParsingException, MalformedURLException {
    239
    240            String xPath = "./deegreewmps:DefaultOnlineResource";
    241            Element elem = (Element) XMLTools.getRequiredNode( root, xPath, nsContext );
    242
    243            OnlineResource ol = parseOnLineResource( elem );
    244            xPath = "./deegreewmps:CacheSize";
    245            int cache = XMLTools.getNodeAsInt( root, xPath, nsContext, 100 );
    246            xPath = "./deegreewmps:MaxLifeTime";
    247            int maxLifeTime = XMLTools.getNodeAsInt( root, xPath, nsContext, 3600 );
    248            xPath = "./deegreewmps:RequestTimeLimit";
    249            int reqTimeLimit = XMLTools.getNodeAsInt( root, xPath, nsContext, 15 );
    250            reqTimeLimit *= 1000;
    251            xPath = "./deegreewmps:MapQuality";
    252            double mapQuality = XMLTools.getNodeAsDouble( root, xPath, nsContext, 0.95 );
    253            xPath = "./deegreewmps:MaxMapWidth";
    254            int maxMapWidth = XMLTools.getNodeAsInt( root, xPath, nsContext, 1000 );
    255            xPath = "./deegreewmps:MaxMapHeight";
    256            int maxMapHeight = XMLTools.getNodeAsInt( root, xPath, nsContext, 1000 );
    257            xPath = "./deegreewmps:Copyright";
    258            String copyright = XMLTools.getNodeAsString( root, xPath, nsContext, "" );
    259            URL dtdLocation = null;
    260            xPath = "deegreewmps:DTDLocation";
    261            if ( XMLTools.getNode( root, xPath, nsContext ) != null ) {
    262                xPath = "./deegreewmps:DTDLocation/deegreewmps:OnlineResource";
    263                elem = (Element) XMLTools.getRequiredNode( root, xPath, nsContext );
    264                OnlineResource olr = parseOnLineResource( elem );
    265                dtdLocation = olr.getLinkage().getHref();
    266            } else {
    267                dtdLocation = new URL( "http://schemas.opengis.net/wms/1.1.1/WMS_MS_Capabilities.dtd" );
    268            }
    269            xPath = "./deegreewmps:AntiAliased";
    270            boolean antiAliased = XMLTools.getNodeAsBoolean( root, xPath, nsContext, true );
    271            xPath = "./deegreewmps:GazetteerParameter";
    272            elem = (Element) XMLTools.getNode( root, xPath, nsContext );
    273            GazetteerParam gazetteer = null;
    274            if ( elem != null ) {
    275                gazetteer = parseGazetteerParameter( elem );
    276            }
    277
    278            Proxy proxy = parseProxy( root );
    279
    280            List<String> synchList = parseSynchTemplates( root );
    281
    282            CacheDatabase cacheDatabase = parseCacheDatabase( root );
    283
    284            PrintMapParam printMapParam = parsePrintMapParam( root );
    285
    286            WMPSDeegreeParams deegreeParams = new WMPSDeegreeParams( cache, maxLifeTime, reqTimeLimit, (float) mapQuality,
    287                                                                     ol, maxMapWidth, maxMapHeight, antiAliased, copyright,
    288                                                                     gazetteer, null, dtdLocation, proxy, synchList,
    289                                                                     cacheDatabase, printMapParam );
    290
    291            return deegreeParams;
    292        }
    293
    294        /**
    295         * Parse the cache database parameters used by the wmps to store the asynchronous requests.
    296         *
    297         * @param root
    298         * @return CacheDatabase
    299         * @throws XMLParsingException
    300         */
    301        private CacheDatabase parseCacheDatabase( Node root )
    302                                throws XMLParsingException {
    303
    304            CacheDatabase cacheDatabase = null;
    305            String xPath = "./deegreewmps:CacheDatabase";
    306            String driver = null;
    307            String url = null;
    308            String user = null;
    309            String password = null;
    310            Node cacheDb = XMLTools.getNode( root, xPath, nsContext );
    311            if ( cacheDb != null ) {
    312                xPath = "./deegreewmps:JDBCConnection";
    313                Node jdbcConnection = XMLTools.getNode( cacheDb, xPath, nsContext );
    314                if ( jdbcConnection != null ) {
    315                    driver = XMLTools.getRequiredNodeAsString( jdbcConnection, "./deegreewmps:Driver", nsContext );
    316                    url = XMLTools.getRequiredNodeAsString( jdbcConnection, "./deegreewmps:Url", nsContext );
    317                    user = XMLTools.getRequiredNodeAsString( jdbcConnection, "./deegreewmps:User", nsContext );
    318                    password = XMLTools.getRequiredNodeAsString( jdbcConnection, "./deegreewmps:Password", nsContext );
    319                } else {
    320                    xPath = "./dgjdbc:JDBCConnection";
    321                    jdbcConnection = XMLTools.getRequiredNode( cacheDb, xPath, nsContext );
    322                    IODocument doc = new IODocument( (Element) jdbcConnection );
    323                    doc.setSystemId( getSystemId() );
    324                    JDBCConnection jdbc = doc.parseJDBCConnection();
    325                    driver = jdbc.getDriver();
    326                    url = jdbc.getURL();
    327                    user = jdbc.getUser();
    328                    password = jdbc.getPassword();
    329                }
    330                cacheDatabase = new CacheDatabase( driver, url, user, password );
    331                LOG.logDebug( "Successfully parsed the deegree wmps cache database parameters." );
    332            }
    333
    334            return cacheDatabase;
    335        }
    336
    337        /**
    338         * Parse the PrintMapParam node and retrieve the (jasper reports)template directory, the default template name if
    339         * none is specified in the request. Also parse the output directory location and the location of the printed image
    340         * output directory.
    341         *
    342         * @param root
    343         * @return PrintMapParam
    344         * @throws XMLParsingException
    345         * @throws MalformedURLException
    346         */
    347        private PrintMapParam parsePrintMapParam( Node root )
    348                                throws XMLParsingException, MalformedURLException {
    349
    350            Node printParam = XMLTools.getRequiredNode( root, "./deegreewmps:PrintMapParam", nsContext );
    351            // set default as pdf
    352            String format = XMLTools.getNodeAsString( printParam, "./deegreewmps:Format", nsContext, "pdf" );
    353            Node templ = XMLTools.getRequiredNode( printParam, "./deegreewmps:Template", nsContext );
    354            String templateDirectory = XMLTools.getRequiredNodeAsString( templ, "./deegreewmps:Directory", nsContext );
    355
    356            templateDirectory = this.resolve( templateDirectory ).toExternalForm();
    357            if ( !templateDirectory.endsWith( "/" ) ) {
    358                templateDirectory = templateDirectory + '/';
    359            }
    360            String plotDirectory = XMLTools.getRequiredNodeAsString( printParam, "./deegreewmps:PlotDirectory", nsContext );
    361            plotDirectory = this.resolve( plotDirectory ).toExternalForm();
    362            String onlineResource = XMLTools.getRequiredNodeAsString( printParam, "./deegreewmps:OnlineResource", nsContext );
    363            String plotImgDir = XMLTools.getRequiredNodeAsString( printParam, "./deegreewmps:PlotImageDirectory", nsContext );
    364            plotImgDir = this.resolve( plotImgDir ).toExternalForm();
    365            String adminMail = XMLTools.getRequiredNodeAsString( printParam, "./deegreewmps:AdministratorEMailAddress",
    366                                                                 nsContext );
    367            String mailHost = XMLTools.getRequiredNodeAsString( printParam, "./deegreewmps:MailHost", nsContext );
    368            String mailHostUser = XMLTools.getNodeAsString( printParam, "./deegreewmps:MailHostUser", nsContext, null );
    369            String mailHostPassword = XMLTools.getNodeAsString( printParam, "./deegreewmps:MailHostPassword", nsContext,
    370                                                                null );
    371
    372            String mailTextTemplate = XMLTools.getNodeAsString( printParam, "./deegreewmps:MailTextTemplate", nsContext,
    373                                                                "You can access the printMap result at {1}" );
    374
    375            int targetRes = XMLTools.getNodeAsInt( printParam, "./deegreewmps:TargetResolution", nsContext, 300 );
    376
    377            return new PrintMapParam( format, templateDirectory, onlineResource, plotDirectory, plotImgDir, adminMail,
    378                                      mailHost, mailHostUser, mailHostPassword, mailTextTemplate, targetRes );
    379        }
    380
    381        /**
    382         * parses the list of templates that shall be handled synchronously
    383         *
    384         * @param root
    385         * @return List
    386         * @throws XMLParsingException
    387         */
    388        private List<String> parseSynchTemplates( Node root )
    389                                throws XMLParsingException {
    390
    391            String xPath = "./deegreewmps:SynchronousTemplates/deegreewmps:Template";
    392            String[] nodes = XMLTools.getNodesAsStrings( root, xPath, nsContext );
    393            List<String> list = new ArrayList<String>( nodes.length );
    394            for ( int i = 0; i < nodes.length; i++ ) {
    395                list.add( nodes[i] );
    396            }
    397
    398            return list;
    399        }
    400
    401        /**
    402         * @param root
    403         * @return Proxy
    404         * @throws XMLParsingException
    405         */
    406        private Proxy parseProxy( Node root )
    407                                throws XMLParsingException {
    408
    409            Proxy proxy = null;
    410            Node pro = XMLTools.getNode( root, "./deegreewmps:Proxy", nsContext );
    411            if ( pro != null ) {
    412                String proxyHost = XMLTools.getRequiredNodeAsString( pro, "./@proxyHost", nsContext );
    413                String proxyPort = XMLTools.getRequiredNodeAsString( pro, "./@proxyPort", nsContext );
    414                proxy = new Proxy( proxyHost, proxyPort );
    415            }
    416
    417            return proxy;
    418        }
    419
    420        /**
    421         * creates an object that describes the access to a gazetteer if one have been defined at the <DeegreeParam>section
    422         * of the capabilities/configuration
    423         *
    424         * @param element
    425         * @return GazetteerParam
    426         * @throws XMLParsingException
    427         */
    428        private GazetteerParam parseGazetteerParameter( Element element )
    429                                throws XMLParsingException {
    430
    431            GazetteerParam gazetteer = null;
    432
    433            if ( element != null ) {
    434                String xPath = "./deegreewmps:OnlineResource";
    435                Element elem = (Element) XMLTools.getRequiredNode( element, xPath, nsContext );
    436                OnlineResource olr = parseOnLineResource( elem );
    437                URL onlineResource = olr.getLinkage().getHref();
    438                // optional: <LocationRadius>, default: 10
    439                double radius = XMLTools.getNodeAsDouble( element, "LocationRadius", nsContext, 10 );
    440                gazetteer = new GazetteerParam( onlineResource, radius );
    441            }
    442
    443            return gazetteer;
    444        }
    445
    446        /**
    447         * returns the layers offered by the WMS
    448         *
    449         * @param layerElem
    450         * @param parent
    451         * @return Layer
    452         * @throws XMLParsingException
    453         * @throws UnknownCRSException
    454         */
    455        @Override
    456        protected Layer parseLayers( Element layerElem, Layer parent )
    457                                throws XMLParsingException, UnknownCRSException {
    458
    459            boolean queryable = XMLTools.getNodeAsBoolean( layerElem, "./@queryable", nsContext, false );
    460            int cascaded = XMLTools.getNodeAsInt( layerElem, "./@cascaded", nsContext, 0 );
    461            boolean opaque = XMLTools.getNodeAsBoolean( layerElem, "./@opaque", nsContext, false );
    462            boolean noSubsets = XMLTools.getNodeAsBoolean( layerElem, "./@noSubsets", nsContext, false );
    463            int fixedWidth = XMLTools.getNodeAsInt( layerElem, "./@fixedWidth", nsContext, 0 );
    464            int fixedHeight = XMLTools.getNodeAsInt( layerElem, "./@fixedHeight", nsContext, 0 );
    465            String name = XMLTools.getNodeAsString( layerElem, "./Name", nsContext, null );
    466            String title = XMLTools.getRequiredNodeAsString( layerElem, "./Title", nsContext );
    467            String layerAbstract = XMLTools.getNodeAsString( layerElem, "./Abstract", nsContext, null );
    468            String[] keywords = XMLTools.getNodesAsStrings( layerElem, "./KeywordList/Keyword", nsContext );
    469            String[] srs = XMLTools.getNodesAsStrings( layerElem, "./SRS", nsContext );
    470            List<Node> nl = XMLTools.getNodes( layerElem, "./BoundingBox", nsContext );
    471            // substitute with Envelope
    472            LayerBoundingBox[] bboxes = null;
    473            if ( nl.size() == 0 && parent != null ) {
    474                // inherit BoundingBoxes from parent layer
    475                bboxes = parent.getBoundingBoxes();
    476            } else {
    477                bboxes = parseLayerBoundingBoxes( nl );
    478            }
    479
    480            Element llBox = (Element) XMLTools.getNode( layerElem, "./LatLonBoundingBox", nsContext );
    481            Envelope llBoundingBox = null;
    482            if ( llBox == null && parent != null ) {
    483                // inherit LatLonBoundingBox parent layer
    484                llBoundingBox = parent.getLatLonBoundingBox();
    485            } else {
    486                llBoundingBox = parseLatLonBoundingBox( llBox );
    487            }
    488
    489            Dimension[] dimensions = parseDimensions( layerElem );
    490            Extent[] extents = parseExtents( layerElem );
    491            Attribution attribution = parseAttribution( layerElem );
    492            AuthorityURL[] authorityURLs = parseAuthorityURLs( layerElem );
    493            MetadataURL[] metadataURLs = parseMetadataURLs( layerElem );
    494            DataURL[] dataURLs = parseDataURL( layerElem );
    495            Identifier[] identifiers = parseIdentifiers( layerElem );
    496            FeatureListURL[] featureListURLs = parseFeatureListURL( layerElem );
    497            Style[] styles = parseStyles( layerElem );
    498            ScaleHint scaleHint = parseScaleHint( layerElem );
    499            AbstractDataSource[] ds = parseDataSources( layerElem, name );
    500
    501            Layer layer = new Layer( queryable, cascaded, opaque, noSubsets, fixedWidth, fixedHeight, name, title,
    502                                     layerAbstract, llBoundingBox, attribution, scaleHint, keywords, srs, bboxes,
    503                                     dimensions, extents, authorityURLs, identifiers, metadataURLs, dataURLs,
    504                                     featureListURLs, styles, null, ds, parent );
    505
    506            // get Child layers
    507            nl = XMLTools.getNodes( layerElem, "./Layer", nsContext );
    508            Layer[] layers = new Layer[nl.size()];
    509            for ( int i = 0; i < layers.length; i++ ) {
    510                layers[i] = parseLayers( (Element) nl.get( i ), layer );
    511            }
    512            // set child layers
    513            layer.setLayer( layers );
    514
    515            return layer;
    516        }
    517
    518        /**
    519         * Parse the Datasources element
    520         *
    521         * @param layerElem
    522         * @param layerName
    523         * @return AbstractDataSource[]
    524         * @throws XMLParsingException
    525         */
    526        private AbstractDataSource[] parseDataSources( Element layerElem, String layerName )
    527                                throws XMLParsingException {
    528
    529            List<Node> nl = XMLTools.getNodes( layerElem, "./deegreewmps:DataSource", nsContext );
    530
    531            AbstractDataSource[] ds = new AbstractDataSource[nl.size()];
    532            for ( int i = 0; i < ds.length; i++ ) {
    533
    534                boolean failOnEx = XMLTools.getNodeAsBoolean( (Node) nl.get( i ), "./@failOnException", nsContext, true );
    535
    536                boolean queryable = XMLTools.getNodeAsBoolean( (Node) nl.get( i ), "./@queryable", nsContext, false );
    537
    538                QualifiedName name = XMLTools.getNodeAsQualifiedName( (Node) nl.get( i ), "./deegreewmps:Name/text()",
    539                                                                      nsContext, new QualifiedName( layerName ) );
    540
    541                String stype = XMLTools.getRequiredNodeAsString( (Node) nl.get( i ), "./deegreewmps:Type", nsContext );
    542
    543                String xPath = "./deegreewmps:OWSCapabilities/deegreewmps:OnlineResource";
    544                Node node = XMLTools.getRequiredNode( (Node) nl.get( i ), xPath, nsContext );
    545
    546                URL url = parseOnLineResource( (Element) node ).getLinkage().getHref();
    547                ScaleHint scaleHint = parseScaleHint( (Node) nl.get( i ) );
    548                Geometry validArea = parseValidArea( (Node) nl.get( i ) );
    549
    550                try {
    551                    if ( "LOCALWFS".equals( stype ) ) {
    552                        ds[i] = createLocalWFSDataSource( (Node) nl.get( i ), failOnEx, queryable, name, url, scaleHint,
    553                                                          validArea, 60 );
    554                    } else if ( "LOCALWCS".equals( stype ) ) {
    555                        ds[i] = createLocalWCSDataSource( (Node) nl.get( i ), failOnEx, queryable, name, url, scaleHint,
    556                                                          validArea, 60 );
    557                    } else if ( "REMOTEWFS".equals( stype ) ) {
    558                        ds[i] = createRemoteWFSDataSource( (Node) nl.get( i ), failOnEx, queryable, name, url, scaleHint,
    559                                                           validArea, 60 );
    560                    } else if ( "REMOTEWCS".equals( stype ) ) {
    561                        ds[i] = createRemoteWCSDataSource( (Node) nl.get( i ), failOnEx, queryable, name, url, scaleHint,
    562                                                           validArea, 60 );
    563                    } else if ( "REMOTEWMS".equals( stype ) ) {
    564                        ds[i] = createRemoteWMSDataSource( (Node) nl.get( i ), failOnEx, queryable, name, url, scaleHint,
    565                                                           validArea, 60 );
    566                    } else {
    567                        throw new XMLParsingException( "invalid DataSource type: " + stype + " defined in deegree WMS "
    568                                                       + "configuration for DataSource: " + name );
    569                    }
    570                } catch ( Exception e ) {
    571                    throw new XMLParsingException( "could not create service instance for " + "WMPS datasource: " + name, e );
    572                }
    573            }
    574
    575            return ds;
    576        }
    577
    578        /**
    579         * returns the area a data source is valid. If the optional element <ValidArea>is not defined in the configuration
    580         * returns <code>null</code>.
    581         *
    582         * @param node
    583         * @return Geometry
    584         * @throws XMLParsingException
    585         */
    586        private Geometry parseValidArea( Node node )
    587                                throws XMLParsingException {
    588
    589            Geometry geom = null;
    590            List<Node> nl = XMLTools.getNodes( node, "./deegreewmps:ValidArea/*", nsContext );
    591            if ( node != null ) {
    592                try {
    593                    for ( int i = 0; i < nl.size(); i++ ) {
    594                        if ( URI.create( nl.get( 0 ).getNamespaceURI() ).equals( GMLNS ) ) {
    595                            geom = GMLGeometryAdapter.wrap( (Element) nl.get( 0 ), null );
    596                            break;
    597                        }
    598                    }
    599                } catch ( GeometryException e ) {
    600                    throw new XMLParsingException( "couldn't parse/create valid area of a " + "datasource", e );
    601                }
    602            }
    603
    604            return geom;
    605        }
    606
    607        /**
    608         * Create a RemoteWMS datasource.
    609         *
    610         * @param node
    611         * @param failOnEx
    612         * @param queryable
    613         * @param name
    614         * @param url
    615         * @param scaleHint
    616         * @param validArea
    617         * @param reqTimeLimit
    618         * @return XMLParsingException
    619         * @throws Exception
    620         */
    621        private RemoteWMSDataSource createRemoteWMSDataSource( Node node, boolean failOnEx, boolean queryable,
    622                                                               QualifiedName name, URL url, ScaleHint scaleHint,
    623                                                               Geometry validArea, int reqTimeLimit )
    624                                throws Exception {
    625
    626            int type = AbstractDataSource.REMOTEWMS;
    627            String xPath = "./deegreewmps:FeatureInfoTransformation/deegreewmps:OnlineResource";
    628            Node fitNode = XMLTools.getNode( node, xPath, nsContext );
    629            URL fitURL = null;
    630            if ( fitNode != null ) {
    631                fitURL = parseOnLineResource( (Element) fitNode ).getLinkage().getHref();
    632            }
    633
    634            GetMap getMap = parseWMSFilterCondition( node );
    635            Color[] colors = parseTransparentColors( node );
    636            WMSCapabilities wCapa = null;
    637            try {
    638                if ( capaCache.get( url ) != null ) {
    639                    wCapa = (WMSCapabilities) capaCache.get( url );
    640                } else {
    641                    WMSCapabilitiesDocument doc = WMSCapabilitiesDocumentFactory.getWMSCapabilitiesDocument( url );
    642                    wCapa = (WMSCapabilities) doc.parseCapabilities();
    643                    capaCache.put( url, wCapa );
    644                }
    645            } catch ( Exception e ) {
    646                LOG.logError( "could not connet: " + url, e );
    647            }
    648            OGCWebService ows = new RemoteWMService( wCapa );
    649
    650            // parse added/passed parameter map/list
    651            Node vendor = XMLTools.getNode( node,
    652                                            "deegreewmps:FilterCondition/deegreewmps:VendorspecificParameterDefinition",
    653                                            nsContext );
    654
    655            List<String> passedParameters = parsePassedParameters( vendor );
    656            Map<String, String> addedParameters = parseAddedParameters( vendor );
    657
    658            return new RemoteWMSDataSource( queryable, failOnEx, name, type, ows, url, scaleHint, validArea, getMap,
    659                                            colors, fitURL, reqTimeLimit, passedParameters, addedParameters );
    660        }
    661
    662        /**
    663         * @param vendor
    664         * @return the parsed list
    665         * @throws XMLParsingException
    666         */
    667        private static List<String> parsePassedParameters( Node vendor )
    668                                throws XMLParsingException {
    669            List<String> passedParameters = new ArrayList<String>( 10 );
    670
    671            if ( vendor == null ) {
    672                return passedParameters;
    673            }
    674
    675            List<Node> nl = XMLTools.getNodes( vendor, "deegreewmps:PassedVendorspecificParameter/deegreewmps:Name",
    676                                               nsContext );
    677
    678            for ( Object obj : nl ) {
    679                passedParameters.add( ( (Node) obj ).getTextContent().toUpperCase() );
    680            }
    681
    682            return passedParameters;
    683        }
    684
    685        /**
    686         * @param vendor
    687         * @return the parsed map
    688         * @throws XMLParsingException
    689         */
    690        private static Map<String, String> parseAddedParameters( Node vendor )
    691                                throws XMLParsingException {
    692            Map<String, String> addedParameters = new HashMap<String, String>( 10 );
    693
    694            if ( vendor == null ) {
    695                return addedParameters;
    696            }
    697
    698            List<Node> nl = XMLTools.getNodes(
    699                                               vendor,
    700                                               "deegreewmps:AddedVendorspecificParameter/deegreewmps:VendorspecificParameter",
    701                                               nsContext );
    702
    703            for ( Object obj : nl ) {
    704                String pName = XMLTools.getRequiredNodeAsString( (Node) obj, "deegreewmps:Name", nsContext );
    705                String pValue = XMLTools.getRequiredNodeAsString( (Node) obj, "deegreewmps:Value", nsContext );
    706
    707                addedParameters.put( pName, pValue );
    708            }
    709            return addedParameters;
    710        }
    711
    712        /**
    713         * Create a Remote WFS datasource.
    714         *
    715         * @param node
    716         * @param failOnEx
    717         * @param queryable
    718         * @param name
    719         * @param url
    720         * @param scaleHint
    721         * @param validArea
    722         * @param reqTimeLimit
    723         * @return RemoteWFSDataSource
    724         * @throws Exception
    725         */
    726        private RemoteWFSDataSource createRemoteWFSDataSource( Node node, boolean failOnEx, boolean queryable,
    727                                                               QualifiedName name, URL url, ScaleHint scaleHint,
    728                                                               Geometry validArea, int reqTimeLimit )
    729                                throws Exception {
    730
    731            int type = AbstractDataSource.REMOTEWFS;
    732            String xPath = "./deegreewmps:FeatureInfoTransformation/deegreewmps:OnlineResource";
    733            Node fitNode = XMLTools.getNode( node, xPath, nsContext );
    734            URL fitURL = null;
    735            if ( fitNode != null ) {
    736                fitURL = parseOnLineResource( (Element) fitNode ).getLinkage().getHref();
    737            }
    738            Query query = parseWFSFilterCondition( node );
    739
    740            WFSCapabilities wfsCapa = null;
    741            if ( capaCache.get( url ) != null ) {
    742                wfsCapa = (WFSCapabilities) capaCache.get( url );
    743            } else {
    744                WFSCapabilitiesDocument wfsDoc = new WFSCapabilitiesDocument();
    745                wfsDoc.load( url );
    746                wfsCapa = (WFSCapabilities) wfsDoc.parseCapabilities();
    747                capaCache.put( url, wfsCapa );
    748            }
    749            // OGCWebService ows = new RemoteWFService( wfsCapa );
    750            OGCWebService ows = null;
    751            xPath = "./deegreewmps:GeometryProperty/text()";
    752            QualifiedName geom = new QualifiedName( "app", "GEOM", new URI( "http://www.deegree.org/app" ) );
    753            QualifiedName geoProp = XMLTools.getNodeAsQualifiedName( node, xPath, nsContext, geom );
    754
    755            return new RemoteWFSDataSource( queryable, failOnEx, name, type, geoProp, ows, url, scaleHint, validArea,
    756                                            query, fitURL, reqTimeLimit );
    757
    758        }
    759
    760        /**
    761         * Create a Local WCS Datasource
    762         *
    763         * @param node
    764         * @param failOnEx
    765         * @param queryable
    766         * @param name
    767         * @param url
    768         * @param scaleHint
    769         * @param validArea
    770         * @param reqTimeLimit
    771         * @return RemoteWFSDataSource
    772         * @throws Exception
    773         */
    774        private LocalWCSDataSource createLocalWCSDataSource( Node node, boolean failOnEx, boolean queryable,
    775                                                             QualifiedName name, URL url, ScaleHint scaleHint,
    776                                                             Geometry validArea, int reqTimeLimit )
    777                                throws Exception {
    778
    779            int type = AbstractDataSource.LOCALWCS;
    780            GetCoverage getCoverage = parseWCSFilterCondition( node );
    781            Color[] colors = parseTransparentColors( node );
    782            WCSConfiguration configuration = null;
    783            if ( capaCache.get( url ) != null ) {
    784                configuration = (WCSConfiguration) capaCache.get( url );
    785            } else {
    786                configuration = WCSConfiguration.create( url );
    787                capaCache.put( url, configuration );
    788            }
    789
    790            OGCWebService ows = new WCService( configuration );
    791
    792            return new LocalWCSDataSource( queryable, failOnEx, name, type, ows, url, scaleHint, validArea, getCoverage,
    793                                           colors, reqTimeLimit );
    794        }
    795
    796        /**
    797         *
    798         * @param node
    799         * @param failOnEx
    800         * @param queryable
    801         * @param name
    802         * @param url
    803         * @param scaleHint
    804         * @param validArea
    805         * @param i
    806         * @return
    807         * @throws XMLParsingException
    808         * @throws SAXException
    809         * @throws IOException
    810         * @throws org.deegree.ogcwebservices.wcs.configuration.InvalidConfigurationException
    811         * @throws InvalidCapabilitiesException
    812         */
    813        private AbstractDataSource createRemoteWCSDataSource( Node node, boolean failOnEx, boolean queryable,
    814                                                              QualifiedName name, URL url, ScaleHint scaleHint,
    815                                                              Geometry validArea, int reqTimeLimit )
    816                                throws XMLParsingException, InvalidCapabilitiesException, IOException, SAXException {
    817            int type = AbstractDataSource.REMOTEWCS;
    818
    819            GetCoverage getCoverage = parseWCSFilterCondition( node );
    820            Color[] colors = parseTransparentColors( node );
    821            WCSCapabilities capabilities = null;
    822            if ( capaCache.get( url ) != null ) {
    823                capabilities = (WCSCapabilities) capaCache.get( url );
    824            } else {
    825                WCSCapabilitiesDocument doc = new WCSCapabilitiesDocument();
    826                doc.load( url );
    827                capabilities = (WCSCapabilities) doc.parseCapabilities();
    828                capaCache.put( url, capabilities );
    829            }
    830
    831            OGCWebService ows = new RemoteWCService( capabilities );
    832
    833            return new RemoteWCSDataSource( queryable, failOnEx, name, type, ows, url, scaleHint, validArea, getCoverage,
    834                                            colors, reqTimeLimit );
    835
    836        }
    837
    838        /**
    839         * Create a Local WFS Datasource
    840         *
    841         * @param node
    842         * @param failOnEx
    843         * @param queryable
    844         * @param name
    845         * @param url
    846         * @param scaleHint
    847         * @param validArea
    848         * @param reqTimeLimit
    849         * @return LocalWFSDataSource
    850         * @throws Exception
    851         */
    852        private LocalWFSDataSource createLocalWFSDataSource( Node node, boolean failOnEx, boolean queryable,
    853                                                             QualifiedName name, URL url, ScaleHint scaleHint,
    854                                                             Geometry validArea, int reqTimeLimit )
    855                                throws Exception {
    856
    857            int type = AbstractDataSource.LOCALWFS;
    858            String xPath = null;
    859            Query query = parseWFSFilterCondition( node );
    860            WFSConfiguration wfsCapa = null;
    861            if ( capaCache.get( url ) != null ) {
    862                wfsCapa = (WFSConfiguration) capaCache.get( url );
    863            } else {
    864                WFSConfigurationDocument wfsDoc = new WFSConfigurationDocument();
    865                wfsDoc.load( url );
    866                wfsCapa = wfsDoc.getConfiguration();
    867                capaCache.put( url, wfsCapa );
    868            }
    869            // OGCWebService ows = WFServiceFactory.getUncachedService( wfsCapa );
    870            OGCWebService ows = WFServiceFactory.createInstance( wfsCapa );
    871
    872            QualifiedName geom = new QualifiedName( "app", "GEOM", new URI( "http://www.deegree.org/app" ) );
    873            xPath = "./deegreewmps:GeometryProperty/text()";
    874            QualifiedName geoProp = XMLTools.getNodeAsQualifiedName( node, xPath, nsContext, geom );
    875
    876            return new LocalWFSDataSource( queryable, failOnEx, name, type, geoProp, ows, url, scaleHint, validArea, query,
    877                                           null, reqTimeLimit );
    878        }
    879
    880        /**
    881         * Parse trasparent colors.
    882         *
    883         * @param node
    884         * @return Color[]
    885         * @throws XMLParsingException
    886         */
    887        private Color[] parseTransparentColors( Node node )
    888                                throws XMLParsingException {
    889
    890            String xPath = "./deegreewmps:TransparentColors/deegreewmps:Color";
    891            List<Node> clnl = XMLTools.getNodes( node, xPath, nsContext );
    892            Color[] colors = new Color[clnl.size()];
    893            for ( int i = 0; i < colors.length; i++ ) {
    894                colors[i] = Color.decode( XMLTools.getStringValue( (Node) clnl.get( i ) ) );
    895            }
    896
    897            return colors;
    898        }
    899
    900        /**
    901         * Parse Scale hint
    902         *
    903         * @param node
    904         * @return ScaleHint
    905         * @throws XMLParsingException
    906         */
    907        private ScaleHint parseScaleHint( Node node )
    908                                throws XMLParsingException {
    909
    910            String xPath = "./deegreewmps:ScaleHint/@min";
    911            double minScale = XMLTools.getNodeAsDouble( node, xPath, nsContext, 0 );
    912            xPath = "./deegreewmps:ScaleHint/@max";
    913            double maxScale = XMLTools.getNodeAsDouble( node, xPath, nsContext, Double.MAX_VALUE );
    914
    915            return new ScaleHint( minScale, maxScale );
    916        }
    917
    918        /**
    919         * Parse WFS Filter condition
    920         *
    921         * @param node
    922         * @return Query
    923         * @throws XMLParsingException
    924         */
    925        private Query parseWFSFilterCondition( Node node )
    926                                throws XMLParsingException {
    927
    928            Query query = null;
    929            String xPath = "./deegreewmps:FilterCondition/wfs:Query";
    930            Node queryNode = XMLTools.getNode( node, xPath, nsContext );
    931            if ( queryNode != null ) {
    932                try {
    933                    query = Query.create( (Element) queryNode );
    934                } catch ( Exception e ) {
    935                    throw new XMLParsingException( StringTools.stackTraceToString( e ) );
    936                }
    937            }
    938
    939            return query;
    940        }
    941
    942        /**
    943         * Parse WCS Filter Condition
    944         *
    945         * @param node
    946         * @return GetCoverage
    947         * @throws XMLParsingException
    948         */
    949        private GetCoverage parseWCSFilterCondition( Node node )
    950                                throws XMLParsingException {
    951
    952            GetCoverage getCoverage = null;
    953
    954            String id = "" + IDGenerator.getInstance().generateUniqueID();
    955
    956            StringBuffer sd = new StringBuffer( 1000 );
    957            sd.append( "version=1.0.0&Coverage=%default%&" );
    958            sd.append( "CRS=EPSG:4326&BBOX=0,0,1,1&Width=1" );
    959            sd.append( "&Height=1&Format=%default%&" );
    960            String xPath = "./deegreewmps:FilterCondition/deegreewmps:WCSRequest";
    961            String s = XMLTools.getNodeAsString( node, xPath, nsContext, "" );
    962            sd.append( s );
    963            try {
    964                getCoverage = GetCoverage.create( id, sd.toString() );
    965            } catch ( Exception e ) {
    966                throw new XMLParsingException( "could not create GetCoverage from WMS " + "FilterCondition", e );
    967            }
    968
    969            return getCoverage;
    970        }
    971
    972        /**
    973         * Returns a GetMap instance , parsing the WMS Filter condition
    974         *
    975         * @param node
    976         * @return GetMap
    977         * @throws XMLParsingException
    978         */
    979        private GetMap parseWMSFilterCondition( Node node )
    980                                throws XMLParsingException {
    981
    982            GetMap getMap = null;
    983
    984            String id = "" + IDGenerator.getInstance().generateUniqueID();
    985
    986            StringBuffer sd = new StringBuffer( 1000 );
    987            sd.append( "REQUEST=GetMap&LAYERS=%default%&" );
    988            sd.append( "STYLES=&SRS=EPSG:4326&BBOX=0,0,1,1&WIDTH=1&" );
    989            sd.append( "HEIGHT=1&FORMAT=%default%" );
    990            Map<String, String> map1 = KVP2Map.toMap( sd.toString() );
    991            String xPath = "./deegreewmps:FilterCondition/deegreewmps:WMSRequest";
    992            String s = XMLTools.getRequiredNodeAsString( node, xPath, nsContext );
    993            Map<String, String> map2 = KVP2Map.toMap( s );
    994            if ( map2.get( "VERSION" ) == null && map2.get( "WMTVER" ) == null ) {
    995                map2.put( "VERSION", "1.1.1" );
    996            }
    997            // if no service is set use WMS as default
    998            if ( map2.get( "SERVICE" ) == null ) {
    999                map2.put( "SERVICE", "WMS" );
    1000            }
    1001            map1.putAll( map2 );
    1002            try {
    1003                map1.put( "ID", id );
    1004                getMap = GetMap.create( map1 );
    1005            } catch ( Exception e ) {
    1006                throw new XMLParsingException( "could not create GetCoverage from WMS " + "FilterCondition", e );
    1007            }
    1008
    1009            return getMap;
    1010        }
    1011
    1012        /**
    1013         * Returns a list of Style elements, parsing the style element
    1014         *
    1015         * @param layerElem
    1016         * @return Style[]
    1017         * @throws XMLParsingException
    1018         */
    1019        @Override
    1020        protected Style[] parseStyles( Element layerElem )
    1021                                throws XMLParsingException {
    1022
    1023            List<Node> nl = XMLTools.getNodes( layerElem, "./Style", nsContext );
    1024            Style[] styles = new Style[nl.size()];
    1025            for ( int i = 0; i < styles.length; i++ ) {
    1026                String name = XMLTools.getRequiredNodeAsString( nl.get( i ), "./Name", nsContext );
    1027                String title = XMLTools.getNodeAsString( nl.get( i ), "./Title", nsContext, null );
    1028                String styleAbstract = XMLTools.getNodeAsString( nl.get( i ), "./Abstract", nsContext, null );
    1029                LegendURL[] legendURLs = parseLegendURL( nl.get( i ) );
    1030                StyleURL styleURL = parseStyleURL( nl.get( i ) );
    1031                StyleSheetURL styleSheetURL = parseStyleSheetURL( nl.get( i ) );
    1032                String xPath = "deegreewmps:StyleResource";
    1033                String styleResource = XMLTools.getNodeAsString( nl.get( i ), xPath, nsContext, "styles.xml" );
    1034                URL sr = null;
    1035                try {
    1036                    sr = resolve( styleResource );
    1037                } catch ( MalformedURLException e ) {
    1038                    throw new XMLParsingException( "could not parse style resource of " + "style: " + name, e );
    1039                }
    1040                styles[i] = new Style( name, title, styleAbstract, legendURLs, styleSheetURL, styleURL, sr );
    1041            }
    1042
    1043            return styles;
    1044        }
    1045
    1046    }