Package org.geowebcache.diskquota.storage

Source Code of org.geowebcache.diskquota.storage.BDBQuotaStore$UsedQuotaByTileSetId

package org.geowebcache.diskquota.storage;

import static org.geowebcache.diskquota.DiskQuotaMonitor.GWC_DISKQUOTA_DISABLED;

import java.io.File;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.geowebcache.storage.DefaultStorageFinder;
import org.geowebcache.storage.StorageException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.util.Assert;

import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.Environment;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.Transaction;
import com.sleepycat.persist.EntityCursor;
import com.sleepycat.persist.EntityStore;
import com.sleepycat.persist.PrimaryIndex;
import com.sleepycat.persist.SecondaryIndex;

public class BDBQuotaStore implements QuotaStore, InitializingBean, DisposableBean {

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

    private static final String GLOBAL_QUOTA_NAME = "___GLOBAL_QUOTA___";

    private EntityStore entityStore;

    private final String cacheRootDir;

    private final TilePageCalculator tilePageCalculator;

    private static ExecutorService transactionRunner;

    private PrimaryIndex<String, TileSet> tileSetById;

    private PrimaryIndex<Integer, Quota> usedQuotaById;

    private PrimaryIndex<Long, TilePage> pageById;

    private PrimaryIndex<Long, PageStats> pageStatsById;

    private SecondaryIndex<String, String, TileSet> tileSetsByLayer;

    private SecondaryIndex<String, Long, TilePage> pageByKey;

    private SecondaryIndex<String, Long, TilePage> pagesByTileSetId;

    private SecondaryIndex<Long, Long, PageStats> pageStatsByPageId;

    private SecondaryIndex<Float, Long, PageStats> pageStatsByLRU;

    private SecondaryIndex<Float, Long, PageStats> pageStatsByLFU;

    private SecondaryIndex<String, Integer, Quota> usedQuotaByTileSetId;

    private volatile boolean open;

    private boolean diskQuotaEnabled;

    public BDBQuotaStore(final DefaultStorageFinder cacheDirFinder,
            TilePageCalculator tilePageCalculator) throws StorageException {

        Assert.notNull(cacheDirFinder, "cacheDirFinder can't be null");
        Assert.notNull(tilePageCalculator, "tilePageCalculator can't be null");

        this.tilePageCalculator = tilePageCalculator;
        this.cacheRootDir = cacheDirFinder.getDefaultPath();

        boolean disabled = Boolean.valueOf(cacheDirFinder.findEnvVar(GWC_DISKQUOTA_DISABLED))
                .booleanValue();
        if (disabled) {
            log.warn(" -- Found environment variable " + GWC_DISKQUOTA_DISABLED
                    + " set to true. DiskQuotaMonitor is disabled.");
        }
        this.diskQuotaEnabled = !disabled;
    }

    /**
     * Initialization method called by Spring, actually loads an applies the page store
     * configuration
     *
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
     */
    public void afterPropertiesSet() throws Exception {
        startUp();
    }

    public void startUp() throws InterruptedException {
        if (!diskQuotaEnabled) {
            log.info(getClass().getName() + " won't start, got env variable "
                    + GWC_DISKQUOTA_DISABLED + "=true");
            return;
        }
        open = true;
        File storeDirectory = new File(cacheRootDir, "diskquota_page_store");
        storeDirectory.mkdirs();
        CustomizableThreadFactory tf = new CustomizableThreadFactory("GWC DiskQuota Store Writer-");
        transactionRunner = Executors.newFixedThreadPool(1, tf);
        try {
            configure(storeDirectory);

            deleteStaleLayersAndCreateMissingTileSets();

            log.info("Berkeley DB JE Disk Quota page store configured at "
                    + storeDirectory.getAbsolutePath());
        } catch (RuntimeException e) {
            transactionRunner.shutdownNow();
            throw e;
        }
        log.info("Quota Store initialized. Global quota: " + getGloballyUsedQuota().toNiceString());
    }

