8 Replies Latest reply on Aug 20, 2008 11:12 AM by alesj

    Using AnnotationEnvironment in Seam

    alesj

      I'll describe a bit how AnnotationEnvoroment works, where it comes from, ...
      but then it should be up to Seam guys to fully incorporate this into Seam's
      component scanning / lookup.

      AnnotationEnv API:

      public interface AnnotationEnvironment
      {
       /**
       * Does this annotation environment contain a class
       * which is annotated with annotation parameter.
       * This only applies to annotations for ElementType.TYPE level.
       *
       * This method should be used if we have no intention
       * to do real lookup of annotated classes, but we're
       * only interested in existance of the annotation.
       * e.g. deployment unit contains @Stateful EJBs
       *
       * @param annotation the annotation we're querying for
       * @return true if there exists a class with annotation param
       * @see #hasClassAnnotatedWith(Class annotation)
       */
       boolean hasClassAnnotatedWith(Class<? extends Annotation> annotation);
      
       /**
       * Does this annotation environment contain a class
       * which is annotated with annotation parameter.
       * This only applies to annotations for ElementType.TYPE level.
       *
       * This method should be used if we have no intention
       * to do real lookup of annotated classes, but we're
       * only interested in existance of the annotation.
       * e.g. deployment unit contains @Stateful EJBs
       *
       * @param annotationName the annotation name we're querying for
       * @return true if there exists a class with annotation param
       * @see #hasClassAnnotatedWith(Class annotation)
       */
       boolean hasClassAnnotatedWith(String annotationName);
      
       /**
       * Get all classes annotated with annotation param.
       *
       * @param <A> the annotation type
       * @param annotation the annotation we're querying for
       * @return set of matching classes
       */
       <A extends Annotation> Set<Element<A, Class<?>>> classIsAnnotatedWith(Class<A> annotation);
      
       /**
       * Get all classes annotated with annotation param.
       *
       * @param annotationName the annotation name we're querying for
       * @return set of matching classes
       */
       Set<Element<Annotation, Class<?>>> classIsAnnotatedWith(String annotationName);
      
       /**
       * Get all classes who have some constructor annotated with annotation param.
       *
       * @param <A> the annotation type
       * @param annotation the annotation we're querying for
       * @return set of matching classes
       */
       <A extends Annotation> Set<Element<A, Constructor<?>>> classHasConstructorAnnotatedWith(Class<A> annotation);
      
       /**
       * Get all classes who have some constructor annotated with annotation param.
       *
       * @param annotationName the annotation name we're querying for
       * @return set of matching classes
       */
       Set<Element<Annotation, Constructor<?>>> classHasConstructorAnnotatedWith(String annotationName);
      
       /**
       * Get all classes who have some field annotated with annotation param.
       *
       * @param <A> the annotation type
       * @param annotation the annotation we're querying for
       * @return set of matching classes
       */
       <A extends Annotation> Set<Element<A, Field>> classHasFieldAnnotatedWith(Class<A> annotation);
      
       /**
       * Get all classes who have some field annotated with annotation param.
       *
       * @param annotationName the annotation name we're querying for
       * @return set of matching classes
       */
       Set<Element<Annotation, Field>> classHasFieldAnnotatedWith(String annotationName);
      
       /**
       * Get all classes who have some method annotated with annotation param.
       *
       * @param <A> the annotation type
       * @param annotation the annotation we're querying for
       * @return set of matching classes
       */
       <A extends Annotation> Set<Element<A, Method>> classHasMethodAnnotatedWith(Class<A> annotation);
      
       /**
       * Get all classes who have some method annotated with annotation param.
       *
       * @param annotationName the annotation name we're querying for
       * @return set of matching classes
       */
       Set<Element<Annotation, Method>> classHasMethodAnnotatedWith(String annotationName);
      
       /**
       * Get all classes who have some method's/constructor's parameter annotated with annotation param.
       *
       * @param <A> the annotation type
       * @param annotation the annotation we're querying for
       * @return set of matching classes
       */
       <A extends Annotation> Set<Element<A, AnnotatedElement>> classHasParameterAnnotatedWith(Class<A> annotation);
      
       /**
       * Get all classes who have some method's/constructor's parameter annotated with annotation param.
       *
       * @param annotationName the annotation name we're querying for
       * @return set of matching classes
       */
       Set<Element<Annotation, AnnotatedElement>> classHasParameterAnnotatedWith(String annotationName);
      }
      
      public interface Element<A extends Annotation, M extends AnnotatedElement>
      {
       /**
       * Get the owner class name.
       *
       * Until we hit getOwner method the class should not be loaded.
       *
       * @return the owner classname
       */
       String getOwnerClassName();
      
       /**
       * Get the annotation owner class.
       *
       * @return the annotation owner class
       */
       Class<?> getOwner();
      
       /**
       * Get the annotation instance.
       *
       * @return the annotation instance
       */
       A getAnnotation();
      
       /**
       * Get the annotated element that holds the annotation.
       *
       * @return the annotated element instance
       */
       M getAnnotatedElement();
      }
      

      The API is pretty simple :-),
      as can be seen it has to 'flavors',
      one where you put direct annotation class as parameter,
      the other one where you put annotation class name.

      The Element has a few useful hooks,
      and its impl detail is that it's completely lazy -
      it doesn't load the class or annotation until asked.

      There is a flag on the annotation scanning deployer,
      that allows for AnnotationEnvironment to already keep
      the annotation instance gathered from Javassist lookup -
      meaning you don't even need to load the class to
      inspect annotation value.
      e.g. you only need classname + annotation member values

      When (annotation scanning) deployers are properly configured,
      this is already the case in current JBoss5 trunk,
      every deployment unit gets AnnotationEnvironment as an attachment.

       DeploymentUnit unit = getAttribute(context, DeploymentUnit.class.getName(), DeploymentUnit.class);
       AnnotationEnvironment env = unit.getAttachment(AnnotationEnvironment.class);
      


      In the Seam case we would have to do a bit of
      hierarchy traversing to get all the AnnotationEnvironments,
      from all deployment units, since we only have web deployment unit,
      in the ServletContext attributes.

      public class JBossSeamListener extends SeamListener
      {
       public void contextInitialized(ServletContextEvent event)
       {
       // TODO - enable some MC scanning notion in Seam
       super.contextInitialized(event);
      
       ServletContext context = event.getServletContext();
       Kernel kernel = getAttribute(context, Kernel.class.getName(), Kernel.class);
       DeploymentUnit unit = getAttribute(context, DeploymentUnit.class.getName(), DeploymentUnit.class);
      


      Looking at the ComponentDeploymentHandler::handle
       public void handle(String name, ClassLoader classLoader)
       {
       if (name.endsWith(".class"))
       {
       String classname = filenameToClassname(name);
       String filename = componentFilename(name);
       try
       {
       ClassFile classFile = getClassFile(name, classLoader);
       boolean installable = ( hasAnnotation(classFile, Name.class) || classLoader.getResources(filename).hasMoreElements() )
       && !"false".equals( getAnnotationValue(classFile, Install.class, "value") );
       if (installable)
       {
       log.trace("found component class: " + name);
       classes.add( (Class<Object>) classLoader.loadClass(classname) );
       }
      

      Looks like we should also do a bit more than just looking for @Name,
      but for the @Install::value==false it should be just a simple Set intersection.
      Where I don't see what exactly does "classLoader.getResources(filename).hasMoreElements() " do/help.

      Pete & co., if you need any other info,
      post it here. :-)

        • 1. Re: Using AnnotationEnvironment in Seam
          pmuir

          Here are some questions/comments:

          1) We currently only scan artifacts which have seam.properties (or META-INF/seam.properties or META-INF/components.xml). This includes:

          * WEB-INF/classes
          * Any lib in WEB-INF/lib
          * Any lib declared in the manifest
          * Any lib declared in the ear (e.g. in lib/, declared as an ejb in application.xml, declared in the manifest)
          * Any lib in our parent classloaders

          How do we replicate this behaviour?

          2) We have other deployment handlers

          e.g. org.jboss.seam.deployment.ComponentsXmlDeploymentHandler::handle

          public void handle(String name, ClassLoader classLoader)
           {
           if (name.endsWith(".component.xml") || name.endsWith("/components.xml"))
           {
           // we want to skip over known meta-directories since Seam will
           // auto-load these without a scan
           if (!name.startsWith("WEB-INF/") && !name.startsWith("META-INF/"))
           {
           resources.add(name);
           }
           }
          
           }


          For this approach to decrease deployment time, we need to not scan at all (or scan a very limited set of files), so can we get this metadata (files, based on their filename) from the MC?

          If you want to look at the handlers which are relevant:

          * org.jboss.seam.deployment.AnnotationDeploymentHandler (support for users to specify their own annotations to handle, the AnnotationEnv backed version can just be a facade)
          * org.jboss.seam.deployment.ComponentDeploymentHandler
          * org.jboss.seam.deployment.ComponentsXmlDeploymentHandler
          * org.jboss.seam.deployment.DotPageDotXmlDeploymentHandler
          * org.jboss.seam.deployment.NamespaceDeploymentHandler
          * org.jboss.seam.bpm.PageflowDeploymentHandler

          3) For the future, can we add support for metaannotations support? This will make it supereasy to use for WB.

          E.g.

          public interface AnnotationEnvironment
          {
           ...
           boolean hasClassMetaAnnotatedWith(Class<? extends Annotation> annotation);
          
           // etc.
          


          annotationEnvironment.hasClassMetaAnnotatedWith(javax.webbeans.DeploymentType.class);


          4) How does it handle exceptions loading classes? Throw them for us to catch?


          • 2. Re: Using AnnotationEnvironment in Seam
            alesj

             

            "pete.muir@jboss.org" wrote:

            1) We currently only scan artifacts which have seam.properties (or META-INF/seam.properties or META-INF/components.xml). This includes:

            * WEB-INF/classes
            * Any lib in WEB-INF/lib
            * Any lib declared in the manifest
            * Any lib declared in the ear (e.g. in lib/, declared as an ejb in application.xml, declared in the manifest)
            * Any lib in our parent classloaders

            How do we replicate this behaviour?

            Basically you're saying you scan the whole classpath,
            but only do real lookup from the root of those deployments/classpath entries
            that have some Seam specific resource present?

            The AnnEnv handles this already since it does lookup at the classloading layer,
            we could perhaps exclude deployments that don't contain any Seam specific resource before we actually do some reading from AnnEnv.

            "pete.muir@jboss.org" wrote:

            2) We have other deployment handlers

            e.g. org.jboss.seam.deployment.ComponentsXmlDeploymentHandler::handle

            public void handle(String name, ClassLoader classLoader)
             {
             if (name.endsWith(".component.xml") || name.endsWith("/components.xml"))
             {
             // we want to skip over known meta-directories since Seam will
             // auto-load these without a scan
             if (!name.startsWith("WEB-INF/") && !name.startsWith("META-INF/"))
             {
             resources.add(name);
             }
             }
            
             }


            For this approach to decrease deployment time, we need to not scan at all (or scan a very limited set of files), so can we get this metadata (files, based on their filename) from the MC?

            If you want to look at the handlers which are relevant:

            * org.jboss.seam.deployment.AnnotationDeploymentHandler (support for users to specify their own annotations to handle, the AnnotationEnv backed version can just be a facade)
            * org.jboss.seam.deployment.ComponentDeploymentHandler
            * org.jboss.seam.deployment.ComponentsXmlDeploymentHandler
            * org.jboss.seam.deployment.DotPageDotXmlDeploymentHandler
            * org.jboss.seam.deployment.NamespaceDeploymentHandler
            * org.jboss.seam.bpm.PageflowDeploymentHandler

            We could add this in a similar way we do annotation scanning -
            as ResourceVisitor on Module.
            Or we could visit deployment's root,
            excluding deployment's metadata locations.

            The result should be then put as attachment to deployment unit's attachments.

            Let me know what exactly you need,
            and I'll try to put the code into seam-int/microcontainer module.

            Or you can do it, and learn a few tricks about MC. :-)

            "pete.muir@jboss.org" wrote:

            3) For the future, can we add support for metaannotations support? This will make it supereasy to use for WB.

            E.g.

            public interface AnnotationEnvironment
            {
             ...
             boolean hasClassMetaAnnotatedWith(Class<? extends Annotation> annotation);
            
             // etc.
            

            annotationEnvironment.hasClassMetaAnnotatedWith(javax.webbeans.DeploymentType.class);


            You mean this:
            @DeploymentType
            public @interface SomeType {}
            
            @SomeType
            public class MyTypeImpl {}
            

            Where you would like to get MyTypeImpl class?

            Should be easy to add.
            I'll have a look.

            "pete.muir@jboss.org" wrote:

            4) How does it handle exceptions loading classes? Throw them for us to catch?

            AnnEnv doesn't load classes. ;-)
            But it will throw RuntimeException when you ask for it and it fails.
            Annotation scanning deployer by default doesn't fail if it has some problems
            reading the annotations. But this is configurable.

            • 3. Re: Using AnnotationEnvironment in Seam
              pmuir

               

              "alesj" wrote:
              Basically you're saying you scan the whole classpath,
              but only do real lookup from the root of those deployments/classpath entries
              that have some Seam specific resource present?


              Yes, we do this to limit the scanning (which in this case isn't relevant), but we need to preserve the behaviour I think.

              The AnnEnv handles this already since it does lookup at the classloading layer, we could perhaps exclude deployments that don't contain any Seam specific resource before we actually do some reading from AnnEnv.


              Sounds good.

              "alesj" wrote:
              "pete.muir@jboss.org" wrote:
              so can we get this metadata (files, based on their filename) from the MC?



              We could add this in a similar way we do annotation scanning -
              as ResourceVisitor on Module.
              Or we could visit deployment's root,
              excluding deployment's metadata locations.

              The result should be then put as attachment to deployment unit's attachments.

              Let me know what exactly you need,
              and I'll try to put the code into seam-int/microcontainer module.

              Currently, I need a way to get all files that end with a suffix (like ".page.xml" or ".jpdl.xml") that exist in archives which are marked to be scanned by Seam (as discussed above).

              "alesj" wrote:
              "pete.muir@jboss.org" wrote:

              3) For the future, can we add support for metaannotations support? This will make it supereasy to use for WB.


              You mean this:
              @DeploymentType
              public @interface SomeType {}
              
              @SomeType
              public class MyTypeImpl {}
              

              Where you would like to get MyTypeImpl class?

              Should be easy to add.
              I'll have a look.


              Exactly.

              "alesj" wrote:
              "pete.muir@jboss.org" wrote:

              4) How does it handle exceptions loading classes? Throw them for us to catch?

              AnnEnv doesn't load classes. ;-)
              But it will throw RuntimeException when you ask for it and it fails.
              Annotation scanning deployer by default doesn't fail if it has some problems
              reading the annotations. But this is configurable.


              • 4. Re: Using AnnotationEnvironment in Seam
                pmuir

                FYI, my plan for how to integrate this with Seam is:

                * to make the deployment handlers expose metadata about the resource (file or class) they want to handle
                - an annotation on the class
                - a filename suffix they want to match against
                - whether the metadata is complete

                * If any handler declares the metadata incomplete, then we must switch back to normal scanning. All handlers built into Seam will be metadata complete.

                * If we are using AnnotationEnv (and equivalent for other resources), then we simply use the metadata the handler exposes to request the Set of files.

                "alesj" wrote:
                Where I don't see what exactly does "classLoader.getResources(filename).hasMoreElements() " do/help.


                Checks to see if a .component.xml exists for this class. If it does, then the component installation is decided later, when that file is read.

                • 5. Re: Using AnnotationEnvironment in Seam
                  epbernard

                  I am not sure where we will get the speed improvement but if it's by limiting the amount of classes we scan manually, the we probably need:

                  - Getting classes/method/ctrs annotated with meta annotations is useful.
                  - Getting intersections of annotated classes too: get all class with @Named and @Startup, could be useful too (not entirely sure but I can see a couple of use cases)
                  - Getting a class hierarchy: its often that you need to build a class hierarchy based on a given class, might be useful as a service?

                  • 6. Re: Using AnnotationEnvironment in Seam
                    alesj

                     

                    "epbernard" wrote:

                    - Getting intersections of annotated classes too: get all class with @Named and @Startup, could be useful too (not entirely sure but I can see a couple of use cases)

                    You can do intersections later on,
                    since I don't see how could I make this more efficient,
                    because I always need to go over all classes/annotations - e.g. for other non-Seam users.

                    You can probably then create your own Element,
                    implementing hash and equals,
                    so that Set operations work as they should.

                    "epbernard" wrote:

                    - Getting a class hierarchy: its often that you need to build a class hierarchy based on a given class, might be useful as a service?

                    I don't understand what exactly you mean here?

                    • 7. Re: Using AnnotationEnvironment in Seam
                      epbernard

                      with this class hierarchy
                      Person <- Customer
                      <- Employee
                      Employee / Customer and Person are marked as @Entity.

                      I need to know the hierarchy relation between them. Should I load the classes and build the class hierarchy, or should this be a service. I guess it does not matter a lot s I need to load the classes in this case.

                      • 8. Re: Using AnnotationEnvironment in Seam
                        alesj

                         

                        "epbernard" wrote:

                        Should I load the classes and build the class hierarchy, or should this be a service. I guess it does not matter a lot s I need to load the classes in this case.

                        Aha, I see what you mean.

                        I could port AnnotationEnvironment to use our Reflect propject - ClassInfo, MethodInfo, FieldInfo, ...
                        Then it would be up to the underlying impl if it would load the class,
                        e.g. Javassist flavor of Reflect would not load it - if configured properly.

                        But this would mean all projects that integrate with AnnEnv should know about Reflect.

                        I'll see how much work is to have both impls,
                        with minimal code changes/house-keeping.