Add Auction House; Extend uniform HTTP API for TAPAS-Tasks
This commit is contained in:
@@ -3,13 +3,10 @@ package ch.unisg.tapastasks;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@SpringBootApplication
|
||||
public class TapasTasksApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
SpringApplication tapasTasksApp = new SpringApplication(TapasTasksApplication.class);
|
||||
tapasTasksApp.run(args);
|
||||
}
|
||||
|
@@ -0,0 +1,102 @@
|
||||
package ch.unisg.tapastasks.tasks.adapter.in.formats;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
* This class is used to process JSON Patch operations for tasks: given a
|
||||
* <a href="http://jsonpatch.com/">JSON Patch</a> for updating the representational state of a task,
|
||||
* this class provides methods for extracting various operations of interest for our domain (e.g.,
|
||||
* changing the status of a task).
|
||||
*/
|
||||
public class TaskJsonPatchRepresentation {
|
||||
public static final String MEDIA_TYPE = "application/json-patch+json";
|
||||
|
||||
private final JsonNode patch;
|
||||
|
||||
/**
|
||||
* Constructs the JSON Patch representation.
|
||||
*
|
||||
* @param patch a JSON Patch as JsonNode
|
||||
*/
|
||||
public TaskJsonPatchRepresentation(JsonNode patch) {
|
||||
this.patch = patch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the first task status replaced in this patch.
|
||||
*
|
||||
* @return the first task status changed in this patch or an empty {@link Optional} if none is
|
||||
* found
|
||||
*/
|
||||
public Optional<Task.Status> extractFirstTaskStatusChange() {
|
||||
Optional<JsonNode> status = extractFirst(node ->
|
||||
isPatchReplaceOperation(node) && hasPath(node, "/taskStatus")
|
||||
);
|
||||
|
||||
if (status.isPresent()) {
|
||||
String taskStatus = status.get().get("value").asText();
|
||||
return Optional.of(Task.Status.valueOf(taskStatus));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the first service provider added or replaced in this patch.
|
||||
*
|
||||
* @return the first service provider changed in this patch or an empty {@link Optional} if none
|
||||
* is found
|
||||
*/
|
||||
public Optional<Task.ServiceProvider> extractFirstServiceProviderChange() {
|
||||
Optional<JsonNode> serviceProvider = extractFirst(node ->
|
||||
(isPatchReplaceOperation(node) || isPatchAddOperation(node))
|
||||
&& hasPath(node, "/serviceProvider")
|
||||
);
|
||||
|
||||
return (serviceProvider.isEmpty()) ? Optional.empty()
|
||||
: Optional.of(new Task.ServiceProvider(serviceProvider.get().get("value").asText()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the first output data addition in this patch.
|
||||
*
|
||||
* @return the output data added in this patch or an empty {@link Optional} if none is found
|
||||
*/
|
||||
public Optional<Task.OutputData> extractFirstOutputDataAddition() {
|
||||
Optional<JsonNode> output = extractFirst(node ->
|
||||
isPatchAddOperation(node) && hasPath(node, "/outputData")
|
||||
);
|
||||
|
||||
return (output.isEmpty()) ? Optional.empty()
|
||||
: Optional.of(new Task.OutputData(output.get().get("value").asText()));
|
||||
}
|
||||
|
||||
private Optional<JsonNode> extractFirst(Predicate<? super JsonNode> predicate) {
|
||||
Stream<JsonNode> stream = StreamSupport.stream(patch.spliterator(), false);
|
||||
return stream.filter(predicate).findFirst();
|
||||
}
|
||||
|
||||
private boolean isPatchAddOperation(JsonNode node) {
|
||||
return isPatchOperationOfType(node, "add");
|
||||
}
|
||||
|
||||
private boolean isPatchReplaceOperation(JsonNode node) {
|
||||
return isPatchOperationOfType(node, "replace");
|
||||
}
|
||||
|
||||
private boolean isPatchOperationOfType(JsonNode node, String operation) {
|
||||
return node.isObject() && node.get("op") != null
|
||||
&& node.get("op").asText().equalsIgnoreCase(operation);
|
||||
}
|
||||
|
||||
private boolean hasPath(JsonNode node, String path) {
|
||||
return node.isObject() && node.get("path") != null
|
||||
&& node.get("path").asText().equalsIgnoreCase(path);
|
||||
}
|
||||
}
|
@@ -0,0 +1,115 @@
|
||||
package ch.unisg.tapastasks.tasks.adapter.in.formats;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* This class is used to expose and consume representations of tasks through the HTTP interface. The
|
||||
* representations conform to the custom JSON-based media type "application/task+json". The media type
|
||||
* is just an identifier and can be registered with
|
||||
* <a href="https://www.iana.org/assignments/media-types/">IANA</a> to promote interoperability.
|
||||
*/
|
||||
final public class TaskJsonRepresentation {
|
||||
// The media type used for this task representation format
|
||||
public static final String MEDIA_TYPE = "application/task+json";
|
||||
|
||||
// A task identifier specific to our implementation (e.g., a UUID). This identifier is then used
|
||||
// to generate the task's URI. URIs are standard uniform identifiers and use a universal syntax
|
||||
// that can be referenced (and dereferenced) independent of context. In our uniform HTTP API,
|
||||
// we identify tasks via URIs and not implementation-specific identifiers.
|
||||
@Getter @Setter
|
||||
private String taskId;
|
||||
|
||||
// A string that represents the task's name
|
||||
@Getter
|
||||
private final String taskName;
|
||||
|
||||
// A string that identifies the task's type. This string could also be a URI (e.g., defined in some
|
||||
// Web ontology, as we shall see later in the course), but it's not constrained to be a URI.
|
||||
// The task's type can be used to assign executors to tasks, to decide what tasks to bid for, etc.
|
||||
@Getter
|
||||
private final String taskType;
|
||||
|
||||
// The task's status: OPEN, ASSIGNED, RUNNING, or EXECUTED (see Task.Status)
|
||||
@Getter @Setter
|
||||
private String taskStatus;
|
||||
|
||||
// If this task is a delegated task (i.e., a shadow of another task), this URI points to the
|
||||
// original task. Because URIs are standard and uniform, we can just dereference this URI to
|
||||
// retrieve a representation of the original task.
|
||||
@Getter @Setter
|
||||
private String originalTaskUri;
|
||||
|
||||
// The service provider who executes this task. The service provider is a any string that identifies
|
||||
// a TAPAS group (e.g., tapas-group1). This identifier could also be a URI (if we have a good reason
|
||||
// for it), but it's not constrained to be a URI.
|
||||
@Getter @Setter
|
||||
private String serviceProvider;
|
||||
|
||||
// A string that provides domain-specific input data for this task. In the context of this project,
|
||||
// we can parse and interpret the input data based on the task's type.
|
||||
@Getter @Setter
|
||||
private String inputData;
|
||||
|
||||
// A string that provides domain-specific output data for this task. In the context of this project,
|
||||
// we can parse and interpret the output data based on the task's type.
|
||||
@Getter @Setter
|
||||
private String outputData;
|
||||
|
||||
/**
|
||||
* Instantiate a task representation with a task name and type.
|
||||
*
|
||||
* @param taskName string that represents the task's name
|
||||
* @param taskType string that represents the task's type
|
||||
*/
|
||||
public TaskJsonRepresentation(String taskName, String taskType) {
|
||||
this.taskName = taskName;
|
||||
this.taskType = taskType;
|
||||
|
||||
this.taskStatus = null;
|
||||
this.originalTaskUri = null;
|
||||
this.serviceProvider = null;
|
||||
this.inputData = null;
|
||||
this.outputData = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a task representation from a domain concept.
|
||||
*
|
||||
* @param task the task
|
||||
*/
|
||||
public TaskJsonRepresentation(Task task) {
|
||||
this(task.getTaskName().getValue(), task.getTaskType().getValue());
|
||||
|
||||
this.taskId = task.getTaskId().getValue();
|
||||
this.taskStatus = task.getTaskStatus().getValue().name();
|
||||
|
||||
this.originalTaskUri = (task.getOriginalTaskUri() == null) ?
|
||||
null : task.getOriginalTaskUri().getValue();
|
||||
|
||||
this.serviceProvider = (task.getProvider() == null) ? null : task.getProvider().getValue();
|
||||
this.inputData = (task.getInputData() == null) ? null : task.getInputData().getValue();
|
||||
this.outputData = (task.getOutputData() == null) ? null : task.getOutputData().getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method used to serialize a task provided as a domain concept in the format exposed
|
||||
* through the uniform HTTP API.
|
||||
*
|
||||
* @param task the task as defined in the domain
|
||||
* @return a string serialization using the JSON-based representation format defined for tasks
|
||||
* @throws JsonProcessingException if a runtime exception occurs during object serialization
|
||||
*/
|
||||
public static String serialize(Task task) throws JsonProcessingException {
|
||||
TaskJsonRepresentation representation = new TaskJsonRepresentation(task);
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
|
||||
return mapper.writeValueAsString(representation);
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
package ch.unisg.tapastasks.tasks.adapter.in.messaging;
|
||||
|
||||
public class UnknownEventException extends RuntimeException { }
|
@@ -0,0 +1,39 @@
|
||||
package ch.unisg.tapastasks.tasks.adapter.in.messaging.http;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonPatchRepresentation;
|
||||
import ch.unisg.tapastasks.tasks.application.handler.TaskAssignedHandler;
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.TaskAssignedEvent;
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.TaskAssignedEventHandler;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task.TaskId;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Listener for task assigned events. A task assigned event corresponds to a JSON Patch that attempts
|
||||
* to change the task's status to ASSIGNED and may also add/replace a service provider (i.e., to what
|
||||
* group the task was assigned). This implementation does not impose that a task assigned event
|
||||
* includes the service provider (i.e., can be null).
|
||||
*
|
||||
* See also {@link TaskAssignedEvent}, {@link Task}, and {@link TaskEventHttpDispatcher}.
|
||||
*/
|
||||
public class TaskAssignedEventListenerHttpAdapter extends TaskEventListener {
|
||||
|
||||
/**
|
||||
* Handles the task assigned event.
|
||||
*
|
||||
* @param taskId the identifier of the task for which an event was received
|
||||
* @param payload the JSON Patch payload of the HTTP PATCH request received for this task
|
||||
* @return
|
||||
*/
|
||||
public Task handleTaskEvent(String taskId, JsonNode payload) {
|
||||
TaskJsonPatchRepresentation representation = new TaskJsonPatchRepresentation(payload);
|
||||
Optional<Task.ServiceProvider> serviceProvider = representation.extractFirstServiceProviderChange();
|
||||
|
||||
TaskAssignedEvent taskAssignedEvent = new TaskAssignedEvent(new TaskId(taskId), serviceProvider);
|
||||
TaskAssignedEventHandler taskAssignedEventHandler = new TaskAssignedHandler();
|
||||
|
||||
return taskAssignedEventHandler.handleTaskAssigned(taskAssignedEvent);
|
||||
}
|
||||
}
|
@@ -0,0 +1,103 @@
|
||||
package ch.unisg.tapastasks.tasks.adapter.in.messaging.http;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonPatchRepresentation;
|
||||
import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonRepresentation;
|
||||
import ch.unisg.tapastasks.tasks.adapter.in.messaging.UnknownEventException;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
import ch.unisg.tapastasks.tasks.domain.TaskNotFoundException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.github.fge.jsonpatch.JsonPatch;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
|
||||
/**
|
||||
* This REST Controller handles HTTP PATCH requests for updating the representational state of Task
|
||||
* resources. Each request to update the representational state of a Task resource can correspond to
|
||||
* at most one domain/integration event. Request payloads use the
|
||||
* <a href="http://jsonpatch.com/">JSON PATCH</a> format and media type.
|
||||
*
|
||||
* A JSON Patch can contain multiple operations (e.g., add, remove, replace) for updating various
|
||||
* parts of a task's representations. One or more JSON Patch operations can represent a domain/integration
|
||||
* event. Therefore, the events can only be determined by inspecting the requested patch (e.g., a request
|
||||
* to change a task's status from RUNNING to EXECUTED). This class is responsible to inspect requested
|
||||
* patches, identify events, and to route them to appropriate listeners.
|
||||
*
|
||||
* For more details on JSON Patch, see: <a href="http://jsonpatch.com/">http://jsonpatch.com/</a>
|
||||
* For some sample HTTP requests, see the README.
|
||||
*/
|
||||
@RestController
|
||||
public class TaskEventHttpDispatcher {
|
||||
// The standard media type for JSON Patch registered with IANA
|
||||
// See: https://www.iana.org/assignments/media-types/application/json-patch+json
|
||||
private final static String JSON_PATCH_MEDIA_TYPE = "application/json-patch+json";
|
||||
|
||||
/**
|
||||
* Handles HTTP PATCH requests with a JSON Patch payload. Routes the requests based on the
|
||||
* the operations requested in the patch. In this implementation, one HTTP Patch request is
|
||||
* mapped to at most one domain event.
|
||||
*
|
||||
* @param taskId the local (i.e., implementation-specific) identifier of the task to the patched;
|
||||
* this identifier is extracted from the task's URI
|
||||
* @param payload the reuqested patch for this task
|
||||
* @return 200 OK and a representation of the task after processing the event; 404 Not Found if
|
||||
* the request URI does not match any task; 400 Bad Request if the request is invalid
|
||||
*/
|
||||
@PatchMapping(path = "/tasks/{taskId}", consumes = {JSON_PATCH_MEDIA_TYPE})
|
||||
public ResponseEntity<String> dispatchTaskEvents(@PathVariable("taskId") String taskId,
|
||||
@RequestBody JsonNode payload) {
|
||||
try {
|
||||
// Throw an exception if the JSON Patch format is invalid. This call is only used to
|
||||
// validate the JSON PATCH syntax.
|
||||
JsonPatch.fromJson(payload);
|
||||
|
||||
// Check for known events and route the events to appropriate listeners
|
||||
TaskJsonPatchRepresentation representation = new TaskJsonPatchRepresentation(payload);
|
||||
Optional<Task.Status> status = representation.extractFirstTaskStatusChange();
|
||||
|
||||
TaskEventListener listener = null;
|
||||
|
||||
// Route events related to task status changes
|
||||
if (status.isPresent()) {
|
||||
switch (status.get()) {
|
||||
case ASSIGNED:
|
||||
listener = new TaskAssignedEventListenerHttpAdapter();
|
||||
break;
|
||||
case RUNNING:
|
||||
listener = new TaskStartedEventListenerHttpAdapter();
|
||||
break;
|
||||
case EXECUTED:
|
||||
listener = new TaskExecutedEventListenerHttpAdapter();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (listener == null) {
|
||||
// The HTTP PATCH request is valid, but the patch does not match any known event
|
||||
throw new UnknownEventException();
|
||||
}
|
||||
|
||||
Task task = listener.handleTaskEvent(taskId, payload);
|
||||
|
||||
// Add the content type as a response header
|
||||
HttpHeaders responseHeaders = new HttpHeaders();
|
||||
responseHeaders.add(HttpHeaders.CONTENT_TYPE, TaskJsonRepresentation.MEDIA_TYPE);
|
||||
|
||||
return new ResponseEntity<>(TaskJsonRepresentation.serialize(task), responseHeaders,
|
||||
HttpStatus.OK);
|
||||
} catch (TaskNotFoundException e) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
|
||||
} catch (IOException | RuntimeException e) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
package ch.unisg.tapastasks.tasks.adapter.in.messaging.http;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
import ch.unisg.tapastasks.tasks.domain.TaskNotFoundException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
/**
|
||||
* Abstract class that handles events specific to a Task. Events are received via an HTTP PATCH
|
||||
* request for a given task and dispatched to Task event listeners (see {@link TaskEventHttpDispatcher}).
|
||||
* Each listener must implement the abstract method {@link #handleTaskEvent(String, JsonNode)}, which
|
||||
* may require additional event-specific validations.
|
||||
*/
|
||||
public abstract class TaskEventListener {
|
||||
|
||||
/**
|
||||
* This abstract method handles a task event and returns the task after the event was handled.
|
||||
*
|
||||
* @param taskId the identifier of the task for which an event was received
|
||||
* @param payload the JSON Patch payload of the HTTP PATCH request received for this task
|
||||
* @return the task for which the HTTP PATCH request is handled
|
||||
* @throws TaskNotFoundException
|
||||
*/
|
||||
public abstract Task handleTaskEvent(String taskId, JsonNode payload) throws TaskNotFoundException;
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
package ch.unisg.tapastasks.tasks.adapter.in.messaging.http;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonPatchRepresentation;
|
||||
import ch.unisg.tapastasks.tasks.application.handler.TaskExecutedHandler;
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.TaskExecutedEvent;
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.TaskExecutedEventHandler;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Listener for task executed events. A task executed event corresponds to a JSON Patch that attempts
|
||||
* to change the task's status to EXECUTED, may add/replace a service provider, and may also add an
|
||||
* output result. This implementation does not impose that a task executed event includes either the
|
||||
* service provider or an output result (i.e., both can be null).
|
||||
*
|
||||
* See also {@link TaskExecutedEvent}, {@link Task}, and {@link TaskEventHttpDispatcher}.
|
||||
*/
|
||||
public class TaskExecutedEventListenerHttpAdapter extends TaskEventListener {
|
||||
|
||||
public Task handleTaskEvent(String taskId, JsonNode payload) {
|
||||
TaskJsonPatchRepresentation representation = new TaskJsonPatchRepresentation(payload);
|
||||
|
||||
Optional<Task.ServiceProvider> serviceProvider = representation.extractFirstServiceProviderChange();
|
||||
Optional<Task.OutputData> outputData = representation.extractFirstOutputDataAddition();
|
||||
|
||||
TaskExecutedEvent taskExecutedEvent = new TaskExecutedEvent(new Task.TaskId(taskId),
|
||||
serviceProvider, outputData);
|
||||
TaskExecutedEventHandler taskExecutedEventHandler = new TaskExecutedHandler();
|
||||
|
||||
return taskExecutedEventHandler.handleTaskExecuted(taskExecutedEvent);
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
package ch.unisg.tapastasks.tasks.adapter.in.messaging.http;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonPatchRepresentation;
|
||||
import ch.unisg.tapastasks.tasks.application.handler.TaskStartedHandler;
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.TaskStartedEvent;
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.TaskStartedEventHandler;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task.TaskId;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Listener for task started events. A task started event corresponds to a JSON Patch that attempts
|
||||
* to change the task's status to RUNNING and may also add/replace a service provider. This
|
||||
* implementation does not impose that a task started event includes the service provider (i.e.,
|
||||
* can be null).
|
||||
*
|
||||
* See also {@link TaskStartedEvent}, {@link Task}, and {@link TaskEventHttpDispatcher}.
|
||||
*/
|
||||
public class TaskStartedEventListenerHttpAdapter extends TaskEventListener {
|
||||
|
||||
public Task handleTaskEvent(String taskId, JsonNode payload) {
|
||||
TaskJsonPatchRepresentation representation = new TaskJsonPatchRepresentation(payload);
|
||||
Optional<Task.ServiceProvider> serviceProvider = representation.extractFirstServiceProviderChange();
|
||||
|
||||
TaskStartedEvent taskStartedEvent = new TaskStartedEvent(new TaskId(taskId), serviceProvider);
|
||||
TaskStartedEventHandler taskStartedEventHandler = new TaskStartedHandler();
|
||||
|
||||
return taskStartedEventHandler.handleTaskStarted(taskStartedEvent);
|
||||
}
|
||||
}
|
@@ -1,8 +1,12 @@
|
||||
package ch.unisg.tapastasks.tasks.adapter.in.web;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonRepresentation;
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.AddNewTaskToTaskListCommand;
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.AddNewTaskToTaskListUseCase;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -12,29 +16,67 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Controller that handles HTTP requests for creating new tasks. This controller implements the
|
||||
* {@link AddNewTaskToTaskListUseCase} use case using the {@link AddNewTaskToTaskListCommand}.
|
||||
*
|
||||
* A new task is created via an HTTP POST request to the /tasks/ endpoint. The body of the request
|
||||
* contains a JSON-based representation with the "application/task+json" media type defined for this
|
||||
* project. This custom media type allows to capture the semantics of our JSON representations for
|
||||
* tasks.
|
||||
*
|
||||
* If the request is successful, the controller returns an HTTP 201 Created status code and a
|
||||
* representation of the created task with Content-Type "application/task+json". The HTTP response
|
||||
* also include a Location header field that points to the URI of the created task.
|
||||
*/
|
||||
@RestController
|
||||
public class AddNewTaskToTaskListWebController {
|
||||
private final AddNewTaskToTaskListUseCase addNewTaskToTaskListUseCase;
|
||||
|
||||
// Used to retrieve properties from application.properties
|
||||
@Autowired
|
||||
private Environment environment;
|
||||
|
||||
public AddNewTaskToTaskListWebController(AddNewTaskToTaskListUseCase addNewTaskToTaskListUseCase) {
|
||||
this.addNewTaskToTaskListUseCase = addNewTaskToTaskListUseCase;
|
||||
}
|
||||
|
||||
@PostMapping(path = "/tasks/", consumes = {TaskMediaType.TASK_MEDIA_TYPE})
|
||||
public ResponseEntity<String> addNewTaskTaskToTaskList(@RequestBody Task task) {
|
||||
@PostMapping(path = "/tasks/", consumes = {TaskJsonRepresentation.MEDIA_TYPE})
|
||||
public ResponseEntity<String> addNewTaskTaskToTaskList(@RequestBody TaskJsonRepresentation payload) {
|
||||
try {
|
||||
AddNewTaskToTaskListCommand command = new AddNewTaskToTaskListCommand(
|
||||
task.getTaskName(), task.getTaskType()
|
||||
);
|
||||
Task.TaskName taskName = new Task.TaskName(payload.getTaskName());
|
||||
Task.TaskType taskType = new Task.TaskType(payload.getTaskType());
|
||||
|
||||
Task newTask = addNewTaskToTaskListUseCase.addNewTaskToTaskList(command);
|
||||
// If the created task is a delegated task, the representation contains a URI reference
|
||||
// to the original task
|
||||
Optional<Task.OriginalTaskUri> originalTaskUriOptional =
|
||||
(payload.getOriginalTaskUri() == null) ? Optional.empty()
|
||||
: Optional.of(new Task.OriginalTaskUri(payload.getOriginalTaskUri()));
|
||||
|
||||
AddNewTaskToTaskListCommand command = new AddNewTaskToTaskListCommand(taskName, taskType,
|
||||
originalTaskUriOptional);
|
||||
|
||||
Task createdTask = addNewTaskToTaskListUseCase.addNewTaskToTaskList(command);
|
||||
|
||||
// When creating a task, the task's representation may include optional input data
|
||||
if (payload.getInputData() != null) {
|
||||
createdTask.setInputData(new Task.InputData(payload.getInputData()));
|
||||
}
|
||||
|
||||
// Add the content type as a response header
|
||||
HttpHeaders responseHeaders = new HttpHeaders();
|
||||
responseHeaders.add(HttpHeaders.CONTENT_TYPE, TaskMediaType.TASK_MEDIA_TYPE);
|
||||
responseHeaders.add(HttpHeaders.CONTENT_TYPE, TaskJsonRepresentation.MEDIA_TYPE);
|
||||
// Construct and advertise the URI of the newly created task; we retrieve the base URI
|
||||
// from the application.properties file
|
||||
responseHeaders.add(HttpHeaders.LOCATION, environment.getProperty("baseuri")
|
||||
+ "tasks/" + createdTask.getTaskId().getValue());
|
||||
|
||||
return new ResponseEntity<>(TaskMediaType.serialize(newTask), responseHeaders, HttpStatus.CREATED);
|
||||
return new ResponseEntity<>(TaskJsonRepresentation.serialize(createdTask), responseHeaders,
|
||||
HttpStatus.CREATED);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage());
|
||||
} catch (ConstraintViolationException e) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
|
||||
}
|
||||
|
@@ -1,8 +1,10 @@
|
||||
package ch.unisg.tapastasks.tasks.adapter.in.web;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.adapter.in.formats.TaskJsonRepresentation;
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.RetrieveTaskFromTaskListQuery;
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.RetrieveTaskFromTaskListUseCase;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -11,6 +13,11 @@ import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Controller that handles HTTP GET requests for retrieving tasks. This controller implements the
|
||||
* {@link RetrieveTaskFromTaskListUseCase} use case using the {@link RetrieveTaskFromTaskListQuery}
|
||||
* query.
|
||||
*/
|
||||
@RestController
|
||||
public class RetrieveTaskFromTaskListWebController {
|
||||
private final RetrieveTaskFromTaskListUseCase retrieveTaskFromTaskListUseCase;
|
||||
@@ -19,10 +26,17 @@ public class RetrieveTaskFromTaskListWebController {
|
||||
this.retrieveTaskFromTaskListUseCase = retrieveTaskFromTaskListUseCase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a representation of task. Returns HTTP 200 OK if the request is successful with a
|
||||
* representation of the task using the Content-Type "applicatoin/task+json".
|
||||
*
|
||||
* @param taskId the local identifier of the requested task (extracted from the task's URI)
|
||||
* @return a representation of the task if the task exists
|
||||
*/
|
||||
@GetMapping(path = "/tasks/{taskId}")
|
||||
public ResponseEntity<String> retrieveTaskFromTaskList(@PathVariable("taskId") String taskId) {
|
||||
RetrieveTaskFromTaskListQuery command = new RetrieveTaskFromTaskListQuery(new Task.TaskId(taskId));
|
||||
Optional<Task> updatedTaskOpt = retrieveTaskFromTaskListUseCase.retrieveTaskFromTaskList(command);
|
||||
RetrieveTaskFromTaskListQuery query = new RetrieveTaskFromTaskListQuery(new Task.TaskId(taskId));
|
||||
Optional<Task> updatedTaskOpt = retrieveTaskFromTaskListUseCase.retrieveTaskFromTaskList(query);
|
||||
|
||||
// Check if the task with the given identifier exists
|
||||
if (updatedTaskOpt.isEmpty()) {
|
||||
@@ -30,11 +44,16 @@ public class RetrieveTaskFromTaskListWebController {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
// Add the content type as a response header
|
||||
HttpHeaders responseHeaders = new HttpHeaders();
|
||||
responseHeaders.add(HttpHeaders.CONTENT_TYPE, TaskMediaType.TASK_MEDIA_TYPE);
|
||||
try {
|
||||
String taskRepresentation = TaskJsonRepresentation.serialize(updatedTaskOpt.get());
|
||||
|
||||
return new ResponseEntity<>(TaskMediaType.serialize(updatedTaskOpt.get()), responseHeaders,
|
||||
HttpStatus.OK);
|
||||
// Add the content type as a response header
|
||||
HttpHeaders responseHeaders = new HttpHeaders();
|
||||
responseHeaders.add(HttpHeaders.CONTENT_TYPE, TaskJsonRepresentation.MEDIA_TYPE);
|
||||
|
||||
return new ResponseEntity<>(taskRepresentation, responseHeaders, HttpStatus.OK);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,23 +0,0 @@
|
||||
package ch.unisg.tapastasks.tasks.adapter.in.web;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
import ch.unisg.tapastasks.tasks.domain.TaskList;
|
||||
import org.json.JSONObject;
|
||||
|
||||
final public class TaskMediaType {
|
||||
public static final String TASK_MEDIA_TYPE = "application/json";
|
||||
|
||||
public static String serialize(Task task) {
|
||||
JSONObject payload = new JSONObject();
|
||||
|
||||
payload.put("taskId", task.getTaskId().getValue());
|
||||
payload.put("taskName", task.getTaskName().getValue());
|
||||
payload.put("taskType", task.getTaskType().getValue());
|
||||
payload.put("taskState", task.getTaskState().getValue());
|
||||
payload.put("taskListName", TaskList.getTapasTaskList().getTaskListName().getValue());
|
||||
|
||||
return payload.toString();
|
||||
}
|
||||
|
||||
private TaskMediaType() { }
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
package ch.unisg.tapastasks.tasks.application.handler;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.TaskAssignedEvent;
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.TaskAssignedEventHandler;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
import ch.unisg.tapastasks.tasks.domain.TaskList;
|
||||
import ch.unisg.tapastasks.tasks.domain.TaskNotFoundException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class TaskAssignedHandler implements TaskAssignedEventHandler {
|
||||
|
||||
@Override
|
||||
public Task handleTaskAssigned(TaskAssignedEvent taskAssignedEvent) throws TaskNotFoundException {
|
||||
TaskList taskList = TaskList.getTapasTaskList();
|
||||
return taskList.changeTaskStatusToAssigned(taskAssignedEvent.getTaskId(),
|
||||
taskAssignedEvent.getServiceProvider());
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
package ch.unisg.tapastasks.tasks.application.handler;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.TaskExecutedEvent;
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.TaskExecutedEventHandler;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
import ch.unisg.tapastasks.tasks.domain.TaskList;
|
||||
import ch.unisg.tapastasks.tasks.domain.TaskNotFoundException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class TaskExecutedHandler implements TaskExecutedEventHandler {
|
||||
|
||||
@Override
|
||||
public Task handleTaskExecuted(TaskExecutedEvent taskExecutedEvent) throws TaskNotFoundException {
|
||||
TaskList taskList = TaskList.getTapasTaskList();
|
||||
return taskList.changeTaskStatusToExecuted(taskExecutedEvent.getTaskId(),
|
||||
taskExecutedEvent.getServiceProvider(), taskExecutedEvent.getOutputData());
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
package ch.unisg.tapastasks.tasks.application.handler;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.TaskStartedEvent;
|
||||
import ch.unisg.tapastasks.tasks.application.port.in.TaskStartedEventHandler;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
import ch.unisg.tapastasks.tasks.domain.TaskList;
|
||||
import ch.unisg.tapastasks.tasks.domain.TaskNotFoundException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class TaskStartedHandler implements TaskStartedEventHandler {
|
||||
|
||||
@Override
|
||||
public Task handleTaskStarted(TaskStartedEvent taskStartedEvent) throws TaskNotFoundException {
|
||||
TaskList taskList = TaskList.getTapasTaskList();
|
||||
return taskList.changeTaskStatusToRunning(taskStartedEvent.getTaskId(),
|
||||
taskStartedEvent.getServiceProvider());
|
||||
}
|
||||
}
|
@@ -1,23 +1,30 @@
|
||||
package ch.unisg.tapastasks.tasks.application.port.in;
|
||||
|
||||
import ch.unisg.tapastasks.common.SelfValidating;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task.TaskType;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task.TaskName;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
import lombok.Getter;
|
||||
import lombok.Value;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Optional;
|
||||
|
||||
@Value
|
||||
public class AddNewTaskToTaskListCommand extends SelfValidating<AddNewTaskToTaskListCommand> {
|
||||
@NotNull
|
||||
private final TaskName taskName;
|
||||
private final Task.TaskName taskName;
|
||||
|
||||
@NotNull
|
||||
private final TaskType taskType;
|
||||
private final Task.TaskType taskType;
|
||||
|
||||
public AddNewTaskToTaskListCommand(TaskName taskName, TaskType taskType) {
|
||||
@Getter
|
||||
private final Optional<Task.OriginalTaskUri> originalTaskUri;
|
||||
|
||||
public AddNewTaskToTaskListCommand(Task.TaskName taskName, Task.TaskType taskType,
|
||||
Optional<Task.OriginalTaskUri> originalTaskUri) {
|
||||
this.taskName = taskName;
|
||||
this.taskType = taskType;
|
||||
this.originalTaskUri = originalTaskUri;
|
||||
|
||||
this.validateSelf();
|
||||
}
|
||||
}
|
||||
|
@@ -5,5 +5,5 @@ import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface RetrieveTaskFromTaskListUseCase {
|
||||
Optional<Task> retrieveTaskFromTaskList(RetrieveTaskFromTaskListQuery command);
|
||||
Optional<Task> retrieveTaskFromTaskList(RetrieveTaskFromTaskListQuery query);
|
||||
}
|
||||
|
@@ -0,0 +1,25 @@
|
||||
package ch.unisg.tapastasks.tasks.application.port.in;
|
||||
|
||||
import ch.unisg.tapastasks.common.SelfValidating;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
import lombok.Getter;
|
||||
import lombok.Value;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Optional;
|
||||
|
||||
@Value
|
||||
public class TaskAssignedEvent extends SelfValidating<TaskAssignedEvent> {
|
||||
@NotNull
|
||||
private final Task.TaskId taskId;
|
||||
|
||||
@Getter
|
||||
private final Optional<Task.ServiceProvider> serviceProvider;
|
||||
|
||||
public TaskAssignedEvent(Task.TaskId taskId, Optional<Task.ServiceProvider> serviceProvider) {
|
||||
this.taskId = taskId;
|
||||
this.serviceProvider = serviceProvider;
|
||||
|
||||
this.validateSelf();
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
package ch.unisg.tapastasks.tasks.application.port.in;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
|
||||
public interface TaskAssignedEventHandler {
|
||||
|
||||
Task handleTaskAssigned(TaskAssignedEvent taskStartedEvent);
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
package ch.unisg.tapastasks.tasks.application.port.in;
|
||||
|
||||
import ch.unisg.tapastasks.common.SelfValidating;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task.*;
|
||||
import lombok.Getter;
|
||||
import lombok.Value;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Optional;
|
||||
|
||||
@Value
|
||||
public class TaskExecutedEvent extends SelfValidating<TaskExecutedEvent> {
|
||||
@NotNull
|
||||
private final TaskId taskId;
|
||||
|
||||
@Getter
|
||||
private final Optional<ServiceProvider> serviceProvider;
|
||||
|
||||
@Getter
|
||||
private final Optional<OutputData> outputData;
|
||||
|
||||
public TaskExecutedEvent(TaskId taskId, Optional<ServiceProvider> serviceProvider,
|
||||
Optional<OutputData> outputData) {
|
||||
this.taskId = taskId;
|
||||
|
||||
this.serviceProvider = serviceProvider;
|
||||
this.outputData = outputData;
|
||||
|
||||
this.validateSelf();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
package ch.unisg.tapastasks.tasks.application.port.in;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
|
||||
public interface TaskExecutedEventHandler {
|
||||
|
||||
Task handleTaskExecuted(TaskExecutedEvent taskExecutedEvent);
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
package ch.unisg.tapastasks.tasks.application.port.in;
|
||||
|
||||
import ch.unisg.tapastasks.common.SelfValidating;
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
import lombok.Getter;
|
||||
import lombok.Value;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Optional;
|
||||
|
||||
@Value
|
||||
public class TaskStartedEvent extends SelfValidating<TaskStartedEvent> {
|
||||
@NotNull
|
||||
private final Task.TaskId taskId;
|
||||
|
||||
@Getter
|
||||
private final Optional<Task.ServiceProvider> serviceProvider;
|
||||
|
||||
public TaskStartedEvent(Task.TaskId taskId, Optional<Task.ServiceProvider> serviceProvider) {
|
||||
this.taskId = taskId;
|
||||
this.serviceProvider = serviceProvider;
|
||||
|
||||
this.validateSelf();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
package ch.unisg.tapastasks.tasks.application.port.in;
|
||||
|
||||
import ch.unisg.tapastasks.tasks.domain.Task;
|
||||
|
||||
public interface TaskStartedEventHandler {
|
||||
|
||||
Task handleTaskStarted(TaskStartedEvent taskStartedEvent);
|
||||
}
|
@@ -21,7 +21,13 @@ public class AddNewTaskToTaskListService implements AddNewTaskToTaskListUseCase
|
||||
@Override
|
||||
public Task addNewTaskToTaskList(AddNewTaskToTaskListCommand command) {
|
||||
TaskList taskList = TaskList.getTapasTaskList();
|
||||
Task newTask = taskList.addNewTaskWithNameAndType(command.getTaskName(), command.getTaskType());
|
||||
|
||||
Task newTask = (command.getOriginalTaskUri().isPresent()) ?
|
||||
// Create a delegated task that points back to the original task
|
||||
taskList.addNewTaskWithNameAndTypeAndOriginalTaskUri(command.getTaskName(),
|
||||
command.getTaskType(), command.getOriginalTaskUri().get())
|
||||
// Create an original task
|
||||
: taskList.addNewTaskWithNameAndType(command.getTaskName(), command.getTaskType());
|
||||
|
||||
//Here we are using the application service to emit the domain event to the outside of the bounded context.
|
||||
//This event should be considered as a light-weight "integration event" to communicate with other services.
|
||||
|
@@ -15,8 +15,8 @@ import java.util.Optional;
|
||||
@Transactional
|
||||
public class RetrieveTaskFromTaskListService implements RetrieveTaskFromTaskListUseCase {
|
||||
@Override
|
||||
public Optional<Task> retrieveTaskFromTaskList(RetrieveTaskFromTaskListQuery command) {
|
||||
public Optional<Task> retrieveTaskFromTaskList(RetrieveTaskFromTaskListQuery query) {
|
||||
TaskList taskList = TaskList.getTapasTaskList();
|
||||
return taskList.retrieveTaskById(command.getTaskId());
|
||||
return taskList.retrieveTaskById(query.getTaskId());
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ import java.util.UUID;
|
||||
|
||||
/**This is a domain entity**/
|
||||
public class Task {
|
||||
public enum State {
|
||||
public enum Status {
|
||||
OPEN, ASSIGNED, RUNNING, EXECUTED
|
||||
}
|
||||
|
||||
@@ -22,38 +22,81 @@ public class Task {
|
||||
private final TaskType taskType;
|
||||
|
||||
@Getter
|
||||
private TaskState taskState;
|
||||
private final OriginalTaskUri originalTaskUri;
|
||||
|
||||
@Getter @Setter
|
||||
private TaskStatus taskStatus;
|
||||
|
||||
@Getter @Setter
|
||||
private ServiceProvider provider;
|
||||
|
||||
@Getter @Setter
|
||||
private InputData inputData;
|
||||
|
||||
@Getter @Setter
|
||||
private OutputData outputData;
|
||||
|
||||
public Task(TaskName taskName, TaskType taskType, OriginalTaskUri taskUri) {
|
||||
this.taskId = new TaskId(UUID.randomUUID().toString());
|
||||
|
||||
public Task(TaskName taskName, TaskType taskType) {
|
||||
this.taskName = taskName;
|
||||
this.taskType = taskType;
|
||||
this.taskState = new TaskState(State.OPEN);
|
||||
this.taskId = new TaskId(UUID.randomUUID().toString());
|
||||
this.originalTaskUri = taskUri;
|
||||
|
||||
this.taskStatus = new TaskStatus(Status.OPEN);
|
||||
|
||||
this.inputData = null;
|
||||
this.outputData = null;
|
||||
}
|
||||
|
||||
protected static Task createTaskWithNameAndType(TaskName name, TaskType type) {
|
||||
//This is a simple debug message to see that the request has reached the right method in the core
|
||||
System.out.println("New Task: " + name.getValue() + " " + type.getValue());
|
||||
return new Task(name,type);
|
||||
return new Task(name, type, null);
|
||||
}
|
||||
|
||||
protected static Task createTaskWithNameAndTypeAndOriginalTaskUri(TaskName name, TaskType type,
|
||||
OriginalTaskUri originalTaskUri) {
|
||||
return new Task(name, type, originalTaskUri);
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class TaskId {
|
||||
private String value;
|
||||
String value;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class TaskName {
|
||||
private String value;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class TaskState {
|
||||
private State value;
|
||||
String value;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class TaskType {
|
||||
private String value;
|
||||
String value;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class OriginalTaskUri {
|
||||
String value;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class TaskStatus {
|
||||
Status value;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class ServiceProvider {
|
||||
String value;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class InputData {
|
||||
String value;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class OutputData {
|
||||
String value;
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,6 @@ package ch.unisg.tapastasks.tasks.domain;
|
||||
import lombok.Getter;
|
||||
import lombok.Value;
|
||||
|
||||
import javax.swing.text.html.Option;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -20,7 +19,7 @@ public class TaskList {
|
||||
|
||||
//Note: We do not care about the management of task lists, there is only one within this service
|
||||
//--> using the Singleton pattern here to make lives easy; we will later load it from a repo
|
||||
//TODO change "tutors" to your group name ("groupx")
|
||||
//TODO: change "tutors" to your group name ("groupx")
|
||||
private static final TaskList taskList = new TaskList(new TaskListName("tapas-tasks-tutors"));
|
||||
|
||||
private TaskList(TaskListName taskListName) {
|
||||
@@ -35,14 +34,27 @@ public class TaskList {
|
||||
//Only the aggregate root is allowed to create new tasks and add them to the task list.
|
||||
//Note: Here we could add some sophisticated invariants/business rules that the aggregate root checks
|
||||
public Task addNewTaskWithNameAndType(Task.TaskName name, Task.TaskType type) {
|
||||
Task newTask = Task.createTaskWithNameAndType(name,type);
|
||||
listOfTasks.value.add(newTask);
|
||||
//This is a simple debug message to see that the task list is growing with each new request
|
||||
System.out.println("Number of tasks: "+listOfTasks.value.size());
|
||||
Task newTask = Task.createTaskWithNameAndType(name, type);
|
||||
this.addNewTaskToList(newTask);
|
||||
|
||||
return newTask;
|
||||
}
|
||||
|
||||
public Task addNewTaskWithNameAndTypeAndOriginalTaskUri(Task.TaskName name, Task.TaskType type,
|
||||
Task.OriginalTaskUri originalTaskUri) {
|
||||
Task newTask = Task.createTaskWithNameAndTypeAndOriginalTaskUri(name, type, originalTaskUri);
|
||||
this.addNewTaskToList(newTask);
|
||||
|
||||
return newTask;
|
||||
}
|
||||
|
||||
private void addNewTaskToList(Task newTask) {
|
||||
//Here we would also publish a domain event to other entities in the core interested in this event.
|
||||
//However, we skip this here as it makes the core even more complex (e.g., we have to implement a light-weight
|
||||
//domain event publisher and subscribers (see "Implementing Domain-Driven Design by V. Vernon, pp. 296ff).
|
||||
return newTask;
|
||||
listOfTasks.value.add(newTask);
|
||||
//This is a simple debug message to see that the task list is growing with each new request
|
||||
System.out.println("Number of tasks: " + listOfTasks.value.size());
|
||||
}
|
||||
|
||||
public Optional<Task> retrieveTaskById(Task.TaskId id) {
|
||||
@@ -55,6 +67,43 @@ public class TaskList {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Task changeTaskStatusToAssigned(Task.TaskId id, Optional<Task.ServiceProvider> serviceProvider)
|
||||
throws TaskNotFoundException {
|
||||
return changeTaskStatus(id, new Task.TaskStatus(Task.Status.ASSIGNED), serviceProvider, Optional.empty());
|
||||
}
|
||||
|
||||
public Task changeTaskStatusToRunning(Task.TaskId id, Optional<Task.ServiceProvider> serviceProvider)
|
||||
throws TaskNotFoundException {
|
||||
return changeTaskStatus(id, new Task.TaskStatus(Task.Status.RUNNING), serviceProvider, Optional.empty());
|
||||
}
|
||||
|
||||
public Task changeTaskStatusToExecuted(Task.TaskId id, Optional<Task.ServiceProvider> serviceProvider,
|
||||
Optional<Task.OutputData> outputData) throws TaskNotFoundException {
|
||||
return changeTaskStatus(id, new Task.TaskStatus(Task.Status.EXECUTED), serviceProvider, outputData);
|
||||
}
|
||||
|
||||
private Task changeTaskStatus(Task.TaskId id, Task.TaskStatus status, Optional<Task.ServiceProvider> serviceProvider,
|
||||
Optional<Task.OutputData> outputData) {
|
||||
Optional<Task> taskOpt = retrieveTaskById(id);
|
||||
|
||||
if (taskOpt.isEmpty()) {
|
||||
throw new TaskNotFoundException();
|
||||
}
|
||||
|
||||
Task task = taskOpt.get();
|
||||
task.setTaskStatus(status);
|
||||
|
||||
if (serviceProvider.isPresent()) {
|
||||
task.setProvider(serviceProvider.get());
|
||||
}
|
||||
|
||||
if (outputData.isPresent()) {
|
||||
task.setOutputData(outputData.get());
|
||||
}
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class TaskListName {
|
||||
private String value;
|
||||
@@ -64,5 +113,4 @@ public class TaskList {
|
||||
public static class ListOfTasks {
|
||||
private List<Task> value;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,3 @@
|
||||
package ch.unisg.tapastasks.tasks.domain;
|
||||
|
||||
public class TaskNotFoundException extends RuntimeException { }
|
@@ -1 +1,2 @@
|
||||
server.port=8081
|
||||
baseuri=https://tapas-tasks.86-119-34-23.nip.io/
|
||||
|
Reference in New Issue
Block a user