View Javadoc

1   package com.atlassian.vcache.internal.core.service;
2   
3   import com.atlassian.vcache.PutPolicy;
4   import com.atlassian.vcache.internal.core.ExternalCacheKeyGenerator;
5   import com.atlassian.vcache.internal.core.RecursionDetector;
6   import com.google.common.collect.BiMap;
7   import com.google.common.collect.HashBiMap;
8   import com.google.common.collect.Maps;
9   import org.slf4j.Logger;
10  import org.slf4j.LoggerFactory;
11  
12  import java.util.Collections;
13  import java.util.HashMap;
14  import java.util.Map;
15  import java.util.Optional;
16  import java.util.Set;
17  import java.util.concurrent.CompletableFuture;
18  import java.util.concurrent.CompletionStage;
19  import java.util.concurrent.ConcurrentHashMap;
20  import java.util.function.BiFunction;
21  import java.util.function.Supplier;
22  
23  import static java.util.Objects.requireNonNull;
24  
25  /**
26   * Represents the request context for an {@link com.atlassian.vcache.ExternalCache}.
27   *
28   * @param <V> the value type
29   * @since 1.0.0
30   */
31  public abstract class AbstractExternalCacheRequestContext<V> {
32      private static final Logger log = LoggerFactory.getLogger(AbstractExternalCacheRequestContext.class);
33  
34      protected final String name;
35  
36      /**
37       * Used to track inflight calls to {@link #computeValue(String, BiFunction)} to detect recursive calls on
38       * the same key. This is because these calls will deadlock.
39       */
40      private final RecursionDetector<String> recursionDetector = new RecursionDetector<>();
41      private final ExternalCacheKeyGenerator keyGenerator;
42      private final Supplier<String> partitionSupplier;
43  
44      // Need to synchronise the map to ensure consistency. Easier to use the Guava BiMap to achieve the result.
45      private final BiMap<String, String> internalToExternalKeyMap = Maps.synchronizedBiMap(HashBiMap.create());
46      // Make the inverse map unmodifiable, to ensure no duplicate processing.
47      private final Map<String, String> externalToInternalKeyMap = Collections.unmodifiableMap(
48              internalToExternalKeyMap.inverse());
49  
50      private final Map<String, CompletionStage<Optional<V>>> internalKeyToValueMap = new ConcurrentHashMap<>();
51      private boolean hasRemoveAll;
52      private final Map<String, DeferredOperation<V>> keyedOperationMap = new HashMap<>();
53  
54      protected AbstractExternalCacheRequestContext(ExternalCacheKeyGenerator keyGenerator,
55                                                    String name,
56                                                    Supplier<String> partitionSupplier) {
57          this.keyGenerator = requireNonNull(keyGenerator);
58          this.name = requireNonNull(name);
59          this.partitionSupplier = requireNonNull(partitionSupplier);
60      }
61  
62      protected abstract long cacheVersion();
63  
64      protected void clearKeyMaps() {
65          internalToExternalKeyMap.clear();
66      }
67  
68      public String externalEntryKeyFor(String internalKey) {
69          final String cached = internalToExternalKeyMap.get(requireNonNull(internalKey));
70          if (cached != null) {
71              return cached;
72          }
73  
74          final String result = keyGenerator.entryKey(
75                  partitionSupplier.get(), name, cacheVersion(), internalKey);
76  
77          // Do a forcePut in case there is a race condition and another thread is also adding the entry
78          internalToExternalKeyMap.forcePut(internalKey, result);
79          return result;
80      }
81  
82      /**
83       * Returns the internal key for a supplied external key. The {@link #externalEntryKeyFor(String)} must have
84       * returned the value now being specified, in the current request.
85       *
86       * @param externalKey the external key previously returned by {@link #externalEntryKeyFor(String)}
87       * @return the internal key
88       */
89      public String internalEntryKeyFor(String externalKey) {
90          return requireNonNull(externalToInternalKeyMap.get(externalKey));
91      }
92  
93      public Optional<Optional<V>> getValueRecorded(String internalKey) {
94          final CompletionStage<Optional<V>> optionalCompletionStage = internalKeyToValueMap.get(requireNonNull(internalKey));
95          if (optionalCompletionStage == null || optionalCompletionStage.toCompletableFuture().isCompletedExceptionally()) {
96              return Optional.empty();
97          }
98          return Optional.ofNullable(optionalCompletionStage.toCompletableFuture().join());
99      }
100 
101     public void recordValue(String internalKey, Optional<V> outcome) {
102         recordValue(requireNonNull(internalKey), CompletableFuture.completedFuture(outcome));
103     }
104 
105     public void recordValue(String internalKey, CompletionStage<Optional<V>> outcome) {
106         log.trace("Cache {}, recording value for {}", name, internalKey);
107         internalKeyToValueMap.put(requireNonNull(internalKey), outcome);
108     }
109 
110     public void recordValues(Map<String, V> knownValues) {
111         log.trace("Cache {}, recording {} known values", name, knownValues.size());
112         knownValues.entrySet().stream().collect(
113                 () -> internalKeyToValueMap,
114                 (m, e) -> m.put(e.getKey(), CompletableFuture.completedFuture(Optional.of(e.getValue()))),
115                 Map::putAll);
116     }
117 
118     public CompletionStage<Optional<V>> computeValue(
119             String key,
120             BiFunction<String, CompletionStage<Optional<V>>, CompletionStage<Optional<V>>> doCompute) {
121         try (RecursionDetector.Guard ignored = recursionDetector.guardOn(key)) {
122             return internalKeyToValueMap.compute(requireNonNull(key), doCompute);
123         }
124     }
125 
126     public void forgetValue(String internalKey) {
127         log.trace("Cache {}, forgetting value for {}", name, internalKey);
128         internalKeyToValueMap.remove(internalKey);
129     }
130 
131     public void forgetAllValues() {
132         log.trace("Cache {}, forgetting all values", name);
133         internalKeyToValueMap.clear();
134     }
135 
136     /**
137      * Saves a new value in the value map and adds the put policy for this operation.
138      *
139      * @param internalKey The internal key.
140      * @param value       The value to record.
141      * @param policy      The {@link PutPolicy} used to record this value.
142      */
143     public void recordPut(String internalKey, V value, PutPolicy policy) {
144         recordValue(internalKey, Optional.of(value));
145         recordPutPolicy(internalKey, value, policy);
146     }
147 
148     /**
149      * Stores the policy for the recordPut().
150      *
151      * @param internalKey The internal key
152      * @param value       The value saved.
153      * @param policy      The {@link PutPolicy} used to record this value.
154      */
155     public void recordPutPolicy(String internalKey, V value, PutPolicy policy) {
156         keyedOperationMap.put(requireNonNull(internalKey), DeferredOperation.putOperation(value, policy));
157     }
158 
159     public void recordRemove(Iterable<String> internalKeys) {
160         for (String internalKey : internalKeys) {
161             recordValue(internalKey, Optional.empty());
162             keyedOperationMap.put(requireNonNull(internalKey), DeferredOperation.removeOperation());
163         }
164     }
165 
166     public void recordRemoveAll() {
167         forgetAllValues();
168         hasRemoveAll = true;
169         keyedOperationMap.clear(); // Forget everything, as starting again
170     }
171 
172     public boolean hasRemoveAll() {
173         return hasRemoveAll;
174     }
175 
176     public void forgetAll() {
177         forgetAllValues();
178         hasRemoveAll = false;
179         keyedOperationMap.clear();
180     }
181 
182     public Set<Map.Entry<String, DeferredOperation<V>>> getKeyedOperations() {
183         return keyedOperationMap.entrySet();
184     }
185 
186     public boolean hasPendingOperations() {
187         return hasRemoveAll || !keyedOperationMap.isEmpty();
188     }
189 
190     /**
191      * Represents a deferred operation.
192      *
193      * @param <V> the value type
194      */
195     public static class DeferredOperation<V> {
196         private final boolean remove;
197         private final CompletionStage<Optional<V>> value;
198         private final Optional<PutPolicy> policy;
199 
200         private DeferredOperation() {
201             this.remove = true;
202             this.value = CompletableFuture.completedFuture(Optional.empty());
203             this.policy = Optional.empty();
204         }
205 
206         private DeferredOperation(V value, PutPolicy policy) {
207             this(CompletableFuture.completedFuture(Optional.of(value)), policy);
208         }
209 
210         private DeferredOperation(CompletionStage<Optional<V>> value, PutPolicy policy) {
211             this.remove = false;
212             this.value = requireNonNull(value);
213             this.policy = Optional.of(policy);
214         }
215 
216         public boolean isRemove() {
217             return remove;
218         }
219 
220         public boolean isPut() {
221             return !remove;
222         }
223 
224         public V getValue() {
225             return value.toCompletableFuture().join().get();
226         }
227 
228         public PutPolicy getPolicy() {
229             return policy.get();
230         }
231 
232         public static <V> DeferredOperation<V> removeOperation() {
233             return new DeferredOperation<>();
234         }
235 
236         public static <V> DeferredOperation<V> putOperation(V value, PutPolicy policy) {
237             return new DeferredOperation<>(value, policy);
238         }
239     }
240 }