/* (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.catalog;
import java.io.IOException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geoserver.catalog.util.CloseableIterator;
import org.geotools.util.logging.Logging;
import org.opengis.filter.Filter;
import org.opengis.filter.MultiValuedFilter.MatchAction;
/**
* Cascade deletes the visited objects, and modifies related object
* so that they are still consistent.
* In particular:
* <ul>
* <li>When removing a {@link LayerInfo} the {@link LayerGroupInfo} are modified
* by removing the layer. If the layer was the last one, the layer group
* is removed as well.
* </li>
* <li>When a {@link StyleInfo} is removed the layers using it as the default
* style are set with the default style, the layers that use is as an extra
* style are modified by removing it. Also, the layer groups using it
* are changed so that the default layer style is used in place of the
* one being removed
* </li>
*/
public class CascadeDeleteVisitor implements CatalogVisitor {
static final Logger LOGGER = Logging.getLogger(CascadeDeleteVisitor.class);
Catalog catalog;
public CascadeDeleteVisitor(Catalog catalog) {
this.catalog = catalog;
}
public void visit(Catalog catalog) {
}
public void visit(WorkspaceInfo workspace) {
// remove owned stores
for ( StoreInfo s : catalog.getStoresByWorkspace( workspace, StoreInfo.class ) ) {
s.accept(this);
}
// remove any linked namespaces
NamespaceInfo ns = catalog.getNamespaceByPrefix( workspace.getName() );
if ( ns != null ) {
ns.accept(this);
}
// remove styles contained in this workspace
for (StyleInfo style : catalog.getStylesByWorkspace(workspace)) {
style.accept(this);
}
// remove layer groups contained in this workspace
for (LayerGroupInfo group : catalog.getLayerGroupsByWorkspace(workspace)) {
group.accept(this);
}
catalog.remove(workspace);
}
public void visit(NamespaceInfo workspace) {
catalog.remove(workspace);
}
void visitStore(StoreInfo store) {
// drill down into layers (into resources since we cannot scan layers)
List<ResourceInfo> resources = catalog.getResourcesByStore(store, ResourceInfo.class);
for (ResourceInfo ri : resources) {
List<LayerInfo> layers = catalog.getLayers(ri);
if (!layers.isEmpty()){
for (LayerInfo li : layers) {
li.accept(this);
}
}
else {
// no layers for the resource, delete directly
ri.accept(this);
}
}
catalog.remove(store);
}
public void visit(DataStoreInfo dataStore) {
visitStore(dataStore);
}
public void visit(CoverageStoreInfo coverageStore) {
visitStore(coverageStore);
}
public void visit(WMSStoreInfo wmsStore) {
visitStore(wmsStore);
}
public void visit(FeatureTypeInfo featureType) {
// when the resource/layer split is done, delete all layers linked to the resource
catalog.remove(featureType);
}
public void visit(CoverageInfo coverage) {
// when the resource/layer split is done, delete all layers linked to the resource
catalog.remove(coverage);
}
public void visit(LayerInfo layer) {
// first update the groups, remove the layer, and if no
// other layers remained, remove the group as well
Filter groupContainsLayer = Predicates.equal("layers.id", layer.getId(), MatchAction.ANY);
try (CloseableIterator<LayerGroupInfo> groups = catalog.list(LayerGroupInfo.class,
groupContainsLayer)) {
while (groups.hasNext()) {
LayerGroupInfo group = groups.next();
// parallel remove of layer and styles
int index = group.getLayers().indexOf(layer);
while (index != -1) {
group.getLayers().remove(index);
group.getStyles().remove(index);
index = group.getLayers().indexOf(layer);
}
// either update or remove the group
if(group.getLayers().size() == 0) {
visit(catalog.getLayerGroup(group.getId()));
} else {
catalog.save(group);
}
}
}
// remove the layer and (for the moment) its resource as well
// TODO: change this to just remove the resource once the
// resource/publish split is done
ResourceInfo resource = layer.getResource();
catalog.remove(layer);
catalog.remove(resource);
}
private StyleInfo getResourceDefaultStyle(ResourceInfo resource, StyleInfo removedStyle) {
StyleInfo style = null;
try {
style = new CatalogBuilder(catalog).getDefaultStyle(resource);
} catch (IOException e) {
// we fall back on the default style (since we cannot roll back the
// entire operation, no transactions in the catalog)
LOGGER.log(Level.WARNING, "Could not find default style for resource "
+ resource + ", using Point style", e);
}
if (style == null || style.equals(removedStyle)) {
return catalog.getStyleByName(StyleInfo.DEFAULT_POINT);
}
return style;
}
private void removeStyleInLayer(LayerInfo layer, StyleInfo style) {
boolean dirty = false;
// remove it from the associated styles
if (layer.getStyles().remove(style)) {
dirty = true;
}
// if it's the default style, choose an associated style or reset it to the default one
StyleInfo ds = layer.getDefaultStyle();
if (ds != null && ds.equals(style)) {
dirty = true;
StyleInfo newDefaultStyle;
if (layer.getStyles().size() > 0) {
newDefaultStyle = layer.getStyles().iterator().next();
layer.getStyles().remove(newDefaultStyle);
} else {
newDefaultStyle = getResourceDefaultStyle(layer.getResource(), style);
}
layer.setDefaultStyle(newDefaultStyle);
}
if (dirty) {
catalog.save(layer);
}
}
private void removeStyleInLayerGroup(LayerGroupInfo group, StyleInfo style) {
boolean dirty = false;
// root layer style
if (style.equals(group.getRootLayerStyle())) {
group.setRootLayerStyle(getResourceDefaultStyle(group.getRootLayer().getResource(), style));
dirty = true;
}
// layer styles
List<StyleInfo> styles = group.getStyles();
for (int i = 0; i < styles.size(); i++) {
StyleInfo publishedStyle = styles.get(i);
if (publishedStyle != null && publishedStyle.equals(style)) {
// if publishedStyle is not null, we have a layer
LayerInfo layer = (LayerInfo) group.getLayers().get(i);
if (!layer.getDefaultStyle().equals(style)) {
// use default style
styles.set(i, layer.getDefaultStyle());
} else {
styles.set(i, getResourceDefaultStyle(layer.getResource(), style));
}
dirty = true;
}
}
if (dirty) {
catalog.save(group);
}
}
public void visit(StyleInfo style) {
// find the layers having this style as primary or secondary
Filter anyStyle = Predicates.equal("styles.id", style.getId(), MatchAction.ANY);
Filter layersAssociated = Predicates.or(Predicates.equal("defaultStyle.id", style.getId()), anyStyle);
// remove style references in layers
try (CloseableIterator<LayerInfo> it = catalog.list(LayerInfo.class, layersAssociated)) {
while (it.hasNext()) {
LayerInfo layer = it.next();
removeStyleInLayer(layer, style);
}
}
// groups can also refer to style, reset each reference to the
// associated layer default style
Filter groupAssociated = Predicates.or(Predicates.equal("rootLayerStyle.id", style.getId()), anyStyle);
try (CloseableIterator<LayerGroupInfo> it = catalog.list(LayerGroupInfo.class,
groupAssociated)) {
while (it.hasNext()) {
LayerGroupInfo group = it.next();
removeStyleInLayerGroup(group, style);
}
}
// finally remove the style
catalog.remove(style);
}
public void visit(LayerGroupInfo layerGroupToRemove) {
// remove layerGroupToRemove references from other groups
Filter associatedTo = Predicates.equal("layers.id", layerGroupToRemove.getId(), MatchAction.ANY);
try (CloseableIterator<LayerGroupInfo> it = catalog
.list(LayerGroupInfo.class, associatedTo)) {
while (it.hasNext()) {
LayerGroupInfo group = it.next();
// parallel remove of layer and styles
int index = getLayerGroupIndex(layerGroupToRemove, group);
while (index != -1) {
group.getLayers().remove(index);
group.getStyles().remove(index);
index = getLayerGroupIndex(layerGroupToRemove, group);
}
if (group.getLayers().size() == 0) {
// if group is empty, delete it
visit(group);
} else {
catalog.save(group);
}
}
}
// finally remove the group
catalog.remove(layerGroupToRemove);
}
/**
* Between modification proxies and security buffering the list of layers of a group it's just
* safer and more predictable to use a id comparison instead of a equals that accounts for
* each and every field
*
* @param layerGroup
* @param container
* @return
*/
private int getLayerGroupIndex(LayerGroupInfo layerGroup, LayerGroupInfo container) {
int idx = 0;
final String id = layerGroup.getId();
for (PublishedInfo pi : container.getLayers()) {
if(pi instanceof LayerGroupInfo && id.equals(pi.getId())) {
return idx;
}
idx++;
}
return -1;
}
public void visit(WMSLayerInfo wmsLayer) {
catalog.remove(wmsLayer);
}
}