    /**
     *
     * @see org.springframework.beans.factory.DisposableBean#destroy()
     */
    public void destroy() throws Exception {
        if (!diskQuotaEnabled) {
            return;
        }
        open = false;
        log.info("Requesting to close quota store...");
        transactionRunner.shutdown();
        try {
            transactionRunner.awaitTermination(30 * 1000, TimeUnit.MILLISECONDS);
        } catch (InterruptedException ie) {
            log.error("Time out shutting down quota store write thread, trying to "
                    + "close the entity store as is.", ie);
        } finally {
            Environment environment = entityStore.getEnvironment();
            entityStore.close();
            environment.close();
        }
        log.info("Quota store closed.");
    }

    private void configure(final File storeDirectory) throws InterruptedException {
        // todo: make config persistent? or just rely on je.properties (I guess so)
        PageStoreConfig config = new PageStoreConfig();
        EntityStoreBuilder builder = new EntityStoreBuilder(config);
        EntityStore entityStore = builder.buildEntityStore(storeDirectory, null);
        this.entityStore = entityStore;

        tileSetById = entityStore.getPrimaryIndex(String.class, TileSet.class);
        pageById = entityStore.getPrimaryIndex(Long.class, TilePage.class);
        pageStatsById = entityStore.getPrimaryIndex(Long.class, PageStats.class);
        usedQuotaById = entityStore.getPrimaryIndex(Integer.class, Quota.class);

        pageByKey = entityStore.getSecondaryIndex(pageById, String.class, "page_key");
        pagesByTileSetId = entityStore.getSecondaryIndex(pageById, String.class, "tileset_id_fk");
        tileSetsByLayer = entityStore.getSecondaryIndex(tileSetById, String.class, "layer");
        pageStatsByLRU = entityStore.getSecondaryIndex(pageStatsById, Float.class, "LRU");
        pageStatsByLFU = entityStore.getSecondaryIndex(pageStatsById, Float.class, "LFU");
        usedQuotaByTileSetId = entityStore.getSecondaryIndex(usedQuotaById, String.class,
                "tileset_id");
        pageStatsByPageId = entityStore.getSecondaryIndex(pageStatsById, Long.class,
                "page_stats_by_page_id");

    }

    private class StartUpInitializer implements Callable<Void> {
        public Void call() throws Exception {
            final Transaction transaction = entityStore.getEnvironment().beginTransaction(null,
                    null);
            try {
                if (null == usedQuotaByTileSetId.get(transaction, GLOBAL_QUOTA_NAME,
                        LockMode.DEFAULT)) {
                    log.debug("First time run: creating global quota object");
                    // need a global TileSet cause the Quota->TileSet relationship is enforced
                    TileSet globalTileSet = new TileSet(GLOBAL_QUOTA_NAME);
                    tileSetById.put(transaction, globalTileSet);

                    Quota globalQuota = new Quota();
                    globalQuota.setTileSetId(GLOBAL_QUOTA_NAME);
                    usedQuotaById.put(transaction, globalQuota);
                    log.debug("created Global Quota");
                }

                final Set<String> layerNames = tilePageCalculator.getLayerNames();
                final Set<String> existingLayers = new GetLayerNames().call();

                final Set<String> layersToDelete = new HashSet<String>(existingLayers);
                layersToDelete.removeAll(layerNames);

                for (String layerName : layersToDelete) {
                    log.info("Deleting disk quota information for layer '" + layerName
                            + "' as it does not exist anymore...");
                    // do not call issue since we're already running on the transaction thread here
                    try {
                        new DeleteLayer(layerName).call(transaction);
                    } catch (Exception e) {
                        log.warn("Error deleting disk quota information for layer '" + layerName
                                + "'", e);
                    }
                }

                // add any missing tileset
                for (String layerName : layerNames) {
                    createLayer(layerName, transaction);
                }
                transaction.commit();
            } catch (RuntimeException e) {
                transaction.abort();
                throw e;
            }
            return null;
        }

    }

