Camunda StartProcess/withResult

There is a task to call Camunda processes in Api Rest style. In zeebe there is instrument startprocessWithresult. I tried to reproduce it with following node.js code:

    axios.post( Camundaurl + '/process-definition/key/' + ProcessKey + '/start', 
       {variables: vars, businessKey: sequenceId}, 
       {httpAgent: httpagent, httpsAgent: httpsagent, timeout: timeout, auth: authCamunda})
    .then(response => {
       data = response.data;
       status = "ACTIVE";
       result = {processKey: ProcessKey, processId: data.id, status: status, data: data, id: sequenceId };

       logger.log({level: 'info', message: {type: "RESPONSE", cluster: 'Camunda', method: 'processStart',
         url: '/process-definition/key/' + ProcessKey + '/start', result: result, responsetime: Date.now() - beginreq, sequenceId: sequenceId}});
  
       if (withresult == false || Date.now() - begintime > timeout) {
         callback (res, {result: result});
         return;
       }
       var delay = 16;   
       setTimeout(waitforresult, delay, res, result, delay, begintime + timeout, begintime, callback);
    })

async function waitforresult (res, result, delay, endtime, begintime, callback) {
  try {
    const response = await axios.get(Camundaurl + '/history/process-instance/' + result.processId, { httpAgent: httpagent, httpsAgent: httpsagent, timeout: 20000, auth: authCamunda }); 
    if (response.data.endTime || response.data.state !== 'ACTIVE') {
      result.status = response.data.state;

      const response2 = await axios.get(Camundaurl + '/history/variable-instance?processInstanceId=' + result.processId, { httpAgent: httpagent, httpsAgent: httpsagent, timeout: 20000, auth: authCamunda }); 
      const vars = response2.data;
      var obj = {};
      for ( var i = 0; i < vars.length; i++) {
        obj[vars[i].name] = vars[i].value;
      }
      result.data.variables = obj;

      logger.log({level: 'info', message: {type: "RESPONSE", cluster: 'Camunda', method: 'processStart',
        url: '/process-definition/key/' + result.processKey + '/start', result: logresult, responsetime: Date.now() - begintime, sequenceId: result.id}});
      callback (res, {result: result});
      return;
    }
    else {
      if (delay > 250) { 
        delay = delay * 1.2; 
      }
      else {
        delay = delay * 2;
      }
      if (delay > 1000) {delay = 1000};
      if (Date.now () > endtime) {
        callback (res, {result: result});
        return;
      }
      setTimeout(waitforresult, delay, res, result, delay, endtime, begintime, callback);
    }
  }
  catch (error) {
    console.log(error);
    logger.log({level: 'error', message: {type: "RESPONSE", cluster: 'Camunda', method: 'processStart',
      url: '/process-definition/key/' + result.processKey + '/start', error: error, responsetime: Date.now() - begintime, sequenceId: result.id}});

    callback (res, {error: {error: error, id: result.id}});
  }
}

I used method to check history data with some timeout. But I want to find way do it async. Is there some event in camunda Engine, to subscribe. I checked ExecutorListener, but it works while process still running, and I dont want to check all EndEvents in every Camunda Process to find out that state is reached.
Is there other way?

Hi @MaximMonin,

Execution listeners are generally the right direction with Camunda BPM as there is no built-in notification API or similar. A custom history event handler can also be used, but is very similar anyway. Here are a couple of ideas how you can avoid some issues when using the listener:

I hope that helps.

Cheers,
Thorben

Ended up with jar plugin

package camunda.plugin.processend;

import org.camunda.bpm.engine.impl.history.event.*;
import org.camunda.bpm.engine.impl.history.handler.HistoryEventHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Base64;
import java.util.ArrayList;
import java.util.List;

public class ProcessEndEventHandler implements HistoryEventHandler {

  private static final Logger log = LoggerFactory.getLogger(ProcessEndEventHandler.class);

  public ProcessEndEventHandler() {
  }

  @Override
  public void handleEvent(HistoryEvent historyEvent) {

    if (historyEvent instanceof HistoricProcessInstanceEventEntity) {
      HistoricProcessInstanceEventEntity processInstanceEventEntity =
        (HistoricProcessInstanceEventEntity) historyEvent;

      /* Process_end for top level process */
      if (historyEvent.getEventType().equals(HistoryEventTypes.PROCESS_INSTANCE_END.getEventName()) &&
          processInstanceEventEntity.getSuperProcessInstanceId() == null) {

        String processId = processInstanceEventEntity.getProcessInstanceId();
        String state = processInstanceEventEntity.getState();
        // log.info("Received <" + historyEvent.getEventType() + "> event for <" + processInstanceEventEntity.toString() + ">");
        // log.info("Process state: <" + state + ">");

        String gateUrl = System.getenv("GATE_SERVER");
        String username = "rpc";
        String password = System.getenv("GATE_PASSWORD");
        String auth = username + ":" + password;                                      
        String url = "https://" + gateUrl + "/api/camunda/process/" + processId + "/ends?state=" + state;

        if (! callApi (url, auth)) {
          /* repeat one more time */
          callApi (url, auth);
        }
      }
    } 
  }
  private boolean callApi (String url, String auth) {
    String base64login = new String(Base64.getEncoder().encodeToString(auth.getBytes()));

    try {
       SSLHelper.getConnection(url)
                .header("Accept", "application/json")
                .header("Content-Type", "application/json")
                .header("Authorization", "Basic " + base64login)
                // .requestBody(body)
                .timeout(5000)
                .ignoreContentType(true) 
                .post();
       return true;
    }
    catch (Exception e) {
      log.info (e.toString());
      return false;
    }
  }

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