
org.tickcode.broadcast.VMMessageBroker Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2012, tickcode.org
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of tickcode, nor tickcode.org, nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package org.tickcode.broadcast;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.log4j.Logger;
import org.tickcode.trace.BreadCrumbTrail;
import org.tickcode.trace.DefaultBreadCrumb;
import org.tickcode.trace.MethodUtil;
/**
* Inspired by http://www.eaipatterns.com/MessageBroker.html, this is the heart of all messaging between decoupled
* {@link Broadcast} implementations.
*
* @author eyon
*
*/
public class VMMessageBroker extends AbstractMessageBroker {
private static Logger logger;
private static boolean loggingOn;
private static boolean settingVMMessageBrokerForAll;
static {
logger = Logger.getLogger(org.tickcode.broadcast.VMMessageBroker.class);
loggingOn = (logger.getEffectiveLevel() != org.apache.log4j.Level.OFF);
}
public VMMessageBroker() {
}
/**
* By using ThreadLocal, every set of method names will be unique per thread. So while we allow
* another thread to broadcast on the same method, for a given thread there can only be method invocations
* on a specific method name once per broadcast.
*/
protected ThreadLocal> methodsOnTheThread = new ThreadLocal>(){
protected java.util.HashSet initialValue() {
return new HashSet();
};
};
protected ConcurrentHashMap interfacesByMethodName = new ConcurrentHashMap();
protected ConcurrentLinkedQueue> errorHandlers = new ConcurrentLinkedQueue>();
private ConcurrentHashMap watchForDuplicatesOfUnderlyingImplementationFromProxies = new ConcurrentHashMap();
protected class BroadcastConsumersForAGivenInterface {
Class broadcastInterface;
Method method;
CopyOnWriteArrayList> consumers = new CopyOnWriteArrayList>();
BroadcastConsumersForAGivenInterface(Class _interface, Method method) {
broadcastInterface = _interface;
this.method = method;
}
public void addBroadcastReceiver(Broadcast consumer) {
if (!weHave(consumer)) {
consumers.add(new WeakReference(consumer));
if (loggingOn) {
logger.debug(consumer.getClass().getName()
+ " has implemented "
+ broadcastInterface.getName()
+ " and should consume these broadcasts.");
}
}
}
/**
* Used only for testing to simulate when the garbage collector runs and
* sets WeakReference values to null
*
* @param consumer
*/
protected void setWeakReferencesToNull(Broadcast consumer) {
for (int i = 0; i < consumers.size(); i++) {
WeakReference c = consumers.get(i);
if (c.get() != null) {
if (c.get() == consumer) {
c.clear();
if (c.get() != null)
throw new RuntimeException(
"Expected this to be null!");
}
}
}
}
protected void remove(Broadcast consumer) {
boolean cleanOutWeakReferences = false;
for (int i = 0; i < consumers.size(); i++) {
WeakReference c = consumers.get(i);
if (c.get() != null) {
if (c.get() == consumer)
consumers.remove(c);
} else
cleanOutWeakReferences = true;
}
if (cleanOutWeakReferences) {
cleanOutWeakReferences();
}
}
protected boolean weHave(Broadcast consumer) {
boolean cleanOutWeakReferences = false;
for (int i = 0; i < consumers.size(); i++) {
WeakReference c = consumers.get(i);
if (c.get() != null) {
if (c.get() == consumer)
return true;
} else
cleanOutWeakReferences = true;
}
if (cleanOutWeakReferences) {
cleanOutWeakReferences();
}
return false;
}
protected Set getConsumers() {
HashSet returnConsumers = new HashSet();
boolean cleanOutWeakReferences = false;
for (int i = 0; i < consumers.size(); i++) {
WeakReference c = consumers.get(i);
if (c.get() != null) {
returnConsumers.add(c.get());
} else
cleanOutWeakReferences = true;
}
if (cleanOutWeakReferences) {
cleanOutWeakReferences();
}
return returnConsumers;
}
protected void cleanOutWeakReferences() {
for (WeakReference ref : consumers) {
if (ref.get() == null)
consumers.remove(ref);
}
}
public void broadcast(Broadcast producer, Object[] params) {
BreadCrumbTrail trail = BreadCrumbTrail.get();
boolean cleanOutWeakReferences = false;
for (int i = 0; i < this.consumers.size(); i++) {
WeakReference ref = this.consumers.get(i);
Broadcast consumer = ref.get();
if (consumer != null) {
try {
if (consumer != producer) {
if (loggingOn) {
logger.debug("We are sending a broadcast to "
+ consumer.getClass().getName()
+ " on interface "
+ MethodUtil.getReadableMethodString(
broadcastInterface, method,
params));
}
trail.add(new DefaultBreadCrumb(
MethodUtil
.getReadableMethodString(
producer.getClass(),
method, params),
MethodUtil.getReadableMethodString(
consumer.getClass(), method)));
method.invoke(consumer, params);
}
} catch (IllegalAccessException ex) {
if (loggingOn) {
logger.error(
"The consumer "
+ consumer.getClass().getName()
+ " on interface "
+ MethodUtil
.getReadableMethodString(
broadcastInterface,
method, params)
+ " has an IllegalAccessException!",
ex);
}
} catch (InvocationTargetException ex) {
if (loggingOn) {
logger.error(
"The consumer "
+ consumer.getClass().getName()
+ " on interface "
+ MethodUtil
.getReadableMethodString(
broadcastInterface,
method, params)
+ " has thrown an exception!", ex
.getCause());
}
for (WeakReference errorHandler : errorHandlers) {
if (errorHandler.get() != null)
errorHandler.get().error(VMMessageBroker.this, consumer,
ex.getCause(), trail);
else {
errorHandlers.remove(errorHandler);
}
}
}
} else {
cleanOutWeakReferences = true;
}
}
if (cleanOutWeakReferences) {
cleanOutWeakReferences();
}
trail.reset();
}
}
/* (non-Javadoc)
* @see org.tickcode.broadcast.MessageBroker#broadcast(org.tickcode.broadcast.Broadcast, java.lang.String, java.lang.Object[])
*/
@Override
public void broadcast(Broadcast producer, String methodName, Object[] params) {
HashSet methodsOnTheCurrentThreadStack = methodsOnTheThread.get();
if(!methodsOnTheCurrentThreadStack.contains(methodName)){
if (loggingOn) {
logger.debug(methodName + "(" + MethodUtil.getArguments(params)
+ ")");
}
methodsOnTheCurrentThreadStack.add(methodName);
beginBroadcasting(producer, methodName, params);
interfacesByMethodName.get(methodName).broadcast(producer, params);
finishedBroadcasting(producer, methodName,params);
methodsOnTheCurrentThreadStack.remove(methodName);
}
}
protected void beginBroadcasting(Broadcast producer, String methodName, Object[] params) {
// available for subclasses
}
protected void finishedBroadcasting(Broadcast producer, String methodName, Object[] params) {
// available for subclasses
}
@Override
public int size() {
HashSet consumer = new HashSet();
for (BroadcastConsumersForAGivenInterface imp : interfacesByMethodName
.values()) {
consumer.addAll(imp.getConsumers());
}
return consumer.size();
}
/* (non-Javadoc)
* @see org.tickcode.broadcast.MessageBroker#remove(org.tickcode.broadcast.Broadcast)
*/
@Override
public void remove(Broadcast consumer) {
//consumer = getBroadcastImplementation(consumer);
for (BroadcastConsumersForAGivenInterface imp : interfacesByMethodName
.values()) {
imp.remove(consumer);
}
}
/* (non-Javadoc)
* @see org.tickcode.broadcast.MessageBroker#add(org.tickcode.broadcast.Broadcast)
*/
@Override
public void add(Broadcast consumer) {
if(!watchForDuplicatesOfUnderlyingImplementationFromProxies.containsKey(getBroadcastImplementation(consumer))){
watchForDuplicatesOfUnderlyingImplementationFromProxies.put(getBroadcastImplementation(consumer), consumer);
}
else{
Broadcast implementation = getBroadcastImplementation(consumer);
Broadcast previousProxy = watchForDuplicatesOfUnderlyingImplementationFromProxies.get(implementation);
if(consumer instanceof java.lang.reflect.Proxy || previousProxy instanceof java.lang.reflect.Proxy){
throw new ProxyImplementationException("You tried to add a proxy with an implementation that was previously added.");
}
}
HashSet broadcastConsumerMethods = new HashSet();
HashMap methodsWithAnnotations = new HashMap();
// note that we have to pull the underlying implementation so that we can actually see the annotations!
for (Method method : getBroadcastImplementation(consumer).getClass().getMethods()) {
if (method.isAnnotationPresent(BroadcastConsumer.class)) {
methodsWithAnnotations.put(method.getName(),
BroadcastConsumer.class);
broadcastConsumerMethods.add(method.getName());
}
if (method.isAnnotationPresent(BroadcastProducer.class)) {
methodsWithAnnotations.put(method.getName(),
BroadcastProducer.class);
}
}
for (Class _interface : consumer.getClass().getInterfaces()) {
if (Broadcast.class.isAssignableFrom(_interface)
&& Broadcast.class != _interface) { // you cannot just implement {@link Broadcast}.
if (loggingOn) {
logger.debug("Interface: " + _interface.getSimpleName()
+ " added for " + consumer.getClass().getSimpleName());
}
for (Method method : _interface.getMethods()) {
if (loggingOn) {
logger.debug("Broadcasting to "
+ MethodUtil.getReadableMethodString(
_interface, method));
}
if (!method.getName().endsWith("$messageBroker") && !Void.TYPE.equals(method.getReturnType())) {
throw new NonVoidBroadcastMethodException(
"You tried to implement a non-void broadcast method. See "
+ MethodUtil.getReadableMethodString(
_interface, method));
}
BroadcastConsumersForAGivenInterface impl = interfacesByMethodName
.get(method.getName());
if (impl == null) {
impl = new BroadcastConsumersForAGivenInterface(
_interface, method);
if(broadcastConsumerMethods.contains(method.getName()))
impl.addBroadcastReceiver(consumer);
interfacesByMethodName.put(method.getName(), impl);
methodsWithAnnotations.remove(method.getName());
} else if( // this is because of the aspect BroadcastImpl.aj
method.getName().endsWith("$messageBroker")
){
// ignore
} else if (impl.broadcastInterface != _interface) {
logger.error("We cannot have two methods with the same name! Please look at "
+ MethodUtil.getReadableMethodString(
impl.broadcastInterface, impl.method)
+ " and "
+ MethodUtil.getReadableMethodString(
_interface, method));
throw new DuplicateMethodException(
"We cannot have two methods from a Broadcast interface with the same name! Please look at "
+ MethodUtil.getReadableMethodString(
impl.broadcastInterface,
impl.method)
+ " and "
+ MethodUtil.getReadableMethodString(
_interface, method));
} else {
if(broadcastConsumerMethods.contains(method.getName()))
impl.addBroadcastReceiver(consumer);
methodsWithAnnotations.remove(method.getName());
}
}
}
}
for (String methodName : methodsWithAnnotations.keySet()) {
String annotation = "@"
+ methodsWithAnnotations.get(methodName).getSimpleName();
throw new WrongUseOfAnnotationException("The method "
+ consumer.getClass().getName() + "." + methodName + "(...)"
+ " has the annotation " + annotation
+ " but does not implement an interface that extends "
+ Broadcast.class.getName());
}
}
/* (non-Javadoc)
* @see org.tickcode.broadcast.MessageBroker#add(org.tickcode.broadcast.ErrorHandler)
*/
@Override
public void add(ErrorHandler handler) {
if (!hasErrorHandler(handler)) {
this.errorHandlers.add(new WeakReference(handler));
}
}
/* (non-Javadoc)
* @see org.tickcode.broadcast.MessageBroker#remove(org.tickcode.broadcast.ErrorHandler)
*/
@Override
public void remove(ErrorHandler handler) {
for (WeakReference h : this.errorHandlers) {
if (h.get() != null) {
if (h.get() == handler)
this.errorHandlers.remove(h);
} else
errorHandlers.remove(h);
}
}
/* (non-Javadoc)
* @see org.tickcode.broadcast.MessageBroker#clear()
*/
@Override
public void clear(){
interfacesByMethodName.clear();
errorHandlers.clear();
}
/**
* Used for testing to simulate when the garbage collector runs and causes a
* WeakReferences to be null for a Broadcast implementation
*/
protected void setWeakReferencesToNull(Broadcast consumer) {
for (BroadcastConsumersForAGivenInterface imp : interfacesByMethodName
.values()) {
imp.setWeakReferencesToNull(consumer);
}
}
private boolean hasErrorHandler(ErrorHandler handler) {
for (WeakReference h : this.errorHandlers) {
if (h.get() != null) {
if (h.get() == handler)
return true;
} else
errorHandlers.remove(h);
}
return false;
}
public static boolean isSettingVMMessageBrokerForAll() {
return settingVMMessageBrokerForAll;
}
public static void setSettingVMMessageBrokerForAll(
boolean settingVMMessageBrokerForAll) {
VMMessageBroker.settingVMMessageBrokerForAll = settingVMMessageBrokerForAll;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy