View Javadoc
1   /**
2    * This file Copyright (c) 2016 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.observation;
35  
36  import info.magnolia.context.LifeTimeJCRSessionUtil;
37  
38  import javax.jcr.RepositoryException;
39  import javax.jcr.Session;
40  import javax.jcr.observation.Event;
41  import javax.jcr.observation.EventListener;
42  import javax.jcr.observation.EventListenerIterator;
43  import javax.jcr.observation.ObservationManager;
44  
45  import org.slf4j.Logger;
46  import org.slf4j.LoggerFactory;
47  
48  import com.google.common.base.Optional;
49  import com.google.common.base.Preconditions;
50  import com.google.common.base.Predicate;
51  import com.google.common.collect.Iterators;
52  
53  /**
54   * <p>Helper class for registering {@link EventListener} for workspace observation.</p>
55   *
56   * <p>Use {@link #observe(String, String, EventListener)} to obtain a {@link Registrar} object for configuring the {@link EventListener event listener}
57   * and call {@link Registrar#register()} to perform the actual registration.</p>
58   *
59   * <p>The returned {@link Handle} should be used to unregister the {@link EventListener event listener}.</p>
60   *
61   * @see EventListener
62   * @see ObservationManager
63   */
64  public class WorkspaceEventListenerRegistration {
65  
66      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;
67  
68      private static final Logger log = LoggerFactory.getLogger(WorkspaceEventListenerRegistration.class);
69  
70      private WorkspaceEventListenerRegistration() {
71      }
72  
73      /**
74       * <p>Creates a {@link Registrar} object for configuring and registering the {@link EventListener}.
75       * <strong>Be careful that if you observe "/", events may be generated for jcr:system, which is "shared" across all workspaces.</strong>
76       * Use {@link info.magnolia.cms.util.FilteredEventListener} and {@link info.magnolia.cms.util.FilteredEventListener#JCR_SYSTEM_EXCLUDING_PREDICATE JCR_SYSTEM_EXCLUDING_PREDICATE} wherever appropriate.</p>
77       *
78       * <p>Note that only one instance of the {@link EventListener} is allowed per workspace.</p>
79       *
80       * @param workspace workspace to be observed
81       * @param observationPath path in the workspace to be observed
82       * @param eventListener event listener
83       * @return {@link Registrar} object for configuring and registering the {@link EventListener}
84       * @throws NullPointerException if any of the mandatory parameters is null
85       */
86      public static Registrar observe(final String workspace, final String observationPath, final EventListener eventListener) {
87          Preconditions.checkNotNull(workspace, "Workspace cannot be null.");
88          Preconditions.checkNotNull(observationPath, "Observation path cannot be null.");
89          Preconditions.checkNotNull(eventListener, "Event listener cannot be null.");
90          return new Registrar(workspace, observationPath, eventListener);
91      }
92  
93      private static ObservationManager getObservationManager(final String workspace) throws RepositoryException {
94          final Session session = LifeTimeJCRSessionUtil.getSession(workspace);
95          return session.getWorkspace().getObservationManager();
96      }
97  
98      @SuppressWarnings("unchecked")
99      private static boolean isAlreadyRegistered(final String workspace, final EventListener listener) throws RepositoryException {
100         EventListenerIterator iterator = getObservationManager(workspace).getRegisteredEventListeners();
101         Optional<EventListener> existingListener = Iterators.tryFind(iterator, new Predicate<EventListener>() {
102             @Override
103             public boolean apply(final EventListener input) {
104                 return input.equals(listener);
105             }
106         });
107         return existingListener.isPresent();
108     }
109 
110     /**
111      * Handle used for unregistering {@link EventListener}s.
112      */
113     public interface Handle {
114 
115         /**
116          * Removes related {@link EventListener} from the {@link ObservationManager}.
117          *
118          * @throws RepositoryException if the unregistration in {@link ObservationManager} fails or if {@link ObservationManager} cannot be obtained
119          */
120         void unregister() throws RepositoryException;
121     }
122 
123     /**
124      * <p>Registrar for easing setup and de/registration of {@link EventListener}.</p>
125      */
126     public static class Registrar {
127 
128         // required
129         private final String workspace;
130         private final String observationPath;
131         private EventListener eventListener;
132 
133         // optional
134         private boolean includeSubNodes = true;
135         private String[] nodeTypes = null;
136         private int eventMasksType = ALL_EVENT_TYPES_MASK;
137         private Long delay = null;
138         private Long maxDelay = null;
139 
140         private Registrar(final String workspace, final String observationPath, final EventListener eventListener) {
141             this.workspace = workspace;
142             this.observationPath = observationPath;
143             this.eventListener = eventListener;
144         }
145 
146         public Registrar withSubNodes(final boolean includeSubNodes) {
147             this.includeSubNodes = includeSubNodes;
148             return this;
149         }
150 
151         public Registrar withNodeTypes(final String... nodeTypes) {
152             this.nodeTypes = nodeTypes;
153             return this;
154         }
155 
156         public Registrar withEventTypesMask(final int eventMasksType) {
157             this.eventMasksType = eventMasksType;
158             return this;
159         }
160 
161         public Registrar withDelay(final Long delay, final Long maxDelay) {
162             this.delay = delay;
163             this.maxDelay = maxDelay;
164             return this;
165         }
166 
167         /**
168          * <p>Registers the {@link EventListener} in the {@link ObservationManager}.</p>
169          *
170          * @return {@link Handle} used for unregistering the {@link EventListener}
171          * @throws RepositoryException if an error occurs during the registration in {@link ObservationManager} or if {@link ObservationManager} cannot be obtained
172          * @throws WorkspaceEventListenerRegistrationException if the related {@link EventListener} is already registered in the same workspace
173          */
174         public Handle register() throws RepositoryException {
175             if (!(eventListener instanceof DeferringEventListener) && delay != null && maxDelay != null) {
176                 eventListener = new DeferringEventListener(eventListener, delay, maxDelay);
177             }
178             if (isAlreadyRegistered(workspace, eventListener)) {
179                 throw new WorkspaceEventListenerRegistrationException("Listener has been already registered. If you want to register same listener multiple times, please create new instance of it.");
180             }
181 
182             log.debug("Registering event listener for path [{}]", observationPath);
183 
184             final ObservationManager observationManager = getObservationManager(workspace);
185             observationManager.addEventListener(eventListener, eventMasksType, observationPath, includeSubNodes, null, nodeTypes, false);
186 
187             return new Handle() {
188                 @Override
189                 public void unregister() throws RepositoryException {
190                     final ObservationManager om = getObservationManager(workspace);
191                     om.removeEventListener(eventListener);
192                 }
193             };
194         }
195 
196     }
197 
198     /**
199      * Exception thrown when same instance of a {@link EventListener} is already registered.
200      */
201     public static class WorkspaceEventListenerRegistrationException extends RuntimeException {
202 
203         public WorkspaceEventListenerRegistrationException(String message) {
204             super(message);
205         }
206 
207     }
208 }