/* (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.Info; import org.geoserver.catalog.WorkspaceInfo; import org.geoserver.catalog.impl.ModificationProxy; import org.geoserver.config.GeoServer; import org.geoserver.config.GeoServerInfo; import org.geoserver.config.ServiceInfo; import org.geoserver.config.SettingsInfo; import org.geoserver.ows.util.OwsUtils; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; /** * This visitor can be used to extract to GeoServer configuration differences. */ public final class ConfigurationDiffVisitor { private final GeoServer geoServerA; private final GeoServer geoServerB; private final List<InfoDiff> differences = new ArrayList<>(); public ConfigurationDiffVisitor(GeoServer geoServerA, GeoServer geoServerB) { this.geoServerA = geoServerA; this.geoServerB = geoServerB; computeDifferences(); } /** * Returns the differences found. */ public List<InfoDiff> differences() { return differences; } /** * Visit both GeoServers and register the differences. */ private void computeDifferences() { // check GeoServer global settings differences if (!checkEquals(geoServerA.getGlobal(), geoServerB.getGlobal())) { differences.add(new InfoDiff(geoServerA.getGlobal(), geoServerB.getGlobal())); } if (!checkEquals(geoServerA.getLogging(), geoServerB.getLogging())) { differences.add(new InfoDiff(geoServerA.getLogging(), geoServerB.getLogging())); } // check services differences computeServicesDifference(); // check settings differences computeSettingsDifference(); } /** * Register services differences between the two GeoServers. */ private void computeServicesDifference() { // get all the services available on both GeoServers Collection<ServiceInfo> servicesA = getAllServices(geoServerA); Collection<ServiceInfo> servicesB = getAllServices(geoServerB); // register the services that are only present in GeoServer B has differences differences.addAll(servicesB.stream() .filter(service -> search(service, servicesA) == null) .map(service -> new InfoDiff(null, service)) .collect(Collectors.toList())); // iterate over GeoServer A services and compare them with GeoServer B services for (ServiceInfo service : servicesA) { ServiceInfo otherService = search(service, servicesB); if (!checkEquals(service, otherService)) { // different service found, the other service may be NULL which means // that this service only exists in GeoServer A differences.add(new InfoDiff(service, otherService)); } } } /** * Searches a service by is name and workspace on a collection of services. */ private static ServiceInfo search(ServiceInfo info, Collection<ServiceInfo> collection) { for (ServiceInfo candidateInfo : collection) { if (checkEqualsByNameAndWorkspace(info, candidateInfo)) { // service found return candidateInfo; } } // service not found return null; } /** * Returns TRUE if the services have the same name and the same workspace. */ private static boolean checkEqualsByNameAndWorkspace(ServiceInfo infoA, ServiceInfo infoB) { return Objects.equals(infoA.getName(), infoB.getName()) && Objects.equals(infoA.getWorkspace(), infoB.getWorkspace()); } /** * Register settings differences between the two GeoServers. */ private void computeSettingsDifference() { // get all the settings available on both GeoServers List<SettingsInfo> settingsA = getAllSettings(geoServerA); List<SettingsInfo> settingsB = getAllSettings(geoServerB); // register the settings that are only present in GeoServer B has differences differences.addAll(settingsB.stream() .filter(settings -> search(settings, settingsA) == null) .map(settings -> new InfoDiff(null, settings)) .collect(Collectors.toList())); // iterate over GeoServer A settings and compare them with GeoServer B services for (SettingsInfo settings : settingsA) { SettingsInfo otherSettings = search(settings, settingsB); if (!checkEquals(settings, otherSettings)) { // different settings found, the other settings may be NULL which means // that this settings only exists in GeoServer A differences.add(new InfoDiff(settings, otherSettings)); } } } /** * Searches settings by is title and workspace on a collection of settings. */ private static SettingsInfo search(SettingsInfo info, Collection<SettingsInfo> collection) { for (SettingsInfo candidateInfo : collection) { if (Objects.equals(info.getId(), candidateInfo.getId())) { // settings found return candidateInfo; } } // settings not found return null; } /** * Get all services info objects of a GeoServer instance, including the global * service and workspace services. */ private static List<ServiceInfo> getAllServices(GeoServer geoServer) { List<ServiceInfo> allServices = new ArrayList<>(); // get global services allServices.addAll(geoServer.getServices()); // get services per workspace List<WorkspaceInfo> workspaces = geoServer.getCatalog().getWorkspaces(); for (WorkspaceInfo workspace : workspaces) { // get the services of this workspace allServices.addAll(geoServer.getFacade().getServices(workspace)); } return allServices; } /** * Get all settings info objects of a GeoServer instance, this will not include * global settings only per workspace settings will be included. */ private static List<SettingsInfo> getAllSettings(GeoServer geoServer) { List<SettingsInfo> allSettings = new ArrayList<>(); // get all settings per workspace List<WorkspaceInfo> workspaces = geoServer.getCatalog().getWorkspaces(); for (WorkspaceInfo workspace : workspaces) { // get this workspace settings SettingsInfo settings = geoServer.getSettings(workspace); if (settings != null) { allSettings.add(settings); } } return allSettings; } /** * Compare two GeoServer info objects ignoring the update sequence. */ private static boolean checkEquals(GeoServerInfo infoA, GeoServerInfo infoB) { // for GeoServer infos to have the same update sequence infoA = ModificationProxy.unwrap(infoA); infoB = ModificationProxy.unwrap(infoB); long updateSequenceB = infoB.getUpdateSequence(); try { infoB.setUpdateSequence(infoA.getUpdateSequence()); // check that the two infos are equal return checkEquals((Info) infoA, infoB); } finally { // restore the original update sequence infoB.setUpdateSequence(updateSequenceB); } } private static boolean checkEquals(Info infoA, Info infoB) { // if only one of the infos is NULL they are not the same if ((infoA == null || infoB == null) && infoA != infoB) { return false; } // force the infos to have the same ID infoA = ModificationProxy.unwrap(infoA); infoB = ModificationProxy.unwrap(infoB); String idB = infoB.getId(); try { OwsUtils.set(infoB, "id", infoA.getId()); // compare the two infos return Objects.equals(infoA, infoB); } finally { // restore the original ID OwsUtils.set(infoB, "id", idB); } } }