/**
* 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.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.geowebcache.GeoWebCacheExtensions;
import org.geowebcache.storage.BlobStoreListener;
import org.geowebcache.storage.DefaultStorageBroker;
import org.springframework.util.Assert;
/**
* Monitors {@link DefaultStorageBroker} activity to keep track of the disk usage.
* <p>
* This class only cares about receiving {@link BlobStoreListener} events and submitting
* {@link QuotaUpdate}s to the provided {@link BlockingQueue}. Another thread is responsible of
* taking the {@link QuotaUpdate} off the queue and updating the quota store as appropriate.
* </p>
*
* @author groldan
* @see DiskQuotaMonitor
* @see QueuedQuotaUpdatesConsumer
*/
class QueuedQuotaUpdatesProducer implements BlobStoreListener {
private static final Log log = LogFactory.getLog(QueuedQuotaUpdatesProducer.class);
private final DiskQuotaConfig quotaConfig;
private final BlockingQueue<QuotaUpdate> queuedUpdates;
private boolean cancelled;
private final QuotaStore quotaStore;
int updateOfferTimeoutSeconds;
/**
*
* @param quotaConfig
* needed to get the {@link DiskQuotaConfig#getDiskBlockSize() disk block size} at
* each tile event, so that the computation is consistent with config changes at
* runtime
* @param queuedUpdates
* queue that this monitor will fill with updates at each tile event. There should be
* a separate thread that takes care of them.
*/
public QueuedQuotaUpdatesProducer(final DiskQuotaConfig quotaConfig,
final BlockingQueue<QuotaUpdate> queuedUpdates, QuotaStore quotaStore) {
Assert.notNull(quotaConfig, "quotaConfig can't be null");
Assert.notNull(queuedUpdates, "queuedUpdates can't be null");
this.quotaConfig = quotaConfig;
this.queuedUpdates = queuedUpdates;
this.quotaStore = quotaStore;
String timeoutStr = GeoWebCacheExtensions.getProperty("GEOWEBCACHE_QUOTA_DIFF_TIMEOUT");
this.updateOfferTimeoutSeconds = 5 * 60; // by default five minutes
if(timeoutStr != null) {
updateOfferTimeoutSeconds = Integer.parseInt(timeoutStr);
}
}
/**
* Receives notification of a tile stored and updates the corresponding layer quota info.
*
* @see org.geowebcache.storage.BlobStoreListener#tileStored
*/
public void tileStored(final String layerName, final String gridSetId, final String blobFormat,
final String parametersId, final long x, final long y, final int z, final long blobSize) {
if (blobSize == 0) {
return;
}
quotaUpdate(layerName, gridSetId, blobFormat, parametersId, blobSize,
new long[] { x, y, z });
}
/**
* @see org.geowebcache.storage.BlobStoreListener#tileDeleted
*/
public void tileDeleted(final String layerName, final String gridSetId,
final String blobFormat, final String parametersId, final long x, final long y,
final int z, final long blobSize) {
long actualSizeFreed = -1 * blobSize;
quotaUpdate(layerName, gridSetId, blobFormat, parametersId, actualSizeFreed, new long[] {
x, y, z });
}
/**
*
* @see org.geowebcache.storage.BlobStoreListener#tileUpdated
*/
public void tileUpdated(String layerName, String gridSetId, String blobFormat,
String parametersId, long x, long y, int z, long blobSize, long oldSize) {
long delta = blobSize - oldSize;
if (delta == 0) {
return;
}
long[] tileIndex = new long[] { x, y, z };
quotaUpdate(layerName, gridSetId, blobFormat, parametersId, delta, tileIndex);
}
/**
* @see org.geowebcache.storage.BlobStoreListener#layerDeleted(java.lang.String)
* @see QuotaStore#deleteLayer(String)
*/
public void layerDeleted(final String layerName) {
quotaStore.deleteLayer(layerName);
}
public void gridSubsetDeleted(String layerName, String gridSetId) {
quotaStore.deleteGridSubset(layerName, gridSetId);
}
public void parametersDeleted(String layerName, String parametersId) {
quotaStore.deleteParameters(layerName, parametersId);
}
public void layerRenamed(String oldLayerName, String newLayerName) {
try {
quotaStore.renameLayer(oldLayerName, newLayerName);
} catch (InterruptedException e) {
log.error("Can't rename " + oldLayerName + " to " + newLayerName + " in quota store", e);
}
}
/**
* Defers executing the update of the quota usage for the given tile set by adding a
* {@link QuotaUpdate} payload to {@link #queuedUpdates} so that the consumer thread performs
* the update without blocking the calling thread.
*
* @param layerName
* @param gridSetId
* @param blobFormat
* @param parametersId
* @param amount
* positive to signal a quota increase, negative to signal a quota decrease
* @param tileIndex
* tile index
*/
private void quotaUpdate(String layerName, String gridSetId, String blobFormat,
String parametersId, long amount, long[] tileIndex) {
if (cancelled(layerName)) {
return;
}
QuotaUpdate payload = new QuotaUpdate(layerName, gridSetId, blobFormat, parametersId,
amount, tileIndex);
try {
if(updateOfferTimeoutSeconds <= 0) {
this.queuedUpdates.put(payload);
} else {
if(!this.queuedUpdates.offer(payload, updateOfferTimeoutSeconds, TimeUnit.SECONDS)) {
throw new RuntimeException("Failed to offer the quota diff to the updates queue "
+ "within the configured timeout of " + updateOfferTimeoutSeconds + " seconds");
}
}
} catch (InterruptedException e) {
if (cancelled(layerName)) {
return;
}
log.info("Quota updates on " + layerName + " abruptly interrupted on thread "
+ Thread.currentThread().getName() + ".");
}
}
private boolean cancelled(String layerName) {
if (cancelled) {
log.debug("Quota updates listener cancelled. Avoiding adding update for layer "
+ layerName + " to quota information queue");
}
return cancelled;
}
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
}