/** * 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/>. */ package org.geowebcache.storage; import java.io.IOException; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import org.geowebcache.io.ByteArrayResource; import org.geowebcache.io.Resource; import org.geowebcache.mime.MimeType; import org.geowebcache.storage.blobstore.file.FilePathGenerator; import com.google.common.base.Preconditions; import com.google.common.base.Ticker; /** * Non-thread safe Resource cache. Currently in-memory only. * * @author Ian Schneider <ischneider@opengeo.org> * @author Kevin Smith, Boundless */ public class TransientCache { private final int maxTiles; private final int maxStorage; private final long expireDelay; private long currentStorage; private Ticker ticker = Ticker.systemTicker(); /** * A path generator that uses the key set as its key to build keys suitable for usage in the in * memory transient cache */ private static FilePathGenerator keyGenerator = new FilePathGenerator(""); private Map<String, CachedResource> cache = new LinkedHashMap<String, CachedResource>() { /** serialVersionUID */ private static final long serialVersionUID = -4106644240603796847L; @Override protected boolean removeEldestEntry(Entry<String, CachedResource> eldest) { return removeEntries(eldest); } }; /** * @deprecated Use {@link #TransientCache(int,int,long)} instead */ public TransientCache(int maxTiles, int maxStorageKB) { this(maxTiles, maxStorageKB, 2000); } /** * * @param maxTiles Maximum number of tiles in cache * @param maxStorageKB Maximum size of cached data in KiB * @param expireDelay Duration for which the cached resource is valid in ms */ public TransientCache(int maxTiles, int maxStorageKB, long expireDelay) { this.maxTiles = maxTiles; this.maxStorage = maxStorageKB * 1024; this.expireDelay = expireDelay; } /** * Count of cached resources. May include expired resources not yet cleared. * @return */ public int size() { return cache.size(); } /** * The currently used storage. May include expired resources not yet cleared. * @return */ public long storageSize() { return currentStorage; } /** * Store a resource * @param key key to store the resource under * @param r the resource to cache */ public void put(String key, Resource r) { byte[] buf = new byte[(int) r.getSize()]; try { r.getInputStream().read(buf); } catch (IOException ex) { throw new RuntimeException(ex); } CachedResource blob = new CachedResource(new ByteArrayResource(buf)); currentStorage += r.getSize(); cache.put(key, blob); } /** * Retrieve a resource * @param key * @return The resource cached under the given key, or null if no resource is cached. */ public Resource get(String key) { CachedResource cached = cache.get(key); if (cached != null) { cache.remove(key); currentStorage -= cached.content.getSize(); if( cached.time+expireDelay < currentTime() ) { return null; } else { return cached.content; } } return null; } /** * A timestamp in milliseconds * @return */ protected long currentTime() { return ticker.read()/1000; } // Gets called by overridden LinkedHashMap.removeEldestEntry private boolean removeEntries(Entry<String, CachedResource> eldest) { // iterator returns items in order added so oldest items are first Iterator<CachedResource> items = cache.values().iterator(); while (items.hasNext() && (currentStorage>maxStorage || cache.size()>maxTiles)) { CachedResource r = items.next(); currentStorage -= r.content.getSize(); items.remove(); } assert currentStorage<=maxStorage; assert currentStorage>=0; assert cache.size()<=maxStorage; return false; } public static String computeTransientKey(TileObject tile) { try { MimeType mime = MimeType.createFromFormat(tile.getBlobFormat()); return keyGenerator.tilePath(tile, mime).getAbsolutePath(); } catch (Exception ex) { throw new RuntimeException(ex); } } private class CachedResource { Resource content; long time; public CachedResource(Resource content, long time) { super(); this.content = content; this.time = time; } public CachedResource(Resource content) { this(content, currentTime()); } } /** * Set a time source for computing expiry. * @param ticker */ public void setTicker(Ticker ticker) { Preconditions.checkNotNull(ticker); this.ticker=ticker; } }