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

io.camunda.operate.webapp.opensearch.reader.OpensearchDecisionInstanceReader Maven / Gradle / Ivy

The 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.operate.webapp.opensearch.reader;

import static io.camunda.operate.entities.dmn.DecisionInstanceState.EVALUATED;
import static io.camunda.operate.entities.dmn.DecisionInstanceState.FAILED;
import static io.camunda.operate.schema.templates.DecisionInstanceTemplate.*;
import static io.camunda.operate.store.opensearch.dsl.QueryDSL.*;
import static io.camunda.operate.store.opensearch.dsl.RequestDSL.searchRequestBuilder;
import static io.camunda.operate.util.CollectionUtil.toSafeListOfStrings;
import static io.camunda.operate.webapp.rest.dto.dmn.list.DecisionInstanceListRequestDto.SORT_BY_PROCESS_INSTANCE_ID;
import static java.util.stream.Collectors.groupingBy;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.camunda.operate.conditions.OpensearchCondition;
import io.camunda.operate.entities.dmn.DecisionInstanceEntity;
import io.camunda.operate.entities.dmn.DecisionInstanceState;
import io.camunda.operate.property.OperateProperties;
import io.camunda.operate.schema.indices.DecisionIndex;
import io.camunda.operate.schema.templates.DecisionInstanceTemplate;
import io.camunda.operate.store.NotFoundException;
import io.camunda.operate.store.opensearch.client.sync.RichOpenSearchClient;
import io.camunda.operate.util.CollectionUtil;
import io.camunda.operate.util.Tuple;
import io.camunda.operate.webapp.reader.DecisionInstanceReader;
import io.camunda.operate.webapp.rest.dto.DtoCreator;
import io.camunda.operate.webapp.rest.dto.dmn.DRDDataEntryDto;
import io.camunda.operate.webapp.rest.dto.dmn.DecisionInstanceDto;
import io.camunda.operate.webapp.rest.dto.dmn.list.DecisionInstanceForListDto;
import io.camunda.operate.webapp.rest.dto.dmn.list.DecisionInstanceListQueryDto;
import io.camunda.operate.webapp.rest.dto.dmn.list.DecisionInstanceListRequestDto;
import io.camunda.operate.webapp.rest.dto.dmn.list.DecisionInstanceListResponseDto;
import io.camunda.operate.webapp.security.identity.IdentityPermission;
import io.camunda.operate.webapp.security.identity.PermissionsService;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.opensearch.client.json.JsonData;
import org.opensearch.client.opensearch._types.SortOptions;
import org.opensearch.client.opensearch._types.SortOrder;
import org.opensearch.client.opensearch._types.query_dsl.Query;
import org.opensearch.client.opensearch._types.query_dsl.RangeQuery;
import org.opensearch.client.opensearch.core.SearchRequest;
import org.opensearch.client.opensearch.core.search.Hit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Component;

@Conditional(OpensearchCondition.class)
@Component
public class OpensearchDecisionInstanceReader implements DecisionInstanceReader {

  private static final Logger LOGGER =
      LoggerFactory.getLogger(OpensearchDecisionInstanceReader.class);

  private final DecisionInstanceTemplate decisionInstanceTemplate;

  private final DateTimeFormatter dateTimeFormatter;

  private final ObjectMapper objectMapper;

  private final OperateProperties operateProperties;

  private final PermissionsService permissionsService;

  private final RichOpenSearchClient richOpenSearchClient;

  public OpensearchDecisionInstanceReader(
      final DecisionInstanceTemplate decisionInstanceTemplate,
      final DateTimeFormatter dateTimeFormatter,
      final ObjectMapper objectMapper,
      final OperateProperties operateProperties,
      @Autowired(required = false) final PermissionsService permissionsService,
      final RichOpenSearchClient richOpenSearchClient) {
    this.decisionInstanceTemplate = decisionInstanceTemplate;
    this.dateTimeFormatter = dateTimeFormatter;
    this.objectMapper = objectMapper;
    this.operateProperties = operateProperties;
    this.permissionsService = permissionsService;
    this.richOpenSearchClient = richOpenSearchClient;
  }

