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