cancel
Showing results for 
Search instead for 
Did you mean: 

JPA integration not working 100% for transient variables???

tbee
Champ in-the-making
Champ in-the-making
I've setup JPA integration and initially it seems to work, but I find a deviation.

My entity classes all extend an AbstractBean class where the code for a.o. property change events is present. This class also has the possibility to block (ignore) these events (this is required when setting up default values, you do not want the entity to become marked "dirty"). Anyhow, in order to block these events there is a transient property in the abstract bean class:


@Transient transient final public AtomicInteger ignoreChangesGate = new AtomicInteger();

I've changed that property temporarly to public, so I can inspect it. Now, when a entity is put into the variables map at the starting of a workflow, this property naturally is not null:


      Map<String, Object> lProcessVariables = new HashMap<>();
      lProcessVariables.put("a", this);
      System.out.println("!!!1 iIgnoreChangesGate = " + this.ignoreChangesGate);
      ProcessInstance lProcessInstance = lProcessEngine.getRuntimeService().startProcessInstanceByKey("frozn", lProcessVariables);

A bit later in the process a task listener is called, which fetches this variable:


   @Override
   public void notify(org.activiti.engine.delegate.DelegateTask delegateTask)
   {
      Application lApplication = (Application)delegateTask.getVariable("a");
      System.out.println("!!!2 iIgnoreChangesGate = " + lApplication.ignoreChangesGate);
      …

When this workflow is run, the following output appears on the console:


!!!1 iIgnoreChangesGate = 0
!!!2 iIgnoreChangesGate = null

The transient property has not been initialized?!! This is very strange, since it is final and the constructor has finished.
So as a test I simply reloaded the entity from the EM.


   @Override
   public void notify(org.activiti.engine.delegate.DelegateTask delegateTask)
   {
      Application lApplication = (Application)delegateTask.getVariable("a");
      System.out.println("!!!2 iIgnoreChangesGate = " + lApplication.ignoreChangesGate);
      // reload
      lApplication = (Application)lEntityManager.find(Application.class, lApplication.getApplicationId());
      System.out.println("!!!3 iIgnoreChangesGate = " + lApplication.ignoreChangesGate);
      …

The output now is:


!!!1 iIgnoreChangesGate = 0
!!!2 iIgnoreChangesGate = null
!!!3 iIgnoreChangesGate = 0

So something is not going right when restoring the entity.
5 REPLIES 5

trademak
Star Contributor
Star Contributor
Hi,

Do you have async behavior in your process definition?
Can you create a unit test that shows this behavior?

Thanks,

tbee
Champ in-the-making
Champ in-the-making
I do not have an async process.

I'vecreated my own implementation, which generates a String from an entity (classname:id) and reloads the entity based on the information in the listener. This works without a problem. (It also has the advantage that, because I mix and match workflow and regular code, it makes sure only one EntityManager is used; no conflicts.)

A unit test, well it is happening as part of my own unit test, so I could strip that down… I'll give it a try.

tbee
Champ in-the-making
Champ in-the-making
Yeah. Now that I've stripped it down far enough, the problem is gone. So there is some kind of conflict between what I do in the frame of my application, and how Activiti restores the entity. So I can't give you a simple unit test that reproduces the problem.

What I do know is that:
- in the activiti implementation a final field ends up being null (that is very strange)
- using my simple implementation of encode-as-string and find-using-string in the same context works fine

My question would be: what magic are you guys doing to restore the entity? It seems the constructor is somehow by passed. (Note that I have 5 layer deep entity classes.) As said, this simple piece of code works fine:

static public <T> T fromId(Object id)
{
  try
  {
   // preconditions
   String lId = "" + id;
   if ("null".equals(lId)) return null;
  
   // split id
   int lIdx = lId.indexOf(":");
   String lClassname = lId.substring(0, lIdx);
   BigInteger lPK = new BigInteger(lId.substring(lIdx + 1));
  
   // get entity
   EntityManager lEntityManager = EntityManagerFinder.find();
   T lEntity = (T)lEntityManager.find(Class.forName(lClassname), lPK);
   return lEntity;
  }
  catch (ClassNotFoundException e) { throw new IllegalStateException(e); }
}

I can offer you to take a remote-desktop peek in my development environment.

tbee
Champ in-the-making
Champ in-the-making
Hmmm. I've taken a peek at what is in the ACT_RU_VARIABLE table and I see that my approach stores a "nl.o837.frozn.bm.Application:74" text. However, Activiti seems to store a serializable. Do you actually serialize and store the whole entity? That should not be, the user manual says "When the variable is requested the next time, it will be loaded from the EntityManager based on the class and Id stored.".

tbee
Champ in-the-making
Champ in-the-making
It's the "transient" keyword; it tells Java's serializer to not serialize a field. And that is how you get a final field to become null…