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