/* (c) 2017 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geogig.geoserver.wms;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.CatalogException;
import org.geoserver.catalog.CatalogVisitor;
import org.geoserver.catalog.CoverageInfo;
import org.geoserver.catalog.CoverageStoreInfo;
import org.geoserver.catalog.DataStoreInfo;
import org.geoserver.catalog.DimensionInfo;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.catalog.LayerGroupInfo;
import org.geoserver.catalog.LayerInfo;
import org.geoserver.catalog.MetadataMap;
import org.geoserver.catalog.NamespaceInfo;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.catalog.StoreInfo;
import org.geoserver.catalog.StyleInfo;
import org.geoserver.catalog.WMSLayerInfo;
import org.geoserver.catalog.WMSStoreInfo;
import org.geoserver.catalog.WorkspaceInfo;
import org.geotools.util.logging.Logging;
import org.locationtech.geogig.geotools.data.GeoGigDataStore;
import org.locationtech.geogig.geotools.data.GeoGigDataStoreFactory;
import org.locationtech.geogig.model.ObjectId;
import com.google.common.base.Optional;
/**
* CatalogListener to ensure GeoGig indexes are created when Time or Elevation dimensions are enabled on GeoGig layers.
*/
public class GeoGigCatalogVisitor implements CatalogVisitor {
private static final Logger LOGGER = Logging.getLogger(GeoGigCatalogVisitor.class);
private static final ExecutorService INDEX_SERVICE = Executors.newSingleThreadExecutor();
private static final ExecutorService FUTURE_SERVICE = Executors.newFixedThreadPool(4);
private String[] getAttribute(final MetadataMap metadata, final String attributeName) {
if (metadata != null && metadata.containsKey(attributeName)) {
DimensionInfo dimension = metadata.get(attributeName, DimensionInfo.class);
final String attribute = dimension.getAttribute();
final String endAttribute = dimension.getEndAttribute();
return new String[] {attribute, endAttribute};
}
return new String[]{};
}
@Override
public void visit(Catalog catalog) {
// do nothing
}
@Override
public void visit(WorkspaceInfo workspace) {
// do nothing
}
@Override
public void visit(NamespaceInfo workspace) {
// do nothing
}
@Override
public void visit(DataStoreInfo dataStore) {
// do nothing
}
@Override
public void visit(CoverageStoreInfo coverageStore) {
// do nothing
}
@Override
public void visit(WMSStoreInfo wmsStore) {
// do nothing
}
@Override
public void visit(FeatureTypeInfo featureType) {
// do nothing
}
@Override
public void visit(CoverageInfo coverage) {
// do nothing
}
private void createOrUpdateIndex(final GeoGigDataStore dataStore, final String layerName,
final String[] extraAttribues) {
// schedule the index creation
final Future<Optional<ObjectId>> indexId = INDEX_SERVICE.submit(
new Callable<Optional<ObjectId>>() {
@Override
public Optional<ObjectId> call() throws Exception {
return dataStore.createOrUpdateIndex(layerName, extraAttribues);
}
});
// handle the Future and generate an error log if the index create/update fails
FUTURE_SERVICE.submit(new Runnable() {
@Override
public void run() {
try {
Optional<ObjectId> objId = indexId.get();
if (!objId.isPresent()) {
LOGGER.log(Level.WARNING,
"Index creation or update finished, but resulted in no Index on Layer: {0}",
layerName);
}
} catch (ExecutionException eex) {
LOGGER.log(Level.WARNING, "Index could not be created or updated on Layer: " +
layerName, eex);
} catch (InterruptedException iex) {
LOGGER.log(Level.WARNING,
"Index may not have been created or updated on Layer: " +
layerName, iex);
}
}
});
}
@Override
public void visit(LayerInfo layer) {
// get the Resource for this layer
final ResourceInfo resource = layer.getResource();
if (resource != null) {
// get the Store to see if it's a GeoGig store
final StoreInfo store = resource.getStore();
if (GeoGigDataStoreFactory.DISPLAY_NAME.equals(store.getType())) {
// it's a GeoGig dataStore
GeoGigDataStore geogigDataStore = null;
try {
geogigDataStore = GeoGigDataStore.class.cast(
((DataStoreInfo) store).getDataStore(null));
} catch (Exception ex) {
throw new CatalogException("Error accessing GeoGig DataStore", ex);
}
if (!geogigDataStore.getAutoIndexing()) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(String.format(
"GeoGig DataStore is not configured for automatic indexing."));
}
// skip it
return;
}
// get the metadata for the layer resource
final MetadataMap metadata = resource.getMetadata();
final String[] timeAttr = getAttribute(metadata, "time");
final String[] elevationAttr = getAttribute(metadata, "elevation");
String[] dimensions = Stream.concat(Arrays.stream(timeAttr),
Arrays.stream(elevationAttr)).toArray(String[]::new);
// create the indexes
createOrUpdateIndex(geogigDataStore, resource.getName(), dimensions);
}
}
}
@Override
public void visit(StyleInfo style) {
// do nothing
}
@Override
public void visit(LayerGroupInfo layerGroup) {
// do nothing
}
@Override
public void visit(WMSLayerInfo wmsLayer) {
// do nothing
}
}