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.entrySet().stream().collect(
108                 () -> internalKeyToValueMap,
109                 (m, e) -> m.put(e.getKey(), Optional.of(e.getValue())),
110                 Map::putAll);
111     }
112 
113     public void forgetValue(String internalKey) {
114         log.trace("Cache {}, forgetting value for {}", name, internalKey);
115         internalKeyToValueMap.remove(internalKey);
116     }
117 
118     public void forgetAllValues() {
119         log.trace("Cache {}, forgetting all values", name);
120         internalKeyToValueMap.clear();
121     }
122 
123     /**
124      * Saves a new value in the value map and adds the put policy for this operation.
125      *
126      * @param internalKey The internal key.
127      * @param value       The value to record.
128      * @param policy      The {@link PutPolicy} used to record this value.
129      */
130     public void recordPut(String internalKey, V value, PutPolicy policy) {
131         recordValue(internalKey, Optional.of(value));
132         recordPutPolicy(internalKey, value, policy);
133     }
134 
135     /**
136      * Stores the policy for the recordPut().
137      *
138      * @param internalKey The internal key
139      * @param value       The value saved.
140      * @param policy      The {@link PutPolicy} used to record this value.
141      */
142     public void recordPutPolicy(String internalKey, V value, PutPolicy policy) {
143         keyedOperationMap.put(requireNonNull(internalKey), DeferredOperation.putOperation(value, policy));
144     }
145 
146     public void recordRemove(Iterable<String> internalKeys) {
147         for (String internalKey : internalKeys) {
148             recordValue(internalKey, Optional.empty());
149             keyedOperationMap.put(requireNonNull(internalKey), DeferredOperation.removeOperation());
150         }
151     }
152 
153     public void recordRemoveAll() {
154         forgetAllValues();
155         hasRemoveAll = true;
156         keyedOperationMap.clear(); // Forget everything, as starting again
157     }
158 
159     public boolean hasRemoveAll() {
160         return hasRemoveAll;
161     }
162 
163     public void forgetAll() {
164         forgetAllValues();
165         hasRemoveAll = false;
166         keyedOperationMap.clear();
167     }
168 
169     public Set<Map.Entry<String, DeferredOperation<V>>> getKeyedOperations() {
170         return keyedOperationMap.entrySet();
171     }
172 
173     public boolean hasPendingOperations() {
174         return hasRemoveAll || !keyedOperationMap.isEmpty();
175     }
176 
177     /**
178      * Represents a deferred operation.
179      *
180      * @param <V> the value type
181      */
182     public static class DeferredOperation<V> {
183         private final boolean remove;
184         private final CompletionStage<Optional<V>> value;
185         private final Optional<PutPolicy> policy;
186 
187         private DeferredOperation() {
188             this.remove = true;
189             this.value = CompletableFuture.completedFuture(Optional.empty());
190             this.policy = Optional.empty();
191         }
192 
193         private DeferredOperation(V value, PutPolicy policy) {
194             this(CompletableFuture.completedFuture(Optional.of(value)), policy);
195         }
196 
197         private DeferredOperation(CompletionStage<Optional<V>> value, PutPolicy policy) {
198             this.remove = false;
199             this.value = requireNonNull(value);
200             this.policy = Optional.of(policy);
201         }
202 
203         public boolean isRemove() {
204             return remove;
205         }
206 
207         public boolean isPut() {
208             return !remove;
209         }
210 
211         public V getValue() {
212             return unsafeJoin(value).get();
213         }
214 
215         public PutPolicy getPolicy() {
216             return policy.get();
217         }
218 
219         public static <V> DeferredOperation<V> removeOperation() {
220             return new DeferredOperation<>();
221         }
222 
223         public static <V> DeferredOperation<V> putOperation(V value, PutPolicy policy) {
224             return new DeferredOperation<>(value, policy);
225         }
226     }
227 }