  @Override
  public DecisionInstanceDto getDecisionInstance(final String decisionInstanceId) {
    final var searchRequest =
        searchRequestBuilder(decisionInstanceTemplate)
            .query(
                withTenantCheck(
                    constantScore(
                        and(
                            ids(decisionInstanceId),
                            term(DecisionInstanceTemplate.ID, decisionInstanceId)))));

    try {
      return DtoCreator.create(
          richOpenSearchClient
              .doc()
              .searchUnique(searchRequest, DecisionInstanceEntity.class, decisionInstanceId),
          DecisionInstanceDto.class);
    } catch (final NotFoundException e) {
      throw new io.camunda.operate.webapp.rest.exception.NotFoundException(e.getMessage());
    }
  }

  @Override
  public DecisionInstanceListResponseDto queryDecisionInstances(
      final DecisionInstanceListRequestDto request) {
    final DecisionInstanceListResponseDto result = new DecisionInstanceListResponseDto();
    final List entities = queryDecisionInstancesEntities(request, result);
    result.setDecisionInstances(DecisionInstanceForListDto.createFrom(entities, objectMapper));
    return result;
  }

  @Override
  public Map> getDecisionInstanceDRDData(
      final String decisionInstanceId) {
    // we need to find all decision instances with the same key, which we extract from
    // decisionInstanceId
    final Long decisionInstanceKey = DecisionInstanceEntity.extractKey(decisionInstanceId);

    final var searchRequest =
        searchRequestBuilder(decisionInstanceTemplate)
            .query(withTenantCheck(term(DecisionInstanceTemplate.KEY, decisionInstanceKey)))
            .source(sourceInclude(DECISION_ID, STATE));

    final List results = new ArrayList<>();
    richOpenSearchClient
        .doc()
        .scrollWith(
            searchRequest,
            Map.class,
            hits ->
                hits.stream()
                    .filter(hit -> hit.source() != null)
                    .forEach(
                        hit -> {
                          final var map = hit.source();
                          results.add(
                              new DRDDataEntryDto(
                                  hit.id(),
                                  (String) map.get(DECISION_ID),
                                  DecisionInstanceState.valueOf((String) map.get(STATE))));
                        }));
    return results.stream().collect(groupingBy(DRDDataEntryDto::getDecisionId));
  }

  @Override
  public Tuple getCalledDecisionInstanceAndDefinitionByFlowNodeInstanceId(
      final String flowNodeInstanceId) {
    final String[] calledDecisionInstanceId = {null};
    final String[] calledDecisionDefinitionName = {null};
    findCalledDecisionInstance(
        flowNodeInstanceId,
        hit -> {
          final var source = hit.source();
          final var rootDecisionDefId = source.getRootDecisionDefinitionId();
          final var decisionDefId = source.getDecisionDefinitionId();
          if (rootDecisionDefId.equals(decisionDefId)) {
            // this is our instance, we will show the link
            calledDecisionInstanceId[0] = source.getId();
            var decisionName = source.getDecisionName();
            if (decisionName == null) {
              decisionName = source.getDecisionId();
            }
            calledDecisionDefinitionName[0] = decisionName;
          } else {
            // we will show only name of the root decision without the link
            var decisionName = source.getRootDecisionName();
            if (decisionName == null) {
              decisionName = source.getRootDecisionId();
            }
            calledDecisionDefinitionName[0] = decisionName;
          }
        });

    return Tuple.of(calledDecisionInstanceId[0], calledDecisionDefinitionName[0]);
  }

