9 Replies Latest reply on Feb 24, 2010 11:26 AM by Kabir Khan

    Web beans on top of Microcontainer deployment order

    Kabir Khan Master

      I've got a very basic prototype up and running, but something just occurred to me, take the following scenario:

      ctx A has an ambiguous dependency which might be fulfilled by ctx B & C.

      A is deployed first and stays in INSTANTIATED state.
      We deploy B and A can be go to INSTALLED.
      Right after B we deploy C, but A has already deployed successfully.
      All is well.

      But with a different deployment order (determined by how the WB deployer orders deployment of bean, e.g. by name) we might have:
      B gets deployed
      C gets deployed
      A is deployed, but finds it has an ambiguous dependency and ends up in ERROR. This is what really should happen.

      So depending on the order classes are installed in the controller it might or might not work. I have no clue how WBs are deployed atm, but maybe we need to do a scan of all classes and do a sort of the dependency ordering?

      I've not yet tried to solve this, just wanted to record it. If the web beans guys have any pointers to where this is done in web beans itself that would be great.

        • 1. Re: Web beans on top of Microcontainer deployment order
          Ales Justin Master

          OK, to first clear up what my idea of MC+WB looks like. ;-)

          It's split into two things, pretty much similar to what we do with Guice-int,
          apart from the fact that in this case we "control" the WB spi and its integration.

          1) MC should control WB beans lifecycle, while reusing its super fancy DI mechanisms
          * instantiation - via @Provides or other WB fanciness
          * configuration - all the rich WB injection stuff
          * pre, post lifecycle callbacks

          The issue you're describing definitely comes into play here.
          But I guess we can somehow halt it until WB knows all of its pieces.
          * e.g. similar on how we do AOP describe
          * or have some WB spi to give us enough info to avoid/report such issues

          2) Bijection (using Seam's copy right :-))
          * MC --> WB

          This probably belongs into existing WB-RI-int project.
          e.g. integrating underlying Controller while we deploy new Bootstrap bean.

          * WB --> MC

          In the worst case this can again be KernelRegistryPlugin.
          But I would like us to inspect all of possibilities before falling back to this one.


          Simple CDI "how to" from Pete:
          - http://www.seamframework.org/Documentation/HowDoIDoNoncontextualInjectionForAThirdpartyFramework

          • 2. Re: Web beans on top of Microcontainer deployment order
            Kabir Khan Master

            I have a prototype of WB -> MC injection working now. The core of it is this class which is a modified version of http://www.seamframework.org/Documentation/HowDoIDoNoncontextualInjectionForAThirdpartyFramework

            public class WebBeansInjector<T> {
            
             private final InjectionTarget<T> it;
             private CreationalContext<T> creationalContext;
            
             WebBeansInjector(BeanManager manager, Class<T> clazz) {
             AnnotatedType<T> type = manager.createAnnotatedType(clazz);
             it = manager.createInjectionTarget(type);
             creationalContext = manager.createCreationalContext(null);
             }
            
             Set<InjectionPoint> getInjectionPoints()
             {
             return it.getInjectionPoints();
             }
            
             T instantiate()
             {
             T t = it.produce(creationalContext);
             return t;
             }
            
             void inject(Object instance)
             {
             it.inject((T)instance, creationalContext);
             }
            
             void postConstruct(Object instance) {
             it.postConstruct((T)instance);
             }
            
             void preDestroy(T instance) {
             it.preDestroy(instance);
             creationalContext.release();
             }
            }
            


            If we deploy the following class as an MC bean
            class McBeanUsingWebBeans{
             @Current SomeWebBean someWebBean;
            
             @Current AnotherWebBean anotherWebBean;
            
             McUsingWebBeans(@Current AnotherWebBean anotherWebBean){
             this.anotherWebBean = anotherWebBean;
             }
            }
            


            This is wrapped up in a custom KernelControllerContext (WebBeansKernelControllerContext). It uses some custom actions:

            *** WebBeansDescribeAction extends DescribeAction
            Delegates to the super DescribeAction for the normal MC describe functionality. It creates the WebBeansInjector and sets that in the context. It then calls WBI.getInjectionPoints() and for each injection point creates a WebBeansDependencyMetaData that is added to the context's BeanMetaData.
            TODO: Remove the dependencies on uninstall

            *** WebBeansInstantiateAction extends InstantiateAction
            Calls WBI.instantiate() which instantiates the bean with constructor injection of the beans found in web beans, and sets the context's target.
            TODO: Some more checks here to see if the constructor should have web beans injection (use current WBI instantiation mechanism) or MC injection of parameters (use MC instantiation mechanism). I don't think we can mix and match the injection models here, meaning we can't have a constructor with some parameters from web beans and others with parameters from MC?

            *** WebBeansConfigureAction extends ConfigureAction
            Delegates to the super ConfigureAction for the normal MC configuration of properties. It then calls WBI.inject, which inject the web beans into the properties.
            TODO: Null out the web beans fields on uninstall manually since web beans does not provide this for us
            TODO: Test with setter injection

            *** WebBeansPostConstructAction ???
            I need to invoke WBI.postConstruct()/preDestroy() from somewhere, but am unsure if I should do that as part of CREATE, START or if I need a new state. If I need a new state, where should that go?

            The WebBeansDependencyMetaData ends up as a WebBeansDependencyItem, which has reference to a InjectionPoint. It's resolve method delegates checking if the dependency is satisfied by delegating to the Web Beans BeanManager:
             public boolean resolve(Controller controller)
             {
             try
             {
             context.getManager().validate(injectionPoint);
             setIDependOn(injectionPoint);
            // addDependsOnMe(controller, context);
             setResolved(true);
             }
             catch(ValidationException e)
             {
             setResolved(false);
             }
            
             return isResolved();
             }
            

            TODO: Some tests for unsatisfied dependencies

            TODO: Discussed with Pete to decorate the AnnotatedType to be able to get annotations from MDR as well as those on the class.

            TODO: Check @Initializer methods get called

            • 3. Re: Web beans on top of Microcontainer deployment order
              Pete Muir Master

              Looks good to me. Only comment I have from the WB side is that creating an injection target should be per bean class, whilst the creational context should be per bean instance (unless you only ever have singletons of the bean of course) as currently I don't cache created injection targets.

              • 4. Re: Web beans on top of Microcontainer deployment order
                Ales Justin Master

                Perhaps instead of directly extending existing Actions,
                you could extract the over-lapping functionality into new super classes?

                "kabir.khan@jboss.com" wrote:

                *** WebBeansPostConstructAction ???
                I need to invoke WBI.postConstruct()/preDestroy() from somewhere, but am unsure if I should do that as part of CREATE, START or if I need a new state. If I need a new state, where should that go?

                I looks like you would need two new states.
                POST_CONSTRUCT = after INSTANTAITE, before CONFIGURED (only @ install)
                PRE_DESTROY = after CREATE, before START (only @ uninstall)

                You can simply add new states to Controller, within some WB+MC singleton.
                See DeployersImpl for example.

                "kabir.khan@jboss.com" wrote:

                 public boolean resolve(Controller controller)
                 {
                 try
                 {
                 context.getManager().validate(injectionPoint);
                 setIDependOn(injectionPoint);
                // addDependsOnMe(controller, context);
                 setResolved(true);
                 }
                 catch(ValidationException e)
                 {
                 setResolved(false);
                 }
                
                 return isResolved();
                 }
                


                Pete, could we have here something that doesn't throw exception in order to see if it's resolved.
                e.g.
                setResolved(context.getManager().canResolve(injectionPoint));
                


                • 5. Re: Web beans on top of Microcontainer deployment order
                  Kabir Khan Master

                  I have committed the initial prototype for the web beans integration. It can be found here https://svn.jboss.org/repos/jbossas/projects/kernel/trunk/webbeans-int.

                  MC beans that should interact with web beans are wrapped in a WebBeansKernelControllerContext as mentioned before. It contains additional controller context actions to instantiate/inject an MC bean with web beans. It also contains an additional controller context action to store mc beans that should be available for injection to web beans in a registry. It contains a web beans AfterBeanDiscovery implementation that is used to add the mc beans to the web beans bean manager. The mc beans added to web beans are done in a custom web beans Bean implementation that takes the existing instance from mc, ignoring the calls to create it. When running in AS there will be a loads and loads of MC beans, which might unintentionally conflict, so we need to find a good way to narrow down which MC beans are available for injection into web beans. Currently, I am only registering MC beans that have web beans annotations on them (ignoring producer methods), but am open for suggestions on how to improve this.

                  I am unsure how this fits together in the real world though. The WebBeansKernelControllerContext needs access to a bean manager to be able to call createCreationalContext(), createInjectionTarget() and createAnnotatedType().

                  For cases when we want to inject MC->WB, the MC beans need to be deployed and 'registered' before the web beans are deployed, and the actual bean manager to be used is created during the deployment process. I have hacked around this for now, by creating a 'bootstrap' bean manager in my tests that is available before web beans are deployed. Luckily, none of the BeanManager methods needed for this process (createAnnotatedType()) do anything with the internal state of the bean manager, so they work.

                  For WB->MC injection, we need access to the correct bean manager that should be used to inject the MC beans, so createCreationalContext() and createInjectionTarget() can be called. I think for this to work we need access to the 'real' BeanManager. So, how do we determine which BeanManager should be used?

                  The registry mentioned currently stores the context names and web beans Bean implementations, but we probably need to take scoped controllers into account there. So how do we determine which controller should be used for a particular bean manager?

                  For MC->WB injection, the MC beans need to be deployed first so that they are registered with the bean manager. For WB->MC injection, the WB beans need to be deployed first since the MC WebBeansKernelControllerContext gets initialised with the bean manager on creation. I think the MC beans needing to be deployed first is pretty definite, but it would be good if the bean manager to be used for WB->MC could be determined somehow at a later stage.

                  I am currently ignoring the fact that there are other Bean implementations than SimpleBean, and also not handling WebBeansKernelControllerContexts for bean factories

                  • 6. Re: Web beans on top of Microcontainer deployment order
                    Kabir Khan Master

                    Reply from Pete Muir:

                    "kabir.khan@jboss.com" wrote:
                    I have committed the initial prototype for the web beans integration. It can be found here https://svn.jboss.org/repos/jbossas/projects/kernel/trunk/webbeans-int.

                    MC beans that should interact with web beans are wrapped in a WebBeansKernelControllerContext as mentioned before. It contains additional controller context actions to instantiate/inject an MC bean with web beans. It also contains an additional controller context action to store mc beans that should be available for injection to web beans in a registry. It contains a web beans AfterBeanDiscovery implementation that is used to add the mc beans to the web beans bean manager. The mc beans added to web beans are done in a custom web beans Bean implementation that takes the existing instance from mc, ignoring the calls to create it. When running in AS there will be a loads and loads of MC beans, which might unintentionally conflict, so we need to find a good way to narrow down which MC beans are available for injection into web beans. Currently, I am only registering MC beans that have web beans annotations on them (ignoring producer methods), but am open for suggestions on how to improve this.

                    I am unsure how this fits together in the real world though. The WebBeansKernelControllerContext needs access to a bean manager to be able to call createCreationalContext(), createInjectionTarget() and createAnnotatedType().

                    For cases when we want to inject MC->WB, the MC beans need to be deployed and 'registered' before the web beans are deployed, and the actual bean manager to be used is created during the deployment process. I have hacked around this for now, by creating a 'bootstrap' bean manager in my tests that is available before web beans are deployed. Luckily, none of the BeanManager methods needed for this process (createAnnotatedType()) do anything with the internal state of the bean manager, so they work.

                    This is fine, you can access the BeanManager after Bootstrap.startContainer() is called, or after Bootstrap.startInitialization(). Beans aren't deployed until Bootstrap.deployBeans().

                    Though assuming you are in a container lifecycle event doing @Current BeanManager beanManager; will give you the correct manager (afaik this works).
                    "kabir.khan@jboss.com" wrote:

                    For WB->MC injection, we need access to the correct bean manager that should be used to inject the MC beans, so createCreationalContext() and createInjectionTarget() can be called. I think for this to work we need access to the 'real' BeanManager. So, how do we determine which BeanManager should be used?


                    You need to determine which BeanDeploymentArchive the bean you want to access belongs to. Until Ales and I write the non-flat deployment strucutre for JBoss AS, this is a moot point really, as there is only one. Then you call

                    BeanManager manager = bootstrap.getManager(beanDeploymentArchive);

                    Note that normally MC beans can't access stuff inside deployments tho.

                    I would assume we will end up with a structure like this in JBoss AS:

                    EAR Deployment
                    EJB JAR BDA
                    -> Server lib/ virtual BDA
                    WAR BDA
                    -> Server lib/ virtual BDA
                    -> EJB JAR BDA

                    (where -> denotes accessibility).

                    So you would *always* put the MC beans in the Server lib/ virtual BDA.


                    • 7. Re: Web beans on top of Microcontainer deployment order
                      Kabir Khan Master

                      As I have mentioned on team calls I think there will be some problems with weld-int once we support JSR-330 annotations natively in MC. Here is a chat between me and Ales describing the problem and a half-baked idea for a way around it once I get the chance to look at it again:

                       

                      [03/02/2010 16:12:03] Kabir Khan: I've had an idea about the Weld/MC/JSR330 stuff

                      [03/02/2010 16:12:14] Kabir Khan: Not sure if it is viable or not

                      [03/02/2010 16:12:38] Kabir Khan: but I think the problem is that at the moment ALL JSR 330 injection is deferred to weld

                      [03/02/2010 16:13:02] Kabir Khan: once we support JSR 330 in MC, the problem I foresee is distiguishing between the two

                      [03/02/2010 16:13:07] Ales Justin: jsr33 has no injection

                      [03/02/2010 16:13:12] Ales Justin: it's just a bunch of annotations

                      [03/02/2010 16:13:29] Kabir Khan: It defines @Inject (or whatever it is called)

                      [03/02/2010 16:13:37] Ales Justin: yes

                      [03/02/2010 16:13:45] Kabir Khan: and basically weld-int handles that

                      [03/02/2010 16:13:53] Kabir Khan: if we want to natively support this in MC

                      [03/02/2010 16:13:58] Kabir Khan: then we have a problem

                      [03/02/2010 16:14:07] Ales Justin: even with qualifiers?

                      [03/02/2010 16:14:18] Kabir Khan: yes, weld takes the whole AnnotatedType

                      [03/02/2010 16:14:31] Kabir Khan: which is basically all the methods etc.

                      [03/02/2010 16:14:40] Kabir Khan: and looks for the ones with @Inject in weld

                      [03/02/2010 16:15:01] Kabir Khan: so there is no clear way to distinguish between the ones from weld and the ones from MC

                      [03/02/2010 16:15:25] Kabir Khan: However AnnotatedType allows the use of decorators, which I already use to add qualifiers from MDR

                      [03/02/2010 16:15:31] Kabir Khan: e.g.

                      [03/02/2010 16:15:34] Kabir Khan: if I have

                      [03/02/2010 16:15:39] Kabir Khan: class Blah{

                      [03/02/2010 16:16:03] Kabir Khan:    @Inject Other other;

                         @Inject THing thing;

                      }

                      [03/02/2010 16:16:22] Kabir Khan: and Other is not a weld bean weld will throw up

                      [03/02/2010 16:16:30] Kabir Khan: What I am thinking of is

                      [03/02/2010 16:16:44] Kabir Khan: it might be possible to fo

                      [03/02/2010 16:16:45] Kabir Khan: do

                      [03/02/2010 16:17:19] Kabir Khan: @Inject Other other;

                      @Inject @Weld Thing thing;

                      [03/02/2010 16:17:34] Kabir Khan: in an MC bean to specify that thing should come from weld

                      [03/02/2010 16:17:48] Kabir Khan: and other comes from MC annotations directly

                      [03/02/2010 16:18:02] Kabir Khan: When passing the annotated type in to weld for injection

                      [03/02/2010 16:18:20] Kabir Khan: the decorated annotated type could leave out things that don't have @Weld

                      [03/02/2010 16:18:38] Kabir Khan: Just a vague thought I had right now

                      • 8. Re: Web beans on top of Microcontainer deployment order
                        Kabir Khan Master

                        I have updated the jsr330 branch and will merge to trunk once I have a added a few more tests. The kernel now supports the JSR 330 usage of @javax.inject.Inject, e.g.:

                         

                        public class SomeBean
                        {
                           @Inject @SomeQualifier Thing t;
                        
                           @Inject 
                           public SomeBean(@SomeQualifier Thing t){}
                        
                           @Inject
                           public void setThingA(@SomeQualifier Thing t){}
                        
                           @Inject @SomeQualifier
                           public void setThingB(Thing t){}
                        
                           @Inject
                           public void installMethod(@SomeQualifier Thing t, @OtherQualifier Thing t2){}
                        
                        }
                        

                         

                        If we have the following MC bean

                        public class MyBean{
                        
                           @Inject Thing t;
                        
                           @Weld @Inject Other o;
                        
                        }
                        

                        t will be injected from the MC using contextual injection, while o will be injected from Weld. This is done by maintaining a registry of annotations that should be ignored by the @Inject annotation plugins, so if the member also has one of those annotations (e.g. @Weld) we don't create the standard MC injection value metadata for it.

                         

                        When creating the weld injection we create a weld AnnotatedType for the bean, for which there is a decorator that looks at the MDR metadata for the bean. I modified the getAnnotations(), isAnnotationPresent() and getAnnotation() methods to strip out the @Inject unless @Weld is also present, so that weld does not try to inject into those members.

                         

                        To make an MC bean injectable into Weld it needs the @WeldEnabled annotation, e.g.:

                        If we have the following MC bean

                        @WeldEnabled
                        public class McBeanForWeld{
                        }