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;
35
36 import info.magnolia.cms.util.DeprecationUtil;
37 import info.magnolia.cms.util.ObservationUtil;
38 import info.magnolia.objectfactory.Components;
39 import info.magnolia.repository.RepositoryManager;
40
41 import java.util.ArrayList;
42 import java.util.HashMap;
43 import java.util.Iterator;
44 import java.util.List;
45 import java.util.Map;
46
47 import javax.inject.Inject;
48 import javax.jcr.RepositoryException;
49 import javax.jcr.observation.Event;
50 import javax.jcr.observation.EventIterator;
51 import javax.jcr.observation.EventListener;
52
53 import org.apache.commons.lang3.StringUtils;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57
58
59
60 public abstract class AbstractListeningFlushPolicy implements FlushPolicy {
61
62 private static final Logger log = LoggerFactory.getLogger(AbstractListeningFlushPolicy.class);
63
64 private List<String> workspaces;
65 private List<String> excludedWorkspaces = new ArrayList<String>();
66 private Map<String, EventListener> registeredListeners = new HashMap<>();
67
68 private final CacheModule cacheModule;
69 private final RepositoryManager repositoryManager;
70
71
72
73
74 public AbstractListeningFlushPolicy() {
75 this.cacheModule = Components.getComponent(CacheModule.class);
76 this.repositoryManager = Components.getComponent(RepositoryManager.class);
77 }
78
79 @Inject
80 public AbstractListeningFlushPolicy(CacheModule cacheModule, RepositoryManager repositoryManager) {
81 this.cacheModule = cacheModule;
82 this.repositoryManager = repositoryManager;
83 }
84
85 @Override
86 public void start(Cache cache) {
87 for (Iterator<String> iter = this.getWorkspacesToProcess().iterator(); iter.hasNext(); ) {
88 final String workspace = iter.next();
89 try {
90 if (repositoryManager.getWorkspaceMapping(workspace) != null) {
91 final CacheCleaner cacheCleaner = new CacheCleaner(cache, workspace);
92 final EventListener listener = ObservationUtil.instanciateDeferredEventListener(cacheCleaner, 5000, 30000);
93 final String path = this.getPath(cache);
94 ObservationUtil.registerChangeListener(workspace, path, listener);
95 registeredListeners.put(workspace + ":" + path, listener);
96 }
97 } catch (Exception e) {
98 log.warn("Failed to register cache flushing observation for workspace '{}'. "
99 + "Publishing any content to this workspace will not result in update of the cache. Please flush the cache manually.", workspace, e.getMessage());
100 }
101 }
102 }
103
104 protected String getPath(Cache cache) {
105 return "/";
106 }
107
108 @Override
109 public void stop(Cache cache) {
110 for (Map.Entry<String, EventListener> entry : registeredListeners.entrySet()) {
111 if (entry.getValue() == null) {
112
113 continue;
114 }
115 ObservationUtil.unregisterChangeListener(StringUtils.substringBefore(entry.getKey(), ":"), entry.getValue());
116 }
117 }
118
119 private List<String> getWorkspacesToProcess() {
120 if (this.getWorkspaces() != null) {
121 return this.getWorkspaces();
122 } else {
123 ArrayList<String> workspaces = new ArrayList<String>(repositoryManager.getWorkspaceNames());
124 workspaces.removeAll(this.getExcludedWorkspaces());
125 return workspaces;
126 }
127 }
128
129
130
131
132
133
134 protected abstract boolean preHandleEvents(Cache cache, String repository);
135
136
137
138
139
140 protected abstract void postHandleEvents(Cache cache, String repository);
141
142
143
144
145
146
147 protected abstract void handleSingleEvent(Cache cache, String repository, Event event);
148
149
150
151
152
153 protected void flushByUUID(String uuid, String repository, Cache cache) {
154
155 final ContentCachingConfiguration config = cacheModule.getContentCaching(cache.getName());
156 final CachePolicy policy = config.getCachePolicy();
157 if (policy == null) {
158
159 return;
160 }
161
162 Object[] cacheEntryKeys = config.getCachePolicy().retrieveCacheKeys(uuid, repository);
163 log.debug("Flushing {} due to detected content {}:{} update.", cacheEntryKeys, repository, uuid);
164
165 if (cacheEntryKeys == null || cacheEntryKeys.length == 0) {
166
167 return;
168 }
169 for (Object key : cacheEntryKeys) {
170 cache.remove(key);
171 }
172
173 }
174
175
176
177
178 protected class CacheCleaner implements EventListener {
179 private final Cache cache;
180 private final String repository;
181
182 public CacheCleaner(Cache cache, String repository) {
183 this.cache = cache;
184 this.repository = repository;
185 }
186
187 @Override
188 public void onEvent(EventIterator events) {
189 List<Event> eventList = new ArrayList<Event>();
190
191 while (events.hasNext()) {
192 final Event event = events.nextEvent();
193 try {
194 if (!event.getPath().startsWith("/jcr:")) {
195 eventList.add(event);
196 }
197 } catch (RepositoryException e) {
198 log.warn("Failed to process an event {}, the observation based cache flushing might not have been fully completed.", event.toString());
199 }
200 }
201 if (eventList.isEmpty()) {
202 return;
203 }
204
205 boolean shouldContinue = preHandleEvents(cache, repository);
206 if (shouldContinue) {
207 for (Event event : eventList) {
208 handleSingleEvent(cache, repository, event);
209 }
210 postHandleEvents(cache, repository);
211 }
212 }
213 }
214
215
216
217
218
219
220
221 public List<String> getWorkspaces() {
222 return workspaces;
223 }
224
225 public void setWorkspaces(List<String> workspaces) {
226 if (!this.getExcludedWorkspaces().isEmpty()) {
227 log.error("You should configure only 'workspaces' or 'excludedWorkspaces' on {}. Not both of them.", this.getClass());
228 return;
229 }
230 this.workspaces = workspaces;
231 }
232
233
234
235
236 public List<String> getExcludedWorkspaces() {
237 return excludedWorkspaces;
238 }
239
240 public void setExcludedWorkspaces(List<String> excludedWorkspaces) {
241 if (this.getWorkspaces() != null) {
242 log.error("You should configure only 'workspaces' or 'excludedWorkspaces' on {}. Not both of them.", this.getClass());
243 return;
244 }
245 this.excludedWorkspaces = excludedWorkspaces;
246 }
247
248
249
250
251 public List<String> getRepositories() {
252 DeprecationUtil.isDeprecated("Use info.magnolia.module.cache.AbstractListeningFlushPolicy#getWorkspaces instead.");
253 return this.getWorkspaces();
254 }
255
256
257
258
259 public void setRepositories(List<String> repositories) {
260 DeprecationUtil.isDeprecated("Use info.magnolia.module.cache.AbstractListeningFlushPolicy#setWorkspaces instead.");
261 this.setWorkspaces(repositories);
262 }
263
264
265
266
267 public void addRepository(String repository) {
268 DeprecationUtil.isDeprecated("Use info.magnolia.module.cache.AbstractListeningFlushPolicy#setWorkspaces instead.");
269 workspaces.add(repository);
270 }
271 }