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