org.geowebcache.diskquota.CacheCleanerTask.java Source code

Java tutorial

Introduction

Here is the source code for org.geowebcache.diskquota.CacheCleanerTask.java

Source

/**
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * @author Gabriel Roldan (OpenGeo) 2010
 *  
 */
package org.geowebcache.diskquota;

import java.math.BigInteger;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.geowebcache.diskquota.CacheCleaner.QuotaResolver;
import org.geowebcache.diskquota.storage.LayerQuota;
import org.geowebcache.diskquota.storage.Quota;

class CacheCleanerTask implements Runnable {

    static final Log log = LogFactory.getLog(CacheCleanerTask.class);

    /**
     * Maintains a set of per layer enforcement tasks, so that no enforcement task is spawn for a
     * layer when one is still running.
     */
    private final Map<String, Future<?>> perLayerRunningCleanUps;

    /**
     * Caches the currently running {@link GlobalQuotaEnforcementTask} so that not two are launched
     * at the same time
     */
    private Future<?> globalCleanUpTask;

    private ExecutorService cleanUpExecutorService;

    private final DiskQuotaMonitor monitor;

    /**
     * @param executor
     *            ExecutorService used to launch quota enforcement tasks
     */
    public CacheCleanerTask(final DiskQuotaMonitor monitor, final ExecutorService executor) {
        this.monitor = monitor;
        this.cleanUpExecutorService = executor;
        this.perLayerRunningCleanUps = new HashMap<String, Future<?>>();
    }

    /**
     * Runs the cache enforcement tasks asynchronously using the {@link ExecutorService} provided in
     * the constructor.
     * <p>
     * Exceptions are catched and logged, not propagated, in order to allow this runnable to be used
     * as a timer so even if one run fails the next runs are still called
     * </p>
     * <p>
     * The process submits one cache cleanup execution task per layer that exceeds it's configured
     * quota, and a single global cache enforcement task for the layers that have no explicitly
     * configured quota limit.
     * </p>
     * 
     * @see java.lang.Runnable#run()
     */
    public void run() {
        try {
            innerRun();
        } catch (InterruptedException e) {
            log.info("CacheCleanerTask called for shut down", e);
            e.printStackTrace();
        } catch (Exception e) {
            log.error("Error running cache diskquota enforcement task", e);
        }
    }

    private void innerRun() throws InterruptedException {
        // first, save the config to account for changes in used quotas

        final DiskQuotaConfig quotaConfig = monitor.getConfig();
        if (!quotaConfig.isEnabled()) {
            log.trace("DiskQuota disabled, ignoring run...");
            return;
        }

        quotaConfig.setLastCleanUpTime(new Date());

        final Set<String> allLayerNames = monitor.getLayerNames();
        final Set<String> configuredLayerNames = quotaConfig.layerNames();
        final Set<String> globallyManagedLayerNames = new HashSet<String>(allLayerNames);

        globallyManagedLayerNames.removeAll(configuredLayerNames);

        for (String layerName : configuredLayerNames) {

            if (monitor.isCacheInfoBuilderRunning(layerName)) {
                if (log.isInfoEnabled()) {
                    log.info("Cache information is still being gathered for layer '" + layerName
                            + "'. Skipping quota enforcement task for this layer.");
                }
                continue;
            }

            Future<?> runningCleanup = perLayerRunningCleanUps.get(layerName);
            if (runningCleanup != null && !runningCleanup.isDone()) {
                if (log.isDebugEnabled()) {
                    log.debug("Cache clean up task still running for layer '" + layerName
                            + "'. Ignoring it for this run.");
                }
                continue;
            }

            final LayerQuota definedQuotaForLayer = quotaConfig.layerQuota(layerName);
            final ExpirationPolicy policy = definedQuotaForLayer.getExpirationPolicyName();
            final Quota quota = definedQuotaForLayer.getQuota();
            final Quota usedQuota = monitor.getUsedQuotaByLayerName(layerName);

            Quota excedent = usedQuota.difference(quota);
            if (excedent.getBytes().compareTo(BigInteger.ZERO) > 0) {
                if (log.isInfoEnabled()) {
                    log.info("Layer '" + layerName + "' exceeds its quota of " + quota.toNiceString() + " by "
                            + excedent.toNiceString() + ". Currently used: " + usedQuota.toNiceString()
                            + ". Clean up task will be performed using expiration policy " + policy);
                }

                Set<String> layerNames = Collections.singleton(layerName);
                QuotaResolver quotaResolver;
                quotaResolver = monitor.newLayerQuotaResolver(layerName);

                LayerQuotaEnforcementTask task;
                task = new LayerQuotaEnforcementTask(layerNames, quotaResolver, monitor);
                Future<Object> future = this.cleanUpExecutorService.submit(task);
                perLayerRunningCleanUps.put(layerName, future);
            }
        }

        if (globallyManagedLayerNames.size() > 0) {
            ExpirationPolicy globalExpirationPolicy = quotaConfig.getGlobalExpirationPolicyName();
            if (globalExpirationPolicy == null) {
                return;
            }
            final Quota globalQuota = quotaConfig.getGlobalQuota();
            if (globalQuota == null) {
                log.info("There's not a global disk quota configured. The following layers "
                        + "will not be checked for excess of disk usage: " + globallyManagedLayerNames);
                return;
            }

            if (globalCleanUpTask != null && !globalCleanUpTask.isDone()) {
                log.debug("Global cache quota enforcement task still running, avoiding issueing a new one...");
                return;
            }

            Quota globalUsedQuota = monitor.getGloballyUsedQuota();
            Quota excedent = globalUsedQuota.difference(globalQuota);

            if (excedent.getBytes().compareTo(BigInteger.ZERO) > 0) {

                log.debug("Submitting global cache quota enforcement task");
                LayerQuotaEnforcementTask task;
                QuotaResolver quotaResolver = monitor.newGlobalQuotaResolver();
                task = new LayerQuotaEnforcementTask(globallyManagedLayerNames, quotaResolver, monitor);
                this.globalCleanUpTask = this.cleanUpExecutorService.submit(task);
            } else {
                if (log.isTraceEnabled()) {
                    log.trace("Won't launch global quota enforcement task, " + globalUsedQuota.toNiceString()
                            + " used out of " + globalQuota.toNiceString()
                            + " configured for the whole cache size.");
                }
            }
        }
    }

    /**
     * 
     * @author Gabriel Roldan
     */
    private static class LayerQuotaEnforcementTask implements Callable<Object> {

        private final Set<String> layerNames;

        private final QuotaResolver quotaResolver;

        private final DiskQuotaMonitor monitor;

        public LayerQuotaEnforcementTask(final Set<String> layerNames, final QuotaResolver quotaResolver,
                final DiskQuotaMonitor monitor) {
            this.layerNames = layerNames;
            this.quotaResolver = quotaResolver;
            this.monitor = monitor;
        }

        /**
         * @see java.util.concurrent.Callable#call()
         */
        public Object call() throws Exception {
            try {
                monitor.expireByLayerNames(layerNames, quotaResolver);
            } catch (InterruptedException e) {
                log.info("Layer quota enforcement task terminated prematurely");
                return null;
            } catch (Exception e) {
                log.warn("Exception expiring tiles for " + layerNames, e);
                throw e;
            }
            return null;
        }

    }

}