1 package com.atlassian.cache.ehcache;
2
3 import java.io.Serializable;
4 import java.util.Collection;
5 import java.util.Map;
6 import java.util.concurrent.locks.Lock;
7 import java.util.concurrent.locks.ReadWriteLock;
8 import java.util.concurrent.locks.ReentrantReadWriteLock;
9
10 import javax.annotation.Nullable;
11
12 import com.atlassian.cache.CacheLoader;
13
14 import com.google.common.base.Function;
15 import com.google.common.base.Throwables;
16 import com.google.common.collect.Multimap;
17 import com.google.common.collect.Multimaps;
18
19 import net.sf.ehcache.CacheException;
20 import net.sf.ehcache.Ehcache;
21 import net.sf.ehcache.Element;
22 import net.sf.ehcache.concurrent.LockType;
23 import net.sf.ehcache.concurrent.Sync;
24 import net.sf.ehcache.constructs.blocking.BlockingCache;
25 import net.sf.ehcache.constructs.blocking.SelfPopulatingCache;
26
27 import static com.atlassian.util.concurrent.Assertions.notNull;
28
29
30
31
32
33
34
35
36
37
38 public class LoadingCache<K,V> extends BlockingCache
39 {
40
41
42
43
44
45 private static final int DEFAULT_NUMBER_OF_MUTEXES = Integer.getInteger(LoadingCache.class.getName() + '.' + "DEFAULT_NUMBER_OF_MUTEXES", 64);
46
47 private final CacheLoader<K, V> loader;
48
49
50 private final ReadWriteLock loadVsRemoveAllLock = new ReentrantReadWriteLock(true);
51
52 public LoadingCache(final Ehcache cache, final CacheLoader<K, V> loader) throws CacheException
53 {
54 super(cache, DEFAULT_NUMBER_OF_MUTEXES);
55 this.loader = notNull("loader", loader);
56 }
57
58 Lock loadLock()
59 {
60 return loadVsRemoveAllLock.readLock();
61 }
62
63 Lock removeAllLock()
64 {
65 return loadVsRemoveAllLock.writeLock();
66 }
67
68 @Override
69 public Element get(final Object key)
70 {
71 if (key == null)
72 {
73 throw new NullPointerException("null keys are not permitted");
74 }
75 Element element = super.get(key);
76 return (element != null) ? element : loadValueAndReleaseLock(key);
77 }
78
79
80
81
82
83
84
85
86
87
88
89
90
91 private Element loadValueAndReleaseLock(final Object key)
92 {
93 Element element;
94 loadLock().lock();
95 try
96 {
97 V value = null;
98 try
99 {
100 value = getFromLoader(key);
101 }
102 finally
103 {
104 element = putLoadedValueAndReleaseLock(key, value);
105 }
106 }
107 finally
108 {
109
110
111 loadLock().unlock();
112 }
113 return element;
114 }
115
116 @Nullable
117 private Element putLoadedValueAndReleaseLock(Object key, V value)
118 {
119
120 if (value != null)
121 {
122 final Element element = new Element(key, value);
123 put(element);
124 return element;
125 }
126
127
128
129
130
131 final Sync lock = getLockForKey(key);
132 if (lock.isHeldByCurrentThread(LockType.WRITE))
133 {
134 lock.unlock(LockType.WRITE);
135 }
136 return null;
137 }
138
139 @SuppressWarnings({ "ConstantConditions", "unchecked" })
140 private V getFromLoader(Object key)
141 {
142 final V value;
143 try
144 {
145 value = loader.load((K)key);
146 }
147 catch (final RuntimeException re)
148 {
149 put(new Element(key, null));
150 throw propagate(key, re);
151 }
152 catch (final Error err)
153 {
154 put(new Element(key, null));
155 throw propagate(key, err);
156 }
157
158 if (value == null)
159 {
160 throw new CacheException("CacheLoader returned null for key " + key);
161 }
162 return value;
163 }
164
165
166
167
168
169
170
171 @Override
172 public boolean remove(Serializable key, boolean doNotNotifyCacheReplicators)
173 {
174 final Sync sync = getLockForKey(key);
175 sync.lock(LockType.WRITE);
176 try
177 {
178 return super.remove(key, doNotNotifyCacheReplicators);
179 }
180 finally
181 {
182 sync.unlock(LockType.WRITE);
183 }
184 }
185
186 @Override
187 public boolean remove(Serializable key)
188 {
189 final Sync sync = getLockForKey(key);
190 sync.lock(LockType.WRITE);
191 try
192 {
193 return super.remove(key);
194 }
195 finally
196 {
197 sync.unlock(LockType.WRITE);
198 }
199 }
200
201 @Override
202 public boolean remove(Object key)
203 {
204 final Sync sync = getLockForKey(key);
205 sync.lock(LockType.WRITE);
206 try
207 {
208 return super.remove(key);
209 }
210 finally
211 {
212 sync.unlock(LockType.WRITE);
213 }
214 }
215
216 @Override
217 public boolean remove(Object key, final boolean doNotNotifyCacheReplicators)
218 {
219 final Sync sync = getLockForKey(key);
220 sync.lock(LockType.WRITE);
221 try
222 {
223 return super.remove(key, doNotNotifyCacheReplicators);
224 }
225 finally
226 {
227 sync.unlock(LockType.WRITE);
228 }
229 }
230
231
232
233
234
235
236
237
238
239 @Override
240 public void removeAll(Collection<?> keys)
241 {
242 removeGroupedBySync(keys, new RemoveCallback()
243 {
244 @Override
245 public void removeUnderLock(Collection<?> keysForSync)
246 {
247 underlyingCache.removeAll(keysForSync);
248 }
249 });
250 }
251
252 @Override
253 public void removeAll(Collection<?> keys, final boolean doNotNotifyCacheReplicators)
254 {
255 removeGroupedBySync(keys, new RemoveCallback()
256 {
257 @Override
258 public void removeUnderLock(Collection<?> keysForSync)
259 {
260 underlyingCache.removeAll(keysForSync, doNotNotifyCacheReplicators);
261 }
262 });
263 }
264
265
266
267
268
269
270
271
272
273 private void removeGroupedBySync(Collection<?> allKeys, RemoveCallback callback)
274 {
275 final Multimap<Sync,?> map = Multimaps.index(allKeys, new Function<Object,Sync>()
276 {
277 @Override
278 public Sync apply(Object key)
279 {
280 return getLockForKey(key);
281 }
282 });
283 removeGroupedBySync(map, callback);
284 }
285
286
287
288
289
290
291
292
293 private static <K> void removeGroupedBySync(Multimap<Sync,K> keysBySync, RemoveCallback callback)
294 {
295 for (Map.Entry<Sync,Collection<K>> entry : keysBySync.asMap().entrySet())
296 {
297 final Sync sync = entry.getKey();
298 final Collection<K> keysUsingThisSync = entry.getValue();
299 sync.lock(LockType.WRITE);
300 try
301 {
302 callback.removeUnderLock(keysUsingThisSync);
303 }
304 finally
305 {
306 sync.unlock(LockType.WRITE);
307 }
308 }
309 }
310
311
312
313
314
315
316
317 @Override
318 public void removeAll()
319 {
320 removeAllLock().lock();
321 try
322 {
323 super.removeAll();
324 }
325 finally
326 {
327 removeAllLock().unlock();
328 }
329 }
330
331 @Override
332 public void removeAll(boolean doNotNotifyCacheReplicators)
333 {
334 removeAllLock().lock();
335 try
336 {
337 super.removeAll(doNotNotifyCacheReplicators);
338 }
339 finally
340 {
341 removeAllLock().unlock();
342 }
343 }
344
345
346
347 private static RuntimeException propagate(Object key, Throwable e)
348 {
349 Throwables.propagateIfInstanceOf(e, CacheException.class);
350 throw new CacheException("Could not fetch object for cache entry with key \"" + key + "\".", e);
351 }
352
353 interface RemoveCallback
354 {
355 void removeUnderLock(Collection<?> keys);
356 }
357 }