cancel
Showing results for 
Search instead for 
Did you mean: 

VariableInstanceEntity saves value twice for new variables

naag
Champ in-the-making
Champ in-the-making
Hi all,

I'm trying to implement a custom VariableType (based on MongoDB) and noticed that VariableType.setValue() will be called twice when the affected variable did not yet exist in the current scope. The first call creates the new VariableInstanceEntity via VariableScopeImpl.createVariableLocal() and saves it immediately. The second call happens right afterwards via VariableScopeImpl.setVariableInstanceValue(), and just saves the exact same data again. The only thing happening in between the two calls is back pointer initialization. The code in question:


public void createVariableLocal(String variableName, Object value) {
   ensureVariableInstancesInitialized();

   if (variableInstances.containsKey(variableName)) {
      throw new ActivitiException("variable '" + variableName
            + "' already exists. Use setVariableLocal if you want to overwrite the value");
   }

   VariableTypes variableTypes = Context.getProcessEngineConfiguration().getVariableTypes();

   VariableType type = variableTypes.findVariableType(value);

   // ***** First call to VariableType.setValue() from inside here
   VariableInstanceEntity variableInstance = VariableInstanceEntity.createAndInsert(variableName, type, value);
   initializeVariableInstanceBackPointer(variableInstance);
   variableInstances.put(variableName, variableInstance);

   // ***** Second call to VariableType.setValue() from inside here
   setVariableInstanceValue(value, variableInstance);
}

In my case this brings to issues:

  • My implementation of VariableType will commit a new record to MongoDB every time that VariableType.setValue() is called. The intention is to trace every update to the variables. If setValue() is called twice, there will be two records in MongoDB.

  • I would like to add some additional attributes to the MongoDB documents, namely the Process Instance ID, Execution ID and Task ID (if available). But during the first call to setValue(), the VariableInstanceEntity is not completely initialized because VariableScopeImpl.initializeVariableInstanceBackPointer() has not been called yet. With the second call, the back pointers are there. So I can cast the valueFields argument to VariableInstanceEntity and get the data.
Now the first issue I can solve by introducing something like a "dirty" field in my variables, so my VariableType will only save the record when there were changes in the data. But I have no idea how I can obtain the details about the current execution.

I tried via Context.getExecutionContext().getExecution() from my VariableType, but face a EmptyStackException 😞

So my two questions are:

  • Am I missing something about VariableType.save() being called twice? Or is it a defect?

  • Is there any way I can obtain the current execution entity (either TaskEntity or ExecutionEntity)? I was thinking to obtain an instance of BusinessProcess CDI bean, but it feels hacky to me.
Thank you for all ideas 🙂

Regards,
Peter
3 REPLIES 3

frederikherema1
Star Contributor
Star Contributor
Nice stuff you're doing, I must say Smiley Wink

Indeed, the "setValue" is called twice. When you look at the VariableInstanceEntity, the setValue is called from create() and setValue() itself. Since the variables are designed to be stored in a ValueFields object which is only written once to the DB (at the end of the transaction) it's not really an issue. I get the point of this being a "redundant" call, but not sure this is something we will change because of the reasoning before.

If you get the setValue-call, the command-context should be available in case there is an execution active (when the variable is set from within the execution, e.g.. javaDelegate or listener). I think that when using the runtime-service for setting variables, the execution-context will be absent. I'm afraid the VariableType approach is a bit isolated from the rest of the engine and it will be hard to get hold of the details you need in an easy way.

Another suggestion I want to bring forward for this kind of requirement is overriding (perhaps subclassing/wrapping existing manager) the DBSqlSession. This is done by defining your own factory for it (forum contains some info on this, quite sure). This allows you override the insert(entity). Here you can first delegate to the activiti SQLSession and then, in case of a HistoricVariableUpdate, log whatever you want from here. The advantage is that the entity contains exeuction-id wheres the type doesn't.
On top of that, the insert is only called once, whereas the setValue() was called twice. IMHO, this fixes both of your problems.

naag
Champ in-the-making
Champ in-the-making
Activiti is a great software, so it inspires us to do nice things with it 🙂

Actually I like the VariableType idea. I use the fields TEXT_ and TEXT2_ to store information about the MongoDB document instance so it can be retrieved again later, so far so good. I thought it to be quite flexible.

Regarding the context, I complete a task via BusinessProcess.completeTask() and this fetches the current task from the associationManager. So in a certain sense, an execution is active, but I guess not in the sense of the core-engine since CDI is just an extension. So now I will first try to acquire the BusinessProcess instance from the BeanManager and see where that takes me (not nice, but alright). Then later I will surely investigate your suggestion about extending DbSqlSession. I guess this is the better approach anyway 🙂

Thanks so much for your quick feedback, it's really helpful to get the right directions!

Regards,
Peter

frederikherema1
Star Contributor
Star Contributor
Good luck with your quest Smiley Wink