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

com.xlrit.gears.runner.runnertarget.GraphQLRunnerTarget Maven / Gradle / Ivy

The newest version!
package com.xlrit.gears.runner.runnertarget;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.xlrit.gears.runner.driver.RunConfig;
import com.xlrit.gears.runner.graphql.Operation;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class GraphQLRunnerTarget extends AbstractRunnerTarget {
	private static final Logger LOG = LoggerFactory.getLogger(GraphQLRunnerTarget.class);

	@Getter
	private String startTime = "";

	private final Map processInstances = new HashMap<>();
	private final Map namedTasks = new HashMap<>();

	public GraphQLRunnerTarget(RunConfig config, RunnerContext context) {
		super(config, context);
	}

	public ArrayNode getInstanceTasks(String key) {
		// get tasks by process id and extract tasks from response
		String instanceId     = processInstances.get(key).processInstanceId();
		Operation operation   = Operation.getTasks(instanceId);
		JsonNode jsonResponse = client.invoke(operation);

		if (!(jsonResponse.at("/processInstance/tasks") instanceof ArrayNode tasks)) {
			//throw new InterpreterException("Expected response to contain array at /processInstance/tasks: " + jsonResponse);
			return objectMapper.createArrayNode();
		}
		return tasks;
	}

	public JsonNode getTaskFromList(TaskExpression taskExpression, ArrayNode jsonTasks) {
		switch(taskExpression.type()) {
			case First -> {
				if (jsonTasks == null || jsonTasks.isEmpty()) throw new InterpreterException(String.format(
						"Could not obtain the first task from task list: %s", jsonTasks));
				return jsonTasks.get(0);
			}
			case Only -> {
				if (jsonTasks == null || jsonTasks.size() != 1) throw new InterpreterException(String.format(
						"There are %d tasks available. Thus the 'only' requirement is not met: %s",
						jsonTasks == null ? 0 : jsonTasks.size(), jsonTasks));
				return jsonTasks.get(0);
			}
		}
		throw new InterpreterException("Invalid task expression: " + taskExpression);
	}

	public TaskId getTaskId(String key, TaskExpression taskExpression) {
		if (taskExpression.type() == TaskType.Identifier) {
			return namedTasks.get(taskExpression.id());
		}
		JsonNode jsonTask = getTaskFromList(taskExpression, getInstanceTasks(key));
		String taskId = jsonTask.get("id").asText();
		return new TaskId(taskId, taskExpression.id());
	}

	@Override
	public void login(String username) {
		// login at the graph ql server
		Operation operation = Operation.login(username, username);
		JsonNode json = client.invoke(operation);

		// get user token
		if (json.get("login") == null) {
			throw new InterpreterException(String.format("Could not obtain user token from %s", json));
		}
		String token = json.get("login").asText();
		client.setToken(token);
	}

	@Override
	public void startProcess(String key, JsonNode valuesOpt) {
		JsonNode values = valuesOpt;
		List files = new ArrayList<>();
		if (valuesOpt != null) {
			values = resolveStartForm(key, valuesOpt);
			values = extractFiles(values, files);
		}

		Operation operation = files.isEmpty() ? Operation.startProcessByKey(key, values) : Operation.startProcessByKey(key, values, files);
		JsonNode result = client.invoke(operation);
		JsonNode processInstanceJson = result.path("startProcessByKey");
		if (processInstanceJson.isMissingNode()) {
			throw new InterpreterException(String.format("Could not interpret ProcessInstance in result: %s", result));
		}

		String kind = processInstanceJson.get("__typename").asText();
		processErrors(processInstanceJson, kind);
		startTime = processInstanceJson.get("startTime").asText();
		deploymentId = processInstanceJson.get("processDefinition").get("deploymentId").asText();

		processInstances.put(key, new ProcessInstance(key, processInstanceJson.get("id").asText()));
	}

	@Override
	public void claimTask(String key, TaskExpression taskExpression) {
		TaskId taskId = getTaskId(key, taskExpression);

		// claim the task
		JsonNode jsonClaimResponse = client.invoke(Operation.claimTask(taskId.id()));

		// verify result
		JsonNode success = jsonClaimResponse.path("claimTask");
		if (success.isMissingNode()) {
			throw new InterpreterException("Could not determine claim task response");
		}
		if (!success.asBoolean()) {
			throw new InterpreterException(String.format("Claiming task '%s' has failed", taskId.id()));
		}

		// store task id
		if (taskExpression.id() != null) {
			namedTasks.put(taskExpression.id(), taskId);
		}
	}

	@Override
	public void transferTask(String key, TaskExpression taskExpression, String currentAssignee) {
		TaskId taskId = getTaskId(key, taskExpression);

		// transfer the task
		JsonNode jsonClaimResponse = client.invoke(Operation.transferTask(taskId.id(), currentAssignee));

		// verify result
		JsonNode success = jsonClaimResponse.path("transferTask");
		if (success.isMissingNode()) {
			throw new InterpreterException("Could not determine transfer task response");
		}
		if (!success.asBoolean()) {
			throw new InterpreterException(String.format("Transfer of task '%s' has failed", taskId.id()));
		}

		// store task id
		if (taskExpression.id() != null) {
			namedTasks.put(taskExpression.id(), taskId);
		}
	}

	@Override
	public void submitTask(String processKey, TaskExpression taskExpression, JsonNode values) {
		// obtain task id
		TaskId taskId = getTaskId(processKey, taskExpression);

		// get the individual task (including the form) and log it
		JsonNode task = client.invoke(Operation.getTask(taskId.id()));
		JsonNode form = task.at("/task/form");
		if (context.config.renderForms) {
			LOG.debug("Task {}: {}", taskId.id(), task);
		}

		// perform the operation
		JsonNode resolvedValues = choiceResolver
				.form(form)
				.formId(taskId.id())
				.isTask(true)
				.resolve(values);
		List files = new ArrayList<>();
		resolvedValues = extractFiles(resolvedValues, files);

		final Operation operation;
		if (files.isEmpty()) operation = Operation.submitTask(taskId.id(), resolvedValues);
		else operation = Operation.submitTask(taskId.id(), resolvedValues, files);

		JsonNode json = client.invoke(operation);

		// verify the result
		JsonNode result = json.path("submitTask");
		if (result.isMissingNode()) throw new InterpreterException("Submitting data to a task has failed");
		String kind = result.asText();
		processErrors(result, kind);
	}

	@Override
	public void basedOn(String value) {}

	// TODO does this actually verify the process has ended?
	public void processCompleted(String processKey) {
		// see if any tasks remain
		JsonNode jsonTasks = getInstanceTasks(processKey);
		if (jsonTasks != null && !jsonTasks.isEmpty()) {
			StringBuilder tasks = new StringBuilder();
			for (JsonNode task : jsonTasks) {
				tasks.append(task);
			}
			throw new InterpreterException(String.format("%d task(s) remaining upon process complete:%n%s",
					jsonTasks.size(), tasks));
		}
	}

	private void processErrors(JsonNode result, String kind) {
		if (kind.equals("InputErrors")) {
			JsonNode errors = result.get("errors");
			if (!errors.isArray()) throw new InterpreterException("Could not obtain errors from result: " + result);
			ArrayNode errorsArray = (ArrayNode)errors;
			StringBuilder msgs = new StringBuilder();
			for (JsonNode error : errorsArray) {
				msgs.append(String.format("Entering value '%s' for %s of type %s caused error %d: %s%n",
						error.get("value").asText(), error.get("label").asText(), error.get("type").asText(),
						error.get("status").asInt(), error.get("message").asText()));
			}
			throw new InterpreterException(errors.size() + " user error(s) occurred: \n" + msgs);
		}
	}

	private JsonNode extractFiles(JsonNode values, List files) {
		if (values.isObject()) {
			ObjectNode object = (ObjectNode) values;
			ObjectNode newObject = objectMapper.createObjectNode();
			for (var ite = object.fields(); ite.hasNext();) {
				var entry = ite.next();
				newObject.set(entry.getKey(), extractFiles(entry.getValue(), files));
			}
			return newObject;
		} else if (values.isArray()) {
			ArrayNode array = (ArrayNode) values;
			ArrayNode newArray = objectMapper.createArrayNode();
			for (var ite = array.elements(); ite.hasNext();) {
				newArray.add(extractFiles(ite.next(), files));
			}
			return newArray;
		} else if (values.isTextual()) {
			String text = values.asText();
			if (text.startsWith("filename:")) {
				String fileName = text.substring(9);
				File file = new File(fileName);
				if (!file.exists()) {
					throw new IllegalArgumentException(String.format("File %s doesn't exist", file.getAbsolutePath()));
				}
				files.add(file);
				int index = files.size() - 1;
				return TextNode.valueOf("name:file" + index);
			}
		}
		return values;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy