1 2 Previous Next 21 Replies Latest reply on Oct 28, 2008 12:54 PM by Adrian Brock

    ClassLoader injection Scope issue

    Ales Justin Master

      Hacking around my demos, I came across this CL usage.

      <deployment xmlns="urn:jboss:bean-deployer:2.0">
      
       <classloader><inject bean="custom-classloader:0.0.0"/></classloader>
      
       <classloader name="custom-classloader" xmlns="urn:jboss:classloader:1.0" export-all="NON_EMPTY" import-all="true"/>
      
       <bean name="CB1" class="org.jboss.demos.ioc.classloader.CustomBean"/>
      
      </deployment>
      

      This looks legit as we have similar config in JBoss5/conf.
      But it fails for me with this exception:
      Caused by: java.lang.IllegalStateException: No context for AbstractDependencyValueMetaData@4d2af2{value=custom-classloader:0.0.0}
       at org.jboss.beans.metadata.plugins.AbstractDependencyValueMetaData.getValue(AbstractDependencyValueMetaData.java:205)
       at org.jboss.kernel.plugins.config.Configurator.getClassLoader(Configurator.java:665)
       at org.jboss.kernel.plugins.config.Configurator.getClassLoader(Configurator.java:646)
       at org.jboss.kernel.plugins.dependency.KernelScopeInfo.getScope(KernelScopeInfo.java:97)


      Why this works in JBoss5 and not here? ;-)
      The reason is that this was never tested outside jbossas/conf directory.
      Meaning none of those config files are picked up by BeanMetaDataDeployer.

      It's the BMDD that causes this to fail while merging scopes.
       ScopeInfo scopeInfo = context.getScopeInfo();
       if (scopeInfo != null)
       {
       mergeScopes(scopeInfo.getScope(), unit.getScope());
       mergeScopes(scopeInfo.getMutableScope(), unit.getMutableScope());
      

      ScopeInfo::getScope
       // THIS IS A HACK - the scope originally gets initialise with a class name, we fix it to have the class
       ScopeKey key = super.getScope();
       Scope scope = key.getScope(CommonLevels.CLASS);
       if (scope == null)
       return key;
       Object qualifier = scope.getQualifier();
       if (qualifier instanceof Class)
       return key;
      
       String className = (String) qualifier;
       ClassLoader cl;
       try
       {
       cl = Configurator.getClassLoader(beanMetaData);
       }
       catch (Throwable t)
       {
       throw new RuntimeException("Error getting classloader for " + key, t);
       }
      

      Where Configurator::getClassLoader invokes AbstractDependencyValueMetaData::getValue,
      but ADVMD instance has not yet been visited, hence its underlying context is null:
       public Object getValue(TypeInfo info, ClassLoader cl) throws Throwable
       {
       ControllerState state = dependentState;
       if (state == null)
       state = ControllerState.INSTALLED;
       if (context == null)
       throw new IllegalStateException("No context for " + this);
       Controller controller = context.getController();
      


      Using MC bean metadata outside MC's Controller looks dangerous. :-)

        • 1. Re: ClassLoader injection Scope issue
          Ales Justin Master

          What can we do here?

          Move scope merging to ScopeInfo.
          e.g.

           void mergeScopes(ScopeInfo other, ScopeType type, ScopeLevel... excludedScopeLevels);
          

          Where type is: DEFAULT, MUTABLE, INSTALL.

          We would then in BeanMDDeployer exclude CommonLevels.CLASS ScopeLevel.
          Or is there some meaning that DeploymentUnit overrides this?

          This would also require that KernelScopeInfo lazily creates default Scope
          - so that we don't touch CommonLevels.CLASS, due to possible CL-not-yet-available issue.


          • 2. Re: ClassLoader injection Scope issue
            Adrian Brock Master

            My guess is that this has nothing to do with JBossAS?
            There is nothing that I'm aware of in JBossAS that does anything different.

            To use the VFSClassLoaderFactory you need to have the
            ClassLoading/ClassLoaderSystem deployed first.

            If you don't do this is a seperate deployment up-front
            (see Common.xml in the classloading-vfs tests)
            then you need to make sure these don't depend on the vfs classloader
            you want to construct otherwise you'll get a "circular dependency".

            See conf/classloader.xml in jboss.xml (note they are first with classloader==null)

            I quote "circular dependency" because it is not really a circular dependency,
            what actually happens is that the classloader (which automatically has classloader=null)
            gets re-ordered to be before the ClassLoading being deployed and so it won't get
            registered using the incallback before you want to want to do
            "ClassLoading".getClassLoader().

            Or more schematically:
            1) deploy ClassLoading
            2) deploy ClassLoader
            3) ClassLoading depends on ClassLoader (because classloader != null)
            4) ClassLoading Module for ClassLoader is created but not registered with
            ClassLoading (no incallback yet)
            5) Try Configurator.getBeanMetaData("ClassLoading") // ERROR it didn't go through the ClassLoading!
            6) If (5) worked then it would register the incallback for "ClassLoading"
            and the ClassLoader would properly be constructed.

            NOTE (5) could also be any request to get the classloader if its ClassLoadingModule
            hasn't been registered with ClassLoading to resolve and create the classloader.

            • 3. Re: ClassLoader injection Scope issue
              Ales Justin Master

               

              "adrian@jboss.org" wrote:
              My guess is that this has nothing to do with JBossAS?
              There is nothing that I'm aware of in JBossAS that does anything different.

              This example (see my initial post's top) is taken from my MC demos - as part of DZone articles:
              - http://anonsvn.jboss.org/repos/jbossas/projects/demos/microcontainer/trunk/

              Afaik the example looks legit.
              CB1 bean wants to use currently defined classloader (custom-classloader) as its classloader.

              And as I explained, or at least tried to explain :-),
              the difference from JBossAS and this example is in how those files get deployed:

              JBossAS; ServerImpl class:
               log.debug("BootstrapURLs=" + bootstrapURLs);
              
               // Create an xml deployer
               kernelDeployer = new BasicXMLDeployer(kernel);
               try
               {
               // Deploy the bootstrap urls
               for (String bootstrapURL : bootstrapURLs)
               {
               url = new URL(configURL, bootstrapURL);
               kernelDeployer.deploy(url);
               }
              
               // Check it is complete
               kernelDeployer.validate();
              

              --> direct install into kernel --> no BeanMetaDataDeployer --> no scope merging

              My example:
              - http://anonsvn.jboss.org/repos/jbossas/projects/demos/microcontainer/trunk/ioc/src/main/resources/META-INF/classloader-beans.xml
              goes over BeanMDDeployer --> scope merging.
              Scope merging requires CL to already be set - see previous post's exception.

              And to my knowledge, no other -beans.xml in JBossAS uses
              this classloader schema + setting this newly created cl as deployments cl,
              except the files referenced in bootstrap.xml.

              "adrian@jboss.org" wrote:

              To use the VFSClassLoaderFactory you need to have the
              ClassLoading/ClassLoaderSystem deployed first.

              If you don't do this is a seperate deployment up-front
              (see Common.xml in the classloading-vfs tests)
              then you need to make sure these don't depend on the vfs classloader
              you want to construct otherwise you'll get a "circular dependency".

              See conf/classloader.xml in jboss.xml (note they are first with classloader==null)

              I already have all of these.
              My demos use simplified version of JBoss5 bootstrap.
              See bootstrap and jmx sub-projects in demos project:
              - http://anonsvn.jboss.org/repos/jbossas/projects/demos/microcontainer/trunk/bootstrap/src/main/resources/META-INF/bootstrap-beans.xml
              - http://anonsvn.jboss.org/repos/jbossas/projects/demos/microcontainer/trunk/jmx/src/main/resources/META-INF/

              "adrian@jboss.org" wrote:

              I quote "circular dependency" because it is not really a circular dependency,
              what actually happens is that the classloader (which automatically has classloader=null)
              gets re-ordered to be before the ClassLoading being deployed and so it won't get
              registered using the incallback before you want to want to do
              "ClassLoading".getClassLoader().

              Or more schematically:
              1) deploy ClassLoading
              2) deploy ClassLoader
              3) ClassLoading depends on ClassLoader (because classloader != null)
              4) ClassLoading Module for ClassLoader is created but not registered with
              ClassLoading (no incallback yet)
              5) Try Configurator.getBeanMetaData("ClassLoading") // ERROR it didn't go through the ClassLoading!
              6) If (5) worked then it would register the incallback for "ClassLoading"
              and the ClassLoader would properly be constructed.

              NOTE (5) could also be any request to get the classloader if its ClassLoadingModule
              hasn't been registered with ClassLoading to resolve and create the classloader.

              Is this all about how classloader.xml is eventually deployed/installed?
              Since I don't see how it's related to my problem.

              • 4. Re: ClassLoader injection Scope issue
                Ales Justin Master

                 

                "alesj" wrote:

                Afaik the example looks legit.
                CB1 bean wants to use currently defined classloader (custom-classloader) as its classloader.

                Or is this not the intent of defining cl inside -beans.xml?

                • 5. Re: ClassLoader injection Scope issue
                  Ales Justin Master

                   

                  "alesj" wrote:

                  "adrian@jboss.org" wrote:

                  I quote "circular dependency" because it is not really a circular dependency,
                  what actually happens is that the classloader (which automatically has classloader=null)
                  gets re-ordered to be before the ClassLoading being deployed and so it won't get
                  registered using the incallback before you want to want to do
                  "ClassLoading".getClassLoader().

                  Or more schematically:
                  1) deploy ClassLoading
                  2) deploy ClassLoader
                  3) ClassLoading depends on ClassLoader (because classloader != null)
                  4) ClassLoading Module for ClassLoader is created but not registered with
                  ClassLoading (no incallback yet)
                  5) Try Configurator.getBeanMetaData("ClassLoading") // ERROR it didn't go through the ClassLoading!
                  6) If (5) worked then it would register the incallback for "ClassLoading"
                  and the ClassLoader would properly be constructed.

                  NOTE (5) could also be any request to get the classloader if its ClassLoadingModule
                  hasn't been registered with ClassLoading to resolve and create the classloader.

                  Is this all about how classloader.xml is eventually deployed/installed?
                  Since I don't see how it's related to my problem.

                  My problem of not getting this is mostly related to names. :-)
                  As I don't follow what exactly is classloader/ClassLoader.
                  e.g. when do you refer to classloader schema element, when to element, real/bean's cl, ... :-)

                  JBossAS's classloader.xml
                  <deployment xmlns="urn:jboss:bean-deployer:2.0">
                  
                   <classloader><inject bean="bootstrap-classloader:0.0.0"/></classloader>
                  
                   <!--
                   The classloader implementation
                   -->
                   <bean name="ClassLoaderSystem" class="org.jboss.classloader.spi.ClassLoaderSystem">
                   <classloader><null/></classloader>
                   <constructor factoryClass="org.jboss.classloader.spi.ClassLoaderSystem" factoryMethod="getInstance"/>
                   </bean>
                  
                   <!--
                   ClassLoader management and resolution
                   -->
                   <bean name="ClassLoading" class="org.jboss.classloading.spi.dependency.ClassLoading">
                   <classloader><null/></classloader>
                   <incallback method="addModule" state="Configured"/>
                   <uncallback method="removeModule" state="Configured"/>
                   </bean>
                  
                   <classloader name="bootstrap-classloader" xmlns="urn:jboss:classloader:1.0" export-all="NON_EMPTY" import-all="true">
                   <!-- FIXME Move to Deployers -->
                   <root>${jboss.lib.url}/jboss-deployers-core-spi.jar</root>
                   <root>${jboss.lib.url}/jboss-deployers-core.jar</root>
                   <root>${jboss.lib.url}/jboss-deployers-client-spi.jar</root>
                   <root>${jboss.lib.url}/jboss-deployers-client.jar</root>
                   <root>${jboss.lib.url}/jboss-deployers-structure-spi.jar</root>
                   <root>${jboss.lib.url}/jboss-deployers-spi.jar</root>
                   <root>${jboss.lib.url}/jboss-deployers-impl.jar</root>
                   <root>${jboss.lib.url}/jboss-deployers-vfs-spi.jar</root>
                   <root>${jboss.lib.url}/jboss-deployers-vfs.jar</root>
                   <!-- System -->
                   <root>${jboss.lib.url}/jboss-system.jar</root>
                   <!-- FIXME Move to JMX -->
                   <root>${jboss.lib.url}/jboss-j2se.jar</root>
                   <root>${jboss.lib.url}/jboss-mbeans.jar</root>
                   <root>${jboss.lib.url}/jboss-jmx.jar</root>
                   <root>${jboss.lib.url}/jboss-system-jmx.jar</root>
                   <root>${jboss.lib.url}/dom4j.jar</root>
                   <!-- JAXB impl here, api is in endorsed -->
                   <root>${jboss.lib.url}/jaxb-impl.jar</root>
                   <!-- STAX2 impl here, api is in endorsed -->
                   <root>${jboss.lib.url}/wstx.jar</root>
                   </classloader>
                  
                  </deployment>
                  



                  • 6. Re: ClassLoader injection Scope issue
                    Adrian Brock Master

                     

                    "alesj" wrote:

                    And as I explained, or at least tried to explain :-),
                    the difference from JBossAS and this example is in how those files get deployed:

                    My example:
                    - http://anonsvn.jboss.org/repos/jbossas/projects/demos/microcontainer/trunk/ioc/src/main/resources/META-INF/classloader-beans.xml
                    goes over BeanMDDeployer --> scope merging.
                    Scope merging requires CL to already be set - see previous post's exception.


                    Ahh! Ok I understand now.

                    The obvious first question is why is it merging scopes? You did this change.

                    Merging the scopes is broken since any deployer that wants to "play with"
                    the metadata will be using the wrong scope, i.e. the wrong metadata.
                    Especially the mutable scope!



                    • 7. Re: ClassLoader injection Scope issue
                      Adrian Brock Master

                      The obvious fix would be to add some mergeScope() methods to ScopeInfo
                      which would know to avoid the hack that converts the class name -> class.

                      The merging of the mutable scope shouldn't be happening. Its a recipe for
                      all sorts of confusion if DefaultScopeBuilder and KernelScopeInfo disagree on
                      what that should be. DefaultScopeBuilder (or some deployer that sets the
                      mutable scope on the deployment unit) should be the only authority for that.

                      • 8. Re: ClassLoader injection Scope issue
                        Adrian Brock Master

                         

                        "adrian@jboss.org" wrote:

                        The obvious first question is why is it merging scopes? You did this change.


                        I guess this is because it doesn't look like DefaultScopeBuilder in the deployers knows
                        anything about classes when creating component scopes?
                        It just adds the INSTANCE level to the DEPLOYMENT/APPLICATION

                        • 9. Re: ClassLoader injection Scope issue
                          Ales Justin Master

                           

                          "adrian@jboss.org" wrote:

                          The obvious first question is why is it merging scopes? You did this change.

                          No merging broke my AnnotatedBeansUnitTestCase test.
                          Some metadata gets lost with the scope override - as we already create some info when creating AbstractKernelControllerContext.

                          Let me try and find if we already discussed this ...

                          • 10. Re: ClassLoader injection Scope issue
                            Ales Justin Master

                             

                            "adrian@jboss.org" wrote:

                            I guess this is because it doesn't look like DefaultScopeBuilder in the deployers knows
                            anything about classes when creating component scopes?
                            It just adds the INSTANCE level to the DEPLOYMENT/APPLICATION

                            Yup, my guess would be we're then missing this
                             public AbstractScopeInfo(Object name, String className)
                             {
                             if (name == null)
                             throw new IllegalArgumentException("Null scope");
                            
                             ScopeKey scopeKey = ScopeKey.DEFAULT_SCOPE.clone();
                             scopeKey.addScope(CommonLevels.INSTANCE, name.toString());
                             if (className != null)
                             scopeKey.addScope(CommonLevels.CLASS, className);
                             setScope(scopeKey);
                             setMutableScope(new ScopeKey(CommonLevels.INSTANCE, name.toString()));
                             }
                            


                            • 11. Re: ClassLoader injection Scope issue
                              Ales Justin Master

                              Mostly the CommonLevels.CLASS. :-)

                              From debuging BeanMDDeployer in AnnotatedBeansUnitTestCase:

                              contextKey = {org.jboss.metadata.spi.scope.ScopeKey@907}"[JVM=THIS, CLASS=class org.jboss.test.deployers.vfs.deployer.bean.support.SimpleAnnotatedConstructor, INSTANCE=Constructor]"
                              
                              unitKey = {org.jboss.metadata.spi.scope.ScopeKey@908}"[JVM=THIS, APPLICATION=KernelDeployerTest, DEPLOYMENT=KernelDeployerTest, INSTANCE=Constructor]"


                              • 12. Re: ClassLoader injection Scope issue
                                Adrian Brock Master

                                 

                                "alesj" wrote:
                                "adrian@jboss.org" wrote:

                                I guess this is because it doesn't look like DefaultScopeBuilder in the deployers knows
                                anything about classes when creating component scopes?
                                It just adds the INSTANCE level to the DEPLOYMENT/APPLICATION

                                Yup, my guess would be we're then missing this
                                 public AbstractScopeInfo(Object name, String className)
                                 {
                                 if (name == null)
                                 throw new IllegalArgumentException("Null scope");
                                
                                 ScopeKey scopeKey = ScopeKey.DEFAULT_SCOPE.clone();
                                 scopeKey.addScope(CommonLevels.INSTANCE, name.toString());
                                 if (className != null)
                                 scopeKey.addScope(CommonLevels.CLASS, className);
                                 setScope(scopeKey);
                                 setMutableScope(new ScopeKey(CommonLevels.INSTANCE, name.toString()));
                                 }
                                


                                So another fix would be to make the Deployer's ScopeBuilder
                                somehow aware of the class scope when it creates the component scope.

                                Then there's no need for any merge.

                                But there's probably a reason why I didn't do that in the first place
                                (other than I just forgot to do it? - which might actually be the real reason. :-)

                                • 13. Re: ClassLoader injection Scope issue
                                  Ales Justin Master

                                   

                                  "adrian@jboss.org" wrote:

                                  So another fix would be to somehow make the Deployer's ScopeBuilder
                                  somehow aware of the class scope when it creates the component scope.

                                  ScopeBuilder per attachment type?
                                   public static ScopeBuilder getScopeBuilder(DeploymentContext deploymentContext)
                                   {
                                   if (deploymentContext == null)
                                   throw new IllegalArgumentException("Null deployment context");
                                   ScopeBuilder builder = deploymentContext.getTransientAttachments().getAttachment(ScopeBuilder.class);
                                   if (builder != null)
                                   return builder;
                                   DeploymentContext parent = deploymentContext.getParent();
                                   if (parent != null)
                                   return getScopeBuilder(parent);
                                   return DefaultScopeBuilder.INSTANCE;
                                   }
                                  

                                  Setting BeanMD aware ScopeBuilder?

                                  • 14. Re: ClassLoader injection Scope issue
                                    Adrian Brock Master

                                     

                                    "alesj" wrote:
                                    "adrian@jboss.org" wrote:

                                    So another fix would be to somehow make the Deployer's ScopeBuilder
                                    somehow aware of the class scope when it creates the component scope.

                                    ScopeBuilder per attachment type?
                                     public static ScopeBuilder getScopeBuilder(DeploymentContext deploymentContext)
                                     {
                                     if (deploymentContext == null)
                                     throw new IllegalArgumentException("Null deployment context");
                                     ScopeBuilder builder = deploymentContext.getTransientAttachments().getAttachment(ScopeBuilder.class);
                                     if (builder != null)
                                     return builder;
                                     DeploymentContext parent = deploymentContext.getParent();
                                     if (parent != null)
                                     return getScopeBuilder(parent);
                                     return DefaultScopeBuilder.INSTANCE;
                                     }
                                    

                                    Setting BeanMD aware ScopeBuilder?


                                    Those sound overly complicated.
                                    The simplest fix would be in the component visitor, something like:

                                     protected static void addBeanComponent(DeploymentUnit unit, BeanMetaData bean)
                                     {
                                     DeploymentUnit component = unit.addComponent(bean.getName());
                                     component.addAttachment(BeanMetaData.class.getName(), bean);
                                    + String className = bean.getBean();
                                    + if (className != null)
                                    + component.getScope().addScope(CommonLevels.CLASS, className);
                                     }
                                    


                                    1 2 Previous Next