org.geogit.gwc.TruncateTilesOnUpdateRefHook.java Source code

Java tutorial

Introduction

Here is the source code for org.geogit.gwc.TruncateTilesOnUpdateRefHook.java

Source

/* Copyright (c) 2014 OpenPlans. All rights reserved.
 * This code is licensed under the GNU GPL 2.0 license, available at the root
 * application directory.
 */
package org.geogit.gwc;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static org.geoserver.catalog.Predicates.and;
import static org.geoserver.catalog.Predicates.equal;

import java.io.Serializable;
import java.util.List;
import java.util.Map;

import javax.annotation.Nullable;

import org.geogit.api.AbstractGeoGitOp;
import org.geogit.api.Context;
import org.geogit.api.Ref;
import org.geogit.api.SymRef;
import org.geogit.api.hooks.CannotRunGeogitOperationException;
import org.geogit.api.hooks.CommandHook;
import org.geogit.api.hooks.Scripting;
import org.geogit.api.plumbing.RefParse;
import org.geogit.api.plumbing.UpdateRef;
import org.geogit.geotools.data.GeoGitDataStore;
import org.geogit.geotools.data.GeoGitDataStoreFactory;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.DataStoreInfo;
import org.geoserver.catalog.LayerInfo;
import org.geoserver.catalog.StoreInfo;
import org.geoserver.catalog.util.CloseableIterator;
import org.geoserver.gwc.GWC;
import org.geoserver.gwc.layer.GeoServerTileLayer;
import org.geowebcache.GeoWebCacheExtensions;
import org.geowebcache.seed.TileBreeder;
import org.opengis.filter.Filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Optional;
import com.google.common.base.Stopwatch;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;

/**
 * A "classpath" command hook that hooks onto the {@link UpdateRef} command and truncates GWC tiles
 * for any {@link GeoGitDataStore} configured in geoserver that's affected by the ref update.
 * <p>
 * Ref updates may come from remote repositories pushing changes to the geogit web api as exposed by
 * the {@code /geogit/<workspace>:<datastore>} repository entry points.
 * <p>
 * When such geogit command is caught, this hook looks for GWC tile layers configured so that the
 * change may affect them, figures out the "minimal bounds" geometry of the changeset, and issues
 * GWC truncate tasks appropriately.
 */
public class TruncateTilesOnUpdateRefHook implements CommandHook {

    private static final Logger LOGGER = LoggerFactory.getLogger(TruncateTilesOnUpdateRefHook.class);

    /**
     * {@link Catalog} filter to retrieve enabled layer infos backed by a geogit datastore
     */
    private static final Filter GEOGIT_LAYERINFO_FILTER = and(equal("enabled", Boolean.TRUE),
            equal("resource.store.type", GeoGitDataStoreFactory.DISPLAY_NAME));

    @Override
    public boolean appliesTo(Class<? extends AbstractGeoGitOp<?>> clazz) {
        return UpdateRef.class.equals(clazz);
    }

