package org.geowebcache.diskquota;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.geowebcache.diskquota.storage.TilePageCalculator;
import org.geowebcache.layer.TileLayer;
import org.geowebcache.layer.TileLayerDispatcher;
import org.geowebcache.layer.TileLayerListener;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.util.Assert;
public class UsageStatsMonitor {
private static final Log log = LogFactory.getLog(UsageStatsMonitor.class);
private static final CustomizableThreadFactory tf = new CustomizableThreadFactory(
"GWC DiskQuota Usage Stats Gathering Thread-");
private final QuotaStore quotaStore;
private final TileLayerDispatcher tileLayerDispatcher;
private final TilePageCalculator tilePageCalculator;
/**
* Single threaded executor service for the {@link #usageStatsConsumer}
*/
private ExecutorService executorService;
/**
* Queue shared by the stats producer and the consumer
*/
private BlockingQueue<UsageStats> sharedQueue;
/**
* Listens to all {@link TileLayer layers}
* {@link TileLayerListener#tileRequested(TileLayer, org.geowebcache.conveyor.ConveyorTile)
* tileRequested} events and puts usage statistics on the {@link #sharedQueue} for the consumer
* to save them to the {@link #quotaStore}
*/
private QueuedUsageStatsProducer usageStatsProducer;
/**
* Task that constantly polls the {@link #sharedQueue} for usage statistics payload objects and
* aggregates them to be saved to the {@link #quotaStore} for the LRU and LFU
* {@link ExpirationPolicy expiration policies}
*/
private QueuedUsageStatsConsumer usageStatsConsumer;
public UsageStatsMonitor(final QuotaStore quotaStore,
final TileLayerDispatcher tileLayerDispatcher) {
Assert.notNull(quotaStore, "quotaStore is null");
Assert.notNull(tileLayerDispatcher, "tileLayerDispatcher is null");
this.quotaStore = quotaStore;
this.tileLayerDispatcher = tileLayerDispatcher;
this.tilePageCalculator = quotaStore.getTilePageCalculator();
}
public void startUp() {
executorService = Executors.newSingleThreadExecutor(tf);
sharedQueue = new LinkedBlockingQueue<UsageStats>(1000);
usageStatsConsumer = new QueuedUsageStatsConsumer(quotaStore, sharedQueue,
tilePageCalculator);
executorService.submit(usageStatsConsumer);
usageStatsProducer = new QueuedUsageStatsProducer(sharedQueue);
Iterable<TileLayer> allLayers = tileLayerDispatcher.getLayerList();
for (TileLayer layer : allLayers) {
layer.addLayerListener(usageStatsProducer);
}
}
/**
* Calls for a shut down and waits until any remaining task finishes before returning
*/
public void shutDown() {
final boolean cancel = false;
shutDown(cancel);
final int maxAttempts = 6;
final int seconds = 5;
int attempts = 1;
while (!executorService.isTerminated()) {
attempts++;
try {
awaitTermination(seconds, TimeUnit.SECONDS);
} catch (InterruptedException e) {
String message = "Usage statistics thread helper for DiskQuota failed to shutdown within "
+ (attempts * seconds)
+ " seconds. Attempt "
+ attempts
+ " of "
+ maxAttempts + "...";
log.warn(message);
if (attempts == maxAttempts) {
throw new RuntimeException(message, e);
}
}
}
}
public void awaitTermination(int timeout, TimeUnit units) throws InterruptedException {
if (!executorService.isShutdown()) {
throw new IllegalStateException("Called awaitTermination but the "
+ "UsageStatsMonitor is not shutting down");
}
executorService.awaitTermination(timeout, units);
}
/**
* Calls for a shut down and returns immediatelly
*/
public void shutDownNow() {
shutDown(true);
}
private void shutDown(final boolean cancel) {
Iterable<TileLayer> allLayers = tileLayerDispatcher.getLayerList();
for (TileLayer layer : allLayers) {
try {
layer.removeLayerListener(usageStatsProducer);
} catch (RuntimeException e) {
log.error("Unexpected exception while removing the usage stats "
+ "listener from layer '" + layer
+ "'. Ignoring in order to continue with the monitor's shutdown "
+ "process", e);
}
}
usageStatsConsumer.shutdown();
if (cancel) {
usageStatsProducer.setCancelled(true);
executorService.shutdownNow();
} else {
executorService.shutdown();
}
sharedQueue = null;
}
}