View Javadoc

1   package com.atlassian.cache.hazelcast;
2   
3   import java.util.Collection;
4   
5   import javax.annotation.Nonnull;
6   
7   import com.atlassian.cache.Cache;
8   import com.atlassian.cache.CacheEntryEvent;
9   import com.atlassian.cache.CacheEntryListener;
10  import com.atlassian.cache.CacheException;
11  import com.atlassian.cache.CacheLoader;
12  import com.atlassian.cache.ManagedCache;
13  import com.atlassian.cache.Supplier;
14  import com.atlassian.cache.impl.CacheEntryListenerSupport;
15  import com.atlassian.cache.impl.CacheLoaderSupplier;
16  import com.atlassian.cache.impl.DefaultCacheEntryEvent;
17  import com.atlassian.cache.impl.ValueCacheEntryListenerSupport;
18  import com.atlassian.hazelcast.serialization.OsgiSafe;
19  
20  import com.google.common.base.Objects;
21  import com.google.common.base.Throwables;
22  import com.hazelcast.core.EntryAdapter;
23  import com.hazelcast.core.EntryEvent;
24  import com.hazelcast.core.IMap;
25  
26  import org.slf4j.Logger;
27  import org.slf4j.LoggerFactory;
28  
29  import static com.atlassian.cache.hazelcast.OsgiSafeUtils.unwrap;
30  import static com.atlassian.cache.hazelcast.OsgiSafeUtils.wrap;
31  import static com.google.common.base.Preconditions.checkNotNull;
32  
33  /**
34   * Hazelcast implementation of the {@link Cache} and {@link ManagedCache} interfaces
35   *
36   * @since 2.4.0
37   */
38  public class HazelcastCache<K, V> extends ManagedCacheSupport implements Cache<K, V>
39  {
40      private static final Logger log = LoggerFactory.getLogger(HazelcastCache.class);
41  
42      private final CacheLoader<K, V> cacheLoader;
43      private final IMap<K, OsgiSafe<V>> map;
44      private final CacheVersion cacheVersion;
45  
46      private final CacheEntryListenerSupport<K, OsgiSafe<V>> listenerSupport = new ValueCacheEntryListenerSupport<K, OsgiSafe<V>>()
47      {
48          @Override
49          protected void initValue(final CacheEntryListenerSupport<K, OsgiSafe<V>> actualListenerSupport)
50          {
51              map.addEntryListener(new HazelcastCacheEntryListener<K, OsgiSafe<V>>(actualListenerSupport), true);
52          }
53  
54          @Override
55          protected void initValueless(final CacheEntryListenerSupport<K, OsgiSafe<V>> actualListenerSupport)
56          {
57              map.addEntryListener(new HazelcastCacheEntryListener<K, OsgiSafe<V>>(actualListenerSupport), false);
58          }
59      };
60  
61      public HazelcastCache(String name, IMap<K, OsgiSafe<V>> map, CacheLoader<K, V> cacheLoader,  CacheVersion cacheVersion, HazelcastCacheManager cacheManager)
62      {
63          super(name, cacheManager);
64  
65          this.map = map;
66          this.cacheVersion = checkNotNull(cacheVersion);
67          if (cacheLoader != null) {
68              this.cacheLoader = new CacheVersionAwareCacheLoader<K, V>(cacheLoader, this.cacheVersion);
69          } else {
70              this.cacheLoader = cacheLoader;
71          }
72      }
73  
74      @Override
75      public void clear()
76      {
77          if (isFlushable())
78          {
79              cleanupMap();
80          }
81          else
82          {
83              log.debug("Not clearing cache {} because it's configured as not flushable.", getName());
84          }
85      }
86  
87      @Override
88      public boolean containsKey(@Nonnull K k)
89      {
90          return map.containsKey(k);
91      }
92  
93      @SuppressWarnings ("unchecked")
94      @Override
95      public V get(@Nonnull final K key)
96      {
97          return getOrLoad(key, cacheLoader == null ? null : new CacheLoaderSupplier<K, V>(key, cacheLoader));
98      }
99  
100     @Nonnull
101     @Override
102     public V get(@Nonnull final K key, @Nonnull final Supplier<? extends V> valueSupplier)
103     {
104         return getOrLoad(key, valueSupplier);
105     }
106 
107     @Nonnull
108     @Override
109     public Collection<K> getKeys()
110     {
111         return map.keySet();
112     }
113 
114     @Override
115     public void put(@Nonnull K key, @Nonnull V value)
116     {
117         map.put(checkNotNull(key, "key"), wrap(checkNotNull(value, "value")));
118     }
119 
120     @Override
121     public V putIfAbsent(@Nonnull K key, @Nonnull V value)
122     {
123         return unwrap(map.putIfAbsent(checkNotNull(key, "key"), wrap(checkNotNull(value, "value"))));
124     }
125 
126     @Override
127     public void remove(@Nonnull K key)
128     {
129         map.remove(checkNotNull(key, "key"));
130     }
131 
132     @Override
133     public boolean remove(@Nonnull K key, @Nonnull V value)
134     {
135         return map.remove(checkNotNull(key, "key"), wrap(checkNotNull(value, "value")));
136     }
137 
138     @Override
139     public void removeAll()
140     {
141         cleanupMap();
142     }
143 
144     private void cleanupMap()
145     {
146       cacheVersion.incrementAndGet();
147       map.clear();
148     }
149 
150     @Override
151     public boolean replace(@Nonnull K key, @Nonnull V oldValue, @Nonnull V newValue)
152     {
153         return map.replace(checkNotNull(key, "key"),
154                 wrap(checkNotNull(oldValue, "oldValue")), wrap(checkNotNull(newValue, "newValue")));
155     }
156 
157     @Override
158     public void addListener(@Nonnull CacheEntryListener<K, V> listener, boolean includeValues)
159     {
160         listenerSupport.add(new OsgiSafeCacheEntryListener<K, V>(listener), includeValues);
161     }
162 
163     @Override
164     public void removeListener(@Nonnull CacheEntryListener<K, V> listener)
165     {
166         listenerSupport.remove(new OsgiSafeCacheEntryListener<K, V>(listener));
167     }
168 
169     @Override
170     protected String getHazelcastMapName()
171     {
172         return map.getName();
173     }
174 
175     private static class OsgiSafeCacheEntryEvent<K, V> extends DefaultCacheEntryEvent<K, V>
176     {
177         public OsgiSafeCacheEntryEvent(CacheEntryEvent<K, OsgiSafe<V>> event)
178         {
179             super(event.getKey(), unwrap(event.getValue()), unwrap(event.getOldValue()));
180         }
181     }
182 
183     private static class OsgiSafeCacheEntryListener<K, V> implements CacheEntryListener<K, OsgiSafe<V>>
184     {
185         private final CacheEntryListener<K, V> delegate;
186 
187         private OsgiSafeCacheEntryListener(CacheEntryListener<K, V> listener)
188         {
189             this.delegate = checkNotNull(listener, "listener");
190         }
191 
192         @Override
193         public void onAdd(@Nonnull CacheEntryEvent<K, OsgiSafe<V>> event)
194         {
195             delegate.onAdd(new OsgiSafeCacheEntryEvent<K, V>(event));
196         }
197 
198         @Override
199         public void onEvict(@Nonnull CacheEntryEvent<K, OsgiSafe<V>> event)
200         {
201             delegate.onEvict(new OsgiSafeCacheEntryEvent<K, V>(event));
202         }
203 
204         @Override
205         public void onRemove(@Nonnull CacheEntryEvent<K, OsgiSafe<V>> event)
206         {
207             delegate.onRemove(new OsgiSafeCacheEntryEvent<K, V>(event));
208         }
209 
210         @Override
211         public void onUpdate(@Nonnull CacheEntryEvent<K, OsgiSafe<V>> event)
212         {
213             delegate.onUpdate(new OsgiSafeCacheEntryEvent<K, V>(event));
214         }
215 
216         @Override
217         public boolean equals(Object o)
218         {
219             if (this == o)
220             {
221                 return true;
222             }
223             if (o == null || getClass() != o.getClass())
224             {
225                 return false;
226             }
227 
228             OsgiSafeCacheEntryListener that = (OsgiSafeCacheEntryListener) o;
229             return delegate.equals(that.delegate);
230         }
231 
232         @Override
233         public int hashCode()
234         {
235             return delegate.hashCode();
236         }
237     }
238 
239 
240     private V getOrLoad(final K key, final Supplier<? extends V> valueSupplier)
241     {
242         try
243         {
244             OsgiSafe<V> value = map.get(checkNotNull(key, "key"));
245             if (value != null)
246             {
247                 return value.getValue();
248             }
249             else if (valueSupplier == null)
250             {
251                 return null;
252             }
253 
254             V newValue = valueSupplier.get();
255             //noinspection ConstantConditions
256             if (newValue == null)
257             {
258                 throw new CacheException("The provided cacheLoader returned null. Null values are not supported.");
259             }
260             value = wrap(newValue);
261             OsgiSafe<V> current = map.putIfAbsent(key, value);
262 
263             return unwrap(Objects.firstNonNull(current, value));
264         }
265         catch (RuntimeException e)
266         {
267             Throwables.propagateIfInstanceOf(e, CacheException.class);
268             throw new CacheException("Problem retrieving a value from cache " + getName(), e);
269         }
270     }
271 
272     private static class HazelcastCacheEntryListener<K, V> extends EntryAdapter<K, V>
273     {
274         private final CacheEntryListenerSupport<K, V> listenerSupport;
275 
276         private HazelcastCacheEntryListener(final CacheEntryListenerSupport<K, V> listenerSupport)
277         {
278             this.listenerSupport = checkNotNull(listenerSupport, "listenerSupport");
279         }
280 
281         @Override
282         public void entryAdded(EntryEvent<K, V> event)
283         {
284             listenerSupport.notifyAdd(event.getKey(), event.getValue());
285         }
286 
287         @Override
288         public void entryRemoved(EntryEvent<K, V> event)
289         {
290             listenerSupport.notifyRemove(event.getKey(), event.getOldValue());
291         }
292 
293         @Override
294         public void entryUpdated(EntryEvent<K, V> event)
295         {
296             listenerSupport.notifyUpdate(event.getKey(), event.getValue(), event.getOldValue());
297         }
298 
299         @Override
300         public void entryEvicted(EntryEvent<K, V> event)
301         {
302             listenerSupport.notifyEvict(event.getKey(), event.getOldValue());
303         }
304     }
305 }