/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wms.eo;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang.StringUtils;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.CatalogBuilder;
import org.geoserver.catalog.CoverageInfo;
import org.geoserver.catalog.CoverageStoreInfo;
import org.geoserver.catalog.DataStoreInfo;
import org.geoserver.catalog.DimensionInfo;
import org.geoserver.catalog.DimensionPresentation;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.catalog.LayerGroupInfo;
import org.geoserver.catalog.LayerInfo;
import org.geoserver.catalog.NamespaceInfo;
import org.geoserver.catalog.PublishedInfo;
import org.geoserver.catalog.PublishedType;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.catalog.StoreInfo;
import org.geoserver.catalog.StyleInfo;
import org.geoserver.catalog.WorkspaceInfo;
import org.geoserver.catalog.impl.DimensionInfoImpl;
import org.geoserver.catalog.util.ReaderDimensionsAccessor;
import org.geotools.coverage.grid.io.DimensionDescriptor;
import org.geotools.coverage.grid.io.GridCoverage2DReader;
import org.geotools.coverage.grid.io.StructuredGridCoverage2DReader;
import org.geotools.data.DataAccessFactory.Param;
import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFactorySpi;
import org.geotools.data.DataUtilities;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.gce.imagemosaic.Utils;
import org.geotools.jdbc.JDBCDataStoreFactory;
import org.geotools.util.NullProgressListener;
import org.geotools.util.Utilities;
import org.geotools.util.logging.Logging;
/**
* Builder class which provides convenience methods for managing EO stores, resources, layers and
* layer groups.
*
* @author Davide Savazzi - geo-solutions.it
*/
public class EoCatalogBuilder implements EoStyles {
private Catalog catalog;
private static final Logger LOGGER = Logging.getLogger(EoCatalogBuilder.class);
/**
* EoCatalogBuilder constructor
*
* @param catalog
*/
public EoCatalogBuilder(Catalog catalog) {
this.catalog = catalog;
}
/**
* Create an EO Geophysical Parameters layer
*
* @param ws workspace
* @param groupName group name
* @param parametersName Geophysical Parameters name
* @param parametersUrl Geophysical Parameters url
* @return created layer
*/
public LayerInfo createEoParametersLayer(WorkspaceInfo ws, String groupName,
String parametersName, String parametersUrl) {
String parametersLayerName = groupName + "_" + parametersName;
return createEoMosaicLayer(ws, parametersLayerName, EoLayerType.GEOPHYSICAL_PARAMETER,
parametersUrl, false);
}
/**
* Create an EO Bitmasks layer
*
* @param ws workspace
* @param groupName group name
* @param masksName bitmasks name
* @param masksUrl bitmasks url
* @return created layer
*/
public LayerInfo createEoMasksLayer(WorkspaceInfo ws, String groupName, String masksName,
String masksUrl) {
Utilities.ensureNonNull("groupName", groupName);
String masksLayerName = groupName + "_" + masksName;
LayerInfo masksLayer = createEoMosaicLayer(ws, masksLayerName, EoLayerType.BITMASK,
masksUrl, false);
if (masksLayer != null) {
addEoStyles(masksLayer, DEFAULT_BITMASK_STYLE);
}
return masksLayer;
}
public LayerInfo createEoBandsLayer(WorkspaceInfo ws, String groupName, String bandsUrl) {
Utilities.ensureNonNull("groupName", groupName);
String bandsLayerName = groupName + "_BANDS";
return createEoMosaicLayer(ws, bandsLayerName, EoLayerType.BAND_COVERAGE, bandsUrl, true);
}
public LayerInfo createEoBrowseImageLayer(WorkspaceInfo ws, String groupName,
String browseImageUrl) {
/*
* Browse Image layer name must be different from EO group name (otherwise GWC will
* complain) In GetCapabilities this name will not appear
*/
Utilities.ensureNonNull("groupName", groupName);
String browseLayerName = groupName + "_BROWSE";
return createEoMosaicLayer(ws, browseLayerName, EoLayerType.BROWSE_IMAGE, browseImageUrl,
false);
}
/**
* Create an EO layer group
*
* @param ws workspace
* @param groupName group name
* @param groupTitle group title
* @param browseImageUrl Browse Image url
* @param bandsUrl Band Coverage url
* @param masksName Bitmasks name
* @param masksUrl Bitmasks url
* @param parametersName Geophysical Parameters name
* @param parametersUrl Geophysical Parameters url
* @return created group
*/
public LayerGroupInfo createEoLayerGroup(WorkspaceInfo ws, String groupName, String groupTitle,
String browseImageUrl, String bandsUrl, String masksName, String masksUrl,
String parametersName, String parametersUrl) {
LayerInfo bandsLayer = createEoBandsLayer(ws, groupName, bandsUrl);
LayerInfo browseLayer = createEoBrowseImageLayer(ws, groupName, browseImageUrl);
LayerInfo paramsLayer = createEoParametersLayer(ws, groupName, parametersName,
parametersUrl);
LayerInfo masksLayer = createEoMasksLayer(ws, groupName, masksName, masksUrl);
LayerInfo outlineLayer;
try {
StructuredGridCoverage2DReader reader = (StructuredGridCoverage2DReader) ((CoverageInfo) bandsLayer.getResource()).getGridCoverageReader(null, null);
outlineLayer = createEoOutlineLayer(bandsUrl, ws, groupName, null, reader);
} catch (Exception e) {
throw new IllegalArgumentException(
"The Outline layer could not be created. Failure message: " + e.getMessage(), e);
}
// create layer group
LayerGroupInfo layerGroup = catalog.getFactory().createLayerGroup();
layerGroup.setWorkspace(ws);
layerGroup.setName(groupName);
layerGroup.setTitle(groupTitle);
layerGroup.setMode(LayerGroupInfo.Mode.EO);
layerGroup.setRootLayer(browseLayer);
layerGroup.setRootLayerStyle(browseLayer.getDefaultStyle());
layerGroup.getLayers().add(outlineLayer);
layerGroup.getStyles().add(outlineLayer.getDefaultStyle());
layerGroup.getLayers().add(bandsLayer);
layerGroup.getStyles().add(bandsLayer.getDefaultStyle());
if (masksLayer != null) {
layerGroup.getLayers().add(masksLayer);
layerGroup.getStyles().add(masksLayer.getDefaultStyle());
}
if (paramsLayer != null) {
layerGroup.getLayers().add(paramsLayer);
layerGroup.getStyles().add(paramsLayer.getDefaultStyle());
}
try {
CatalogBuilder builder = new CatalogBuilder(catalog);
builder.calculateLayerGroupBounds(layerGroup);
catalog.add(layerGroup);
return layerGroup;
} catch (Exception e) {
throw new IllegalArgumentException("The layer group '" + groupName
+ "' could not be created. Failure message: " + e.getMessage(), e);
}
}
private Properties loadProperties(File propertiesFile) throws IOException {
Properties properties = new Properties();
InputStream inputStream = new BufferedInputStream(new FileInputStream(propertiesFile));
try {
properties.load(inputStream);
} finally {
inputStream.close();
}
return properties;
}
protected DataStoreFactorySpi getOutlineDataStoreFactory(File dir) throws Exception {
File datastorePropertiesFile = new File(dir, "datastore.properties");
if (datastorePropertiesFile.exists()) {
Properties datastoreProperties = loadProperties(datastorePropertiesFile);
String SPIClass = datastoreProperties.getProperty("SPI");
return (DataStoreFactorySpi) Class.forName(SPIClass).newInstance();
} else {
return new ShapefileDataStoreFactory();
}
}
/**
* Get database type from DataStoreFactorySpi
*
* @param dataStoreFactory
* @return database type
*/
protected String getDbType(DataStoreFactorySpi dataStoreFactory) {
String dbType = null;
Param[] params = dataStoreFactory.getParametersInfo();
for (Param param : params) {
if (JDBCDataStoreFactory.DBTYPE.key.equals(param.key)) {
dbType = (String) param.getDefaultValue();
}
}
if (dbType == null) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING, "dbtype parameter not found in dataStoreFactory "
+ dataStoreFactory + ", using default.");
}
}
return dbType;
}
/**
* Create Outline store parameters
*
* @param dir mosaic directory
* @param dataStoreFactory
* @return parameters
* @throws IOException
*/
protected Map<String, Serializable> getOutlineDataStoreParameters(File dir,
DataStoreFactorySpi dataStoreFactory) throws IOException {
File datastorePropertiesFile = new File(dir, "datastore.properties");
if (datastorePropertiesFile.exists()) {
Properties datastoreProperties = loadProperties(datastorePropertiesFile);
Map<String, Serializable> params = Utils.createDataStoreParamsFromPropertiesFile(
datastoreProperties, dataStoreFactory);
String dbType = getDbType(dataStoreFactory);
params.put("dbtype", dbType);
if ("h2".equals(dbType)) {
String dbParameter = (String) params.get("database");
// if the reference is relative, we need to build the absolute path
if (!new File(dbParameter).isAbsolute()) {
File actualPath = new File(dir, dbParameter).getCanonicalFile();
params.put("database", actualPath.getAbsolutePath());
}
}
return params;
} else {
// shp store
File shpFile = new File(dir, dir.getName() + ".shp");
Map<String, Serializable> params = new HashMap<String, Serializable>();
// TODO is there a better way to convert a path to a URL?
// DataUtilities.fileToURL(file) doesn't work (GeoServer saves an empty url)
params.put(ShapefileDataStoreFactory.URLP.key, "file://" + shpFile.getAbsolutePath());
params.put(ShapefileDataStoreFactory.MEMORY_MAPPED.key, true);
// TODO other params?
// params.put(ShapefileDataStoreFactory.DBFTIMEZONE.key, Utils.UTC_TIME_ZONE);
return params;
}
}
/**
* Create EO Outline layer
*/
public LayerInfo createEoOutlineLayer(String url, WorkspaceInfo ws, String groupName,
String coverageName, StructuredGridCoverage2DReader reader) throws Exception {
File dir = DataUtilities.urlToFile(new URL(url));
if (ws == null) {
ws = catalog.getDefaultWorkspace();
}
// store creation from bands directory
String storeName = dir.getName();
String layerName = groupName + "_outlines";
CatalogBuilder builder = new CatalogBuilder(catalog);
DataStoreInfo store = null;
FeatureTypeInfo featureType = null;
LayerInfo layer = null;
boolean success = false;
try {
store = builder.buildDataStore(layerName);
DataStoreFactorySpi dataStoreFactory = getOutlineDataStoreFactory(dir);
Map<String, Serializable> parameters = getOutlineDataStoreParameters(dir,
dataStoreFactory);
NamespaceInfo ns = catalog.getNamespaceByPrefix(ws.getName());
parameters.put("namespace", ns.getURI());
store.setType(dataStoreFactory.getDisplayName());
store.setWorkspace(ws);
store.getConnectionParameters().putAll(parameters);
catalog.add(store);
builder.setStore(store);
// featuretyepinfo and layerinfo
DataStore dataStore = (DataStore) store.getDataStore(new NullProgressListener());
String featureTypeName = coverageName != null ? coverageName : storeName;
SimpleFeatureSource featureSource = dataStore.getFeatureSource(featureTypeName);
featureType = builder.buildFeatureType(featureSource);
featureType.setName(layerName);
featureType.setTitle(layerName);
builder.setupBounds(featureType, featureSource);
// dimensions
boolean foundTime = enableDimensions(featureType, coverageName, reader);
if (!foundTime) {
throw new IllegalArgumentException(
"Unable to enable TIME dimension on outline layer:" + layerName);
}
catalog.add(featureType);
// layer
layer = builder.buildLayer(featureType);
layer.setName(layerName);
layer.setTitle(layerName);
layer.setEnabled(true);
layer.setQueryable(true);
layer.setType(PublishedType.VECTOR);
layer.getMetadata().put(EoLayerType.KEY, EoLayerType.COVERAGE_OUTLINE.name());
addEoStyles(layer, DEFAULT_OUTLINE_STYLE);
catalog.add(layer);
success = true;
return layer;
} finally {
// poor excuse for a rollback, but better than nothing
if (!success) {
if (layer != null) {
catalog.remove(layer);
}
if (featureType != null) {
catalog.remove(featureType);
}
if (store != null) {
catalog.remove(store);
}
}
}
}
/**
* Add EO styles to layer
*
* @param layer
* @param defaultStyleName
*/
private void addEoStyles(LayerInfo layer, String defaultStyleName) {
StyleInfo defaultStyle = catalog.getStyleByName(defaultStyleName);
if (defaultStyle != null) {
layer.setDefaultStyle(defaultStyle);
} else {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING, "EO Style not found: " + defaultStyleName);
}
}
for (String styleName : EO_STYLE_NAMES) {
StyleInfo style = catalog.getStyleByName(styleName);
if (style != null) {
layer.getStyles().add(style);
} else {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING, "EO Style not found: " + styleName);
}
}
}
}
/**
* Create a new mosaic store
*
* @param ws workspace
* @param name store name
* @param url
* @return created store
*/
protected CoverageStoreInfo createEoMosaicStore(WorkspaceInfo ws, String name, String url) {
CoverageStoreInfo storeInfo = catalog.getFactory().createCoverageStore();
storeInfo.setWorkspace(ws);
storeInfo.setType("ImageMosaic");
storeInfo.setEnabled(true);
storeInfo.setName(name);
storeInfo.setURL(url);
try {
catalog.add(storeInfo);
return storeInfo;
} catch (RuntimeException e) {
String msg = "The coverage store '" + name
+ "' could not be created. Failure message: " + e.getMessage();
if (LOGGER.isLoggable(Level.INFO)) {
LOGGER.log(Level.INFO, msg, e);
}
throw new IllegalArgumentException(msg, e);
}
}
/**
* Create a new mosaic layer
*
* @param ws workspace
* @param name store name and layer name
* @param type EO layer type
* @param url mosaic url
* @param checkDimensions check time and at least another dimension is present
* @return created layer
*/
public LayerInfo createEoMosaicLayer(WorkspaceInfo ws, String name, EoLayerType type,
String url, boolean checkDimensions) {
if (StringUtils.isEmpty(url)) {
return null;
}
CoverageStoreInfo store = createEoMosaicStore(ws, name, url);
CatalogBuilder builder = new CatalogBuilder(catalog);
builder.setStore(store);
try {
CoverageInfo resource = builder.buildCoverage();
boolean dimensionsPresent = enableDimensions(resource);
if (checkDimensions) {
if (!dimensionsPresent) {
// rollback: delete store
catalog.remove(store);
throw new IllegalArgumentException("The layer '" + name
+ "' could not be created: no dimensions found");
}
}
resource.setName(name);
resource.setTitle(name);
catalog.add(resource);
LayerInfo layer = builder.buildLayer(resource);
layer.setName(name);
layer.setTitle(name);
layer.setEnabled(true);
layer.setQueryable(true);
layer.setType(PublishedType.RASTER);
layer.getMetadata().put(EoLayerType.KEY, type.name());
catalog.add(layer);
return layer;
} catch (Exception e) {
throw new IllegalArgumentException("The layer '" + name
+ "' could not be created. Failure message: " + e.getMessage(), e);
}
}
/**
* Check presence of TIME dimension and at least one custom dimension. Enable all dimensions
* found.
*/
private boolean enableDimensions(CoverageInfo ci) {
boolean timeDimension = false;
boolean customDimension = false;
GridCoverage2DReader reader = null;
try {
// acquire a reader
reader = (GridCoverage2DReader) ci.getGridCoverageReader(null, null);
if (reader == null) {
throw new RuntimeException("Unable to acquire reader for this coverageinfo: "
+ ci.getName());
}
// inspect dimensions
final ReaderDimensionsAccessor ra = new ReaderDimensionsAccessor(reader);
for (String domain : ra.getCustomDomains()) {
if (LOGGER.isLoggable(Level.FINE)) {
boolean hasRange = ra.hasRange(domain);
boolean hasResolution = ra.hasResolution(domain);
LOGGER.fine(ci.getName() + ": found " + domain + " dimension (hasRange: "
+ hasRange + ", hasResolution: " + hasResolution + ")");
}
DimensionInfo dimension = new DimensionInfoImpl();
dimension.setEnabled(true);
dimension.setPresentation(DimensionPresentation.LIST);
ci.getMetadata().put(ResourceInfo.CUSTOM_DIMENSION_PREFIX + domain, dimension);
customDimension = true;
}
String elev = reader.getMetadataValue(GridCoverage2DReader.HAS_ELEVATION_DOMAIN);
if (Boolean.parseBoolean(elev)) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(ci.getName() + ": found ELEVATION dimension");
}
DimensionInfo dimension = new DimensionInfoImpl();
dimension.setEnabled(true);
dimension.setPresentation(DimensionPresentation.LIST);
ci.getMetadata().put(ResourceInfo.ELEVATION, dimension);
customDimension = true;
}
String time = reader.getMetadataValue(GridCoverage2DReader.HAS_TIME_DOMAIN);
if (Boolean.parseBoolean(time)) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(ci.getName() + ": found TIME dimension");
}
DimensionInfo dimension = new DimensionInfoImpl();
dimension.setEnabled(true);
dimension.setPresentation(DimensionPresentation.LIST);
ci.getMetadata().put(ResourceInfo.TIME, dimension);
timeDimension = true;
}
} catch (IOException e) {
if (LOGGER.isLoggable(Level.SEVERE)) {
LOGGER.log(Level.SEVERE, "Failed to access coverage reader custom dimensions", e);
}
}
return timeDimension && customDimension;
}
/**
* Delete a layer, its resource and its store
*
* @param layer
*/
private void delete(LayerInfo layer) {
ResourceInfo resource = layer.getResource();
StoreInfo store = resource.getStore();
catalog.remove(layer);
catalog.remove(resource);
catalog.remove(store);
}
/**
* Delete a layer group, all its layers and their respective stores
*
* @param group
*/
public void delete(LayerGroupInfo group) {
// load layers in group
group = catalog.getLayerGroupByName(group.getWorkspace(), group.getName());
try {
catalog.remove(group);
delete(group.getRootLayer());
for (PublishedInfo p : group.getLayers()) {
if (p instanceof LayerGroupInfo) {
delete(group);
} else {
delete((LayerInfo) p);
}
}
} catch (RuntimeException e) {
throw new IllegalArgumentException("The group '" + group.getName()
+ "' could not be removed. Failure message: " + e.getMessage(), e);
}
}
/**
* Check presence of TIME dimension . Enable all dimensions found.
* @throws IOException
*/
private boolean enableDimensions(FeatureTypeInfo fi, String coverageName, StructuredGridCoverage2DReader reader) throws IOException {
Utilities.ensureNonNull("FeatureTypeInfo", fi);
Utilities.ensureNonNull("reader", reader);
List<DimensionDescriptor> dimensionDescriptors = reader.getDimensionDescriptors(coverageName == null ? reader.getGridCoverageNames()[0] : coverageName);
boolean timeDimension = false;
for (DimensionDescriptor dd : dimensionDescriptors) {
DimensionInfo di = new DimensionInfoImpl();
String key;
String units = dd.getUnits();
String symbol = dd.getUnitSymbol();
if(ResourceInfo.TIME.equalsIgnoreCase(dd.getName())) {
timeDimension = true;
key = ResourceInfo.TIME;
units = DimensionInfo.TIME_UNITS;
} else if(ResourceInfo.ELEVATION.equalsIgnoreCase(dd.getName())) {
key = ResourceInfo.ELEVATION;
units = DimensionInfo.ELEVATION_UNITS;
symbol = DimensionInfo.ELEVATION_UNIT_SYMBOL;
} else {
key = ResourceInfo.CUSTOM_DIMENSION_PREFIX + dd.getName();
}
di.setEnabled(true);
di.setAttribute(dd.getStartAttribute());
di.setEndAttribute(dd.getEndAttribute());
di.setPresentation(DimensionPresentation.LIST);
di.setUnits(units);
di.setUnitSymbol(symbol);
fi.getMetadata().put(key, di);
}
return timeDimension;
}
}