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

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

There is a newer version: 1.17.1
Show newest version
package com.xlrit.gears.runner.runnertarget;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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.graphql.GraphQLClient;
import com.xlrit.gears.runner.graphql.Operation;
import com.xlrit.gears.runner.run.Config;
import com.xlrit.gears.runner.utils.GraphQLUtils;

import java.util.Optional;

public class GraphQLRunnerTarget implements RunnerTarget {
	private final Memory memory = new Memory();
	private final GraphQLClient client;

	public GraphQLRunnerTarget(Config config) {
		client = new GraphQLClient(config.endpoint, new ObjectMapper());
	}

	@Override
	public void loadData(String srcDir, String pattern) {
		JsonNode jsonResponse = client.invoke(Operation.loadData(srcDir, pattern));
		System.out.printf("Load data: %s%n", jsonResponse);
	}

	private String processInstanceKey(String key) {
		return "processinstance:" + key;
	}

	private String taskIdKey(String key) {
		return "taskid:" + key;
	}

	public ProcessInstance getProcessInstance(String key) {
		return memory.load(processInstanceKey(key));
	}

	public ArrayNode getInstanceTasks(String key) {
		// get tasks by process id and extract tasks from response
		String instanceId     = getProcessInstance(key).processInstanceId();
		Operation operation   = Operation.getTasks(instanceId);
		JsonNode jsonResponse = client.invoke(operation);
		return ((ArrayNode)jsonResponse.get("processInstance").get("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 memory.load(taskIdKey(taskExpression.id()));
		}
		JsonNode jsonTask = getTaskFromList(taskExpression, getInstanceTasks(key));
		String taskId = jsonTask.get("id").asText();
		return new TaskId(taskId, taskExpression.id());
	}

	public String getDeploymentId(String key) {
		return memory.load(processInstanceKey(key)).deploymentId();
	}

	@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 setDateTime(String value) {
		Operation operation = Operation.overrideCurrentDateTime(value);
		JsonNode json = client.invoke(operation);
		assert(json.get("overrideCurrentDateTime").asBoolean());
	}

	@Override
	public void scenarioStart(String scenario) {}

	@Override
	public void scenarioFinish() {
		clearDateTime();
	}

	@Override
	public void finish() {}

	private void clearDateTime() {
		JsonNode json = client.invoke(Operation.clearCurrentDateTime);
		assert(json.get("clearCurrentDateTime").asBoolean());
	}

	@Override
	public void startProcess(String key, JsonNode valuesOpt) {
		JsonNode values = valuesOpt;
		if (valuesOpt != null) {
			JsonNode processDefinitionIdResult = client.invoke(Operation.processDefinitionByKey(key));
			JsonNode jsonId = processDefinitionIdResult.path("processDefinitionByKey").path("id");
			if (jsonId.isMissingNode()) {
				throw new InterpreterException(String.format("Could not interpret processDefinitionId in result: %s",
						processDefinitionIdResult));
			}
			String processDefinitionId = jsonId.asText();
			values = resolveLabels(valuesOpt, processDefinitionId, null, false);
		}

		JsonNode result = client.invoke(Operation.startProcessByKey(key, values));
		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();
		if (kind.equals("InputErrors")) {
			JsonNode errors = processInstanceJson.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);
		}

		// associate process result with the process key and also as the "current" process (without a specific key)
		memory.store(processInstanceKey(key), new ProcessInstance(key, processInstanceJson));
		memory.store(processInstanceKey(""), new ProcessInstance("", processInstanceJson));
	}

	@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) {
			memory.store(taskIdKey(taskExpression.id()), taskId);
		}
	}

	@Override
	public void dataAssertion(String key, String grql) {
		GrqlResolver grqlResolver = new GrqlResolver(key, this);
		String jpql = grqlResolver.toJpql(grql);

		// perform the operation
		String deploymentId = key == null ? "" : getDeploymentId(key);
		JsonNode json = client.invoke(Operation.dataAssertion(jpql, deploymentId));
		JsonNode result = json.path("checkData");
		if (result.isMissingNode()) {
			throw new InterpreterException("Could not extract result from data assertion query");
		}
		if (!result.isBoolean()) {
			throw new InterpreterException("Data assertion is not of type boolean: " + result);
		}
		if (!result.asBoolean()) {
			StringBuilder msg = new StringBuilder();
			msg.append("Data assertion failed:\n")
					.append(grql);
			if (!grqlResolver.resolved.isEmpty()) {
				msg.append("With variable(s):");
				for (var entry : grqlResolver.resolved.entrySet()) {
					msg.append('\t')
							.append(entry.getKey())
							.append(": ")
							.append(entry.getValue())
							.append('\n');
				}
			}
			throw new InterpreterException(msg.toString());
		}
	}

	@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()));

		// perform the operation
		Operation operation = Operation.submitTask(taskId.id(), resolveLabels(values, taskId.id(), null, true));
		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();
		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);
		}
	}

	@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 JsonNode resolveLabels(JsonNode values, String choicesId, String fieldId, boolean task) {
		ObjectNode newValues = new ObjectMapper().createObjectNode();
		for (var ite = values.fields(); ite.hasNext();) {
			var current = ite.next();
			String path = fieldId == null ? current.getKey() : String.format("%s.%s", fieldId, current.getKey());
			if (current.getValue().isObject()) {
				newValues.set(current.getKey(), convertObject((ObjectNode) current.getValue(), choicesId, path, task));
			} else if (current.getValue().isArray()) {
				newValues.set(current.getKey(), convertArray(current.getValue(), choicesId, path, task));
			} else {
				newValues.set(current.getKey(), current.getValue());
			}
		}
		return newValues;
	}

	private ArrayNode convertArray(JsonNode array, String choicesId, String fieldId, boolean task) {
		ArrayNode newArray = new ObjectMapper().createArrayNode();
		int index = 0;
		for (JsonNode cur : array) {
			String path = String.format("%s[%d]", fieldId, index++);
			newArray.add(resolveLabels(cur, choicesId, path, task));
		}
		return newArray;
	}

	private JsonNode convertObject(ObjectNode objectValue, String choicesId, String fieldId, boolean task) {
		if (objectValue.size() > 1) {
			return resolveLabels(objectValue, choicesId, fieldId, task);
		}
		var kvp = objectValue.fields().next();
		return switch (kvp.getKey()) {
			case "label" -> new TextNode(labelToValue(kvp.getValue().asText(), choicesId, fieldId, task));
			case "value" -> new TextNode(kvp.getValue().asText());
			default -> resolveLabels(objectValue, choicesId, fieldId, task);
		};
	}

	private String labelToValue(String label, String choicesId, String fieldId, boolean task) {
		ArrayNode choices = (ArrayNode)(client.invoke(Operation.choicesByLabel(choicesId, fieldId, label, task)).get("choices"));
		if (choices.isEmpty()) {
			throw new InterpreterException(String.format("Label %s not found for input %s", label, fieldId));
		}
		return choices.get(0).get("value").asText();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy