3 Replies Latest reply on Dec 4, 2009 11:10 AM by jaikiran

    Embedded Server and classloading issues

    jaikiran

      I have been trying to use Embedded Server API in the testsuite of one of the JBoss EJB3 components. The setup details are:

      - JBoss AS 6.0 M1 (built from tag)
      - Embedded Trunk (1.0.0-SNAPSHOT)
      - Java 1.6

      In the EJB3 component project, the embedded API is being pulled in by the following dependency:

      <dependency>
       <groupId>org.jboss.embedded</groupId>
       <artifactId>jboss-embedded-depchain</artifactId>
       <version>1.0.0-SNAPSHOT</version>
       <scope>test</scope>
       <type>pom</type>
      </dependency>
      


      The jboss-embedded-depchain brings in all the dependencies required for the JBoss AS.

      Within the testsuite, the Embedded Server is booted as follows:

       private static org.jboss.logging.Logger logger = Logger.getLogger(Blah.class);
      
       /**
       * JBoss AS server
       */
       private JBossASEmbeddedServer server;
       ...// uses the classloader of the client application
       this.server = JBossASEmbeddedServerFactory.createServer();
       this.server.getConfiguration().jbossHome(jbossHome);
       logger.info("Starting server at " + jbossHome);
       this.server.start();
       logger.info("Server at " + jbossHome + " started");
      


      So now on to the problems i am running into. The EJB3 component where this testsuite resides, has it's own set of dependencies apart from the dependency on embedded server API. So it declares it's own version of jboss-logging-spi (note, that i am using the logging dependency as a simple example here, but the dependency can be anything). Let's say the version being used in the project is 2.0.5.GA and the one being used within the AS is 2.1.1.GA. So effectively, Maven decides that the version i declare in my pom is the nearest and decides to include that in the classpath of this project. So i now have 2.0.5.GA of jboss-logging in the classpath.

      When i then try to create and boot the server through the embeded API, it fails to boot because the AS expects a different version of logging spi (2.1.1.GA) to be available in the classpath. Just for completeness, here's the exception stacktrace:

      20:27:15,042 ERROR [AbstractKernelController] Error installing to Instantiated: name=LogBridgeHandler state=Described
      java.lang.NoClassDefFoundError: org/jboss/logging/log4j/JDKLevel
       at org.jboss.logbridge.LevelMapper.<init>(LevelMapper.java:43)
       at org.jboss.logbridge.LogBridgeHandler.<init>(LogBridgeHandler.java:46)
       at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
       at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
       at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
       at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
       at org.jboss.reflect.plugins.introspection.ReflectionUtils.newInstance(ReflectionUtils.java:149)
       at org.jboss.reflect.plugins.introspection.ReflectConstructorInfoImpl.newInstance(ReflectConstructorInfoImpl.java:106)
       at org.jboss.joinpoint.plugins.BasicConstructorJoinPoint.dispatch(BasicConstructorJoinPoint.java:80)
       at org.jboss.aop.microcontainer.integration.AOPConstructorJoinpoint.createTarget(AOPConstructorJoinpoint.java:295)
       at org.jboss.aop.microcontainer.integration.AOPConstructorJoinpoint.dispatch(AOPConstructorJoinpoint.java:116)
       at org.jboss.kernel.plugins.dependency.KernelControllerContextAction$JoinpointDispatchWrapper.execute(KernelControllerContextAction.java:243)
       at org.jboss.kernel.plugins.dependency.ExecutionWrapper.execute(ExecutionWrapper.java:47)
       at org.jboss.kernel.plugins.dependency.KernelControllerContextAction.dispatchExecutionWrapper(KernelControllerContextAction.java:111)
       at org.jboss.kernel.plugins.dependency.KernelControllerContextAction.dispatchJoinPoint(KernelControllerContextAction.java:72)
       at org.jboss.kernel.plugins.dependency.InstantiateAction.installActionInternal(InstantiateAction.java:66)
       at org.jboss.kernel.plugins.dependency.InstallsAwareAction.installAction(InstallsAwareAction.java:54)
       at org.jboss.kernel.plugins.dependency.InstallsAwareAction.installAction(InstallsAwareAction.java:42)
       at org.jboss.dependency.plugins.action.SimpleControllerContextAction.simpleInstallAction(SimpleControllerContextAction.java:62)
       at org.jboss.dependency.plugins.action.AccessControllerContextAction.install(AccessControllerContextAction.java:71)
       at org.jboss.dependency.plugins.AbstractControllerContextActions.install(AbstractControllerContextActions.java:51)
       at org.jboss.dependency.plugins.AbstractControllerContext.install(AbstractControllerContext.java:348)
       at org.jboss.dependency.plugins.AbstractController.install(AbstractController.java:1632)
       at org.jboss.dependency.plugins.AbstractController.incrementState(AbstractController.java:935)
       at org.jboss.dependency.plugins.AbstractController.resolveContexts(AbstractController.java:1083)
       at org.jboss.dependency.plugins.AbstractController.resolveContexts(AbstractController.java:985)
       at org.jboss.dependency.plugins.AbstractController.install(AbstractController.java:775)
       at org.jboss.dependency.plugins.AbstractController.install(AbstractController.java:540)
       at org.jboss.kernel.plugins.deployment.AbstractKernelDeployer.deployBean(AbstractKernelDeployer.java:319)
       at org.jboss.kernel.plugins.deployment.AbstractKernelDeployer.deployBeans(AbstractKernelDeployer.java:297)
       at org.jboss.kernel.plugins.deployment.AbstractKernelDeployer.deploy(AbstractKernelDeployer.java:130)
       at org.jboss.kernel.plugins.deployment.BasicKernelDeployer.deploy(BasicKernelDeployer.java:76)
       at org.jboss.bootstrap.impl.mc.deployer.TempBasicXMLDeployer.deploy(TempBasicXMLDeployer.java:92)
       at org.jboss.bootstrap.impl.mc.deployer.TempBasicXMLDeployer.deploy(TempBasicXMLDeployer.java:162)
       at org.jboss.bootstrap.impl.mc.server.AbstractMCServerBase.doStart(AbstractMCServerBase.java:318)
       at org.jboss.bootstrap.impl.as.server.AbstractJBossASServerBase.doStart(AbstractJBossASServerBase.java:376)
       at org.jboss.bootstrap.impl.base.server.AbstractServer$StartServerTask.run(AbstractServer.java:434)
       at java.lang.Thread.run(Thread.java:619)
      Caused by: java.lang.ClassNotFoundException: org.jboss.logging.log4j.JDKLevel
       at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
       at java.security.AccessController.doPrivileged(Native Method)
       at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
       at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
       at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
       at java.lang.ClassLoader.loadClass(ClassLoader.java:252)
       at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:320)
       ... 38 more
      


      But as i said, this is not limited to logging versions, but is a more generic issue.

      So here's the questions:

      1) Relying on Maven (or more generically a dependency resolution tool) to bring in the right set of dependencies to boot the AS *from within some other project* probably isn't the right thing? i.e. project A which is a client of the Embedded server API, might need it's own version of an artifact which conflicts with the AS required version of the same artifact. And then letting the build tool decide which one is best out of these 2 versions could lead to issues.
      Infact, in this scenario, *both* the versions should be brought in, for both the AS and the client application to work as expected (see #2 below).

      2) Theoretically, when the AS is being created/booted it should use its own different classloader than the classloader of the application which is triggering the boot. This way the client application can have it's own different versions of the same artifacts which the AS requires. Probably, the Embedded server implementation should internally create a classloader out of the right set of jars (/locations) for the AS and then use that classloader for AS boot/deploy and other operations. How to get hold of the right set of jars is a different step (step#3 below).

      3) Maybe if the Embedded server API knows of JBOSS_HOME locations, then probably it can create the classpath for the AS based on the known library locations of the AS (JBOSS_HOME/lib, JBOSS_HOME/common/lib etc...)? But this is going to tie the Embedded server implementation to a specific version of AS, since if tomorrow the AS decides to have it's libraries in a different location then Embedded server implementation will be affected too. Actually, it needs a bit more thinking, to get a solution.

      Thoughts?


        • 1. Re: Embedded Server and classloading issues
          alrubinger

          These are good points that I've been trying to push off until a later release, but I think it's important enough to address for the upcoming EmbeddedAS 1.0.0-alpha-1.

          https://jira.jboss.org/jira/browse/EMB-73

          A bit of an overview first.

          Booting AS is a tricky business which involves careful placement of libraries into the proper ClassLoaders. If we leak out references in a parent CL to something that's visible to a child, then NCDFE pops up. This is why we have the ugly DEFAULT_LIBRARY_BOOT_LIST in org.jboss.Main.

          The first hacky solution was a sledgehammer approach where *all* AS dependencies make their way into the application classpath, hence the AppCL can see everything. This is what's done in the EmbeddedAS "testsuite" module for instance, where the sole dependency declaration is upon "depchain".

          EMB-73 introduces a better mechanism whereby the client becomes responsible for:

          1) Putting EmbeddedAS APIs and referents on the application classpath (so the test may be loaded)
          2) Constructing a URLClassLoader armed with the AS boot libraries required for the server to start
          3) Loading a server instance and starting it within the context of that CL.

          The overall process looks like this:

          // Get JBOSS_HOME
           final URL jbossHome = this.getJBossHome();
          
           // Make the new ClassLoader
           final ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
           final ClassLoader jbossHomeClassLoader = JBossHomeClassLoader.newInstance(jbossHome, additionalUrls
           .toArray(new URL[]
           {}), oldCl);
          
           // Make Server
           server = JBossASEmbeddedServerFactory.createServer(jbossHomeClassLoader);
          
           // Start
           log.info("Starting Server: " + server);
          
           // Set TCCL
           Thread.currentThread().setContextClassLoader(jbossHomeClassLoader);
          
           try
           {
           // Start the Server
           server.start();
           log.info("...started.");
          
           // Test
           TestCase.assertEquals("Server did not report started as expected", LifecycleState.STARTED, server.getState());
          
           // Shutdown if started
           if (server != null && server.getState().equals(LifecycleState.STARTED))
           {
           // Shutdown
           log.info("Shutting down server: " + server);
           server.shutdown();
           }
           }
           finally
           {
           // Reset the TCCL
           Thread.currentThread().setContextClassLoader(oldCl);
           }


          And the application classpath has very little on it; only API-level stuff:

          [alr@localhost testsuite-jbosshomecl]$ mvn dependency:tree
          [INFO] +- org.jboss.embedded:jboss-embedded-api:jar:1.0.0-SNAPSHOT:test
          [INFO] | +- org.jboss.bootstrap:jboss-bootstrap-api-as:jar:2.0.0-alpha-2:test
          [INFO] | | \- org.jboss.bootstrap:jboss-bootstrap-api:jar:2.0.0-alpha-2:test
          [INFO] | \- org.jboss.shrinkwrap:shrinkwrap-api:jar:1.0.0-alpha-2:test
          [INFO] \- junit:junit:jar:4.6:test


          Note that no longer are we polluting the classpath or repackaging binaries in this approach. Looking forward I want AS Main to build off this same structure.

          Regarding your questions, in general doing the type of testing of AS internals is a bit risky because if your test relies upon nonpublic code, it's likely that you'll need to load in nearly all of AS libs on the AppCL. Even one reference to the MC Kernel infers things like logging, jboss-man, jboss-dep, vfs, aop, etc...so for this usage I think "depchain" is the easiest way to get everything in one go.

          I don't have a specific solution to the versioning problems you illustrate. AS very carefully selects its thirdparty dependencies and once we start changing things, I expect havoc.

          "Jaikiran" wrote:
          1) Relying on Maven (or more generically a dependency resolution tool) to bring in the right set of dependencies to boot the AS *from within some other project* probably isn't the right thing? i.e. project A which is a client of the Embedded server API, might need it's own version of an artifact which conflicts with the AS required version of the same artifact. And then letting the build tool decide which one is best out of these 2 versions could lead to issues.
          Infact, in this scenario, *both* the versions should be brought in, for both the AS and the client application to work as expected (see #2 below).


          Agreed. In the case of "depchain", I the idea isn't that the build tool is deciding upon the dependencies, only that it's configured to pull in the same ones that AS defines. You should get the dependency set of AS in the end. The other side of the coin there is that if you override the version of some artifact which is defined by AS, you open yourself to problems.

          "jaikiran" wrote:
          2) Theoretically, when the AS is being created/booted it should use its own different classloader than the classloader of the application which is triggering the boot. This way the client application can have it's own different versions of the same artifacts which the AS requires. Probably, the Embedded server implementation should internally create a classloader out of the right set of jars (/locations) for the AS and then use that classloader for AS boot/deploy and other operations. How to get hold of the right set of jars is a different step (step#3 below).


          Yep; I just hadn't provided that until tonight. :) Your post here lead me to reconsider the ship criteria for 1.0.0-alpha-1, so take a look at:

          http://anonsvn.jboss.org/repos/jbossas/projects/embedded/trunk/testsuite-jbosshomecl/

          ...to ensure this works similarly to how you'd expect as well.

          "jaikiran" wrote:
          3) Maybe if the Embedded server API knows of JBOSS_HOME locations, then probably it can create the classpath for the AS based on the known library locations of the AS (JBOSS_HOME/lib, JBOSS_HOME/common/lib etc...)? But this is going to tie the Embedded server implementation to a specific version of AS, since if tomorrow the AS decides to have it's libraries in a different location then Embedded server implementation will be affected too. Actually, it needs a bit more thinking, to get a solution.


          I think this is the same/similar to your question and my proposed solution above, no?

          Input welcomed, we release soon.

          S,
          ALR

          • 2. Re: Embedded Server and classloading issues
            alrubinger
            • 3. Re: Embedded Server and classloading issues
              jaikiran

              Sorry, i did not respond earlier. I did however try out the changes related to JBossHomeClassLoader and it (partially) gets me past the issues we discussed here. The reason i did not respond earlier is - I still haven't figured completely figured out whether, what i am expecting out of the Embedded Server classloading is actually a valid requirement. I'll update this thread with more details, once i get a better understanding of some common use-cases and the related classloader issues.