  private List queryDecisionInstancesEntities(
      final DecisionInstanceListRequestDto request, final DecisionInstanceListResponseDto result) {
    final var query = createRequestQuery(request.getQuery());

    LOGGER.debug("Decision instance search request: \n{}", query);

    final var searchRequest =
        searchRequestBuilder(decisionInstanceTemplate)
            .query(query)
            .source(sourceExclude(RESULT, EVALUATED_INPUTS, EVALUATED_OUTPUTS));

    applySorting(searchRequest, request);

    LOGGER.debug(
        "Search request will search in: \n{}", decisionInstanceTemplate.getFullQualifiedName());

    final var response =
        richOpenSearchClient.doc().search(searchRequest, DecisionInstanceEntity.class);
    result.setTotalCount(response.hits().total().value());

    final List decisionInstanceEntities =
        new ArrayList<>(
            response.hits().hits().stream()
                .filter(hit -> hit.source() != null)
                .map(hit -> hit.source().setSortValues(hit.sort().toArray()))
                .toList());
    if (request.getSearchBefore() != null) {
      Collections.reverse(decisionInstanceEntities);
    }
    return decisionInstanceEntities;
  }

  private Query createRequestQuery(
      final DecisionInstanceListQueryDto decisionInstanceListQueryDto) {
    final var query =
        withTenantCheck(
            and(
                createEvaluatedFailedQuery(decisionInstanceListQueryDto),
                createDecisionDefinitionIdsQuery(decisionInstanceListQueryDto),
                createIdsQuery(decisionInstanceListQueryDto),
                createProcessInstanceIdQuery(decisionInstanceListQueryDto),
                createEvaluationDateQuery(decisionInstanceListQueryDto),
                createReadPermissionQuery(),
                createTenantIdQuery(decisionInstanceListQueryDto)));
    return query == null ? matchAll() : query;
  }

  private Query createTenantIdQuery(final DecisionInstanceListQueryDto query) {
    if (query.getTenantId() != null) {
      return term(DecisionInstanceTemplate.TENANT_ID, query.getTenantId());
    }
    return null;
  }

  private Query createReadPermissionQuery() {
    if (permissionsService == null) {
      return null;
    }
    final var allowed = permissionsService.getDecisionsWithPermission(IdentityPermission.READ);
    if (allowed == null) {
      return null;
    }
    return allowed.isAll() ? matchAll() : stringTerms(DecisionIndex.DECISION_ID, allowed.getIds());
  }

  private Query createEvaluationDateQuery(
      final DecisionInstanceListQueryDto decisionInstanceListQueryDto) {
    if (decisionInstanceListQueryDto.getEvaluationDateAfter() != null
        || decisionInstanceListQueryDto.getEvaluationDateBefore() != null) {
      final var query =
          RangeQuery.of(
              q -> {
                q.field(EVALUATION_DATE);
                if (decisionInstanceListQueryDto.getEvaluationDateAfter() != null) {
                  q.gte(
                      JsonData.of(
                          dateTimeFormatter.format(
                              decisionInstanceListQueryDto.getEvaluationDateAfter())));
                }
                if (decisionInstanceListQueryDto.getEvaluationDateBefore() != null) {
                  q.lt(
                      JsonData.of(
                          dateTimeFormatter.format(
                              decisionInstanceListQueryDto.getEvaluationDateBefore())));
                }
                q.format(operateProperties.getOpensearch().getDateFormat());
                return q;
              });
      return query._toQuery();
    }
    return null;
  }

  private Query createProcessInstanceIdQuery(final DecisionInstanceListQueryDto query) {
    if (query.getProcessInstanceId() != null) {
      return term(PROCESS_INSTANCE_KEY, query.getProcessInstanceId());
    }
    return null;
  }

  private Query createIdsQuery(final DecisionInstanceListQueryDto query) {
    if (CollectionUtil.isNotEmpty(query.getIds())) {
      return stringTerms(ID, query.getIds());
    }
    return null;
  }

