All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.camunda.exporter.handlers.IncidentHandler Maven / Gradle / Ivy

There is a newer version: 8.7.0-alpha2
Show newest version
/*
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
 * one or more contributor license agreements. See the NOTICE file distributed
 * with this work for additional information regarding copyright ownership.
 * Licensed under the Camunda License 1.0. You may not use this file
 * except in compliance with the Camunda License 1.0.
 */
package io.camunda.exporter.handlers;

import static io.camunda.webapps.schema.descriptors.operate.template.IncidentTemplate.*;

import io.camunda.exporter.cache.ExporterEntityCache;
import io.camunda.exporter.cache.process.CachedProcessEntity;
import io.camunda.exporter.store.BatchRequest;
import io.camunda.exporter.utils.ExporterUtil;
import io.camunda.exporter.utils.ProcessCacheUtil;
import io.camunda.webapps.operate.TreePath;
import io.camunda.webapps.schema.entities.operate.ErrorType;
import io.camunda.webapps.schema.entities.operate.IncidentEntity;
import io.camunda.webapps.schema.entities.operate.IncidentState;
import io.camunda.zeebe.protocol.record.Record;
import io.camunda.zeebe.protocol.record.ValueType;
import io.camunda.zeebe.protocol.record.intent.IncidentIntent;
import io.camunda.zeebe.protocol.record.value.IncidentRecordValue;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class IncidentHandler implements ExportHandler {

  private static final Logger LOGGER = LoggerFactory.getLogger(IncidentHandler.class);
  private final Map> recordsMap = new HashMap<>();
  private final String indexName;
  private final boolean concurrencyMode;
  private final ExporterEntityCache processCache;

  public IncidentHandler(
      final String indexName,
      final boolean concurrencyMode,
      final ExporterEntityCache processCache) {
    this.indexName = indexName;
    this.concurrencyMode = concurrencyMode;
    this.processCache = processCache;
  }

  @Override
  public ValueType getHandledValueType() {
    return ValueType.INCIDENT;
  }

  @Override
  public Class getEntityType() {
    return IncidentEntity.class;
  }

  @Override
  public boolean handlesRecord(final Record record) {
    final var intent = record.getIntent();
    return !intent.equals(IncidentIntent.RESOLVED);
  }

  @Override
  public List generateIds(final Record record) {
    return List.of(ExporterUtil.toStringOrNull(record.getKey()));
  }

  @Override
  public IncidentEntity createNewEntity(final String id) {
    return new IncidentEntity().setId(id);
  }

  @Override
  public void updateEntity(final Record record, final IncidentEntity entity) {
    final IncidentRecordValue recordValue = record.getValue();
    final long incidentKey = record.getKey();
    entity
        .setId(ExporterUtil.toStringOrNull(incidentKey))
        .setKey(incidentKey)
        .setPartitionId(record.getPartitionId())
        .setPosition(record.getPosition());
    if (recordValue.getJobKey() > 0) {
      entity.setJobKey(recordValue.getJobKey());
    }
    if (recordValue.getProcessInstanceKey() > 0) {
      entity.setProcessInstanceKey(recordValue.getProcessInstanceKey());
    }
    if (recordValue.getProcessDefinitionKey() > 0) {
      entity.setProcessDefinitionKey(recordValue.getProcessDefinitionKey());
    }
    entity.setBpmnProcessId(recordValue.getBpmnProcessId());
    final String errorMessage = ExporterUtil.trimWhitespace(recordValue.getErrorMessage());
    entity
        .setErrorMessage(errorMessage)
        .setErrorType(
            ErrorType.fromZeebeErrorType(
                recordValue.getErrorType() == null ? null : recordValue.getErrorType().name()))
        .setFlowNodeId(recordValue.getElementId());
    if (recordValue.getElementInstanceKey() > 0) {
      entity.setFlowNodeInstanceKey(recordValue.getElementInstanceKey());
    }
    entity
        .setState(IncidentState.PENDING)
        .setCreationTime(
            OffsetDateTime.ofInstant(Instant.ofEpochMilli(record.getTimestamp()), ZoneOffset.UTC))
        .setTenantId(ExporterUtil.tenantOrDefault(recordValue.getTenantId()));

    entity.setTreePath(buildTreePath(record));

    recordsMap.put(entity.getId(), record);
  }

  @Override
  public void flush(final IncidentEntity entity, final BatchRequest batchRequest) {
    final String id = entity.getId();
    final Record record = recordsMap.get(id);
    final String intentStr = (record == null) ? null : record.getIntent().name();
    if (intentStr == null) {
      LOGGER.warn("Intent is null for incident: id {}", id);
    }
    final Map updateFields = getUpdateFieldsMapByIntent(intentStr, entity);
    updateFields.put(POSITION, entity.getPosition());
    if (concurrencyMode) {
      batchRequest.upsertWithScript(
          indexName, String.valueOf(entity.getKey()), entity, getScript(), updateFields);
    } else {
      batchRequest.upsert(indexName, String.valueOf(entity.getKey()), entity, updateFields);
    }
  }

  @Override
  public String getIndexName() {
    return indexName;
  }

  private String buildTreePath(final Record record) {

    final IncidentRecordValue value = record.getValue();
    final List> elementInstancePath = value.getElementInstancePath();
    final List callingElementPath = value.getCallingElementPath();
    final List processDefinitionPath = value.getProcessDefinitionPath();

    final Long processInstanceKey = Long.valueOf(value.getProcessInstanceKey());

    // example of how the tree path is built when current instance is on the third level of calling
    // hierarchy:
    // PI_/FN_/FNI_/
    // PI_/FN_/FNI_/
    // PI_/FN_/FNI_
    final TreePath treePath = new TreePath();
    for (int i = 0; i < elementInstancePath.size(); i++) {
      final List keysWithinOnePI = elementInstancePath.get(i);
      treePath.appendProcessInstance(keysWithinOnePI.get(0));
      if (keysWithinOnePI.get(0).equals(processInstanceKey)) {
        // when we reached current processInstanceKey, we build the last peace of tree path
        treePath
            .appendFlowNode(value.getElementId())
            .appendFlowNodeInstance(value.getElementInstanceKey());
        break;
      } else {
        final var callActivityId =
            ProcessCacheUtil.getCallActivityId(
                processCache, processDefinitionPath.get(i), callingElementPath.get(i));
        if (callActivityId.isPresent()) {
          treePath.appendFlowNode(callActivityId.get());
        } else {
          LOGGER.warn(
              "No process found in cache. TreePath won't contain proper callActivityId. processInstanceKey: {}, processDefinitionKey: {}, incidentKey: {}",
              processInstanceKey,
              processDefinitionPath.get(i),
              record.getKey());
          treePath.appendFlowNode(String.valueOf(callingElementPath.get(i)));
        }
        treePath.appendFlowNodeInstance(String.valueOf(keysWithinOnePI.get(1)));
      }
    }

    return treePath.toString();
  }

  private static Map getUpdateFieldsMapByIntent(
      final String intent, final IncidentEntity incidentEntity) {
    final Map updateFields = new HashMap<>();
    if (Objects.equals(intent, IncidentIntent.MIGRATED.name())) {
      updateFields.put(BPMN_PROCESS_ID, incidentEntity.getBpmnProcessId());
      updateFields.put(PROCESS_DEFINITION_KEY, incidentEntity.getProcessDefinitionKey());
      updateFields.put(FLOW_NODE_ID, incidentEntity.getFlowNodeId());
    }
    return updateFields;
  }

  private static String getScript() {
    return String.format(
        "if (ctx._source.%s == null || ctx._source.%s < params.%s) { "
            + "ctx._source.%s = params.%s; " // position
            + "if (params.%s != null) {"
            + "   ctx._source.%s = params.%s; " // PROCESS_DEFINITION_KEY
            + "   ctx._source.%s = params.%s; " // BPMN_PROCESS_ID
            + "   ctx._source.%s = params.%s; " // FLOW_NODE_ID
            + "}"
            + "}",
        POSITION,
        POSITION,
        POSITION,
        POSITION,
        POSITION,
        PROCESS_DEFINITION_KEY,
        PROCESS_DEFINITION_KEY,
        PROCESS_DEFINITION_KEY,
        BPMN_PROCESS_ID,
        BPMN_PROCESS_ID,
        FLOW_NODE_ID,
        FLOW_NODE_ID);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy