package org.togglz.core.repository.cache;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.togglz.core.Feature;
import org.togglz.core.repository.FeatureState;
import org.togglz.core.repository.StateRepository;
/**
*
* Simple implementation of {@link StateRepository} which adds caching capabilities to an existing repository. You should
* consider using this class if lookups in your {@link StateRepository} are expensive (like database queries).
*
* @author Christian Kaltepoth
*
*/
public class CachingStateRepository implements StateRepository {
private final StateRepository delegate;
private final Map<String, CacheEntry> cache = new HashMap<String, CacheEntry>();
private long ttl;
/**
* Creates a caching facade for the supplied {@link StateRepository}. The cached state of a feature will only expire if
* {@link #setFeatureState(FeatureState)} is invoked. You should therefore never use this constructor if the feature state
* is modified directly (for example by modifying the database table or the properties file).
*
* @param delegate The repository to delegate invocations to
*/
public CachingStateRepository(StateRepository delegate) {
this(delegate, 0);
}
/**
* Creates a caching facade for the supplied {@link StateRepository}. The cached state of a feature will expire after the
* supplied TTL or if {@link #setFeatureState(FeatureState)} is invoked.
*
* @param delegate The repository to delegate invocations to
* @param ttl The time in milliseconds after which a cache entry will expire
* @throws IllegalArgumentException if the specified ttl is negative
*/
public CachingStateRepository(StateRepository delegate, long ttl) {
if (ttl < 0) {
throw new IllegalArgumentException("Negative TTL value: " + ttl);
}
this.delegate = delegate;
this.ttl = ttl;
}
/**
* Creates a caching facade for the supplied {@link StateRepository}. The cached state of a feature will expire after the
* supplied TTL rounded down to milliseconds or if {@link #setFeatureState(FeatureState)} is invoked.
*
* @param delegate The repository to delegate invocations to
* @param ttl The time in a given {@code ttlTimeUnit} after which a cache entry will expire
* @param ttlTimeUnit The unit that {@code ttl} is expressed in
*/
public CachingStateRepository(StateRepository delegate, long ttl, TimeUnit ttlTimeUnit) {
this(delegate, ttlTimeUnit.toMillis(ttl));
}
@Override
public FeatureState getFeatureState(Feature feature) {
// first try to find it from the cache
CacheEntry entry = cache.get(feature.name());
if (entry != null && !isExpired(entry)) {
return entry.getState() != null ? entry.getState().copy() : null;
}
// no cache hit
FeatureState featureState = delegate.getFeatureState(feature);
// cache the result (may be null)
cache.put(feature.name(), new CacheEntry(featureState != null ? featureState.copy() : null));
// return the result
return featureState;
}
@Override
public void setFeatureState(FeatureState featureState) {
delegate.setFeatureState(featureState);
cache.remove(featureState.getFeature().name());
}
/**
* Checks whether this supplied {@link CacheEntry} should be ignored.
*/
private boolean isExpired(CacheEntry entry) {
if (ttl == 0) {
return false;
}
return entry.getTimestamp() + ttl < System.currentTimeMillis();
}
/**
* This class represents a cached repository lookup
*/
private static class CacheEntry {
private final FeatureState state;
private final long timestamp;
public CacheEntry(FeatureState state) {
this.state = state;
this.timestamp = System.currentTimeMillis();
}
public FeatureState getState() {
return state;
}
public long getTimestamp() {
return timestamp;
}
}
}