001: package ri.cache;
002:
003: import ri.cache.util.CacheListenerSupport;
004: import ri.cache.loader.NullCacheLoader;
005:
006: import java.util.ArrayList;
007: import java.util.Collection;
008: import java.util.HashMap;
009: import java.util.HashSet;
010: import java.util.Iterator;
011: import java.util.List;
012: import java.util.Map;
013: import java.util.Set;
014:
015: import javax.cache.Cache;
016: import javax.cache.CacheEntry;
017: import javax.cache.CacheException;
018: import javax.cache.CacheListener;
019: import javax.cache.CacheStatistics;
020: import javax.cache.CacheManager;
021:
022: // A Cache which ties two delegate Maps together and makes them act as one
023: // Cache.
024: //
025: // The assumption is that the L1 Map should be smaller and faster than the
026: // L2 Map and thus L1 is queried before L2. The L2 Map is kept synchronized
027: // with the L1 Map via a TieringStrategy.
028:
029: public class TieredCache implements Cache {
030:
031: private final CacheListenerSupport listeners = new CacheListenerSupport();
032: private final ReferenceStatistics statistics = new ReferenceStatistics();
033:
034: // TODO: Using a NullCacheLoader for the time being.
035: private final AsyncLoader warmer = new AsyncLoader(
036: new NullCacheLoader(), 10L);
037: private final TieringStrategy strategy;
038:
039: private final Map L1;
040: private final Map L2;
041:
042: public TieredCache(Map l1, Map l2) {
043: this (l1, l2, new WriteThroughTieringStrategy(l2));
044: }
045:
046: public TieredCache(Map l1, Map l2, TieringStrategy s) {
047: L1 = l1;
048: L2 = l2;
049: strategy = s;
050: }
051:
052: public Map getL1() {
053: return L1;
054: }
055:
056: public Map getL2() {
057: return L2;
058: }
059:
060: public boolean equals(Object o) {
061: return L1.equals(o);
062: }
063:
064: public int hashCode() {
065: return L1.hashCode();
066: }
067:
068: public int size() {
069: return L1.size() + L2.size();
070: }
071:
072: public boolean isEmpty() {
073: if (!L1.isEmpty())
074: return false;
075: return L2.isEmpty();
076: }
077:
078: public Object get(Object key) {
079: Object value = L1.get(key);
080: if (value == null) {
081: statistics.incrementMisses();
082: value = L2.get(key);
083: L1.put(key, value);
084: } else {
085: statistics.incrementHits();
086: }
087: return value;
088: }
089:
090: public Map getAll(Collection keys) throws CacheException {
091: Map results = new HashMap(keys.size());
092: List toLoad = new ArrayList();
093:
094: for (Iterator i = keys.iterator(); i.hasNext();) {
095: Object key = i.next();
096: Object value = L1.get(key);
097: if (value == null) {
098: statistics.incrementMisses();
099: toLoad.add(key);
100: } else {
101: statistics.incrementHits();
102: results.put(key, value);
103: }
104: }
105:
106: if (toLoad.size() > 0) {
107: Map m;
108: // REVIEW adam@bea.com 25-Jun-04 - It would be nice if this method
109: // were on Map or at least on BatchMap or something.
110: if (L2 instanceof Cache) {
111: m = ((Cache) L2).getAll(toLoad);
112: } else {
113: m = new HashMap();
114: for (Iterator i = toLoad.iterator(); i.hasNext();) {
115: Object key = i.next();
116: m.put(key, L2.get(key));
117: }
118: }
119: results.putAll(m);
120: L1.putAll(m);
121: }
122:
123: return results;
124: }
125:
126: public void load(Object key) throws CacheException {
127: warmer.load(key);
128: }
129:
130: public void loadAll(Collection keys) throws CacheException {
131: warmer.loadAll(keys);
132: }
133:
134: // REVIEW adam@bea.com 25-Jun-04 - In this model peek isn't really needed.
135: // The user can just call getL1().get(key);
136: public Object peek(Object key) {
137: return getL1().get(key);
138: }
139:
140: // REVIEW adam@bea.com 25-Jun-04 - Need to figure out what to do about
141: // these.
142: public CacheEntry getCacheEntry(Object key) {
143: throw new UnsupportedOperationException();
144: }
145:
146: public void evict() {
147: throw new UnsupportedOperationException();
148: }
149:
150: public CacheStatistics getStatistics() {
151: return statistics;
152: }
153:
154: public void addListener(CacheListener listener) {
155: listeners.addListener(listener);
156: }
157:
158: public void removeListener(CacheListener listener) {
159: listeners.addListener(listener);
160: }
161:
162: public State getState() {
163: // TODO: Implement me!!
164: return null;
165: }
166:
167: public String getName() {
168: // TODO: Implement me!!
169: return null;
170: }
171:
172: public Object put(Object key, Object value, long timeToLive) {
173: // TODO: Implement me!!
174: return null;
175: }
176:
177: public void shutdown() {
178: // TODO: Implement me!!
179: }
180:
181: public void clearStatistics() {
182: // TODO: Implement me!!
183: }
184:
185: public CacheStatistics getCacheStatistics() {
186: // TODO: Implement me!!
187: return null;
188: }
189:
190: public CacheManager getCacheManager() {
191: // TODO: Implement me!!
192: return null;
193: }
194:
195: public void setCacheManager(CacheManager cacheManager) {
196: // TODO: Implement me!!
197: }
198:
199: public boolean containsKey(Object key) {
200: return L1.containsKey(key) || L2.containsKey(key);
201: }
202:
203: public Object put(Object key, Object value) {
204: strategy.onPut(key, value);
205: Object o = L1.put(key, value);
206: listeners.onPut(key);
207: return o;
208: }
209:
210: public void putAll(Map t) {
211: strategy.onPutAll(t);
212: L1.putAll(t);
213: }
214:
215: public Object remove(Object key) {
216: strategy.onRemove(key);
217: Object o = L1.remove(key);
218: listeners.onRemove(key);
219: return o;
220: }
221:
222: public void clear() {
223: strategy.onClear();
224: L1.clear();
225: listeners.onClear();
226: }
227:
228: public boolean containsValue(Object value) {
229: return L1.containsValue(value) || L2.containsValue(value);
230: }
231:
232: public Set keySet() {
233: Set s = new HashSet();
234: s.addAll(L1.keySet());
235: s.addAll(L2.keySet());
236: return s;
237: }
238:
239: public Collection values() {
240: List l = new ArrayList();
241: l.addAll(L1.values());
242: l.addAll(L2.values());
243: return l;
244: }
245:
246: public Set entrySet() {
247: Set s = new HashSet();
248: s.addAll(L1.entrySet());
249: s.addAll(L2.entrySet());
250: return s;
251: }
252:
253: public interface TieringStrategy {
254: public void onPut(Object key, Object value);
255:
256: // included to support efficient batch operations
257: public void onPutAll(Map m);
258:
259: public void onRemove(Object key);
260:
261: public void onClear();
262: }
263:
264: public static class WriteThroughTieringStrategy implements
265: TieringStrategy {
266: private final Map target;
267:
268: public WriteThroughTieringStrategy(Map m) {
269: target = m;
270: }
271:
272: public void onRemove(Object key) {
273: target.remove(key);
274: }
275:
276: public void onPut(Object key, Object value) {
277: target.put(key, value);
278: }
279:
280: public void onPutAll(Map m) {
281: target.putAll(m);
282: }
283:
284: public void onClear() {
285: target.clear();
286: }
287: }
288:
289: public class ReadOnlyTieringStrategy implements TieringStrategy {
290:
291: public void onPut(Object key, Object value) {
292: throw new UnsupportedOperationException();
293: }
294:
295: public void onPutAll(Map m) {
296: throw new UnsupportedOperationException();
297: }
298:
299: public void onRemove(Object key) {
300: throw new UnsupportedOperationException();
301: }
302:
303: public void onClear() {
304: throw new UnsupportedOperationException();
305: }
306: }
307:
308: }
|