    public void createLayer(final String layerName) throws InterruptedException {
        issueSync(new Callable<Void>() {

            public Void call() throws Exception {
                final Transaction transaction = entityStore.getEnvironment().beginTransaction(null,
                        null);
                try {
                    createLayer(layerName, transaction);
                    transaction.commit();
                } catch (RuntimeException e) {
                    transaction.abort();
                }
                return null;
            }
        });
    }

    private void createLayer(String layerName, final Transaction transaction) {
        Set<TileSet> layerTileSets = tilePageCalculator.getTileSetsFor(layerName);
        for (TileSet tset : layerTileSets) {
            getOrCreateTileSet(transaction, tset);
        }
    }

    private TileSet getOrCreateTileSet(final Transaction transaction, final TileSet tset) {
        String id = tset.getId();
        TileSet stored;
        if (null == (stored = tileSetById.get(transaction, id, LockMode.DEFAULT))) {
            log.debug("Creating TileSet for quota tracking: " + tset);
            tileSetById.putNoReturn(transaction, tset);
            stored = tset;
            Quota tileSetUsedQuota = new Quota();
            tileSetUsedQuota.setTileSetId(tset.getId());
            usedQuotaById.putNoReturn(transaction, tileSetUsedQuota);
        }
        return stored;
    }

    /**
     * Asynchronously issues the given {@code command} to the working transactional thread
     */
    private <E> Future<E> issue(final Callable<E> command) {
        if (!open) {
            throw new IllegalStateException("QuotaStore is closed.");
        }
        Future<E> future = transactionRunner.submit(command);
        return future;
    }

    /**
     * Synchronously issues the given {@code command} to the working transactional thread
     *
     * @throws InterruptedException
     *             in case the calling thread was interrupted while waiting for the command to
     *             complete
     */
    private <E> E issueSync(final Callable<E> command) throws InterruptedException {
        Future<E> result = issue(command);
        try {
            return result.get();
        } catch (RuntimeException e) {
            throw e;
        } catch (InterruptedException e) {
            log.debug("Caught InterruptedException while waiting for command "
                    + command.getClass().getSimpleName());
            throw e;
        } catch (ExecutionException e) {
            log.warn(e);
            Throwable cause = e.getCause();
            if (cause instanceof RuntimeException) {
                throw (RuntimeException) cause;
            }
            throw new RuntimeException(cause);
        }
    }

    private void deleteStaleLayersAndCreateMissingTileSets() throws InterruptedException {
        issueSync(new StartUpInitializer());
    }

    private class GetLayerNames implements Callable<Set<String>> {

        public Set<String> call() throws Exception {
            EntityCursor<String> layerNameCursor = tileSetsByLayer.keys(null, CursorConfig.DEFAULT);
            Set<String> names = new HashSet<String>();
            try {
                String name;
                while ((name = layerNameCursor.nextNoDup()) != null) {
                    if (!GLOBAL_QUOTA_NAME.equals(name)) {
                        names.add(name);
                    }
                }
            } finally {
                layerNameCursor.close();
            }
            return names;
        }

    }

    public Quota getGloballyUsedQuota() throws InterruptedException {
        return getUsedQuotaByTileSetId(GLOBAL_QUOTA_NAME);
    }

    public Quota getUsedQuotaByTileSetId(final String tileSetId) throws InterruptedException {
        Quota usedQuota = issueSync(new UsedQuotaByTileSetId(tileSetId));
        return usedQuota;
    }

    private final class UsedQuotaByTileSetId implements Callable<Quota> {
        private final String tileSetId;

        private UsedQuotaByTileSetId(String tileSetId) {
            this.tileSetId = tileSetId;
        }

        public Quota call() throws Exception {
            Quota quota = usedQuotaByTileSetId.get(null, tileSetId, LockMode.READ_COMMITTED);
            if (quota == null) {
                quota = new Quota();
            }
            return quota;
        }
    }

    private class DeleteLayer implements Callable<Void> {

        private final String layerName;

        public DeleteLayer(String layerName) {
            this.layerName = layerName;
        }

        public Void call() throws Exception {
            Transaction transaction = entityStore.getEnvironment().beginTransaction(null, null);
            try {
                call(transaction);
                transaction.commit();
            } catch (RuntimeException e) {
                transaction.abort();
                throw e;
            }
            return null;
        }

