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.MoreObjects;
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
35
36
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<>(actualListenerSupport), true);
52 }
53
54 @Override
55 protected void initValueless(final CacheEntryListenerSupport<K, OsgiSafe<V>> actualListenerSupport)
56 {
57 map.addEntryListener(new HazelcastCacheEntryListener<>(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 {
69 this.cacheLoader = new CacheVersionAwareCacheLoader<>(cacheLoader, this.cacheVersion);
70 }
71 else
72 {
73 this.cacheLoader = cacheLoader;
74 }
75 }
76
77 @Override
78 public void clear()
79 {
80 cleanupMap();
81 }
82
83 @Override
84 public boolean containsKey(@Nonnull K k)
85 {
86 return map.containsKey(k);
87 }
88
89 @SuppressWarnings ("unchecked")
90 @Override
91 public V get(@Nonnull final K key)
92 {
93 return getOrLoad(key, cacheLoader == null ? null : new CacheLoaderSupplier<K, V>(key, cacheLoader));
94 }
95
96 @Nonnull
97 @Override
98 public V get(@Nonnull final K key, @Nonnull final Supplier<? extends V> valueSupplier)
99 {
100 return getOrLoad(key, valueSupplier);
101 }
102
103 @Nonnull
104 @Override
105 public Collection<K> getKeys()
106 {
107 return map.keySet();
108 }
109
110 @Override
111 public void put(@Nonnull K key, @Nonnull V value)
112 {
113 map.put(checkNotNull(key, "key"), wrap(checkNotNull(value, "value")));
114 }
115
116 @Override
117 public V putIfAbsent(@Nonnull K key, @Nonnull V value)
118 {
119 return unwrap(map.putIfAbsent(checkNotNull(key, "key"), wrap(checkNotNull(value, "value"))));
120 }
121
122 @Override
123 public void remove(@Nonnull K key)
124 {
125 map.remove(checkNotNull(key, "key"));
126 }
127
128 @Override
129 public boolean remove(@Nonnull K key, @Nonnull V value)
130 {
131 return map.remove(checkNotNull(key, "key"), wrap(checkNotNull(value, "value")));
132 }
133
134 @Override
135 public void removeAll()
136 {
137 cleanupMap();
138 }
139
140 private void cleanupMap()
141 {
142 cacheVersion.incrementAndGet();
143 map.clear();
144 }
145
146 @Override
147 public boolean replace(@Nonnull K key, @Nonnull V oldValue, @Nonnull V newValue)
148 {
149 return map.replace(checkNotNull(key, "key"),
150 wrap(checkNotNull(oldValue, "oldValue")), wrap(checkNotNull(newValue, "newValue")));
151 }
152
153 @Override
154 public void addListener(@Nonnull CacheEntryListener<K, V> listener, boolean includeValues)
155 {
156 listenerSupport.add(new OsgiSafeCacheEntryListener<K, V>(listener), includeValues);
157 }
158
159 @Override
160 public void removeListener(@Nonnull CacheEntryListener<K, V> listener)
161 {
162 listenerSupport.remove(new OsgiSafeCacheEntryListener<K, V>(listener));
163 }
164
165 @Nonnull
166 @Override
167 protected String getHazelcastMapName()
168 {
169 return map.getName();
170 }
171
172 private static class OsgiSafeCacheEntryEvent<K, V> extends DefaultCacheEntryEvent<K, V>
173 {
174 public OsgiSafeCacheEntryEvent(CacheEntryEvent<K, OsgiSafe<V>> event)
175 {
176 super(event.getKey(), unwrap(event.getValue()), unwrap(event.getOldValue()));
177 }
178 }
179
180 private static class OsgiSafeCacheEntryListener<K, V> implements CacheEntryListener<K, OsgiSafe<V>>
181 {
182 private final CacheEntryListener<K, V> delegate;
183
184 private OsgiSafeCacheEntryListener(CacheEntryListener<K, V> listener)
185 {
186 this.delegate = checkNotNull(listener, "listener");
187 }
188
189 @Override
190 public void onAdd(@Nonnull CacheEntryEvent<K, OsgiSafe<V>> event)
191 {
192 delegate.onAdd(new OsgiSafeCacheEntryEvent<>(event));
193 }
194
195 @Override
196 public void onEvict(@Nonnull CacheEntryEvent<K, OsgiSafe<V>> event)
197 {
198 delegate.onEvict(new OsgiSafeCacheEntryEvent<>(event));
199 }
200
201 @Override
202 public void onRemove(@Nonnull CacheEntryEvent<K, OsgiSafe<V>> event)
203 {
204 delegate.onRemove(new OsgiSafeCacheEntryEvent<>(event));
205 }
206
207 @Override
208 public void onUpdate(@Nonnull CacheEntryEvent<K, OsgiSafe<V>> event)
209 {
210 delegate.onUpdate(new OsgiSafeCacheEntryEvent<>(event));
211 }
212
213 @Override
214 public boolean equals(Object o)
215 {
216 if (this == o)
217 {
218 return true;
219 }
220 if (o == null || getClass() != o.getClass())
221 {
222 return false;
223 }
224
225 OsgiSafeCacheEntryListener that = (OsgiSafeCacheEntryListener) o;
226 return delegate.equals(that.delegate);
227 }
228
229 @Override
230 public int hashCode()
231 {
232 return delegate.hashCode();
233 }
234 }
235
236
237 private V getOrLoad(final K key, final Supplier<? extends V> valueSupplier)
238 {
239 try
240 {
241 OsgiSafe<V> value = map.get(checkNotNull(key, "key"));
242 if (value != null)
243 {
244 return value.getValue();
245 }
246 else if (valueSupplier == null)
247 {
248 return null;
249 }
250
251 V newValue = valueSupplier.get();
252
253 if (newValue == null)
254 {
255 throw new CacheException("The provided cacheLoader returned null. Null values are not supported.");
256 }
257 value = wrap(newValue);
258 OsgiSafe<V> current = map.putIfAbsent(key, value);
259
260 return unwrap(MoreObjects.firstNonNull(current, value));
261 }
262 catch (RuntimeException e)
263 {
264 Throwables.propagateIfInstanceOf(e, CacheException.class);
265 throw new CacheException("Problem retrieving a value from cache " + getName(), e);
266 }
267 }
268
269 private static class HazelcastCacheEntryListener<K, V> extends EntryAdapter<K, V>
270 {
271 private final CacheEntryListenerSupport<K, V> listenerSupport;
272
273 private HazelcastCacheEntryListener(final CacheEntryListenerSupport<K, V> listenerSupport)
274 {
275 this.listenerSupport = checkNotNull(listenerSupport, "listenerSupport");
276 }
277
278 @Override
279 public void entryAdded(EntryEvent<K, V> event)
280 {
281 listenerSupport.notifyAdd(event.getKey(), event.getValue());
282 }
283
284 @Override
285 public void entryRemoved(EntryEvent<K, V> event)
286 {
287 listenerSupport.notifyRemove(event.getKey(), event.getOldValue());
288 }
289
290 @Override
291 public void entryUpdated(EntryEvent<K, V> event)
292 {
293 listenerSupport.notifyUpdate(event.getKey(), event.getValue(), event.getOldValue());
294 }
295
296 @Override
297 public void entryEvicted(EntryEvent<K, V> event)
298 {
299 listenerSupport.notifyEvict(event.getKey(), event.getOldValue());
300 }
301 }
302 }