Merge pull request #70 from Rossi1337/master

Support Message filtering per Expression Language
This commit is contained in:
Benjamin Diedrichsen 2014-05-22 10:01:05 +02:00
commit 86bdcad336
8 changed files with 1044 additions and 555 deletions

541
pom.xml
View File

@ -1,261 +1,280 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent> <parent>
<groupId>org.sonatype.oss</groupId> <groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId> <artifactId>oss-parent</artifactId>
<version>7</version> <version>7</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>net.engio</groupId> <groupId>net.engio</groupId>
<artifactId>mbassador</artifactId> <artifactId>mbassador</artifactId>
<version>1.1.11-SNAPSHOT</version> <version>1.1.11-SNAPSHOT</version>
<packaging>bundle</packaging> <packaging>bundle</packaging>
<name>mbassador</name> <name>mbassador</name>
<description> <description>
Mbassador is a fast and flexible message bus system following the publish subscribe pattern. 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 It is designed for ease of use and aims to be feature rich and extensible
while preserving resource efficiency and performance. while preserving resource efficiency and performance.
It features: It features:
declarative handler definition via annotations, declarative handler definition via annotations,
sync and/or async message delivery, sync and/or async message delivery,
weak-references, weak-references,
message filtering, message filtering,
ordering of message handlers etc. ordering of message handlers etc.
</description> </description>
<url>https://github.com/bennidi/mbassador</url> <url>https://github.com/bennidi/mbassador</url>
<licenses> <licenses>
<license> <license>
<name>MIT license</name> <name>MIT license</name>
<url>http://www.opensource.org/licenses/mit-license.php</url> <url>http://www.opensource.org/licenses/mit-license.php</url>
</license> </license>
</licenses> </licenses>
<scm> <scm>
<url>git@github.com:bennidi/mbassador.git</url> <url>git@github.com:bennidi/mbassador.git</url>
<connection>scm:git:git@github.com:bennidi/mbassador.git</connection> <connection>scm:git:git@github.com:bennidi/mbassador.git</connection>
<tag>mbassador-1.1.4</tag> <tag>mbassador-1.1.4</tag>
<developerConnection>scm:git:git@github.com:bennidi/mbassador.git</developerConnection> <developerConnection>scm:git:git@github.com:bennidi/mbassador.git</developerConnection>
</scm> </scm>
<developers> <developers>
<developer> <developer>
<id>bennidi</id> <id>bennidi</id>
<name>Benjamin Diedrichsen</name> <name>Benjamin Diedrichsen</name>
<timezone>+1</timezone> <timezone>+1</timezone>
<email>b.diedrichsen@googlemail.com</email> <email>b.diedrichsen@googlemail.com</email>
</developer> </developer>
</developers> </developers>
<properties> <properties>
<nazgul-codestyle.version>2.0.1</nazgul-codestyle.version> <nazgul-codestyle.version>2.0.1</nazgul-codestyle.version>
<jdk.version>1.6</jdk.version> <jdk.version>1.6</jdk.version>
<pmd.plugin.version>3.0.1</pmd.plugin.version> <pmd.plugin.version>3.0.1</pmd.plugin.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.java.version>1.6</project.build.java.version> <project.build.java.version>1.6</project.build.java.version>
<github.url>file://${project.basedir}/mvn-local-repo</github.url> <github.url>file://${project.basedir}/mvn-local-repo</github.url>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<version>4.10</version> <version>4.10</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>
<version>1.7.5</version> <version>1.7.5</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId> <artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version> <version>1.7.5</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> <dependency>
<groupId>javax.el</groupId>
<!-- Local repository (for testing) <artifactId>el-api</artifactId>
<distributionManagement> <version>2.2</version>
<repository> </dependency>
<id>mbassador-github-repo</id> <dependency>
<url>${github.url}</url> <groupId>de.odysseus.juel</groupId>
</repository> <artifactId>juel-impl</artifactId>
</distributionManagement> <version>2.2.7</version>
--> <scope>runtime</scope>
<optional>true</optional>
<build> </dependency>
<plugins> <dependency>
<!-- plugin> <groupId>de.odysseus.juel</groupId>
<groupId>org.apache.maven.plugins</groupId> <artifactId>juel-spi</artifactId>
<artifactId>maven-pmd-plugin</artifactId> <version>2.2.7</version>
<version>${pmd.plugin.version}</version> <scope>runtime</scope>
<configuration> <optional>true</optional>
<excludeRoots> </dependency>
<excludeRoot>src/main/generated</excludeRoot> </dependencies>
<excludeRoot>src/test</excludeRoot>
</excludeRoots> <!-- Local repository (for testing)
<rulesets> <distributionManagement>
<ruleset>/codestyle/pmd-rules.xml</ruleset> <repository>
</rulesets> <id>mbassador-github-repo</id>
<targetJdk>${jdk.version}</targetJdk> <url>${github.url}</url>
<sourceEncoding>${project.build.sourceEncoding}</sourceEncoding> </repository>
</configuration> </distributionManagement>
<executions> -->
<execution>
<goals> <build>
<goal>check</goal> <plugins>
<goal>cpd-check</goal> <!-- plugin>
</goals> <groupId>org.apache.maven.plugins</groupId>
</execution> <artifactId>maven-pmd-plugin</artifactId>
</executions> <version>${pmd.plugin.version}</version>
<dependencies> <configuration>
<dependency> <excludeRoots>
<groupId>se.jguru.nazgul.tools.codestyle</groupId> <excludeRoot>src/main/generated</excludeRoot>
<artifactId>nazgul-codestyle</artifactId> <excludeRoot>src/test</excludeRoot>
<version>${nazgul-codestyle.version}</version> </excludeRoots>
</dependency> <rulesets>
</dependencies> <ruleset>/codestyle/pmd-rules.xml</ruleset>
</plugin --> </rulesets>
<targetJdk>${jdk.version}</targetJdk>
<plugin> <sourceEncoding>${project.build.sourceEncoding}</sourceEncoding>
<groupId>org.apache.felix</groupId> </configuration>
<artifactId>maven-bundle-plugin</artifactId> <executions>
<version>2.3.7</version> <execution>
<extensions>true</extensions> <goals>
<configuration> <goal>check</goal>
<instructions> <goal>cpd-check</goal>
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName> </goals>
<Export-Package>{local-packages}</Export-Package> </execution>
</instructions> </executions>
</configuration> <dependencies>
</plugin> <dependency>
<groupId>se.jguru.nazgul.tools.codestyle</groupId>
<plugin> <artifactId>nazgul-codestyle</artifactId>
<groupId>org.apache.maven.plugins</groupId> <version>${nazgul-codestyle.version}</version>
<artifactId>maven-compiler-plugin</artifactId> </dependency>
<configuration> </dependencies>
<source>${project.build.java.version}</source> </plugin -->
<target>${project.build.java.version}</target>
</configuration> <plugin>
</plugin> <groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<plugin> <version>2.3.7</version>
<groupId>org.apache.maven.plugins</groupId> <extensions>true</extensions>
<artifactId>maven-release-plugin</artifactId> <configuration>
<version>2.4</version> <instructions>
<configuration> <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
<mavenExecutorId>forked-path</mavenExecutorId> <Export-Package>{local-packages}</Export-Package>
</configuration> </instructions>
</plugin> </configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId> <plugin>
<artifactId>maven-surefire-plugin</artifactId> <groupId>org.apache.maven.plugins</groupId>
<configuration> <artifactId>maven-compiler-plugin</artifactId>
<skipTests>false</skipTests> <configuration>
<excludes> <source>${project.build.java.version}</source>
<!-- exclude the suite which is a convenience class for running all tests from IDE or using scripts --> <target>${project.build.java.version}</target>
<exclude>AllTests.java</exclude> </configuration>
</excludes> </plugin>
</configuration>
</plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId>
<!-- bind the source attaching to package phase --> <artifactId>maven-release-plugin</artifactId>
<plugin> <version>2.4</version>
<groupId>org.apache.maven.plugins</groupId> <configuration>
<artifactId>maven-source-plugin</artifactId> <mavenExecutorId>forked-path</mavenExecutorId>
<executions> </configuration>
<execution> </plugin>
<id>attach-sources</id>
<goals> <plugin>
<goal>jar</goal> <groupId>org.apache.maven.plugins</groupId>
</goals> <artifactId>maven-surefire-plugin</artifactId>
</execution> <configuration>
</executions> <skipTests>false</skipTests>
</plugin> <excludes>
<!-- exclude the suite which is a convenience class for running all tests from IDE or using scripts -->
<plugin> <exclude>AllTests.java</exclude>
<groupId>org.apache.maven.plugins</groupId> </excludes>
<artifactId>maven-javadoc-plugin</artifactId> </configuration>
<executions> </plugin>
<execution>
<id>attach-javadocs</id> <!-- bind the source attaching to package phase -->
<goals> <plugin>
<goal>jar</goal> <groupId>org.apache.maven.plugins</groupId>
</goals> <artifactId>maven-source-plugin</artifactId>
</execution> <executions>
</executions> <execution>
</plugin> <id>attach-sources</id>
<goals>
<!-- <goal>jar</goal>
These two plugins take care of building and publishing the javadoc, using </goals>
mvn clean javadoc:javadoc scm-publish:publish-scm </execution>
--> </executions>
<plugin> </plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <plugin>
<version>2.9.1</version> <groupId>org.apache.maven.plugins</groupId>
<configuration> <artifactId>maven-javadoc-plugin</artifactId>
<aggregate>true</aggregate> <executions>
<show>public</show> <execution>
<nohelp>true</nohelp> <id>attach-javadocs</id>
<header>mbassador, ${project.version}</header> <goals>
<footer>mbassador, ${project.version}</footer> <goal>jar</goal>
<doctitle>mbassador, ${project.version}</doctitle> </goals>
</configuration> </execution>
</plugin> </executions>
<plugin> </plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-scm-publish-plugin</artifactId> <!--
<version>1.0-beta-2</version> These two plugins take care of building and publishing the javadoc, using
<configuration> mvn clean javadoc:javadoc scm-publish:publish-scm
<checkoutDirectory>${project.build.directory}/scmpublish</checkoutDirectory> -->
<checkinComment>Publishing javadoc for ${project.artifactId}:${project.version}</checkinComment> <plugin>
<content>${project.reporting.outputDirectory}/apidocs</content> <groupId>org.apache.maven.plugins</groupId>
<skipDeletedFiles>true</skipDeletedFiles> <artifactId>maven-javadoc-plugin</artifactId>
<pubScmUrl>scm:git:git@github.com:bennidi/mbassador.git</pubScmUrl> <version>2.9.1</version>
<scmBranch>gh-pages</scmBranch> <!-- branch with static site on github--> <configuration>
</configuration> <aggregate>true</aggregate>
</plugin> <show>public</show>
</plugins> <nohelp>true</nohelp>
</build> <header>mbassador, ${project.version}</header>
<footer>mbassador, ${project.version}</footer>
<profiles> <doctitle>mbassador, ${project.version}</doctitle>
<profile> </configuration>
<id>release-sign-artifacts</id> </plugin>
<activation> <plugin>
<property> <groupId>org.apache.maven.plugins</groupId>
<name>performRelease</name> <artifactId>maven-scm-publish-plugin</artifactId>
<value>true</value> <version>1.0-beta-2</version>
</property> <configuration>
</activation> <checkoutDirectory>${project.build.directory}/scmpublish</checkoutDirectory>
<build> <checkinComment>Publishing javadoc for ${project.artifactId}:${project.version}</checkinComment>
<plugins> <content>${project.reporting.outputDirectory}/apidocs</content>
<plugin> <skipDeletedFiles>true</skipDeletedFiles>
<groupId>org.apache.maven.plugins</groupId> <pubScmUrl>scm:git:git@github.com:bennidi/mbassador.git</pubScmUrl>
<artifactId>maven-gpg-plugin</artifactId> <scmBranch>gh-pages</scmBranch> <!-- branch with static site on github-->
<version>1.4</version> </configuration>
<executions> </plugin>
<execution> </plugins>
<id>sign-artifacts</id> </build>
<phase>verify</phase>
<goals> <profiles>
<goal>sign</goal> <profile>
</goals> <id>release-sign-artifacts</id>
</execution> <activation>
</executions> <property>
</plugin> <name>performRelease</name>
</plugins> <value>true</value>
</build> </property>
</profile> </activation>
</profiles> <build>
</project> <plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -1,45 +1,57 @@
package net.engio.mbassy.dispatch; package net.engio.mbassy.dispatch;
import net.engio.mbassy.bus.MessagePublication; import net.engio.mbassy.bus.MessagePublication;
import net.engio.mbassy.listener.IMessageFilter; 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 * A dispatcher that implements message filtering based on the filter configuration
* message dispatcher after having performed the filtering logic. * 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 * @author bennidi
*/ * Date: 11/23/12
public class FilteredMessageDispatcher extends DelegatingMessageDispatcher { */
public class FilteredMessageDispatcher extends DelegatingMessageDispatcher {
private final IMessageFilter[] filter;
private final IMessageFilter[] filter;
public FilteredMessageDispatcher(IMessageDispatcher dispatcher) {
super(dispatcher); public FilteredMessageDispatcher(IMessageDispatcher dispatcher) {
this.filter = dispatcher.getContext().getHandlerMetadata().getFilter(); super(dispatcher);
} this.filter = dispatcher.getContext().getHandlerMetadata().getFilter();
}
private boolean passesFilter(Object message) {
private boolean passesFilter(Object message) {
if (filter == null) {
return true; if (filter == null) {
} else { return true;
for (IMessageFilter aFilter : filter) { } else {
if (!aFilter.accepts(message, getContext().getHandlerMetadata())) { for (IMessageFilter aFilter : filter) {
return false; if (!aFilter.accepts(message, getContext().getHandlerMetadata())) {
} return false;
} }
return true; }
} return true;
} }
}
@Override
public void dispatch(MessagePublication publication, Object message, Iterable listeners){ @Override
if (passesFilter(message)) { public void dispatch(MessagePublication publication, Object message, Iterable listeners){
getDelegate().dispatch(publication, message, 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());
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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<FeatureDescriptor> 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
}
}

View File

@ -1,66 +1,76 @@
package net.engio.mbassy.listener; package net.engio.mbassy.listener;
import net.engio.mbassy.dispatch.HandlerInvocation; import net.engio.mbassy.dispatch.HandlerInvocation;
import net.engio.mbassy.dispatch.ReflectiveHandlerInvocation; import net.engio.mbassy.dispatch.ReflectiveHandlerInvocation;
import java.lang.annotation.*; import java.lang.annotation.*;
/** /**
* Mark any method of any class(=listener) as a message handler and configure the handler * Mark any method of any class(=listener) as a message handler and configure the handler
* using different properties. * using different properties.
* *
* @author bennidi * @author bennidi
* Date: 2/8/12 * Date: 2/8/12
*/ */
@Retention(value = RetentionPolicy.RUNTIME) @Retention(value = RetentionPolicy.RUNTIME)
@Inherited @Inherited
@Target(value = {ElementType.METHOD}) @Target(value = {ElementType.METHOD})
public @interface Handler { public @interface Handler {
/** /**
* Add any numbers of filters to the handler. All filters are evaluated before the 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. * is actually invoked, which is only if all the filters accept the message.
*/ */
Filter[] filters() default {}; Filter[] filters() default {};
/**
* Define the mode in which a message is delivered to each listener. Listeners can be notified /**
* sequentially or concurrently. * 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
Invoke delivery() default Invoke.Synchronously; * <code>true</code> to allow the event or <code>false</code> to block it from delivery to the handler.
* The message itself is available as "msg" variable.
/** * @return the condition in EL syntax.
* 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 String condition() default "";
* the same message type are invoked.
*/ /**
int priority() default 0; * Define the mode in which a message is delivered to each listener. Listeners can be notified
* sequentially or concurrently.
/** */
* Define whether or not the handler accepts sub types of the message type it declares in its Invoke delivery() default Invoke.Synchronously;
* signature.
*/ /**
boolean rejectSubtypes() default false; * 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.
/** */
* Enable or disable the handler. Disabled handlers do not receive any messages. int priority() default 0;
* 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 /**
*/ * Define whether or not the handler accepts sub types of the message type it declares in its
boolean enabled() default true; * signature.
*/
boolean rejectSubtypes() default false;
/**
* 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. * 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
* Note: Providing a custom invocation will most likely reduce performance, since the JIT-Compiler * handlers that have been declared by a superclass but do not apply to the subclass
* can not do some of its sophisticated byte code optimizations. */
* boolean enabled() default true;
*/
Class<? extends HandlerInvocation> invocation() default ReflectiveHandlerInvocation.class;
/**
* 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;
}

View File

@ -1,183 +1,194 @@
package net.engio.mbassy.listener; package net.engio.mbassy.listener;
import net.engio.mbassy.dispatch.HandlerInvocation; import net.engio.mbassy.dispatch.HandlerInvocation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
/** /**
* Any method in any class annotated with the @Handler annotation represents a message handler. The class that contains * 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 * 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. * defines such a message listener.
* *
* @author bennidi * @author bennidi
* Date: 11/14/12 * Date: 11/14/12
*/ */
public class MessageHandler { public class MessageHandler {
public static final class Properties{ public static final class Properties{
public static final String HandlerMethod = "handler"; public static final String HandlerMethod = "handler";
public static final String InvocationMode = "invocationMode"; public static final String InvocationMode = "invocationMode";
public static final String Filter = "filter"; public static final String Filter = "filter";
public static final String Enveloped = "envelope"; public static final String Condition = "condition";
public static final String HandledMessages = "messages"; public static final String Enveloped = "envelope";
public static final String IsSynchronized = "synchronized"; public static final String HandledMessages = "messages";
public static final String Listener = "listener"; public static final String IsSynchronized = "synchronized";
public static final String AcceptSubtypes = "subtypes"; public static final String Listener = "listener";
public static final String Priority = "priority"; public static final String AcceptSubtypes = "subtypes";
public static final String Invocation = "invocation"; 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. /**
* * 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 handler The handler annotated method of the listener
* @param filter The set of preconfigured filters if any * @param handlerConfig The annotation that configures the handler
* @param listenerConfig The listener metadata * @param filter The set of preconfigured filters if any
* @return A map of properties initialized from the given parameters that will conform to the requirements of the * @param listenerConfig The listener metadata
* {@link MessageHandler} constructor. See {@see MessageHandler.validate()} for more details. * @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<String, Object> Create(Method handler, Handler handlerConfig, IMessageFilter[] filter, MessageListener listenerConfig){ */
if(handler == null){ public static final Map<String, Object> Create(Method handler, Handler handlerConfig, IMessageFilter[] filter, MessageListener listenerConfig){
throw new IllegalArgumentException("The message handler configuration may not be null"); 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 net.engio.mbassy.listener.Enveloped enveloped = handler.getAnnotation(Enveloped.class);
? enveloped.messages() Class[] handledMessages = enveloped != null
: handler.getParameterTypes(); ? enveloped.messages()
handler.setAccessible(true); : handler.getParameterTypes();
Map<String, Object> properties = new HashMap<String, Object>(); handler.setAccessible(true);
properties.put(HandlerMethod, handler); Map<String, Object> properties = new HashMap<String, Object>();
properties.put(Filter, filter != null ? filter : new IMessageFilter[]{}); properties.put(HandlerMethod, handler);
properties.put(Priority, handlerConfig.priority()); properties.put(Filter, filter != null ? filter : new IMessageFilter[]{});
properties.put(Invocation, handlerConfig.invocation()); properties.put(Condition, handlerConfig.condition());
properties.put(InvocationMode, handlerConfig.delivery()); properties.put(Priority, handlerConfig.priority());
properties.put(Enveloped, enveloped != null); properties.put(Invocation, handlerConfig.invocation());
properties.put(AcceptSubtypes, !handlerConfig.rejectSubtypes()); properties.put(InvocationMode, handlerConfig.delivery());
properties.put(Listener, listenerConfig); properties.put(Enveloped, enveloped != null);
properties.put(IsSynchronized, handler.getAnnotation(Synchronized.class) != null); properties.put(AcceptSubtypes, !handlerConfig.rejectSubtypes());
properties.put(HandledMessages, handledMessages); properties.put(Listener, listenerConfig);
return properties; properties.put(IsSynchronized, handler.getAnnotation(Synchronized.class) != null);
} properties.put(HandledMessages, handledMessages);
} return properties;
}
}
private final Method handler;
private final IMessageFilter[] filter; private final Method handler;
private final int priority; private final IMessageFilter[] filter;
private final Class<? extends HandlerInvocation> invocation; private String condition;
private final Invoke invocationMode; private final int priority;
private final boolean isEnvelope; private final Class<? extends HandlerInvocation> invocation;
private final Class[] handledMessages; private final Invoke invocationMode;
private final boolean acceptsSubtypes; private final boolean isEnvelope;
private final MessageListener listenerConfig; private final Class[] handledMessages;
private final boolean isSynchronized; private final boolean acceptsSubtypes;
public MessageHandler(Map<String, Object> properties){ private final MessageListener listenerConfig;
super();
validate(properties); private final boolean isSynchronized;
this.handler = (Method)properties.get(Properties.HandlerMethod);
this.filter = (IMessageFilter[])properties.get(Properties.Filter);
this.priority = (Integer)properties.get(Properties.Priority); public MessageHandler(Map<String, Object> properties){
this.invocation = (Class<? extends HandlerInvocation>)properties.get(Properties.Invocation); super();
this.invocationMode = (Invoke)properties.get(Properties.InvocationMode); validate(properties);
this.isEnvelope = (Boolean)properties.get(Properties.Enveloped); this.handler = (Method)properties.get(Properties.HandlerMethod);
this.acceptsSubtypes = (Boolean)properties.get(Properties.AcceptSubtypes); this.filter = (IMessageFilter[])properties.get(Properties.Filter);
this.listenerConfig = (MessageListener)properties.get(Properties.Listener); this.condition = (String)properties.get(Properties.Condition);
this.isSynchronized = (Boolean)properties.get(Properties.IsSynchronized); this.priority = (Integer)properties.get(Properties.Priority);
this.handledMessages = (Class[])properties.get(Properties.HandledMessages); this.invocation = (Class<? extends HandlerInvocation>)properties.get(Properties.Invocation);
} this.invocationMode = (Invoke)properties.get(Properties.InvocationMode);
this.isEnvelope = (Boolean)properties.get(Properties.Enveloped);
private void validate(Map<String, Object> properties){ this.acceptsSubtypes = (Boolean)properties.get(Properties.AcceptSubtypes);
Object[][] expectedProperties = new Object[][]{ this.listenerConfig = (MessageListener)properties.get(Properties.Listener);
new Object[]{Properties.HandlerMethod, Method.class }, this.isSynchronized = (Boolean)properties.get(Properties.IsSynchronized);
new Object[]{Properties.Priority, Integer.class }, this.handledMessages = (Class[])properties.get(Properties.HandledMessages);
new Object[]{Properties.Invocation, Class.class }, }
new Object[]{Properties.Filter, IMessageFilter[].class },
new Object[]{Properties.Enveloped, Boolean.class }, private void validate(Map<String, Object> properties){
new Object[]{Properties.HandledMessages, Class[].class }, Object[][] expectedProperties = new Object[][]{
new Object[]{Properties.IsSynchronized, Boolean.class }, new Object[]{Properties.HandlerMethod, Method.class },
new Object[]{Properties.Listener, MessageListener.class }, new Object[]{Properties.Priority, Integer.class },
new Object[]{Properties.AcceptSubtypes, Boolean.class } new Object[]{Properties.Invocation, Class.class },
}; new Object[]{Properties.Filter, IMessageFilter[].class },
for(Object[] property : expectedProperties){ new Object[]{Properties.Condition, String.class },
if (properties.get(property[0]) == null || !((Class)property[1]).isAssignableFrom(properties.get(property[0]).getClass())) new Object[]{Properties.Enveloped, Boolean.class },
throw new IllegalArgumentException("Property " + property[0] + " was expected to be not null and of type " + property[1] new Object[]{Properties.HandledMessages, Class[].class },
+ " but was: " + properties.get(property[0])); 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()))
public boolean isSynchronized(){ throw new IllegalArgumentException("Property " + property[0] + " was expected to be not null and of type " + property[1]
return isSynchronized; + " but was: " + properties.get(property[0]));
} }
public boolean useStrongReferences(){
return listenerConfig.useStrongReferences(); }
}
public boolean isSynchronized(){
public boolean isFromListener(Class listener){ return isSynchronized;
return listenerConfig.isFromListener(listener); }
}
public boolean useStrongReferences(){
public boolean isAsynchronous() { return listenerConfig.useStrongReferences();
return invocationMode.equals(Invoke.Asynchronously); }
}
public boolean isFromListener(Class listener){
public boolean isFiltered() { return listenerConfig.isFromListener(listener);
return filter.length > 0; }
}
public boolean isAsynchronous() {
public int getPriority() { return invocationMode.equals(Invoke.Asynchronously);
return priority; }
}
public boolean isFiltered() {
public Method getHandler() { return filter.length > 0 || (condition != null && condition.trim().length() > 0);
return handler; }
}
public int getPriority() {
public IMessageFilter[] getFilter() { return priority;
return filter; }
}
public Method getHandler() {
public Class[] getHandledMessages() { return handler;
return handledMessages; }
}
public IMessageFilter[] getFilter() {
public boolean isEnveloped() { return filter;
return isEnvelope; }
}
public String getCondition() {
public Class<? extends HandlerInvocation> getHandlerInvocation(){ return this.condition;
return invocation; }
}
public Class[] getHandledMessages() {
public boolean handlesMessage(Class<?> messageType) { return handledMessages;
for (Class<?> handledMessage : handledMessages) { }
if (handledMessage.equals(messageType)) {
return true; public boolean isEnveloped() {
} return isEnvelope;
if (handledMessage.isAssignableFrom(messageType) && acceptsSubtypes()) { }
return true;
} public Class<? extends HandlerInvocation> getHandlerInvocation(){
} return invocation;
return false; }
}
public boolean handlesMessage(Class<?> messageType) {
public boolean acceptsSubtypes() { for (Class<?> handledMessage : handledMessages) {
return acceptsSubtypes; if (handledMessage.equals(messageType)) {
} return true;
}
} if (handledMessage.isAssignableFrom(messageType) && acceptsSubtypes()) {
return true;
}
}
return false;
}
public boolean acceptsSubtypes() {
return acceptsSubtypes;
}
}

View File

@ -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);
}
}