View Javadoc
1   package com.atlassian.cache.memory;
2   
3   import com.atlassian.cache.CacheLoader;
4   import com.atlassian.cache.CacheSettings;
5   import com.atlassian.cache.CacheSettingsBuilder;
6   import com.atlassian.cache.CacheSettingsDefaultsProvider;
7   import com.atlassian.cache.CachedReference;
8   import com.atlassian.cache.ManagedCache;
9   import com.atlassian.cache.impl.AbstractCacheManager;
10  import com.atlassian.cache.impl.ReferenceKey;
11  import com.atlassian.cache.impl.StrongSupplier;
12  import com.atlassian.cache.impl.WeakSupplier;
13  import com.atlassian.instrumentation.DefaultInstrumentRegistry;
14  import com.atlassian.instrumentation.SimpleTimer;
15  import com.atlassian.instrumentation.caches.CacheCollector;
16  import com.atlassian.instrumentation.caches.CacheKeys;
17  import com.google.common.cache.Cache;
18  import com.google.common.cache.CacheBuilder;
19  import com.google.common.cache.LoadingCache;
20  
21  import javax.annotation.Nonnull;
22  import javax.annotation.Nullable;
23  
24  import static java.util.concurrent.TimeUnit.MILLISECONDS;
25  
26  /**
27   * Maintains a mapping of name -> Cache and provides factory methods for creating and getting caches.
28   *
29   * @since 2.0
30   */
31  public class MemoryCacheManager extends AbstractCacheManager
32  {
33      @Nonnull
34      public MemoryCacheManager()
35      {
36          super(null);
37      }
38  
39      public MemoryCacheManager(CacheSettingsDefaultsProvider cacheSettingsDefaultsProvider)
40      {
41          super(cacheSettingsDefaultsProvider);
42      }
43  
44      @Nonnull
45      @SuppressWarnings("unchecked")
46      @Override
47      public <K, V> com.atlassian.cache.Cache<K, V> getCache(@Nonnull String name, @Nullable CacheLoader<K, V> loader, @Nonnull CacheSettings settings)
48      {
49          return getCache(name, loader, settings, DelegatingCache.DEFAULT_CREATE_FUNCTION);
50      }
51  
52      @Nonnull
53      @SuppressWarnings("unchecked")
54      public <K, V> com.atlassian.cache.Cache<K, V> getCache(@Nonnull String name, @Nullable CacheLoader<K, V> loader,
55                                                             @Nonnull CacheSettings settings,
56                                                             @Nonnull final DelegatingCache.CreateFunction createFunction)
57      {
58          if (null == loader)
59          {
60              return (com.atlassian.cache.Cache<K, V>) createSimpleCache(name, mergeSettings(name, settings), createFunction);
61          }
62          else
63          {
64              return (com.atlassian.cache.Cache<K, V>) createComputingCache(name, mergeSettings(name, settings), loader, createFunction);
65          }
66      }
67  
68      @Nonnull
69      @Override
70      public <V> CachedReference<V> getCachedReference(@Nonnull final String name,
71                                                       @Nonnull final com.atlassian.cache.Supplier<V> supplier,
72                                                       @Nonnull final CacheSettings settings)
73      {
74          return getCachedReference(name, supplier, settings, DelegatingCachedReference.DEFAULT_CREATE_FUNCTION);
75      }
76  
77      public <V> CachedReference<V> getCachedReference(@Nonnull final String name,
78                                                       @Nonnull final com.atlassian.cache.Supplier<V> supplier,
79                                                       @Nonnull final CacheSettings settings,
80                                                       @Nonnull final DelegatingCachedReference.CreateFunction createFunction)
81      {
82          // Force the cache settings to be flushable.
83          final CacheSettings overridenSettings = mergeSettings(name,
84                  settings.override(new CacheSettingsBuilder().flushable().build()));
85  
86          return cacheCreationLocks.apply(name).withLock(new java.util.function.Supplier<DelegatingCachedReference<V>>()
87          {
88              @Override
89              public DelegatingCachedReference<V> get()
90              {
91                  // We need to always create a new instance as any old loader may belong to a plugin that has gone away,
92                  // resulting in a whole world of ClassLoader pain.
93                  final DelegatingCachedReference.DelegatingReferenceRemovalListener<V> listener = new DelegatingCachedReference.DelegatingReferenceRemovalListener<V>();
94  
95                  final CacheCollector collector = new DefaultInstrumentRegistry().pullCacheCollector(name);
96  
97                  LoadingCache<ReferenceKey, V> computingCache = createCacheBuilder(overridenSettings)
98                          .removalListener(listener)
99                          .build(new com.google.common.cache.CacheLoader<ReferenceKey, V>()
100                         {
101                             // Wrap the loader with timing code so if stats are enabled we can get numbers.
102                             @Override
103                             public V load(@Nonnull final ReferenceKey key) throws Exception
104                             {
105                                 if(collector.isEnabled())
106                                 {
107                                     SimpleTimer timer = new SimpleTimer(CacheKeys.LOAD_TIME.toString());
108                                     timer.start();
109                                     try
110                                     {
111                                         V value = supplier.get();
112                                         listener.onSupply(value);
113                                         return value;
114                                     }
115                                     finally
116                                     {
117                                         timer.end();
118                                         collector.miss();
119                                         collector.getSplits().add(timer);
120                                     }
121                                 }
122                                 else
123                                 {
124                                     V value = supplier.get();
125                                     listener.onSupply(value);
126                                     return value;
127                                 }
128                             }
129                         });
130 
131                 DelegatingCachedReference<V> cache = createFunction.create(computingCache, name, overridenSettings, collector);
132                 listener.setCachedReference(cache);
133                 putCacheInMap(name, new WeakSupplier<ManagedCache>(cache));
134                 return cache;
135             }
136         });
137     }
138 
139     @Override
140     protected ManagedCache createSimpleCache(@Nonnull String name, @Nonnull CacheSettings settings) {
141         return createSimpleCache(name, settings, DelegatingCache.DEFAULT_CREATE_FUNCTION);
142     }
143 
144     protected <K,V> ManagedCache createSimpleCache(@Nonnull final String name, @Nonnull final CacheSettings settings,
145                                                    @Nonnull final DelegatingCache.CreateFunction createFunction)
146     {
147 
148         java.util.function.Supplier<ManagedCache> cacheSupplier = caches.get(name);
149         if (cacheSupplier != null)
150         {
151             ManagedCache cache = cacheSupplier.get();
152             if (cache != null)
153             {
154                 return cache;
155             }
156         }
157         return cacheCreationLocks.apply(name).withLock(new java.util.function.Supplier<ManagedCache>()
158         {
159             @Override
160             public ManagedCache get()
161             {
162                 if (!caches.containsKey(name))
163                 {
164                     DelegatingCache.DelegatingRemovalListener<K, V> listener = new DelegatingCache.DelegatingRemovalListener<>();
165 
166                     final Cache<K, V> simpleCache = createCacheBuilder(settings)
167                             .removalListener(listener)
168                             .build();
169 
170                     DelegatingCache<K, V> cache = createFunction.create(simpleCache, name, settings, null);
171                     listener.setCache(cache);
172 
173                     putCacheInMap(name, new StrongSupplier<ManagedCache>(cache));
174                 }
175                 return caches.get(name).get();
176             }
177         });
178     }
179 
180 
181     @Override
182     protected <K, V> ManagedCache createComputingCache(@Nonnull String name, @Nonnull CacheSettings settings, @Nullable CacheLoader<K, V> loader) {
183         return createComputingCache(name, settings, loader, DelegatingCache.DEFAULT_CREATE_FUNCTION);
184     }
185 
186     protected <K, V> ManagedCache createComputingCache(@Nonnull final String name,
187                                                        @Nonnull final CacheSettings settings,
188                                                        final CacheLoader<K, V> loader,
189                                                        @Nonnull final DelegatingCache.CreateFunction createFunction)
190     {
191         return cacheCreationLocks.apply(name).withLock(new java.util.function.Supplier<ManagedCache>()
192         {
193             @Override
194             public ManagedCache get()
195             {
196                 // We need to always create a new instance as any old loader may belong to a plugin that has gone away,
197                 // resulting in a whole world of ClassLoader pain.
198                 final DelegatingCache.DelegatingRemovalListener<K, V> listener = new DelegatingCache.DelegatingRemovalListener<K, V>();
199 
200                 final CacheLoader<K, V> wrappedLoader = new CacheLoader<K, V>()
201                 {
202                     @Nonnull
203                     @Override
204                     public V load(@Nonnull K key)
205                     {
206                         V value = loader.load(key);
207                         listener.onSupply(key, value);
208                         return value;
209                     }
210                 };
211 
212                 final Cache<K, V> simpleCache = createCacheBuilder(settings)
213                         .removalListener(listener)
214                         .build();
215 
216                 DelegatingCache<K, V> cache = createFunction.create(simpleCache, name, settings, wrappedLoader);
217                 listener.setCache(cache);
218                 putCacheInMap(name, new WeakSupplier<ManagedCache>(cache));
219                 return cache;
220             }
221         });
222     }
223 
224     /**
225      * Use this method to store values in caches map to allow read/write synchronization in @{@link JMXMemoryCacheManager}
226      *
227      * @param name cache name
228      * @param supplier supplier containing cache to add
229      */
230     protected void putCacheInMap(@Nonnull String name, @Nonnull java.util.function.Supplier<ManagedCache> supplier)
231     {
232         caches.put(name, supplier);
233     }
234 
235     private static CacheBuilder<Object, Object> createCacheBuilder(CacheSettings settings)
236     {
237         final CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder();
238         if (null != settings.getMaxEntries())
239         {
240             cacheBuilder.maximumSize(settings.getMaxEntries());
241         }
242 
243         // Old form of stats collection - leave in place. It allows stats from the delegate.
244         if (null != settings.getStatisticsEnabled() && settings.getStatisticsEnabled())
245         {
246             cacheBuilder.recordStats();
247         }
248 
249         if (null != settings.getExpireAfterAccess())
250         {
251             cacheBuilder.expireAfterAccess(settings.getExpireAfterAccess(), MILLISECONDS);
252         }
253         else if (null != settings.getExpireAfterWrite())
254         {
255             cacheBuilder.expireAfterWrite(settings.getExpireAfterWrite(), MILLISECONDS);
256         }
257         return cacheBuilder;
258     }
259 }