  private Query createDecisionDefinitionIdsQuery(final DecisionInstanceListQueryDto query) {
    if (CollectionUtil.isNotEmpty(query.getDecisionDefinitionIds())) {
      return stringTerms(DECISION_DEFINITION_ID, query.getDecisionDefinitionIds());
    }
    return null;
  }

  private Query createEvaluatedFailedQuery(final DecisionInstanceListQueryDto query) {
    if (query.isEvaluated() && query.isFailed()) {
      // cover all instances
      return null;
    } else if (query.isFailed()) {
      return term(STATE, FAILED.name());
    } else if (query.isEvaluated()) {
      return term(STATE, EVALUATED.name());
    } else {
      return matchNone();
    }
  }

  private void applySorting(
      final SearchRequest.Builder searchRequest, final DecisionInstanceListRequestDto request) {
    final String sortBy = getSortBy(request);

    final boolean directSorting =
        request.getSearchAfter() != null || request.getSearchBefore() == null;
    if (request.getSorting() != null) {
      final SortOptions sort1;
      final SortOrder sort1DirectOrder =
          request.getSorting().getSortOrder().equalsIgnoreCase("desc")
              ? SortOrder.Desc
              : SortOrder.Asc;
      if (directSorting) {
        sort1 = sortOptions(sortBy, sort1DirectOrder, "_last");
      } else {
        sort1 = sortOptions(sortBy, reverseOrder(sort1DirectOrder), "_first");
      }
      searchRequest.sort(sort1);
    }

    final SortOptions sort2;
    final SortOptions sort3;
    final Object[] querySearchAfter;
    if (directSorting) { // this sorting is also the default one for 1st page
      sort2 = sortOptions(KEY, SortOrder.Asc);
      sort3 = sortOptions(EXECUTION_INDEX, SortOrder.Asc);
      querySearchAfter = request.getSearchAfter(objectMapper); // may be null
    } else { // searchBefore != null
      // reverse sorting
      sort2 = sortOptions(KEY, SortOrder.Desc);
      sort3 = sortOptions(EXECUTION_INDEX, SortOrder.Desc);
      querySearchAfter = request.getSearchBefore(objectMapper);
    }

    searchRequest.sort(sort2).sort(sort3).size(request.getPageSize());
    if (querySearchAfter != null) {
      searchRequest.searchAfter(toSafeListOfStrings(querySearchAfter));
    }
  }

  private String getSortBy(final DecisionInstanceListRequestDto request) {
    if (request.getSorting() != null) {
      String sortBy = request.getSorting().getSortBy();
      sortBy =
          switch (sortBy) {
            case DecisionInstanceTemplate.ID ->
                // we sort by id as numbers, not as strings
                KEY;
            case DecisionInstanceListRequestDto.SORT_BY_TENANT_ID -> TENANT_ID;
            case SORT_BY_PROCESS_INSTANCE_ID -> PROCESS_INSTANCE_KEY;
            default -> sortBy;
          };
      return sortBy;
    }
    return null;
  }

  private void findCalledDecisionInstance(
      final String flowNodeInstanceId,
      final Consumer> decisionInstanceConsumer) {

    final var searchRequest =
        searchRequestBuilder(decisionInstanceTemplate.getAlias())
            .query(withTenantCheck(term(ELEMENT_INSTANCE_KEY, flowNodeInstanceId)))
            .source(
                sourceInclude(
                    ROOT_DECISION_DEFINITION_ID,
                    ROOT_DECISION_NAME,
                    ROOT_DECISION_ID,
                    DECISION_DEFINITION_ID,
                    DECISION_NAME,
                    DecisionIndex.DECISION_ID))
            .sort(
                List.of(
                    sortOptions(EVALUATION_DATE, SortOrder.Desc),
                    sortOptions(EXECUTION_INDEX, SortOrder.Desc)));
    final var response =
        richOpenSearchClient.doc().search(searchRequest, DecisionInstanceEntity.class);
    if (response.hits().total().value() >= 1) {
      decisionInstanceConsumer.accept(response.hits().hits().get(0));
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy