package de.axone.cache.ng; import java.text.DateFormat; import java.util.Date; import de.axone.cache.ng.CacheNG.Cache; import de.axone.data.Duration; public class CacheWrapperDelayedInvalidation<K,O> extends CacheWrapper<K,O> { private final long timeoutDuration; private long timeoutStart; public CacheWrapperDelayedInvalidation( Cache<K, O> wrapped, long timeoutDuration ) { super( wrapped ); this.timeoutDuration = timeoutDuration; } @Override public void invalidateAll( boolean force ) { if( force ){ wrapped.invalidateAll( force ); } else { timeoutStart = System.currentTimeMillis(); } } private boolean isAlive( K key, CacheNG.Cache.Entry<O> entry ){ // Entry is newer than starting of timeout. // This happend regularily if the entry is re-fetched after invalidation if( entry.creation() > timeoutStart ) return true; long elapsed = System.currentTimeMillis() - timeoutStart; // Invalid immediately if > duration if( elapsed >= timeoutDuration ) return false; long stretch = Integer.MAX_VALUE / timeoutDuration; int random = RandomMapper.positiveInteger( key.hashCode() ); if( stretch * elapsed < random ){ return false; } return true; } @Override public Entry<O> fetchEntry( K key ) { Entry<O> entry = super.fetchEntry( key ); if( entry == null ) return null; if( ! isAlive( key, entry ) ){ wrapped.invalidate( key ); return null; } return entry; } @Override public boolean isCached( K key ) { // This is quick if( ! super.isCached( key ) ) return false; // Fetch for further checks Cache.Entry<O> entry = super.fetchEntry( key ); return entry != null && isAlive( key, entry ); } @Override public String info() { return super.info() + String.format( "[Delayed (%.0fs) %s]", Duration.inSeconds( timeoutDuration ), DateFormat.getDateTimeInstance().format( new Date( timeoutStart+timeoutDuration ) ) ); } }