1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.module.cache.cachepolicy;
35
36 import info.magnolia.cms.cache.CacheConstants;
37 import info.magnolia.cms.core.AggregationState;
38 import info.magnolia.context.MgnlContext;
39 import info.magnolia.context.WebContext;
40 import info.magnolia.module.cache.Cache;
41 import info.magnolia.module.cache.CacheModule;
42 import info.magnolia.module.cache.CachePolicy;
43 import info.magnolia.module.cache.CachePolicyResult;
44 import info.magnolia.module.cache.FlushPolicy;
45 import info.magnolia.module.cache.cachekey.CacheKeyGenerator;
46 import info.magnolia.module.cache.cachekey.DefaultCacheKey;
47 import info.magnolia.module.cache.cachekey.DefaultCacheKeyGenerator;
48 import info.magnolia.module.cache.cachepolicy.instructor.CacheInstructor;
49 import info.magnolia.module.cache.cachepolicy.voters.BrowserTtlVoter;
50 import info.magnolia.module.cache.cachepolicy.voters.ServerTtlVoter;
51 import info.magnolia.module.cache.cachepolicy.voters.TtlVoting;
52 import info.magnolia.module.cache.filter.CacheResponseWrapper;
53 import info.magnolia.module.cache.filter.UncacheableEntry;
54 import info.magnolia.voting.voters.VoterSet;
55
56 import java.util.Collections;
57 import java.util.HashSet;
58 import java.util.Set;
59 import java.util.function.BiConsumer;
60
61 import javax.inject.Inject;
62 import javax.inject.Provider;
63
64 import org.slf4j.Logger;
65 import org.slf4j.LoggerFactory;
66
67
68
69
70 public class Default implements CachePolicy {
71
72 public static final String UUID_KEY_MAP_KEY = "uuid-key-mapping";
73 private static final Logger log = LoggerFactory.getLogger(Default.class);
74
75 private CacheKeyGenerator<?> cacheKeyGenerator = new DefaultCacheKeyGenerator();
76 private VoterSet shouldBypassVoters;
77 private VoterSet<CacheResponseWrapper> ttlVoters = new VoterSet<>();
78
79 private boolean refreshOnNoCacheRequests = false;
80
81 private final CacheModule cacheModule;
82 private final Provider<CacheInstructor> cacheInstructorProvider;
83
84 @Inject
85 public Default(CacheModule cacheModule, Provider<CacheInstructor> cacheInstructorProvider) {
86 this.cacheModule = cacheModule;
87 this.cacheInstructorProvider = cacheInstructorProvider;
88 ttlVoters.setVoting(new TtlVoting());
89 ttlVoters.addVoter(new ServerTtlVoter(cacheInstructorProvider));
90 ttlVoters.addVoter(new BrowserTtlVoter());
91 }
92
93
94 @Override
95 public CachePolicyResult shouldCache(final Cache cache, final AggregationState aggregationState, final FlushPolicy flushPolicy) {
96 final Object key = retrieveCacheKey(aggregationState);
97
98 if (shouldBypass(aggregationState, key)) {
99 return new CachePolicyResult(CachePolicyResult.bypass, key, null);
100 }
101
102 if (shouldRefresh(aggregationState, key)) {
103 log.debug("Cache refresh requested for {}", key);
104 return new CachePolicyResult(CachePolicyResult.store, key, null);
105 }
106
107
108
109
110
111
112
113
114
115
116
117 final Object cachedEntry = cache.get(key);
118
119
120 if (cachedEntry != null) {
121 if (cachedEntry instanceof UncacheableEntry) {
122 return new CachePolicyResult(CachePolicyResult.bypass, key, null);
123 } else {
124 return new CachePolicyResult(CachePolicyResult.useCache, key, cachedEntry);
125 }
126 }
127 return new CachePolicyResult(CachePolicyResult.store, key, null);
128 }
129
130
131
132
133
134
135 protected boolean shouldRefresh(AggregationState aggregationState, Object key) {
136 if (isRefreshOnNoCacheRequests()) {
137 String cacheControl = ((WebContext) MgnlContext.getInstance()).getRequest().getHeader(CacheConstants.HEADER_CACHE_CONTROL);
138 String pragma = ((WebContext) MgnlContext.getInstance()).getRequest().getHeader(CacheConstants.HEADER_PRAGMA);
139 return (cacheControl != null && cacheControl.equals(CacheConstants.HEADER_VALUE_NO_CACHE)) || (pragma != null && pragma.equals(CacheConstants.HEADER_VALUE_NO_CACHE));
140 }
141 return false;
142 }
143
144 protected boolean shouldBypass(AggregationState aggregationState, Object key) {
145 final String uri;
146 if (key instanceof DefaultCacheKey) {
147 uri = ((DefaultCacheKey) key).getUri();
148 } else {
149 uri = key.toString();
150 }
151
152 if (shouldBypassVoters != null) {
153 return shouldBypassVoters.vote(uri) <= 0;
154 }
155 log.warn("No cache voter defined.");
156 return false;
157 }
158
159 public Object retrieveCacheKey(final AggregationState aggregationState) {
160 CacheKeyGenerator<?> cacheKeyGenerator = cacheInstructorProvider.get().getKeyGenerator();
161 if (cacheKeyGenerator == null) {
162 cacheKeyGenerator = getCacheKeyGenerator();
163 }
164 return cacheKeyGenerator.generateKey(aggregationState);
165 }
166
167 @Override
168 public Object[] retrieveCacheKeys(final String uuid, final String repository) {
169 final String uuidKey = repository + ":" + uuid;
170 final Set<Object> keys = getUUIDKeySetFromCacheSafely(uuidKey);
171 return keys.toArray();
172 }
173
174 @Override
175 public void persistCacheKey(final String repo, final String uuid, final Object key) {
176 final String uuidKey = repo + ":" + uuid;
177 final Cache cache = getUuidKeyCache();
178
179 threadSafeUUIDCacheOp(cache, uuidKey, (c, k) -> {
180 k.add(key);
181 c.put(uuidKey, k);
182 });
183 }
184
185 private Set<Object> threadSafeUUIDCacheOp(final Cache cache, final String uuidKey, final BiConsumer<Cache, Set<Object>> c) {
186 synchronized (cache) {
187 Set<Object> keys = (Set<Object>) cache.get(uuidKey);
188 if (keys == null) {
189 keys = Collections.synchronizedSet(new HashSet<>());
190 }
191 c.accept(cache, keys);
192 return keys;
193 }
194 }
195
196 @Override
197 public Object[] removeCacheKeys(final String uuid, final String repository) {
198 final String uuidKey = repository + ":" + uuid;
199 final Cache cache = getUuidKeyCache();
200
201 return threadSafeUUIDCacheOp(cache, uuidKey, (c, k) -> c.remove(uuidKey)).toArray();
202 }
203
204 private Cache getUuidKeyCache() {
205 return cacheModule.getCacheFactory().getCache(UUID_KEY_MAP_KEY);
206 }
207
208
209
210
211
212 private synchronized Set<Object> getUUIDKeySetFromCacheSafely(String uuidKey) {
213 final Cache cache = getUuidKeyCache();
214 synchronized (cache) {
215
216 return threadSafeUUIDCacheOp(cache, uuidKey, (c, k) -> c.put(uuidKey, k));
217 }
218 }
219
220 public boolean isRefreshOnNoCacheRequests() {
221 return this.refreshOnNoCacheRequests;
222 }
223
224 public void setRefreshOnNoCacheRequests(boolean allowNoCacheHeader) {
225 this.refreshOnNoCacheRequests = allowNoCacheHeader;
226 }
227
228 @Override
229 public VoterSet<CacheResponseWrapper> getTtlVoters() {
230 return ttlVoters;
231 }
232
233 public void setTtlVoters(VoterSet<CacheResponseWrapper> ttlVoters) {
234 this.ttlVoters = ttlVoters;
235 }
236
237 public VoterSet getShouldBypassVoters() {
238 return shouldBypassVoters;
239 }
240
241 public void setShouldBypassVoters(VoterSet shouldBypassVoters) {
242 this.shouldBypassVoters = shouldBypassVoters;
243 }
244
245 public CacheKeyGenerator<?> getCacheKeyGenerator() {
246 return cacheKeyGenerator;
247 }
248
249 public void setCacheKeyGenerator(CacheKeyGenerator<?> cacheKeyGenerator) {
250 this.cacheKeyGenerator = cacheKeyGenerator;
251 }
252 }