    @Override
    public <C extends AbstractGeoGitOp<?>> C pre(C command) throws CannotRunGeogitOperationException {

        /*
         * Store the ref name and its old value in the command's user properties to be used in the
         * post-hook if the operation was successful
         */
        Map<String, Object> params = Scripting.getParamMap(command);
        String refName = (String) params.get("name");
        command.getClientData().put("name", refName);
        if (Ref.WORK_HEAD.equals(refName) || Ref.STAGE_HEAD.equals(refName)) {
            command.getClientData().put("ignore", Boolean.TRUE);
            // ignore updates to work/stage heads, we only care of updates to branches
            return command;
        }

        Optional<Ref> currentValue = command.command(RefParse.class).setName(refName).call();
        command.getClientData().put("oldValue", currentValue);

        LOGGER.debug("GWC geogit truncate pre-hook engaged for ref '{}'", refName);
        return command;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T post(AbstractGeoGitOp<T> command, Object retVal, boolean success) throws Exception {
        checkArgument(command instanceof UpdateRef);
        final UpdateRef cmd = (UpdateRef) command;
        final String refName = (String) cmd.getClientData().get("name");
        checkState(refName != null, "refName not captured in pre-hook");
        if (Boolean.TRUE.equals(cmd.getClientData().get("ignore"))) {
            LOGGER.debug("GWC geogit truncate post-hook returning, ref '{}' is ignored.", refName);
            return (T) retVal;
        }

        if (!success) {
            LOGGER.info("GWC geogit truncate post-hook returning, UpdateRef operation failed on ref '{}'.",
                    refName);
            return (T) retVal;
        }

        final GWC mediator = GWC.get();
        if (mediator == null) {
            LOGGER.debug("GWC geogit truncate post-hook returning, GWC mediator not installed?.");
            return (T) retVal;
        }

        final Optional<Ref> oldValue = (Optional<Ref>) cmd.getClientData().get("oldValue");
        final Optional<Ref> newValue = (Optional<Ref>) retVal;// == oldValue if the ref was deleted

        checkState(oldValue != null, "oldValue not captured in pre-hook");

        if (oldValue.equals(newValue)) {
            LOGGER.debug("GWC geogit truncate post-hook returning, ref '{}' didn't change ({}).", refName,
                    oldValue);
            return (T) retVal;
        }

        List<LayerInfo> affectedLayers;
        final String newRefName = newValue.get().getName();
        Stopwatch sw = Stopwatch.createStarted();
        affectedLayers = findAffectedLayers(mediator, command.context(), newRefName);
        LOGGER.debug("GWC geogit truncate post-hook found {} affected layers on branch {} in {}.",
                affectedLayers.size(), refName, sw.stop());

        for (LayerInfo layer : affectedLayers) {
            truncate(mediator, command.context(), layer, oldValue, newValue);
        }
        return (T) retVal;
    }

    private void truncate(GWC mediator, Context geogitContext, LayerInfo layer, Optional<Ref> oldValue,
            Optional<Ref> newValue) {

        GeoServerTileLayer tileLayer = mediator.getTileLayer(layer);
        if (tileLayer == null) {
            return;
        }

        TileBreeder breeder = GeoWebCacheExtensions.bean(TileBreeder.class);
        checkState(breeder != null);// if GWC wasn't installed it should have returned earlier
        TruncateHelper.issueTruncateTasks(geogitContext, oldValue, newValue, tileLayer, breeder);

    }

    private List<LayerInfo> findAffectedLayers(GWC mediator, Context context, String newRefName) {

        final Catalog catalog = mediator.getCatalog();

        ListMultimap<StoreInfo, LayerInfo> affectedLayers = LinkedListMultimap.create();

        CloseableIterator<LayerInfo> geogitLayers;
        geogitLayers = catalog.list(LayerInfo.class, GEOGIT_LAYERINFO_FILTER);
        try {
            while (geogitLayers.hasNext()) {
                LayerInfo layerInfo = geogitLayers.next();
                // re check for the cascaded enabled() property
                if (!layerInfo.enabled()) {
                    continue;
                }
                // now we're sure the layer info is enabled and so is its store
                // ignore if there's no tile layer for it
                if (!mediator.hasTileLayer(layerInfo)) {
                    continue;
                }
                final DataStoreInfo store = (DataStoreInfo) layerInfo.getResource().getStore();
                if (affectedLayers.containsKey(store)) {
                    affectedLayers.put(store, layerInfo);
                } else {
                    @Nullable
                    final String dataStoreHead = findDataStoreHeadRefName(store, context);
                    if (newRefName.equals(dataStoreHead)) {
                        affectedLayers.put(store, layerInfo);
                    }
                }

            }
        } finally {
            geogitLayers.close();
        }
        return (List<LayerInfo>) affectedLayers.values();
    }

    private String findDataStoreHeadRefName(DataStoreInfo store, Context context) {
        String dataStoreHead;
        Map<String, Serializable> storeParams = store.getConnectionParameters();

        final String branch = (String) storeParams.get(GeoGitDataStoreFactory.BRANCH.key);
        final String head = (String) storeParams.get(GeoGitDataStoreFactory.HEAD.key);
        dataStoreHead = (head == null) ? branch : head;
        if (dataStoreHead == null) {
            Optional<Ref> currHead = context.command(RefParse.class).setName(Ref.HEAD).call();
            if (!currHead.isPresent() || !(currHead.get() instanceof SymRef)) {
                // can't figure out the current branch, ignore?
                return null;
            }
            dataStoreHead = ((SymRef) currHead.get()).getTarget();
        } else {
            Optional<Ref> storeRef = context.command(RefParse.class).setName(dataStoreHead).call();
            if (storeRef.isPresent()) {
                Ref ref = storeRef.get();
                if (ref instanceof SymRef) {
                    dataStoreHead = ((SymRef) ref).getTarget();
                } else {
                    dataStoreHead = ref.getName();
                }
            } else {
                LOGGER.info("HEAD '{}' configured for store '{}' does not resolve to a ref", dataStoreHead,
                        store.getName());
                dataStoreHead = null;
            }
        }
        return dataStoreHead;
    }
}