View Javadoc

1   package com.atlassian.cache.memory;
2   
3   import java.util.Collection;
4   import java.util.SortedMap;
5   import java.util.concurrent.Callable;
6   import java.util.concurrent.ExecutionException;
7   
8   import javax.annotation.Nonnull;
9   import javax.annotation.Nullable;
10  
11  import com.atlassian.cache.Cache;
12  import com.atlassian.cache.CacheEntryListener;
13  import com.atlassian.cache.CacheException;
14  import com.atlassian.cache.CacheSettings;
15  import com.atlassian.cache.CacheStatisticsKey;
16  import com.atlassian.util.concurrent.Supplier;
17  
18  import com.atlassian.cache.impl.CacheEntryListenerSupport;
19  import com.atlassian.cache.impl.DefaultCacheEntryListenerSupport;
20  import com.google.common.base.Throwables;
21  import com.google.common.cache.LoadingCache;
22  import com.google.common.cache.RemovalListener;
23  import com.google.common.cache.RemovalNotification;
24  import com.google.common.util.concurrent.UncheckedExecutionException;
25  
26  import static com.atlassian.cache.memory.DelegatingCacheStatistics.toStatistics;
27  
28  /**
29   * A Cache that delegates Concurrent Map.
30   *
31   * @since 2.0
32   */
33  class DelegatingCache<K, V> extends ManagedCacheSupport implements Cache<K, V>
34  {
35      private final com.google.common.cache.Cache<K, V> internalCache;
36      private final CacheEntryListenerSupport<K, V> listenerSupport;
37  
38      private DelegatingCache(final com.google.common.cache.Cache<K, V> internalCache, String name, CacheSettings settings)
39      {
40          super(name, settings);
41          this.internalCache = internalCache;
42          this.listenerSupport = new DefaultCacheEntryListenerSupport<K, V>();
43      }
44  
45      static <K, V> DelegatingCache<K, V> create(final LoadingCache<K, V> internalCache, String name, CacheSettings settings)
46      {
47          return new DelegatingLoadingCache<K, V>(internalCache, name, settings);
48      }
49  
50      static <K, V> DelegatingCache<K, V> create(final com.google.common.cache.Cache<K, V> internalCache, String name, CacheSettings settings)
51      {
52          return new DelegatingCache<K, V>(internalCache, name, settings);
53      }
54  
55      @Override
56      public boolean containsKey(@Nonnull K key)
57      {
58          return null != internalCache.getIfPresent(key);
59      }
60  
61      @Nonnull
62      @Override
63      public Collection<K> getKeys()
64      {
65          try
66          {
67              return internalCache.asMap().keySet();
68          }
69          catch (Exception e)
70          {
71              throw new CacheException(e);
72          }
73      }
74  
75      @Override
76      public void put(@Nonnull final K key, @Nonnull final V value)
77      {
78          try
79          {
80              V oldValue = internalCache.asMap().put(key, value);
81              if (oldValue == null)
82              {
83                  // Here we care only for the case when oldValue was null, e.g. missing as in the other
84                  // cases DelegatingRemovalListener will be called with REPLACED notification
85                  listenerSupport.notifyAdd(key, value);
86              }
87          }
88          catch (Exception e)
89          {
90              throw new CacheException(e);
91          }
92      }
93  
94      @Override
95      public V get(@Nonnull final K key)
96      {
97          return internalCache.getIfPresent(key);
98      }
99  
100     @Nonnull
101     @Override
102     public V get(@Nonnull final K key, @Nonnull final com.atlassian.cache.Supplier<? extends V> valueLoader)
103     {
104         try
105         {
106             return internalCache.get(key, new Callable<V>()
107             {
108                 @Override
109                 public V call()
110                 {
111                     return valueLoader.get();
112                 }
113             });
114         }
115         catch (UncheckedExecutionException e)
116         {
117             Throwable cause = e.getCause();
118             Throwables.propagateIfInstanceOf(cause, CacheException.class);
119             throw new CacheException(cause);
120         }
121         catch (ExecutionException e)
122         {
123             Throwable cause = e.getCause();
124             Throwables.propagateIfInstanceOf(cause, CacheException.class);
125             throw new CacheException(cause);
126         }
127         catch (Exception e)
128         {
129             Throwables.propagateIfInstanceOf(e, CacheException.class);
130             throw new CacheException(e);
131         }
132     }
133 
134     @Override
135     public void remove(@Nonnull final K key)
136     {
137         try
138         {
139             try
140             {
141                 internalCache.getUnchecked(key);
142             }
143             catch (UncheckedExecutionException e)
144             {
145                 //Even if the get fails we still want to remove and report only if the remove fails.
146             }
147             finally
148             {
149                 internalCache.invalidate(key);
150             }
151         }
152         catch (Exception e)
153         {
154             throw new CacheException(e);
155         }
156     }
157 
158     @Override
159     public void removeAll()
160     {
161         try
162         {
163             internalCache.invalidateAll();
164         }
165         catch (Exception e)
166         {
167             throw new CacheException(e);
168         }
169     }
170 
171     @Override
172     public V putIfAbsent(@Nonnull K key, @Nonnull V value)
173     {
174         try
175         {
176             V oldValue = internalCache.asMap().putIfAbsent(key, value);
177             if (oldValue == null)
178             {
179                 // Here we care only for the case when oldValue was null, e.g. missing as in the other
180                 // cases DelegatingRemovalListener will be called with REPLACED notification
181                 listenerSupport.notifyAdd(key, value);
182             }
183             return oldValue;
184         }
185         catch (Exception e)
186         {
187             throw new CacheException(e);
188         }
189     }
190 
191     @Override
192     public boolean remove(@Nonnull K key, @Nonnull V value)
193     {
194         try
195         {
196             boolean wasRemoved;
197             try
198             {
199                 internalCache.getUnchecked(key);
200             }
201             catch (UncheckedExecutionException e)
202             {
203                 //Even if the get fails we still want to remove and report only if the remove fails.
204             }
205             finally
206             {
207                 wasRemoved = internalCache.asMap().remove(key, value);
208             }
209             return wasRemoved;
210         }
211         catch (Exception e)
212         {
213             throw new CacheException(e);
214         }
215     }
216 
217     @Override
218     public boolean replace(@Nonnull K key, @Nonnull V oldValue, @Nonnull V newValue)
219     {
220         try
221         {
222             return internalCache.asMap().replace(key, oldValue, newValue);
223         }
224         catch (Exception e)
225         {
226             throw new CacheException(e);
227         }
228     }
229 
230     @Nonnull
231     @Override
232     public SortedMap<CacheStatisticsKey,Supplier<Long>> getStatistics()
233     {
234         return toStatistics(internalCache);
235     }
236 
237     @Override
238     public void clear()
239     {
240         removeAll();
241     }
242 
243     @Override
244     public boolean equals(@Nullable final Object other)
245     {
246         if (other instanceof DelegatingCache)
247         {
248             DelegatingCache<?,?> otherDelegatingCache = (DelegatingCache<?,?>) other;
249             if (internalCache.equals(otherDelegatingCache.internalCache))
250             {
251                 return true;
252             }
253         }
254         return false;
255     }
256 
257     @Override
258     public int hashCode()
259     {
260         return 3 + internalCache.hashCode();
261     }
262 
263     @Override
264     public void addListener(@Nonnull CacheEntryListener<K, V> listener, boolean includeValues)
265     {
266         listenerSupport.add(listener, includeValues);
267     }
268 
269     @Override
270     public void removeListener(@Nonnull CacheEntryListener<K, V> listener)
271     {
272         listenerSupport.remove(listener);
273     }
274 
275     protected static class DelegatingRemovalListener<K, V> implements RemovalListener<K, V>
276     {
277         private DelegatingCache<K, V> cache;
278 
279         protected void onSupply(K key, V value)
280         {
281             cache.listenerSupport.notifyAdd(key, value);
282         }
283 
284         @Override
285         public void onRemoval(RemovalNotification<K, V> notification)
286         {
287             switch (notification.getCause())
288             {
289                 case COLLECTED:
290                 case EXPIRED:
291                     cache.listenerSupport.notifyEvict(notification.getKey(), notification.getValue());
292                     break;
293                 case EXPLICIT:
294                     cache.listenerSupport.notifyRemove(notification.getKey(), notification.getValue());
295                     break;
296                 case REPLACED:
297                     K key = notification.getKey();
298                     cache.listenerSupport.notifyUpdate(key, cache.internalCache.getIfPresent(key), notification.getValue());
299                     break;
300             }
301         }
302 
303         public void setCache(DelegatingCache<K, V> cache)
304         {
305             this.cache = cache;
306         }
307     }
308 
309     private static class DelegatingLoadingCache<K, V> extends DelegatingCache<K, V>
310     {
311         private final LoadingCache<K, V> internalCache;
312 
313         private DelegatingLoadingCache(final com.google.common.cache.LoadingCache<K, V> internalCache, final String name, final CacheSettings settings)
314         {
315             super(internalCache, name, settings);
316             this.internalCache = internalCache;
317         }
318 
319         @Override
320         public V get(@Nonnull final K key)
321         {
322             try
323             {
324                 return internalCache.get(key);
325             }
326             catch (UncheckedExecutionException e)
327             {
328                 Throwable cause = e.getCause();
329                 Throwables.propagateIfInstanceOf(cause, CacheException.class);
330                 throw new CacheException(cause);
331             }
332             catch (ExecutionException e)
333             {
334                 Throwable cause = e.getCause();
335                 Throwables.propagateIfInstanceOf(cause, CacheException.class);
336                 throw new CacheException(cause);
337             }
338             catch (Exception e)
339             {
340                 Throwables.propagateIfInstanceOf(e, CacheException.class);
341                 throw new CacheException(e);
342             }
343         }
344     }
345 }