View Javadoc

1   package com.atlassian.vcache.internal.core.service;
2   
3   import com.atlassian.vcache.JvmCache;
4   import com.atlassian.vcache.JvmCacheSettings;
5   import com.atlassian.vcache.VCacheException;
6   import com.atlassian.vcache.internal.core.RecursionDetector;
7   import com.google.common.cache.Cache;
8   import com.google.common.cache.CacheBuilder;
9   import com.google.common.util.concurrent.UncheckedExecutionException;
10  
11  import java.util.Arrays;
12  import java.util.HashSet;
13  import java.util.Map;
14  import java.util.Optional;
15  import java.util.Set;
16  import java.util.concurrent.ExecutionException;
17  import java.util.concurrent.TimeUnit;
18  import java.util.concurrent.locks.ReentrantLock;
19  import java.util.function.Function;
20  import java.util.function.Supplier;
21  
22  import static com.atlassian.vcache.internal.NameValidator.requireValidCacheName;
23  import static java.util.Objects.requireNonNull;
24  
25  /**
26   * Implementation of the {@link JvmCache} that uses Guava.
27   *
28   * @param <K> the key type
29   * @param <V> the value type
30   * @since 1.0
31   */
32  public class GuavaJvmCache<K, V> implements JvmCache<K, V> {
33      private final ReentrantLock globalLock = new ReentrantLock();
34      private final String name;
35      private final Cache<K, V> delegate;
36      private final RecursionDetector<K> recursionDetector = new RecursionDetector<>();
37  
38      /**
39       * Creates an instance. <b>NOTE:</b> all the supplied settings must be set.
40       *
41       * @param name     the name of the cache
42       * @param settings the settings for the cache.
43       */
44      public GuavaJvmCache(String name, JvmCacheSettings settings) {
45          this.name = requireValidCacheName(name);
46          //noinspection OptionalGetWithoutIsPresent
47          this.delegate = CacheBuilder.newBuilder()
48                  .maximumSize(settings.getMaxEntries().get())
49                  .expireAfterWrite(settings.getDefaultTtl().get().toMillis(), TimeUnit.MILLISECONDS)
50                  .build();
51      }
52  
53      @Override
54      public Set<K> getKeys() {
55          return withLock(() -> new HashSet<>(delegate.asMap().keySet()));
56      }
57  
58      @Override
59      public Optional<V> get(K key) {
60          return withLock(() -> Optional.ofNullable(delegate.getIfPresent(key)));
61      }
62  
63      @Override
64      public V get(K key, Supplier<? extends V> supplier) {
65          try (RecursionDetector.Guard<K> ignored = recursionDetector.guardOn(key)) {
66              return withLock(() -> {
67                  try {
68                      return delegate.get(requireNonNull(key), supplier::get);
69                  } catch (UncheckedExecutionException | ExecutionException e) {
70                      throw new VCacheException("Supplier failed", e);
71                  }
72              });
73          }
74      }
75  
76      @SafeVarargs
77      @Override
78      public final Map<K, V> getBulk(Function<Set<K>, Map<K, V>> factory, K... keys) {
79          // This method needs to be final to prevent subclasses bypassing the locking enforced by this class.
80          return getBulk(factory, Arrays.asList(keys));
81      }
82  
83      @Override
84      public Map<K, V> getBulk(Function<Set<K>, Map<K, V>> factory, Iterable<K> keys) {
85          return withLock(() -> LocalCacheUtils.getBulk(
86                  factory,
87                  keys,
88                  this::get,
89                  args -> put(args.key, args.value),
90                  recursionDetector));
91      }
92  
93      @Override
94      public void put(K key, V value) {
95          withLock(() -> delegate.put(requireNonNull(key), requireNonNull(value)));
96      }
97  
98      @Override
99      public Optional<V> putIfAbsent(K key, V value) {
100         return withLock(() -> Optional.ofNullable(delegate.asMap().putIfAbsent(requireNonNull(key), requireNonNull(value))));
101     }
102 
103     @Override
104     public boolean replaceIf(K key, V currentValue, V newValue) {
105         return withLock(() -> delegate.asMap().replace(requireNonNull(key), requireNonNull(currentValue), requireNonNull(newValue)));
106     }
107 
108     @Override
109     public void remove(K key) {
110         withLock(() -> delegate.invalidate(key));
111     }
112 
113     @Override
114     public void removeAll() {
115         withLock((Runnable) delegate::invalidateAll);
116     }
117 
118     @Override
119     public boolean removeIf(K key, V value) {
120         return withLock(() -> delegate.asMap().remove(key, value));
121     }
122 
123     @Override
124     public String getName() {
125         return name;
126     }
127 
128     private <R> R withLock(Supplier<R> supplier) {
129         globalLock.lock();
130         try {
131             return supplier.get();
132         } finally {
133             globalLock.unlock();
134         }
135     }
136 
137     private void withLock(Runnable runner) {
138         globalLock.lock();
139         try {
140             runner.run();
141         } finally {
142             globalLock.unlock();
143         }
144     }
145 }