refactorings. abstract base class. repackaging
This commit is contained in:
parent
2ae0e5fae0
commit
9ce3ceb6f4
247
src/main/java/org/mbassy/AbstractMessageBus.java
Normal file
247
src/main/java/org/mbassy/AbstractMessageBus.java
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
package org.mbassy;
|
||||||
|
|
||||||
|
import org.mbassy.common.IPredicate;
|
||||||
|
import org.mbassy.common.ReflectionUtils;
|
||||||
|
import org.mbassy.listener.Listener;
|
||||||
|
import org.mbassy.listener.MetadataReader;
|
||||||
|
import org.mbassy.subscription.Subscription;
|
||||||
|
import org.mbassy.subscription.SubscriptionDeliveryRequest;
|
||||||
|
import org.mbassy.subscription.SubscriptionFactory;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class AbstractMessageBus<T, P extends IMessageBus.IPostCommand> implements IMessageBus<T, P> {
|
||||||
|
|
||||||
|
|
||||||
|
// This predicate is used to find all message listeners (methods annotated with @Listener)
|
||||||
|
private static final IPredicate<Method> AllMessageListeners = new IPredicate<Method>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(Method target) {
|
||||||
|
return target.getAnnotation(Listener.class) != null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is the default error handler it will simply log to standard out and
|
||||||
|
// print stack trace if available
|
||||||
|
protected static final class ConsoleLogger implements IPublicationErrorHandler {
|
||||||
|
@Override
|
||||||
|
public void handleError(PublicationError error) {
|
||||||
|
System.out.println(error);
|
||||||
|
if (error.getCause() != null) error.getCause().printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
// executor for asynchronous listeners using unbound queuing strategy to ensure that no events get lost
|
||||||
|
private ExecutorService executor;
|
||||||
|
|
||||||
|
private MetadataReader metadataReader = new MetadataReader();
|
||||||
|
|
||||||
|
// all subscriptions per message type
|
||||||
|
// this is the primary list for dispatching a specific message
|
||||||
|
// write access is synchronized and happens very infrequently
|
||||||
|
private final Map<Class, Collection<Subscription>> subscriptionsPerMessage = new HashMap(50);
|
||||||
|
|
||||||
|
// all subscriptions per messageHandler type
|
||||||
|
// this list provides fast access for subscribing and unsubscribing
|
||||||
|
// write access is synchronized and happens very infrequently
|
||||||
|
private final Map<Class, Collection<Subscription>> subscriptionsPerListener = new HashMap(50);
|
||||||
|
|
||||||
|
// remember already processed classes that do not contain any listeners
|
||||||
|
private final Collection<Class> nonListeners = new HashSet();
|
||||||
|
|
||||||
|
// this handler will receive all errors that occur during message dispatch or message handling
|
||||||
|
private CopyOnWriteArrayList<IPublicationErrorHandler> errorHandlers = new CopyOnWriteArrayList<IPublicationErrorHandler>();
|
||||||
|
|
||||||
|
// all threads that are available for asynchronous message dispatching
|
||||||
|
private final CopyOnWriteArrayList<Thread> dispatchers = new CopyOnWriteArrayList<Thread>();
|
||||||
|
|
||||||
|
// all pending messages scheduled for asynchronous dispatch are queued here
|
||||||
|
private final LinkedBlockingQueue<SubscriptionDeliveryRequest<T>> pendingMessages = new LinkedBlockingQueue<SubscriptionDeliveryRequest<T>>();
|
||||||
|
|
||||||
|
private final SubscriptionFactory subscriptionFactory;
|
||||||
|
|
||||||
|
// initialize the dispatch workers
|
||||||
|
private void initDispatcherThreads(int numberOfThreads) {
|
||||||
|
for (int i = 0; i < numberOfThreads; i++) {
|
||||||
|
// each thread will run forever and process incoming
|
||||||
|
//dispatch requests
|
||||||
|
Thread dispatcher = new Thread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
pendingMessages.take().execute();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
handlePublicationError(new PublicationError(e, "Asynchronous publication interrupted", null, null, null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dispatchers.add(dispatcher);
|
||||||
|
dispatcher.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractMessageBus() {
|
||||||
|
this(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractMessageBus(int dispatcherThreadCount) {
|
||||||
|
this(2, new ThreadPoolExecutor(5, 50, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractMessageBus(int dispatcherThreadCount, ExecutorService executor) {
|
||||||
|
this.executor = executor;
|
||||||
|
initDispatcherThreads(dispatcherThreadCount > 0 ? dispatcherThreadCount : 2);
|
||||||
|
addErrorHandler(new ConsoleLogger());
|
||||||
|
subscriptionFactory = getSubscriptionFactory();
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract SubscriptionFactory getSubscriptionFactory();
|
||||||
|
|
||||||
|
protected void initialize(){}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<IPublicationErrorHandler> getRegisteredErrorHandlers() {
|
||||||
|
return Collections.unmodifiableCollection(errorHandlers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unsubscribe(Object listener) {
|
||||||
|
if (listener == null) return;
|
||||||
|
Collection<Subscription> subscriptions = subscriptionsPerListener.get(listener.getClass());
|
||||||
|
if (subscriptions == null) return;
|
||||||
|
for (Subscription subscription : subscriptions) {
|
||||||
|
subscription.unsubscribe(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void subscribe(Object listener) {
|
||||||
|
try {
|
||||||
|
Class listeningClass = listener.getClass();
|
||||||
|
if (nonListeners.contains(listeningClass))
|
||||||
|
return; // early reject of known classes that do not participate in eventing
|
||||||
|
Collection<Subscription> subscriptionsByListener = subscriptionsPerListener.get(listeningClass);
|
||||||
|
if (subscriptionsByListener == null) { // if the type is registered for the first time
|
||||||
|
synchronized (this) { // new subscriptions must be processed sequentially for each class
|
||||||
|
subscriptionsByListener = subscriptionsPerListener.get(listeningClass);
|
||||||
|
if (subscriptionsByListener == null) { // double check (a bit ugly but works here)
|
||||||
|
List<Method> messageHandlers = getListeners(listeningClass); // get all methods with subscriptions
|
||||||
|
subscriptionsByListener = new ArrayList<Subscription>(messageHandlers.size()); // it's safe to use non-concurrent collection here (read only)
|
||||||
|
if (messageHandlers.isEmpty()) { // remember the class as non listening class
|
||||||
|
nonListeners.add(listeningClass);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// create subscriptions for all detected listeners
|
||||||
|
for (Method messageHandler : messageHandlers) {
|
||||||
|
if (!isValidMessageHandler(messageHandler)) continue; // ignore invalid listeners
|
||||||
|
Class eventType = getMessageType(messageHandler);
|
||||||
|
Subscription subscription = subscriptionFactory.createSubscription(metadataReader.getHandlerMetadata(messageHandler));
|
||||||
|
subscription.subscribe(listener);
|
||||||
|
addMessageTypeSubscription(eventType, subscription);
|
||||||
|
subscriptionsByListener.add(subscription);
|
||||||
|
//updateMessageTypeHierarchy(eventType);
|
||||||
|
}
|
||||||
|
subscriptionsPerListener.put(listeningClass, subscriptionsByListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// register the listener to the existing subscriptions
|
||||||
|
for (Subscription sub : subscriptionsByListener) sub.subscribe(listener);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void addErrorHandler(IPublicationErrorHandler handler) {
|
||||||
|
errorHandlers.add(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addAsynchronousDeliveryRequest(SubscriptionDeliveryRequest<T> request) {
|
||||||
|
pendingMessages.offer(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// obtain the set of subscriptions for the given message type
|
||||||
|
protected Collection<Subscription> getSubscriptionsByMessageType(Class messageType) {
|
||||||
|
Set<Subscription> subscriptions = new TreeSet<Subscription>(Subscription.SubscriptionByPriorityDesc);
|
||||||
|
|
||||||
|
if (subscriptionsPerMessage.get(messageType) != null) {
|
||||||
|
subscriptions.addAll(subscriptionsPerMessage.get(messageType));
|
||||||
|
}
|
||||||
|
for (Class eventSuperType : getSuperclasses(messageType)) {
|
||||||
|
if (subscriptionsPerMessage.get(eventSuperType) != null) {
|
||||||
|
subscriptions.addAll(subscriptionsPerMessage.get(eventSuperType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// IMPROVEMENT: use tree list that sorts during insertion
|
||||||
|
//Collections.sort(subscriptions, new SubscriptionByPriorityDesc());
|
||||||
|
return subscriptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<Class> getSuperclasses(Class from) {
|
||||||
|
Collection<Class> superclasses = new LinkedList<Class>();
|
||||||
|
while (!from.equals(Object.class)) {
|
||||||
|
superclasses.add(from.getSuperclass());
|
||||||
|
from = from.getSuperclass();
|
||||||
|
}
|
||||||
|
return superclasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
// associate a suscription with a message type
|
||||||
|
private void addMessageTypeSubscription(Class messageType, Subscription subscription) {
|
||||||
|
Collection<Subscription> subscriptions = subscriptionsPerMessage.get(messageType);
|
||||||
|
if (subscriptions == null) {
|
||||||
|
subscriptions = new CopyOnWriteArraySet<Subscription>();
|
||||||
|
subscriptionsPerMessage.put(messageType, subscriptions);
|
||||||
|
}
|
||||||
|
subscriptions.add(subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean isValidMessageHandler(Method handler) {
|
||||||
|
if (handler.getParameterTypes().length != 1) {
|
||||||
|
// a messageHandler only defines one parameter (the message)
|
||||||
|
System.out.println("Found no or more than one parameter in messageHandler [" + handler.getName()
|
||||||
|
+ "]. A messageHandler must define exactly one parameter");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Class getMessageType(Method listener) {
|
||||||
|
return listener.getParameterTypes()[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// get all listeners defined by the given class (includes
|
||||||
|
// listeners defined in super classes)
|
||||||
|
private static List<Method> getListeners(Class<?> target) {
|
||||||
|
return ReflectionUtils.getMethods(AllMessageListeners, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void handlePublicationError(PublicationError error) {
|
||||||
|
for (IPublicationErrorHandler errorHandler : errorHandlers)
|
||||||
|
errorHandler.handleError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void finalize() throws Throwable {
|
||||||
|
super.finalize();
|
||||||
|
for (Thread dispatcher : dispatchers) {
|
||||||
|
dispatcher.interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Executor getExecutor() {
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,8 @@
|
|||||||
package org.mbassy;
|
package org.mbassy;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* A message bus offers facilities for publishing messages to registered listeners. Messages can be dispatched
|
* A message bus offers facilities for publishing messages to registered listeners. Messages can be dispatched
|
||||||
@ -69,6 +72,25 @@ public interface IMessageBus<T, P extends IMessageBus.IPostCommand> {
|
|||||||
*/
|
*/
|
||||||
public P post(T message);
|
public P post(T message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publication errors may occur at various points of time during message delivery. A handler may throw an exception,
|
||||||
|
* may not be accessible due to security constraints or is not annotated properly.
|
||||||
|
* In any of all possible cases a publication error is created and passed to each of the registered error handlers.
|
||||||
|
* A call to this method will add the given error handler to the chain
|
||||||
|
*
|
||||||
|
* @param errorHandler
|
||||||
|
*/
|
||||||
|
public void addErrorHandler(IPublicationErrorHandler errorHandler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an immutable collection containing all the registered error handlers
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Collection<IPublicationErrorHandler> getRegisteredErrorHandlers();
|
||||||
|
|
||||||
|
public Executor getExecutor();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static interface IPostCommand{
|
public static interface IPostCommand{
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package org.mbassy.common;
|
package org.mbassy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO. Insert class description here
|
* TODO. Insert class description here
|
@ -1,102 +1,32 @@
|
|||||||
package org.mbassy;
|
package org.mbassy;
|
||||||
|
|
||||||
import org.mbassy.filter.Filter;
|
import org.mbassy.subscription.*;
|
||||||
import org.mbassy.filter.MessageFilter;
|
|
||||||
import org.mbassy.common.*;
|
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
|
||||||
public class MBassador<T> implements IMessageBus<T, SimplePostCommand>{
|
public class MBassador<T> extends AbstractMessageBus<T, SimplePostCommand<T>>{
|
||||||
|
|
||||||
|
|
||||||
// This predicate is used to find all message listeners (methods annotated with @Listener)
|
|
||||||
private static final IPredicate<Method> AllMessageListeners = new IPredicate<Method>() {
|
|
||||||
@Override
|
|
||||||
public boolean apply(Method target) {
|
|
||||||
return target.getAnnotation(Listener.class) != null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// This is the default error handler it will simply log to standard out and
|
|
||||||
// print stack trace if available
|
|
||||||
protected static final class ConsoleLogger implements IPublicationErrorHandler {
|
|
||||||
@Override
|
|
||||||
public void handleError(PublicationError error) {
|
|
||||||
System.out.println(error);
|
|
||||||
if (error.getCause() != null) error.getCause().printStackTrace();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// executor for asynchronous listeners using unbound queuing strategy to ensure that no events get lost
|
|
||||||
private ExecutorService executor;
|
|
||||||
|
|
||||||
// cache already created filter instances
|
|
||||||
private final Map<Class<? extends MessageFilter>, MessageFilter> filterCache = new HashMap<Class<? extends MessageFilter>, MessageFilter>();
|
|
||||||
|
|
||||||
// all subscriptions per message type
|
|
||||||
// this is the primary list for dispatching a specific message
|
|
||||||
// write access is synchronized and happens very infrequently
|
|
||||||
private final Map<Class, Collection<Subscription>> subscriptionsPerMessage = new HashMap(50);
|
|
||||||
|
|
||||||
// all subscriptions per messageHandler type
|
|
||||||
// this list provides fast access for subscribing and unsubscribing
|
|
||||||
private final Map<Class, Collection<Subscription>> subscriptionsPerListener = new HashMap(50);
|
|
||||||
|
|
||||||
// remember already processed classes that do not contain any listeners
|
|
||||||
private final Collection<Class> nonListeners = new HashSet();
|
|
||||||
|
|
||||||
// this handler will receive all errors that occur during message dispatch or message handling
|
|
||||||
private IPublicationErrorHandler errorHandler = new ConsoleLogger();
|
|
||||||
|
|
||||||
|
|
||||||
// all threads that are available for asynchronous message dispatching
|
|
||||||
private final CopyOnWriteArrayList<Thread> dispatchers = new CopyOnWriteArrayList<Thread>();
|
|
||||||
|
|
||||||
// all pending messages scheduled for asynchronous dispatch are queued here
|
|
||||||
private final LinkedBlockingQueue<T> pendingMessages = new LinkedBlockingQueue<T>();
|
|
||||||
|
|
||||||
// initialize the dispatch workers
|
|
||||||
private void initDispatcherThreads(int numberOfThreads) {
|
|
||||||
for (int i = 0; i < numberOfThreads; i++) {
|
|
||||||
// each thread will run forever and process incoming
|
|
||||||
//dispatch requests
|
|
||||||
Thread dispatcher = new Thread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
publish(pendingMessages.take());
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
errorHandler.handleError(new PublicationError(e, "Asynchronous publication interrupted", null, null, null));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
dispatchers.add(dispatcher);
|
|
||||||
dispatcher.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public MBassador(){
|
public MBassador(){
|
||||||
this(2);
|
this(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MBassador(int dispatcherThreadCount){
|
public MBassador(int dispatcherThreadCount){
|
||||||
this(2, new ThreadPoolExecutor(5, 50, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>()));
|
super(dispatcherThreadCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MBassador(int dispatcherThreadCount, ExecutorService executor){
|
public MBassador(int dispatcherThreadCount, ExecutorService executor){
|
||||||
this.executor = executor;
|
super(dispatcherThreadCount,executor);
|
||||||
initDispatcherThreads(dispatcherThreadCount > 0 ? dispatcherThreadCount : 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SubscriptionFactory getSubscriptionFactory() {
|
||||||
|
return new SubscriptionFactory(this);
|
||||||
|
}
|
||||||
|
|
||||||
public void publishAsync(T message){
|
public void publishAsync(T message){
|
||||||
pendingMessages.offer(message);
|
addAsynchronousDeliveryRequest(new SubscriptionDeliveryRequest<T>(getSubscriptionsByMessageType(message.getClass()), message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -125,370 +55,9 @@ public class MBassador<T> implements IMessageBus<T, SimplePostCommand>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void unsubscribe(Object listener){
|
|
||||||
if (listener == null) return;
|
|
||||||
Collection<Subscription> subscriptions = subscriptionsPerListener.get(listener.getClass());
|
|
||||||
if(subscriptions == null)return;
|
|
||||||
for (Subscription subscription : subscriptions) {
|
|
||||||
subscription.unsubscribe(listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SimplePostCommand post(T message) {
|
public SimplePostCommand post(T message) {
|
||||||
return new SimplePostCommand(this, message);
|
return new SimplePostCommand(this, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void subscribe(Object listener){
|
|
||||||
Class listeningClass = listener.getClass();
|
|
||||||
if (nonListeners.contains(listeningClass))
|
|
||||||
return; // early reject of known classes that do not participate in eventing
|
|
||||||
Collection<Subscription> subscriptionsByListener = subscriptionsPerListener.get(listeningClass);
|
|
||||||
if (subscriptionsByListener == null) { // if the type is registered for the first time
|
|
||||||
synchronized (this) { // new subscriptions must be processed sequentially for each class
|
|
||||||
subscriptionsByListener = subscriptionsPerListener.get(listeningClass);
|
|
||||||
if (subscriptionsByListener == null) { // double check (a bit ugly but works here)
|
|
||||||
List<Method> messageHandlers = getListeners(listeningClass); // get all methods with subscriptions
|
|
||||||
subscriptionsByListener = new ArrayList<Subscription>(messageHandlers.size()); // it's safe to use non-concurrent collection here (read only)
|
|
||||||
if (messageHandlers.isEmpty()) { // remember the class as non listening class
|
|
||||||
nonListeners.add(listeningClass);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// create subscriptions for all detected listeners
|
|
||||||
for (Method messageHandler : messageHandlers) {
|
|
||||||
if (!isValidMessageHandler(messageHandler)) continue; // ignore invalid listeners
|
|
||||||
MessageFilter[] filter = getFilter(messageHandler.getAnnotation(Listener.class));
|
|
||||||
Class eventType = getMessageType(messageHandler);
|
|
||||||
Subscription subscription = createSubscription(messageHandler, filter);
|
|
||||||
subscription.subscribe(listener);
|
|
||||||
addMessageTypeSubscription(eventType, subscription);
|
|
||||||
subscriptionsByListener.add(subscription);
|
|
||||||
//updateMessageTypeHierarchy(eventType);
|
|
||||||
}
|
|
||||||
subscriptionsPerListener.put(listeningClass, subscriptionsByListener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// register the listener to the existing subscriptions
|
|
||||||
for (Subscription sub : subscriptionsByListener) sub.subscribe(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setErrorHandler(IPublicationErrorHandler handler){
|
|
||||||
this.errorHandler = handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// obtain the set of subscriptions for the given message type
|
|
||||||
private Collection<Subscription> getSubscriptionsByMessageType(Class messageType) {
|
|
||||||
List<Subscription> subscriptions = new LinkedList<Subscription>();
|
|
||||||
|
|
||||||
if(subscriptionsPerMessage.get(messageType) != null) {
|
|
||||||
subscriptions.addAll(subscriptionsPerMessage.get(messageType));
|
|
||||||
}
|
|
||||||
for (Class eventSuperType : getSuperclasses(messageType)){
|
|
||||||
if(subscriptionsPerMessage.get(eventSuperType) != null){
|
|
||||||
subscriptions.addAll(subscriptionsPerMessage.get(eventSuperType));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// IMPROVEMENT: use tree list that sorts during insertion
|
|
||||||
//Collections.sort(subscriptions, new SubscriptionByPriorityDesc());
|
|
||||||
return subscriptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<Class> getSuperclasses(Class from){
|
|
||||||
Collection<Class> superclasses = new LinkedList<Class>();
|
|
||||||
while(!from.equals(Object.class)){
|
|
||||||
superclasses.add(from.getSuperclass());
|
|
||||||
from = from.getSuperclass();
|
|
||||||
}
|
|
||||||
return superclasses;
|
|
||||||
}
|
|
||||||
|
|
||||||
// associate a suscription with a message type
|
|
||||||
private void addMessageTypeSubscription(Class messageType, Subscription subscription) {
|
|
||||||
Collection<Subscription> subscriptions = subscriptionsPerMessage.get(messageType);
|
|
||||||
if (subscriptions == null) {
|
|
||||||
subscriptions = new CopyOnWriteArraySet<Subscription>();
|
|
||||||
subscriptionsPerMessage.put(messageType, subscriptions);
|
|
||||||
}
|
|
||||||
subscriptions.add(subscription);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private boolean isValidMessageHandler(Method handler) {
|
|
||||||
if (handler.getParameterTypes().length != 1) {
|
|
||||||
// a messageHandler only defines one parameter (the message)
|
|
||||||
System.out.println("Found no or more than one parameter in messageHandler [" + handler.getName()
|
|
||||||
+ "]. A messageHandler must define exactly one parameter");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Class getMessageType(Method listener) {
|
|
||||||
return listener.getParameterTypes()[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// get all listeners defined by the given class (includes
|
|
||||||
// listeners defined in super classes)
|
|
||||||
private static List<Method> getListeners(Class<?> target) {
|
|
||||||
return ReflectionUtils.getMethods(AllMessageListeners, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
// retrieve all instances of filters associated with the given subscription
|
|
||||||
private MessageFilter[] getFilter(Listener subscription) {
|
|
||||||
if (subscription.value().length == 0) return null;
|
|
||||||
MessageFilter[] filters = new MessageFilter[subscription.value().length];
|
|
||||||
int i = 0;
|
|
||||||
for (Filter filterDef : subscription.value()) {
|
|
||||||
MessageFilter filter = filterCache.get(filterDef.value());
|
|
||||||
if (filter == null) {
|
|
||||||
try {
|
|
||||||
filter = filterDef.value().newInstance();
|
|
||||||
filterCache.put(filterDef.value(), filter);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
handlePublicationError(new PublicationError()
|
|
||||||
.setMessage("Error retrieving filter"));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
filters[i] = filter;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
return filters;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void handlePublicationError(PublicationError error) {
|
|
||||||
errorHandler.handleError(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void finalize() throws Throwable {
|
|
||||||
super.finalize();
|
|
||||||
for(Thread dispatcher : dispatchers){
|
|
||||||
dispatcher.interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Subscription createSubscription(Method messageHandler, MessageFilter[] filter){
|
|
||||||
if(filter == null || filter.length == 0){
|
|
||||||
if(isAsynchronous(messageHandler)){
|
|
||||||
return new UnfilteredAsynchronousSubscription(messageHandler);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
return new UnfilteredSynchronousSubscription(messageHandler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
if(isAsynchronous(messageHandler)){
|
|
||||||
return new FilteredAsynchronousSubscription(messageHandler, filter);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
return new FilteredSynchronousSubscription(messageHandler, filter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isAsynchronous(Method messageHandler){
|
|
||||||
return messageHandler.getAnnotation(Listener.class).mode().equals(Listener.Dispatch.Asynchronous);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscription is a thread safe container for objects that contain message handlers
|
|
||||||
*/
|
|
||||||
private abstract class Subscription {
|
|
||||||
|
|
||||||
private final Method messageHandler;
|
|
||||||
|
|
||||||
protected ConcurrentSet<Object> listeners = new ConcurrentSet<Object>();
|
|
||||||
|
|
||||||
private int priority = 0;
|
|
||||||
|
|
||||||
private Subscription(Method messageHandler) {
|
|
||||||
// TODO: init priority
|
|
||||||
this.messageHandler = messageHandler;
|
|
||||||
this.messageHandler.setAccessible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void publish(Object message);
|
|
||||||
|
|
||||||
protected abstract void dispatch(final Object message, final Object listener);
|
|
||||||
|
|
||||||
|
|
||||||
public int getPriority(){
|
|
||||||
return priority;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void subscribe(Object o) {
|
|
||||||
listeners.add(o);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void invokeHandler(final Object message, final Object listener){
|
|
||||||
try {
|
|
||||||
messageHandler.invoke(listener, message);
|
|
||||||
}catch(IllegalAccessException e){
|
|
||||||
MBassador.this.handlePublicationError(
|
|
||||||
new PublicationError(e, "Error during messageHandler notification. " +
|
|
||||||
"The class or method is not accessible",
|
|
||||||
messageHandler, listener, message));
|
|
||||||
}
|
|
||||||
catch(IllegalArgumentException e){
|
|
||||||
MBassador.this.handlePublicationError(
|
|
||||||
new PublicationError(e, "Error during messageHandler notification. " +
|
|
||||||
"Wrong arguments passed to method. Was: " + message.getClass()
|
|
||||||
+ "Expected: " + messageHandler.getParameterTypes()[0],
|
|
||||||
messageHandler, listener, message));
|
|
||||||
}
|
|
||||||
catch (InvocationTargetException e) {
|
|
||||||
MBassador.this.handlePublicationError(
|
|
||||||
new PublicationError(e, "Error during messageHandler notification. " +
|
|
||||||
"Message handler threw exception",
|
|
||||||
messageHandler, listener, message));
|
|
||||||
}
|
|
||||||
catch (Throwable e) {
|
|
||||||
MBassador.this.handlePublicationError(
|
|
||||||
new PublicationError(e, "Error during messageHandler notification. " +
|
|
||||||
"Unexpected exception",
|
|
||||||
messageHandler, listener, message));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void unsubscribe(Object existingListener) {
|
|
||||||
listeners.remove(existingListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private abstract class UnfilteredSubscription extends Subscription{
|
|
||||||
|
|
||||||
|
|
||||||
private UnfilteredSubscription(Method messageHandler) {
|
|
||||||
super(messageHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void publish(Object message) {
|
|
||||||
|
|
||||||
Iterator<Object> iterator = listeners.iterator();
|
|
||||||
Object listener = null;
|
|
||||||
while ((listener = iterator.next()) != null) {
|
|
||||||
dispatch(message, listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class UnfilteredAsynchronousSubscription extends UnfilteredSubscription{
|
|
||||||
|
|
||||||
|
|
||||||
private UnfilteredAsynchronousSubscription(Method messageHandler) {
|
|
||||||
super(messageHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void dispatch(final Object message, final Object listener){
|
|
||||||
MBassador.this.executor.execute(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
invokeHandler(message, listener);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class UnfilteredSynchronousSubscription extends UnfilteredSubscription{
|
|
||||||
|
|
||||||
|
|
||||||
private UnfilteredSynchronousSubscription(Method messageHandler) {
|
|
||||||
super(messageHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void dispatch(final Object message, final Object listener){
|
|
||||||
invokeHandler(message, listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private abstract class FilteredSubscription extends Subscription{
|
|
||||||
|
|
||||||
private final MessageFilter[] filter;
|
|
||||||
|
|
||||||
|
|
||||||
private FilteredSubscription(Method messageHandler, MessageFilter[] filter) {
|
|
||||||
super(messageHandler);
|
|
||||||
this.filter = filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean passesFilter(Object message, Object listener) {
|
|
||||||
|
|
||||||
if (filter == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
for (int i = 0; i < filter.length; i++) {
|
|
||||||
if (!filter[i].accepts(message, listener)) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void publish(Object message) {
|
|
||||||
|
|
||||||
Iterator<Object> iterator = listeners.iterator();
|
|
||||||
Object listener = null;
|
|
||||||
while ((listener = iterator.next()) != null) {
|
|
||||||
if(passesFilter(message, listener)) {
|
|
||||||
dispatch(message, listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FilteredSynchronousSubscription extends FilteredSubscription{
|
|
||||||
|
|
||||||
|
|
||||||
private FilteredSynchronousSubscription(Method messageHandler, MessageFilter[] filter) {
|
|
||||||
super(messageHandler, filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void dispatch(final Object message, final Object listener){
|
|
||||||
MBassador.this.executor.execute(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
invokeHandler(message, listener);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FilteredAsynchronousSubscription extends FilteredSubscription{
|
|
||||||
|
|
||||||
|
|
||||||
private FilteredAsynchronousSubscription(Method messageHandler, MessageFilter[] filter) {
|
|
||||||
super(messageHandler, filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void dispatch(final Object message, final Object listener){
|
|
||||||
invokeHandler(message, listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private final class SubscriptionByPriorityDesc implements Comparator<Subscription> {
|
|
||||||
@Override
|
|
||||||
public int compare(Subscription o1, Subscription o2) {
|
|
||||||
return o1.getPriority() - o2.getPriority();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package org.mbassy.common;
|
package org.mbassy;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package org.mbassy.filter;
|
package org.mbassy.listener;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
@ -1,6 +1,4 @@
|
|||||||
package org.mbassy;
|
package org.mbassy.listener;
|
||||||
|
|
||||||
import org.mbassy.filter.Filter;
|
|
||||||
|
|
||||||
import java.lang.annotation.*;
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
@ -16,12 +14,10 @@ import java.lang.annotation.*;
|
|||||||
@Target(value = {ElementType.METHOD})
|
@Target(value = {ElementType.METHOD})
|
||||||
public @interface Listener {
|
public @interface Listener {
|
||||||
|
|
||||||
Filter[] value() default {}; // no filters by default
|
Filter[] filters() default {}; // no filters by default
|
||||||
|
|
||||||
Dispatch mode() default Dispatch.Synchronous;
|
Mode dispatch() default Mode.Synchronous;
|
||||||
|
|
||||||
public static enum Dispatch{
|
int priority() default 0;
|
||||||
Synchronous,Asynchronous
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package org.mbassy.filter;
|
package org.mbassy.listener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object filters can be used to prevent certain messages to be delivered to a specific listener.
|
* Object filters can be used to prevent certain messages to be delivered to a specific listener.
|
@ -0,0 +1,51 @@
|
|||||||
|
package org.mbassy.listener;
|
||||||
|
|
||||||
|
import org.mbassy.listener.Listener;
|
||||||
|
import org.mbassy.listener.Mode;
|
||||||
|
import org.mbassy.listener.MessageFilter;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User: benni
|
||||||
|
* Date: 11/14/12
|
||||||
|
*/
|
||||||
|
public class MessageHandlerMetadata {
|
||||||
|
|
||||||
|
private Method handler;
|
||||||
|
|
||||||
|
private MessageFilter[] filter;
|
||||||
|
|
||||||
|
private Listener listenerConfig;
|
||||||
|
|
||||||
|
private boolean isAsynchronous = false;
|
||||||
|
|
||||||
|
|
||||||
|
public MessageHandlerMetadata(Method handler, MessageFilter[] filter, Listener listenerConfig) {
|
||||||
|
this.handler = handler;
|
||||||
|
this.filter = filter;
|
||||||
|
this.listenerConfig = listenerConfig;
|
||||||
|
this.isAsynchronous = listenerConfig.dispatch().equals(Mode.Asynchronous);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isAsynchronous(){
|
||||||
|
return isAsynchronous;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFiltered(){
|
||||||
|
return filter == null || filter.length == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPriority(){
|
||||||
|
return listenerConfig.priority();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Method getHandler() {
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageFilter[] getFilter() {
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
}
|
43
src/main/java/org/mbassy/listener/MetadataReader.java
Normal file
43
src/main/java/org/mbassy/listener/MetadataReader.java
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package org.mbassy.listener;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created with IntelliJ IDEA.
|
||||||
|
* User: benni
|
||||||
|
* Date: 11/16/12
|
||||||
|
* Time: 10:22 AM
|
||||||
|
* To change this template use File | Settings | File Templates.
|
||||||
|
*/
|
||||||
|
public class MetadataReader {
|
||||||
|
|
||||||
|
// cache already created filter instances
|
||||||
|
private final Map<Class<? extends MessageFilter>, MessageFilter> filterCache = new HashMap<Class<? extends MessageFilter>, MessageFilter>();
|
||||||
|
|
||||||
|
// retrieve all instances of filters associated with the given subscription
|
||||||
|
private MessageFilter[] getFilter(Listener subscription) throws Exception{
|
||||||
|
if (subscription.filters().length == 0) return null;
|
||||||
|
MessageFilter[] filters = new MessageFilter[subscription.filters().length];
|
||||||
|
int i = 0;
|
||||||
|
for (Filter filterDef : subscription.filters()) {
|
||||||
|
MessageFilter filter = filterCache.get(filterDef.value());
|
||||||
|
if (filter == null) {
|
||||||
|
filter = filterDef.value().newInstance();
|
||||||
|
filterCache.put(filterDef.value(), filter);
|
||||||
|
|
||||||
|
}
|
||||||
|
filters[i] = filter;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public MessageHandlerMetadata getHandlerMetadata(Method messageHandler) throws Exception{
|
||||||
|
Listener config = messageHandler.getAnnotation(Listener.class);
|
||||||
|
MessageFilter[] filter = getFilter(config);
|
||||||
|
return new MessageHandlerMetadata(messageHandler, filter, config);
|
||||||
|
}
|
||||||
|
}
|
12
src/main/java/org/mbassy/listener/Mode.java
Normal file
12
src/main/java/org/mbassy/listener/Mode.java
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package org.mbassy.listener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created with IntelliJ IDEA.
|
||||||
|
* User: benni
|
||||||
|
* Date: 11/16/12
|
||||||
|
* Time: 10:01 AM
|
||||||
|
* To change this template use File | Settings | File Templates.
|
||||||
|
*/
|
||||||
|
public enum Mode {
|
||||||
|
Synchronous,Asynchronous
|
||||||
|
}
|
@ -2,8 +2,10 @@ package org.mbassy;
|
|||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mbassy.filter.Filter;
|
import org.mbassy.listener.Filter;
|
||||||
import org.mbassy.filter.MessageFilter;
|
import org.mbassy.listener.Listener;
|
||||||
|
import org.mbassy.listener.MessageFilter;
|
||||||
|
import org.mbassy.listener.Mode;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -187,14 +189,17 @@ public class MBassadorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// this handler will be invoked asynchronously
|
// this handler will be invoked asynchronously
|
||||||
@Listener(mode = Listener.Dispatch.Asynchronous)
|
@Listener(priority = 0, dispatch = Mode.Asynchronous)
|
||||||
public void handleSubTestEvent(SubTestEvent event) {
|
public void handleSubTestEvent(SubTestEvent event) {
|
||||||
event.counter.incrementAndGet();
|
event.counter.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
// this handler will receive events of type SubTestEvent
|
// this handler will receive events of type SubTestEvent
|
||||||
// or any subtabe and that passes the given filter
|
// or any subtabe and that passes the given filter
|
||||||
@Listener({@Filter(MessageFilter.None.class),@Filter(MessageFilter.All.class)})
|
@Listener(
|
||||||
|
priority = 10,
|
||||||
|
dispatch = Mode.Synchronous,
|
||||||
|
filters = {@Filter(MessageFilter.None.class),@Filter(MessageFilter.All.class)})
|
||||||
public void handleFiltered(SubTestEvent event) {
|
public void handleFiltered(SubTestEvent event) {
|
||||||
event.counter.incrementAndGet();
|
event.counter.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user