Understanding History event types

We have implemented a custom history handler to send history events to an AWS SQS queue. We are particularly interested in capturing events related to User Tasks. We are seeing that for some User Tasks the history generates a Start event followed immediately by an Update event. The Start event has a TaskId of null, while the Update event has a valid TaskId.

Why do some User Tasks create an Update event while some do not? We are definitely not updating anything externally, these task are simply getting started by the workflow coming to them. This would be so much easier if the Start event had the TaskId populated.

How does your custom handler implementation looks like?

Here’s the handler. It sends the events to an AWS sqs queue.

package com.firstam.workflow.handlers;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import com.firstam.workflow.data.Payload;
import com.firstam.workflow.util.CaseConverter;
import org.camunda.bpm.engine.impl.history.event.HistoricActivityInstanceEventEntity;
import org.camunda.bpm.engine.impl.history.event.HistoricIncidentEventEntity;
import org.camunda.bpm.engine.impl.history.event.HistoryEvent;
import org.camunda.bpm.engine.impl.history.handler.HistoryEventHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.UUID;

import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY;
import static com.fasterxml.jackson.annotation.JsonCreator.Mode.PROPERTIES;
import static com.fasterxml.jackson.annotation.PropertyAccessor.FIELD;

public class CustomHistoryEventHandler implements HistoryEventHandler {

private static final Logger LOGGER = LoggerFactory.getLogger(com.firstam.workflow.handlers.CustomHistoryEventHandler.class);

private static final CustomHistoryEventHandler INSTANCE = new CustomHistoryEventHandler();

private ObjectMapper mapper;

private MessageHandler messageHandler = new AWSQueueMessageHandler();

public static CustomHistoryEventHandler getInstance() {
    return INSTANCE;
}

public CustomHistoryEventHandler() {
    // Configure the JSON Mapper PrettyPrint, Date Format and Property Visibility
    //    Date will look like this:  2020-07-09 22:43:26,538
    mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);

    DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS");
    mapper.setDateFormat(dateFormat);
    mapper.registerModule(new ParameterNamesModule(PROPERTIES));
    mapper.setVisibility(FIELD, ANY);
}

public CustomHistoryEventHandler(MessageHandler messageHandler) {
    this();
    this.messageHandler = messageHandler;

}

@Override
public void handleEvent(HistoryEvent historyEvent) {
    try {
        if (historyEvent instanceof HistoricIncidentEventEntity
                || historyEvent instanceof HistoricActivityInstanceEventEntity) {
            String event = "";
            if (historyEvent instanceof HistoricActivityInstanceEventEntity) {
                event = historyEvent.getEventType() + "_" + CaseConverter.convert2SnakeCase(((HistoricActivityInstanceEventEntity) historyEvent).getActivityId());
            } else {
                event = historyEvent.getEventType() + "_" + "Incident";
            }

            Payload payload = new Payload(UUID.randomUUID().toString(),"CAMUNDA", event, historyEvent);

            String payloadJson ="";
            try {
                payloadJson = mapper.writeValueAsString(payload);
                messageHandler.sendMessage(payloadJson);
            } catch (JsonProcessingException e) {
                LOGGER.error("Exception converting {}:{} to Json format", historyEvent.getExecutionId(), historyEvent.getEventType(), e);
            }
        }
    } catch (NullPointerException npe) {
        LOGGER.error("**Event Handler Null Pointer Exception** ", npe);
    }
    catch (Exception e){
        LOGGER.error("History Event Handler Exception on event {}", historyEvent.getExecutionId(), e);
    }
}

@Override
public void handleEvents(List<HistoryEvent> historyEvents) {
    for (HistoryEvent historyEvent : historyEvents) {
        handleEvent(historyEvent);
    }
}

public MessageHandler getMessageHandler() {
    return messageHandler;
}

public void setMessageHandler(MessageHandler messageHandler) {
    this.messageHandler = messageHandler;
}

}

Hi @Bill_Powell,

If you are interested in capturing events related to User Tasks then I guess you should check against HistoricTaskInstanceEventEntity type

historyEvent instanceof HistoricTaskInstanceEventEntity

2 Likes

History events are just DB transactions.

See https://github.com/StephenOTT/forms-manager/blob/master/src/main/kotlin/formsmanager/camunda/hazelcast/history/HazelcastHistoryEventHandler.kt for a hazelcast equivalent of the DB provider. You should be able to take the above code and covert to a SQS.

If you are looking to collect all events then they are basically just DB creations, updates and deletes sent as ordered events

2 Likes

Thanks Stephen and Hassang. It seems obvious now that you pointed it out.