/**
* Copyright 2010-2011 Voxeo Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License.
*
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package com.voxeo.moho.common.event;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.log4j.Logger;
import com.voxeo.moho.common.util.InheritLogContextFutureTask;
import com.voxeo.moho.common.util.InheritLogContextRunnable;
import com.voxeo.moho.common.util.Utils;
import com.voxeo.moho.event.Event;
import com.voxeo.moho.event.EventSource;
import com.voxeo.moho.utils.EnumEvent;
import com.voxeo.moho.utils.EventListener;
public class EventDispatcher {
private static final Logger LOG = Logger.getLogger(EventDispatcher.class);
private ConcurrentHashMap<Class<?>, List<Object>> clazzListeners = new ConcurrentHashMap<Class<?>, List<Object>>();
private ConcurrentHashMap<Object, List<Object>> enumListeners = new ConcurrentHashMap<Object, List<Object>>();
private ConcurrentHashMap<Object, List<Object>> lifecycleObjectMap = new ConcurrentHashMap<Object, List<Object>>();
private Executor executor = null;
private boolean needOrder = true;
private Lock lifecycleLock = new ReentrantLock();
private Queue<FutureTask<?>> _queue = new LinkedList<FutureTask<?>>();
private boolean processorRunning = false;
public EventDispatcher() {
}
public EventDispatcher(final Executor executor) {
this.executor = executor;
}
public void addListener(final Class<?> eventClazz, final EventListener<?> listener) {
List<Object> list = clazzListeners.get(eventClazz);
if (list == null) {
list = new CopyOnWriteArrayList<Object>();
final List<Object> existing = clazzListeners.putIfAbsent(eventClazz, list);
if (existing != null) {
existing.add(listener);
}
else {
list.add(listener);
}
}
else {
list.add(listener);
}
}
public <E extends Enum<E>> void addListener(final E type, final EventListener<? extends EnumEvent<?, E>> listener) {
List<Object> list = enumListeners.get(type);
if (list == null) {
list = new CopyOnWriteArrayList<Object>();
final List<Object> existing = enumListeners.putIfAbsent(type, list);
if (existing != null) {
existing.add(listener);
}
else {
list.add(listener);
}
}
else {
list.add(listener);
}
}
/**
* @param lifecycleObject
* - This is a way of grouping listeners together, for convenient
* removal later. Listeners added with this method can be removed
* later in one shot by calling removeListenersForLifecycleObject()
*/
public void addListener(final Object lifecycleObject, final Class<?> eventClazz, final EventListener<?> listener) {
lifecycleLock.lock();
try {
addToLifecycleMap(lifecycleObject, listener);
addListener(eventClazz, listener);
}
finally {
lifecycleLock.unlock();
}
}
/**
* @param lifecycleObject
* - This is a way of grouping listeners together, for convenient
* removal later. Listeners added with this method can be removed
* later in one shot by calling removeListenersForLifecycleObject()
*/
public <E extends Enum<E>> void addListener(final Object lifecycleObject, final E type,
final EventListener<? extends EnumEvent<?, E>> listener) {
lifecycleLock.lock();
try {
addToLifecycleMap(lifecycleObject, listener);
addListener(type, listener);
}
finally {
lifecycleLock.unlock();
}
}
private void addToLifecycleMap(final Object lifecycleObject, final EventListener<?> listener) {
List<Object> list = lifecycleObjectMap.get(lifecycleObject);
if (list == null) {
list = new CopyOnWriteArrayList<Object>();
final List<Object> existing = lifecycleObjectMap.putIfAbsent(lifecycleObject, list);
if (existing != null) {
existing.add(listener);
}
else {
list.add(listener);
}
}
else {
list.add(listener);
}
}
/**
* @param lifecycleObject
* - Removes all listeners that were added with
* {@link #addListener(Object, Enum, EventListener)} or
* {@link #addListener(Object, Class, EventListener)}
*/
public void removeListenersForLifecycleObject(final Object lifecycleObject) {
lifecycleLock.lock();
try {
if (lifecycleObjectMap != null) {
final List<Object> list = lifecycleObjectMap.remove(lifecycleObject);
if (list != null) {
for (final Object o : list) {
removeListener((EventListener<?>) o);
}
}
}
}
finally {
lifecycleLock.unlock();
}
}
public void removeListener(final EventListener<?> listener) {
for (final List<Object> list : clazzListeners.values()) {
list.remove(listener);
}
for (final List<Object> list : enumListeners.values()) {
list.remove(listener);
}
}
public <S extends EventSource, T extends Event<S>> Future<T> fire(final T event) {
return fire(event, false);
}
public <S extends EventSource, T extends Event<S>> Future<T> fire(final T event, final boolean narrowType) {
return fire(event, narrowType, null);
}
public <S extends EventSource, T extends Event<S>> Future<T> fire(final T event, final boolean narrowType,
final Runnable afterExec) {
final FutureTask<T> task = new InheritLogContextFutureTask<T>(new InheritLogContextRunnable() {
@SuppressWarnings({"unchecked"})
public void run() {
if (LOG.isTraceEnabled()) {
LOG.trace("Firing event :" + event);
}
// clazz is a subtype of the Event interface or Event itself.
Class<?> clazz = Utils.getEventType(event.getClass());
if (clazz != null) {
out: do {
final List<Object> list = clazzListeners.get(clazz);
if (list != null) {
LOG.debug("Dispatching Event to listener:" + event);
for (final Object listener : list) {
try {
((EventListener<T>) listener).onEvent(event);
}
catch (Exception ex) {
LOG.warn(ex + " is uncaught when handling " + event);
LOG.debug(ex + " is uncaught when handling " + event, ex);
HandleUncaughtException(ex, event);
break out;
}
}
}
clazz = Utils.getEventType(clazz);
}
while (narrowType && clazz != null);
}
out: if (event instanceof EnumEvent) {
final EnumEvent<S, ? extends Enum<?>> enumEvent = (EnumEvent<S, ? extends Enum<?>>) event;
final List<Object> list = enumListeners.get(enumEvent.getType());
if (list != null) {
LOG.debug("Dispatching Event to listener:" + event);
for (final Object listener : list) {
try {
((EventListener<T>) listener).onEvent(event);
}
catch (Exception ex) {
LOG.warn(ex + " is uncaught when handling " + event);
LOG.debug(ex + " is uncaught when handling " + event, ex);
HandleUncaughtException(ex, event);
break out;
}
}
}
}
if (afterExec != null) {
afterExec.run();
}
}
}, event);
if (needOrder) {
synchronized (_queue) {
boolean excuteProcessor = false;
_queue.offer(task);
if (!processorRunning) {
processorRunning = true;
excuteProcessor = true;
}
if (excuteProcessor) {
executor.execute(new TaskProcessor());
}
}
}
else {
executor.execute(task);
}
return task;
}
private class TaskProcessor extends InheritLogContextRunnable {
public void run() {
while (true) {
FutureTask<?> task = null;
synchronized (_queue) {
task = _queue.poll();
if (task == null) {
processorRunning = false;
break;
}
}
try {
task.run();
task.get();
}
catch (Throwable t) {
LOG.info("Throwable when processing task.", t);
}
}
}
}
public void setExecutor(final Executor executor, boolean order) {
this.executor = executor;
this.needOrder = order;
}
protected <S extends EventSource> Future<Event<S>> HandleUncaughtException(Exception ex, Event<S> evt) {
Event<S> newEvt = new UncaughtExceptionEventImpl<S>(evt.getSource(), ex, evt);
return fire(newEvt, true);
}
}