1 package com.atlassian.vcache.internal.guava;
2
3 import com.atlassian.marshalling.api.MarshallingPair;
4 import com.atlassian.vcache.ExternalCacheException;
5 import com.atlassian.vcache.internal.RequestContext;
6 import com.atlassian.vcache.internal.core.ExternalCacheKeyGenerator;
7 import com.atlassian.vcache.internal.core.TransactionControlManager;
8 import com.atlassian.vcache.internal.core.cas.IdentifiedData;
9 import com.atlassian.vcache.internal.core.metrics.MetricsRecorder;
10 import com.atlassian.vcache.internal.core.service.AbstractExternalCacheRequestContext;
11 import com.atlassian.vcache.internal.core.service.AbstractTransactionalExternalCache;
12 import com.atlassian.vcache.internal.core.service.UnversionedExternalCacheRequestContext;
13 import com.google.common.cache.Cache;
14 import org.slf4j.Logger;
15 import org.slf4j.LoggerFactory;
16
17 import java.util.Map;
18 import java.util.Optional;
19 import java.util.Set;
20 import java.util.function.Supplier;
21
22 import static com.atlassian.vcache.internal.core.cas.IdentifiedUtils.marshall;
23 import static com.atlassian.vcache.internal.core.cas.IdentifiedUtils.unmarshall;
24 import static java.util.Objects.requireNonNull;
25
26
27
28
29
30
31
32 public class GuavaTransactionalExternalCache<V>
33 extends AbstractTransactionalExternalCache<V> {
34 private static final Logger log = LoggerFactory.getLogger(GuavaTransactionalExternalCache.class);
35
36 private final Cache<String, IdentifiedData> delegate;
37 private final ExternalCacheKeyGenerator keyGenerator;
38 private final Optional<MarshallingPair<V>> valueMarshalling;
39 private final TransactionControlManager transactionControlManager;
40
41 public GuavaTransactionalExternalCache(
42 String name,
43 Cache<String, IdentifiedData> delegate,
44 Supplier<RequestContext> contextSupplier,
45 ExternalCacheKeyGenerator keyGenerator,
46 Optional<MarshallingPair<V>> valueMarshalling,
47 TransactionControlManager transactionControlManager,
48 MetricsRecorder metricsRecorder) {
49 super(name, contextSupplier, metricsRecorder);
50 this.delegate = requireNonNull(delegate);
51 this.keyGenerator = requireNonNull(keyGenerator);
52 this.valueMarshalling = requireNonNull(valueMarshalling);
53 this.transactionControlManager = requireNonNull(transactionControlManager);
54 }
55
56 @Override
57 public void transactionSync() {
58 log.trace("Cache {}: synchronising operations", name);
59 final AbstractExternalCacheRequestContext<V> cacheContext = ensureCacheContext();
60
61 if (cacheContext.hasRemoveAll()) {
62 delegate.asMap().clear();
63 }
64
65 performKeyedOperations(cacheContext);
66 cacheContext.forgetAll();
67 }
68
69 @Override
70 protected AbstractExternalCacheRequestContext<V> ensureCacheContext() {
71 final RequestContext requestContext = contextSupplier.get();
72
73 transactionControlManager.registerTransactionalExternalCache(requestContext, name, this);
74
75 return requestContext.computeIfAbsent(this, () -> {
76
77
78 log.trace("Cache {}: Setting up a new context", name);
79 return new UnversionedExternalCacheRequestContext<>(
80 keyGenerator, getName(), requestContext::partitionIdentifier);
81 });
82 }
83
84 @Override
85 protected Logger getLogger() {
86 return log;
87 }
88
89 @Override
90 protected final ExternalCacheException mapException(Exception ex) {
91 return GuavaUtils.mapException(ex);
92 }
93
94 @Override
95 protected final Optional<V> directGet(String externalKey) {
96 return unmarshall(delegate.getIfPresent(externalKey), valueMarshalling);
97 }
98
99 @Override
100 protected final Map<String, Optional<V>> directGetBulk(Set<String> externalKeys) {
101 return GuavaUtils.directGetBulk(externalKeys, delegate, valueMarshalling);
102 }
103
104 private void performKeyedOperations(AbstractExternalCacheRequestContext<V> cacheContext) {
105 try {
106 for (Map.Entry<String, AbstractExternalCacheRequestContext.DeferredOperation<V>> entry
107 : cacheContext.getKeyedOperations()) {
108 final String externalKey = cacheContext.externalEntryKeyFor(entry.getKey());
109
110 if (entry.getValue().isRemove()) {
111 log.trace("Cache {}: performing remove on entry {}", name, entry.getKey());
112 delegate.asMap().remove(externalKey);
113 } else {
114 log.trace("Cache {}: performing {} on entry {}", name, entry.getValue().getPolicy(), entry.getKey());
115 final IdentifiedData identifiedData = marshall(entry.getValue().getValue(), valueMarshalling);
116
117 final boolean putOutcome =
118 GuavaUtils.directPut(
119 externalKey,
120 identifiedData,
121 entry.getValue().getPolicy(),
122 delegate);
123
124 if (!putOutcome) {
125 log.debug("Cache {}: Unable to perform put() operation {} on entry {}, clearing cache",
126 name, entry.getValue().getPolicy(), entry.getKey());
127 delegate.asMap().clear();
128 break;
129 }
130 }
131 }
132 } catch (ExternalCacheException bugger) {
133 log.error("Cache {}: an operation failed in transaction sync, so clearing the cache", name);
134 delegate.asMap().clear();
135 }
136 }
137
138 }