package org.togglz.core.repository.cache;

import java.util.HashMap;
import java.util.Map;

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<Feature, CacheEntry> cache = new HashMap<Feature, 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
     */
    public CachingStateRepository(StateRepository delegate, long ttl) {
        this.delegate = delegate;
        this.ttl = ttl;
    }

    @Override
    public FeatureState getFeatureState(Feature feature) {

        // first try to find it from the cache
        CacheEntry entry = cache.get(feature);
        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, 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());
    }

    /**
     * Checks whether this supplied {@link CacheEntry} should be ignored.
     */
    private boolean isExpired(CacheEntry entry) {
        if (ttl > 0) {
            return entry.getTimestamp() + ttl < System.currentTimeMillis();
        }
        return false;
    }

    /**
     * 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;
        }

    }

}

@@ -83,16 +83,54 @@
 til.Map;
+%0Aimport java.util.concurrent.TimeUnit;
 %0A%0Aimport
@@ -1797,32 +1797,668 @@
 l = ttl;%0A    %7D%0A%0A
+    /**%0A     * Creates a caching facade for the supplied %7B@link StateRepository%7D. The cached state of a feature will expire after the%0A     * supplied TTL rounded down to milliseconds or if %7B@link #setFeatureState(FeatureState)%7D is invoked.%0A     *%0A     * @param delegate The repository to delegate invocations to%0A     * @param ttl The time in a given %7B@code ttlTimeUnit%7D after which a cache entry will expire%0A     * @param ttlTimeUnit The unit that %7B@code ttl%7D is expressed in%0A     */%0A    public CachingStateRepository(StateRepository delegate, long ttl, TimeUnit ttlTimeUnit) %7B%0A        this(delegate, ttlTimeUnit.toMillis(ttl));%0A    %7D%0A%0A
     @Override%0A  

Allow to specify cache TTL in other time units than millisecond