/* $HeadURL:: $ * $Id$ * * Copyright (c) 2006-2010 by Public Library of Science * http://plos.org * http://ambraproject.org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.ambraproject.service.cache; import java.io.Serializable; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; /** * A simple cache interface * * @author Pradeep Krishnan * @author Joe Osowski */ public interface Cache { /** * Gets an object from the cache. * * @param key the key * * @return the value or null if not found in cache */ public Item get(Object key); /** * Read-thru cache that looks up from cache first and falls back to the supplied look-up. * The entry returned by the lookup is committed immediately to the cache and becomes available * to all other caches that share the same underlying cache/store. (READ_COMMITTED) * * @param <T> type of the value expected * @param <E> type of the exception expected to be thrown by lookup * @param key the key to use for the lookup * @param refresh the max-age of entries in the cache (in seconds), or -1 for indefinite * @param lookup the lookup to call to get the value if not found in the cache; may be null * * @return the value in the cache, or returned by the <var>lookup</var> if not in the cache * * @throws E the exception thrown by lookup */ public <T, E extends Exception> T get(Object key, int refresh, Lookup<T, E> lookup) throws E; /** * Read-thru cache that looks up from cache first and falls back to the supplied look-up. * The entry returned by the lookup is committed immediately to the cache and becomes available * to all other caches that share the same underlying cache/store. (READ_COMMITTED) * * @param <T> type of the value expected * @param <E> type of the exception expected to be thrown by lookup * @param key the key to use for the lookup * @param lookup the lookup to call to get the value if not found in the cache; may be null * * @return the value in the cache, or returned by the <var>lookup</var> if not in the cache * * @throws E the exception thrown by lookup */ public <T, E extends Exception> T get(Object key, Lookup<T, E> lookup) throws E; /** * Puts an object into the cache. * * @param key the key * @param val the value */ public void put(Object key, Item val); /** * Removes an object at the specified key. * * @param key the key */ public void remove(Object key); /** * Removes all items from the cache. */ public void removeAll(); public static interface CachedItem extends Serializable {} /** * A holder for values that are to be cached. Mainly so that 'null' * can be stored for a cache key. */ public static class Item implements CachedItem { /** A version compatible with older (ambra 0.9.2 or before) */ private static final long serialVersionUID = -3696178832412763814L; private final Object value; private final transient int ttl; // in seconds /** * Cached item with an infinite time to live. * * @param value the value to store (could be null) */ public Item(Object value) { this(value, -1); } /** * Cached item. * * @param value the value to store (could be null) * @param ttl the time to live in seconds */ public Item(Object value, int ttl) { this.value = value; this.ttl = ttl; } public Object getValue() { return value; } public int getTtl() { return ttl; } public String toString() { return (value == null) ? "NULL" : value.toString(); } } /** * The call back to support read-thru cache lookup. * * @param <T> the type of value looked up * @param <E> the type of exception to expect on lookup */ public static abstract class Lookup<T, E extends Exception> { /** * Looks up a value to populate the cache. * * @return the value to return; this value (including null) will be put into the cache * * @throws E from look up. */ public abstract T lookup() throws E; /** * Executes a read-thru cache operation. The control is given here primarily to support * any locking strategies that the lookup implementation provides. By default it executes the * supplied operation. * * @param operation the operation to execute * * @return the value returned by executing the operation * * @throws Exception the exception thrown by lookup */ public Item execute(Operation operation) throws Exception { return operation.execute(false); } /** * A call-back operation from the Cache during a read-thru cache read. */ public interface Operation { public Item execute(boolean degradedMode) throws Exception; } } /** * A lookup implementation for Read-thru caches that synchronizes all operations via a * monitor lock held on the supplied lock object. This ensures that the cache is only populated * by one thread. This mechanism is prone to dead-locks and therefore the LockedLockup should be * considered instead. * * @param <T> the type of value looked up * @param <E> the type of exception to expect on lookup */ public static abstract class SynchronizedLookup<T, E extends Exception> extends Lookup<T, E> { private Object lock; public SynchronizedLookup(Object lock) { this.lock = lock; } public Item execute(Operation operation) throws Exception { synchronized (lock) { return operation.execute(false); } } } /** * A lookup implementation for Read-thru caches that synchronizes all operations via a lock * held on the supplied lock object. This ensures that the cache is only populated by one * thread. This is the preferred lookup strategy since dead-locks are detected by timeouts and * degrades to loading from the backing cache/store. * * @param <T> the type of value looked up * @param <E> the type of exception to expect on lookup */ public static abstract class LockedLookup<T, E extends Exception> extends Lookup<T, E> { private Lock lock; private long timeWait; private TimeUnit unit; public LockedLookup(Lock lock, long timeWait, TimeUnit unit) { this.lock = lock; this.timeWait = timeWait; this.unit = unit; } public Item execute(Operation operation) throws Exception { boolean acquired = lock.tryLock(timeWait, unit); try { return operation.execute(!acquired); } finally { if (acquired) lock.unlock(); } } } }