View Javadoc

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