Clover icon

Magnolia Module Cache 5.5.9

  1. Project Clover database Mon Nov 25 2019 16:46:50 CET
  2. Package info.magnolia.module.cache

File AbstractListeningFlushPolicy.java

 

Coverage histogram

../../../../img/srcFileCovDistChart5.png
63% of files have more coverage

Code metrics

26
63
13
2
284
165
29
0.46
4.85
6.5
2.23
8.9% of code in this file is excluded from these metrics.

Classes

Class Line # Actions
AbstractListeningFlushPolicy 62 47 11.6% 22 37
0.513157951.3%
AbstractListeningFlushPolicy.CacheCleaner 191 16 0% 7 23
0.11538461611.5%
 

Contributing tests

This file is covered by 5 tests. .

Source view

1    /**
2    * This file Copyright (c) 2008-2018 Magnolia International
3    * Ltd. (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10    * This file is distributed in the hope that it will be
11    * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12    * implied warranty of MERCHANTABILITY or FITNESS FOR A
13    * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14    * Redistribution, except as permitted by whichever of the GPL
15    * or MNA you select, is prohibited.
16    *
17    * 1. For the GPL license (GPL), you can redistribute and/or
18    * modify this file under the terms of the GNU General
19    * Public License, Version 3, as published by the Free Software
20    * Foundation. You should have received a copy of the GNU
21    * General Public License, Version 3 along with this program;
22    * if not, write to the Free Software Foundation, Inc., 51
23    * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24    *
25    * 2. For the Magnolia Network Agreement (MNA), this file
26    * and the accompanying materials are made available under the
27    * terms of the MNA which accompanies this distribution, and
28    * is available at http://www.magnolia-cms.com/mna.html
29    *
30    * Any modifications to this file must keep this entire header
31    * intact.
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.Arrays;
43    import java.util.Collection;
44    import java.util.HashMap;
45    import java.util.Iterator;
46    import java.util.List;
47    import java.util.Map;
48   
49    import javax.inject.Inject;
50    import javax.jcr.RepositoryException;
51    import javax.jcr.observation.Event;
52    import javax.jcr.observation.EventIterator;
53    import javax.jcr.observation.EventListener;
54   
55    import org.apache.commons.lang3.StringUtils;
56    import org.slf4j.Logger;
57    import org.slf4j.LoggerFactory;
58   
59    /**
60    * Implemenation of the <code>FlushPolicy</code> providing functionality for triggering flush operation based on changes in observed workspaces.
61    */
 
