Problems with jBPM 3.2.6.SP1 and class loaders
mauromol Dec 15, 2009 4:33 AMHello 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 atimer. 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 applicationclasspath, 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}at my.package.TimerCreateActionHandler.execute(TimerCreateActionHandler.java:31)
The whole stack trace follows:
{quote}
java.lang.ClassCastException: my.package.MyClass cannot be cast to my.package.MyClassat 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.