Custom history level - per process-definition history level

Hello,

I am trying to implement a custom history level. I’ve been following this tutorial. There is an example on how to create a history level based on process instance id. I edited this and now I am facing this one problem. I cannot get process definition id from entities which come from start event form.

This is the method I use for getting process definition:

protected String getProcessDefinitionId(Object entity) {
    if (entity instanceof ExecutionEntity) {
        return ((ExecutionEntity) entity).getProcessDefinitionId();
    } else if (entity instanceof VariableInstance) {
        VariableInstanceEntity variableInstance = (VariableInstanceEntity) entity;
        ProcessInstance processInstance = getRuntimeService().createProcessInstanceQuery()
                .processInstanceId(variableInstance.getProcessInstanceId()).singleResult();
        return processInstance.getProcessDefinitionId();
    } else if (entity instanceof HistoryEvent) {
        return ((HistoryEvent) entity).getProcessDefinitionId();
    } else if (entity instanceof IdentityLinkEntity) {
        return ((IdentityLinkEntity) entity).getProcessDefId();
    } else if (entity instanceof TaskEntity) {
        return ((TaskEntity) entity).getProcessDefinitionId();
    } else if (entity instanceof DecisionDefinitionEntity) {
        DecisionDefinitionEntity decisionDefinitionEntity = (DecisionDefinitionEntity) entity;
        ProcessDefinition processDefinition = getRepositoryService().createProcessDefinitionQuery()
                .deploymentId(decisionDefinitionEntity.getDeploymentId()).singleResult();
        return processDefinition.getId();
    } else {
        throw new ProcessEngineException("Unable to find process definition id for class " + entity.getClass().getName());
    }
}

I am getting NullPointerException on the line return processInstance.getProcessDefinitionId();. I suppose it has something to do with transaction-commiting, but I can’t think of any solution.

Any tips on how to do this?

Thank you.
Denis

Hey Denis,

it seems that the process instance query returns null.
So there are questions which should be answered first:

  • Does the VariableInstanceEntity object contain the correct process instance id?
  • Is the process instance running ?

Greets,
Chris

Hi Chris,

thanks for your reply. Yes, the query returns null.

The VariableInstanceEntity contains “some” process instance id (different from null).

I’m not sure about your second question (and partly first, too). The NullPointerException occures when I start a new process instance, I mean it happens when saving inputs from the start event form.

When I print out the process instance id, that I get from VariableInstanceEntity object, and search for it in my database, I get no results, ie. the process instance does not exist. I was thinking that it could be something with transactions.

For example: An user fills up the form in start event and clicks to proceed. A transaction begins and the form variables are being saved into the database. Then it asks the custom history level whether each variable should be stored in history service. The history level uses RuntimeService to search for the ProcessInstance by the given process instance id and fails because the transaction wasn’t commited yet. That is the point where the NullPointerException is thrown. It rollbacks the transaction, so the process instance isn’t to be found in the database, when I search for it.
– although, I’m not really sure about this scenario (could’n find anything about it)

Hey Denis,

is it possible that you post your process model?
I assume that your process instance is already finished at the time you execute the query to get the process instance.
Do you tried to query the historic process instance?

Greetings,
Chris

Hey Chris,
(sorry about the delay caused by me)
I am attaching a definition of the process that I am testing the history level on, but I dont think that the instance is finished at the time of the query execution since it dies after starting the process (and asking my history level whether the entity should be logged).

I tried to query historic process instance now. It is null, too.

In my database, I cannot find the process by the process instance id, that I get from the entity passed to history level, neither in historic or runtime instances.

Greetings,
Denis

test.bpmn (4.4 KB)

I still haven’t figured it out. Does anyone have an idea, please?

Hi,

so after a few hours of searching and getting no relevant results I came up with a solution which I am not proud of.

Let me sum it up for any person interested in this.

I was trying to implement a custom history level, which would decide which history level (already implemented in Camunda) would be used according to definition of a process in which the history-event occured.

I have found a solution that solves a slightly different task. It is the per-process history level available in camunda repository. Since I wanted to decide by process definition id (not by process instance id), I tried to modify the solution.

At this point, I faced a problem that I couldn’t figure out elegantly.

These are types of entities that are being resolved by history level:

  • ExecutionEntity
  • VariableInstance
  • HistoryEvent
  • IdentityLinkEntity
  • TaskEntity
  • DecisionDefinitionEntity

The VariableInstance was critical for me, because I couldn’t get ID of its process definition from the instance. I needed to make it work, so I used reflection for it.

I am attaching my current code:

public class PerProcessHistoryLevel implements HistoryLevel {

    private static final String HISTORY_LEVEL_NAME = "per-process";
    private static final int HISTORY_LEVEL_ID = 12;

    private static final String DEFAULT_HISTORY_LEVEL_NAME = HISTORY_LEVEL_FULL.getName();
    private static final String PROPERTY_NAME = "history";

    protected Map<String, HistoryLevel> historyLevels = new HashMap<>();
    protected Map<String, HistoryLevel> delegateHistoryLevelPerProcess = new HashMap<>();

    private RepositoryService repositoryService;

    private final ProcessEngineConfigurationImpl processEngineConfiguration;

    public PerProcessHistoryLevel(ProcessEngineConfigurationImpl processEngineConfiguration) {
        historyLevels.put(HISTORY_LEVEL_NONE.getName(), HISTORY_LEVEL_NONE);
        historyLevels.put(HISTORY_LEVEL_ACTIVITY.getName(), HISTORY_LEVEL_ACTIVITY);
        historyLevels.put(HISTORY_LEVEL_AUDIT.getName(), HISTORY_LEVEL_AUDIT);
        historyLevels.put(HISTORY_LEVEL_FULL.getName(), HISTORY_LEVEL_FULL);

        this.processEngineConfiguration = processEngineConfiguration;
    }

    public void addHistoryLevels(List<HistoryLevel> historyLevels) {
        for (HistoryLevel historyLevel : historyLevels) {
            this.historyLevels.put(historyLevel.getName(), historyLevel);
        }
    }

    public int getId() {
        return HISTORY_LEVEL_ID;
    }

    public String getName() {
        return HISTORY_LEVEL_NAME;
    }

    public boolean isHistoryEventProduced(HistoryEventType historyEventType, Object entity) {
        if (entity == null) {
            return true;
        }
        return isDelegateHistoryLevelEventProduced(historyEventType, entity);
    }

    protected Collection<CamundaProperty> getCamundaProperties(String processDefinitionId) {
        Process process = (Process) getRepositoryService().getBpmnModelInstance(processDefinitionId).getDefinitions().getUniqueChildElementByType(Process.class);

        ExtensionElements extensionElements = process.getExtensionElements();
        if (extensionElements != null) {
            CamundaProperties properties = (CamundaProperties) extensionElements.getUniqueChildElementByType(CamundaProperties.class);
            if (properties != null) {
                return properties.getCamundaProperties();
            }
        }
        return null;
    }

    protected boolean isDelegateHistoryLevelEventProduced(HistoryEventType historyEventType, Object entity) {
        String processDefinitionId = getProcessDefinitionId(entity);
        if (processDefinitionId == null) {
            return historyLevels.get(DEFAULT_HISTORY_LEVEL_NAME).isHistoryEventProduced(historyEventType, entity);
        }
        HistoryLevel delegateHistoryLevel = getDelegateHistoryLevel(processDefinitionId);

        return delegateHistoryLevel != null && delegateHistoryLevel.isHistoryEventProduced(historyEventType, entity);
    }

    protected HistoryLevel getDelegateHistoryLevel(String processDefinitionId) {
        HistoryLevel delegateHistoryLevel = delegateHistoryLevelPerProcess.get(processDefinitionId);

        if (delegateHistoryLevel == null) {
            setDelegateHistoryLevel(processDefinitionId);
            delegateHistoryLevel = delegateHistoryLevelPerProcess.get(processDefinitionId);
        }

        return delegateHistoryLevel;
    }

    protected void setDelegateHistoryLevel(String processDefinitionId) {
        Collection<CamundaProperty> camundaProperties = getCamundaProperties(processDefinitionId);

        String historyLevelName = DEFAULT_HISTORY_LEVEL_NAME;
        if (camundaProperties != null) {
            for (CamundaProperty camundaProperty : camundaProperties) {
                if (camundaProperty.getCamundaName().equals(PROPERTY_NAME)) {
                    historyLevelName = camundaProperty.getCamundaValue();
                    break;
                }
            }
        }
        HistoryLevel historyLevelToSet = historyLevels.get(historyLevelName);
        delegateHistoryLevelPerProcess.put(processDefinitionId, historyLevelToSet);
    }

    protected RepositoryService getRepositoryService() {
        if (repositoryService == null) {
            repositoryService = processEngineConfiguration.getProcessEngine().getRepositoryService();
        }
        return repositoryService;
    }

    protected String getProcessDefinitionId(Object entity) {
        if (entity instanceof ExecutionEntity) {
            return ((ExecutionEntity) entity).getProcessDefinitionId();
        } else if (entity instanceof VariableInstance) {
            VariableInstanceEntity variableInstance = (VariableInstanceEntity) entity;
            Class<?> c = variableInstance.getClass();
            try {
                Field executionField = c.getDeclaredField("execution");
                executionField.setAccessible(true);
                Object executionFieldObj = executionField.get(variableInstance);
                ExecutionEntity execution = (ExecutionEntity) executionFieldObj;
                if (execution != null) {
                    return execution.getProcessDefinitionId();
                } else {
                    return null;
                }
            } catch (NoSuchFieldException e) {
                throw new ProcessEngineException(e);
            } catch (IllegalAccessException e) {
                throw new ProcessEngineException(e);
            }
        } else if (entity instanceof HistoryEvent) {
            return ((HistoryEvent) entity).getProcessDefinitionId();
        } else if (entity instanceof IdentityLinkEntity) {
            return ((IdentityLinkEntity) entity).getProcessDefId();
        } else if (entity instanceof TaskEntity) {
            return ((TaskEntity) entity).getProcessDefinitionId();
        } else if (entity instanceof DecisionDefinitionEntity) {
            return null;
        } else {
            throw new ProcessEngineException("Unable to find process definition id for class " + entity.getClass().getName());
        }
    }

}

I know that there are many parts miserably designed and I would be trully grateful for any suggestions on how to implement it properly.

Thank you,
Denis

Hi Denis,

Feel free to upload your current solution to github. Add some acceptance tests so that we can check if a different solution is a valid refactoring. Then I’m happy to look into it if I have the time.

Cheers,
Thorben