/****************************************************************************************** MIT License Copyright (c) 2012 Benjamin Diedrichsen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ********************************************************************************************/ package net.engio.mbassy; import net.engio.mbassy.common.ReflectionUtils; import net.engio.mbassy.dispatch.MessagingContext; import net.engio.mbassy.listener.MessageHandlerMetadata; import net.engio.mbassy.listener.MetadataReader; import net.engio.mbassy.subscription.Subscription; import net.engio.mbassy.subscription.SubscriptionFactory; import java.util.*; import java.util.concurrent.*; /** * The base class for all message bus implementations. * * @param <T> * @param <P> */ public abstract class AbstractMessageBus<T, P extends IMessageBus.IPostCommand> implements IMessageBus<T, P> { // executor for asynchronous listeners using unbound queuing strategy to ensure that no events get lost private final ExecutorService executor; // the metadata reader that is used to parse objects passed to the subscribe method private final MetadataReader 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 final List<IPublicationErrorHandler> errorHandlers = new CopyOnWriteArrayList<IPublicationErrorHandler>(); // all threads that are available for asynchronous message dispatching private final List<Thread> dispatchers = new CopyOnWriteArrayList<Thread>(); // all pending messages scheduled for asynchronous dispatch are queued here private final BlockingQueue<MessagePublication> pendingMessages; // this factory is used to create specialized subscriptions based on the given message handler configuration // it can be customized by implementing the getSubscriptionFactory() method private final SubscriptionFactory subscriptionFactory; public AbstractMessageBus(BusConfiguration configuration) { this.executor = configuration.getExecutor(); subscriptionFactory = configuration.getSubscriptionFactory(); this.metadataReader = configuration.getMetadataReader(); pendingMessages = new LinkedBlockingQueue<MessagePublication>(configuration.getMaximumNumberOfPendingMessages()); initDispatcherThreads(configuration.getNumberOfMessageDispatchers()); addErrorHandler(new IPublicationErrorHandler.ConsoleLogger()); } // 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) { Thread.currentThread().interrupt(); return; } } } }); dispatcher.setDaemon(true); // do not prevent the JVM from exiting dispatchers.add(dispatcher); dispatcher.start(); } } @Override public Collection<IPublicationErrorHandler> getRegisteredErrorHandlers() { return Collections.unmodifiableCollection(errorHandlers); } public boolean unsubscribe(Object listener) { if (listener == null) return false; Collection<Subscription> subscriptions = subscriptionsPerListener.get(listener.getClass()); if (subscriptions == null) return false; boolean isRemoved = true; for (Subscription subscription : subscriptions) { isRemoved = isRemoved && subscription.unsubscribe(listener); } return isRemoved; } 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 subscriptionsByListener = subscriptionsPerListener.get(listeningClass); if (subscriptionsByListener == null) { // double check (a bit ugly but works here) List<MessageHandlerMetadata> messageHandlers = metadataReader.getMessageHandlers(listeningClass); if (messageHandlers.isEmpty()) { // remember the class as non listening class if no handlers are found nonListeners.add(listeningClass); return; } subscriptionsByListener = new ArrayList<Subscription>(messageHandlers.size()); // it's safe to use non-concurrent collection here (read only) // create subscriptions for all detected listeners for (MessageHandlerMetadata messageHandler : messageHandlers) { // create the subscription Subscription subscription = subscriptionFactory .createSubscription(new MessagingContext(this, messageHandler)); subscription.subscribe(listener); subscriptionsByListener.add(subscription);// add it for the listener type (for future subscriptions) List<Class<?>> messageTypes = messageHandler.getHandledMessages(); for(Class<?> messageType : messageTypes){ addMessageTypeSubscription(messageType, 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); } // this method enqueues a message delivery request protected MessagePublication addAsynchronousDeliveryRequest(MessagePublication request){ try { pendingMessages.put(request); return request.markScheduled(); } catch (InterruptedException e) { return request.setError(); } } // this method enqueues a message delivery request protected MessagePublication addAsynchronousDeliveryRequest(MessagePublication request, long timeout, TimeUnit unit){ try { return pendingMessages.offer(request, timeout, unit) ? request.markScheduled() : request.setError(); } catch (InterruptedException e) { return request.setError(); } } // obtain the set of subscriptions for the given message type // Note: never returns null! protected Collection<Subscription> getSubscriptionsByMessageType(Class messageType) { Set<Subscription> subscriptions = new TreeSet<Subscription>(Subscription.SubscriptionByPriorityDesc); if (subscriptionsPerMessage.get(messageType) != null) { subscriptions.addAll(subscriptionsPerMessage.get(messageType)); } // TODO: get superclasses is eligible for caching for (Class eventSuperType : ReflectionUtils.getSuperclasses(messageType)) { Collection<Subscription> subs = subscriptionsPerMessage.get(eventSuperType); if (subs != null) { for(Subscription sub : subs){ if(sub.handlesMessageType(messageType))subscriptions.add(sub); } } } return subscriptions; } // associate a suscription with a message type // NOTE: Not thread-safe! must be synchronized in outer scope private void addMessageTypeSubscription(Class messageType, Subscription subscription) { Collection<Subscription> subscriptions = subscriptionsPerMessage.get(messageType); if (subscriptions == null) { subscriptions = new LinkedList<Subscription>(); subscriptionsPerMessage.put(messageType, subscriptions); } subscriptions.add(subscription); } public void handlePublicationError(PublicationError error) { for (IPublicationErrorHandler errorHandler : errorHandlers){ errorHandler.handleError(error); } } @Override protected void finalize() throws Throwable { shutdown(); super.finalize(); } private void shutdown(){ for (Thread dispatcher : dispatchers) { dispatcher.interrupt(); } executor.shutdown(); } public boolean hasPendingMessages(){ return pendingMessages.size() > 0; } @Override public Executor getExecutor() { return executor; } }