Add support for meta annotations
This commit is contained in:
parent
9a81992a0a
commit
1834145fe7
@ -1,14 +1,20 @@
|
|||||||
package net.engio.mbassy.common;
|
package net.engio.mbassy.common;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.AnnotatedElement;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.*;
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author bennidi
|
* @author bennidi
|
||||||
* Date: 2/16/12
|
* Date: 2/16/12
|
||||||
* Time: 12:14 PM
|
* Time: 12:14 PM
|
||||||
*/
|
*/
|
||||||
public class ReflectionUtils {
|
public class ReflectionUtils
|
||||||
|
{
|
||||||
|
|
||||||
public static List<Method> getMethods( IPredicate<Method> condition, Class<?> target ) {
|
public static List<Method> getMethods( IPredicate<Method> condition, Class<?> target ) {
|
||||||
List<Method> methods = new LinkedList<Method>();
|
List<Method> methods = new LinkedList<Method>();
|
||||||
@ -18,7 +24,8 @@ public class ReflectionUtils {
|
|||||||
methods.add( method );
|
methods.add( method );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
}
|
||||||
|
catch ( Exception e ) {
|
||||||
//nop
|
//nop
|
||||||
}
|
}
|
||||||
if ( !target.equals( Object.class ) ) {
|
if ( !target.equals( Object.class ) ) {
|
||||||
@ -41,7 +48,8 @@ public class ReflectionUtils {
|
|||||||
while ( !current.equals( overridingMethod.getDeclaringClass() ) ) {
|
while ( !current.equals( overridingMethod.getDeclaringClass() ) ) {
|
||||||
try {
|
try {
|
||||||
return current.getDeclaredMethod( overridingMethod.getName(), overridingMethod.getParameterTypes() );
|
return current.getDeclaredMethod( overridingMethod.getName(), overridingMethod.getParameterTypes() );
|
||||||
} catch (NoSuchMethodException e) {
|
}
|
||||||
|
catch ( NoSuchMethodException e ) {
|
||||||
current = current.getSuperclass();
|
current = current.getSuperclass();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,13 +83,43 @@ public class ReflectionUtils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <A extends Annotation> A getAnnotation( Method method, Class<A> annotationType ) {
|
||||||
|
return getAnnotation( (AnnotatedElement) method, annotationType );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <A extends Annotation> A getAnnotation( Class from, Class<A> annotationType ) {
|
||||||
|
return getAnnotation( (AnnotatedElement) from, annotationType );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for an Annotation of the given type on the class. Supports meta annotations.
|
||||||
|
*
|
||||||
|
* @param from AnnotatedElement (class, method...)
|
||||||
|
* @param annotationType Annotation class to look for.
|
||||||
|
* @param <A> Annotation class
|
||||||
|
* @return Annotation instance or null
|
||||||
|
*/
|
||||||
|
public static <A extends Annotation> A getAnnotation( AnnotatedElement from, Class<A> annotationType ) {
|
||||||
|
A ann = from.getAnnotation( annotationType );
|
||||||
|
if ( ann == null ) {
|
||||||
|
for ( Annotation metaAnn : from.getAnnotations() ) {
|
||||||
|
ann = metaAnn.annotationType().getAnnotation( annotationType );
|
||||||
|
if ( ann != null ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ann;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isOverriddenBy( Method superclassMethod, Method subclassMethod ) {
|
private static boolean isOverriddenBy( Method superclassMethod, Method subclassMethod ) {
|
||||||
// if the declaring classes are the same or the subclass method is not defined in the subclass
|
// if the declaring classes are the same or the subclass method is not defined in the subclass
|
||||||
// hierarchy of the given superclass method or the method names are not the same then
|
// hierarchy of the given superclass method or the method names are not the same then
|
||||||
// subclassMethod does not override superclassMethod
|
// subclassMethod does not override superclassMethod
|
||||||
if (superclassMethod.getDeclaringClass().equals(subclassMethod.getDeclaringClass())
|
if ( superclassMethod.getDeclaringClass().equals(
|
||||||
|| !superclassMethod.getDeclaringClass().isAssignableFrom(subclassMethod.getDeclaringClass())
|
subclassMethod.getDeclaringClass() ) || !superclassMethod.getDeclaringClass().isAssignableFrom(
|
||||||
|| !superclassMethod.getName().equals(subclassMethod.getName())) {
|
subclassMethod.getDeclaringClass() ) || !superclassMethod.getName().equals(
|
||||||
|
subclassMethod.getName() ) ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ import java.lang.annotation.Target;
|
|||||||
*/
|
*/
|
||||||
@Retention(value = RetentionPolicy.RUNTIME)
|
@Retention(value = RetentionPolicy.RUNTIME)
|
||||||
@Inherited
|
@Inherited
|
||||||
@Target(value = {ElementType.METHOD})
|
@Target(value = {ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||||
public @interface Enveloped {
|
public @interface Enveloped {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,7 +14,7 @@ import java.lang.annotation.*;
|
|||||||
*/
|
*/
|
||||||
@Retention(value = RetentionPolicy.RUNTIME)
|
@Retention(value = RetentionPolicy.RUNTIME)
|
||||||
@Inherited
|
@Inherited
|
||||||
@Target(value = {ElementType.METHOD})
|
@Target(value = {ElementType.METHOD,ElementType.ANNOTATION_TYPE})
|
||||||
public @interface Handler {
|
public @interface Handler {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,7 +13,7 @@ import java.lang.annotation.*;
|
|||||||
* @author bennidi
|
* @author bennidi
|
||||||
*/
|
*/
|
||||||
@Retention(value = RetentionPolicy.RUNTIME)
|
@Retention(value = RetentionPolicy.RUNTIME)
|
||||||
@Target(value = {ElementType.TYPE})
|
@Target(value = {ElementType.TYPE, ElementType.ANNOTATION_TYPE})
|
||||||
@Inherited
|
@Inherited
|
||||||
public @interface Listener {
|
public @interface Listener {
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.engio.mbassy.listener;
|
package net.engio.mbassy.listener;
|
||||||
|
|
||||||
|
import net.engio.mbassy.common.ReflectionUtils;
|
||||||
import net.engio.mbassy.dispatch.HandlerInvocation;
|
import net.engio.mbassy.dispatch.HandlerInvocation;
|
||||||
import net.engio.mbassy.dispatch.el.ElFilter;
|
import net.engio.mbassy.dispatch.el.ElFilter;
|
||||||
|
|
||||||
@ -48,7 +49,7 @@ public class MessageHandler {
|
|||||||
if(filter == null){
|
if(filter == null){
|
||||||
filter = new IMessageFilter[]{};
|
filter = new IMessageFilter[]{};
|
||||||
}
|
}
|
||||||
net.engio.mbassy.listener.Enveloped enveloped = handler.getAnnotation(Enveloped.class);
|
net.engio.mbassy.listener.Enveloped enveloped = ReflectionUtils.getAnnotation( handler, Enveloped.class );
|
||||||
Class[] handledMessages = enveloped != null
|
Class[] handledMessages = enveloped != null
|
||||||
? enveloped.messages()
|
? enveloped.messages()
|
||||||
: handler.getParameterTypes();
|
: handler.getParameterTypes();
|
||||||
@ -76,7 +77,7 @@ public class MessageHandler {
|
|||||||
properties.put(Enveloped, enveloped != null);
|
properties.put(Enveloped, enveloped != null);
|
||||||
properties.put(AcceptSubtypes, !handlerConfig.rejectSubtypes());
|
properties.put(AcceptSubtypes, !handlerConfig.rejectSubtypes());
|
||||||
properties.put(Listener, listenerConfig);
|
properties.put(Listener, listenerConfig);
|
||||||
properties.put(IsSynchronized, handler.getAnnotation(Synchronized.class) != null);
|
properties.put(IsSynchronized, ReflectionUtils.getAnnotation( handler, Synchronized.class) != null);
|
||||||
properties.put(HandledMessages, handledMessages);
|
properties.put(HandledMessages, handledMessages);
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package net.engio.mbassy.listener;
|
package net.engio.mbassy.listener;
|
||||||
|
|
||||||
import net.engio.mbassy.common.IPredicate;
|
import net.engio.mbassy.common.IPredicate;
|
||||||
|
import net.engio.mbassy.common.ReflectionUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@ -42,7 +43,7 @@ public class MessageListener<T> {
|
|||||||
|
|
||||||
public MessageListener(Class<T> listenerDefinition) {
|
public MessageListener(Class<T> listenerDefinition) {
|
||||||
this.listenerDefinition = listenerDefinition;
|
this.listenerDefinition = listenerDefinition;
|
||||||
listenerAnnotation = listenerDefinition.getAnnotation(Listener.class);
|
listenerAnnotation = ReflectionUtils.getAnnotation( listenerDefinition, Listener.class );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ public class MetadataReader {
|
|||||||
private static final IPredicate<Method> AllMessageHandlers = new IPredicate<Method>() {
|
private static final IPredicate<Method> AllMessageHandlers = new IPredicate<Method>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Method target) {
|
public boolean apply(Method target) {
|
||||||
return target.getAnnotation(Handler.class) != null;
|
return ReflectionUtils.getAnnotation(target, Handler.class) != null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -52,7 +52,6 @@ public class MetadataReader {
|
|||||||
return filters;
|
return filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// get all listeners defined by the given class (includes
|
// get all listeners defined by the given class (includes
|
||||||
// listeners defined in super classes)
|
// listeners defined in super classes)
|
||||||
public MessageListener getMessageListener(Class target) {
|
public MessageListener getMessageListener(Class target) {
|
||||||
@ -70,7 +69,7 @@ public class MetadataReader {
|
|||||||
// for each handler there will be no overriding method that specifies @Handler annotation
|
// for each handler there will be no overriding method that specifies @Handler annotation
|
||||||
// but an overriding method does inherit the listener configuration of the overwritten method
|
// but an overriding method does inherit the listener configuration of the overwritten method
|
||||||
for (Method handler : bottomMostHandlers) {
|
for (Method handler : bottomMostHandlers) {
|
||||||
Handler handlerConfig = handler.getAnnotation(Handler.class);
|
Handler handlerConfig = ReflectionUtils.getAnnotation( handler, Handler.class);
|
||||||
if (!handlerConfig.enabled() || !isValidMessageHandler(handler)) {
|
if (!handlerConfig.enabled() || !isValidMessageHandler(handler)) {
|
||||||
continue; // disabled or invalid listeners are ignored
|
continue; // disabled or invalid listeners are ignored
|
||||||
}
|
}
|
||||||
@ -89,7 +88,7 @@ public class MetadataReader {
|
|||||||
|
|
||||||
|
|
||||||
private boolean isValidMessageHandler(Method handler) {
|
private boolean isValidMessageHandler(Method handler) {
|
||||||
if (handler == null || handler.getAnnotation(Handler.class) == null) {
|
if (handler == null || ReflectionUtils.getAnnotation( handler, Handler.class) == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (handler.getParameterTypes().length != 1) {
|
if (handler.getParameterTypes().length != 1) {
|
||||||
@ -98,7 +97,7 @@ public class MetadataReader {
|
|||||||
+ "]. A messageHandler must define exactly one parameter");
|
+ "]. A messageHandler must define exactly one parameter");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Enveloped envelope = handler.getAnnotation(Enveloped.class);
|
Enveloped envelope = ReflectionUtils.getAnnotation( handler, Enveloped.class);
|
||||||
if (envelope != null && !MessageEnvelope.class.isAssignableFrom(handler.getParameterTypes()[0])) {
|
if (envelope != null && !MessageEnvelope.class.isAssignableFrom(handler.getParameterTypes()[0])) {
|
||||||
System.out.println("Message envelope configured but no subclass of MessageEnvelope found as parameter");
|
System.out.println("Message envelope configured but no subclass of MessageEnvelope found as parameter");
|
||||||
return false;
|
return false;
|
||||||
|
@ -18,6 +18,6 @@ import java.lang.annotation.*;
|
|||||||
*/
|
*/
|
||||||
@Retention(value = RetentionPolicy.RUNTIME)
|
@Retention(value = RetentionPolicy.RUNTIME)
|
||||||
@Inherited
|
@Inherited
|
||||||
@Target(value = {ElementType.METHOD})
|
@Target(value = {ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||||
public @interface Synchronized {
|
public @interface Synchronized {
|
||||||
}
|
}
|
||||||
|
139
src/test/java/net/engio/mbassy/CustomHandlerAnnotationTest.java
Normal file
139
src/test/java/net/engio/mbassy/CustomHandlerAnnotationTest.java
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
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.common.ReflectionUtils;
|
||||||
|
import net.engio.mbassy.listener.*;
|
||||||
|
import net.engio.mbassy.subscription.MessageEnvelope;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests a custom handler annotation with a @Handler meta annotation and a default filter.
|
||||||
|
*/
|
||||||
|
public class CustomHandlerAnnotationTest extends MessageBusTest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handler annotation that adds a default filter on the NamedMessage.
|
||||||
|
* Enveloped is in no way required, but simply added to test a meta enveloped annotation.
|
||||||
|
*/
|
||||||
|
@Retention(value = RetentionPolicy.RUNTIME)
|
||||||
|
@Inherited
|
||||||
|
@Handler(filters = { @Filter(NamedMessageFilter.class) })
|
||||||
|
@Synchronized
|
||||||
|
@Target(value = { ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||||
|
static @interface NamedMessageHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return The message names supported.
|
||||||
|
*/
|
||||||
|
String[] value();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test enveloped meta annotation.
|
||||||
|
*/
|
||||||
|
@Retention(value = RetentionPolicy.RUNTIME)
|
||||||
|
@Target(value = { ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||||
|
@Inherited
|
||||||
|
@Handler(filters = { @Filter(NamedMessageFilter.class) })
|
||||||
|
@Enveloped(messages = NamedMessage.class)
|
||||||
|
static @interface EnvelopedNamedMessageHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return The message names supported.
|
||||||
|
*/
|
||||||
|
String[] value();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for a NamedMessageHandler annotation on the handler method.
|
||||||
|
* The annotation specifies the supported message names.
|
||||||
|
*/
|
||||||
|
public static class NamedMessageFilter implements IMessageFilter<NamedMessage>
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public boolean accepts( NamedMessage message, MessageHandler metadata ) {
|
||||||
|
NamedMessageHandler namedMessageHandler =
|
||||||
|
ReflectionUtils.getAnnotation( metadata.getHandler(), NamedMessageHandler.class );
|
||||||
|
|
||||||
|
if ( namedMessageHandler != null ) {
|
||||||
|
return Arrays.asList( namedMessageHandler.value() ).contains( message.getName() );
|
||||||
|
}
|
||||||
|
|
||||||
|
EnvelopedNamedMessageHandler envelopedHandler =
|
||||||
|
ReflectionUtils.getAnnotation( metadata.getHandler(), EnvelopedNamedMessageHandler.class );
|
||||||
|
|
||||||
|
return envelopedHandler != null && Arrays.asList( envelopedHandler.value() ).contains( message.getName() );
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class NamedMessage
|
||||||
|
{
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
NamedMessage( String name ) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class NamedMessageListener
|
||||||
|
{
|
||||||
|
final Set<NamedMessage> handledByOne = new HashSet<NamedMessage>();
|
||||||
|
final Set<NamedMessage> handledByTwo = new HashSet<NamedMessage>();
|
||||||
|
final Set<NamedMessage> handledByThree = new HashSet<NamedMessage>();
|
||||||
|
|
||||||
|
@NamedMessageHandler({ "messageOne", "messageTwo" })
|
||||||
|
void handlerOne( NamedMessage message ) {
|
||||||
|
handledByOne.add( message );
|
||||||
|
}
|
||||||
|
|
||||||
|
@EnvelopedNamedMessageHandler({ "messageTwo", "messageThree" })
|
||||||
|
void handlerTwo( MessageEnvelope envelope ) {
|
||||||
|
handledByTwo.add( (NamedMessage) envelope.getMessage() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@NamedMessageHandler("messageThree")
|
||||||
|
void handlerThree( NamedMessage message ) {
|
||||||
|
handledByThree.add( message );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMetaHandlerFiltering() {
|
||||||
|
MBassador bus = getBus( BusConfiguration.Default() );
|
||||||
|
|
||||||
|
NamedMessageListener listener = new NamedMessageListener();
|
||||||
|
bus.subscribe( listener );
|
||||||
|
|
||||||
|
NamedMessage messageOne = new NamedMessage( "messageOne" );
|
||||||
|
NamedMessage messageTwo = new NamedMessage( "messageTwo" );
|
||||||
|
NamedMessage messageThree = new NamedMessage( "messageThree" );
|
||||||
|
|
||||||
|
bus.publish( messageOne );
|
||||||
|
bus.publish( messageTwo );
|
||||||
|
bus.publish( messageThree );
|
||||||
|
|
||||||
|
assertTrue( listener.handledByOne.contains( messageOne ) );
|
||||||
|
assertTrue( listener.handledByOne.contains( messageTwo ) );
|
||||||
|
assertFalse( listener.handledByOne.contains( messageThree ) );
|
||||||
|
|
||||||
|
assertFalse( listener.handledByTwo.contains( messageOne ) );
|
||||||
|
assertTrue( listener.handledByTwo.contains( messageTwo ) );
|
||||||
|
assertTrue( listener.handledByTwo.contains( messageThree ) );
|
||||||
|
|
||||||
|
assertFalse( listener.handledByThree.contains( messageOne ) );
|
||||||
|
assertFalse( listener.handledByThree.contains( messageTwo ) );
|
||||||
|
assertTrue( listener.handledByThree.contains( messageThree ) );
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user