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

com.xlrit.gears.server.graphql.TaskResolver Maven / Gradle / Ivy

The newest version!
package com.xlrit.gears.server.graphql;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import com.xlrit.gears.base.choice.Choice;
import com.xlrit.gears.base.choice.Choices;
import com.xlrit.gears.base.util.Range;
import com.xlrit.gears.engine.facade.EngineFacade;
import com.xlrit.gears.engine.facade.InProgress;
import com.xlrit.gears.engine.flowable.CustomTaskListQuery;
import com.xlrit.gears.engine.security.AuthManager;

import com.fasterxml.jackson.databind.JsonNode;
import graphql.schema.DataFetchingEnvironment;
import lombok.RequiredArgsConstructor;
import org.flowable.common.engine.impl.interceptor.CommandExecutor;
import org.flowable.common.engine.impl.service.CommonEngineServiceImpl;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.service.impl.TaskQueryProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.multipart.MultipartFile;

import static com.xlrit.gears.engine.util.EngineUtils.requireFound;

@Controller
@RequiredArgsConstructor
public class TaskResolver {
	private static final Logger LOG = LoggerFactory.getLogger(TaskResolver.class);

	private final TaskService    taskService;
	private final RuntimeService runtimeService;
	private final AuthManager    authManager;
	private final EngineFacade   engineFacade;

	// === queries === //

	@PreAuthorize("isAuthenticated()")
	@QueryMapping
	public Task task(@Argument String id, DataFetchingEnvironment env) {
		TaskQuery query = taskService.createTaskQuery().taskId(id);
		if (env.getSelectionSet().contains("error")) {
			query.includeTaskLocalVariables();
		}
		return query.singleResult();
	}

	@QueryMapping
	public Choice choice(@Argument String taskId, @Argument String processDefinitionId, @Argument String path, @Argument String value, @Argument JsonNode values) {
		LOG.info("Returning choice for taskId={}, processDefinitionId={}, path={}, value={}, values={}", taskId, processDefinitionId, path, value, values);
		Choices choices = engineFacade.getChoices(taskId, processDefinitionId, path, values);
		return choices == null ? null : choices.find(value).orElse(null);
	}

	@QueryMapping
	public List choices(@Argument String taskId, @Argument String processDefinitionId, @Argument String path, @Argument String filter, @Argument Range range, @Argument JsonNode values) {
		LOG.info("Returning choices for taskId={}, processDefinitionId={}, path={}, filter={}, range={}, values={}", taskId, processDefinitionId, path, filter, range, values);
		Choices choices = engineFacade.getChoices(taskId, processDefinitionId, path, values);
		return choices == null ? Collections.emptyList() : choices.items(filter, range).collect(Collectors.toList());
	}

	@PreAuthorize("hasRole('admin')")
	@QueryMapping
	public List allTasks(@Argument String filter, @Argument Range range) {
		return listTasks(filter, range, query -> {});
	}

	@QueryMapping
	public long allTasksCount(@Argument String filter) {
		if (!authManager.isAdmin()) return -1L;
		return countTasks(filter, query -> {});
	}

	@QueryMapping
	public List assignedTasks(@Argument String filter, @Argument Range range) {
		String currentUserId = authManager.getCurrentUserId();
		if (currentUserId == null) return Collections.emptyList();

		return listTasks(filter, range, query -> query.assignee(currentUserId));
	}

	@QueryMapping
	public long assignedTasksCount(@Argument String filter) {
		String currentUserId = authManager.getCurrentUserId();
		if (currentUserId == null) return 0L;

		return countTasks(filter, query -> query.assignee(currentUserId));
	}

	@QueryMapping
	public List groupTasks(@Argument String filter, @Argument Range range) {
		Set currentUserRoleNames = authManager.getCurrentUserRoleNames();
		if (currentUserRoleNames == null || currentUserRoleNames.isEmpty()) return Collections.emptyList();

		return listTasks(filter, range, query -> query.candidateGroups(currentUserRoleNames).assigned(false));
	}

	@QueryMapping
	public long groupTasksCount(@Argument String filter) {
		Set currentUserRoleNames = authManager.getCurrentUserRoleNames();
		if (currentUserRoleNames == null || currentUserRoleNames.isEmpty()) return 0L;

		return countTasks(filter, query -> query.candidateGroups(currentUserRoleNames).assigned(false));
	}

	@QueryMapping
	public List transferableTasks(@Argument String filter, @Argument Range range) {
		String currentUserId = authManager.getCurrentUserId();
		Set currentUserRoleNames = authManager.getCurrentUserRoleNames();
		if (currentUserId == null || currentUserRoleNames == null || currentUserRoleNames.isEmpty()) return Collections.emptyList();

		return listTasks(filter, range, query -> query.candidateGroups(currentUserRoleNames).assigneeNot(currentUserId));
	}

	@QueryMapping
	public long transferableTasksCount(@Argument String filter) {
		String currentUserId = authManager.getCurrentUserId();
		Set currentUserRoleNames = authManager.getCurrentUserRoleNames();
		if (currentUserId == null || currentUserRoleNames == null || currentUserRoleNames.isEmpty()) return 0L;

		return countTasks(filter, query -> query.candidateGroups(currentUserRoleNames).assigneeNot(currentUserId));
	}

	// === mutations === //

	@PreAuthorize("isAuthenticated()")
	@MutationMapping
	public boolean claimTask(@Argument("id") String taskId) {
		Objects.requireNonNull(taskId, "taskId must not be null");
		engineFacade.claimTask(taskId);
		return true;
	}

	@PreAuthorize("isAuthenticated()")
	@MutationMapping
	public boolean transferTask(@Argument("id") String taskId, @Argument("currentAssignee") String currentAssignee) {
		Objects.requireNonNull(taskId, "taskId must not be null");
		Objects.requireNonNull(currentAssignee, "currentAssignee must not be null");
		engineFacade.transferTask(taskId, currentAssignee);
		return true;
	}

	@PreAuthorize("isAuthenticated()")
	@MutationMapping
	public boolean saveTask(@Argument("id") String taskId, @Argument("values") JsonNode formValues, @Argument List files) {
		Objects.requireNonNull(taskId, "taskId must not be null");
		LOG.info("saveTask: id={}, values={}", taskId, formValues);

		if (files != null && !files.isEmpty()) {
			LOG.info("saveTask: received files");
			for (MultipartFile file : files) {
				LOG.info("- name={}, filename={}, type={}", file.getName(), file.getOriginalFilename(), file.getContentType());
			}
		}

		engineFacade.saveTask(taskId, formValues, files);

		return true;
	}

	/** GraphQL type is SubmitResult aka SubmitInProgress | ProcessInstance | ProcessInstanceEnded | InputErrors */
	@PreAuthorize("isAuthenticated()")
	@MutationMapping
	public Object submitTask(@Argument String deploymentId, @Argument("id") String taskId, @Argument JsonNode values, @Argument List files, @Argument Long timeoutMillis) {
		Objects.requireNonNull(taskId, "taskId must not be null");
		if (timeoutMillis == null) timeoutMillis = Long.MAX_VALUE;

		if (files != null && !files.isEmpty()) {
			LOG.info("submitTask: received files");
			for (MultipartFile file : files) {
				LOG.info("- name={}, filename={}, type={}", file.getName(), file.getOriginalFilename(), file.getContentType());
			}
		}

		Future futureResult = engineFacade.submitTaskAsync(deploymentId, taskId, values, files);
		try {
			LOG.debug("Getting futureResult with a timeout of {} seconds", timeoutMillis);
			return futureResult.get(timeoutMillis, TimeUnit.MILLISECONDS);
		}
		catch (TimeoutException e) {
			return new InProgress(taskId);
		}
		catch (InterruptedException e) {
			throw new RuntimeException("Submit task processing was interrupted", e);
		}
		catch (ExecutionException e) {
			Throwable cause = e.getCause();
			throw (cause instanceof RuntimeException ? (RuntimeException) cause : new RuntimeException("A checked exception occurred", cause));
		}
	}

	@MutationMapping
	@PreAuthorize("isAuthenticated()")
	public ProcessInstance cancelTask(@Argument String deploymentId, @Argument("id") String taskId) {
		Objects.requireNonNull(taskId, "taskId must not be null");
		Task task = requireFound(
			taskService.createTaskQuery().taskId(taskId).singleResult(),
			"task", "id", taskId);

		String instanceId = task.getProcessInstanceId();
		String messageName = "cancel";
		String executionId = task.getExecutionId();

		LOG.info(
			"cancelTask" +
			": messageName=" + messageName +
			", instanceId=" + instanceId +
			", executionId=" + executionId +
			", getTaskDefinitionId=" + task.getTaskDefinitionId() +
			", getTaskDefinitionKey=" + task.getTaskDefinitionKey());

		Execution cancelExecution =
			runtimeService.createExecutionQuery()
				.processInstanceId(instanceId)
				.messageEventSubscriptionName(messageName)
				.singleResult();

		runtimeService.messageEventReceived(messageName, cancelExecution.getId());

		return runtimeService.createProcessInstanceQuery()
			.processInstanceId(instanceId)
			.singleResult();
	}

	private List listTasks(String filter, Range range, Consumer customizer) {
		return createCustomTaskListQuery()
			.orderBy(TaskQueryProperty.CREATE_TIME).desc()
			.filter(filter)
			.customize(customizer)
			.listPage(range.getStart(), range.getCount());
	}

	private long countTasks(String filter, Consumer customizer) {
		return createCustomTaskListQuery()
			.filter(filter)
			.customize(customizer)
			.count();
	}

	private CustomTaskListQuery createCustomTaskListQuery() {
		CommandExecutor commandExecutor = ((CommonEngineServiceImpl) taskService).getCommandExecutor();
		return new CustomTaskListQuery(commandExecutor);
	}
}