/** * Copyright (C) 2012-2014 Gist Labs, LLC. (http://gistlabs.com) * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package com.gistlabs.mechanize.cache.inMemory; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; import com.gistlabs.mechanize.cache.api.CacheEntry; import com.gistlabs.mechanize.cache.api.HttpCache; /** * An in-memory HttpCache, by default capped to 64MB of response content. (This only counts the entity length). * @author jheintz * */ public class InMemoryHttpCache implements HttpCache { final ConcurrentMap<String,CacheEntry> cache = new ConcurrentHashMap<String,CacheEntry>(1024); final ConcurrentLinkedQueue<String> uriFifo = new ConcurrentLinkedQueue<String>(); final long maxBytes; final AtomicLong currentBytes = new AtomicLong(0); public InMemoryHttpCache() { this(64); } public InMemoryHttpCache(final int numberOfMegabytes) { maxBytes = 1024 * 1024 * numberOfMegabytes; } @Override public String toString() { return String.format("InMemoryHttpCache[current=%skb]", currentBytes.get()/1024); } @Override public CacheEntry get(final String uri) { CacheEntry entry = cache.get(uri); if (entry!=null) { // refresh LRU entry.getResponse().setHeader("Via", "mechanize"); uriFifo.remove(uri); uriFifo.offer(uri); } return entry; } @Override public void remove(final String uri) { CacheEntry removed = cache.remove(uri); uriFifo.remove(uri); if (removed!=null) currentBytes.addAndGet(-getByteCount(removed)); } @Override public boolean putIfAbsent(final String uri, final CacheEntry maybe) { if (!ensureCapacity(getByteCount(maybe))) return false; // and don't cache CacheEntry previous = cache.putIfAbsent(uri, maybe); currentBytes.addAndGet(getByteCount(maybe) - getByteCount(previous)); return true; } @Override public boolean replace(final String uri, final CacheEntry cachedValue, final CacheEntry maybe) { if (!ensureCapacity(getByteCount(maybe))) { remove(uri); return false; // and don't cache } boolean replaced = cache.replace(uri, cachedValue, maybe); if (replaced) currentBytes.addAndGet(getByteCount(maybe)-getByteCount(cachedValue)); return true; } protected boolean ensureCapacity(final long byteCount) { if (byteCount>maxBytes) return false; while(currentBytes.get()+byteCount > maxBytes) remove(uriFifo.poll()); return true; } /** * null safe get byte count helper * * @param entry * @return */ protected long getByteCount(final CacheEntry entry) { return entry==null ? 0 : entry.byteCount(); } }