assignment service + executor pool improvments

This commit is contained in:
2021-10-17 00:31:48 +02:00
parent 4a85548a9e
commit a61f111879
43 changed files with 849 additions and 134 deletions

View File

@@ -1,12 +0,0 @@
package ch.unisg.assignment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@RequestMapping("/")
public String index() {
return "Hello World! Assignment";
}
}

View File

@@ -0,0 +1,36 @@
package ch.unisg.assignment.assignment.adapter.in.web;
import javax.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import ch.unisg.assignment.assignment.application.port.in.ApplyForTaskCommand;
import ch.unisg.assignment.assignment.application.port.in.ApplyForTaskUseCase;
import ch.unisg.assignment.assignment.domain.ExecutorInfo;
import ch.unisg.assignment.assignment.domain.Task;
@RestController
public class ApplyForTaskController {
private final ApplyForTaskUseCase applyForTaskUseCase;
public ApplyForTaskController(ApplyForTaskUseCase applyForTaskUseCase) {
this.applyForTaskUseCase = applyForTaskUseCase;
}
@PostMapping(path = "/task/apply", consumes = {"application/json"})
public Task applyForTask(@RequestBody ExecutorInfo executorInfo) {
try {
ApplyForTaskCommand command = new ApplyForTaskCommand(executorInfo.getExecutorType(),
executorInfo.getIp(), executorInfo.getPort());
return applyForTaskUseCase.applyForTask(command);
} catch (ConstraintViolationException e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
}
}
}

View File

@@ -0,0 +1,41 @@
package ch.unisg.assignment.assignment.adapter.in.web;
import javax.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import ch.unisg.assignment.assignment.application.port.in.NewTaskCommand;
import ch.unisg.assignment.assignment.application.port.in.NewTaskUseCase;
import ch.unisg.assignment.assignment.domain.Task;
@RestController
public class NewTaskController {
private final NewTaskUseCase newTaskUseCase;
public NewTaskController(NewTaskUseCase newTaskUseCase) {
this.newTaskUseCase = newTaskUseCase;
}
@PostMapping(path = "/task", consumes = {"application/json"})
public ResponseEntity<Void> addNewTaskTaskToTaskList(@RequestBody Task task) {
try {
NewTaskCommand command = new NewTaskCommand(
task.getTaskID(), task.getTaskType()
);
boolean success = newTaskUseCase.addNewTaskToQueue(command);
if (success) {
return new ResponseEntity<>(HttpStatus.CREATED);
}
return new ResponseEntity<>(HttpStatus.CONFLICT);
} catch (ConstraintViolationException e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
}
}
}

View File

@@ -0,0 +1,34 @@
package ch.unisg.assignment.assignment.adapter.in.web;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import ch.unisg.assignment.assignment.application.port.in.TaskCompletedCommand;
import ch.unisg.assignment.assignment.application.port.in.TaskCompletedUseCase;
import ch.unisg.assignment.assignment.domain.Task;
@RestController
public class TaskCompletedController {
private final TaskCompletedUseCase taskCompletedUseCase;
public TaskCompletedController(TaskCompletedUseCase taskCompletedUseCase) {
this.taskCompletedUseCase = taskCompletedUseCase;
}
@PostMapping(path = "/task/completed", consumes = {"application/json"})
public ResponseEntity<Void> addNewTaskTaskToTaskList(@RequestBody Task task) {
TaskCompletedCommand command = new TaskCompletedCommand(task.getTaskID(), task.getTaskType(),
task.getStatus(), task.getResult());
taskCompletedUseCase.taskCompleted(command);
return new ResponseEntity<>(HttpStatus.OK);
}
}

View File

@@ -0,0 +1,40 @@
package ch.unisg.assignment.assignment.adapter.out.web;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import ch.unisg.assignment.assignment.application.port.out.NewTaskEventPort;
import ch.unisg.assignment.assignment.domain.NewTaskEvent;
@Component
@Primary
public class PublishNewTaskEventAdapter implements NewTaskEventPort {
String server = "http://127.0.0.1:8085";
@Override
public void publishNewTaskEvent(NewTaskEvent event) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(server + "/newtask/" + event.taskType))
.GET()
.build();
try {
client.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
e.printStackTrace();
// Restore interrupted state...
Thread.currentThread().interrupt();
}
}
}

