5 Replies Latest reply on Dec 18, 2009 6:33 AM by Mauro Molinari

    Problems with jBPM 3.2.6.SP1 and class loaders

    Mauro Molinari Novice

      Hello to all.

      I'm using jBPM 3.2.6.SP1 embedded into a web application and I have an issue with class loaders.

      I'm going to explain it better.

      I have a process definition that defines a node with an associated timer:

      when the timer fires, a transition is taken. After this event occurs, the token passes to another node

      (a task node, precisely), whose task-create event is bound to an action handler that retrieves

      a (persistent) variable from the process instance context and then adds a comment on the task

      instance just created. Such variable is of type my.package.MyClass and this class is a serializable Java

      bean provided with the process definition (so it's in the PAR), meant to be used exactly in this

      way (persisted with the process instance, then retrieved, etc.).

      As I said, the mentioned code is executed asynchronously by the JobExecutor, because it is bound to a

      timer. However, my problem is that during the execution of the task-create action handler, when I

      retrieve the instance of my.package.MyClass class stored in the context I get the following error:

      java.lang.ClassCastException: my.package.MyClass cannot be cast to my.package.MyClass

      Please note that, as I said, my.package.MyClass is in the PAR and it is NOT in the web application

      classpath, so an only my.package.MyClass exists and it is the one in the PAR.

       

      The line of code I use to retrieve the variable value is the following:

      {code}

      MyClass myObject = (MyClass) executionContext.getVariable(MY_VAR_NAME);
      {code}


      The whole stack trace follows:

      {quote}
      java.lang.ClassCastException: my.package.MyClass cannot be cast to my.package.MyClass

      at my.package.TimerCreateActionHandler.execute(TimerCreateActionHandler.java:31)

      at org.jbpm.graph.def.Action.execute(Action.java:125)
      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
      at java.lang.reflect.Method.invoke(Method.java:597)
      at org.hibernate.proxy.pojo.cglib.CGLIBLazyInitializer.invoke(CGLIBLazyInitializer.java:157)
      at org.jbpm.graph.def.Action$$EnhancerByCGLIB$$8223ae63.execute(<generated>)
      at org.jbpm.graph.def.Action.execute(Action.java:118)
      at org.jbpm.graph.def.GraphElement.executeAction(GraphElement.java:281)
      at org.jbpm.graph.def.GraphElement.executeActions(GraphElement.java:238)
      at org.jbpm.graph.def.GraphElement.fireAndPropagateEvent(GraphElement.java:212)
      at org.jbpm.graph.def.GraphElement.fireEvent(GraphElement.java:195)
      at org.jbpm.taskmgmt.exe.TaskInstance.create(TaskInstance.java:157)
      at org.jbpm.taskmgmt.exe.TaskMgmtInstance.createTaskInstance(TaskMgmtInstance.java:211)
      at org.jbpm.graph.node.TaskNode.execute(TaskNode.java:180)
      at org.jbpm.graph.def.Node.enter(Node.java:388)
      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
      at java.lang.reflect.Method.invoke(Method.java:597)
      at org.hibernate.proxy.pojo.cglib.CGLIBLazyInitializer.invoke(CGLIBLazyInitializer.java:157)
      at org.jbpm.graph.def.Node$$EnhancerByCGLIB$$649732f.enter(<generated>)
      at org.jbpm.graph.def.Transition.take(Transition.java:166)
      at org.jbpm.graph.def.Node.leave(Node.java:477)
      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
      at java.lang.reflect.Method.invoke(Method.java:597)
      at org.hibernate.proxy.pojo.cglib.CGLIBLazyInitializer.invoke(CGLIBLazyInitializer.java:157)
      at org.jbpm.graph.def.Node$$EnhancerByCGLIB$$649732f.leave(<generated>)
      at org.jbpm.graph.exe.Token.signal(Token.java:226)
      at org.jbpm.graph.exe.Token.signal(Token.java:179)
      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
      at java.lang.reflect.Method.invoke(Method.java:597)
      at org.hibernate.proxy.pojo.cglib.CGLIBLazyInitializer.invoke(CGLIBLazyInitializer.java:157)
      at org.jbpm.graph.exe.Token$$EnhancerByCGLIB$$b2d819c3.signal(<generated>)
      at org.jbpm.job.Timer.execute(Timer.java:88)
      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
      at java.lang.reflect.Method.invoke(Method.java:597)
      at org.hibernate.proxy.pojo.cglib.CGLIBLazyInitializer.invoke(CGLIBLazyInitializer.java:157)
      at org.jbpm.job.Job$$EnhancerByCGLIB$$9ac69912.execute(<generated>)
      at another.package.MyJobExecutor$MyJobExecutorThread$3.execute(MyJobExecutor.java:159)

      at another.package.MyJobExecutor$MyJobExecutorThread$3.execute(MyJobExecutor.java:137)

      at another.package.MyJobExecutor$MyJobExecutorThread.executeInTransaction(MyJobExecutor.java:261)

      at another.package.MyJobExecutor$MyJobExecutorThread.executeJob(MyJobExecutor.java:135)

      {quote}

       

      Please note that MyJobExecutor and MyJobExecutorThread are two extensions of JobExecutor and JobExecutorThread

      (provided with the webapp) that behave exactly like the superclasses except for the fact that they

      launch the job execution within a JTA transaction (this is needed by my webapp transactional

      infrastructure).

       

      Now, what is going on?

      My suspect is against the fix for bug JBPM-1448: after it,

      {quote}org.jbpm.graph.def.Action.execute(ExecutionContext){quote} is as follows:

       

      {code}

      ClassLoader surroundingClassLoader = Thread.currentThread().getContextClassLoader();
      try {
      // set context class loader correctly for delegation class (https://jira.jboss.org/jira/browse/JBPM-1448)
      Thread.currentThread().setContextClassLoader(JbpmConfiguration.getProcessClassLoader(executionContext.getProcessDefinition()));

      // ... execute action ...

      } finally {
      Thread.currentThread().setContextClassLoader(surroundingClassLoader);
      }

      {code}

       

      Leaving the default settings for class loaders handling, this causes the current thread to be given

      a new instance of the process class loader as its context class loader. This new instance of the process

      class loader has the class loader of jBPM as its parent, that is (as far as I understand it well) the

      class loader of the webapp. In my case I suspect that the following is happening:

      *some of the objects referenced by the ExecutionContext (including MY_VAR_NAME variable value,

      and its class my.package.MyClass) are initialized (and their classes are loaded) through class loader

      CL1, which is a process class loader with the webapp class loader as its parent

      *before executing the task-create action handler, a new process class loader CL2 (whose parent is still

      the webapp class loader) is set as the thread context class loader

      *the code of the action handler is loaded through CL2, that is, when the line

      {code}MyClass myObject = (MyClass) executionContext.getVariable(MY_VAR_NAME){code} is executed,

      Java tries to cast the variable value to the my.package.MyClass class instance loaded by CL2

      *however, meanwhile my.package.MyClass was loaded by CL1 and the variable value was already initialized

      (by Hibernate?), so that the actual value of MY_VAR_NAME is already in memory and is of type

      my.package.MyClass class instance, but loaded by CL1

      *since my.package.MyClass loaded by CL2 is different from my.package.MyClass loaded by CL1, a

      ClassCastException occurs

       

      This said, I think that the solution of this problem would be to make

      {code}org.jbpm.instantiation.DefaultProcessClassLoaderFactory{code} create process class loaders

      with the current thread context class loader (rather than the jBPM class loader) as parent. In this

      way, in my cse, CL2 would have CL1 as its parent, so another instance of my.package.MyClass wouldn't be

      loaded when the task-create action handler is executed and the ClassCastException wouldn't occur.

       

      However, I would like to discuss with you (and with the jBPM developers in particular) if my supicions and

      my conclusions are right or if there's something else going wrong (so that I need to find another

      solution).

       

      Thanks in advance.

      Mauro.

        • 1. Re: Problems with jBPM 3.2.6.SP1 and class loaders
          Mauro Molinari Novice
          Sorry for the awful formatting, but I find the message editor of the new forum very confusing and unconfortable to use. Moreover, it does not work with Opera 10.10...

          I tried to use the Wiki Markup but with poor success...
          • 2. Re: Problems with jBPM 3.2.6.SP1 and class loaders
            Mauro Molinari Novice

            Shame on me, I found the solution... I was migrating from jBPM 3.3.0 GA, so my jbpm.cfg.xml contained the default settings. But looking at the javadoc and the source code of org.jbpm.util.ClassLoaderUtil.getClassLoader(), I discovered that I can configure the jbpm.classLoader property to "context" to get the exact behaviour I need.

             

            However, IMHO, this should be the default setting...

             

            Mauro.

            • 3. Re: Problems with jBPM 3.2.6.SP1 and class loaders
              Mauro Molinari Novice

              Unfortunately, my problem is not solved yet. By debugging I discovered that the current thread context class loader is that of the webapp, but the variable value has been loaded through another process class loader that must have been created somewhere else in the chain of actions that leads to my use case scenario.

               

              So I still have two versions of my.package.MyClass loaded by two instances of ProcessClassLoader with no relationship between each other and I'm stuck... The only solution I can think of is to define a process class loader factory that caches instances for a given process definition (identified by its id), so that every request to the factory for a specific process definition class loader is served with the same ProcessClassLoader instance, instead of creating a new instance every time...

               

              Anyway, I still have some doubts about the current (default) jBPM behaviour, whether it's been done like this on purpose, whether I'm missing something or if this is actually a problem and I should open an issue in JIRA. Maybe my problem is the same as https://jira.jboss.org/jira/browse/JBPM-2428, although my scenario is a bit different (I'm not undeploying and redeploying the process).

               

              Any other suggestion is welcome.

              • 4. Re: Problems with jBPM 3.2.6.SP1 and class loaders
                Mauro Molinari Novice

                I was able to solve the original problem by doing as I said, that is creating a new ProcessClassLoaderFactory implementation that caches class loaders for a given process definition. To successfully do this I also had to create my own extension of ClassLoader, because the default ProcessClassLoader keeps a reference to a ProcessDefinition instance, which is not good given that the class loader will then be used in different contexts (with different Hibernate sessions, etc.).

                 

                However, now I'm encountering another similar problem, this time with VariableInstance. The method getValue() will work correctly if and only if:

                - the class of its value is NOT a class in the PAR

                OR

                - a token is set in the variable instance: I can't understand if this should always be true, however I find cases in which token is null; now, if the class of the value is a class in the PAR, the method getValue() will call org.jbpm.context.exe.Converter.revert(? super Object), without specifying any token... this causes the org.jbpm.context.exe.converter.SerializableToByteArrayConverter to use a plain ObjectInputStream to deserialize the value and, if there is not the process class loader in the caller chain, a ClassNotFound exception will occur

                 

                Actually, I find some records in JBPM_VARIABLEINSTANCE table with column TOKEN_ set to null... How is it possible?

                 

                I've opened a JIRA for this (https://jira.jboss.org/jira/browse/JBPM-2692). I can't find any viable workaround without patching jBPM. Also, the easisest patch I can think of is to promote org.jbpm.context.exe.variableinstance.ByteArrayInstance.getObject() to public, get the serialized value with it and do the deserialization by myself.

                 

                Any other suggestion is welcome.

                • 5. Re: Problems with jBPM 3.2.6.SP1 and class loaders
                  Mauro Molinari Novice
                  I updated the JIRA report with a proposed patch. The latest problem was caused by a deleted variable instance still reachable when looking at the process instance logs.