View Javadoc

1   /**
2    * This file Copyright (c) 2003-2011 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.cms.util;
35  
36  import info.magnolia.context.LifeTimeJCRSessionUtil;
37  
38  import java.util.ArrayList;
39  import java.util.Iterator;
40  import java.util.List;
41  
42  import javax.jcr.RepositoryException;
43  import javax.jcr.Session;
44  import javax.jcr.Workspace;
45  import javax.jcr.observation.Event;
46  import javax.jcr.observation.EventIterator;
47  import javax.jcr.observation.EventListener;
48  import javax.jcr.observation.ObservationManager;
49  
50  import org.apache.jackrabbit.core.observation.ObservationManagerImpl;
51  import org.slf4j.Logger;
52  import org.slf4j.LoggerFactory;
53  
54  
55  /**
56   * Util to register JCR observations. Supports delayed execution of the listener to handle event bursts.
57   *
58   * @version $Id: ObservationUtil.java 56279 2012-03-28 07:58:23Z ehechinger $
59   */
60  public class ObservationUtil {
61  
62      private final static Logger log = LoggerFactory.getLogger(ObservationUtil.class);
63  
64      private static final int ALL_EVENT_TYPES_MASK = Event.NODE_ADDED | Event.NODE_REMOVED | Event.NODE_MOVED | Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED;
65  
66  
67      /**
68       * Unregisters all EventListeners for the given workspace.
69       */
70      public static void dispose(String workspace) {
71          try {
72              ObservationManager om = getObservationManager(workspace);
73  
74              if(om instanceof ObservationManagerImpl) {
75                  ((ObservationManagerImpl)om).dispose();
76              } else {
77                  log.warn("Observation can not be disposed ");
78              }
79          } catch (RepositoryException e) {
80              log.error("Unable to Unregisters all EventListeners for the following workspace "+workspace, e);
81          }
82      }
83  
84  
85      /**
86       * Registers an EventListener for any node type.
87       *
88       * @see #registerChangeListener(String,String,boolean,String[],javax.jcr.observation.EventListener)
89       */
90      public static void registerChangeListener(String workspace, String observationPath, EventListener listener) {
91          registerChangeListener(workspace, observationPath, true, listener);
92      }
93  
94      /**
95       * Registers an EventListener for any node type.
96       *
97       * @see #registerChangeListener(String,String,boolean,String[],javax.jcr.observation.EventListener)
98       */
99      public static void registerChangeListener(String workspace, String observationPath, boolean includeSubnodes, EventListener listener) {
100         registerChangeListener(workspace, observationPath, includeSubnodes, (String[]) null, listener);
101     }
102 
103     /**
104      * Registers an EventListener for a specific node type.
105      *
106      * @see #registerChangeListener(String,String,boolean,String[],javax.jcr.observation.EventListener)
107      */
108     public static void registerChangeListener(String workspace, String observationPath, boolean includeSubnodes, String nodeType, EventListener listener) {
109         registerChangeListener(workspace, observationPath, includeSubnodes, new String[] { nodeType }, listener);
110     }
111 
112     /**
113      * Registers an EventListener for a specific node type and event types.
114      *
115      * @see #registerChangeListener(String,String,boolean,String[],int,javax.jcr.observation.EventListener)
116      */
117     public static void registerChangeListener(String workspace, String observationPath, boolean includeSubnodes, String nodeType, int eventTypesMask, EventListener listener) {
118         if (nodeType == null) {
119             registerChangeListener(workspace, observationPath, includeSubnodes, (String[]) null, eventTypesMask, listener);
120         } else {
121             registerChangeListener(workspace, observationPath, includeSubnodes, new String[] { nodeType }, eventTypesMask, listener);
122         }
123     }
124 
125     /**
126      * Registers an EventListener for a specific set of node types and event types.
127      *
128      * @see #registerChangeListener(String, String, boolean, String[], int, javax.jcr.observation.EventListener)
129      */
130     public static void registerChangeListener(String workspace, String observationPath, boolean includeSubnodes, String[] nodeTypes, EventListener listener) {
131         registerChangeListener(workspace, observationPath, includeSubnodes, nodeTypes, ALL_EVENT_TYPES_MASK, listener);
132     }
133 
134     /**
135      * Register a single event listener, bound to the given path. Be careful
136      * that if you observe "/", events are going to be generated for jcr:system,
137      * which is "shared" across all workspaces.
138      *
139      * @param workspace
140      * @param observationPath workspace path
141      * @param includeSubnodes the isDeep parameter of ObservationManager.addEventListener()
142      * @param nodeTypes the node types to filter events for
143      * @param eventTypesMask an ORed mask of even types (Event constants)
144      * @param listener event listener
145      * @see ObservationManager#addEventListener
146      * @see Event
147      */
148     public static void registerChangeListener(String workspace, String observationPath, boolean includeSubnodes, String[] nodeTypes, int eventTypesMask, EventListener listener) {
149         log.debug("Registering event listener for path [{}]", observationPath); //$NON-NLS-1$
150 
151         try {
152             ObservationManager observationManager = getObservationManager(workspace);
153             if (observationManager == null) {
154                 log.error("Unable to add event listeners for " + observationPath); //$NON-NLS-1$
155                 throw new IllegalStateException("Observation manager can't be obtained due to invalid session.");
156             }
157 
158             observationManager.addEventListener(listener, eventTypesMask, observationPath, includeSubnodes, null, nodeTypes, false);
159         } catch (RepositoryException e) {
160             log.error("Unable to add event listeners for " + observationPath, e); //$NON-NLS-1$
161         }
162     }
163 
164     public static void registerDeferredChangeListener(String workspace, String observationPath, EventListener listener, long delay, long maxDelay) {
165         registerDeferredChangeListener(workspace, observationPath, true, (String[]) null, listener, delay, maxDelay);
166     }
167 
168     public static void registerDeferredChangeListener(String workspace, String observationPath, boolean includeSubnodes, EventListener listener, long delay, long maxDelay) {
169         registerDeferredChangeListener(workspace, observationPath, includeSubnodes, (String[]) null, listener, delay, maxDelay);
170     }
171 
172     public static void registerDeferredChangeListener(String workspace, String observationPath, boolean includeSubnodes, String nodeType, EventListener listener, long delay, long maxDelay) {
173         registerDeferredChangeListener(workspace, observationPath, includeSubnodes, new String[] { nodeType }, listener, delay, maxDelay);
174     }
175 
176     /**
177      * The event firing is deferred in case there is a series of fired events.
178      *
179      * @return the wrapped EventListener so that one can unregister it.
180      */
181     public static void registerDeferredChangeListener(String workspace, String observationPath, boolean includeSubnodes, String[] nodeTypes, EventListener listener, long delay, long maxDelay) {
182         final EventListener deferredListener = instanciateDeferredEventListener(listener, delay, maxDelay);
183         registerChangeListener(workspace, observationPath, includeSubnodes, nodeTypes, deferredListener);
184     }
185 
186     /**
187      * Use this and register the returned EventListener with the
188      * registerChangeListener() methods, if you need to be able to later
189      * unregister your EventListener.
190      */
191     public static EventListener instanciateDeferredEventListener(EventListener listener, long delay, long maxDelay) {
192         return new DeferringEventListener(listener, delay, maxDelay);
193     }
194 
195     public static void unregisterChangeListener(String workspace, EventListener listener) {
196         try {
197             ObservationManager om = getObservationManager(workspace);
198             if (om == null) {
199                 // session have been invalidated and Observation manager is disconnected. Nothing to unregister. Bail out.
200                 return;
201             }
202             om.removeEventListener(listener);
203         } catch (RepositoryException e) {
204             log.error("Unable to remove event listener [" + listener + "] from workspace " + workspace, e);
205         }
206     }
207 
208     private static ObservationManager getObservationManager(String workspace) throws RepositoryException {
209         Workspace wks = getSession(workspace).getWorkspace();
210         return wks.getSession().isLive() ? wks.getObservationManager() : null;
211     }
212 
213     private static Session getSession(String workspace) throws RepositoryException {
214         return LifeTimeJCRSessionUtil.getSession(workspace);
215     }
216 
217     /**
218      * A listener using an {@link ObservationBasedDelayedExecutor}.
219      */
220     public static class DeferringEventListener implements EventListener {
221 
222         private ObservationBasedDelayedExecutor executor;
223 
224         private EventListener listener;
225 
226         public DeferringEventListener(EventListener listener, long delay, long maxDelay) {
227             this.listener = listener;
228             executor = new ObservationBasedDelayedExecutor(listener, delay, maxDelay);
229         }
230 
231         @Override
232         public void onEvent(EventIterator events) {
233             this.executor.consume(events);
234         }
235 
236         @Override
237         public String toString() {
238             return super.toString() + ":" + this.listener;
239         }
240     }
241 
242     /**
243      * Deferred event handling. Uses the DelayedExecutor class.
244      */
245     public static class ObservationBasedDelayedExecutor {
246 
247         private final DelayedExecutor delayedExecutor;
248         private final List eventsBuffer = new ArrayList();
249 
250         public ObservationBasedDelayedExecutor(final EventListener listener, long delay, long maxDelay) {
251             delayedExecutor = new DelayedExecutor(new Runnable() {
252                 @Override
253                 public void run() {
254                     // during execution consume is blocked
255                     synchronized (eventsBuffer) {
256                         listener.onEvent(new ListBasedEventIterator(eventsBuffer));
257                         eventsBuffer.clear();
258                     }
259                 }
260             }, delay, maxDelay);
261         }
262 
263         protected void consume(EventIterator events) {
264             synchronized (this.eventsBuffer) {
265                 while (events.hasNext()) {
266                     this.eventsBuffer.add(events.next());
267                 }
268                 delayedExecutor.trigger();
269             }
270         }
271     }
272 
273     /**
274      * List based event iterator. Used to collect events in a list which are
275      * later on passed to the listener.
276      */
277     public static class ListBasedEventIterator implements EventIterator {
278 
279         private Iterator iterator;
280         private List events;
281         private int pos = 0;
282 
283         public ListBasedEventIterator(List events) {
284             this.events = events;
285             this.iterator = events.iterator();
286         }
287 
288         @Override
289         public boolean hasNext() {
290             return this.iterator.hasNext();
291         }
292 
293         @Override
294         public Object next() {
295             pos++;
296             return this.iterator.next();
297         }
298 
299         @Override
300         public void remove() {
301             this.iterator.remove();
302         }
303 
304         @Override
305         public Event nextEvent() {
306             return (Event) next();
307         }
308 
309         @Override
310         public long getPosition() {
311             return pos;
312         }
313 
314         @Override
315         public long getSize() {
316             return events.size();
317         }
318 
319         @Override
320         public void skip(long skipNum) {
321             for (int i = 0; i < skipNum; i++) {
322                 next();
323             }
324         }
325     }
326 
327 }