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

io.camunda.operate.store.elasticsearch.ElasticsearchProcessStore Maven / Gradle / Ivy

/*
 * 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.operate.store.elasticsearch;

import static io.camunda.operate.schema.indices.ProcessIndex.BPMN_XML;
import static io.camunda.operate.schema.templates.FlowNodeInstanceTemplate.TREE_PATH;
import static io.camunda.operate.schema.templates.ListViewTemplate.BPMN_PROCESS_ID;
import static io.camunda.operate.schema.templates.ListViewTemplate.ID;
import static io.camunda.operate.schema.templates.ListViewTemplate.INCIDENT;
import static io.camunda.operate.schema.templates.ListViewTemplate.JOIN_RELATION;
import static io.camunda.operate.schema.templates.ListViewTemplate.KEY;
import static io.camunda.operate.schema.templates.ListViewTemplate.PARENT_PROCESS_INSTANCE_KEY;
import static io.camunda.operate.schema.templates.ListViewTemplate.PROCESS_INSTANCE_JOIN_RELATION;
import static io.camunda.operate.schema.templates.ListViewTemplate.PROCESS_KEY;
import static io.camunda.operate.schema.templates.ListViewTemplate.PROCESS_NAME;
import static io.camunda.operate.schema.templates.ListViewTemplate.STATE;
import static io.camunda.operate.util.ElasticsearchUtil.QueryType.ALL;
import static io.camunda.operate.util.ElasticsearchUtil.QueryType.ONLY_RUNTIME;
import static io.camunda.operate.util.ElasticsearchUtil.UPDATE_RETRY_COUNT;
import static io.camunda.operate.util.ElasticsearchUtil.createSearchRequest;
import static io.camunda.operate.util.ElasticsearchUtil.joinWithAnd;
import static io.camunda.operate.util.ElasticsearchUtil.scrollWith;
import static org.elasticsearch.index.query.QueryBuilders.constantScoreQuery;
import static org.elasticsearch.index.query.QueryBuilders.idsQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
import static org.elasticsearch.search.aggregations.AggregationBuilders.cardinality;
import static org.elasticsearch.search.aggregations.AggregationBuilders.terms;
import static org.elasticsearch.search.aggregations.AggregationBuilders.topHits;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.camunda.operate.conditions.ElasticsearchCondition;
import io.camunda.operate.entities.ProcessEntity;
import io.camunda.operate.entities.listview.ProcessInstanceForListViewEntity;
import io.camunda.operate.entities.listview.ProcessInstanceState;
import io.camunda.operate.exceptions.OperateRuntimeException;
import io.camunda.operate.property.OperateProperties;
import io.camunda.operate.schema.indices.ProcessIndex;
import io.camunda.operate.schema.templates.ListViewTemplate;
import io.camunda.operate.schema.templates.OperationTemplate;
import io.camunda.operate.schema.templates.ProcessInstanceDependant;
import io.camunda.operate.schema.templates.TemplateDescriptor;
import io.camunda.operate.store.NotFoundException;
import io.camunda.operate.store.ProcessStore;
import io.camunda.operate.tenant.TenantAwareElasticsearchClient;
import io.camunda.operate.util.ElasticsearchUtil;
import io.camunda.operate.util.TreePath;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.IdsQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.index.query.TermsQueryBuilder;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.SingleBucketAggregation;
import org.elasticsearch.search.aggregations.bucket.filter.FilterAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.metrics.Cardinality;
import org.elasticsearch.search.aggregations.metrics.TopHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Conditional;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;

@Conditional(ElasticsearchCondition.class)
@Component
public class ElasticsearchProcessStore implements ProcessStore {
  public static final FilterAggregationBuilder INCIDENTS_AGGREGATION =
      AggregationBuilders.filter(
          "incidents",
          joinWithAnd(
              termQuery(INCIDENT, true), termQuery(JOIN_RELATION, PROCESS_INSTANCE_JOIN_RELATION)));
  public static final FilterAggregationBuilder RUNNING_AGGREGATION =
      AggregationBuilders.filter(
          "running", termQuery(ListViewTemplate.STATE, ProcessInstanceState.ACTIVE));
  private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchProcessStore.class);
  private static final String DISTINCT_FIELD_COUNTS = "distinctFieldCounts";
  private final ProcessIndex processIndex;

  private final ListViewTemplate listViewTemplate;

  private final List processInstanceDependantTemplates;

  private final ObjectMapper objectMapper;

  private final RestHighLevelClient esClient;

  private final TenantAwareElasticsearchClient tenantAwareClient;

  private final OperateProperties operateProperties;

  public ElasticsearchProcessStore(
      final ProcessIndex processIndex,
      final ListViewTemplate listViewTemplate,
      final List processInstanceDependantTemplates,
      @Qualifier("operateObjectMapper") final ObjectMapper objectMapper,
      final OperateProperties operateProperties,
      final RestHighLevelClient esClient,
      final TenantAwareElasticsearchClient tenantAwareClient) {
    this.processIndex = processIndex;
    this.listViewTemplate = listViewTemplate;
    this.processInstanceDependantTemplates = processInstanceDependantTemplates;
    this.objectMapper = objectMapper;
    this.operateProperties = operateProperties;
    this.esClient = esClient;
    this.tenantAwareClient = tenantAwareClient;
  }

  @Override
  public Optional getDistinctCountFor(final String fieldName) {
    final String indexAlias = processIndex.getAlias();
    LOGGER.debug("Called distinct count for field {} in index alias {}.", fieldName, indexAlias);
    final SearchRequest searchRequest =
        new SearchRequest(indexAlias)
            .source(
                new SearchSourceBuilder()
                    .query(QueryBuilders.matchAllQuery())
                    .size(0)
                    .aggregation(
                        cardinality(DISTINCT_FIELD_COUNTS)
                            .precisionThreshold(1_000)
                            .field(fieldName)));
    try {
      final SearchResponse searchResponse = esClient.search(searchRequest, RequestOptions.DEFAULT);
      final Cardinality distinctFieldCounts =
          searchResponse.getAggregations().get(DISTINCT_FIELD_COUNTS);
      return Optional.of(distinctFieldCounts.getValue());
    } catch (final Exception e) {
      LOGGER.error(
          String.format(
              "Error in distinct count for field %s in index alias %s.", fieldName, indexAlias),
          e);
      return Optional.empty();
    }
  }

  @Override
  public void refreshIndices(final String... indices) {
    if (indices == null || indices.length == 0) {
      throw new OperateRuntimeException("Refresh indices needs at least one index to refresh.");
    }
    try {
      esClient.indices().refresh(new RefreshRequest(indices), RequestOptions.DEFAULT);
    } catch (final IOException ex) {
      throw new OperateRuntimeException("Failed to refresh indices " + Arrays.asList(indices), ex);
    }
  }

  @Override
  public ProcessEntity getProcessByKey(final Long processDefinitionKey) {
    final SearchRequest searchRequest =
        new SearchRequest(processIndex.getAlias())
            .source(
                new SearchSourceBuilder()
                    .query(QueryBuilders.termQuery(ProcessIndex.KEY, processDefinitionKey)));

    try {
      final SearchResponse response = tenantAwareClient.search(searchRequest);
      if (response.getHits().getTotalHits().value == 1) {
        return fromSearchHit(response.getHits().getHits()[0].getSourceAsString());
      } else if (response.getHits().getTotalHits().value > 1) {
        throw new NotFoundException(
            String.format("Could not find unique process with key '%s'.", processDefinitionKey));
      } else {
        throw new NotFoundException(
            String.format("Could not find process with key '%s'.", processDefinitionKey));
      }
    } catch (final IOException e) {
      final String message =
          String.format("Exception occurred, while obtaining the process: %s", e.getMessage());
      LOGGER.error(message, e);
      throw new OperateRuntimeException(message, e);
    }
  }

  @Override
  public String getDiagramByKey(final Long processDefinitionKey) {
    final IdsQueryBuilder q = idsQuery().addIds(processDefinitionKey.toString());

    final SearchRequest searchRequest =
        new SearchRequest(processIndex.getAlias())
            .source(new SearchSourceBuilder().query(q).fetchSource(BPMN_XML, null));

    try {
      final SearchResponse response = tenantAwareClient.search(searchRequest);

      if (response.getHits().getTotalHits().value == 1) {
        final Map result = response.getHits().getHits()[0].getSourceAsMap();
        return (String) result.get(BPMN_XML);
      } else if (response.getHits().getTotalHits().value > 1) {
        throw new NotFoundException(
            String.format("Could not find unique process with id '%s'.", processDefinitionKey));
      } else {
        throw new NotFoundException(
            String.format("Could not find process with id '%s'.", processDefinitionKey));
      }
    } catch (final IOException e) {
      final String message =
          String.format(
              "Exception occurred, while obtaining the process diagram: %s", e.getMessage());
      LOGGER.error(message, e);
      throw new OperateRuntimeException(message, e);
    }
  }

  @Override
  public Map> getProcessesGrouped(
      final String tenantId, @Nullable final Set allowedBPMNProcessIds) {
    final String tenantsGroupsAggName = "group_by_tenantId";
    final String groupsAggName = "group_by_bpmnProcessId";
    final String processesAggName = "processes";

    final AggregationBuilder agg =
        terms(tenantsGroupsAggName)
            .field(ProcessIndex.TENANT_ID)
            .size(ElasticsearchUtil.TERMS_AGG_SIZE)
            .subAggregation(
                terms(groupsAggName)
                    .field(ProcessIndex.BPMN_PROCESS_ID)
                    .size(ElasticsearchUtil.TERMS_AGG_SIZE)
                    .subAggregation(
                        topHits(processesAggName)
                            .fetchSource(
                                new String[] {
                                  ProcessIndex.ID,
                                  ProcessIndex.NAME,
                                  ProcessIndex.VERSION,
                                  ProcessIndex.VERSION_TAG,
                                  ProcessIndex.BPMN_PROCESS_ID,
                                  ProcessIndex.TENANT_ID
                                },
                                null)
                            .size(ElasticsearchUtil.TOPHITS_AGG_SIZE)
                            .sort(ProcessIndex.VERSION, SortOrder.DESC)));

    final SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().aggregation(agg).size(0);
    sourceBuilder.query(buildQuery(tenantId, allowedBPMNProcessIds));
    final SearchRequest searchRequest =
        new SearchRequest(processIndex.getAlias()).source(sourceBuilder);

    try {
      final SearchResponse searchResponse = tenantAwareClient.search(searchRequest);
      final Terms groups = searchResponse.getAggregations().get(tenantsGroupsAggName);
      final Map> result = new HashMap<>();

      groups.getBuckets().stream()
          .forEach(
              b -> {
                final String groupTenantId = b.getKeyAsString();
                final Terms processGroups = b.getAggregations().get(groupsAggName);

                processGroups.getBuckets().stream()
                    .forEach(
                        tenantB -> {
                          final String bpmnProcessId = tenantB.getKeyAsString();
                          final ProcessKey groupKey = new ProcessKey(bpmnProcessId, groupTenantId);
                          result.put(groupKey, new ArrayList<>());

                          final TopHits processes = tenantB.getAggregations().get(processesAggName);
                          final SearchHit[] hits = processes.getHits().getHits();
                          for (final SearchHit searchHit : hits) {
                            final ProcessEntity processEntity =
                                fromSearchHit(searchHit.getSourceAsString());
                            result.get(groupKey).add(processEntity);
                          }
                        });
              });

      return result;
    } catch (final IOException e) {
      final String message =
          String.format(
              "Exception occurred, while obtaining grouped processes: %s", e.getMessage());
      LOGGER.error(message, e);
      throw new OperateRuntimeException(message, e);
    }
  }

  @Override
  public Map getProcessesIdsToProcessesWithFields(
      @Nullable final Set allowedBPMNIds, final int maxSize, final String... fields) {
    final Map map = new HashMap<>();

    final SearchSourceBuilder sourceBuilder =
        new SearchSourceBuilder().size(maxSize).fetchSource(fields, null);
    if (allowedBPMNIds == null) {
      sourceBuilder.query(QueryBuilders.matchAllQuery());
    } else {
      sourceBuilder.query(
          QueryBuilders.termsQuery(ListViewTemplate.BPMN_PROCESS_ID, allowedBPMNIds));
    }
    final SearchRequest searchRequest =
        new SearchRequest(processIndex.getAlias()).source(sourceBuilder);

    try {
      final SearchResponse response = tenantAwareClient.search(searchRequest);
      response
          .getHits()
          .forEach(
              hit -> {
                final ProcessEntity entity = fromSearchHit(hit.getSourceAsString());
                map.put(entity.getKey(), entity);
              });
      return map;
    } catch (final IOException e) {
      final String message =
          String.format("Exception occurred, while obtaining processes: %s", e.getMessage());
      LOGGER.error(message, e);
      throw new OperateRuntimeException(message, e);
    }
  }

  @Override
  public long deleteProcessDefinitionsByKeys(final Long... processDefinitionKeys) {
    if (processDefinitionKeys == null || processDefinitionKeys.length == 0) {
      return 0;
    }
    final DeleteByQueryRequest query =
        new DeleteByQueryRequest(processIndex.getAlias())
            .setQuery(QueryBuilders.termsQuery(ProcessIndex.KEY, processDefinitionKeys));
    try {
      final BulkByScrollResponse response = esClient.deleteByQuery(query, RequestOptions.DEFAULT);
      return response.getDeleted();
    } catch (final IOException ex) {
      throw new OperateRuntimeException("Failed to delete process definitions by keys", ex);
    }
  }

  @Override
  public ProcessInstanceForListViewEntity getProcessInstanceListViewByKey(
      final Long processInstanceKey) {
    try {
      final QueryBuilder query =
          joinWithAnd(
              idsQuery().addIds(String.valueOf(processInstanceKey)),
              termQuery(ListViewTemplate.PROCESS_INSTANCE_KEY, processInstanceKey));

      final SearchRequest request =
          ElasticsearchUtil.createSearchRequest(listViewTemplate, ALL)
              .source(new SearchSourceBuilder().query(constantScoreQuery(query)));

      final SearchResponse response;

      response = tenantAwareClient.search(request);
      final SearchHits searchHits = response.getHits();
      if (searchHits.getTotalHits().value == 1 && searchHits.getHits().length == 1) {
        return ElasticsearchUtil.fromSearchHit(
            searchHits.getAt(0).getSourceAsString(),
            objectMapper,
            ProcessInstanceForListViewEntity.class);
      } else if (response.getHits().getTotalHits().value > 1) {
        throw new NotFoundException(
            String.format(
                "Could not find unique process instance with id '%s'.", processInstanceKey));
      } else {
        throw new NotFoundException(
            (String.format("Could not find process instance with id '%s'.", processInstanceKey)));
      }
    } catch (final IOException e) {
      throw new OperateRuntimeException(e);
    }
  }

  @Override
  public Map getCoreStatistics(@Nullable final Set allowedBPMNIds) {
    final SearchSourceBuilder sourceBuilder =
        new SearchSourceBuilder()
            .size(0)
            .aggregation(INCIDENTS_AGGREGATION)
            .aggregation(RUNNING_AGGREGATION);
    if (allowedBPMNIds == null) {
      sourceBuilder.query(QueryBuilders.matchAllQuery());
    } else {
      sourceBuilder.query(
          QueryBuilders.termsQuery(ListViewTemplate.BPMN_PROCESS_ID, allowedBPMNIds));
    }
    final SearchRequest searchRequest =
        ElasticsearchUtil.createSearchRequest(listViewTemplate, ONLY_RUNTIME).source(sourceBuilder);

    try {
      final SearchResponse response = tenantAwareClient.search(searchRequest);
      final Aggregations aggregations = response.getAggregations();
      final long runningCount =
          ((SingleBucketAggregation) aggregations.get("running")).getDocCount();
      final long incidentCount =
          ((SingleBucketAggregation) aggregations.get("incidents")).getDocCount();
      return Map.of("running", runningCount, "incidents", incidentCount);
    } catch (final IOException e) {
      final String message =
          String.format(
              "Exception occurred, while obtaining process instance core statistics: %s",
              e.getMessage());
      LOGGER.error(message, e);
      throw new OperateRuntimeException(message, e);
    }
  }

  @Override
  public String getProcessInstanceTreePathById(final String processInstanceId) {
    final QueryBuilder query =
        joinWithAnd(
            termQuery(JOIN_RELATION, PROCESS_INSTANCE_JOIN_RELATION),
            termQuery(KEY, processInstanceId));
    final SearchRequest request =
        ElasticsearchUtil.createSearchRequest(listViewTemplate)
            .source(new SearchSourceBuilder().query(query).fetchSource(TREE_PATH, null));
    try {
      final SearchResponse response = tenantAwareClient.search(request);
      if (response.getHits().getTotalHits().value > 0) {
        return (String) response.getHits().getAt(0).getSourceAsMap().get(TREE_PATH);
      } else {
        throw new NotFoundException(
            String.format("Process instance not found: %s", processInstanceId));
      }
    } catch (final IOException e) {
      final String message =
          String.format(
              "Exception occurred, while obtaining tree path for process instance: %s",
              e.getMessage());
      throw new OperateRuntimeException(message, e);
    }
  }

  @Override
  public List> createCallHierarchyFor(
      final List processInstanceIds, final String currentProcessInstanceId) {
    final List> callHierarchy = new ArrayList<>();

    final List processInstanceIdsWithoutCurrentProcess =
        new ArrayList<>(processInstanceIds);
    // remove id of current process instance
    processInstanceIdsWithoutCurrentProcess.remove(currentProcessInstanceId);

    final QueryBuilder query =
        joinWithAnd(
            termQuery(JOIN_RELATION, PROCESS_INSTANCE_JOIN_RELATION),
            termsQuery(ID, processInstanceIdsWithoutCurrentProcess));
    final SearchRequest request =
        ElasticsearchUtil.createSearchRequest(listViewTemplate)
            .source(
                new SearchSourceBuilder()
                    .query(query)
                    .fetchSource(
                        new String[] {ID, PROCESS_KEY, PROCESS_NAME, BPMN_PROCESS_ID}, null));
    try {
      tenantAwareClient.search(
          request,
          () -> {
            scrollWith(
                request,
                esClient,
                searchHits -> {
                  Arrays.stream(searchHits.getHits())
                      .forEach(
                          sh -> {
                            final Map source = sh.getSourceAsMap();
                            callHierarchy.add(
                                Map.of(
                                    "instanceId", String.valueOf(source.get(ID)),
                                    "processDefinitionId", String.valueOf(source.get(PROCESS_KEY)),
                                    "processDefinitionName",
                                        String.valueOf(
                                            source.getOrDefault(
                                                PROCESS_NAME, source.get(BPMN_PROCESS_ID)))));
                          });
                });
            return null;
          });
    } catch (final IOException e) {
      final String message =
          String.format(
              "Exception occurred, while obtaining process instance call hierarchy: %s",
              e.getMessage());
      throw new OperateRuntimeException(message, e);
    }
    return callHierarchy;
  }

  @Override
  public long deleteDocument(final String indexName, final String idField, final String id)
      throws IOException {
    final DeleteByQueryRequest query =
        new DeleteByQueryRequest(indexName).setQuery(QueryBuilders.termsQuery(idField, id));
    final BulkByScrollResponse response = esClient.deleteByQuery(query, RequestOptions.DEFAULT);
    LOGGER.debug("Delete document {} in {} response: {}", id, indexName, response.getStatus());
    return response.getDeleted();
  }

  @Override
  public void deleteProcessInstanceFromTreePath(final String processInstanceKey) {
    final BulkRequest bulkRequest = new BulkRequest();
    // select process instance - get tree path
    final String treePath = getProcessInstanceTreePathById(processInstanceKey);

    // select all process instances with term treePath == tree path
    // update all this process instances to remove corresponding part of tree path
    // 2 cases:
    // - middle level: we remove /PI_key/FN_name/FNI_key from the middle
    // - end level: we remove /PI_key from the end

    final QueryBuilder query =
        ((BoolQueryBuilder)
                joinWithAnd(
                    termQuery(JOIN_RELATION, PROCESS_INSTANCE_JOIN_RELATION),
                    termQuery(TREE_PATH, treePath)))
            .mustNot(termQuery(KEY, processInstanceKey));
    final SearchRequest request =
        ElasticsearchUtil.createSearchRequest(listViewTemplate)
            .source(new SearchSourceBuilder().query(query).fetchSource(TREE_PATH, null));
    try {
      tenantAwareClient.search(
          request,
          () -> {
            ElasticsearchUtil.scroll(
                request,
                hits -> {
                  Arrays.stream(hits.getHits())
                      .forEach(
                          sh -> {
                            final UpdateRequest updateRequest = new UpdateRequest();
                            final Map updateFields = new HashMap<>();
                            final String newTreePath =
                                new TreePath((String) sh.getSourceAsMap().get(TREE_PATH))
                                    .removeProcessInstance(processInstanceKey)
                                    .toString();
                            updateFields.put(TREE_PATH, newTreePath);
                            updateRequest
                                .index(sh.getIndex())
                                .id(sh.getId())
                                .doc(updateFields)
                                .retryOnConflict(UPDATE_RETRY_COUNT);
                            bulkRequest.add(updateRequest);
                          });
                },
                esClient);
            return null;
          });
      ElasticsearchUtil.processBulkRequest(
          esClient,
          bulkRequest,
          operateProperties.getElasticsearch().getBulkRequestMaxSizeInBytes());
    } catch (final Exception e) {
      throw new OperateRuntimeException(
          String.format(
              "Exception occurred when deleting process instance %s from tree path: %s",
              processInstanceKey, e.getMessage()));
    }
  }

  @Override
  public List getProcessInstancesByProcessAndStates(
      final long processDefinitionKey,
      final Set states,
      final int size,
      final String[] includeFields) {

    if (states == null || states.isEmpty()) {
      throw new OperateRuntimeException("Parameter 'states' is needed to search by states.");
    }

    final QueryBuilder query =
        joinWithAnd(
            QueryBuilders.termQuery(JOIN_RELATION, PROCESS_INSTANCE_JOIN_RELATION),
            QueryBuilders.termQuery(PROCESS_KEY, processDefinitionKey),
            QueryBuilders.termsQuery(
                STATE, states.stream().map(Enum::name).collect(Collectors.toList())));
    final SearchSourceBuilder source =
        new SearchSourceBuilder().size(size).query(query).fetchSource(includeFields, null);
    final SearchRequest searchRequest = createSearchRequest(listViewTemplate, ALL).source(source);

    try {
      final SearchResponse response = tenantAwareClient.search(searchRequest);
      return ElasticsearchUtil.mapSearchHits(
          response.getHits().getHits(), objectMapper, ProcessInstanceForListViewEntity.class);
    } catch (final IOException ex) {
      throw new OperateRuntimeException(
          String.format(
              "Failed to search process instances by processDefinitionKey [%s] and states [%s]",
              processDefinitionKey, states),
          ex);
    }
  }

  @Override
  public List getProcessInstancesByParentKeys(
      final Set parentProcessInstanceKeys, final int size, final String[] includeFields) {

    if (parentProcessInstanceKeys == null || parentProcessInstanceKeys.isEmpty()) {
      throw new OperateRuntimeException(
          "Parameter 'parentProcessInstanceKeys' is needed to search by parents.");
    }

    final QueryBuilder query =
        joinWithAnd(
            QueryBuilders.termQuery(JOIN_RELATION, PROCESS_INSTANCE_JOIN_RELATION),
            QueryBuilders.termsQuery(PARENT_PROCESS_INSTANCE_KEY, parentProcessInstanceKeys));
    final SearchSourceBuilder source =
        new SearchSourceBuilder().size(size).query(query).fetchSource(includeFields, null);
    final SearchRequest searchRequest = createSearchRequest(listViewTemplate, ALL).source(source);

    try {
      return tenantAwareClient.search(
          searchRequest,
          () ->
              ElasticsearchUtil.scroll(
                  searchRequest, ProcessInstanceForListViewEntity.class, objectMapper, esClient));
    } catch (final IOException ex) {
      throw new OperateRuntimeException(
          "Failed to search process instances by parentProcessInstanceKeys", ex);
    }
  }

  @Override
  public long deleteProcessInstancesAndDependants(final Set processInstanceKeys) {
    if (processInstanceKeys == null || processInstanceKeys.isEmpty()) {
      return 0;
    }

    long count = 0;
    final List processInstanceDependantsWithoutOperation =
        processInstanceDependantTemplates.stream()
            .filter(template -> !(template instanceof OperationTemplate))
            .toList();
    try {
      for (final ProcessInstanceDependant template : processInstanceDependantsWithoutOperation) {
        final String indexName = ((TemplateDescriptor) template).getAlias();
        final DeleteByQueryRequest query =
            new DeleteByQueryRequest(indexName)
                .setQuery(
                    QueryBuilders.termsQuery(
                        ProcessInstanceDependant.PROCESS_INSTANCE_KEY, processInstanceKeys));
        final BulkByScrollResponse response = esClient.deleteByQuery(query, RequestOptions.DEFAULT);
        count += response.getDeleted();
      }

      final DeleteByQueryRequest query =
          new DeleteByQueryRequest(listViewTemplate.getAlias())
              .setQuery(
                  QueryBuilders.termsQuery(
                      ListViewTemplate.PROCESS_INSTANCE_KEY, processInstanceKeys));
      final BulkByScrollResponse response = esClient.deleteByQuery(query, RequestOptions.DEFAULT);
      count += response.getDeleted();
    } catch (final IOException ex) {
      throw new OperateRuntimeException(
          "Failed to delete process instances and dependants by keys", ex);
    }

    return count;
  }

  private QueryBuilder buildQuery(final String tenantId, final Set allowedBPMNProcessIds) {
    final TermsQueryBuilder bpmnProcessIdQ =
        allowedBPMNProcessIds != null ? termsQuery(BPMN_PROCESS_ID, allowedBPMNProcessIds) : null;
    final TermQueryBuilder tenantIdQ =
        tenantId != null ? termQuery(ProcessIndex.TENANT_ID, tenantId) : null;
    QueryBuilder q = joinWithAnd(bpmnProcessIdQ, tenantIdQ);
    if (q == null) {
      q = matchAllQuery();
    }
    return q;
  }

  private ProcessEntity fromSearchHit(final String processString) {
    return ElasticsearchUtil.fromSearchHit(processString, objectMapper, ProcessEntity.class);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy