View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.common.events;
20  
21  import java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.Hashtable;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.stream.Collectors;
27  
28  import org.apache.logging.log4j.LogManager;
29  import org.apache.logging.log4j.Logger;
30  import org.mycore.common.MCRException;
31  import org.mycore.common.config.MCRConfiguration2;
32  import org.mycore.common.config.MCRConfigurationException;
33  
34  /**
35   * Acts as a multiplexer to forward events that are created to all registered
36   * event handlers, in the order that is configured in mycore properties. For
37   * information how to configure, see MCREventHandler javadocs.
38   * 
39   * @see MCREventHandler
40   * @see MCREventHandlerBase
41   * 
42   * @author Frank Lützenkirchen
43   */
44  public class MCREventManager {
45  
46      public static final String CONFIG_PREFIX = "MCR.EventHandler.";
47  
48      /** Call event handlers in forward direction (create, update) */
49      public static final boolean FORWARD = true;
50  
51      /** Call event handlers in backward direction (delete) */
52      public static final boolean BACKWARD = false;
53  
54      private static Logger logger = LogManager.getLogger(MCREventManager.class);
55  
56      private static MCREventManager instance;
57  
58      /** Table of all configured event handlers * */
59      private Hashtable<MCREvent.ObjectType, List<MCREventHandler>> handlers;
60  
61      private MCREventManager() {
62          handlers = new Hashtable<>();
63  
64          Map<String, String> props = MCRConfiguration2.getPropertiesMap()
65              .entrySet()
66              .stream()
67              .filter(p -> p.getKey().startsWith(CONFIG_PREFIX))
68              .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
69  
70          List<String> propertyKeyList = new ArrayList<>(props.size());
71          for (Object name : props.keySet()) {
72              String key = name.toString();
73              if (!key.startsWith(CONFIG_PREFIX + "Mode.")) {
74                  propertyKeyList.add(key);
75              }
76          }
77          Collections.sort(propertyKeyList);
78  
79          for (String propertyKey : propertyKeyList) {
80              EventHandlerProperty eventHandlerProperty = new EventHandlerProperty(propertyKey);
81  
82              MCREvent.ObjectType type = eventHandlerProperty.getType();
83              String mode = eventHandlerProperty.getMode();
84  
85              logger.debug("EventManager instantiating handler {} for type {}", props.get(propertyKey), type);
86  
87              if (propKeyIsSet(propertyKey)) {
88                  addEventHandler(type, getEventHandler(mode, propertyKey));
89              }
90          }
91      }
92  
93      /**
94       * The singleton manager instance
95       * 
96       * @return the single event manager
97       */
98      public static synchronized MCREventManager instance() {
99          if (instance == null) {
100             instance = new MCREventManager();
101         }
102 
103         return instance;
104     }
105 
106     private boolean propKeyIsSet(String propertyKey) {
107         return MCRConfiguration2.getString(propertyKey).isPresent();
108     }
109 
110     private List<MCREventHandler> getOrCreateEventHandlerListOfType(MCREvent.ObjectType type) {
111         return handlers.computeIfAbsent(type, k -> new ArrayList<>());
112     }
113 
114     /**
115      * This method is called by the component that created the event and acts as
116      * a multiplexer that invokes all registered event handlers doHandleEvent
117      * methods. If something goes wrong and an exception is caught, the
118      * undoHandleEvent methods of all event handlers that are at a position
119      * BEFORE the failed one, will be called in reversed order. The parameter
120      * direction controls the order in which the event handlers are called.
121      *
122      * @see MCREventHandler#doHandleEvent
123      * @see MCREventHandlerBase
124      *
125      * @param evt
126      *            the event that happened
127      * @param direction
128      *            the order in which the event handlers are called
129      */
130     public void handleEvent(MCREvent evt, boolean direction) {
131         List<MCREventHandler> list = handlers.get(evt.getObjectType());
132         if (list == null) {
133             return;
134         }
135 
136         int first = direction ? 0 : list.size() - 1;
137         int last = direction ? list.size() - 1 : 0;
138         int step = direction ? 1 : -1;
139         int undoPos = first;
140 
141         Exception handleEventExceptionCaught = null;
142         for (int i = first; i != last + step; i += step) {
143             MCREventHandler eh = list.get(i);
144             logger.debug("EventManager {} {} calling handler {}", evt.getObjectType(), evt.getEventType(),
145                 eh.getClass().getName());
146 
147             try {
148                 eh.doHandleEvent(evt);
149             } catch (Exception ex) {
150                 handleEventExceptionCaught = ex;
151                 logger.error("Exception caught while calling event handler", ex);
152                 logger.error("Trying rollback by calling undo method of event handlers");
153 
154                 undoPos = i;
155 
156                 break;
157             }
158         }
159 
160         // Rollback by calling undo of successfull handlers
161         for (int i = undoPos - step; i != first - step; i -= step) {
162             MCREventHandler eh = list.get(i);
163             logger.debug("EventManager {} {} calling undo of handler {}", evt.getObjectType(), evt.getEventType(),
164                 eh.getClass().getName());
165 
166             try {
167                 eh.undoHandleEvent(evt);
168             } catch (Exception ex) {
169                 logger.error("Exception caught while calling undo of event handler", ex);
170             }
171         }
172 
173         if (handleEventExceptionCaught != null) {
174             String msg = "Exception caught in EventHandler, rollback by calling undo of successfull handlers done.";
175             throw new MCRException(msg, handleEventExceptionCaught);
176         }
177     }
178 
179     /** Same as handleEvent( evt, MCREventManager.FORWARD ) */
180     public void handleEvent(MCREvent evt) throws MCRException {
181         handleEvent(evt, MCREventManager.FORWARD);
182     }
183 
184     /**
185      * Appends the event handler to the end of the list.
186      *
187      * @param type type of event e.g. MCRObject
188      */
189     public MCREventManager addEventHandler(MCREvent.ObjectType type, MCREventHandler handler) {
190         getOrCreateEventHandlerListOfType(type).add(handler);
191         return this;
192     }
193 
194     /**
195      * Inserts the event handler at the specified position.
196      *
197      * @param type type of event e.g. MCRObject
198      * @param index index at which the specified element is to be inserted
199      */
200     public MCREventManager addEventHandler(MCREvent.ObjectType type, MCREventHandler handler, int index) {
201         getOrCreateEventHandlerListOfType(type).add(index, handler);
202         return this;
203     }
204 
205     /**
206      * Removes the specified event handler.
207      *
208      * @param type type of event handler
209      * @param handler the event handler to remove
210      */
211     public MCREventManager removeEventHandler(MCREvent.ObjectType type, MCREventHandler handler) {
212         List<MCREventHandler> handlerList = this.handlers.get(type);
213         handlerList.remove(handler);
214         if (handlerList.isEmpty()) {
215             this.handlers.remove(type);
216         }
217         return this;
218     }
219 
220     /**
221      * Removes all event handler of the specified type.
222      *
223      * @param type type to removed
224      */
225     public MCREventManager removeEventHandler(MCREvent.ObjectType type) {
226         this.handlers.remove(type);
227         return this;
228     }
229 
230     /**
231      * Clears the <code>MCREventManager</code> so that it contains no <code>MCREventHandler</code>.
232      */
233     public MCREventManager clear() {
234         this.handlers.clear();
235         return this;
236     }
237 
238     public MCREventHandler getEventHandler(String mode, String propertyValue) {
239         if ("Class".equals(mode)) {
240             return MCRConfiguration2.<MCREventHandler>getSingleInstanceOf(propertyValue)
241                 .orElseThrow(() -> MCRConfiguration2.createConfigurationException(propertyValue));
242         }
243         String className = CONFIG_PREFIX + "Mode." + mode;
244         MCREventHandlerInitializer configuredInitializer = MCRConfiguration2
245             .<MCREventHandlerInitializer>getSingleInstanceOf(className)
246             .orElseThrow(() -> MCRConfiguration2.createConfigurationException(className));
247         return configuredInitializer.getInstance(propertyValue);
248     }
249 
250     public interface MCREventHandlerInitializer {
251         MCREventHandler getInstance(String propertyValue);
252     }
253 
254     /**
255      * Parse the property key of event handlers, extract type and mode.
256      *
257      * @see MCREventHandler
258      *
259      * @author Huu Chi Vu
260      *
261      */
262     private class EventHandlerProperty {
263 
264         private MCREvent.ObjectType type;
265 
266         private String mode;
267 
268         EventHandlerProperty(String propertyKey) {
269             String[] splitedKey = propertyKey.split("\\.");
270             if (splitedKey.length != 5) {
271                 throw new MCRConfigurationException("Property key " + propertyKey + " for event handler not valid.");
272             }
273 
274             this.setType(MCREvent.ObjectType.fromClassName(splitedKey[2]));
275             this.setMode(splitedKey[4]);
276         }
277 
278         public MCREvent.ObjectType getType() {
279             return type;
280         }
281 
282         private void setType(MCREvent.ObjectType type) {
283             this.type = type;
284         }
285 
286         public String getMode() {
287             return mode;
288         }
289 
290         private void setMode(String mode) {
291             this.mode = mode;
292         }
293 
294     }
295 }