3 Replies Latest reply on Feb 1, 2010 9:12 AM by alesj

    JBREFLECT - Exception processing non-public annotation

    jaikiran
      Pete has reported a failure in CDI testsuite against the AS trunk [https://jira.jboss.org/jira/browse/JBAS-7676]. The testcase involves a @Stateless annotated EJB. Apart from this annotation, the EJB class also has one other annotation. Here's how the EJB class and its related classes looks like:
      
      {code:java}
      package org.jboss.jsr299.tck.tests.definition.qualifier.enterprise;
      
      @Stateless
      public class BorderCollie extends LongHairedDog
        implements BorderCollieLocal
      {
      }
      {code}
      
      {code:java}
      package org.jboss.jsr299.tck.tests.definition.qualifier.enterprise;
      
      @Hairy(clipped=false)
      class LongHairedDog
      {
      }
      {code}
      
      {code:java}
      package org.jboss.jsr299.tck.tests.definition.qualifier.enterprise;
      
      
      @Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD})
      ...//removed irrelevant stuff
      @interface Hairy
      {
        public abstract boolean clipped();
      }
      
      {code}
      
      During annotation scanning of this EJB class, it runs into this error, which causes the EJB metadata of this class to be messed up and the deployment failing:
      
      {code:java}
      2010-01-31 00:44:49,980 TRACE [org.jboss.mcann.repository.GenericAnnotationResourceVisitor] (HDScanner) Scanning class org.jboss.jsr299.tck.tests.definition.qualifier.enterprise.BorderCollie for annotations
      2010-01-31 00:44:49,984 TRACE [org.jboss.mcann.repository.GenericAnnotationResourceVisitor] (HDScanner) Exception reading resource: org/jboss/jsr299/tck/tests/definition/qualifier/enterprise/BorderCollie.class: java.lang.RuntimeException: Error retrieving annotation attribute values
        at org.jboss.reflect.plugins.AnnotationValueFactory.createAnnotationValue(AnnotationValueFactory.java:107)
        at org.jboss.reflect.plugins.introspection.IntrospectionTypeInfoFactoryImpl.createAnnotationValue(IntrospectionTypeInfoFactoryImpl.java:137)
        at org.jboss.reflect.plugins.introspection.IntrospectionTypeInfoFactoryImpl.getAnnotations(IntrospectionTypeInfoFactoryImpl.java:130)
        at org.jboss.reflect.plugins.InheritableAnnotationHolder.getDeclaredAnnotations(InheritableAnnotationHolder.java:95)
        at org.jboss.reflect.plugins.ClassInfoImpl.getAnnotations(ClassInfoImpl.java:179)
        at org.jboss.reflect.plugins.AbstractAnnotatedInfo.getUnderlyingAnnotations(AbstractAnnotatedInfo.java:63)
        at org.jboss.mcann.repository.GenericAnnotationResourceVisitor.handleClass(GenericAnnotationResourceVisitor.java:154)
      ...//removed irrelevant stacktrace
      Caused by: java.lang.IllegalAccessException: Class org.jboss.reflect.plugins.introspection.ReflectionUtils can not access a member of class org.jboss.jsr299.tck.tests.definition.qualifier.enterprise.Hairy with modifiers "public abstract"
        at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
        at java.lang.reflect.Method.invoke(Method.java:588)
        at org.jboss.reflect.plugins.introspection.ReflectionUtils.invoke(ReflectionUtils.java:59)
        at org.jboss.reflect.plugins.introspection.ReflectMethodInfoImpl.invoke(ReflectMethodInfoImpl.java:151)
        at org.jboss.reflect.plugins.AnnotationValueFactory.createAnnotationValue(AnnotationValueFactory.java:100)
        ... 60 more
      {code}
      
      The root cause of this issue seems to be a problem in JBREFLECT, which runs into issue trying to create annotation value information out of a non-public annotation (notice that "Hairy" in the above example is non-public). 
      
      
      I was able to reproduce this issue with a simple testcase in JBREFLECT trunk:
      
      {code:java}
      Index: src/test/java/org/jboss/test/classinfo/annotation/test/NonPublicAnnotationTestCase.java
      ===================================================================
      --- src/test/java/org/jboss/test/classinfo/annotation/test/NonPublicAnnotationTestCase.java     (revision 0)
      +++ src/test/java/org/jboss/test/classinfo/annotation/test/NonPublicAnnotationTestCase.java     (revision 0)
      @@ -0,0 +1,89 @@
      +/**
      + * 
      + */
      +package org.jboss.test.classinfo.annotation.test;
      +
      +import java.lang.annotation.Annotation;
      +
      +import junit.framework.Test;
      +
      +import org.jboss.reflect.plugins.introspection.IntrospectionTypeInfoFactory;
      +import org.jboss.reflect.spi.ClassInfo;
      +import org.jboss.reflect.spi.TypeInfo;
      +import org.jboss.reflect.spi.TypeInfoFactory;
      +import org.jboss.test.ContainerTest;
      +
      +/**
      + * NonPublicAnnotationTestCase
      + * 
      + * Tests that a Class annotated with a non-public annotation is
      + * correctly parsed for annotations. 
      + * 

      + * This test is responsible the testing fix for the bug in JBREFLECT + * which causes the following exception: + *

      + *

      + * Caused by: java.lang.IllegalAccessException: Class org.jboss.reflect.plugins.introspection.ReflectionUtils can not access a member of class org.jboss.test.classinfo.annotation.test.NonPublicAnnotation with modifiers "public abstract" + *   at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65) + *   at java.lang.reflect.Method.invoke(Method.java:588) + *   at org.jboss.reflect.plugins.introspection.ReflectionUtils.invoke(ReflectionUtils.java:59) + *   at org.jboss.reflect.plugins.introspection.ReflectMethodInfoImpl.invoke(ReflectMethodInfoImpl.java:151) + *   at org.jboss.reflect.plugins.AnnotationValueFactory.createAnnotationValue(AnnotationValueFactory.java:100) + *   ... 33 more + * + *

      + * @author Jaikiran Pai + * @version $Revision: $ + */ +public class NonPublicAnnotationTestCase extends ContainerTest +{ +   /** +    * Constructor +    * @param name +    */ +   public NonPublicAnnotationTestCase(String name) +   { +      super(name); +   } +   +   public static Test suite() +   { +      return suite(NonPublicAnnotationTestCase.class); +   } + +   /** +    * Test that the {@link NonPublicAnnotation} when applied to {@link SimpleAnnotatedClass} +    * is correctly parsed and is available through {@link ClassInfo#getUnderlyingAnnotations()} +    * +    * @throws Exception +    */ +   public void testNonPublicAnnotationOnClass() throws Exception +   { +      ClassInfo info = getClassInfo(SimpleAnnotatedClass.class); +      +      System.out.println("---> Getting annotations"); +      Annotation[] annotations = info.getUnderlyingAnnotations(); +      assertEquals("Incorrect number of annotations found on " + SimpleAnnotatedClass.class, 1, annotations.length); +      Annotation annotation = annotations[0]; +      assertTrue("Incorrect annotation type found on " + SimpleAnnotatedClass.class, (annotation instanceof NonPublicAnnotation)); +      NonPublicAnnotation expectedAnnotation = (NonPublicAnnotation) annotation; +      assertEquals("Incorrect value found for annotation " + NonPublicAnnotation.class +            + " on class " + SimpleAnnotatedClass.class, SimpleAnnotatedClass.ANNOTATION_ELEMENT_NAME, expectedAnnotation.name()); +   } + +   /** +    * Utitlity method +    * @param clazz +    * @return +    */ +   protected ClassInfo getClassInfo(Class clazz) +   { +      TypeInfoFactory factory = new IntrospectionTypeInfoFactory(); +      TypeInfo info = factory.getTypeInfo(clazz); +      assertNotNull(info); +      assertTrue(info instanceof ClassInfo); +      ClassInfo cinfo = (ClassInfo) info; +      getLog().debug(cinfo); +      return cinfo; +   } +} Index: src/test/java/org/jboss/test/classinfo/annotation/test/NonPublicAnnotation.java =================================================================== --- src/test/java/org/jboss/test/classinfo/annotation/test/NonPublicAnnotation.java     (revision 0) +++ src/test/java/org/jboss/test/classinfo/annotation/test/NonPublicAnnotation.java     (revision 0) @@ -0,0 +1,31 @@ +/** + * + */ +package org.jboss.test.classinfo.annotation.test; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * NonPublicAnnotation + * + * A non-public annotation. + * + * NOTE: This {@link NonPublicAnnotation}, {@link SimpleAnnotatedClass} and + * even the {@link NonPublicAnnotationTestCase} are all placed in the same package + * *intentionally*, so that this annotation is accessible for the {@link SimpleAnnotatedClass} + * as well as {@link NonPublicAnnotationTestCase} during the tests. + * + * @author Jaikiran Pai + * @version $Revision: $ + */ +@Target (ElementType.TYPE) +@Retention (RetentionPolicy.RUNTIME) +@interface NonPublicAnnotation { + +   public abstract String name(); +   +   public abstract boolean isTest(); +} Index: src/test/java/org/jboss/test/classinfo/annotation/test/SimpleAnnotatedClass.java =================================================================== --- src/test/java/org/jboss/test/classinfo/annotation/test/SimpleAnnotatedClass.java     (revision 0) +++ src/test/java/org/jboss/test/classinfo/annotation/test/SimpleAnnotatedClass.java     (revision 0) @@ -0,0 +1,25 @@ +/** + * + */ +package org.jboss.test.classinfo.annotation.test; + + +/** + * SimpleAnnotatedClass + * + * A simple class used in tests + * + * NOTE: The {@link NonPublicAnnotation}, {@link SimpleAnnotatedClass} and + * even the {@link NonPublicAnnotationTestCase} are all placed in the same package + * *intentionally*, so that this annotation is accessible for the {@link SimpleAnnotatedClass} + * as well as {@link NonPublicAnnotationTestCase} during the tests. + * + * @author Jaikiran Pai + * @version $Revision: $ + */ +@NonPublicAnnotation (name=SimpleAnnotatedClass.ANNOTATION_ELEMENT_NAME, isTest=true) +public class SimpleAnnotatedClass +{ + +   public static final String ANNOTATION_ELEMENT_NAME = "SomeName"; +} {code} The testcase fails with the same exception: {code:java} ------------------------------------------------------------------------------- Test set: org.jboss.test.classinfo.annotation.test.NonPublicAnnotationTestCase ------------------------------------------------------------------------------- Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.141 sec <<< FAILURE! testNonPublicAnnotationOnClass(org.jboss.test.classinfo.annotation.test.NonPublicAnnotationTestCase)  Time elapsed: 0.066 sec  <<< ERROR! java.lang.RuntimeException: Error retrieving annotation attribute values ...//removed irrelevant stacktrace   at org.apache.maven.surefire.booter.SurefireBooter.main(SurefireBooter.java:1009) Caused by: java.lang.IllegalAccessException: Class org.jboss.reflect.plugins.introspection.ReflectionUtils can not access a member of class org.jboss.test.classinfo.annotation.test.NonPublicAnnotation with modifiers "public abstract"   at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)   at java.lang.reflect.Method.invoke(Method.java:588)   at org.jboss.reflect.plugins.introspection.ReflectionUtils.invoke(ReflectionUtils.java:59)   at org.jboss.reflect.plugins.introspection.ReflectMethodInfoImpl.invoke(ReflectMethodInfoImpl.java:151)   at org.jboss.reflect.plugins.AnnotationValueFactory.createAnnotationValue(AnnotationValueFactory.java:100)    ... 33 more {code} Is there a way to disable Java language access check for the methods through JBREFLECT project? Or does that have to be done (selectively) through the underlying (ReflectMethodInfoImpl) impl?   
        • 1. Re: JBREFLECT - Exception processing non-public annotation
          alesj

          Is there a way to disable Java language access check for the methods through JBREFLECT project? Or does that have to be done (selectively) through the underlying (ReflectMethodInfoImpl) impl?  

          Disabling by default would create a security hole. ;-)

           

          Can you please push this test into the Reflect trunk (commenting it atm),

          and I'll have a look.

          • 3. Re: JBREFLECT - Exception processing non-public annotation
            alesj

            I added this patch.

             

            Index: src/main/java/org/jboss/reflect/plugins/introspection/ReflectMethodInfoImpl.java
            ===================================================================
            --- src/main/java/org/jboss/reflect/plugins/introspection/ReflectMethodInfoImpl.java    (revision 94787)
            +++ src/main/java/org/jboss/reflect/plugins/introspection/ReflectMethodInfoImpl.java    Mon Feb 01 15:03:48 CET 2010
            @@ -101,12 +101,17 @@
                 */
                public void setMethod(Method method)
                {
            +      boolean isDeclaringClassPublic = true;
                   if (method != null)
            +      {
                      accessCheck(Modifier.isPublic(method.getModifiers()));
            +         isDeclaringClassPublic = isDeclaringClassPublic(method);
            +         accessCheck(isDeclaringClassPublic);
            +      }
             
                   this.method = method;
             
            -      if (isPublic() == false && method != null)
            +      if (method != null && (isPublic() == false || isDeclaringClassPublic == false))
                      setAccessible();
                }
             
            @@ -120,13 +125,25 @@
                   accessCheck();
                   return method;
                }
            -   
            +
                /**
            +    * Is declaring class public.
            +    *
            +    * @param method the method
            +    * @return if declaring class is public return true, else false
            +    */
            +   protected static boolean isDeclaringClassPublic(Method method)
            +   {
            +      Class<?> owner = method.getDeclaringClass();
            +      return Modifier.isPublic(owner.getModifiers());
            +   }
            +
            +   /**
                 * Check access permission.
                 */
                protected final void accessCheck() // final because we don't want subclasses to disable it
                {
            -      accessCheck(isPublic());
            +      accessCheck(isPublic() && (method == null || isDeclaringClassPublic(method)));
                }
             
                /**