package act.event;
/*-
* #%L
* ACT Framework
* %%
* Copyright (C) 2014 - 2017 ActFramework
* %%
* 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.
* #L%
*/
import act.Destroyable;
import act.app.App;
import act.app.AppServiceBase;
import act.app.event.AppEvent;
import act.app.event.AppEventId;
import act.app.event.AppEventListener;
import act.event.bytecode.ReflectedSimpleEventListener;
import act.inject.DependencyInjectionBinder;
import act.inject.DependencyInjector;
import act.job.AppJobManager;
import org.osgl.mvc.result.Result;
import org.osgl.util.C;
import org.osgl.util.E;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import static act.app.App.LOGGER;
@ApplicationScoped
public class EventBus extends AppServiceBase<EventBus> {
private boolean once;
private final List[] appEventListeners;
private final List[] asyncAppEventListeners;
private final ConcurrentMap<Class<? extends EventObject>, List<ActEventListener>> actEventListeners;
private final ConcurrentMap<Class<? extends EventObject>, List<ActEventListener>> asyncActEventListeners;
private final ConcurrentMap<AppEventId, AppEvent> appEventLookup;
private final ConcurrentMap<Object, List<SimpleEventListener>> adhocEventListeners;
private final ConcurrentMap<Object, List<SimpleEventListener>> asyncAdhocEventListeners;
private EventBus onceBus;
private EventBus(App app, boolean once) {
super(app, true);
appEventListeners = initAppListenerArray();
asyncAppEventListeners = initAppListenerArray();
actEventListeners = new ConcurrentHashMap<>();
asyncActEventListeners = new ConcurrentHashMap<>();
appEventLookup = initAppEventLookup(app);
adhocEventListeners = new ConcurrentHashMap<>();
asyncAdhocEventListeners = new ConcurrentHashMap<>();
loadDefaultEventListeners();
if (!once) {
onceBus = new EventBus(app, true);
onceBus.once = true;
}
}
@Inject
public EventBus(App app) {
this(app, false);
}
@Override
protected void releaseResources() {
if (null != onceBus) {
onceBus.releaseResources();
}
releaseAppEventListeners(appEventListeners);
releaseAppEventListeners(asyncAppEventListeners);
releaseActEventListeners(actEventListeners);
releaseActEventListeners(asyncActEventListeners);
releaseAdhocEventListeners(adhocEventListeners);
releaseAdhocEventListeners(asyncAdhocEventListeners);
appEventLookup.clear();
}
@SuppressWarnings("unchecked")
private boolean callNowIfEmitted(AppEventId appEventId, AppEventListener l) {
if (app().eventEmitted(appEventId)) {
try {
l.on(appEventLookup.get(appEventId));
} catch (Exception e) {
LOGGER.warn(e, "error calling event handler");
}
return true;
}
return false;
}
@SuppressWarnings("unchecked")
private EventBus _bind(List[] listeners, AppEventId appEventId, AppEventListener l) {
if (callNowIfEmitted(appEventId, l)) {
return this;
}
List<AppEventListener> list = listeners[appEventId.ordinal()];
if (!list.contains(l)) list.add(l);
return this;
}
@SuppressWarnings("unchecked")
public synchronized EventBus bind(final AppEventId appEventId, final AppEventListener l) {
return _bind(appEventListeners, appEventId, l);
}
@SuppressWarnings("unused")
public synchronized EventBus bindAsync(AppEventId appEventId, AppEventListener l) {
return _bind(asyncAppEventListeners, appEventId, l);
}
@SuppressWarnings("unused")
/**
* Alias of {@link #bind(AppEventId, AppEventListener)}
*/
public synchronized EventBus bindSync(AppEventId appEventId, AppEventListener l) {
return bind(appEventId, l);
}
public static boolean isAsync(AnnotatedElement c) {
Annotation[] aa = c.getAnnotations();
for (Annotation a : aa) {
if (a.annotationType().getName().contains("Async")) {
return true;
}
}
return false;
}
private synchronized EventBus _bind(final ConcurrentMap<Class<? extends EventObject>, List<ActEventListener>> listeners, final Class<? extends EventObject> c, final ActEventListener l, int ttl) {
List<ActEventListener> list = listeners.get(c);
if (null == list) {
list = C.newList();
listeners.putIfAbsent(c, list);
}
if (!list.contains(l)) {
list.add(l);
E.illegalArgumentIf(ttl < 0);
if (ttl > 0) {
app().jobManager().delay(new Runnable() {
@Override
public void run() {
synchronized (EventBus.this) {
_unbind(listeners, c, l);
}
}
}, ttl, TimeUnit.SECONDS);
}
}
return this;
}
private synchronized EventBus _unbind(Map<Class<? extends EventObject>, List<ActEventListener>> listeners, Class<? extends EventObject> c, ActEventListener l) {
List<ActEventListener> list = listeners.get(c);
if (null != list) {
list.remove(l);
}
return this;
}
public EventBus bind(Class<? extends EventObject> c, ActEventListener l) {
boolean async = isAsync(l.getClass()) || isAsync(c);
ConcurrentMap<Class<? extends EventObject>, List<ActEventListener>> listeners = async ? asyncActEventListeners : actEventListeners;
return _bind(listeners, c, l, 0);
}
public synchronized EventBus once(Class<? extends EventObject> c, OnceEventListenerBase l) {
if (null != onceBus) {
onceBus.bind(c, l);
} else {
bind(c, l);
}
return this;
}
/**
* Bind a transient event list to event with type `c`
* @param c the target event type
* @param l the listener
* @param ttl the number of seconds the listener should live
* @return this event bus instance
*/
public EventBus bind(Class<? extends EventObject> c, ActEventListener l, int ttl) {
boolean async = isAsync(l.getClass()) || isAsync(c);
ConcurrentMap<Class<? extends EventObject>, List<ActEventListener>> listeners = async ? asyncActEventListeners : actEventListeners;
return _bind(listeners, c, l, ttl);
}
public EventBus bindSync(Class<? extends EventObject> c, ActEventListener l) {
return _bind(actEventListeners, c, l, 0);
}
public EventBus bindSync(final Class<? extends EventObject> c, final ActEventListener l, int ttl) {
return _bind(actEventListeners, c, l, ttl);
}
public EventBus bindAsync(Class<? extends EventObject> c, ActEventListener l) {
return _bind(asyncActEventListeners, c, l, 0);
}
public EventBus bindAsync(Class<? extends EventObject> c, ActEventListener l, int ttl) {
return _bind(asyncActEventListeners, c, l, ttl);
}
@SuppressWarnings("unchecked")
private boolean callOn(ActEvent e, ActEventListener l) {
try {
if (l instanceof OnceEventListener) {
return ((OnceEventListener) l).tryHandle(e);
} else {
l.on(e);
return true;
}
} catch (Result r) {
// in case event listener needs to return a result back
throw r;
} catch (RuntimeException x) {
throw x;
} catch (Exception x) {
throw E.unexpected(x, x.getMessage());
}
}
private <T extends ActEvent> void callOn(final T event, List<? extends ActEventListener> listeners, boolean async) {
if (null == listeners) {
return;
}
AppJobManager jobManager = null;
if (async) {
jobManager = app().jobManager();
}
Set<ActEventListener> toBeRemoved = C.newSet();
for (final ActEventListener l : listeners) {
if (!async) {
boolean result = callOn(event, l);
if (result && once) {
toBeRemoved.add(l);
}
} else {
jobManager.now(new Runnable() {
@Override
public void run() {
callOn(event, l);
}
});
}
}
if (once && !toBeRemoved.isEmpty()) {
listeners.removeAll(toBeRemoved);
}
}
@SuppressWarnings("unchecked")
private void callOn(final AppEvent event, List[] appEventListeners, boolean async) {
List<AppEventListener> ll = appEventListeners[event.id()];
callOn(event, ll, async);
}
private void callOn(ActEvent event, Map<Class<? extends EventObject>, List<ActEventListener>> listeners, boolean async) {
List<ActEventListener> list = listeners.get(event.eventType());
callOn(event, list, async);
}
public synchronized EventBus emit(AppEventId eventId) {
return emit(appEventLookup.get(eventId));
}
public synchronized EventBus trigger(AppEventId eventId) {
return emit(eventId);
}
public synchronized EventBus emit(final AppEvent event) {
if (isDestroyed()) {
return this;
}
callOn(event, asyncAppEventListeners, true);
callOn(event, appEventListeners, false);
return this;
}
public synchronized EventBus trigger(final AppEvent event) {
return emit(event);
}
public synchronized EventBus emitAsync(AppEventId eventId) {
return emitAsync(appEventLookup.get(eventId));
}
public synchronized EventBus emitAsync(final AppEvent event) {
if (isDestroyed()) {
return this;
}
callOn(event, asyncAppEventListeners, true);
callOn(event, appEventListeners, true);
return this;
}
public synchronized EventBus triggerAsync(final AppEvent event) {
return emitAsync(event);
}
public synchronized EventBus emitSync(AppEventId eventId) {
return emitSync(appEventLookup.get(eventId));
}
public synchronized EventBus triggerSync(AppEventId eventId) {
return emitSync(eventId);
}
public synchronized EventBus emitSync(AppEvent event) {
if (isDestroyed()) {
return this;
}
callOn(event, asyncAppEventListeners, false);
callOn(event, appEventListeners, false);
return this;
}
public synchronized EventBus triggerSync(AppEvent event) {
return emitSync(event);
}
public synchronized EventBus emitSync(final ActEvent event) {
if (isDestroyed()) {
return this;
}
callOn(event, asyncActEventListeners, false);
callOn(event, actEventListeners, false);
boolean isSystemEvent = event instanceof SystemEvent;
if (!isSystemEvent) {
Object payload = event.source();
if (null != payload) {
emitSync(payload.getClass(), payload);
}
emitSync(event.getClass(), event);
}
if (null != onceBus) {
onceBus.triggerSync(event);
}
return this;
}
public EventBus triggerSync(final ActEvent event) {
return emitSync(event);
}
@SuppressWarnings("unchecked")
public EventBus emit(final ActEvent event) {
if (isDestroyed()) {
return this;
}
callOn(event, asyncActEventListeners, true);
callOn(event, actEventListeners, false);
boolean isSystemEvent = event instanceof SystemEvent;
if (!isSystemEvent) {
Object payload = event.source();
if (null != payload) {
emit(payload.getClass(), payload);
}
emit(event.getClass(), event);
}
if (null != onceBus) {
onceBus.trigger(event);
}
return this;
}
public EventBus trigger(final ActEvent event) {
return emit(event);
}
public EventBus emitAsync(final ActEvent event) {
if (isDestroyed()) {
return this;
}
callOn(event, asyncActEventListeners, true);
callOn(event, actEventListeners, true);
boolean isSystemEvent = event instanceof SystemEvent;
if (!isSystemEvent) {
Object payload = event.source();
if (null != payload) {
emitAsync(payload.getClass(), payload);
}
emitAsync(event.getClass(), event);
}
if (null != onceBus) {
onceBus.triggerAsync(event);
}
return this;
}
public EventBus triggerAsync(final ActEvent event) {
return emitAsync(event);
}
private EventBus _bind(ConcurrentMap<Object, List<SimpleEventListener>> listeners, Object event, SimpleEventListener l) {
List<SimpleEventListener> list = listeners.get(event);
if (null == list) {
list = new ArrayList<>();
listeners.putIfAbsent(event, list);
}
if (!list.contains(l)) {
list.add(l);
}
return this;
}
public EventBus bind(Object event, SimpleEventListener l) {
boolean async = event instanceof Class && (isAsync((Class) event));
async = async || ((l instanceof ReflectedSimpleEventListener) && ((ReflectedSimpleEventListener) l).isAsync());
return _bind(async ? asyncAdhocEventListeners : adhocEventListeners, event, l);
}
public EventBus bindAsync(Object event, SimpleEventListener l) {
return _bind(asyncAdhocEventListeners, event, l);
}
@SuppressWarnings("unchecked")
private void callOn(SimpleEventListener l, Object ... args) {
try {
l.invoke(args);
} catch (Result r) {
// in case event listener needs to return a result back
throw r;
} catch (Exception x) {
LOGGER.error(x, "Error executing event listener");
}
}
private boolean callOn(List<? extends SimpleEventListener> listeners, boolean async, final Object ... args) {
if (null == listeners) {
return false;
}
AppJobManager jobManager = null;
if (async) {
jobManager = app().jobManager();
}
boolean hasListener = !listeners.isEmpty();
if (!hasListener) {
return false;
}
// copy the list to avoid ConcurrentModificationException
listeners = C.list(listeners);
for (final SimpleEventListener l : listeners) {
if (!async) {
callOn(l, args);
} else {
jobManager.now(new Runnable() {
@Override
public void run() {
l.invoke(args);
}
});
}
}
return true;
}
public void emit(Enum<?> event, Object... args) {
emit(event.name(), args);
}
public void emit(Object event, Object ... args) {
_emit(false, true, event, args);
}
public void emitSync(Object event, Object ... args) {
_emit(false, false, event, args);
}
public void emitAsync(Object event, Object ... args) {
_emit(true, true, event, args);
}
private void _emit(boolean async1, boolean async2, Object event, Object ... args) {
boolean hit = callOn(adhocEventListeners.get(event), async1, args);
hit = callOn(asyncAdhocEventListeners.get(event), async2, args) || hit;
if (!hit && 0 == args.length) {
_emit(async1, async2, event.getClass(), event);
}
if (null != onceBus) {
onceBus._emit(async1, async2, event, args);
}
}
public void trigger(Object event, Object ... args) {
emit(event, args);
}
public void triggerAsync(Object event, Object ... args) {
emitAsync(event, args);
}
private ConcurrentMap<AppEventId, AppEvent> initAppEventLookup(App app) {
ConcurrentMap<AppEventId, AppEvent> map = new ConcurrentHashMap<>();
AppEventId[] ids = AppEventId.values();
int len = ids.length;
for (int i = 0; i < len; ++i) {
AppEventId id = ids[i];
map.put(id, id.of(app));
}
return map;
}
private List[] initAppListenerArray() {
AppEventId[] ids = AppEventId.values();
int len = ids.length;
List[] l = new List[len];
for (int i = 0; i < len; ++i) {
l[i] = C.newList();
}
return l;
}
private void releaseAppEventListeners(List[] array) {
int len = array.length;
for (int i = 0; i < len; ++i) {
List<AppEventListener> l = array[i];
Destroyable.Util.destroyAll(l, ApplicationScoped.class);
l.clear();
}
}
private void releaseActEventListeners(Map<?, List<ActEventListener>> listeners) {
for (List<ActEventListener> l : listeners.values()) {
Destroyable.Util.destroyAll(l, ApplicationScoped.class);
l.clear();
}
listeners.clear();
}
private void releaseAdhocEventListeners(Map<Object, List<SimpleEventListener>> listeners) {
for (List<SimpleEventListener> l : listeners.values()) {
Destroyable.Util.tryDestroyAll(l, ApplicationScoped.class);
l.clear();
}
listeners.clear();
}
public void loadDefaultEventListeners() {
loadDiBinderListener();
}
private void loadDiBinderListener() {
bind(DependencyInjectionBinder.class, new ActEventListenerBase<DependencyInjectionBinder>() {
@Override
public void on(DependencyInjectionBinder event) throws Exception {
DependencyInjector injector = app().injector();
if (null == injector) {
LOGGER.warn("Dependency injector not found");
} else {
injector.registerDiBinder(event);
}
}
});
}
}