001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: https://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: https://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.main;
029
030import java.lang.reflect.Field;
031import java.lang.reflect.Modifier;
032import java.util.ArrayList;
033import java.util.HashMap;
034import java.util.Iterator;
035import java.util.List;
036import java.util.Map;
037import java.util.concurrent.ConcurrentHashMap;
038
039import org.apache.commons.logging.Log;
040
041/**
042 * Manager that controls the OpenCms event system.
043 *
044 * There is only one instance of this event manager class used by the OpenCms runtime.
045 * This instance can be obtained by calling {@link OpenCms#getEventManager()}.<p>
046 *
047 * Events can be used in OpenCms to notify custom event listeners that certain system events have happened.
048 * Event listeners have to implement the interface {@link org.opencms.main.I_CmsEventListener}.<p>
049 *
050 * @since 7.0.0
051 *
052 * @see org.opencms.main.CmsEvent
053 * @see org.opencms.main.I_CmsEventListener
054 */
055public class CmsEventManager {
056
057    /** Required as template for event list generation. */
058    protected static final I_CmsEventListener[] EVENT_LIST = new I_CmsEventListener[0];
059
060    /** The static log object for this class. */
061    private static final Log LOG = CmsLog.getLog(CmsEventManager.class);
062
063    /** Maps event type ids to the corresponding field names - for debug purposes. */
064    private static ConcurrentHashMap<Integer, String> m_eventNames = new ConcurrentHashMap<>();
065
066    /** Stores the active event listeners. */
067    private Map<Integer, List<I_CmsEventListener>> m_eventListeners;
068
069    /**
070     * Create a new instance of an OpenCms event manager.<p>
071     */
072    public CmsEventManager() {
073
074        m_eventListeners = new HashMap<Integer, List<I_CmsEventListener>>();
075    }
076
077    /**
078     * Finds the field name in I_CmsEventListener for a specific event type.
079     *
080     * <p>For debugging/logging.
081     *
082     * @param eventType the event type id
083     * @return the field name
084     */
085    public static String getEventName(int eventType) {
086
087        return m_eventNames.computeIfAbsent(Integer.valueOf(eventType), k -> "" + k);
088    }
089
090    /**
091     * Add an OpenCms event listener that listens to all events.<p>
092     *
093     * @param listener the listener to add
094     */
095    public void addCmsEventListener(I_CmsEventListener listener) {
096
097        addCmsEventListener(listener, null);
098    }
099
100    /**
101     * Add an OpenCms event listener.<p>
102     *
103     * @param listener the listener to add
104     * @param eventTypes the events to listen for
105     */
106    public void addCmsEventListener(I_CmsEventListener listener, int[] eventTypes) {
107
108        synchronized (m_eventListeners) {
109            if (eventTypes == null) {
110                // no event types given - register the listener for all event types
111                eventTypes = new int[] {I_CmsEventListener.LISTENERS_FOR_ALL_EVENTS.intValue()};
112            }
113            for (int i = 0; i < eventTypes.length; i++) {
114                // register the listener for all configured event types
115                Integer eventType = Integer.valueOf(eventTypes[i]);
116                List<I_CmsEventListener> listeners = m_eventListeners.get(eventType);
117                if (listeners == null) {
118                    listeners = new ArrayList<I_CmsEventListener>();
119                    m_eventListeners.put(eventType, listeners);
120                }
121                if (!listeners.contains(listener)) {
122                    // add listerner only if it is not already registered
123                    listeners.add(listener);
124                }
125            }
126        }
127    }
128
129    /**
130     * Notify all event listeners that a particular event has occurred.<p>
131     *
132     * @param event the event that is forwarded to all listeners
133     */
134    public void fireEvent(CmsEvent event) {
135
136        fireEventHandler(m_eventListeners.get(event.getTypeInteger()), event);
137        fireEventHandler(m_eventListeners.get(I_CmsEventListener.LISTENERS_FOR_ALL_EVENTS), event);
138    }
139
140    /**
141     * Notify all event listeners that a particular event has occurred without any additional event data.<p>
142     *
143     * @param type event type
144     */
145    public void fireEvent(int type) {
146
147        fireEvent(type, new HashMap<String, Object>());
148    }
149
150    /**
151     * Notify all event listeners that a particular event has occurred.<p>
152     *
153     * @param type event type
154     * @param data event data
155     */
156    public void fireEvent(int type, Map<String, Object> data) {
157
158        fireEvent(new CmsEvent(type, data));
159    }
160
161    /**
162     * Removes a cms event listener.<p>
163     *
164     * @param listener the listener to remove
165     */
166    public void removeCmsEventListener(I_CmsEventListener listener) {
167
168        synchronized (m_eventListeners) {
169            Iterator<Integer> it = m_eventListeners.keySet().iterator();
170            while (it.hasNext()) {
171                List<I_CmsEventListener> listeners = m_eventListeners.get(it.next());
172                listeners.remove(listener);
173            }
174        }
175    }
176
177    /**
178     * Fires the specified event to a list of event listeners.<p>
179     *
180     * @param listeners the listeners to fire
181     * @param event the event to fire
182     */
183    protected void fireEventHandler(List<I_CmsEventListener> listeners, CmsEvent event) {
184
185        if (!LOG.isDebugEnabled()) {
186            // no logging required
187            if ((listeners != null) && (listeners.size() > 0)) {
188                // handle all event listeners that listen to this event type
189                I_CmsEventListener[] list = listeners.toArray(EVENT_LIST);
190                // loop through all registered event listeners
191                for (int i = 0; i < list.length; i++) {
192                    try {
193                        // fire the event
194                        list[i].cmsEvent(event);
195                    } catch (Throwable t) {
196                        LOG.error(
197                            Messages.get().getBundle().key(
198                                Messages.ERR_CALLING_EVENT_LISTENER_FAILED_2,
199                                list[i].getClass().getName(),
200                                event.toString()),
201                            t);
202                    }
203                }
204            }
205        } else {
206            // add lots of event debug output (this should usually be disabled)
207            // repeat event handling code to avoid multiple "is log enabled" checks in normal operation
208            LOG.debug(Messages.get().getBundle().key(Messages.LOG_DEBUG_EVENT_1, event.toString()));
209            if ((listeners != null) && (listeners.size() > 0)) {
210                // handle all event listeners that listen to this event type
211                I_CmsEventListener[] list = listeners.toArray(EVENT_LIST);
212                // log the event data
213                if (event.getData() != null) {
214                    Iterator<String> i = event.getData().keySet().iterator();
215                    while (i.hasNext()) {
216                        String key = i.next();
217                        Object value = event.getData().get(key);
218                        LOG.debug(
219                            Messages.get().getBundle().key(
220                                Messages.LOG_DEBUG_EVENT_VALUE_3,
221                                key,
222                                value,
223                                event.toString()));
224                    }
225                } else {
226                    LOG.debug(Messages.get().getBundle().key(Messages.LOG_DEBUG_NO_EVENT_VALUE_1, event.toString()));
227                }
228                // log all the registered event listeners
229                for (int j = 0; j < list.length; j++) {
230                    LOG.debug(
231                        Messages.get().getBundle().key(
232                            Messages.LOG_DEBUG_EVENT_LISTENERS_3,
233                            list[j],
234                            Integer.valueOf(j),
235                            event.toString()));
236                }
237                // loop through all registered event listeners
238                for (int i = 0; i < list.length; i++) {
239                    LOG.debug(
240                        Messages.get().getBundle().key(
241                            Messages.LOG_DEBUG_EVENT_START_LISTENER_3,
242                            list[i],
243                            Integer.valueOf(i),
244                            event.toString()));
245                    try {
246                        // fire the event
247                        list[i].cmsEvent(event);
248                    } catch (Throwable t) {
249                        LOG.error(
250                            Messages.get().getBundle().key(
251                                Messages.ERR_CALLING_EVENT_LISTENER_FAILED_2,
252                                list[i].getClass().getName(),
253                                event.toString()),
254                            t);
255                    }
256                    LOG.debug(
257                        Messages.get().getBundle().key(
258                            Messages.LOG_DEBUG_EVENT_END_LISTENER_3,
259                            list[i],
260                            Integer.valueOf(i),
261                            event.toString()));
262                }
263            } else {
264                LOG.debug(Messages.get().getBundle().key(Messages.LOG_DEBUG_EVENT_NO_LISTENER_1, event.toString()));
265            }
266            LOG.debug(Messages.get().getBundle().key(Messages.LOG_DEBUG_EVENT_COMPLETE_1, event.toString()));
267        }
268    }
269
270    /**
271     * Returns the map of all configured event listeners.<p>
272     *
273     * @return the map of all configured event listeners
274     */
275    protected Map<Integer, List<I_CmsEventListener>> getEventListeners() {
276
277        return m_eventListeners;
278    }
279
280    /**
281     * Initialize this event manager with all events from the given base event manager.<p>
282     *
283     * @param base the base event manager to initialize this event manager with
284     */
285    protected void initialize(CmsEventManager base) {
286
287        m_eventListeners = new HashMap<Integer, List<I_CmsEventListener>>(base.getEventListeners());
288        try {
289            for (Field field : I_CmsEventListener.class.getDeclaredFields()) {
290                if (Modifier.isStatic(field.getModifiers())) {
291                    if ((field.getType() == int.class) && field.getName().startsWith("EVENT_")) {
292                        m_eventNames.put(Integer.valueOf(field.getInt(null)), field.getName());
293                    }
294                }
295            }
296        } catch (Exception e) {
297            LOG.error(e.getLocalizedMessage(), e);
298        }
299    }
300}