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

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;
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
* <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.
*/
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;
}

View File

@ -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<String, Object> 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<String, Object> properties = new HashMap<String, Object>();
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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> properties = new HashMap<String, Object>();
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<String, Object> 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<String, Object> 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;
}
}

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