Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.camunda.tasklist.webapp.service.TaskService 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.tasklist.webapp.service;
import static io.camunda.tasklist.Metrics.*;
import static io.camunda.tasklist.util.CollectionUtil.countNonNullObjects;
import static java.util.Collections.emptySet;
import static java.util.Objects.requireNonNullElse;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.camunda.tasklist.Metrics;
import io.camunda.tasklist.entities.TaskEntity;
import io.camunda.tasklist.entities.TaskImplementation;
import io.camunda.tasklist.exceptions.TasklistRuntimeException;
import io.camunda.tasklist.store.TaskMetricsStore;
import io.camunda.tasklist.store.TaskStore;
import io.camunda.tasklist.store.VariableStore;
import io.camunda.tasklist.views.TaskSearchView;
import io.camunda.tasklist.webapp.es.TaskValidator;
import io.camunda.tasklist.webapp.graphql.entity.*;
import io.camunda.tasklist.webapp.rest.exception.ForbiddenActionException;
import io.camunda.tasklist.webapp.rest.exception.InvalidRequestException;
import io.camunda.tasklist.webapp.security.AssigneeMigrator;
import io.camunda.tasklist.webapp.security.UserReader;
import io.camunda.zeebe.client.ZeebeClient;
import io.camunda.zeebe.client.api.command.ClientException;
import io.camunda.zeebe.client.api.command.CompleteJobCommandStep1;
import io.camunda.zeebe.client.api.response.AssignUserTaskResponse;
import java.io.IOException;
import java.util.*;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpServerErrorException;
@Component
public class TaskService {
private static final Logger LOGGER = LoggerFactory.getLogger(TaskService.class);
@Autowired private UserReader userReader;
@Autowired
@Qualifier("tasklistZeebeClient")
private ZeebeClient zeebeClient;
@Autowired private TaskStore taskStore;
@Autowired private VariableService variableService;
@Autowired
@Qualifier("tasklistObjectMapper")
private ObjectMapper objectMapper;
@Autowired private Metrics metrics;
@Autowired private TaskMetricsStore taskMetricsStore;
@Autowired private AssigneeMigrator assigneeMigrator;
@Autowired private TaskValidator taskValidator;
public List getTasks(final TaskQueryDTO query) {
return getTasks(query, emptySet(), false);
}
public List getTasks(
final TaskQueryDTO query,
final Set includeVariableNames,
final boolean fetchFullValuesFromDB) {
if (countNonNullObjects(
query.getSearchAfter(), query.getSearchAfterOrEqual(),
query.getSearchBefore(), query.getSearchBeforeOrEqual())
> 1) {
throw new InvalidRequestException(
"Only one of [searchAfter, searchAfterOrEqual, searchBefore, searchBeforeOrEqual] must be present in request.");
}
if (query.getPageSize() <= 0) {
throw new InvalidRequestException("Page size should be a positive number");
}
if (query.getImplementation() != null
&& !query.getImplementation().equals(TaskImplementation.ZEEBE_USER_TASK)
&& !query.getImplementation().equals(TaskImplementation.JOB_WORKER)) {
throw new InvalidRequestException(
String.format(
"Invalid implementation, the valid values are %s and %s",
TaskImplementation.ZEEBE_USER_TASK, TaskImplementation.JOB_WORKER));
}
final List tasks = taskStore.getTasks(query.toTaskQuery());
final Set fieldNames =
fetchFullValuesFromDB
? emptySet()
: Set.of(
"id",
"name",
"previewValue",
"isValueTruncated"); // use fieldNames to not fetch fullValue from DB
final Map> variablesPerTaskId =
CollectionUtils.isEmpty(includeVariableNames)
? Collections.emptyMap()
: variableService.getVariablesPerTaskId(
tasks.stream()
.map(
taskView ->
VariableStore.GetVariablesRequest.createFrom(
taskView, new ArrayList<>(includeVariableNames), fieldNames))
.toList());
return tasks.stream()
.map(
it ->
TaskDTO.createFrom(
it,
Optional.ofNullable(variablesPerTaskId.get(it.getId()))
.map(list -> list.toArray(new VariableDTO[list.size()]))
.orElse(null),
objectMapper))
.toList();
}
public TaskDTO getTask(final String taskId) {
return TaskDTO.createFrom(taskStore.getTask(taskId), objectMapper);
}
public TaskDTO assignTask(
final String taskId, final String assignee, Boolean allowOverrideAssignment) {
if (allowOverrideAssignment == null) {
allowOverrideAssignment = true;
}
final UserDTO currentUser = getCurrentUser();
if (StringUtils.isEmpty(assignee) && currentUser.isApiUser()) {
throw new InvalidRequestException("Assignee must be specified");
}
if (StringUtils.isNotEmpty(assignee)
&& !currentUser.isApiUser()
&& !assignee.equals(currentUser.getUserId())) {
throw new ForbiddenActionException(
"User doesn't have the permission to assign another user to this task");
}
final TaskEntity taskBefore = taskStore.getTask(taskId);
taskValidator.validateCanAssign(taskBefore, allowOverrideAssignment);
final String taskAssignee = determineTaskAssignee(assignee);
if (taskBefore.getImplementation().equals(TaskImplementation.ZEEBE_USER_TASK)) {
try {
final AssignUserTaskResponse assigneeResponse =
zeebeClient
.newUserTaskAssignCommand(Long.parseLong(taskId))
.assignee(taskAssignee)
.send()
.join();
} catch (final ClientException exception) {
throw new TasklistRuntimeException(exception.getMessage());
}
}
final TaskEntity claimedTask = taskStore.persistTaskClaim(taskBefore, taskAssignee);
updateClaimedMetric(claimedTask);
return TaskDTO.createFrom(claimedTask, objectMapper);
}
private String determineTaskAssignee(final String assignee) {
final UserDTO currentUser = getCurrentUser();
return StringUtils.isEmpty(assignee) && !currentUser.isApiUser()
? currentUser.getUserId()
: assignee;
}
public TaskDTO completeTask(
final String taskId,
final List variables,
final boolean withDraftVariableValues) {
final Map variablesMap = new HashMap<>();
requireNonNullElse(variables, Collections.emptyList())
.forEach(variable -> variablesMap.put(variable.getName(), extractTypedValue(variable)));
try {
LOGGER.info("Starting completion of task with ID: {}", taskId);
final TaskEntity task = taskStore.getTask(taskId);
taskValidator.validateCanComplete(task);
try {
if (task.getImplementation().equals(TaskImplementation.JOB_WORKER)) {
// complete
CompleteJobCommandStep1 completeJobCommand =
zeebeClient.newCompleteCommand(Long.parseLong(taskId));
completeJobCommand = completeJobCommand.variables(variablesMap);
completeJobCommand.send().join();
} else {
zeebeClient
.newUserTaskCompleteCommand(Long.parseLong(taskId))
.variables(variablesMap)
.send()
.join();
}
} catch (final ClientException exception) {
throw new TasklistRuntimeException(exception.getMessage());
}
// persist completion and variables
final TaskEntity completedTaskEntity = taskStore.persistTaskCompletion(task);
try {
LOGGER.info("Start variable persistence: {}", taskId);
variableService.persistTaskVariables(taskId, variables, withDraftVariableValues);
deleteDraftTaskVariablesSafely(taskId);
updateCompletedMetric(completedTaskEntity);
LOGGER.info("Task with ID {} completed successfully.", taskId);
if (task.getImplementation().equals(TaskImplementation.JOB_WORKER)) {
// Remove variables for Job workers
// Remove this line after version 8.8
variableService.removeVariableByFlowNodeInstanceId(task.getFlowNodeInstanceId());
}
} catch (final Exception e) {
LOGGER.error(
"Task with key {} was COMPLETED but error happened after completion: {}.",
taskId,
e.getMessage());
}
return TaskDTO.createFrom(completedTaskEntity, objectMapper);
} catch (final HttpServerErrorException e) { // Track only internal server errors
LOGGER.error("Error completing task with ID: {}. Details: {}", taskId, e.getMessage(), e);
throw new TasklistRuntimeException("Error completing task with ID: " + taskId, e);
}
}
void deleteDraftTaskVariablesSafely(final String taskId) {
try {
LOGGER.info(
"Start deletion of draft task variables associated with task with id='{}'", taskId);
variableService.deleteDraftTaskVariables(taskId);
} catch (final Exception ex) {
final String errorMessage =
String.format(
"Error during deletion of draft task variables associated with task with id='%s'",
taskId);
LOGGER.error(errorMessage, ex);
}
}
private Object extractTypedValue(final VariableInputDTO variable) {
if (variable.getValue().equals("null")) {
return objectMapper
.nullNode(); // JSON Object null must be instanced like "null", also should not send to
// objectMapper null values
}
try {
return objectMapper.readValue(variable.getValue(), Object.class);
} catch (final IOException e) {
throw new TasklistRuntimeException(e.getMessage(), e);
}
}
public TaskDTO unassignTask(final String taskId) {
final TaskEntity taskBefore = taskStore.getTask(taskId);
taskValidator.validateCanUnassign(taskBefore);
final TaskEntity taskEntity = taskStore.persistTaskUnclaim(taskBefore);
if (taskBefore.getImplementation().equals(TaskImplementation.ZEEBE_USER_TASK)) {
try {
zeebeClient.newUserTaskUnassignCommand(taskBefore.getKey()).send().join();
} catch (final ClientException exception) {
taskStore.persistTaskClaim(taskBefore, taskBefore.getAssignee());
throw new TasklistRuntimeException(exception.getMessage());
}
}
return TaskDTO.createFrom(taskEntity, objectMapper);
}
private UserDTO getCurrentUser() {
return userReader.getCurrentUser();
}
private void updateClaimedMetric(final TaskEntity task) {
metrics.recordCounts(COUNTER_NAME_CLAIMED_TASKS, 1, getTaskMetricLabels(task));
}
private void updateCompletedMetric(final TaskEntity task) {
LOGGER.info("Updating completed task metric for task with ID: {}", task.getId());
try {
metrics.recordCounts(COUNTER_NAME_COMPLETED_TASKS, 1, getTaskMetricLabels(task));
assigneeMigrator.migrateUsageMetrics(getCurrentUser().getUserId());
taskMetricsStore.registerTaskCompleteEvent(task);
} catch (final Exception e) {
LOGGER.error("Error updating completed task metric for task with ID: {}", task.getId(), e);
throw new TasklistRuntimeException(
"Error updating completed task metric for task with ID: " + task.getId(), e);
}
}
private String[] getTaskMetricLabels(final TaskEntity task) {
final String keyUserId;
if (getCurrentUser().isApiUser()) {
if (task.getAssignee() != null) {
keyUserId = task.getAssignee();
} else {
keyUserId = UserReader.DEFAULT_USER;
}
} else {
keyUserId = userReader.getCurrentUserId();
}
return new String[] {
TAG_KEY_BPMN_PROCESS_ID, task.getBpmnProcessId(),
TAG_KEY_FLOW_NODE_ID, task.getFlowNodeBpmnId(),
TAG_KEY_USER_ID, keyUserId,
TAG_KEY_ORGANIZATION_ID, userReader.getCurrentOrganizationId()
};
}
}