62    public abstract class AbstractListeningFlushPolicy implements FlushPolicy {
63   
64    private static final Logger log = LoggerFactory.getLogger(AbstractListeningFlushPolicy.class);
65   
66    private List<String> workspaces;
67    private List<String> excludedWorkspaces = new ArrayList<String>();
68    private Map<String, EventListener> registeredListeners = new HashMap<>();
69   
70    private final CacheModule cacheModule;
71    private final RepositoryManager repositoryManager;
72   
73    /**
74    * @deprecated since 5.4. Use {@link #AbstractListeningFlushPolicy(CacheModule, RepositoryManager)} instead.
75    */
 
76  0 toggle public AbstractListeningFlushPolicy() {
77  0 this.cacheModule = Components.getComponent(CacheModule.class);
78  0 this.repositoryManager = Components.getComponent(RepositoryManager.class);
79    }
80   
 
81  77 toggle @Inject
82    public AbstractListeningFlushPolicy(CacheModule cacheModule, RepositoryManager repositoryManager) {
83  77 this.cacheModule = cacheModule;
84  77 this.repositoryManager = repositoryManager;
85    }
86   
 
87  7 toggle @Override
88    public void start(Cache cache) {
89  20 for (Iterator<String> iter = this.getWorkspacesToProcess().iterator(); iter.hasNext(); ) {
90  13 final String workspace = iter.next();
91  13 try {
92  13 if (repositoryManager.getWorkspaceMapping(workspace) != null) {
93  13 for (String path : getPaths(cache, workspace)) {
94  13 final CacheCleaner cacheCleaner = new CacheCleaner(cache, workspace);
95  13 final EventListener listener = ObservationUtil.instanciateDeferredEventListener(cacheCleaner, 5000, 30000);
96  13 ObservationUtil.registerChangeListener(workspace, path, listener);
97  13 registeredListeners.put(cache.getName() + ":" + workspace + ":" + path, listener);
98    }
99    }
100    } catch (Exception e) {
101  0 log.warn("Failed to register cache flushing observation for workspace '{}' (cache named {}): ", workspace, cache.getName(), e);
102    }
103    }
104    }
105   
 
106  13 toggle protected Collection<String> getPaths(Cache cache, String workspace) {
107  13 return Arrays.asList(getPath(cache)); //calling the deprecated method to preserve compatibility
108    }
109   
110    /**
111    * @deprecated since 5.4.4. Use {@link #getPaths(Cache, String)} instead.
112    */
 
113  13 toggle @Deprecated
114    protected String getPath(Cache cache) {
115  13 return "/";
116    }
117   
 
118  5 toggle @Override
119    public void stop(Cache cache) {
120  5 for (Map.Entry<String, EventListener> entry : new HashMap<String, EventListener>(registeredListeners).entrySet()) { //duplicate the map to prevent ConcurrentModificationException (might be trigger by observation from advance cache)
121  6 if (entry.getValue() == null) {
122    // happens on restart of cache module after someone configures new listener repository ... we are trying to stop the listener which was not created yet
123  0 continue;
124    }
125  6 if (StringUtils.startsWith(entry.getKey(), cache.getName() + ":")) { //this class might be reused by multiple caches, we need to stop only listeners which belong to the actual cache
126  4 ObservationUtil.unregisterChangeListener(StringUtils.substringBetween(entry.getKey(), ":"), entry.getValue());
127  4 registeredListeners.remove(entry.getKey());
128    }
129    }
130    }
131   
 
132  7 toggle private List<String> getWorkspacesToProcess() {
133  7 if (this.getWorkspaces() != null) {
134  0 return this.getWorkspaces();
135    } else {
136  7 ArrayList<String> workspaces = new ArrayList<String>(repositoryManager.getWorkspaceNames());
137  7 workspaces.removeAll(this.getExcludedWorkspaces());
138  7 return workspaces;
139    }
140    }
141   
142    /**
143    * Implement this method to react on buffered events on a given cache and repository.
144    *
145    * @return true if single events should be processed as well, false otherwise.
146    */
147    protected abstract boolean preHandleEvents(Cache cache, String repository);
148   
149    /**
150    * Implement this method to wrap up flushing process after all single events have been processed.
151    * This method will be invoked only if {@link #preHandleEvents(Cache, String)} returns true;
152    */
153    protected abstract void postHandleEvents(Cache cache, String repository);
154   
155    /**
156    * Implement this method to react on each and every event on a given cache and repository,
157    * even if multiple where buffered.
158    * This method will be invoked only if {@link #preHandleEvents(Cache, String)} returns true;
159    */
160    protected abstract void handleSingleEvent(Cache cache, String repository, Event event);
161   
162    /**
163    * Flushes all content related to given uuid&repository combination from provided cache.
164    * Note that more then only one pages can be flushed when this method is called.
165    */
 
166  0 toggle protected void flushByUUID(String uuid, String repository, Cache cache) {
167   
168  0 final ContentCachingConfiguration config = cacheModule.getContentCaching(cache.getName());
169  0 final CachePolicy policy = config.getCachePolicy();
170  0 if (policy == null) {
171    // no cache policy, no cache key, nothing to flush here ...
172  0 return;
173    }
174    // do NOT remove key mappings, just retrieve them. Rather try remove not existent items then leaving entries in other caches that might depend on the same mapping
175  0 Object[] cacheEntryKeys = config.getCachePolicy().retrieveCacheKeys(uuid, repository);
176  0 log.debug("Flushing {} due to detected content {}:{} update.", cacheEntryKeys, repository, uuid);
177   
178  0 if (cacheEntryKeys == null || cacheEntryKeys.length == 0) {
179    // nothing to remove
180  0 return;
181    }
182  0 for (Object key : cacheEntryKeys) {
183  0 cache.remove(key);
184    }
185    // we are done here
186    }
187   
188    /**
189    * Event listener triggering the cleanup of the cache.
190    */
 
191    protected class CacheCleaner implements EventListener {
192    private final Cache cache;
193    private final String repository;
194   
 
195  13 toggle public CacheCleaner(Cache cache, String repository) {
196  13 this.cache = cache;
197  13 this.repository = repository;
198    }
199   
 
200  0 toggle @Override
201    public void onEvent(EventIterator events) {
202  0 List<Event> eventList = new ArrayList<Event>();
203    // do not react on jcr: specific events. Those are sent to every registered workspace when any of the workspaces stores new version of its content
204  0 while (events.hasNext()) {
205  0 final Event event = events.nextEvent();
206  0 try {
207  0 if (!event.getPath().startsWith("/jcr:")) {
208  0 eventList.add(event);
209    }
210    } catch (RepositoryException e) {
211  0 log.warn("Failed to process an event {}, the observation based cache flushing might not have been fully completed.", event.toString());
212    }
213    }
214  0 if (eventList.isEmpty()) {
215  0 return;
216    }
217    // if there are still any events left, continue here
218  0 boolean shouldContinue = preHandleEvents(cache, repository);
219  0 if (shouldContinue) {
220  0 for (Event event : eventList) {
221  0 handleSingleEvent(cache, repository, event);
222    }
223  0 postHandleEvents(cache, repository);
224    }
225    }
226    }
227   
228    // --------- config methods below
229   
230    /**
231    * The workspaces to which the listener is attached - upon any event on these,
232    * the cache is cleared.
233    */
 
234    toggle public List<String> getWorkspaces() {
235    return workspaces;
236    }
237   
 
238  0 toggle public void setWorkspaces(List<String> workspaces) {
239  0 if (!this.getExcludedWorkspaces().isEmpty()) {
240  0 log.error("You should configure only 'workspaces' or 'excludedWorkspaces' on {}. Not both of them.", this.getClass());
241  0 return;
242    }
243  0 this.workspaces = workspaces;
244    }
245   
246    /**
247    * The workspaces to which the listener is NOT attached.
248    */
 
249    toggle public List<String> getExcludedWorkspaces() {
250    return excludedWorkspaces;
251    }
252   
 
253  3 toggle public void setExcludedWorkspaces(List<String> excludedWorkspaces) {
254  3 if (this.getWorkspaces() != null) {
255  0 log.error("You should configure only 'workspaces' or 'excludedWorkspaces' on {}. Not both of them.", this.getClass());
256  0 return;
257    }
258  3 this.excludedWorkspaces = excludedWorkspaces;
259    }
260   
261    /**
262    * @deprecated since 5.4. Use {@link #getWorkspaces()} instead.
263    */
 
264    toggle public List<String> getRepositories() {
265    DeprecationUtil.isDeprecated("Use info.magnolia.module.cache.AbstractListeningFlushPolicy#getWorkspaces instead.");
266    return this.getWorkspaces();
267    }
268   
269    /**
270    * @deprecated since 5.4. Use {@link #setWorkspaces(List)} instead.
271    */
 
272    toggle public void setRepositories(List<String> repositories) {
273    DeprecationUtil.isDeprecated("Use info.magnolia.module.cache.AbstractListeningFlushPolicy#setWorkspaces instead.");
274    this.setWorkspaces(repositories);
275    }
276   
277    /**
278    * @deprecated since 5.4. Use {@link #setWorkspaces(List)} instead.
279    */
 
280  0 toggle public void addRepository(String repository) {
281  0 DeprecationUtil.isDeprecated("Use info.magnolia.module.cache.AbstractListeningFlushPolicy#setWorkspaces instead.");
282  0 workspaces.add(repository);
283    }
284    }