diff --git a/pom.xml b/pom.xml
index 7c20883..ddc0172 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,261 +1,280 @@
-
-
-
- org.sonatype.oss
- oss-parent
- 7
-
-
- 4.0.0
- net.engio
- mbassador
- 1.1.11-SNAPSHOT
- bundle
- mbassador
-
- Mbassador is a fast and flexible message bus system following the publish subscribe pattern.
- It is designed for ease of use and aims to be feature rich and extensible
- while preserving resource efficiency and performance.
-
- It features:
- declarative handler definition via annotations,
- sync and/or async message delivery,
- weak-references,
- message filtering,
- ordering of message handlers etc.
-
-
- https://github.com/bennidi/mbassador
-
-
- MIT license
- http://www.opensource.org/licenses/mit-license.php
-
-
-
- git@github.com:bennidi/mbassador.git
- scm:git:git@github.com:bennidi/mbassador.git
- mbassador-1.1.4
- scm:git:git@github.com:bennidi/mbassador.git
-
-
-
-
- bennidi
- Benjamin Diedrichsen
- +1
- b.diedrichsen@googlemail.com
-
-
-
-
- 2.0.1
- 1.6
- 3.0.1
-
- UTF-8
- 1.6
- file://${project.basedir}/mvn-local-repo
-
-
-
-
-
- junit
- junit
- 4.10
- test
-
-
-
- org.slf4j
- slf4j-api
- 1.7.5
- test
-
-
-
- org.slf4j
- slf4j-log4j12
- 1.7.5
- test
-
-
-
-
-
-
-
-
-
-
-
- org.apache.felix
- maven-bundle-plugin
- 2.3.7
- true
-
-
- ${project.groupId}.${project.artifactId}
- {local-packages}
-
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
-
- ${project.build.java.version}
-
-
-
-
- org.apache.maven.plugins
- maven-release-plugin
- 2.4
-
- forked-path
-
-
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
-
- false
-
-
- AllTests.java
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-source-plugin
-
-
- attach-sources
-
- jar
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
-
-
- attach-javadocs
-
- jar
-
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
- 2.9.1
-
- true
- public
- true
- mbassador, ${project.version}
-
- mbassador, ${project.version}
-
-
-
- org.apache.maven.plugins
- maven-scm-publish-plugin
- 1.0-beta-2
-
- ${project.build.directory}/scmpublish
- Publishing javadoc for ${project.artifactId}:${project.version}
- ${project.reporting.outputDirectory}/apidocs
- true
- scm:git:git@github.com:bennidi/mbassador.git
- gh-pages
-
-
-
-
-
-
-
- release-sign-artifacts
-
-
- performRelease
- true
-
-
-
-
-
- org.apache.maven.plugins
- maven-gpg-plugin
- 1.4
-
-
- sign-artifacts
- verify
-
- sign
-
-
-
-
-
-
-
-
-
+
+
+
+ org.sonatype.oss
+ oss-parent
+ 7
+
+
+ 4.0.0
+ net.engio
+ mbassador
+ 1.1.11-SNAPSHOT
+ bundle
+ mbassador
+
+ Mbassador is a fast and flexible message bus system following the publish subscribe pattern.
+ It is designed for ease of use and aims to be feature rich and extensible
+ while preserving resource efficiency and performance.
+
+ It features:
+ declarative handler definition via annotations,
+ sync and/or async message delivery,
+ weak-references,
+ message filtering,
+ ordering of message handlers etc.
+
+
+ https://github.com/bennidi/mbassador
+
+
+ MIT license
+ http://www.opensource.org/licenses/mit-license.php
+
+
+
+ git@github.com:bennidi/mbassador.git
+ scm:git:git@github.com:bennidi/mbassador.git
+ mbassador-1.1.4
+ scm:git:git@github.com:bennidi/mbassador.git
+
+
+
+
+ bennidi
+ Benjamin Diedrichsen
+ +1
+ b.diedrichsen@googlemail.com
+
+
+
+
+ 2.0.1
+ 1.6
+ 3.0.1
+
+ UTF-8
+ 1.6
+ file://${project.basedir}/mvn-local-repo
+
+
+
+
+
+ junit
+ junit
+ 4.10
+ test
+
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.5
+ test
+
+
+
+ org.slf4j
+ slf4j-log4j12
+ 1.7.5
+ test
+
+
+
+ javax.el
+ el-api
+ 2.2
+
+
+ de.odysseus.juel
+ juel-impl
+ 2.2.7
+ runtime
+ true
+
+
+ de.odysseus.juel
+ juel-spi
+ 2.2.7
+ runtime
+ true
+
+
+
+
+
+
+
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+ 2.3.7
+ true
+
+
+ ${project.groupId}.${project.artifactId}
+ {local-packages}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ ${project.build.java.version}
+
+
+
+
+ org.apache.maven.plugins
+ maven-release-plugin
+ 2.4
+
+ forked-path
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ false
+
+
+ AllTests.java
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+
+
+ attach-sources
+
+ jar
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 2.9.1
+
+ true
+ public
+ true
+ mbassador, ${project.version}
+
+ mbassador, ${project.version}
+
+
+
+ org.apache.maven.plugins
+ maven-scm-publish-plugin
+ 1.0-beta-2
+
+ ${project.build.directory}/scmpublish
+ Publishing javadoc for ${project.artifactId}:${project.version}
+ ${project.reporting.outputDirectory}/apidocs
+ true
+ scm:git:git@github.com:bennidi/mbassador.git
+ gh-pages
+
+
+
+
+
+
+
+ release-sign-artifacts
+
+
+ performRelease
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ 1.4
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/net/engio/mbassy/dispatch/FilteredMessageDispatcher.java b/src/main/java/net/engio/mbassy/dispatch/FilteredMessageDispatcher.java
index 0e4bcc7..89efa9c 100644
--- a/src/main/java/net/engio/mbassy/dispatch/FilteredMessageDispatcher.java
+++ b/src/main/java/net/engio/mbassy/dispatch/FilteredMessageDispatcher.java
@@ -1,45 +1,57 @@
-package net.engio.mbassy.dispatch;
-
-import net.engio.mbassy.bus.MessagePublication;
-import net.engio.mbassy.listener.IMessageFilter;
-
-/**
- * A dispatcher that implements message filtering based on the filter configuration
- * of the associated message handler. It will delegate message delivery to another
- * message dispatcher after having performed the filtering logic.
- *
- * @author bennidi
- * Date: 11/23/12
- */
-public class FilteredMessageDispatcher extends DelegatingMessageDispatcher {
-
- private final IMessageFilter[] filter;
-
- public FilteredMessageDispatcher(IMessageDispatcher dispatcher) {
- super(dispatcher);
- this.filter = dispatcher.getContext().getHandlerMetadata().getFilter();
- }
-
- private boolean passesFilter(Object message) {
-
- if (filter == null) {
- return true;
- } else {
- for (IMessageFilter aFilter : filter) {
- if (!aFilter.accepts(message, getContext().getHandlerMetadata())) {
- return false;
- }
- }
- return true;
- }
- }
-
-
- @Override
- public void dispatch(MessagePublication publication, Object message, Iterable listeners){
- if (passesFilter(message)) {
- getDelegate().dispatch(publication, message, listeners);
- }
- }
-
-}
+package net.engio.mbassy.dispatch;
+
+import net.engio.mbassy.bus.MessagePublication;
+import net.engio.mbassy.dispatch.el.ElFilter;
+import net.engio.mbassy.listener.IMessageFilter;
+
+/**
+ * A dispatcher that implements message filtering based on the filter configuration
+ * of the associated message handler. It will delegate message delivery to another
+ * message dispatcher after having performed the filtering logic.
+ *
+ * @author bennidi
+ * Date: 11/23/12
+ */
+public class FilteredMessageDispatcher extends DelegatingMessageDispatcher {
+
+ private final IMessageFilter[] filter;
+
+ public FilteredMessageDispatcher(IMessageDispatcher dispatcher) {
+ super(dispatcher);
+ this.filter = dispatcher.getContext().getHandlerMetadata().getFilter();
+ }
+
+ private boolean passesFilter(Object message) {
+
+ if (filter == null) {
+ return true;
+ } else {
+ for (IMessageFilter aFilter : filter) {
+ if (!aFilter.accepts(message, getContext().getHandlerMetadata())) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+
+ @Override
+ public void dispatch(MessagePublication publication, Object message, Iterable listeners){
+ if (passesFilter(message) && passesELFilter(message)) {
+ getDelegate().dispatch(publication, message, listeners);
+ }
+ }
+
+ /*************************************************************************
+ * This will test the EL expression defined on the Handler annotation.
+ * This is like a "parameterizable" filter.
+ * @param me the message object to filter with the EL expression if there is one.
+ * @return true if the event is allowed, false if it is rejected.
+ ************************************************************************/
+
+ private boolean passesELFilter(Object message) {
+ ElFilter filter = ElFilter.getInstance();
+ return filter != null && filter.accepts(message, getContext().getHandlerMetadata());
+ }
+}
diff --git a/src/main/java/net/engio/mbassy/dispatch/el/ElFilter.java b/src/main/java/net/engio/mbassy/dispatch/el/ElFilter.java
new file mode 100644
index 0000000..14f9163
--- /dev/null
+++ b/src/main/java/net/engio/mbassy/dispatch/el/ElFilter.java
@@ -0,0 +1,113 @@
+package net.engio.mbassy.dispatch.el;
+
+import javax.el.ExpressionFactory;
+import javax.el.ValueExpression;
+
+import net.engio.mbassy.listener.IMessageFilter;
+import net.engio.mbassy.listener.MessageHandler;
+
+/*****************************************************************************
+ * A filter that will use a expression from the handler annotation and
+ * parse it as EL.
+ ****************************************************************************/
+
+public class ElFilter implements IMessageFilter {
+
+ private static ElFilter instance;
+
+ static {
+ try {
+ instance = new ElFilter();
+ } catch (Exception e) {
+ // Most likely the javax.el package is not available.
+ instance = null;
+ }
+ }
+
+ private ExpressionFactory elFactory;
+
+ /*************************************************************************
+ * Constructor
+ ************************************************************************/
+
+ private ElFilter() {
+ super();
+ initELFactory();
+ }
+
+ /*************************************************************************
+ * Get an implementation of the ExpressionFactory. This uses the
+ * Java service lookup mechanism to find a proper implementation.
+ * If none if available we do not support EL filters.
+ ************************************************************************/
+
+ private void initELFactory() {
+ try {
+ this.elFactory = ExpressionFactory.newInstance();
+ } catch (RuntimeException e) {
+ // No EL implementation on the class path.
+ elFactory = null;
+ }
+ }
+
+ /*************************************************************************
+ * accepts
+ * @see net.engio.mbassy.listener.IMessageFilter#accepts(java.lang.Object, net.engio.mbassy.listener.MessageHandler)
+ ************************************************************************/
+ @Override
+ public boolean accepts(Object message, MessageHandler metadata) {
+ String expression = metadata.getCondition();
+ if (expression == null || expression.trim().length() == 0) {
+ return true;
+ }
+ if (elFactory == null) {
+ // TODO should we test this some where earlier? Perhaps in MessageHandler.validate() ?
+ throw new IllegalStateException("A handler uses an EL filter but no EL implementation is available.");
+ }
+
+ expression = cleanupExpression(expression);
+
+ EventContext context = new EventContext();
+ context.bindToEvent(message);
+
+ return evalExpression(expression, context);
+ }
+
+ /*************************************************************************
+ * @param expression
+ * @param context
+ * @return
+ ************************************************************************/
+
+ private boolean evalExpression(String expression, EventContext context) {
+ ValueExpression ve = elFactory.createValueExpression(context, expression, Boolean.class);
+ Object result = ve.getValue(context);
+ if (!(result instanceof Boolean)) {
+ throw new IllegalStateException("A handler uses an EL filter but the output is not \"true\" or \"false\".");
+ }
+ return (Boolean)result;
+ }
+
+ /*************************************************************************
+ * Make it a valid expression because the parser expects it like this.
+ * @param expression
+ * @return
+ ************************************************************************/
+
+ private String cleanupExpression(String expression) {
+
+ if (!expression.trim().startsWith("${") && !expression.trim().startsWith("#{")) {
+ expression = "${"+expression+"}";
+ }
+ return expression;
+ }
+
+ /*************************************************************************
+ * @return the one and only
+ ************************************************************************/
+
+ public static synchronized ElFilter getInstance() {
+ return instance;
+ }
+
+}
diff --git a/src/main/java/net/engio/mbassy/dispatch/el/EventContext.java b/src/main/java/net/engio/mbassy/dispatch/el/EventContext.java
new file mode 100644
index 0000000..41adb2c
--- /dev/null
+++ b/src/main/java/net/engio/mbassy/dispatch/el/EventContext.java
@@ -0,0 +1,102 @@
+package net.engio.mbassy.dispatch.el;
+
+import java.lang.reflect.Method;
+
+import javax.el.BeanELResolver;
+import javax.el.CompositeELResolver;
+import javax.el.ELContext;
+import javax.el.ELResolver;
+import javax.el.FunctionMapper;
+import javax.el.ValueExpression;
+import javax.el.VariableMapper;
+
+/*****************************************************************************
+ * An EL context that knows how to resolve everything from a
+ * given message but event.
+ ****************************************************************************/
+
+public class EventContext extends ELContext {
+
+ private final CompositeELResolver resolver;
+ private final FunctionMapper functionMapper;
+ private final VariableMapper variableMapper;
+ private RootResolver rootResolver;
+
+ /*************************************************************************
+ * Constructor
+ *
+ * @param me
+ ************************************************************************/
+
+ public EventContext() {
+ super();
+ this.functionMapper = new NoopFunctionMapper();
+ this.variableMapper = new NoopMapperImpl();
+
+ this.resolver = new CompositeELResolver();
+ this.rootResolver = new RootResolver();
+ this.resolver.add(rootResolver);
+ this.resolver.add(new BeanELResolver(true));
+ }
+
+ /*************************************************************************
+ * Binds an event object with the EL expression. This will allow access
+ * to all properties of a given event.
+ * @param event to bind.
+ ************************************************************************/
+
+ public void bindToEvent(Object event) {
+ this.rootResolver.setRoot(event);
+ }
+
+ /*************************************************************************
+ * The resolver for the event object.
+ * @see javax.el.ELContext#getELResolver()
+ ************************************************************************/
+ @Override
+ public ELResolver getELResolver() {
+ return this.resolver;
+ }
+
+ /*************************************************************************
+ * @see javax.el.ELContext#getFunctionMapper()
+ ************************************************************************/
+ @Override
+ public FunctionMapper getFunctionMapper() {
+ return this.functionMapper;
+ }
+
+ /*************************************************************************
+ * @see javax.el.ELContext#getVariableMapper()
+ ************************************************************************/
+ @Override
+ public VariableMapper getVariableMapper() {
+ return this.variableMapper;
+ }
+
+ /*****************************************************************************
+ * Dummy implementation.
+ ****************************************************************************/
+
+ private class NoopMapperImpl extends VariableMapper {
+ public ValueExpression resolveVariable(String s) {
+ return null;
+ }
+
+ public ValueExpression setVariable(String s,
+ ValueExpression valueExpression) {
+ return null;
+ }
+ }
+
+ /*****************************************************************************
+ * Dummy implementation.
+ ****************************************************************************/
+
+ private class NoopFunctionMapper extends FunctionMapper {
+ public Method resolveFunction(String s, String s1) {
+ return null;
+ }
+ }
+
+}
diff --git a/src/main/java/net/engio/mbassy/dispatch/el/RootResolver.java b/src/main/java/net/engio/mbassy/dispatch/el/RootResolver.java
new file mode 100644
index 0000000..c9d6fdb
--- /dev/null
+++ b/src/main/java/net/engio/mbassy/dispatch/el/RootResolver.java
@@ -0,0 +1,89 @@
+package net.engio.mbassy.dispatch.el;
+
+import java.beans.FeatureDescriptor;
+import java.util.Iterator;
+
+import javax.el.ELContext;
+import javax.el.ELResolver;
+
+/*****************************************************************************
+ * A resolver that will resolve the "msg" variable to the event object that
+ * is posted.
+ ****************************************************************************/
+
+public class RootResolver extends ELResolver {
+
+ private static final String ROOT_VAR_NAME = "msg";
+ public Object rootObject;
+
+ /*************************************************************************
+ * @param rootObject
+ ************************************************************************/
+
+ public void setRoot(Object rootObject) {
+ this.rootObject = rootObject;
+ }
+
+ /*************************************************************************
+ * getValue
+ * @see javax.el.ELResolver#getValue(javax.el.ELContext, java.lang.Object, java.lang.Object)
+ ************************************************************************/
+ @Override
+ public Object getValue(ELContext context, Object base, Object property) {
+ if (context == null) {
+ throw new NullPointerException();
+ }
+ if (base == null && ROOT_VAR_NAME.equals(property)) {
+ context.setPropertyResolved(true);
+ return this.rootObject;
+ }
+ return null;
+ }
+
+
+ /*************************************************************************
+ * getCommonPropertyType
+ * @see javax.el.ELResolver#getCommonPropertyType(javax.el.ELContext, java.lang.Object)
+ ************************************************************************/
+ @Override
+ public Class> getCommonPropertyType(ELContext context, Object base) {
+ return String.class;
+ }
+
+ /*************************************************************************
+ * getFeatureDescriptors
+ * @see javax.el.ELResolver#getFeatureDescriptors(javax.el.ELContext, java.lang.Object)
+ ************************************************************************/
+ @Override
+ public Iterator getFeatureDescriptors(ELContext context, Object base) {
+ return null;
+ }
+
+ /*************************************************************************
+ * getType
+ * @see javax.el.ELResolver#getType(javax.el.ELContext, java.lang.Object, java.lang.Object)
+ ************************************************************************/
+ @Override
+ public Class> getType(ELContext context, Object base, Object property) {
+ return null;
+ }
+
+ /*************************************************************************
+ * isReadOnly
+ * @see javax.el.ELResolver#isReadOnly(javax.el.ELContext, java.lang.Object, java.lang.Object)
+ ************************************************************************/
+ @Override
+ public boolean isReadOnly(ELContext context, Object base, Object property) {
+ return true;
+ }
+
+ /*************************************************************************
+ * setValue
+ * @see javax.el.ELResolver#setValue(javax.el.ELContext, java.lang.Object, java.lang.Object, java.lang.Object)
+ ************************************************************************/
+ @Override
+ public void setValue(ELContext context, Object base, Object property, Object value) {
+ // Do nothing
+ }
+
+}
diff --git a/src/main/java/net/engio/mbassy/listener/Handler.java b/src/main/java/net/engio/mbassy/listener/Handler.java
index bfceecd..785e33c 100644
--- a/src/main/java/net/engio/mbassy/listener/Handler.java
+++ b/src/main/java/net/engio/mbassy/listener/Handler.java
@@ -1,66 +1,76 @@
-package net.engio.mbassy.listener;
-
-import net.engio.mbassy.dispatch.HandlerInvocation;
-import net.engio.mbassy.dispatch.ReflectiveHandlerInvocation;
-
-import java.lang.annotation.*;
-
-/**
- * Mark any method of any class(=listener) as a message handler and configure the handler
- * using different properties.
- *
- * @author bennidi
- * Date: 2/8/12
- */
-@Retention(value = RetentionPolicy.RUNTIME)
-@Inherited
-@Target(value = {ElementType.METHOD})
-public @interface Handler {
-
- /**
- * Add any numbers of filters to the handler. All filters are evaluated before the handler
- * is actually invoked, which is only if all the filters accept the message.
- */
- Filter[] filters() default {};
-
- /**
- * Define the mode in which a message is delivered to each listener. Listeners can be notified
- * sequentially or concurrently.
- */
- Invoke delivery() default Invoke.Synchronously;
-
- /**
- * Handlers are ordered by priority and handlers with higher priority are processed before
- * those with lower priority, i.e. Influence the order in which different handlers that consume
- * the same message type are invoked.
- */
- int priority() default 0;
-
- /**
- * Define whether or not the handler accepts sub types of the message type it declares in its
- * signature.
- */
- boolean rejectSubtypes() default false;
-
-
- /**
- * Enable or disable the handler. Disabled handlers do not receive any messages.
- * This property is useful for quick changes in configuration and necessary to disable
- * handlers that have been declared by a superclass but do not apply to the subclass
- */
- boolean enabled() default true;
-
-
- /**
- * Each handler call is implemented as an invocation object that implements the invocation mechanism.
- * The basic implementation uses reflection and is the default. It is possible though to provide a custom
- * invocation to add additional logic.
- *
- * Note: Providing a custom invocation will most likely reduce performance, since the JIT-Compiler
- * can not do some of its sophisticated byte code optimizations.
- *
- */
- Class extends HandlerInvocation> invocation() default ReflectiveHandlerInvocation.class;
-
-
-}
+package net.engio.mbassy.listener;
+
+import net.engio.mbassy.dispatch.HandlerInvocation;
+import net.engio.mbassy.dispatch.ReflectiveHandlerInvocation;
+
+import java.lang.annotation.*;
+
+/**
+ * Mark any method of any class(=listener) as a message handler and configure the handler
+ * using different properties.
+ *
+ * @author bennidi
+ * Date: 2/8/12
+ */
+@Retention(value = RetentionPolicy.RUNTIME)
+@Inherited
+@Target(value = {ElementType.METHOD})
+public @interface Handler {
+
+ /**
+ * Add any numbers of filters to the handler. All filters are evaluated before the handler
+ * is actually invoked, which is only if all the filters accept the message.
+ */
+ Filter[] filters() default {};
+
+
+ /**
+ * Defines a filter condition as Expression Language. This can be used to filter the events based on
+ * attributes of the event object. Note that the expression must resolve to either
+ * true
to allow the event or false
to block it from delivery to the handler.
+ * The message itself is available as "msg" variable.
+ * @return the condition in EL syntax.
+ */
+ String condition() default "";
+
+ /**
+ * Define the mode in which a message is delivered to each listener. Listeners can be notified
+ * sequentially or concurrently.
+ */
+ Invoke delivery() default Invoke.Synchronously;
+
+ /**
+ * Handlers are ordered by priority and handlers with higher priority are processed before
+ * those with lower priority, i.e. Influence the order in which different handlers that consume
+ * the same message type are invoked.
+ */
+ int priority() default 0;
+
+ /**
+ * Define whether or not the handler accepts sub types of the message type it declares in its
+ * signature.
+ */
+ boolean rejectSubtypes() default false;
+
+
+ /**
+ * Enable or disable the handler. Disabled handlers do not receive any messages.
+ * This property is useful for quick changes in configuration and necessary to disable
+ * handlers that have been declared by a superclass but do not apply to the subclass
+ */
+ boolean enabled() default true;
+
+
+ /**
+ * Each handler call is implemented as an invocation object that implements the invocation mechanism.
+ * The basic implementation uses reflection and is the default. It is possible though to provide a custom
+ * invocation to add additional logic.
+ *
+ * Note: Providing a custom invocation will most likely reduce performance, since the JIT-Compiler
+ * can not do some of its sophisticated byte code optimizations.
+ *
+ */
+ Class extends HandlerInvocation> invocation() default ReflectiveHandlerInvocation.class;
+
+
+}
diff --git a/src/main/java/net/engio/mbassy/listener/MessageHandler.java b/src/main/java/net/engio/mbassy/listener/MessageHandler.java
index 4ce8b39..4008273 100644
--- a/src/main/java/net/engio/mbassy/listener/MessageHandler.java
+++ b/src/main/java/net/engio/mbassy/listener/MessageHandler.java
@@ -1,183 +1,194 @@
-package net.engio.mbassy.listener;
-
-import net.engio.mbassy.dispatch.HandlerInvocation;
-
-import java.lang.reflect.Method;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Any method in any class annotated with the @Handler annotation represents a message handler. The class that contains
- * the handler is called a message listener and more generally, any class containing a message handler in its class hierarchy
- * defines such a message listener.
- *
- * @author bennidi
- * Date: 11/14/12
- */
-public class MessageHandler {
-
- public static final class Properties{
-
- public static final String HandlerMethod = "handler";
- public static final String InvocationMode = "invocationMode";
- public static final String Filter = "filter";
- public static final String Enveloped = "envelope";
- public static final String HandledMessages = "messages";
- public static final String IsSynchronized = "synchronized";
- public static final String Listener = "listener";
- public static final String AcceptSubtypes = "subtypes";
- public static final String Priority = "priority";
- public static final String Invocation = "invocation";
-
- /**
- * Create the property map for the {@link MessageHandler} constructor using the default objects.
- *
- * @param handler The handler annotated method of the listener
- * @param handlerConfig The annotation that configures the handler
- * @param filter The set of preconfigured filters if any
- * @param listenerConfig The listener metadata
- * @return A map of properties initialized from the given parameters that will conform to the requirements of the
- * {@link MessageHandler} constructor. See {@see MessageHandler.validate()} for more details.
- */
- public static final Map Create(Method handler, Handler handlerConfig, IMessageFilter[] filter, MessageListener listenerConfig){
- if(handler == null){
- throw new IllegalArgumentException("The message handler configuration may not be null");
- }
- net.engio.mbassy.listener.Enveloped enveloped = handler.getAnnotation(Enveloped.class);
- Class[] handledMessages = enveloped != null
- ? enveloped.messages()
- : handler.getParameterTypes();
- handler.setAccessible(true);
- Map properties = new HashMap();
- properties.put(HandlerMethod, handler);
- properties.put(Filter, filter != null ? filter : new IMessageFilter[]{});
- properties.put(Priority, handlerConfig.priority());
- properties.put(Invocation, handlerConfig.invocation());
- properties.put(InvocationMode, handlerConfig.delivery());
- properties.put(Enveloped, enveloped != null);
- properties.put(AcceptSubtypes, !handlerConfig.rejectSubtypes());
- properties.put(Listener, listenerConfig);
- properties.put(IsSynchronized, handler.getAnnotation(Synchronized.class) != null);
- properties.put(HandledMessages, handledMessages);
- return properties;
- }
- }
-
-
- private final Method handler;
-
- private final IMessageFilter[] filter;
-
- private final int priority;
-
- private final Class extends HandlerInvocation> invocation;
-
- private final Invoke invocationMode;
-
- private final boolean isEnvelope;
-
- private final Class[] handledMessages;
-
- private final boolean acceptsSubtypes;
-
- private final MessageListener listenerConfig;
-
- private final boolean isSynchronized;
-
- public MessageHandler(Map properties){
- super();
- validate(properties);
- this.handler = (Method)properties.get(Properties.HandlerMethod);
- this.filter = (IMessageFilter[])properties.get(Properties.Filter);
- this.priority = (Integer)properties.get(Properties.Priority);
- this.invocation = (Class extends HandlerInvocation>)properties.get(Properties.Invocation);
- this.invocationMode = (Invoke)properties.get(Properties.InvocationMode);
- this.isEnvelope = (Boolean)properties.get(Properties.Enveloped);
- this.acceptsSubtypes = (Boolean)properties.get(Properties.AcceptSubtypes);
- this.listenerConfig = (MessageListener)properties.get(Properties.Listener);
- this.isSynchronized = (Boolean)properties.get(Properties.IsSynchronized);
- this.handledMessages = (Class[])properties.get(Properties.HandledMessages);
- }
-
- private void validate(Map properties){
- Object[][] expectedProperties = new Object[][]{
- new Object[]{Properties.HandlerMethod, Method.class },
- new Object[]{Properties.Priority, Integer.class },
- new Object[]{Properties.Invocation, Class.class },
- new Object[]{Properties.Filter, IMessageFilter[].class },
- new Object[]{Properties.Enveloped, Boolean.class },
- new Object[]{Properties.HandledMessages, Class[].class },
- new Object[]{Properties.IsSynchronized, Boolean.class },
- new Object[]{Properties.Listener, MessageListener.class },
- new Object[]{Properties.AcceptSubtypes, Boolean.class }
- };
- for(Object[] property : expectedProperties){
- if (properties.get(property[0]) == null || !((Class)property[1]).isAssignableFrom(properties.get(property[0]).getClass()))
- throw new IllegalArgumentException("Property " + property[0] + " was expected to be not null and of type " + property[1]
- + " but was: " + properties.get(property[0]));
- }
-
-
- }
-
- public boolean isSynchronized(){
- return isSynchronized;
- }
-
- public boolean useStrongReferences(){
- return listenerConfig.useStrongReferences();
- }
-
- public boolean isFromListener(Class listener){
- return listenerConfig.isFromListener(listener);
- }
-
- public boolean isAsynchronous() {
- return invocationMode.equals(Invoke.Asynchronously);
- }
-
- public boolean isFiltered() {
- return filter.length > 0;
- }
-
- public int getPriority() {
- return priority;
- }
-
- public Method getHandler() {
- return handler;
- }
-
- public IMessageFilter[] getFilter() {
- return filter;
- }
-
- public Class[] getHandledMessages() {
- return handledMessages;
- }
-
- public boolean isEnveloped() {
- return isEnvelope;
- }
-
- public Class extends HandlerInvocation> getHandlerInvocation(){
- return invocation;
- }
-
- public boolean handlesMessage(Class> messageType) {
- for (Class> handledMessage : handledMessages) {
- if (handledMessage.equals(messageType)) {
- return true;
- }
- if (handledMessage.isAssignableFrom(messageType) && acceptsSubtypes()) {
- return true;
- }
- }
- return false;
- }
-
- public boolean acceptsSubtypes() {
- return acceptsSubtypes;
- }
-
-}
+package net.engio.mbassy.listener;
+
+import net.engio.mbassy.dispatch.HandlerInvocation;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Any method in any class annotated with the @Handler annotation represents a message handler. The class that contains
+ * the handler is called a message listener and more generally, any class containing a message handler in its class hierarchy
+ * defines such a message listener.
+ *
+ * @author bennidi
+ * Date: 11/14/12
+ */
+public class MessageHandler {
+
+ public static final class Properties{
+
+ public static final String HandlerMethod = "handler";
+ public static final String InvocationMode = "invocationMode";
+ public static final String Filter = "filter";
+ public static final String Condition = "condition";
+ public static final String Enveloped = "envelope";
+ public static final String HandledMessages = "messages";
+ public static final String IsSynchronized = "synchronized";
+ public static final String Listener = "listener";
+ public static final String AcceptSubtypes = "subtypes";
+ public static final String Priority = "priority";
+ public static final String Invocation = "invocation";
+
+ /**
+ * Create the property map for the {@link MessageHandler} constructor using the default objects.
+ *
+ * @param handler The handler annotated method of the listener
+ * @param handlerConfig The annotation that configures the handler
+ * @param filter The set of preconfigured filters if any
+ * @param listenerConfig The listener metadata
+ * @return A map of properties initialized from the given parameters that will conform to the requirements of the
+ * {@link MessageHandler} constructor. See {@see MessageHandler.validate()} for more details.
+ */
+ public static final Map Create(Method handler, Handler handlerConfig, IMessageFilter[] filter, MessageListener listenerConfig){
+ if(handler == null){
+ throw new IllegalArgumentException("The message handler configuration may not be null");
+ }
+ net.engio.mbassy.listener.Enveloped enveloped = handler.getAnnotation(Enveloped.class);
+ Class[] handledMessages = enveloped != null
+ ? enveloped.messages()
+ : handler.getParameterTypes();
+ handler.setAccessible(true);
+ Map properties = new HashMap();
+ properties.put(HandlerMethod, handler);
+ properties.put(Filter, filter != null ? filter : new IMessageFilter[]{});
+ properties.put(Condition, handlerConfig.condition());
+ properties.put(Priority, handlerConfig.priority());
+ properties.put(Invocation, handlerConfig.invocation());
+ properties.put(InvocationMode, handlerConfig.delivery());
+ properties.put(Enveloped, enveloped != null);
+ properties.put(AcceptSubtypes, !handlerConfig.rejectSubtypes());
+ properties.put(Listener, listenerConfig);
+ properties.put(IsSynchronized, handler.getAnnotation(Synchronized.class) != null);
+ properties.put(HandledMessages, handledMessages);
+ return properties;
+ }
+ }
+
+
+ private final Method handler;
+
+ private final IMessageFilter[] filter;
+
+ private String condition;
+
+ private final int priority;
+
+ private final Class extends HandlerInvocation> invocation;
+
+ private final Invoke invocationMode;
+
+ private final boolean isEnvelope;
+
+ private final Class[] handledMessages;
+
+ private final boolean acceptsSubtypes;
+
+ private final MessageListener listenerConfig;
+
+ private final boolean isSynchronized;
+
+
+ public MessageHandler(Map properties){
+ super();
+ validate(properties);
+ this.handler = (Method)properties.get(Properties.HandlerMethod);
+ this.filter = (IMessageFilter[])properties.get(Properties.Filter);
+ this.condition = (String)properties.get(Properties.Condition);
+ this.priority = (Integer)properties.get(Properties.Priority);
+ this.invocation = (Class extends HandlerInvocation>)properties.get(Properties.Invocation);
+ this.invocationMode = (Invoke)properties.get(Properties.InvocationMode);
+ this.isEnvelope = (Boolean)properties.get(Properties.Enveloped);
+ this.acceptsSubtypes = (Boolean)properties.get(Properties.AcceptSubtypes);
+ this.listenerConfig = (MessageListener)properties.get(Properties.Listener);
+ this.isSynchronized = (Boolean)properties.get(Properties.IsSynchronized);
+ this.handledMessages = (Class[])properties.get(Properties.HandledMessages);
+ }
+
+ private void validate(Map properties){
+ Object[][] expectedProperties = new Object[][]{
+ new Object[]{Properties.HandlerMethod, Method.class },
+ new Object[]{Properties.Priority, Integer.class },
+ new Object[]{Properties.Invocation, Class.class },
+ new Object[]{Properties.Filter, IMessageFilter[].class },
+ new Object[]{Properties.Condition, String.class },
+ new Object[]{Properties.Enveloped, Boolean.class },
+ new Object[]{Properties.HandledMessages, Class[].class },
+ new Object[]{Properties.IsSynchronized, Boolean.class },
+ new Object[]{Properties.Listener, MessageListener.class },
+ new Object[]{Properties.AcceptSubtypes, Boolean.class }
+ };
+ for(Object[] property : expectedProperties){
+ if (properties.get(property[0]) == null || !((Class)property[1]).isAssignableFrom(properties.get(property[0]).getClass()))
+ throw new IllegalArgumentException("Property " + property[0] + " was expected to be not null and of type " + property[1]
+ + " but was: " + properties.get(property[0]));
+ }
+
+
+ }
+
+ public boolean isSynchronized(){
+ return isSynchronized;
+ }
+
+ public boolean useStrongReferences(){
+ return listenerConfig.useStrongReferences();
+ }
+
+ public boolean isFromListener(Class listener){
+ return listenerConfig.isFromListener(listener);
+ }
+
+ public boolean isAsynchronous() {
+ return invocationMode.equals(Invoke.Asynchronously);
+ }
+
+ public boolean isFiltered() {
+ return filter.length > 0 || (condition != null && condition.trim().length() > 0);
+ }
+
+ public int getPriority() {
+ return priority;
+ }
+
+ public Method getHandler() {
+ return handler;
+ }
+
+ public IMessageFilter[] getFilter() {
+ return filter;
+ }
+
+ public String getCondition() {
+ return this.condition;
+ }
+
+ public Class[] getHandledMessages() {
+ return handledMessages;
+ }
+
+ public boolean isEnveloped() {
+ return isEnvelope;
+ }
+
+ public Class extends HandlerInvocation> getHandlerInvocation(){
+ return invocation;
+ }
+
+ public boolean handlesMessage(Class> messageType) {
+ for (Class> handledMessage : handledMessages) {
+ if (handledMessage.equals(messageType)) {
+ return true;
+ }
+ if (handledMessage.isAssignableFrom(messageType) && acceptsSubtypes()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean acceptsSubtypes() {
+ return acceptsSubtypes;
+ }
+
+}
diff --git a/src/test/java/net/engio/mbassy/ConditionTest.java b/src/test/java/net/engio/mbassy/ConditionTest.java
new file mode 100644
index 0000000..333642b
--- /dev/null
+++ b/src/test/java/net/engio/mbassy/ConditionTest.java
@@ -0,0 +1,133 @@
+package net.engio.mbassy;
+
+import net.engio.mbassy.bus.MBassador;
+import net.engio.mbassy.bus.config.BusConfiguration;
+import net.engio.mbassy.common.MessageBusTest;
+import net.engio.mbassy.listener.Handler;
+
+import org.junit.Test;
+
+/*****************************************************************************
+ * Some unit tests for the "condition" filter.
+ ****************************************************************************/
+
+public class ConditionTest extends MessageBusTest {
+
+ public static class TestEvent {
+
+ public Object result;
+ private String type;
+ private int size;
+
+ public TestEvent(String type, int size) {
+ super();
+ this.type = type;
+ this.size = size;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public int getSize() {
+ return size;
+ }
+
+ }
+
+ public static class ConditionalMessageListener {
+
+ @Handler(condition = "msg.type == 'TEST'")
+ public void handleTypeMessage(TestEvent message) {
+ message.result = "handleTypeMessage";
+ }
+
+ @Handler(condition = "msg.size > 4")
+ public void handleSizeMessage(TestEvent message) {
+ message.result = "handleSizeMessage";
+ }
+
+ @Handler(condition = "msg.size > 2 && msg.size < 4")
+ public void handleCombinedEL(TestEvent message) {
+ message.result = "handleCombinedEL";
+ }
+
+ @Handler(condition = "msg.getType().equals('XYZ') && msg.getSize() == 1")
+ public void handleMethodAccessEL(TestEvent message) {
+ message.result = "handleMethodAccessEL";
+ }
+
+
+ }
+
+ /*************************************************************************
+ * @throws Exception
+ ************************************************************************/
+ @Test
+ public void testSimpleStringCondition() throws Exception {
+ MBassador bus = getBus(BusConfiguration.Default());
+ bus.subscribe(new ConditionalMessageListener());
+
+ TestEvent message = new TestEvent("TEST", 0);
+ bus.publish(message);
+
+ assertEquals("handleTypeMessage", message.result);
+ }
+
+ /*************************************************************************
+ * @throws Exception
+ ************************************************************************/
+ @Test
+ public void testSimpleNumberCondition() throws Exception {
+ MBassador bus = getBus(BusConfiguration.Default());
+ bus.subscribe(new ConditionalMessageListener());
+
+ TestEvent message = new TestEvent("", 5);
+ bus.publish(message);
+
+ assertEquals("handleSizeMessage", message.result);
+ }
+
+ /*************************************************************************
+ * @throws Exception
+ ************************************************************************/
+ @Test
+ public void testHandleCombinedEL() throws Exception {
+ MBassador bus = getBus(BusConfiguration.Default());
+ bus.subscribe(new ConditionalMessageListener());
+
+ TestEvent message = new TestEvent("", 3);
+ bus.publish(message);
+
+ assertEquals("handleCombinedEL", message.result);
+ }
+
+ /*************************************************************************
+ * @throws Exception
+ ************************************************************************/
+ @Test
+ public void testNotMatchingAnyCondition() throws Exception {
+ MBassador bus = getBus(BusConfiguration.Default());
+ bus.subscribe(new ConditionalMessageListener());
+
+ TestEvent message = new TestEvent("", 0);
+ bus.publish(message);
+
+ assertTrue(message.result == null);
+ }
+
+ /*************************************************************************
+ * @throws Exception
+ ************************************************************************/
+ @Test
+ public void testHandleMethodAccessEL() throws Exception {
+ MBassador bus = getBus(BusConfiguration.Default());
+ bus.subscribe(new ConditionalMessageListener());
+
+ TestEvent message = new TestEvent("XYZ", 1);
+ bus.publish(message);
+
+ assertEquals("handleMethodAccessEL", message.result);
+ }
+
+}