/* (c) 2014 - 2015 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.gwc.layer; import static org.geoserver.gwc.GWC.tileLayerName; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Logger; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.CatalogException; import org.geoserver.catalog.CatalogInfo; import org.geoserver.catalog.LayerGroupInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.Predicates; import org.geoserver.catalog.StyleInfo; 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.util.CloseableIterator; import org.geoserver.gwc.GWC; import org.geotools.util.logging.Logging; import org.geowebcache.filter.parameters.ParameterFilter; import com.google.common.collect.ImmutableSet; /** * Listens to changes in {@link StyleInfo styles} for the GeoServer {@link Catalog} and applies the * needed {@link ParameterFilter} changes to the corresponding {@link GeoServerTileLayer}. * * @author Arne Kepp * @author Gabriel Roldan */ public class CatalogStyleChangeListener implements CatalogListener { private static Logger log = Logging.getLogger(CatalogStyleChangeListener.class); /** * Holds the CatalogModifyEvent from {@link #handleModifyEvent} to be taken after the change was * applied to the {@link Catalog} at {@link #handlePostModifyEvent} and check whether it is * necessary to perform any action on the cache based on the changed properties */ private static ThreadLocal<CatalogModifyEvent> PRE_MODIFY_EVENT = new ThreadLocal<CatalogModifyEvent>(); private final GWC mediator; private Catalog catalog; public CatalogStyleChangeListener(final GWC mediator, Catalog catalog) { this.mediator = mediator; this.catalog = catalog; } /** * @see org.geoserver.catalog.event.CatalogListener#handleAddEvent(org.geoserver.catalog.event.CatalogAddEvent) */ public void handleAddEvent(CatalogAddEvent event) throws CatalogException { // no need to handle style additions, they are added before being attached to a layerinfo } /** * Handles the rename of a style, truncating the cache for any layer that has it as a cached * alternate style; modifications to the actual style are handled at * {@link #handlePostModifyEvent(CatalogPostModifyEvent)}. * <p> * When a style is renamed, the {@link LayerInfo} and {@link LayerGroupInfo} that refer to it * are not modified, since they refer to the {@link StyleInfo} by id, so they don't really care * about the name of the style. For the tiled layer its different, because styles are referred * to by {@link GeoServerTileLayerInfoImpl#cachedStyles() name} in order to create the * appropriate "STYLES" {@link ParameterFilter parameter filter}. This method will look for any * tile layer backed by a {@link LayerInfo} that has a cache for the renamed style as an * alternate style (i.e. through a parameter filter) and will truncate the cache for that * layer/style. This is so because there's no way in GWC to just rename a style (that would * imply getting to the parameter filters that refer to that style in the meta-store and change * it's value preserving the parametersId) * <p> * * @see org.geoserver.catalog.event.CatalogListener#handleModifyEvent(org.geoserver.catalog.event.CatalogModifyEvent) */ public void handleModifyEvent(CatalogModifyEvent event) throws CatalogException { CatalogInfo source = event.getSource(); if (source instanceof StyleInfo) { final List<String> propertyNames = event.getPropertyNames(); if (!propertyNames.contains("name") && !propertyNames.contains("workspace")) { return; } final int nameIdx = propertyNames.indexOf("name"); final String oldName = (String) event.getOldValues().get(nameIdx); final String newName = (String) event.getNewValues().get(nameIdx); final int workspaceIdx = propertyNames.indexOf("wokspace"); final String oldWorkspaceName = workspaceIdx != -1 ? (String) event.getOldValues().get( workspaceIdx) : null; final String newWorkspaceName = workspaceIdx != -1 ? (String) event.getNewValues().get( workspaceIdx) : null; final String oldStyleName = getPrefixedName(oldWorkspaceName, oldName); final String newStyleName = getPrefixedName(newWorkspaceName, newName); handleStyleRenamed(oldStyleName, newStyleName); } else if (source instanceof WorkspaceInfo) { PRE_MODIFY_EVENT.set(event); } } private void handleStyleRenamed(final String oldStyleName, final String newStyleName) { if (oldStyleName.equals(newStyleName)) { return; } List<GeoServerTileLayer> affectedLayers; affectedLayers = mediator.getTileLayersForStyle(oldStyleName); for (GeoServerTileLayer tl : affectedLayers) { LayerInfo layerInfo = tl.getLayerInfo(); if (layerInfo == null) { // no extra styles for layer groups continue; } GeoServerTileLayerInfo info = tl.getInfo(); ImmutableSet<String> styleNames = info.cachedStyles(); if (styleNames.contains(oldStyleName)) { tl.resetParameterFilters(); // pity, we don't have a way to just rename a style in GWC mediator.truncateByLayerAndStyle(tl.getName(), oldStyleName); Set<String> newStyles = new HashSet<String>(styleNames); newStyles.remove(oldStyleName); newStyles.add(newStyleName); String defaultStyle = layerInfo.getDefaultStyle() == null ? null : layerInfo .getDefaultStyle().prefixedName(); TileLayerInfoUtil.setCachedStyles(info, defaultStyle, newStyles); mediator.save(tl); } } } private String getPrefixedName(String workspace, String name) { if (workspace != null) { return workspace + ":" + name; } else { return name; } } /** * Truncates all tile sets referring the modified {@link StyleInfo} * * @see org.geoserver.catalog.event.CatalogListener#handlePostModifyEvent */ public void handlePostModifyEvent(CatalogPostModifyEvent event) throws CatalogException { Object source = event.getSource(); if (source instanceof StyleInfo) { StyleInfo si = (StyleInfo) source; handleStyleChange(si); } else if (source instanceof WorkspaceInfo) { WorkspaceInfo ws = (WorkspaceInfo) source; handleWorkspaceChange(ws); } } private void handleWorkspaceChange(WorkspaceInfo ws) { final CatalogModifyEvent preModifyEvent = PRE_MODIFY_EVENT.get(); PRE_MODIFY_EVENT.remove(); final List<String> changedProperties = preModifyEvent.getPropertyNames(); // was the workspace name modified? this implies a name change in workspace local styles int nameIdx = changedProperties.indexOf("name"); if (nameIdx == -1) { return; } String oldWorkspaceName = (String) preModifyEvent.getOldValues().get(nameIdx); String newWorkspaceName = (String) preModifyEvent.getNewValues().get(nameIdx); // grab the styles CloseableIterator<StyleInfo> styles = catalog.list(StyleInfo.class, Predicates.equal("workspace.name", newWorkspaceName)); try { while (styles.hasNext()) { StyleInfo style = styles.next(); String oldStyleName = oldWorkspaceName + ":" + style.getName(); String newStyleName = newWorkspaceName + ":" + style.getName(); handleStyleRenamed(oldStyleName, newStyleName); } } finally { styles.close(); } } /** * Options are: * <ul> * <li>A {@link LayerInfo} has {@code modifiedStyle} as either its default or style or as one of * its alternate styles * <li>A {@link LayerGroupInfo} contains a layer using {@code modifiedStyle} * <li>{@code modifiedStyle} is explicitly assigned to a {@link LayerGroupInfo} * </ul> * * @param modifiedStyle */ private void handleStyleChange(final StyleInfo modifiedStyle) { final String styleName = modifiedStyle.prefixedName(); log.finer("Handling style modification: " + styleName); // First we collect all the layers that use this style Iterable<LayerInfo> layers = mediator.getLayerInfosFor(modifiedStyle); for (LayerInfo affectedLayer : layers) { // If the style name changes, we need to update the layer's parameter filter String prefixedName = tileLayerName(affectedLayer); log.info("Truncating layer '" + prefixedName + "' due to a change in style '" + styleName + "'"); mediator.truncateByLayerAndStyle(prefixedName, styleName); } // Now we check for layer groups that are affected for (LayerGroupInfo layerGroup : mediator.getLayerGroupsFor(modifiedStyle)) { String layerGroupName = tileLayerName(layerGroup); log.info("Truncating layer group '" + layerGroupName + "' due to a change in style '" + styleName + "'"); mediator.truncate(layerGroupName); } } /** * No need to do anything here, when a style is removed all the layers that reference it are * updated first * * @see org.geoserver.catalog.event.CatalogListener#handleRemoveEvent */ public void handleRemoveEvent(CatalogRemoveEvent event) throws CatalogException { // } /** * */ public void reloaded() { // } }