/*
* Copyright (c) 2010 The University of Reading
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University of Reading, nor the names of the
* authors or contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package thredds.server.wms.config;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.xpath.XPath;
import thredds.catalog.XMLEntityResolver;
import thredds.server.wms.ThreddsLayer;
import ucar.nc2.units.SimpleUnit;
import uk.ac.rdg.resc.edal.util.Range;
import uk.ac.rdg.resc.edal.util.Ranges;
/**
* Encapsulates the sysadmin's settings of the detailed configuration of the WMS
* (see wmsConfig.xml).
*/
public class WmsDetailedConfig
{
private LayerSettings defaultSettings;
/** Maps standard names to corresponding default settings */
private Map<String, StandardNameSettings> standardNames = new HashMap<>();
/** Maps dataset paths to corresponding default settings */
private Map<String, DatasetPathSettings> datasetPaths = new HashMap<>();
/** Private constructor to prevent direct instantiation */
private WmsDetailedConfig() {}
/**
* Parses the XML file from the given file.
* @return a new WmsDetailedConfig object, if and only if parsing was successful.
* @throws IOException if there was an io error reading from the file.
*/
public static WmsDetailedConfig fromFile(File configFile) throws IOException, WmsConfigException
{
return fromInputStream(new FileInputStream(configFile));
}
/**
* Parses the XML file from the given input stream, then closes the input stream.
* @return a new WmsDetailedConfig object, if and only if parsing was successful.
* @throws IOException if there was an io error reading from the input stream
*/
public static WmsDetailedConfig fromInputStream(InputStream in) throws IOException, WmsConfigException
{
WmsDetailedConfig wmsConfig = new WmsDetailedConfig();
try
{
// Parse the document, with validation
XMLEntityResolver.initEntity("http://www.unidata.ucar.edu/schemas/thredds/dtd/ncwms/wmsConfig.dtd",
"/resources/thredds/schemas/wmsConfig.dtd",
"http://www.unidata.ucar.edu/schemas/thredds/dtd/ncwms/wmsConfig.dtd");
Document doc = new XMLEntityResolver(true).getSAXBuilder().build(in);
in.close();
// Load the global default settings
Element defaultSettingsEl =
(Element)XPath.selectSingleNode(doc, "/wmsConfig/global/defaults");
// We don't have to check for a null return value since we validated
// the document against the DTD upon reading. Similarly we know that
// all the default settings are non-null: null values would have caused
// a validation error
wmsConfig.defaultSettings = new LayerSettings(defaultSettingsEl);
// Load the overrides for specific standard names
@SuppressWarnings("unchecked")
List<Element> standardNamesList =
(List<Element>)XPath.selectNodes(doc, "/wmsConfig/global/standardNames/standardName");
for (Element standardNameEl : standardNamesList)
{
StandardNameSettings sns = new StandardNameSettings(standardNameEl);
wmsConfig.standardNames.put(sns.getStandardName(), sns);
}
// Load the overrides for specific dataset paths
@SuppressWarnings("unchecked")
List<Element> datasetPathsList =
(List<Element>)XPath.selectNodes(doc, "/wmsConfig/overrides/datasetPath");
for (Element datasetPathEl : datasetPathsList)
{
DatasetPathSettings pathSettings = new DatasetPathSettings(datasetPathEl);
wmsConfig.datasetPaths.put(pathSettings.getPathSpec(), pathSettings);
}
}
catch(JDOMException jdome)
{
throw new WmsConfigException(jdome);
}
return wmsConfig;
}
/**
* Gets the settings for the given {@link ThreddsLayer}. None of the fields
* will be null in the returned object.
*/
public LayerSettings getSettings(ThreddsLayer layer)
{
LayerSettings settings = new LayerSettings();
// See if there are specific overrides for this layer's dataset
String dsPath = layer.getDataset().getDatasetPath();
DatasetPathSettings dpSettings = this.getBestDatasetPathMatch(dsPath);
if (dpSettings != null)
{
// First we look for the most specific settings, i.e. those for the variable
LayerSettings varSettings = dpSettings.getSettingsPerVariable().get(layer.getId());
if (varSettings != null) settings.replaceNullValues(varSettings);
// Now we look at the default settings for the dataset and use them
// to insert any currently-unset values
LayerSettings pathDefaults = dpSettings.getDefaultSettings();
if (pathDefaults != null) settings.replaceNullValues(pathDefaults);
}
// Now look for any per-standard name defaults
if (layer.getStandardName() != null)
{
StandardNameSettings stdNameSettings = this.standardNames.get(layer.getStandardName());
if (stdNameSettings != null)
{
boolean defaultColorScaleRangeUnset = settings.getDefaultColorScaleRange() == null;
// Set the remaining unset values
settings.replaceNullValues(stdNameSettings.getSettings());
// If the default color scale range was previously unset, we
// must check the units of the new color scale range.
if (defaultColorScaleRangeUnset &&
stdNameSettings.getSettings().getDefaultColorScaleRange() != null)
{
Range<Float> newColorScaleRange = convertUnits(
stdNameSettings.getSettings().getDefaultColorScaleRange(),
stdNameSettings.getUnits(),
layer.getUnits()
);
// If the units are not convertible, we'll set back to null
settings.setDefaultColorScaleRange(newColorScaleRange);
}
}
}
// Use the global defaults to set any remaining unset values
settings.replaceNullValues(this.defaultSettings);
return settings;
}
/**
* Converts the given range of values to a new unit.
* @param floatRange The range of values to convert
* @param oldUnits The units of {@code floatRange}
* @param newUnits The units into which the range is to be converted
* @return a new Range object containing the same values as {@code floatRange}
* but in the new units. If the units are not convertible, this method shall
* return null.
*/
private static Range<Float> convertUnits(Range<Float> floatRange, String oldUnits, String newUnits)
{
SimpleUnit oldUnit = SimpleUnit.factory(oldUnits);
SimpleUnit newUnit = SimpleUnit.factory(newUnits);
if (oldUnit == null || newUnit == null) return null;
try
{
return Ranges.newRange(
(float)oldUnit.convertTo(floatRange.getMinimum(), newUnit),
(float)oldUnit.convertTo(floatRange.getMaximum(), newUnit)
);
}
catch(Exception e)
{
return null;
}
}
/**
* Find the dataset path settings that best match the given url path, or null
* if there is no match. If multiple settings match the url path, an exact
* match will "win". If there is no exact match the longest
* pattern in the config file "wins" (crudely, this is probably the most
* precise match).
*/
private DatasetPathSettings getBestDatasetPathMatch(String urlPath)
{
// First look for an exact match (small optimization)
DatasetPathSettings settings = this.datasetPaths.get(urlPath);
if (settings != null) return settings;
// Now look through all the settings for a pattern match, retaining the
// match with the longest pattern.
int longestPatternMatchLength = 0;
DatasetPathSettings bestMatch = null;
for (DatasetPathSettings dpSettings : this.datasetPaths.values())
{
if (dpSettings.pathSpecMatches(urlPath))
{
if (dpSettings.getPathSpec().length() > longestPatternMatchLength)
{
longestPatternMatchLength = dpSettings.getPathSpec().length();
bestMatch = dpSettings;
}
}
}
return bestMatch;
}
public static void main(String[] args) throws Exception
{
Range<Float> newRange = convertUnits(Ranges.newRange(268.0f, 305.0f), "K", "Celsius");
System.out.println(newRange);
InputStream in = new FileInputStream(
"C:\\Documents and Settings\\Jon\\My Documents\\projects\\THREDDS\\" +
"svn\\tds\\src\\main\\webapp\\WEB-INF\\altContent\\startup\\wmsConfig.xml");
WmsDetailedConfig wmsConfig = WmsDetailedConfig.fromInputStream(in);
System.out.println(wmsConfig.defaultSettings);
System.out.println();
System.out.println("Dataset paths:");
for (Map.Entry<String, DatasetPathSettings> entry : wmsConfig.datasetPaths.entrySet())
{
System.out.println(entry.getKey());
System.out.println(entry.getValue().getDefaultSettings());
for (Map.Entry<String, LayerSettings> varEntry : entry.getValue().getSettingsPerVariable().entrySet())
{
System.out.println("Variable " + varEntry.getKey() + ":");
System.out.println(" " + varEntry.getValue());
}
}
System.out.println();
System.out.println("Standard names:");
for (Map.Entry<String, StandardNameSettings> entry : wmsConfig.standardNames.entrySet())
{
System.out.println(entry.getKey());
System.out.println(entry.getValue().getUnits());
System.out.println(entry.getValue().getSettings());
}
}
}