        public void call(Transaction transaction) {

            EntityCursor<TileSet> tileSets = tileSetsByLayer.entities(transaction, layerName, true,
                    layerName, true, null);
            try {
                TileSet tileSet;
                Quota freed;
                Quota global;
                while (null != (tileSet = tileSets.next())) {
                    freed = usedQuotaByTileSetId
                            .get(transaction, tileSet.getId(), LockMode.DEFAULT);
                    global = usedQuotaByTileSetId.get(transaction, GLOBAL_QUOTA_NAME,
                            LockMode.DEFAULT);

                    tileSets.delete();
                    global.subtract(freed.getBytes());
                    usedQuotaById.put(transaction, global);
                }
            } finally {
                tileSets.close();
            }
        }

    }

    public void deleteLayer(final String layerName) {
        Assert.notNull(layerName);
        issue(new DeleteLayer(layerName));
    }

    public void renameLayer(String oldLayerName, String newLayerName) throws InterruptedException {
        Assert.notNull(oldLayerName);
        Assert.notNull(newLayerName);
        issueSync(new RenameLayer(oldLayerName, newLayerName));
    }

    private class RenameLayer implements Callable<Void> {

        private final String oldLayerName;

        private final String newLayerName;

        public RenameLayer(final String oldLayerName, final String newLayerName) {
            this.oldLayerName = oldLayerName;
            this.newLayerName = newLayerName;
        }

        /**
         * Copy over old {@link TileSet}s, used {@link Quota}s and {@link TilePage}s from
         * oldLayerName to newLayerName and delete the old ones
         *
         * @see java.util.concurrent.Callable#call()
         */
        public Void call() throws Exception {
            Transaction transaction = entityStore.getEnvironment().beginTransaction(null, null);
            try {
                copyTileSets(transaction);
                DeleteLayer deleteCommand = new DeleteLayer(oldLayerName);
                deleteCommand.call(transaction);
                transaction.commit();
            } catch (RuntimeException e) {
                transaction.abort();
                throw e;
            }
            return null;
        }

        private void copyTileSets(Transaction transaction) {
            EntityCursor<TileSet> tileSets = tileSetsByLayer.entities(transaction, oldLayerName,
                    true, oldLayerName, true, null);
            try {
                TileSet oldTileSet;
                TileSet newTileSet;
                Quota oldQuota;
                Quota newQuota;
                TilePage oldPage;
                TilePage newPage;
                while (null != (oldTileSet = tileSets.next())) {
                    final String gridsetId = oldTileSet.getGridsetId();
                    final String blobFormat = oldTileSet.getBlobFormat();
                    final Long parametersId = oldTileSet.getParametersId();
                    newTileSet = new TileSet(newLayerName, gridsetId, blobFormat, parametersId);
                    // this creates the tileset's empty used Quota too
                    newTileSet = getOrCreateTileSet(transaction, newTileSet);

                    final String oldTileSetId = oldTileSet.getId();
                    final String newTileSetId = newTileSet.getId();

                    oldQuota = usedQuotaByTileSetId
                            .get(transaction, oldTileSetId, LockMode.DEFAULT);
                    newQuota = usedQuotaByTileSetId
                            .get(transaction, newTileSetId, LockMode.DEFAULT);
                    newQuota.setBytes(oldQuota.getBytes());
                    usedQuotaById.putNoReturn(transaction, newQuota);

                    EntityCursor<TilePage> oldPages = pagesByTileSetId.entities(transaction,
                            oldTileSetId, true, oldTileSetId, true, CursorConfig.DEFAULT);
                    try {
                        while (null != (oldPage = oldPages.next())) {
                            long oldPageId = oldPage.getId();
                            newPage = new TilePage(newTileSetId, oldPage.getPageX(),
                                    oldPage.getPageY(), oldPage.getZoomLevel());
                            pageById.put(transaction, newPage);
                            PageStats pageStats = pageStatsByPageId.get(oldPageId);
                            if (pageStats != null) {
                                pageStats.setPageId(newPage.getId());
                                pageStatsById.putNoReturn(transaction, pageStats);
                            }
                        }
                    } finally {
                        oldPages.close();
                    }
                }
            } finally {
                tileSets.close();
            }
        }
    }

