View Javadoc

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