/* (c) 2016 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 static org.geoserver.catalog.Predicates.and;
import static org.geoserver.catalog.Predicates.equal;
import static org.locationtech.geogig.geotools.data.GeoGigDataStoreFactory.BRANCH;
import static org.locationtech.geogig.geotools.data.GeoGigDataStoreFactory.DISPLAY_NAME;
import static org.locationtech.geogig.geotools.data.GeoGigDataStoreFactory.HEAD;
import static org.locationtech.geogig.geotools.data.GeoGigDataStoreFactory.REPOSITORY;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geogig.geoserver.config.RepositoryInfo;
import org.geogig.geoserver.config.RepositoryManager;
import org.geoserver.catalog.AuthorityURLInfo;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.CatalogException;
import org.geoserver.catalog.CatalogInfo;
import org.geoserver.catalog.DataStoreInfo;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.catalog.LayerIdentifierInfo;
import org.geoserver.catalog.LayerInfo;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.catalog.StoreInfo;
import org.geoserver.catalog.WorkspaceInfo;
import org.geoserver.catalog.event.CatalogAddEvent;
import org.geoserver.catalog.event.CatalogListener;
import org.geoserver.catalog.event.CatalogModifyEvent;
import org.geoserver.catalog.event.CatalogPostModifyEvent;
import org.geoserver.catalog.event.CatalogRemoveEvent;
import org.geoserver.catalog.impl.AuthorityURL;
import org.geoserver.catalog.impl.LayerIdentifier;
import org.geoserver.catalog.util.CloseableIterator;
import org.geoserver.config.GeoServer;
import org.geoserver.wms.WMSInfo;
import org.geotools.data.DataStore;
import org.geotools.util.logging.Logging;
import org.locationtech.geogig.geotools.data.GeoGigDataStoreFactory;
import org.opengis.filter.Filter;
/**
* Ensures a global WMS {@link AuthorityURL} exists with name {@code GEOGIG_ENTRY_POINT} and URL
* {@code http://geogig.org}, and that each {@link LayerInfo layer} from a geogig datastore gets a
* {@link LayerIdentifierInfo} with authority {@code GEOGIG_ENTRY_POINT} and the identifier composed
* of {@code <repositoryId>:<nativeName>[:<branch/head>]}
* <p>
* The identifier is made of the following parts:
* <ul>
* <li> {@code <repositoryId>}: {@link RepositoryInfo#getId() RepositoryInfo ID} that identifies the
* repository referred by the layer's {@link DataStore}. {@link RepositoryInfo}s are managed by
* {@link RepositoryManager} and are the way this plugin supports configuring several datastores
* against the same repository without duplication of information.
* <li> {@code <nativeName>}: the layer's resource {@link ResourceInfo#getNativeName() native name}
* <li> {@code <branch/head>}: the geogig datastore's configured
* {@link GeoGigDataStoreFactory#BRANCH branch} or {@link GeoGigDataStoreFactory#HEAD}, whichever is
* present, or absent if no branch or head is configured (and hence the datastore operates on
* whatever the current HEAD is)
* </ul>
* <p>
* Handles the following events:
* <ul>
* <li> {@link WorkspaceInfo} renamed: all geogig layers of stores in that workspace get their
* authority URL identifiers updated
* <li> {@link DataStoreInfo} renamed: all geogig layers of stores in that workspace get their
* authority URL identifiers updated
* <li> {@link DataStoreInfo} workspace changed: all geogig layers of stores in that workspace get
* their authority URL identifiers updated
* <li> {@link LayerInfo} added: if its a geogig layer, creates its geogig authority URL identifier
* and saves the layer info
* </ul>
*/
public class GeogigLayerIntegrationListener implements CatalogListener {
private static final Logger LOGGER = Logging.getLogger(GeogigLayerIntegrationListener.class);
public static final String AUTHORITY_URL_NAME = "GEOGIG_ENTRY_POINT";
public static final String AUTHORITY_URL = "http://geogig.org";
private final GeoServer geoserver;
private final GeoGigCatalogVisitor visitor = new GeoGigCatalogVisitor();
/**
*/
public GeogigLayerIntegrationListener(GeoServer geoserver) {
LOGGER.log(Level.FINE, "Initialized {0}", getClass().getName());
this.geoserver = geoserver;
this.geoserver.getCatalog().addListener(this);
}
@Override
public void handleAddEvent(CatalogAddEvent event) throws CatalogException {
if (!(event.getSource() instanceof LayerInfo)) {
return;
}
LayerInfo layer = (LayerInfo) event.getSource();
if (!isGeogigLayer(layer)) {
return;
}
event.getSource().accept(visitor);
if (!forceServiceRootLayerHaveGeogigAuthURL()) {
return;
}
setIdentifier(layer);
}
private static final ThreadLocal<CatalogInfo> PRE_MOFIFY_EVENT = new ThreadLocal<CatalogInfo>();
@Override
public void handleModifyEvent(CatalogModifyEvent event) throws CatalogException {
if (PRE_MOFIFY_EVENT.get() != null) {
LOGGER.log(Level.FINE, "pre-modify event exists, ignoring handleModifyEvent ({0})", PRE_MOFIFY_EVENT.get());
return;
}
event.getSource().accept(visitor);
final CatalogInfo source = event.getSource();
final boolean isGeogigStore = isGeogigStore(source);
boolean tryPostUpdate = (source instanceof WorkspaceInfo) || isGeogigStore;
final List<String> propertyNames = event.getPropertyNames();
tryPostUpdate &= propertyNames.contains("name")
|| propertyNames.contains("connectionParameters");
if (tryPostUpdate) {
LOGGER.log(Level.FINE, "Storing event for post-handling on {0}", source);
PRE_MOFIFY_EVENT.set(source);
}
}
@Override
public void handlePostModifyEvent(CatalogPostModifyEvent event) throws CatalogException {
final CatalogInfo preModifySource = PRE_MOFIFY_EVENT.get();
if (preModifySource == null) {
return;
}
if (!event.getSource().getId().equals(preModifySource.getId())) {
return;
}
PRE_MOFIFY_EVENT.remove();
LOGGER.log(Level.FINE, "handing post-modify event for {0}", preModifySource);
event.getSource().accept(visitor);
CatalogInfo source = event.getSource();
if (source instanceof WorkspaceInfo) {
handlePostWorkspaceChange((WorkspaceInfo) source);
} else if (source instanceof DataStoreInfo) {
handlePostGeogigStoreChange((DataStoreInfo) source);
}
}
private void handlePostGeogigStoreChange(DataStoreInfo source) {
Catalog catalog = geoserver.getCatalog();
final String storeId = source.getId();
Filter filter = equal("resource.store.id", storeId);
CloseableIterator<LayerInfo> affectedLayers = catalog.list(LayerInfo.class, filter);
updateLayers(affectedLayers);
}
private void handlePostWorkspaceChange(WorkspaceInfo source) {
Catalog catalog = geoserver.getCatalog();
final String wsId = source.getId();
final String storeType = DISPLAY_NAME;
Filter filter = and(equal("resource.store.workspace.id", wsId),
equal("resource.store.type", storeType));
CloseableIterator<LayerInfo> affectedLayers = catalog.list(LayerInfo.class, filter);
updateLayers(affectedLayers);
}
private void updateLayers(CloseableIterator<LayerInfo> affectedLayers) {
try {
while (affectedLayers.hasNext()) {
LayerInfo geogigLayer = affectedLayers.next();
setIdentifier(geogigLayer);
}
} finally {
affectedLayers.close();
}
}
private boolean forceServiceRootLayerHaveGeogigAuthURL() {
LOGGER.fine("Checking for root layer geogig auth URL");
WMSInfo serviceInfo = geoserver.getService(WMSInfo.class);
if (serviceInfo == null) {
LOGGER.info("No WMSInfo available in GeoServer. This is strange but can happen");
return false;
}
List<AuthorityURLInfo> authorityURLs = serviceInfo.getAuthorityURLs();
for (AuthorityURLInfo urlInfo : authorityURLs) {
if (AUTHORITY_URL_NAME.equals(urlInfo.getName())) {
LOGGER.fine("geogig root layer auth URL already exists");
return true;
}
}
AuthorityURL geogigAuthURL = new AuthorityURL();
geogigAuthURL.setName(AUTHORITY_URL_NAME);
geogigAuthURL.setHref(AUTHORITY_URL);
serviceInfo.getAuthorityURLs().add(geogigAuthURL);
LOGGER.fine("Saving geogig root layer auth URL");
geoserver.save(serviceInfo);
LOGGER.info("geogig root layer auth URL saved");
return true;
}
/**
* Does nothing
*/
@Override
public void handleRemoveEvent(CatalogRemoveEvent event) throws CatalogException {
// do nothing
}
/**
* Does nothing
*/
@Override
public void reloaded() {
// do nothing
}
private void setIdentifier(LayerInfo layer) {
LOGGER.log(Level.FINE, "Updating geogig auth identifier for layer {0}", layer.prefixedName());
final String layerIdentifier = buildLayerIdentifier(layer);
updateIdentifier(layer, layerIdentifier);
}
private void updateIdentifier(LayerInfo geogigLayer, final String newIdentifier) {
List<LayerIdentifierInfo> layerIdentifiers = geogigLayer.getIdentifiers();
{
LayerIdentifierInfo id = null;
for (LayerIdentifierInfo identifier : layerIdentifiers) {
if (AUTHORITY_URL_NAME.equals(identifier.getAuthority())) {
id = identifier;
break;
}
}
if (id != null) {
if (newIdentifier.equals(id.getIdentifier())) {
return;
}
layerIdentifiers.remove(id);
}
}
LayerIdentifier newId = new LayerIdentifier();
newId.setAuthority(AUTHORITY_URL_NAME);
newId.setIdentifier(newIdentifier);
layerIdentifiers.add(newId);
Catalog catalog = geoserver.getCatalog();
catalog.save(geogigLayer);
LOGGER.log(Level.INFO, "Updated geogig auth identifier for layer {0} as {1}",
new Object[]{geogigLayer.prefixedName(), newIdentifier});
}
private String buildLayerIdentifier(LayerInfo geogigLayer) {
FeatureTypeInfo resource = (FeatureTypeInfo) geogigLayer.getResource();
DataStoreInfo store = resource.getStore();
Map<String, Serializable> connectionParameters = store.getConnectionParameters();
final String repositoryId = (String) connectionParameters.get(REPOSITORY.key);
Serializable refSpec = connectionParameters.get(BRANCH.key);
if (refSpec == null) {
refSpec = connectionParameters.get(HEAD.key);
}
StringBuilder identifier = new StringBuilder(repositoryId).append(':').append(
resource.getNativeName());
if (refSpec != null) {
identifier.append(':').append(refSpec);
}
return identifier.toString();
}
private boolean isGeogigLayer(LayerInfo layer) {
ResourceInfo resource = layer.getResource();
StoreInfo store = resource.getStore();
return isGeogigStore(store);
}
private boolean isGeogigStore(CatalogInfo store) {
if (!(store instanceof DataStoreInfo)) {
return false;
}
final String storeType = ((DataStoreInfo) store).getType();
boolean isGeogigLayer = DISPLAY_NAME.equals(storeType);
return isGeogigLayer;
}
}