    /**
     *
     * @param layerName
     * @return the used quota for the given layer, may need to create a new one before returning if
     *         no quota usage information for that layer already exists
     * @throws InterruptedException
     */
    public Quota getUsedQuotaByLayerName(final String layerName) throws InterruptedException {
        return issueSync(new UsedQuotaByLayerName(layerName));
    }

    private final class UsedQuotaByLayerName implements Callable<Quota> {
        private final String layerName;

        public UsedQuotaByLayerName(final String layerName) {
            this.layerName = layerName;
        }

        public Quota call() throws Exception {
            Quota aggregated = null;

            EntityCursor<TileSet> layerTileSetsIds;
            layerTileSetsIds = tileSetsByLayer.entities(null, layerName, true, layerName, true,
                    CursorConfig.DEFAULT);
            TileSet tileSet;
            try {
                Quota tileSetUsedQuota;
                while (null != (tileSet = layerTileSetsIds.next())) {
                    if (aggregated == null) {
                        aggregated = new Quota();
                    }
                    tileSetUsedQuota = new UsedQuotaByTileSetId(tileSet.getId()).call();
                    aggregated.add(tileSetUsedQuota);
                }
            } finally {
                layerTileSetsIds.close();
            }
            if (aggregated == null) {
                aggregated = new Quota();
            }

            return aggregated;
        }
    }

    public long[][] getTilesForPage(TilePage page) throws InterruptedException {
        TileSet tileSet = getTileSetById(page.getTileSetId());
        long[][] gridCoverage = tilePageCalculator.toGridCoverage(tileSet, page);
        return gridCoverage;
    }

    public Set<TileSet> getTileSets() {
        Map<String, TileSet> map = new HashMap<String, TileSet>(tileSetById.map());
        map.remove(GLOBAL_QUOTA_NAME);
        HashSet<TileSet> hashSet = new HashSet<TileSet>(map.values());
        return hashSet;
    }

    public TileSet getTileSetById(final String tileSetId) throws InterruptedException {
        return issueSync(new Callable<TileSet>() {

            public TileSet call() throws Exception {
                TileSet tileSet = tileSetById.get(tileSetId);
                if (tileSet == null) {
                    throw new IllegalArgumentException("TileSet does not exist: " + tileSetId);
                }
                return tileSet;
            }
        });
    }

    public TilePageCalculator getTilePageCalculator() {
        return tilePageCalculator;
    }

    /**
     * Adds the {@link TilePage#getNumPresentTilesInPage() number of tiles} present in each of the
     * argument pages
     *
     * @param quotaDiff
     *
     * @param tileCountDiffs
     * @throws InterruptedException
     */
    public void addToQuotaAndTileCounts(final TileSet tileSet, final Quota quotaDiff,
            final Collection<PageStatsPayload> tileCountDiffs) throws InterruptedException {
        issueSync(new AddToQuotaAndTileCounts(tileSet, quotaDiff, tileCountDiffs));
    }

    private class AddToQuotaAndTileCounts implements Callable<Void> {

        private final TileSet tileSet;

        private final Collection<PageStatsPayload> tileCountDiffs;

        private final Quota quotaDiff;

        public AddToQuotaAndTileCounts(final TileSet tileSet, Quota quotaDiff,
                final Collection<PageStatsPayload> tileCountDiffs) {
            this.tileSet = tileSet;
            this.quotaDiff = quotaDiff;
            this.tileCountDiffs = tileCountDiffs;
        }

