View Javadoc

1   /**
2    * This file Copyright (c) 2008-2010 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.beans.config.ContentRepository;
37  import info.magnolia.cms.util.ObservationUtil;
38  import info.magnolia.module.ModuleRegistry;
39  
40  import javax.jcr.observation.Event;
41  import javax.jcr.observation.EventIterator;
42  import javax.jcr.observation.EventListener;
43  import javax.jcr.RepositoryException;
44  import java.util.ArrayList;
45  import java.util.HashMap;
46  import java.util.Iterator;
47  import java.util.List;
48  import java.util.Map;
49  
50  import org.apache.commons.lang.builder.ToStringBuilder;
51  import org.slf4j.Logger;
52  import org.slf4j.LoggerFactory;
53  
54  /**
55   * Implemenation of the <code>FlushPolicy</code> providing functionality for triggering flush operation based on changes in observed repositories.
56   * @author gjoseph
57   * @version $Revision: $ ($Author: $)
58   */
59  public abstract class AbstractListeningFlushPolicy implements FlushPolicy {
60  
61      private static final Logger log = LoggerFactory.getLogger(AbstractListeningFlushPolicy.class);
62  
63      private List repositories = new ArrayList();
64      private Map registeredListeners = new HashMap();
65  
66      /**
67       * The repositories to which the listener is attached - upon any event on these,
68       * the cache is cleared.
69       */
70      public List getRepositories() {
71          return repositories;
72      }
73  
74      public void setRepositories(List repositories) {
75          this.repositories = repositories;
76      }
77  
78      public void addRepository(String repository) {
79          repositories.add(repository);
80      }
81  
82      public void start(Cache cache) {
83          for (Iterator iter = repositories.iterator(); iter.hasNext();) {
84              final String repository = (String) iter.next();
85              if (ContentRepository.getRepositoryMapping(repository) != null) {
86                  final CacheCleaner cacheCleaner = new CacheCleaner(cache, repository);
87                  final EventListener listener = ObservationUtil.instanciateDeferredEventListener(cacheCleaner, 5000, 30000);
88                  try {
89                      ObservationUtil.registerChangeListener(repository, "/", listener);
90                  } catch (Exception e) {
91                      log.warn("Failed to register cache flushing observation for repository {} due to {}", repository, e.getMessage());
92                      log.warn("Publishing any content to {} will not result in update of the cache. Please flush the cache manually.");
93                  }
94                  registeredListeners.put(repository, listener);
95              }
96          }
97      }
98  
99      public void stop(Cache cache) {
100         final Iterator i = registeredListeners.keySet().iterator();
101         while (i.hasNext()) {
102             final String repository = (String) i.next();
103             final EventListener listener = (EventListener) registeredListeners.get(repository);
104             if (listener == null) {
105                 // happens on restart of cache module after someone configures new listener repository ... we are trying to stop the listener which was not created yet
106                 continue;
107             }
108             ObservationUtil.unregisterChangeListener(repository, listener);
109         }
110     }
111 
112     /**
113      * Implement this method to react on buffered events on a given cache and repository.
114      * @return true if single events should be processed as well, false otherwise.
115      */
116     protected abstract boolean preHandleEvents(Cache cache, String repository);
117 
118     /**
119      * Implement this method to wrap up flushing process after all single events have been processed.
120      * This method will be invoked only if {@link #preHandleEvents(Cache, String)} returns true;
121      */
122     protected abstract void postHandleEvents(Cache cache, String repository);
123 
124 
125     /**
126      * Implement this method to react on each and every event on a given cache and repository,
127      * even if multiple where buffered.
128      * This method will be invoked only if {@link #preHandleEvents(Cache, String)} returns true;
129      */
130     protected abstract void handleSingleEvent(Cache cache, String repository, Event event);
131 
132     /**
133      * Flushes all content related to given uuid&repository combination from provided cache.
134      * Note that more then only one pages can be flushed when this method is called.
135      * @param uuid
136      */
137     protected void flushByUUID(String uuid, String repository, Cache cache) {
138         CacheModule cacheModule = ((CacheModule) ModuleRegistry.Factory.getInstance().getModuleInstance("cache"));
139 
140             final CacheConfiguration config = cacheModule.getConfiguration(cache.getName());
141             final CachePolicy policy = config.getCachePolicy();
142             if (policy == null) {
143                 // no cache policy, no cache key, nothing to flush here ...
144                 return;
145             }
146             Object[] cacheEntryKeys = config.getCachePolicy().removeCacheKeys(uuid, repository);
147             log.debug("Flushing {} due to detected content update.", ToStringBuilder.reflectionToString(cacheEntryKeys));
148 
149             if (cacheEntryKeys == null || cacheEntryKeys.length == 0) {
150                 // nothing to remove
151                 return;
152             }
153             for (Object key : cacheEntryKeys) {
154                 cache.remove(key);
155             }
156             // we are done here
157     }
158 
159     /**
160      * Event listener triggering the cleanup of the cache.
161      */
162     protected class CacheCleaner implements EventListener {
163         private final Cache cache;
164         private final String repository;
165 
166         public CacheCleaner(Cache cache, String repository) {
167             this.cache = cache;
168             this.repository = repository;
169         }
170 
171         public void onEvent(EventIterator events) {
172             List<Event> eventList = new ArrayList<Event>();
173             // 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
174             while (events.hasNext()) {
175                 final Event event = events.nextEvent();
176                 try {
177                     if (!event.getPath().startsWith("/jcr:")) {
178                         eventList.add(event);
179                     }
180                 } catch (RepositoryException e) {
181                     log.warn("Failed to process an event {}, the observation based cache flushing might not have been fully completed.", event.toString());
182                 }
183             }
184             if (eventList.isEmpty()) {
185                 return;
186             }
187             // if there are still any events left, continue here
188             boolean shouldContinue = preHandleEvents(cache, repository);
189             if (shouldContinue) {
190                 for (Event event : eventList) {
191                     handleSingleEvent(cache, repository, event);
192                 }
193                 postHandleEvents(cache, repository);
194             }
195         }
196     }
197 }