/* (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.geoserver.cluster.integration;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.CatalogInfo;
import org.geoserver.catalog.CatalogVisitor;
import org.geoserver.catalog.CoverageInfo;
import org.geoserver.catalog.CoverageStoreInfo;
import org.geoserver.catalog.DataStoreInfo;
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.StyleInfo;
import org.geoserver.catalog.WMSLayerInfo;
import org.geoserver.catalog.WMSStoreInfo;
import org.geoserver.catalog.WorkspaceInfo;
import org.geoserver.config.GeoServerDataDirectory;
import org.geoserver.platform.resource.Resource;
import org.geoserver.util.IOUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* Utility visitor that will extract the differences between catalog objects.
* This visitor can be used to fully compare two catalogs.
*/
public final class CatalogDiffVisitor implements CatalogVisitor {
private final Catalog otherCatalog;
private final GeoServerDataDirectory dataDir;
private final GeoServerDataDirectory otherDataDir;
private final List<InfoDiff> differences = new ArrayList<>();
public CatalogDiffVisitor(Catalog otherCatalog, GeoServerDataDirectory dataDir, GeoServerDataDirectory otherDataDir) {
this.otherCatalog = otherCatalog;
this.dataDir = dataDir;
this.otherDataDir = otherDataDir;
}
@Override
public void visit(Catalog catalog) {
listDiffOther(catalog.getWorkspaces(), otherCatalog.getWorkspaces());
listDiffOther(catalog.getNamespaces(), otherCatalog.getNamespaces());
listDiffOther(catalog.getDataStores(), otherCatalog.getDataStores());
listDiffOther(catalog.getCoverageStores(), otherCatalog.getCoverageStores());
listDiffOther(catalog.getStores(WMSStoreInfo.class), otherCatalog.getStores(WMSStoreInfo.class));
listDiffOther(catalog.getFeatureTypes(), otherCatalog.getFeatureTypes());
listDiffOther(catalog.getCoverages(), otherCatalog.getCoverages());
listDiffOther(catalog.getLayers(), otherCatalog.getLayers());
listDiffOther(catalog.getResources(WMSLayerInfo.class), otherCatalog.getResources(WMSLayerInfo.class));
listDiffOther(catalog.getStyles(), otherCatalog.getStyles());
listDiffOther(catalog.getLayerGroups(), otherCatalog.getLayerGroups());
catalog.getWorkspaces().forEach(info -> info.accept(this));
catalog.getNamespaces().forEach(info -> info.accept(this));
catalog.getDataStores().forEach(info -> info.accept(this));
catalog.getCoverageStores().forEach(info -> info.accept(this));
catalog.getStores(WMSStoreInfo.class).forEach(info -> info.accept(this));
catalog.getFeatureTypes().forEach(info -> info.accept(this));
catalog.getCoverages().forEach(info -> info.accept(this));
catalog.getLayers().forEach(info -> info.accept(this));
catalog.getResources(WMSLayerInfo.class).forEach(info -> info.accept(this));
catalog.getStyles().forEach(info -> info.accept(this));
catalog.getLayerGroups().forEach(info -> info.accept(this));
}
@Override
public void visit(WorkspaceInfo workspace) {
WorkspaceInfo otherWorkspace = otherCatalog.getWorkspace(workspace.getId());
if (otherWorkspace == null) {
differences.add(new InfoDiff(workspace, null));
} else if (!(Objects.equals(workspace.getName(), otherWorkspace.getName()) &&
checkEquals(workspace.getMetadata(), otherWorkspace.getMetadata()))) {
differences.add(new InfoDiff(workspace, otherWorkspace));
}
}
@Override
public void visit(NamespaceInfo namespace) {
NamespaceInfo otherNamespace = otherCatalog.getNamespace(namespace.getId());
if (!(Objects.equals(namespace, otherNamespace) &&
checkEquals(namespace.getMetadata(), namespace.getMetadata()))) {
differences.add(new InfoDiff(namespace, otherNamespace));
}
}
@Override
public void visit(DataStoreInfo dataStore) {
DataStoreInfo otherDataStore = otherCatalog.getDataStore(dataStore.getId());
if (!(Objects.equals(dataStore, otherDataStore) &&
Objects.equals(dataStore.getType(), otherDataStore.getType()) &&
checkEquals(dataStore.getMetadata(), otherDataStore.getMetadata()) &&
checkEquals(dataStore.getConnectionParameters(), otherDataStore.getConnectionParameters()))) {
differences.add(new InfoDiff(dataStore, otherDataStore));
}
}
@Override
public void visit(CoverageStoreInfo coverageStore) {
CoverageStoreInfo otherCoverageStore = otherCatalog.getCoverageStore(coverageStore.getId());
if (!(Objects.equals(coverageStore, otherCoverageStore) &&
Objects.equals(coverageStore.getType(), otherCoverageStore.getType()) &&
Objects.equals(coverageStore.getFormat(), otherCoverageStore.getFormat()) &&
Objects.equals(coverageStore.getURL(), otherCoverageStore.getURL()) &&
checkEquals(coverageStore.getMetadata(), otherCoverageStore.getMetadata()) &&
checkEquals(coverageStore.getConnectionParameters(), otherCoverageStore.getConnectionParameters()))) {
differences.add(new InfoDiff(coverageStore, otherCoverageStore));
}
}
@Override
public void visit(WMSStoreInfo wmsStore) {
WMSStoreInfo otherWmsStore = otherCatalog.getStore(wmsStore.getId(), WMSStoreInfo.class);
if (!(Objects.equals(wmsStore, otherWmsStore) &&
Objects.equals(wmsStore.getType(), otherWmsStore.getType()) &&
Objects.equals(wmsStore.getCapabilitiesURL(), otherWmsStore.getCapabilitiesURL()) &&
Objects.equals(wmsStore.getUsername(), otherWmsStore.getUsername()) &&
Objects.equals(wmsStore.getPassword(), otherWmsStore.getPassword()) &&
Objects.equals(wmsStore.getMaxConnections(), otherWmsStore.getMaxConnections()) &&
Objects.equals(wmsStore.getReadTimeout(), otherWmsStore.getReadTimeout()) &&
Objects.equals(wmsStore.getConnectTimeout(), otherWmsStore.getConnectTimeout()) &&
checkEquals(wmsStore.getMetadata(), otherWmsStore.getMetadata()) &&
checkEquals(wmsStore.getConnectionParameters(), otherWmsStore.getConnectionParameters()))) {
differences.add(new InfoDiff(wmsStore, otherWmsStore));
}
}
@Override
public void visit(FeatureTypeInfo featureType) {
FeatureTypeInfo otherFeatureType = otherCatalog.getFeatureType(featureType.getId());
if (otherFeatureType != null) {
otherFeatureType.setProjectionPolicy(featureType.getProjectionPolicy());
}
if (!(Objects.equals(featureType, otherFeatureType) &&
checkEquals(featureType.getAttributes(), otherFeatureType.getAttributes()) &&
checkEquals(featureType.getResponseSRS(), otherFeatureType.getResponseSRS()) &&
checkEquals(featureType.getAlias(), otherFeatureType.getAlias()) &&
checkEquals(featureType.getKeywords(), otherFeatureType.getKeywords()) &&
checkEquals(featureType.getDataLinks(), otherFeatureType.getDataLinks()) &&
checkEquals(featureType.getMetadataLinks(), otherFeatureType.getMetadataLinks()) &&
checkEquals(featureType.getMetadata(), otherFeatureType.getMetadata()))) {
differences.add(new InfoDiff(featureType, otherFeatureType));
}
}
@Override
public void visit(CoverageInfo coverage) {
CoverageInfo otherCoverage = otherCatalog.getCoverage(coverage.getId());
if (!(Objects.equals(coverage, otherCoverage) &&
checkEquals(coverage.getDimensions(), otherCoverage.getDimensions()) &&
checkEquals(coverage.getInterpolationMethods(), otherCoverage.getInterpolationMethods()) &&
checkEquals(coverage.getParameters(), otherCoverage.getParameters()) &&
checkEquals(coverage.getResponseSRS(), otherCoverage.getResponseSRS()) &&
checkEquals(coverage.getAlias(), otherCoverage.getAlias()) &&
checkEquals(coverage.getKeywords(), otherCoverage.getKeywords()) &&
checkEquals(coverage.getDataLinks(), otherCoverage.getDataLinks()) &&
checkEquals(coverage.getMetadataLinks(), otherCoverage.getMetadataLinks()) &&
checkEquals(coverage.getMetadata(), otherCoverage.getMetadata()))) {
differences.add(new InfoDiff(coverage, otherCoverage));
}
}
@Override
public void visit(LayerInfo layer) {
LayerInfo otherLayer = otherCatalog.getLayer(layer.getId());
if (!(Objects.equals(layer, otherLayer) &&
Objects.equals(layer.isAdvertised(), otherLayer.isAdvertised()) &&
checkEquals(layer.getAuthorityURLs(), otherLayer.getAuthorityURLs()) &&
checkEquals(layer.getIdentifiers(), otherLayer.getIdentifiers()) &&
checkEquals(layer.getStyles(), otherLayer.getStyles()) &&
checkEquals(layer.getMetadata(), otherLayer.getMetadata()))) {
differences.add(new InfoDiff(layer, otherLayer));
}
}
@Override
public void visit(StyleInfo style) {
StyleInfo otherStyle = otherCatalog.getStyle(style.getId());
if (!(Objects.equals(style, otherStyle) &&
getStyleFile(style, dataDir).equals(getStyleFile(otherStyle, otherDataDir)))) {
differences.add(new InfoDiff(style, otherStyle));
}
}
@Override
public void visit(LayerGroupInfo layerGroup) {
LayerGroupInfo otherLayerGroup = otherCatalog.getLayerGroup(layerGroup.getId());
if (!(Objects.equals(layerGroup, otherLayerGroup) &&
checkEquals(layerGroup.getStyles(), otherLayerGroup.getStyles()) &&
checkEquals(layerGroup.getAuthorityURLs(), otherLayerGroup.getAuthorityURLs()) &&
checkEquals(layerGroup.getIdentifiers(), otherLayerGroup.getIdentifiers()) &&
checkEquals(layerGroup.getMetadataLinks(), otherLayerGroup.getMetadataLinks()) &&
checkEquals(layerGroup.getMetadata(), otherLayerGroup.getMetadata()) &&
checkEquals(layerGroup.getLayers(), otherLayerGroup.getLayers()))) {
differences.add(new InfoDiff(layerGroup, otherLayerGroup));
}
}
@Override
public void visit(WMSLayerInfo wmsLayer) {
WMSLayerInfo otherWmsLayer = otherCatalog.getResource(wmsLayer.getId(), WMSLayerInfo.class);
if (!(Objects.equals(wmsLayer, otherWmsLayer) &&
checkEquals(wmsLayer.getAlias(), otherWmsLayer.getAlias()) &&
checkEquals(wmsLayer.getKeywords(), otherWmsLayer.getKeywords()) &&
checkEquals(wmsLayer.getDataLinks(), otherWmsLayer.getDataLinks()) &&
checkEquals(wmsLayer.getMetadataLinks(), otherWmsLayer.getMetadataLinks()) &&
checkEquals(wmsLayer.getMetadata(), otherWmsLayer.getMetadata()))) {
differences.add(new InfoDiff(wmsLayer, otherWmsLayer));
}
}
/**
* Returns the differences found.
*/
public List<InfoDiff> differences() {
return differences;
}
/**
* Register has differences the elements from the other collection that are not present in collection.
*/
private <T extends CatalogInfo> void listDiffOther(Collection<T> collection, Collection<T> otherCollection) {
differences.addAll(otherCollection.stream()
.filter(info -> !containsElementWithId(info.getId(), collection))
.map(info -> new InfoDiff(null, info))
.collect(Collectors.toList()));
}
/**
* Searches a catalog info based in is ID in a provided collection-
*/
private <T extends CatalogInfo> boolean containsElementWithId(String id, Collection<T> collection) {
for (T element : collection) {
if (element.getId().equals(id)) {
// catalog info element found
return true;
}
}
// catalog info element not found
return false;
}
/**
* Retrieves a style file content has a string.
*/
private String getStyleFile(StyleInfo styleInfo, GeoServerDataDirectory dataDir) {
Resource resource = dataDir.get(styleInfo, styleInfo.getFilename());
try (InputStream input = resource.in();
ByteArrayOutputStream output = new ByteArrayOutputStream()) {
// reads the style file content to the byte array
IOUtils.copy(input, output);
return new String(output.toByteArray());
} catch (IOException exception) {
throw new RuntimeException(String.format(
"Error reading style '%s'.", styleInfo.getName()));
}
}
/**
* Return TRUE if the two collection contains exactly the same elements in any order.
*/
private boolean checkEquals(Collection<?> collectionA, Collection<?> collectionB) {
if (collectionA.size() != collectionB.size()) {
// collections have a different size so their are different
return false;
}
for (Object object : collectionA) {
if (!collectionB.contains(object)) {
// this element is not present in collection B
return false;
}
}
// the two collections contain the same elements
return true;
}
/**
* Checks that the contained metadata is the same.
*/
private boolean checkEquals(MetadataMap metadataA, MetadataMap metadataB) {
return checkEquals(metadataA.getMap(), metadataB.getMap());
}
/**
* Checks that two maps have exactly the same elements.
*/
private boolean checkEquals(Map<String, Serializable> mapA, Map<String, Serializable> mapB) {
if (mapA.size() != mapB.size()) {
// the have a different number of elements, so their are different
return false;
}
for (Map.Entry<String, Serializable> entry : mapA.entrySet()) {
if (!Objects.equals(entry.getValue(), mapB.get(entry.getKey()))) {
// element doesn't exists in map B or is value is different
return false;
}
}
// the two maps contain the same elements
return true;
}
}