/* * Copyright (C) 2003-2011 eXo Platform SAS. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.etk.component.base.event; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import org.etk.common.logging.Logger; import org.etk.kernel.container.xml.InitParams; /** * Created by The eXo Platform SAS * Author : eXoPlatform * thanhvucong.78@google.com * Aug 6, 2011 */ public class ListenerService { /** * This executor used for asynchronously event broadcast. */ private final Executor executor; /** * Listeners by name map. */ private final Map<String, List<Listener>> listeners_; private static final Logger log = Logger.getLogger(ListenerService.class); /** * Construct a listener service. */ public ListenerService() { listeners_ = new HashMap<String, List<Listener>>(); executor = Executors.newFixedThreadPool(1, new ListenerThreadFactory()); } /** * Construct a listener service. */ public ListenerService(InitParams params) { listeners_ = new HashMap<String, List<Listener>>(); int poolSize = 1; if (params != null) { if (params.getValueParam("asynchPoolSize") != null) { poolSize = Integer.parseInt(params.getValueParam("asynchPoolSize").getValue()); } } executor = Executors.newFixedThreadPool(poolSize, new ListenerThreadFactory()); } /** * This method is used to register a {@link Listener} to the events of the * same name. It is similar to addListener(listener.getName(), listener) * * @param listener the listener to notify any time an even of the same name is * triggered */ public void addListener(Listener listener) { addListener(listener.getName(), listener); } /** * This method is used to register a new {@link Listener}. Any time an event * of the given event name has been triggered, the {@link Listener} will be * notified. This method will: * <ol> * <li>Check if it exists a list of listeners that have been registered for * the given event name, create a new list if no list exists</li> * <li>Add the listener to the list</li> * </ol> * * @param eventName The name of the event to listen to * @param listener The Listener to notify any time the event with the given * name is triggered */ public void addListener(String eventName, Listener listener) { // Check is Listener or its superclass asynchronous, if so - wrap it in // AsynchronousListener. Class listenerClass = listener.getClass(); do { if (listenerClass.isAnnotationPresent(Asynchronous.class)) { listener = new AsynchronousListener(listener); break; } else { listenerClass = listenerClass.getSuperclass(); } } while (listenerClass != null); List<Listener> list = listeners_.get(eventName); if (list == null) { list = new ArrayList<Listener>(); listeners_.put(eventName, list); } list.add(listener); } /** * This method is used to broadcast an event. This method should: 1. Check if * there is a list of listener that listen to the event name. 2. If there is a * list of listener, create the event object with the given name , source and * data 3. For each listener in the listener list, invoke the method * onEvent(Event) * * @param <S> The type of the source that broacast the event * @param <D> The type of the data that the source object is working on * @param name The name of the event * @param source The source object instance * @param data The data object instance * @throws Exception TODO: Should not delegate to the method broadcast(Event) */ final public <S, D> void broadcast(String name, S source, D data) throws Exception { List<Listener> list = listeners_.get(name); if (list == null) return; for (Listener<S, D> listener : list) { if (log.isDebugEnabled()) { log.debug("broadcasting event " + name + " on " + listener.getName()); } try { listener.onEvent(new Event<S, D>(name, source, data)); } catch (Exception e) { // log exception and keep broadcast events log.error("Exception on broadcasting events occures: " + e.getMessage(), e.getCause()); log.info("Exception occures but keep broadcast events."); } } } /** * This method is used when a developer want to implement his own event object * and broadcast the event. The method should: 1. Check if there is a list of * listener that listen to the event name. 2. If there is a list of the * listener, ror each listener in the listener list, invoke the method * onEvent(Event) * * @param <T> The type of the event object, the type of the event object has * to be extended from the Event type * @param event The event instance * @throws Exception */ final public <T extends Event> void broadcast(T event) throws Exception { List<Listener> list = listeners_.get(event.getEventName()); if (list == null) { return; } for (Listener listener : list) { try { listener.onEvent(event); } catch (Exception e) { // log exception and keep broadcast events log.error("Exception on broadcasting events occures: " + e.getMessage(), e.getCause()); log.info("Exception occures but keep broadcast events."); } } } /** * This AsynchronousListener is a wrapper for original listener, that executes * wrapped listeners onEvent() in separate thread. */ protected class AsynchronousListener<S, D> extends Listener<S, D> { private Listener<S, D> listener; public AsynchronousListener(Listener<S, D> listener) { this.listener = listener; } @Override public String getName() { return listener.getName(); } @Override public void setName(String s) { listener.setName(s); } @Override public String getDescription() { return listener.getDescription(); } @Override public void setDescription(String s) { listener.setDescription(s); } @Override public void onEvent(Event<S, D> event) throws Exception { executor.execute(new RunListener<S, D>(listener, event)); } } /** * This thread executes listener.onEvent(event) method. */ protected class RunListener<S, D> implements Runnable { private Listener<S, D> listener; private Event<S, D> event; public RunListener(Listener<S, D> listener, Event<S, D> event) { this.listener = listener; this.event = event; } /** * {@inheritDoc} */ public void run() { try { listener.onEvent(event); } catch (Exception e) { // Do not throw exception. Event is asynchronous so just report error. // Must say that exception will be ignored even in synchronous events. log.error("Exception on broadcasting events occures: " + e.getMessage(), e.getCause()); } } } }