package avrobase.caching; import avrobase.AvroBase; import avrobase.AvroBaseException; import avrobase.Creator; import avrobase.ForwardingAvroBase; import avrobase.Mutator; import avrobase.Row; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Element; import net.sf.ehcache.config.CacheConfiguration; import org.apache.avro.specific.SpecificRecord; import java.io.Serializable; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; /** * Cache the results of an avrobase and also send out messages to listeners when one is updated. * <p/> * User: sam * Date: 5/10/11 * Time: 1:37 PM */ public class Cacher<T extends SpecificRecord, K> extends ForwardingAvroBase<T, K> { private final KeyMaker<K> keyMaker; public static interface Listener<K> { void invalidate(K row); } public static interface KeyMaker<K> { Object make(K key); } private Cache cache; public Cacher(AvroBase<T, K> delegate, KeyMaker<K> keyMaker, Cache cache) { super(delegate); this.keyMaker = keyMaker; this.cache = cache; } private List<Listener<K>> listeners = new ArrayList<Listener<K>>(); public void addCacheListener(Listener<K> cl) { listeners.add(cl); } private void invalidate(K row) { for (Listener<K> listener : listeners) { listener.invalidate(row); } } @Override public void delete(K key) throws AvroBaseException { super.delete(key); cache.remove(keyMaker.make(key)); invalidate(key); } @Override public K create(T value) throws AvroBaseException { K k = super.create(value); Row<T, K> tkRow = new Row<T, K>(value, k); cache.put(new Element(keyMaker.make(k), tkRow)); return k; } @Override public Row<T, K> get(K row) throws AvroBaseException { Object key = keyMaker.make(row); Element element = cache.get(key); Row<T, K> tkRow; if (element == null) { tkRow = super.get(row); cache.put(new Element(key, tkRow)); invalidate(row); } else { // TODO: until we offer immutable rows, clone the result Serializable value = element.getValue(); tkRow = value == null ? null : ((Row<T, K>) value).clone(); } return tkRow; } @Override public Row<T, K> mutate(K row, Mutator<T> tMutator) throws AvroBaseException { Row<T, K> mutate = super.mutate(row, tMutator); Object key = keyMaker.make(row); if (mutate == null) { cache.remove(key); } else { cache.put(new Element(key, mutate)); } invalidate(row); return mutate; } @Override public Row<T, K> mutate(K row, Mutator<T> tMutator, Creator<T> tCreator) throws AvroBaseException { Row<T, K> mutate = super.mutate(row, tMutator, tCreator); Object key = keyMaker.make(row); if (mutate == null) { cache.remove(key); } else { cache.put(new Element(key, mutate)); } invalidate(row); return mutate; } @Override public void put(K row, T value) throws AvroBaseException { super.put(row, value); cache.put(new Element(keyMaker.make(row), new Row<T, K>(value, row))); invalidate(row); } @Override public boolean put(K row, T value, long version) throws AvroBaseException { boolean put = super.put(row, value, version); cache.put(new Element(keyMaker.make(row), new Row<T, K>(value, row, version))); invalidate(row); return put; } @Override public Iterable<Row<T, K>> scan(K startRow, K stopRow) throws AvroBaseException { final Iterable<Row<T, K>> scan = super.scan(startRow, stopRow); return new Iterable<Row<T, K>>() { @Override public Iterator<Row<T, K>> iterator() { final Iterator<Row<T, K>> iterator = scan.iterator(); return new Iterator<Row<T, K>>() { @Override public boolean hasNext() { return iterator.hasNext(); } @Override public Row<T, K> next() { Row<T, K> next = iterator.next(); cache.put(new Element(keyMaker.make(next.row), next)); invalidate(next.row); return next; } @Override public void remove() { } }; } }; } public void invalidate() { cache.flush(); } }