cancel
Showing results for 
Search instead for 
Did you mean: 

JPA process variables not fully supported?

chris_joelly
Champ in-the-making
Champ in-the-making
Hello,

i just figured out that JPA entities are defined as not cachable in JPAEntityVariableType.

I have the following use case where this is a real problem:

i use JSF/activiti-cdi, in the JSF based user task views i use things like:

<customer:detail customer="#{processVariables['order'].customer}" />
I observed that the entity 'customer', and its related entity 'address' is updated correctly when i modify values in the JSF view and execute

<h:commandButton id="complete" value="#{msg.completeTaskButton}" action="#{businessProcess.completeTask(true)}" />
Following the user task there are service tasks which works on the process variable 'order'.
But when i execute code like

   @Override
   public void execute(DelegateExecution execution) throws Exception {
      Order order = (Order) execution.getVariable(VariableNames.ORDER);
   }
i see that the values set on the entities just before in the user task are not there.
The reason is that in VariableInstanceEntity the following happens:

  public Object getValue() {
    if (!type.isCachable() || cachedValue==null) {
      cachedValue = type.getValue(this);
    }
    return cachedValue;
  }
In the 'order' referenced by cachedValue i see the modified entity from the user task, but due to the fact that JPAEntityVariableType is not cachable
it is read from the database and all modifications are lost.

Is this the expected behavior for JPA process variables?

If so, how can i solve this issue?

Thanks
Chris
13 REPLIES 13

chris_joelly
Champ in-the-making
Champ in-the-making
ok.

well, this conclusion matches with my conclusion after i studied the code during my debugging sessions. I was heavily missleaded by the user guide and some blog postings which say that Activiti supports JPA entities as process variables, which it actually does when it comes to reading entities from the database. When it comes to storing them, and for sure people want to modify them during user tasks and service tasks, then Activiti support is not there.

Especially the blog post by Bernd Rücker (http://www.bpm-guide.de/2012/04/04/pageflow-vs-process-flow-and-ui-mediator-pattern/) gives the impression that using activiti-cdi together with JSF and other JEE technologies makes such use cases possible. Only JPA variables are not covered.. Because they dont work out of the box in such an environment when they are modified through JSF and wants to get persisted when BusinessProcess.completeTask() is executed from an JSF command … Smiley Happy

frederikherema1
Star Contributor
Star Contributor
Yes, that's true, when working in such environments (and activiti participates in such transitions/contexts) the updates to the variables themselves are reflected as where activiti out of the box, only uses the id's. I didn't grasp the initial problem you had, so tok a while to see Smiley Wink

chris_joelly
Champ in-the-making
Champ in-the-making
I want to come back to the following statements:
Your approach seems valid, up to the point where you "flush" the entity-manager on each "set" of a variable. The entity-manager should only be flushed at the end of the command-context-chain (if all goes well).
What is the command-context-chain? When i complete a user task, this starts a command-context-chain till execution arrives at the next wait state? Or from the start process to some wait state? Well, with the ensure-to-get-the-enities-id-flush this strategy already seems broken. From a performance perspective this might have an influence if many JPA entities are used as process variables and a flush is executed after each setValue. But if something goes bad a roll back is executed and the state of the process will still be valid, in terms of the execution and the data are synchronized?
So instead of getting the entity-manager directly in the setValue(), rather you should initialize the JPAEntityManagerSession the way the reading of JPA-variables does. This way, IF a variable is set, the entity manager is initialized in the session and will be not-null when session is flushed, resulting in the desired behavior.
With JPAEntityManagerSession u mean the EntityManagerSessionImpl? if i got u right this is what i did when adding the em.merge… What about removing the entityManagerSession.flush() from the JPAEntityVariableType.setValue() call? This is only needed if JPA entities are created within Activiti tasks which dont have their identity set? In my case this would never be the case because the processes will always work on already existing entities. But as it has only a minor performance impact i think its not a big deal?
Could you try this approach and perhaps attach the patch, so we can introduce this in the acidity code-base?
Well, that 4 lines of codes … if Activiti is designed the way that it does not touch JPA entities it might introduce side effects on other users which expect Activiti to not change their JPA entities? Maybe it is possible to introduce it with another jpaXXX config parameter for activiti.cfg.xml which defaults to the actual behavior?

chris_joelly
Champ in-the-making
Champ in-the-making
Today i tried to create an JPA entity from within an service task which is executed via an process which is triggered by a start timer event and set this newly created entity as process variable. The entity is just instantiated with new and no PersistenceContext was involved. only setters are called to set its member variables.

I did not expect that it would work, but the things i observed was somehow strange Smiley Happy

The summary is:

the entity was persisted two times, with two different ids, but only the second one was stored as process variable.

Now the long story Smiley Happy

I think the 'problem' is in the method VariableScopeImpl.createVariableLocal() where the entity is stored 'twice' to the variableInstance, first with the VariableInstanceEntity.createAndInsert() call where the entity is modified during the flush and merge and then with the setVariableInstanceValue(value, variableInstance), which still use the unmodified entity because the merge and flush operations are not propagated back to the original entity.

I just added a value = variableInstance.getValue() just before the setVariableInstanceValue(value, variableInstance) and re-read the entity from the persistence context. And now the second entity is not created and everything looks fine. At least for my use cases.

So, finally i replaced the entityManagerSession.flush() with an em.merge() in JPAEntityVariableType.setValue() and added the above mentioned line to  VariableScopeImpl.createVariableLocal() to make the persistence of existing and new JPA entities work with Activiti.

What do u think? Would this introduce side effects?


One downside i could already see when i run the Activiti unit tests:

Here is one of the JPA entities used in the JPA test cases, and how they look before and after the merge:

before:
org.activiti.standalone.jpa.FieldAccessJPAEntity@4260ab

after:
org.apache.openjpa.enhance.org$activiti$standalone$jpa$FieldAccessJPAEntity$pcsubclass@df88d2

and because the @Entity annotation is not declared @Inherited it seems that the OpenJPA enhancement removes the Entity annotation of the JPA entities when they are merged to a persistence contet…

So, the detection if an object is an JPA entity breaks now when using OpenJPA as JPA provider when a merge is performed. It seems that the unit tests never run with entities which belonged to an persistence context?

some related stuff:
http://greensopinion.blogspot.co.at/2009/08/how-i-lost-my-annotations-jpa-hibernate.html
http://struberg.wordpress.com/2012/01/08/jpa-enhancement-done-right/