View File

@@ -0,0 +1,46 @@
package ch.unisg.assignment.assignment.adapter.out.web;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import org.json.JSONObject;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import ch.unisg.assignment.assignment.application.port.out.TaskAssignedEventPort;
import ch.unisg.assignment.assignment.domain.TaskAssignedEvent;
@Component
@Primary
public class PublishTaskAssignedEventAdapter implements TaskAssignedEventPort {
String server = "http://127.0.0.1:8085";
@Override
public void publishTaskAssignedEvent(TaskAssignedEvent event) {
String body = new JSONObject()
.put("taskId", event.taskID)
.toString();
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(server + "/tasks/completeTask"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
try {
client.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
e.printStackTrace();
// Restore interrupted state...
Thread.currentThread().interrupt();
}
}
}

View File

@@ -0,0 +1,49 @@
package ch.unisg.assignment.assignment.adapter.out.web;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import org.json.JSONObject;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import ch.unisg.assignment.assignment.application.port.out.TaskCompletedEventPort;
import ch.unisg.assignment.assignment.domain.TaskCompletedEvent;
@Component
@Primary
public class PublishTaskCompletedEventAdapter implements TaskCompletedEventPort {
String server = "http://127.0.0.1:8081";
@Override
public void publishTaskCompleted(TaskCompletedEvent event) {
String body = new JSONObject()
.put("taskId", event.taskID)
.put("status", event.status)
.put("taskResult", event.result)
.toString();
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(server + "/tasks/completeTask"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
try {
client.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
e.printStackTrace();
// Restore interrupted state...
Thread.currentThread().interrupt();
}
}
}

View File

@@ -0,0 +1,29 @@
package ch.unisg.assignment.assignment.application.port.in;
import javax.validation.constraints.NotNull;
import ch.unisg.assignment.common.SelfValidating;
import lombok.EqualsAndHashCode;
import lombok.Value;
@Value
@EqualsAndHashCode(callSuper=false)
public class ApplyForTaskCommand extends SelfValidating<ApplyForTaskCommand>{
@NotNull
private final String taskType;
@NotNull
private final String executorIP;
@NotNull
private final int executorPort;
public ApplyForTaskCommand(String taskType, String executorIP, int executorPort) {
this.taskType = taskType;
this.executorIP = executorIP;
this.executorPort = executorPort;
this.validateSelf();
}
}

View File

@@ -0,0 +1,7 @@
package ch.unisg.assignment.assignment.application.port.in;
import ch.unisg.assignment.assignment.domain.Task;
public interface ApplyForTaskUseCase {
Task applyForTask(ApplyForTaskCommand applyForTaskCommand);
}

View File

@@ -0,0 +1,23 @@
package ch.unisg.assignment.assignment.application.port.in;
import javax.validation.constraints.NotNull;
import ch.unisg.assignment.common.SelfValidating;
import lombok.Value;
@Value
public class NewTaskCommand extends SelfValidating<NewTaskCommand> {
@NotNull
private final String taskID;
@NotNull
private final String taskType;
public NewTaskCommand(String taskID, String taskType) {
this.taskID = taskID;
this.taskType = taskType;
this.validateSelf();
}
}

View File

@@ -0,0 +1,5 @@
package ch.unisg.assignment.assignment.application.port.in;
public interface NewTaskUseCase {
boolean addNewTaskToQueue(NewTaskCommand newTaskCommand);
}

View File

@@ -0,0 +1,33 @@
package ch.unisg.assignment.assignment.application.port.in;
import javax.validation.constraints.NotNull;
import ch.unisg.assignment.common.SelfValidating;
import lombok.EqualsAndHashCode;
import lombok.Value;
@Value
@EqualsAndHashCode(callSuper=false)
public class TaskCompletedCommand extends SelfValidating<TaskCompletedCommand>{
@NotNull
private final String taskID;
@NotNull
private final String taskType;
@NotNull
private final String taskStatus;
@NotNull
private final String taskResult;
public TaskCompletedCommand(String taskID, String taskType, String taskStatus, String taskResult) {
this.taskID = taskID;
this.taskType = taskType;
this.taskStatus = taskStatus;
this.taskResult = taskResult;
this.validateSelf();
}
}

View File

@@ -0,0 +1,5 @@
package ch.unisg.assignment.assignment.application.port.in;
public interface TaskCompletedUseCase {
void taskCompleted(TaskCompletedCommand taskCompletedCommand);
}

View File

@@ -0,0 +1,7 @@
package ch.unisg.assignment.assignment.application.port.out;
import ch.unisg.assignment.assignment.domain.NewTaskEvent;
public interface NewTaskEventPort {
void publishNewTaskEvent(NewTaskEvent event);
}

View File

@@ -0,0 +1,7 @@
package ch.unisg.assignment.assignment.application.port.out;
import ch.unisg.assignment.assignment.domain.TaskAssignedEvent;
public interface TaskAssignedEventPort {
void publishTaskAssignedEvent(TaskAssignedEvent taskAssignedEvent);
}

View File

@@ -0,0 +1,7 @@
package ch.unisg.assignment.assignment.application.port.out;
import ch.unisg.assignment.assignment.domain.TaskCompletedEvent;
public interface TaskCompletedEventPort {
void publishTaskCompleted(TaskCompletedEvent event);
}

View File

@@ -0,0 +1,34 @@
package ch.unisg.assignment.assignment.application.service;
import javax.transaction.Transactional;
import org.springframework.stereotype.Component;
import ch.unisg.assignment.assignment.application.port.in.ApplyForTaskCommand;
import ch.unisg.assignment.assignment.application.port.in.ApplyForTaskUseCase;
import ch.unisg.assignment.assignment.application.port.out.TaskAssignedEventPort;
import ch.unisg.assignment.assignment.domain.Roster;
import ch.unisg.assignment.assignment.domain.Task;
import ch.unisg.assignment.assignment.domain.TaskAssignedEvent;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Component
@Transactional
public class ApplyForTaskService implements ApplyForTaskUseCase {
private final TaskAssignedEventPort taskAssignedEventPort;
@Override
public Task applyForTask(ApplyForTaskCommand command) {
Task task = Roster.getInstance().assignTaskToExecutor(command.getTaskType(),
command.getExecutorIP(), command.getExecutorPort());
if (task != null) {
taskAssignedEventPort.publishTaskAssignedEvent(new TaskAssignedEvent(task.getTaskID()));
}
return task;
}
}

View File

@@ -0,0 +1,46 @@
package ch.unisg.assignment.assignment.application.service;
import java.util.Arrays;
import java.util.List;
import javax.transaction.Transactional;
import org.springframework.stereotype.Component;
import ch.unisg.assignment.assignment.application.port.in.NewTaskCommand;
import ch.unisg.assignment.assignment.application.port.in.NewTaskUseCase;
import ch.unisg.assignment.assignment.application.port.out.NewTaskEventPort;
import ch.unisg.assignment.assignment.domain.NewTaskEvent;
import ch.unisg.assignment.assignment.domain.Roster;
import ch.unisg.assignment.assignment.domain.Task;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Component
@Transactional
public class NewTaskService implements NewTaskUseCase {
private final NewTaskEventPort newTaskEventPort;
@Override
public boolean addNewTaskToQueue(NewTaskCommand command) {
// TODO Get availableTaskTypes from executor pool
List<String> availableTaskTypes = Arrays.asList("addition", "robot");
if (!availableTaskTypes.contains(command.getTaskType())) {
return false;
}
Task task = new Task(command.getTaskID(), command.getTaskType());
Roster.getInstance().addTaskToQueue(task);
// TODO this event should be in the roster function xyz
NewTaskEvent newTaskEvent = new NewTaskEvent(task.getTaskType());
newTaskEventPort.publishNewTaskEvent(newTaskEvent);
return true;
}
}

View File

@@ -0,0 +1,31 @@
package ch.unisg.assignment.assignment.application.service;
import javax.transaction.Transactional;
import org.springframework.stereotype.Component;
import ch.unisg.assignment.assignment.application.port.in.TaskCompletedCommand;
import ch.unisg.assignment.assignment.application.port.in.TaskCompletedUseCase;
import ch.unisg.assignment.assignment.application.port.out.TaskCompletedEventPort;
import ch.unisg.assignment.assignment.domain.Roster;
import ch.unisg.assignment.assignment.domain.TaskCompletedEvent;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Component
@Transactional
public class TaskCompletedService implements TaskCompletedUseCase {
private final TaskCompletedEventPort taskCompletedEventPort;
@Override
public void taskCompleted(TaskCompletedCommand command) {
Roster.getInstance().taskCompleted(command.getTaskID());
taskCompletedEventPort.publishTaskCompleted(new TaskCompletedEvent(command.getTaskID(),
command.getTaskStatus(), command.getTaskResult()));
}
}

View File

@@ -0,0 +1,18 @@
package ch.unisg.assignment.assignment.domain;
import lombok.Getter;
import lombok.Setter;
public class ExecutorInfo {
@Getter
@Setter
private String ip;
@Getter
@Setter
private int port;
@Getter
@Setter
private String executorType;
}

View File

@@ -0,0 +1,9 @@
package ch.unisg.assignment.assignment.domain;
public class NewTaskEvent {
public String taskType;
public NewTaskEvent(String taskType) {
this.taskType = taskType;
}
}

View File

@@ -0,0 +1,49 @@
package ch.unisg.assignment.assignment.domain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
public class Roster {
private static final Roster roster = new Roster();
private HashMap<String, ArrayList<Task>> queues = new HashMap<>();
private HashMap<String, RosterItem> rosterMap = new HashMap<>();
public static Roster getInstance() {
return roster;
}
private Roster() {}
public void addTaskToQueue(Task task) {
if (queues.containsKey(task.getTaskType().toUpperCase())) {
queues.get(task.getTaskType().toUpperCase()).add(task);
} else {
queues.put(task.getTaskType().toUpperCase(), new ArrayList<>(Arrays.asList(task)));
}
}
public Task assignTaskToExecutor(String taskType, String executorIP, int executorPort) {
if (!queues.containsKey(taskType.toUpperCase())) {
return null;
}
if (queues.get(taskType.toUpperCase()).isEmpty()) {
return null;
}
Task task = queues.get(taskType.toUpperCase()).remove(0);
rosterMap.put(task.getTaskID(), new RosterItem(task.getTaskID(), task.getTaskType(),
executorIP, executorPort));
return task;
}
public void taskCompleted(String taskID) {
rosterMap.remove(taskID);
}
}

View File

@@ -0,0 +1,27 @@
package ch.unisg.assignment.assignment.domain;
import lombok.Getter;
public class RosterItem {
@Getter
private String taskID;
@Getter
private String taskType;
@Getter
private String executorIP;
@Getter
private int executorPort;
public RosterItem(String taskID, String taskType, String executorIP, int executorPort) {
this.taskID = taskID;
this.taskType = taskType;
this.executorIP = executorIP;
this.executorPort = executorPort;
}
}

View File

@@ -0,0 +1,27 @@
package ch.unisg.assignment.assignment.domain;
import lombok.Getter;
import lombok.Setter;
public class Task {
@Getter
private String taskID;
@Getter
private String taskType;
@Getter
@Setter
private String result;
@Getter
@Setter
private String status;
public Task(String taskID, String taskType) {
this.taskID = taskID;
this.taskType = taskType;
}
}

View File

@@ -0,0 +1,9 @@
package ch.unisg.assignment.assignment.domain;
public class TaskAssignedEvent {
public String taskID;
public TaskAssignedEvent(String taskID) {
this.taskID = taskID;
}
}

View File

@@ -0,0 +1,15 @@
package ch.unisg.assignment.assignment.domain;
public class TaskCompletedEvent {
public String taskID;
public String status;
public String result;
public TaskCompletedEvent(String taskID, String status, String result) {
this.taskID = taskID;
this.status = status;
this.result = result;
}
}

View File

@@ -0,0 +1,31 @@
package ch.unisg.assignment.common;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.Set;
public abstract class SelfValidating<T> {
private Validator validator;
protected SelfValidating() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
/**
* Evaluates all Bean Validations on the attributes of this
* instance.
*/
protected void validateSelf() {
@SuppressWarnings("unchecked")
Set<ConstraintViolation<T>> violations = validator.validate((T) this);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
}
}

View File

@@ -1 +1 @@
server.port=8081
server.port=8082