-
-
Save rsrini7/ea80d339e2441e4a56be4b5cd513f3bb to your computer and use it in GitHub Desktop.
Decorator of net.sf.ehcache.Ehcache. This class is an equivalent of an asynchronous net.sf.ehcache.constructs.blocking.SelfPopulatingCache. All cache entry of this cache will be eternal AND will have a timeToLive. When an element is "expired" (time of cr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ...; | |
import java.util.HashMap; | |
import java.util.Map; | |
import java.util.concurrent.ExecutorService; | |
import java.util.concurrent.Executors; | |
import java.util.concurrent.Semaphore; | |
import java.util.concurrent.TimeUnit; | |
import net.sf.ehcache.CacheException; | |
import net.sf.ehcache.Ehcache; | |
import net.sf.ehcache.Element; | |
import net.sf.ehcache.constructs.blocking.CacheEntryFactory; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
/** | |
* Decorator of net.sf.ehcache.Ehcache. This class is an equivalent of an asynchronous | |
* net.sf.ehcache.constructs.blocking.SelfPopulatingCache. All cache entry of this cache will be eternal AND will have a timeToLive. When a | |
* element is "expired" (time of creation + time to live < now) the cache will return this "expired" objet but will asynchronously update | |
* this cache entry by delegating its creation to a net.sf.ehcache.constructs.blocking.CacheEntryFactory if it is not already being created. | |
*/ | |
public class AsynchronousCache { | |
private static final Logger LOGGER = LoggerFactory.getLogger(AsynchronousCache.class); | |
private static final int DEFAULT_TIME_TO_LIVE = 60; | |
/** | |
* ExecutorService responsible of asynchronously delegate the creation of a new cache entry and update the cache. | |
*/ | |
private ExecutorService executor = Executors.newSingleThreadExecutor(); | |
/** | |
* Map of semaphore to control that a cache entry is not already being updated. | |
*/ | |
private Map<Object, Semaphore> semaphoreMap = new HashMap<Object, Semaphore>(); | |
/** The cache decorated by this class. */ | |
private Ehcache cache; | |
/** Responsible of creating new cache entries. */ | |
private CacheEntryFactory factory; | |
/** | |
* Looks up an entry. All cache entry of this cache will be eternal AND will have a timeToLive. When a element is "expired" (time of | |
* creation + time to live < now) the cache will return this "expired" objet but will asynchronously update this cache entry by | |
* delegating its creation to a net.sf.ehcache.constructs.blocking.CacheEntryFactory if it is not already being created. | |
*/ | |
public Element get(final Object key) { | |
Element element = cache.get(key); | |
if (element != null && isUpToDate(element)) { | |
LOGGER.debug("cache hit for key : {}", key); | |
} else { | |
LOGGER.debug("entry expired for key : {}", key); | |
try { | |
executor.execute(new RefreshElementTask(key)); | |
} catch (final Exception e) { | |
throw new CacheException("Could not fetch object for cache entry with key " + key, e); | |
} | |
} | |
return element; | |
} | |
/** | |
* Return true if an element is up to date : (creation time + time to live) > now. | |
* | |
* @param element the cache element. | |
* @return true if an element is up to date. | |
*/ | |
private boolean isUpToDate(Element element) { | |
long now = System.currentTimeMillis(); | |
long expirationTime = element.getCreationTime() + (TimeUnit.SECONDS.toMillis(element.getTimeToLive())); | |
return now < expirationTime; | |
} | |
private final class RefreshElementTask implements Runnable { | |
private final Object key; | |
private RefreshElementTask(Object key) { | |
this.key = key; | |
} | |
@Override | |
public void run() { | |
final Semaphore semaphore; | |
if (semaphoreMap.get(key) == null) { | |
semaphoreMap.put(key, new Semaphore(1)); | |
} | |
semaphore = semaphoreMap.get(key); | |
if (semaphore.tryAcquire()) { | |
LOGGER.debug("semaphore acquired for key : {}", key); | |
try { | |
refreshElement(); | |
} catch (Exception e) { | |
LOGGER.warn("Error while retrieving entry for key " + key, e); | |
extendsElementTimeToLive(); | |
} finally { | |
semaphore.release(); | |
} | |
} else { | |
LOGGER.debug("Entry for key {} already being retrieved", key); | |
} | |
} | |
private void refreshElement() throws Exception { | |
/* | |
* Another get to avoid this bug : | |
* Thead-1 : cache miss | |
* Thead-1 : acquire | |
* Thead-2 : cache miss | |
* Thead-1 : release | |
* Thead-2 : acquire | |
* Thead-2 : release | |
*/ | |
Element element = cache.get(key); | |
if (element != null && isUpToDate(element)) { | |
LOGGER.debug("cache hit for key : {}", key); | |
} else { | |
Object entry = factory.createEntry(key); | |
element = makeAndCheckElement(key, entry); | |
if (entry != null) { | |
putElementInCache(element); | |
} | |
} | |
} | |
private void extendsElementTimeToLive() { | |
Element element = cache.get(key); | |
if (element != null) { | |
element = new Element(key, element.getValue()); | |
element.setTimeToLive(DEFAULT_TIME_TO_LIVE); | |
} else { | |
element = new Element(key, null); | |
element.setTimeToLive(DEFAULT_TIME_TO_LIVE); | |
} | |
putElementInCache(element); | |
} | |
private void putElementInCache(Element element) { | |
// Protection : setEternal set timeToLive to 0 for ehcache 2.0+ | |
int timeToLive = element.getTimeToLive(); | |
element.setEternal(true); | |
element.setTimeToLive(timeToLive); | |
cache.put(element); | |
LOGGER.debug("put in cache key : {}", key); | |
} | |
/** | |
* Create an net.sf.ehcache.Element object from the value return by | |
* net.sf.ehcache.constructs.blocking.CacheEntryFactory.createEntry(Object). | |
* | |
* @param key the cache entry key. | |
* @param value the cache entry value. | |
* @return the net.sf.ehcache.Element created. | |
* @throws CacheException | |
*/ | |
private Element makeAndCheckElement(Object key, Object value) { | |
// check if null | |
if (value == null) { | |
return new Element(key, null); | |
} | |
// simply build a new element using the supplied key | |
if (!(value instanceof Element)) { | |
return new Element(key, value); | |
} | |
// It is already an element - perform sanity checks | |
Element element = (Element)value; | |
if ((element.getObjectKey() == null) && (key == null)) { | |
return element; | |
} else if (element.getObjectKey() == null) { | |
throw new CacheException("CacheEntryFactory returned an Element with a null key"); | |
} else if (!element.getObjectKey().equals(key)) { | |
throw new CacheException("CacheEntryFactory returned an Element with a different key: " + element.getObjectKey() | |
+ " compared to the key that was requested: " + key); | |
} else { | |
return element; | |
} | |
} | |
} | |
// GETTERS/SETTERS | |
public void setCache(Ehcache cache) { | |
this.cache = cache; | |
} | |
public void setFactory(CacheEntryFactory factory) { | |
this.factory = factory; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment