9 Replies Latest reply on Jul 23, 2009 5:22 AM by njiang

    servicemix-camel classloader problem

    rmayo

      Hi,

       

      I believe I've discovered a bug in the servicemix-camel component. 

       

      I am using a camel SU deployed in servicemix (v3.3.1.15-fuse) to coordinate the routing between the various service units in my service assembly.  My camel router receives exchanges from a jbi endpoint and then uses the pipeline pattern to pass the exchange to other jbi endpoints which process/transform the exchange along the way.

       

      When my camel RouteBuilder recieves a message exchange, the initial classloader is the SU classloader as I would expect (contains all the jars bundled in the lib directory of my camel SU).  BUT, after sending an exchange to another jbi:endpoint, the context classloader is now set to the Camel Component and NOT the SU classloader - which means the jars bundled in the SU lib are no longer available.

       

      For example:

       

      public class MyRouteBuilder extends RouteBuilder {

       

          public class PrintClassLoader implements Processor {

              public void process(Exchange exchange) throws Exception {

                  ClassLoader cl = Thread.currentThread().getContextClassLoader();

                  System.out.println("ClassLoader: " + cl);

              }

          }

       

          public void configure() {

       

              // receives a message to a JBI endpoint...

              from("jbi:endpoint:urn:foo:bar:MyService:MyEndpoint")

                  .process(new PrintClassLoader())  // this will be the SU classloader "camel-context"

       

                  .to(jbi:endpoint:urn:foo:bar:MyService:MyOtherEndpoint")

                  .process(new PrintClassLoader())  // this will be the Component classloader

          }

      }

       

       

      I noticed this because in my router, I have a Processor which ultimately uses hibernate to access the database.  Since the processor is invoked after routing the exchange to another JBI endpoint, the hibernate jars are no longer available.

       

      I would think that using Camel to coordinate routing between jbi endpoints is a typical scenario when using ServiceMix (no?), but I haven't seen this issue reported on any of the forums so far. Am I doing something wrong?

       

      Any help would be greatly appreciated.

       

      Thanks.

      -Ryan

        • 1. Re: servicemix-camel classloader problem
          njiang

          Hi

           

          I checked the smx-camel component and camel core, there is no code to change the Thread's ContextClassLoader.

           

          Did you do any thing on the camel processor? Can you submit a test case to reproduce the issue?

           

          BTW, Can you give the fuse ServiceMix 3.4.0.x a try to see if the issue is still there ?

          • 2. Re: servicemix-camel classloader problem
            rmayo

            The same behavior occurs in 3.4.0.2-fuse.  I've attached an example which will demonstrate the problem.

             

            Thanks.

            • 3. Re: servicemix-camel classloader problem
              rmayo

              Should this be reported as a bug?

              • 4. Re: servicemix-camel classloader problem
                pedroneveu

                Hi Ryan,

                 

                Sorrry for the delay.

                 

                Yes this does look like a bug.  I have reproduced this behavior inhouse (thanks for the testcase) and have logged a bug for it: ESB-801

                 

                Regards,

                 

                Pedro

                • 5. Re: servicemix-camel classloader problem
                  njiang

                  Sorry for reply this thread later.

                   

                  I just had a chance to run the test case , and found this issue was not a bug of servicemix-camel component by adding a line code to print out the thread name in loadClass method.

                   

                  System.out.println("Thread ID is " + thread.getName() + " " + thread.getId());

                   

                  Since ServiceMix's component has their own thread pool to handle the message , you will find thread names are different.

                  If you want to let your camel rule work, you need to set the thread context classloader yourself in the processor.

                  • 6. Re: servicemix-camel classloader problem
                    njiang

                    I just attached the MyRouteBuilder.java in the ESB-801.

                     

                    Please check it out , it shows how to set the classloader with su's.

                    • 7. Re: servicemix-camel classloader problem
                      rmayo

                      Hi,

                       

                      Thanks for looking into this issue.

                       

                      I am aware that the threads are different and are being provided by ServiceMix's thread pool, but I still think that any code executed in the camel route should have access to the jars in the SU's classpath.

                       

                      The problem first occurred when using a Hibernate DAO to do a database lookup after forwarding the exchange to a JBI enpoint. Hibernate uses Thread.currentThread().getContextClassLoader() to create a new JDBC connection.  See BorrowedConnectionProxy.getProxyClassLoader()

                       

                      Hibernate is just one example of a 3rd party library that uses the current threads context class loader.  Since servicemix attempts to set the proper context classloader (see DeliveryChannelImpl.processInBound() and AsyncBaseLifeCycle.doProcess())

                       

                      Having to explicitly set the current thread's context classloader after forwarding an exchange to a different endpoint seems more like "a workaround" then the normal procedure when using camel routes inside of ServiceMix. This does not seem to be documented anywhere.

                       

                      for example:

                       

                      from("jbi:endpoint:http://test/MyProviderService/myConsumer)

                          .to("jbi:endpoint:http://test/MyProviderService/myProviderA")

                              // add Processor work around to reset the context classloader

                              .process(new Processor() {

                                  public void process(Exchange arg0) throws Exception {

                      Thread.currentThread().setContextClassLoader(MyRouteBuilder.class.getClassLoader());

                                      // do something that uses context classloader

                                  }

                              })

                          .to("jbi:endpoint:http://test/MyProviderService/myProviderB")

                              // add Processor work around to reset the context classloader

                              .process(new Processor() {

                                  public void process(Exchange arg0) throws Exception {

                      Thread.currentThread().setContextClassLoader(MyRouteBuilder.class.getClassLoader());

                                      // do something else  that uses context classloader

                                  }

                              })

                      );

                       

                       

                      I have to disagree and say that this is indeed a bug.  Maybe in the servicemix-camel component or at least with the way ServiceMix sets the current Thread from the pool and/or how ServiceMix sets the current thread's context classloader.

                      • 8. Re: servicemix-camel classloader problem
                        njiang

                        Hi,

                         

                        I'm trying to find a way to set the classloader in servicemix-camel component, but after digging the code for a while , I don't think it is possible to do it inside servicemix-camel or other servicemix component.

                         

                        servicemix-camel component works an adapter for camel and servicemix, so camel Jbi endpoint can talk with servicemix endpoint. As you know camel uses pipeline pattern to chain the processors and endpoints (which can act as consumer and producer) to together.

                         

                        Now back to the classloader issue, for the Jbi endpoint in your routing rule, it acts as a camel producer which delegate the camel exchange to servicemix jms endpoint, and then calling the processor which is after the Jbi endpoint like pipeline.  Please check out the JbiProducer.process() and CamelConsumerEndpoint for more information.

                         

                        Since CamelConsumerEndpoint and JbiEndpoint don't know anything about the route application classloader and the further processors , I don't find a way to set the thread context classloader there and I don't want to pollute the camel-core (the control code for the pipeline).  The best place of setting the thread context classloader is the process method which I showed to you (we can also add a interceptor to implement this feature) :).

                         

                        Edited by: njiang on Oct 13, 2009 9:07 PM

                        • 9. Re: servicemix-camel classloader problem
                          rmayo

                          Hi,

                           

                          Thanks again for looking into the issue.  I wouldn't want you to have to pollute the camel or servicemix code either. 

                           

                          The solution of setting the context classloader yourself after sending to any jbi endpoint will work.  Thanks.

                           

                          It is not what I would have expected to have to do though, so I think it would be a good idea to document it somewhere in both the servicemix and camel documentation.

                           

                          Thanks again.