        public Void call() throws Exception {
            final Transaction tx = entityStore.getEnvironment().beginTransaction(null, null);
            try {
                TileSet storedTileset = getOrCreateTileSet(tx, tileSet);
                // increase the tileset used quota
                addToUsedQuota(tx, storedTileset, quotaDiff);

                // and each page's fillFactor for lru/lfu expiration
                if (tileCountDiffs.size() > 0) {
                    TilePage page;
                    String pageKey;
                    for (PageStatsPayload payload : tileCountDiffs) {
                        page = payload.getPage();
                        pageKey = page.getKey();
                        PageStats pageStats;

                        TilePage storedPage = pageByKey.get(tx, pageKey, LockMode.DEFAULT);
                        if (null == storedPage) {
                            pageById.put(tx, page);
                            storedPage = page;
                            pageStats = new PageStats(storedPage.getId());
                            // pageStatsById.put(tx, pageStats);
                        } else {
                            pageStats = pageStatsByPageId.get(tx, storedPage.getId(), null);
                        }

                        final byte level = page.getZoomLevel();
                        final BigInteger tilesPerPage = tilePageCalculator.getTilesPerPage(tileSet,
                                level);
                        final int tilesAdded = payload.getNumTiles();

                        pageStats.addTiles(tilesAdded, tilesPerPage);
                        pageStatsById.putNoReturn(tx, pageStats);
                    }
                }
                tx.commit();
                return null;
            } catch (RuntimeException e) {
                e.printStackTrace();
                tx.abort();
                throw e;
            }
        }

        private void addToUsedQuota(final Transaction tx, final TileSet tileSet,
                final Quota quotaDiff) {
            Quota usedQuota = usedQuotaByTileSetId.get(tx, tileSet.getId(), LockMode.DEFAULT);
            Quota globalQuota = usedQuotaByTileSetId.get(tx, GLOBAL_QUOTA_NAME, LockMode.DEFAULT);

            usedQuota.add(quotaDiff);
            globalQuota.add(quotaDiff);

            usedQuotaById.putNoReturn(tx, usedQuota);
            usedQuotaById.putNoReturn(tx, globalQuota);
        }

    }

    /**
     * Asynchronously updates (or set if not exists) the
     * {@link PageStats#getFrequencyOfUsePerMinute()} and
     * {@link PageStats#getLastAccessTimeMinutes()} values for the stored versions of the page
     * statistics using {@link PageStats#addHits(long)}; these values are influenced by the
     * {@code PageStats}' {@link PageStats#getFillFactor() fillFactor}.
     *
     * @param statsUpdates
     * @return
     */
    public Future<List<PageStats>> addHitsAndSetAccesTime(
            final Collection<PageStatsPayload> statsUpdates) {

        Assert.notNull(statsUpdates);

        return issue(new AddHitsAndSetAccesTime(statsUpdates));
    }

    /**
     *
     *
     */
    private class AddHitsAndSetAccesTime implements Callable<List<PageStats>> {

        private final Collection<PageStatsPayload> statsUpdates;

        public AddHitsAndSetAccesTime(Collection<PageStatsPayload> statsUpdates) {
            this.statsUpdates = statsUpdates;
        }

        public List<PageStats> call() throws Exception {
            List<PageStats> allStats = new ArrayList<PageStats>(statsUpdates.size());
            PageStats pageStats = null;
            final Transaction tx = entityStore.getEnvironment().beginTransaction(null, null);
            try {
                for (PageStatsPayload payload : statsUpdates) {
                    TilePage page = payload.getPage();
                    TileSet storedTileset = tileSetById.get(tx, page.getTileSetId(),
                            LockMode.DEFAULT);
                    if (null == storedTileset) {
                        log.info("Can't add usage stats. TileSet does not exist. Was it deleted? "
                                + page.getTileSetId());
                        continue;
                    }

                    TilePage storedPage = pageByKey.get(tx, page.getKey(), null);

                    if (storedPage == null) {
                        pageById.put(tx, page);
                        storedPage = page;
                        pageStats = new PageStats(storedPage.getId());
                    } else {
                        pageStats = pageStatsByPageId.get(tx, storedPage.getId(), null);
                    }

                    final int addedHits = payload.getNumHits();
                    final int lastAccessTimeMinutes = (int) (payload.getLastAccessTime() / 1000 / 60);
                    final int creationTimeMinutes = storedPage.getCreationTimeMinutes();
                    pageStats.addHitsAndAccessTime(addedHits, lastAccessTimeMinutes,
                            creationTimeMinutes);
                    pageStatsById.putNoReturn(tx, pageStats);
                    allStats.add(pageStats);
                }
                tx.commit();
                return allStats;
            } catch (RuntimeException e) {
                tx.abort();
                throw e;
            }
        }
    }

