From c84b7967e9eb2f6307466b1644977423831d2ef1 Mon Sep 17 00:00:00 2001 From: Yasser Zamani Date: Sun, 5 Feb 2017 17:54:28 +0330 Subject: [PATCH 1/2] [WW-4694] annotation processing improved in order to navigate around proxies, superclasses and interfaces --- .../xwork2/util/AnnotationUtils.java | 113 +++++++++++++++--- .../xwork2/util/AnnotationUtilsTest.java | 37 ++++-- .../xwork2/util/annotation/DummyClass.java | 5 +- .../xwork2/util/annotation/DummyClassExt.java | 5 +- .../util/annotation/DummyInterface.java | 7 ++ .../xwork2/util/annotation/MyAnnotationI.java | 8 ++ 6 files changed, 147 insertions(+), 28 deletions(-) create mode 100644 core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyInterface.java create mode 100644 core/src/test/java/com/opensymphony/xwork2/util/annotation/MyAnnotationI.java diff --git a/core/src/main/java/com/opensymphony/xwork2/util/AnnotationUtils.java b/core/src/main/java/com/opensymphony/xwork2/util/AnnotationUtils.java index ef0ee536b..1c37918c2 100644 --- a/core/src/main/java/com/opensymphony/xwork2/util/AnnotationUtils.java +++ b/core/src/main/java/com/opensymphony/xwork2/util/AnnotationUtils.java @@ -122,7 +122,14 @@ public class AnnotationUtils { Collection toReturn = new HashSet<>(); for (Method m : clazz.getMethods()) { - if (ArrayUtils.isNotEmpty(annotation) && isAnnotatedBy(m, annotation)) { + boolean found = false; + for( Class c : annotation ){ + if( null != findAnnotation(m, c) ){ + found = true; + break; + } + } + if (found) { toReturn.add(m); } else if (ArrayUtils.isEmpty(annotation) && ArrayUtils.isNotEmpty(m.getAnnotations())) { toReturn.add(m); @@ -133,22 +140,100 @@ public class AnnotationUtils { } /** - * Varargs version of AnnotatedElement.isAnnotationPresent() - * @param annotatedElement element to check - * @param annotation the {@link Annotation}s to find - * @return true is element is annotated by one of the annotation - * @see AnnotatedElement + * Find a single {@link Annotation} of {@code annotationType} from the supplied + * {@link Method}, traversing its super methods (i.e., from superclasses and + * interfaces) if no annotation can be found on the given method itself. + *

Annotations on methods are not inherited by default, so we need to handle + * this explicitly. + * @param method the method to look for annotations on + * @param annotationType the annotation type to look for + * @return the annotation found, or {@code null} if none */ - public static boolean isAnnotatedBy(AnnotatedElement annotatedElement, Class... annotation) { - if (ArrayUtils.isEmpty(annotation)) { - return false; - } - - for( Class c : annotation ){ - if( annotatedElement.isAnnotationPresent(c) ) return true; + public static A findAnnotation(Method method, Class annotationType) { + A result = getAnnotation(method, annotationType); + Class clazz = method.getDeclaringClass(); + if (result == null) { + result = searchOnInterfaces(method, annotationType, clazz.getInterfaces()); } + while (result == null) { + clazz = clazz.getSuperclass(); + if (clazz == null || clazz.equals(Object.class)) { + break; + } + try { + Method equivalentMethod = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes()); + result = getAnnotation(equivalentMethod, annotationType); + } + catch (NoSuchMethodException ex) { + // No equivalent method found + } + if (result == null) { + result = searchOnInterfaces(method, annotationType, clazz.getInterfaces()); + } + } + return result; + } - return false; + /** + * Get a single {@link Annotation} of {@code annotationType} from the supplied + * Method, Constructor or Field. Meta-annotations will be searched if the annotation + * is not declared locally on the supplied element. + * @param annotatedElement the Method, Constructor or Field from which to get the annotation + * @param annotationType the annotation type to look for, both locally and as a meta-annotation + * @return the matching annotation, or {@code null} if none found + */ + public static T getAnnotation(AnnotatedElement annotatedElement, Class annotationType) { + try { + T ann = annotatedElement.getAnnotation(annotationType); + if (ann == null) { + for (Annotation metaAnn : annotatedElement.getAnnotations()) { + ann = metaAnn.annotationType().getAnnotation(annotationType); + if (ann != null) { + break; + } + } + } + return ann; + } + catch (Exception ex) { + // Assuming nested Class values not resolvable within annotation attributes... + return null; + } + } + + private static A searchOnInterfaces(Method method, Class annotationType, Class... ifcs) { + A annotation = null; + for (Class iface : ifcs) { + if (isInterfaceWithAnnotatedMethods(iface)) { + try { + Method equivalentMethod = iface.getMethod(method.getName(), method.getParameterTypes()); + annotation = getAnnotation(equivalentMethod, annotationType); + } + catch (NoSuchMethodException ex) { + // Skip this interface - it doesn't have the method... + } + if (annotation != null) { + break; + } + } + } + return annotation; + } + + private static boolean isInterfaceWithAnnotatedMethods(Class iface) { + boolean found = false; + for (Method ifcMethod : iface.getMethods()) { + try { + if (ifcMethod.getAnnotations().length > 0) { + found = true; + break; + } + } + catch (Exception ex) { + // Assuming nested Class values not resolvable within annotation attributes... + } + } + return found; } /** diff --git a/core/src/test/java/com/opensymphony/xwork2/util/AnnotationUtilsTest.java b/core/src/test/java/com/opensymphony/xwork2/util/AnnotationUtilsTest.java index e59abe679..8ad4f3585 100644 --- a/core/src/test/java/com/opensymphony/xwork2/util/AnnotationUtilsTest.java +++ b/core/src/test/java/com/opensymphony/xwork2/util/AnnotationUtilsTest.java @@ -5,8 +5,11 @@ import com.opensymphony.xwork2.util.annotation.DummyClass; import com.opensymphony.xwork2.util.annotation.DummyClassExt; import com.opensymphony.xwork2.util.annotation.MyAnnotation; import com.opensymphony.xwork2.util.annotation.MyAnnotation2; +import com.opensymphony.xwork2.util.annotation.MyAnnotationI; + import junit.framework.TestCase; +import java.lang.annotation.Retention; import java.lang.reflect.AnnotatedElement; import java.util.Collection; @@ -15,22 +18,32 @@ import java.util.Collection; */ public class AnnotationUtilsTest extends TestCase { - @SuppressWarnings("unchecked") - public void testIsAnnotatedByWithoutAnnotationArgsReturnsFalse() throws Exception { - assertFalse(AnnotationUtils.isAnnotatedBy(DummyClass.class)); - assertFalse(AnnotationUtils.isAnnotatedBy(DummyClass.class.getMethod("methodWithAnnotation"))); + public void testGetAnnotationMeta() throws Exception { + assertNotNull(AnnotationUtils.getAnnotation(DummyClass.class.getMethod("methodWithAnnotation"), Retention.class)); + } + + public void testGetAnnotation() throws Exception { + assertNull(AnnotationUtils.getAnnotation(DummyClass.class.getMethod("methodWithAnnotation"), Deprecated.class)); + assertNotNull(AnnotationUtils.getAnnotation(DummyClass.class.getMethod("methodWithAnnotation"), MyAnnotation.class)); + } + + public void testFindAnnotationFromSuperclass() throws Exception { + assertNotNull(AnnotationUtils.findAnnotation(DummyClassExt.class.getMethod("methodWithAnnotation"), MyAnnotation.class)); + } + + public void testFindAnnotationFromInterface() throws Exception { + assertNotNull(AnnotationUtils.findAnnotation(DummyClass.class.getMethod("interfaceMethodWithAnnotation"), MyAnnotationI.class)); + } + + public void testFindAnnotation() throws Exception { + assertNotNull(AnnotationUtils.findAnnotation(DummyClassExt.class.getMethod("anotherAnnotatedMethod"), MyAnnotation2.class)); } @SuppressWarnings("unchecked") - public void testIsAnnotatedByWithSingleAnnotationArgMatchingReturnsTrue() throws Exception { - assertTrue(AnnotationUtils.isAnnotatedBy(DummyClass.class.getMethod("methodWithAnnotation"), MyAnnotation.class)); - } + public void testGetAnnotatedMethodsIncludingSuperclassAndInterface() throws Exception { - @SuppressWarnings("unchecked") - public void testIsAnnotatedByWithMultiAnnotationArgMatchingReturnsTrue() throws Exception { - assertFalse(AnnotationUtils.isAnnotatedBy(DummyClass.class.getMethod("methodWithAnnotation"), Deprecated.class)); - assertTrue(AnnotationUtils.isAnnotatedBy(DummyClass.class.getMethod("methodWithAnnotation"), MyAnnotation.class, Deprecated.class)); - assertTrue(AnnotationUtils.isAnnotatedBy(DummyClass.class.getMethod("methodWithAnnotation"), Deprecated.class, MyAnnotation.class)); + Collection ans = AnnotationUtils.getAnnotatedMethods(DummyClassExt.class, Deprecated.class, MyAnnotation.class, MyAnnotation2.class, MyAnnotationI.class); + assertEquals(3, ans.size()); } @SuppressWarnings("unchecked") diff --git a/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyClass.java b/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyClass.java index 22d6e07f3..da2f80e41 100644 --- a/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyClass.java +++ b/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyClass.java @@ -1,7 +1,7 @@ package com.opensymphony.xwork2.util.annotation; @MyAnnotation("class-test") -public class DummyClass { +public class DummyClass implements DummyInterface { public DummyClass() { } @@ -10,4 +10,7 @@ public class DummyClass { public void methodWithAnnotation() { } + @Override + public void interfaceMethodWithAnnotation() { + } } diff --git a/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyClassExt.java b/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyClassExt.java index cfeebdf82..7c024f69d 100644 --- a/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyClassExt.java +++ b/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyClassExt.java @@ -5,5 +5,8 @@ public final class DummyClassExt extends DummyClass { @MyAnnotation2 public void anotherAnnotatedMethod() { } - + + @Override + public void methodWithAnnotation() { + } } diff --git a/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyInterface.java b/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyInterface.java new file mode 100644 index 000000000..6faa3e46e --- /dev/null +++ b/core/src/test/java/com/opensymphony/xwork2/util/annotation/DummyInterface.java @@ -0,0 +1,7 @@ +package com.opensymphony.xwork2.util.annotation; + +public interface DummyInterface { + + @MyAnnotationI + public void interfaceMethodWithAnnotation(); +} \ No newline at end of file diff --git a/core/src/test/java/com/opensymphony/xwork2/util/annotation/MyAnnotationI.java b/core/src/test/java/com/opensymphony/xwork2/util/annotation/MyAnnotationI.java new file mode 100644 index 000000000..ea866b90e --- /dev/null +++ b/core/src/test/java/com/opensymphony/xwork2/util/annotation/MyAnnotationI.java @@ -0,0 +1,8 @@ +package com.opensymphony.xwork2.util.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface MyAnnotationI { +} From 301844bbaadf52b6c07914ca7a4b327af7856538 Mon Sep 17 00:00:00 2001 From: Yasser Zamani Date: Wed, 8 Feb 2017 00:07:13 +0330 Subject: [PATCH 2/2] [WW-4472] using improved AnnotationUtils to navigate around proxies --- .../xwork2/interceptor/DefaultWorkflowInterceptor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java index 271fa6ded..a9f2565c9 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java @@ -207,7 +207,7 @@ public class DefaultWorkflowInterceptor extends MethodFilterInterceptor { */ protected String processInputConfig(final Object action, final String method, final String currentResultName) throws Exception { String resultName = currentResultName; - InputConfig annotation = action.getClass().getMethod(method, EMPTY_CLASS_ARRAY).getAnnotation(InputConfig.class); + InputConfig annotation = AnnotationUtils.findAnnotation(action.getClass().getMethod(method, EMPTY_CLASS_ARRAY), InputConfig.class); if (annotation != null) { if (StringUtils.isNotEmpty(annotation.methodName())) { Method m = action.getClass().getMethod(annotation.methodName());