diff --git a/src/main/java/net/engio/mbassy/common/ReflectionUtils.java b/src/main/java/net/engio/mbassy/common/ReflectionUtils.java index efebc34..b420a47 100644 --- a/src/main/java/net/engio/mbassy/common/ReflectionUtils.java +++ b/src/main/java/net/engio/mbassy/common/ReflectionUtils.java @@ -1,100 +1,138 @@ package net.engio.mbassy.common; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; 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 * Date: 2/16/12 * Time: 12:14 PM */ -public class ReflectionUtils { +public class ReflectionUtils +{ - public static List getMethods(IPredicate condition, Class target) { - List methods = new LinkedList(); - try { - for (Method method : target.getDeclaredMethods()) { - if (condition.apply(method)) { - methods.add(method); - } - } - } catch (Exception e) { - //nop - } - if (!target.equals(Object.class)) { - methods.addAll(getMethods(condition, target.getSuperclass())); - } - return methods; - } + public static List getMethods( IPredicate condition, Class target ) { + List methods = new LinkedList(); + try { + for ( Method method : target.getDeclaredMethods() ) { + if ( condition.apply( method ) ) { + methods.add( method ); + } + } + } + catch ( Exception e ) { + //nop + } + if ( !target.equals( Object.class ) ) { + methods.addAll( getMethods( condition, target.getSuperclass() ) ); + } + return methods; + } - /** - * Traverses the class hierarchy upwards, starting at the given subclass, looking - * for an override of the given methods -> finds the bottom most override of the given - * method if any exists - * - * @param overridingMethod - * @param subclass - * @return - */ - public static Method getOverridingMethod(final Method overridingMethod, final Class subclass) { - Class current = subclass; - while (!current.equals(overridingMethod.getDeclaringClass())) { - try { - return current.getDeclaredMethod(overridingMethod.getName(), overridingMethod.getParameterTypes()); - } catch (NoSuchMethodException e) { - current = current.getSuperclass(); - } - } - return null; - } + /** + * Traverses the class hierarchy upwards, starting at the given subclass, looking + * for an override of the given methods -> finds the bottom most override of the given + * method if any exists + * + * @param overridingMethod + * @param subclass + * @return + */ + public static Method getOverridingMethod( final Method overridingMethod, final Class subclass ) { + Class current = subclass; + while ( !current.equals( overridingMethod.getDeclaringClass() ) ) { + try { + return current.getDeclaredMethod( overridingMethod.getName(), overridingMethod.getParameterTypes() ); + } + catch ( NoSuchMethodException e ) { + current = current.getSuperclass(); + } + } + return null; + } - public static Set getSuperclasses(Class from) { - Set superclasses = new HashSet(); - collectInterfaces(from, superclasses); - while (!from.equals(Object.class) && !from.isInterface()) { - superclasses.add(from.getSuperclass()); - from = from.getSuperclass(); - collectInterfaces(from, superclasses); - } - return superclasses; - } + public static Set getSuperclasses( Class from ) { + Set superclasses = new HashSet(); + collectInterfaces( from, superclasses ); + while ( !from.equals( Object.class ) && !from.isInterface() ) { + superclasses.add( from.getSuperclass() ); + from = from.getSuperclass(); + collectInterfaces( from, superclasses ); + } + return superclasses; + } - public static void collectInterfaces(Class from, Set accumulator){ - for(Class intface : from.getInterfaces()){ - accumulator.add(intface); - collectInterfaces(intface, accumulator); - } - } + public static void collectInterfaces( Class from, Set accumulator ) { + for ( Class intface : from.getInterfaces() ) { + accumulator.add( intface ); + collectInterfaces( intface, accumulator ); + } + } - public static boolean containsOverridingMethod(final List allMethods, final Method methodToCheck) { - for (Method method : allMethods) { - if (isOverriddenBy(methodToCheck, method)) { - return true; - } - } - return false; - } + public static boolean containsOverridingMethod( final List allMethods, final Method methodToCheck ) { + for ( Method method : allMethods ) { + if ( isOverriddenBy( methodToCheck, method ) ) { + return true; + } + } + return false; + } - 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 - // hierarchy of the given superclass method or the method names are not the same then - // subclassMethod does not override superclassMethod - if (superclassMethod.getDeclaringClass().equals(subclassMethod.getDeclaringClass()) - || !superclassMethod.getDeclaringClass().isAssignableFrom(subclassMethod.getDeclaringClass()) - || !superclassMethod.getName().equals(subclassMethod.getName())) { - return false; - } + public static A getAnnotation( Method method, Class annotationType ) { + return getAnnotation( (AnnotatedElement) method, annotationType ); + } - Class[] superClassMethodParameters = superclassMethod.getParameterTypes(); - Class[] subClassMethodParameters = subclassMethod.getParameterTypes(); - // method must specify the same number of parameters - //the parameters must occur in the exact same order - for (int i = 0; i < subClassMethodParameters.length; i++) { - if (!superClassMethodParameters[i].equals(subClassMethodParameters[i])) { - return false; - } - } - return true; - } + public static A getAnnotation( Class from, Class 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 Annotation class + * @return Annotation instance or null + */ + public static A getAnnotation( AnnotatedElement from, Class 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 ) { + // 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 + // subclassMethod does not override superclassMethod + if ( superclassMethod.getDeclaringClass().equals( + subclassMethod.getDeclaringClass() ) || !superclassMethod.getDeclaringClass().isAssignableFrom( + subclassMethod.getDeclaringClass() ) || !superclassMethod.getName().equals( + subclassMethod.getName() ) ) { + return false; + } + + Class[] superClassMethodParameters = superclassMethod.getParameterTypes(); + Class[] subClassMethodParameters = subclassMethod.getParameterTypes(); + // method must specify the same number of parameters + //the parameters must occur in the exact same order + for ( int i = 0; i < subClassMethodParameters.length; i++ ) { + if ( !superClassMethodParameters[i].equals( subClassMethodParameters[i] ) ) { + return false; + } + } + return true; + } } diff --git a/src/main/java/net/engio/mbassy/listener/Enveloped.java b/src/main/java/net/engio/mbassy/listener/Enveloped.java index bc29f55..938031b 100644 --- a/src/main/java/net/engio/mbassy/listener/Enveloped.java +++ b/src/main/java/net/engio/mbassy/listener/Enveloped.java @@ -15,7 +15,7 @@ import java.lang.annotation.Target; */ @Retention(value = RetentionPolicy.RUNTIME) @Inherited -@Target(value = {ElementType.METHOD}) +@Target(value = {ElementType.METHOD, ElementType.ANNOTATION_TYPE}) public @interface Enveloped { /** diff --git a/src/main/java/net/engio/mbassy/listener/Handler.java b/src/main/java/net/engio/mbassy/listener/Handler.java index 785e33c..229a7ed 100644 --- a/src/main/java/net/engio/mbassy/listener/Handler.java +++ b/src/main/java/net/engio/mbassy/listener/Handler.java @@ -14,7 +14,7 @@ import java.lang.annotation.*; */ @Retention(value = RetentionPolicy.RUNTIME) @Inherited -@Target(value = {ElementType.METHOD}) +@Target(value = {ElementType.METHOD,ElementType.ANNOTATION_TYPE}) public @interface Handler { /** diff --git a/src/main/java/net/engio/mbassy/listener/Listener.java b/src/main/java/net/engio/mbassy/listener/Listener.java index 38b0b95..3b03951 100644 --- a/src/main/java/net/engio/mbassy/listener/Listener.java +++ b/src/main/java/net/engio/mbassy/listener/Listener.java @@ -13,7 +13,7 @@ import java.lang.annotation.*; * @author bennidi */ @Retention(value = RetentionPolicy.RUNTIME) -@Target(value = {ElementType.TYPE}) +@Target(value = {ElementType.TYPE, ElementType.ANNOTATION_TYPE}) @Inherited public @interface Listener { diff --git a/src/main/java/net/engio/mbassy/listener/MessageHandler.java b/src/main/java/net/engio/mbassy/listener/MessageHandler.java index c0a9f7b..b87f0b4 100644 --- a/src/main/java/net/engio/mbassy/listener/MessageHandler.java +++ b/src/main/java/net/engio/mbassy/listener/MessageHandler.java @@ -1,5 +1,6 @@ package net.engio.mbassy.listener; +import net.engio.mbassy.common.ReflectionUtils; import net.engio.mbassy.dispatch.HandlerInvocation; import net.engio.mbassy.dispatch.el.ElFilter; @@ -48,7 +49,7 @@ public class MessageHandler { if(filter == null){ 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 ? enveloped.messages() : handler.getParameterTypes(); @@ -76,7 +77,7 @@ public class MessageHandler { properties.put(Enveloped, enveloped != null); properties.put(AcceptSubtypes, !handlerConfig.rejectSubtypes()); 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); return properties; } diff --git a/src/main/java/net/engio/mbassy/listener/MessageListener.java b/src/main/java/net/engio/mbassy/listener/MessageListener.java index 6c0cb6e..6358728 100644 --- a/src/main/java/net/engio/mbassy/listener/MessageListener.java +++ b/src/main/java/net/engio/mbassy/listener/MessageListener.java @@ -1,6 +1,7 @@ package net.engio.mbassy.listener; import net.engio.mbassy.common.IPredicate; +import net.engio.mbassy.common.ReflectionUtils; import java.util.ArrayList; import java.util.Collection; @@ -42,7 +43,7 @@ public class MessageListener { public MessageListener(Class listenerDefinition) { this.listenerDefinition = listenerDefinition; - listenerAnnotation = listenerDefinition.getAnnotation(Listener.class); + listenerAnnotation = ReflectionUtils.getAnnotation( listenerDefinition, Listener.class ); } diff --git a/src/main/java/net/engio/mbassy/listener/MetadataReader.java b/src/main/java/net/engio/mbassy/listener/MetadataReader.java index dba0374..a84221a 100644 --- a/src/main/java/net/engio/mbassy/listener/MetadataReader.java +++ b/src/main/java/net/engio/mbassy/listener/MetadataReader.java @@ -22,7 +22,7 @@ public class MetadataReader { private static final IPredicate AllMessageHandlers = new IPredicate() { @Override 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; } - // get all listeners defined by the given class (includes // listeners defined in super classes) 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 // but an overriding method does inherit the listener configuration of the overwritten method for (Method handler : bottomMostHandlers) { - Handler handlerConfig = handler.getAnnotation(Handler.class); + Handler handlerConfig = ReflectionUtils.getAnnotation( handler, Handler.class); if (!handlerConfig.enabled() || !isValidMessageHandler(handler)) { continue; // disabled or invalid listeners are ignored } @@ -89,7 +88,7 @@ public class MetadataReader { private boolean isValidMessageHandler(Method handler) { - if (handler == null || handler.getAnnotation(Handler.class) == null) { + if (handler == null || ReflectionUtils.getAnnotation( handler, Handler.class) == null) { return false; } if (handler.getParameterTypes().length != 1) { @@ -98,7 +97,7 @@ public class MetadataReader { + "]. A messageHandler must define exactly one parameter"); return false; } - Enveloped envelope = handler.getAnnotation(Enveloped.class); + Enveloped envelope = ReflectionUtils.getAnnotation( handler, Enveloped.class); if (envelope != null && !MessageEnvelope.class.isAssignableFrom(handler.getParameterTypes()[0])) { System.out.println("Message envelope configured but no subclass of MessageEnvelope found as parameter"); return false; diff --git a/src/main/java/net/engio/mbassy/listener/Synchronized.java b/src/main/java/net/engio/mbassy/listener/Synchronized.java index 7151d56..800278d 100644 --- a/src/main/java/net/engio/mbassy/listener/Synchronized.java +++ b/src/main/java/net/engio/mbassy/listener/Synchronized.java @@ -18,6 +18,6 @@ import java.lang.annotation.*; */ @Retention(value = RetentionPolicy.RUNTIME) @Inherited -@Target(value = {ElementType.METHOD}) +@Target(value = {ElementType.METHOD, ElementType.ANNOTATION_TYPE}) public @interface Synchronized { } diff --git a/src/main/java/net/engio/mbassy/subscription/SubscriptionManager.java b/src/main/java/net/engio/mbassy/subscription/SubscriptionManager.java index d9adecc..0988bdb 100644 --- a/src/main/java/net/engio/mbassy/subscription/SubscriptionManager.java +++ b/src/main/java/net/engio/mbassy/subscription/SubscriptionManager.java @@ -163,7 +163,7 @@ public class SubscriptionManager { readWriteLock.readLock().lock(); if (subscriptionsPerMessage.get(messageType) != null) { - subscriptions.addAll(subscriptionsPerMessage.get(messageType)); + subscriptions.addAll(subscriptionsPerMessage.get(messageType)); } for (Class eventSuperType : ReflectionUtils.getSuperclasses(messageType)) { Collection subs = subscriptionsPerMessage.get(eventSuperType); diff --git a/src/test/java/net/engio/mbassy/CustomHandlerAnnotationTest.java b/src/test/java/net/engio/mbassy/CustomHandlerAnnotationTest.java new file mode 100644 index 0000000..f70cb0c --- /dev/null +++ b/src/test/java/net/engio/mbassy/CustomHandlerAnnotationTest.java @@ -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 + { + @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 handledByOne = new HashSet(); + final Set handledByTwo = new HashSet(); + final Set handledByThree = new HashSet(); + + @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 ) ); + } +}