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
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<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
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 }