    /**
     * @param layerNames
     * @return
     * @throws InterruptedException
     */
    public TilePage getLeastFrequentlyUsedPage(final Set<String> layerNames)
            throws InterruptedException {

        SecondaryIndex<Float, Long, PageStats> expirationPolicyIndex = pageStatsByLFU;
        TilePage nextToExpire = issueSync(new FindPageToExpireByLayer(expirationPolicyIndex,
                layerNames));

        return nextToExpire;
    }

    /**
     * @param layerNames
     * @return
     * @throws InterruptedException
     */
    public TilePage getLeastRecentlyUsedPage(final Set<String> layerNames)
            throws InterruptedException {
        SecondaryIndex<Float, Long, PageStats> expirationPolicyIndex = pageStatsByLRU;
        TilePage nextToExpire = issueSync(new FindPageToExpireByLayer(expirationPolicyIndex,
                layerNames));

        return nextToExpire;
    }

    /**
     * @param expirationPolicyIndex
     * @param layerNames
     * @return
     */
    private class FindPageToExpireByLayer implements Callable<TilePage> {
        private final SecondaryIndex<Float, Long, PageStats> expirationPolicyIndex;

        private final Set<String> layerNames;

        public FindPageToExpireByLayer(
                SecondaryIndex<Float, Long, PageStats> expirationPolicyIndex, Set<String> layerNames) {
            this.expirationPolicyIndex = expirationPolicyIndex;
            this.layerNames = layerNames;
        }

        public TilePage call() throws Exception {

            // find out the tilesets for the requested layers
            final Set<String> tileSetIds = new HashSet<String>();
            for (String layerName : layerNames) {
                EntityCursor<TileSet> keys = tileSetsByLayer.entities(layerName, true, layerName,
                        true);
                try {
                    TileSet tileSet;
                    while ((tileSet = keys.next()) != null) {
                        tileSetIds.add(tileSet.getId());
                    }
                } finally {
                    keys.close();
                }
            }

            TilePage nextToExpire = null;
            // find out the LRU page that matches a requested tileset
            final EntityCursor<PageStats> pageStatsCursor = expirationPolicyIndex.entities();

            try {
                String tileSetId;
                long pageId;
                PageStats pageStats;
                while ((pageStats = pageStatsCursor.next()) != null) {
                    if (pageStats.getFillFactor() > 0) {
                        pageId = pageStats.getPageId();
                        TilePage tilePage = pageById.get(pageId);
                        tileSetId = tilePage.getTileSetId();
                        if (tileSetIds.contains(tileSetId)) {
                            nextToExpire = tilePage;
                            break;
                        }
                    }
                }
            } finally {
                pageStatsCursor.close();
            }

            return nextToExpire;
        }
    }

    public PageStats setTruncated(final TilePage tilePage) throws InterruptedException {
        return issueSync(new TruncatePage(tilePage));
    }

    private class TruncatePage implements Callable<PageStats> {
        private final TilePage tilePage;

        public TruncatePage(TilePage tilePage) {
            this.tilePage = tilePage;
        }

        public PageStats call() throws Exception {
            Transaction tx = entityStore.getEnvironment().beginTransaction(null, null);
            try {
                PageStats pageStats = pageStatsByPageId.get(tx, tilePage.getId(), null);
                if (pageStats != null) {
                    pageStats.setFillFactor(0f);
                    pageStatsById.putNoReturn(tx, pageStats);
                }
                tx.commit();
                return pageStats;
            } catch (Exception e) {
                tx.abort();
                throw e;
            }
        }
    }

}
TOP

Related Classes of org.geowebcache.diskquota.storage.BDBQuotaStore$UsedQuotaByTileSetId

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.