import org.apache.log4j.Logger; import org.hibernate.exception.ExceptionUtils; import net.sf.ehcache.CacheException; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Element; import net.sf.ehcache.constructs.blocking.SelfPopulatingCache; import net.sf.ehcache.Ehcache; import net.sf.ehcache.constructs.blocking.CacheEntryFactory; import net.sf.ehcache.event.CacheEventListener; /** * SelfPopulatingAgedCache is a SelfPopulatingCache * which means it uses a CacheEntryFactory to create missing elements * and return them from get. It also inherits the ability to block * other attempts to get the same key while a load is in progress with * an upper bound timeout (which does not apply to the client that first * tries to load the key). If you want to keep the cache fresh with * a background thread and have the same upper bound for all calls, * look into also using an ehcache Loader with this. * * This class adds a add way to set it's timeout in the constructor, * and also tests for expired elements and reloads them transparently. * * ehcache.xml can only setup the basic cache properties (age till timeout * and so forth), so to use this decorated cache, install it like this: * * MyImplConstructor or similar init() method { * Ehcache c = CacheManager.getInstance().getEhcache("com.netjets.ThingCache"); * if (!(c instanceof SelfPopulatingAgedCache)) { * c = new SelfPopulatingAgedCache(c, categoryGetter, 5000); * } * * All this make final usage a one-liner: * * Thing getThing(Object key) { * return (Thing) CacheManager.getInstance().getEhcache("com.netjets.ThingCache") * .get(key).getValue(); * * @author pinkham */ public class SelfPopulatingAgedCache extends SelfPopulatingCache { static final Logger logger = Logger.getLogger(SelfPopulatingAgedCache.class); public SelfPopulatingAgedCache(Ehcache c, CacheEntryFactory ef, int timeout) { // super sets factory to this wrapper super(c, new ExceptionEatingCacheEntryFactoryWrapper(ef, "Credential token expired")); setTimeoutMillis(timeout); CacheManager.getInstance().replaceCacheWithDecoratedCache(c, this); // cache factory wrapper is also a listener but we need to register it getCacheEventNotificationService().registerListener((CacheEventListener)factory); } @Override public Element get(Object key) { try { return super.get(key); // decorated base cache handles expiration } catch (CacheException e) { // for some reason, CacheException doesn't call super(msg, cause) but has it's own cause field. // Add the cache name too // finally, subclass RuntimeException with empty {} // to get the more descriptive exception name // SelfPopulatingAgedCache$RuntimeException. RuntimeException e2 = new RuntimeException( e.getMessage()+ " for cache: "+cache.getName(), e.getInitialCause()){}; //e2.setStackTrace(e.getStackTrace()); // as if they had done it right throw e2; } } // TODO: Fix the real root cause, but meanwhile, this might buy us some time QC 44371 protected static final class ExceptionEatingCacheEntryFactoryWrapper implements CacheEventListener, CacheEntryFactory { private CacheEntryFactory factory; private String contains; protected ExceptionEatingCacheEntryFactoryWrapper(CacheEntryFactory factory, String contains) { this.factory = factory; this.contains = contains; } private Element lastExpiredElement; public Object createEntry(Object key) throws Exception { try { Object ret = factory.createEntry(key); lastExpiredElement = null; // don't leak memory if there's no exception return ret; } catch (Exception e) { if (contains == null || ExceptionUtils.getStackTrace(e).contains(contains)) { if (lastExpiredElement != null && lastExpiredElement.getKey().equals(key)) { Object ret = lastExpiredElement.getObjectValue(); // keep re-using it's data logger.warn(factory.getClass().getName()+ " Ignoring exception and using old cache value: ",e); return ret; } logger.warn(factory.getClass().getName()+ " Unable to ignore first exception since cache has no old value: "+e); } throw e; } } public void dispose() {} public void notifyElementEvicted(Ehcache ehcache, Element element) {} public void notifyElementExpired(Ehcache ehcache, Element element) { lastExpiredElement = element; // save it before it goes away } public void notifyElementPut(Ehcache ehcache, Element element) {} public void notifyElementRemoved(Ehcache ehcache, Element element) {} public void notifyElementUpdated(Ehcache ehcache, Element element) {} public void notifyRemoveAll(Ehcache ehcache) {} public Object clone() throws CloneNotSupportedException { return super.clone(); } } }