Merge pull request #23 from SCS-ASSE-FS21-Group1/assignment

assignment service + executor-base improvements
This commit is contained in:
reynisson 2021-10-17 13:57:43 +02:00 committed by GitHub
commit 94766b6fd4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 1026 additions and 182 deletions

View File

@ -37,7 +37,7 @@ jobs:
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2 restore-keys: ${{ runner.os }}-m2
- name: Build executorBase - name: Build executorBase
run: mvn -f executor-base/pom.xml -B verify run: mvn -f executor-base/pom.xml -B install
- name: Build and analyze - name: Build and analyze
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -37,7 +37,7 @@ jobs:
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2 restore-keys: ${{ runner.os }}-m2
- name: Build executorBase - name: Build executorBase
run: mvn -f executor-base/pom.xml -B verify run: mvn -f executor-base/pom.xml -B install
- name: Build and analyze - name: Build and analyze
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -30,6 +30,11 @@
<scope>runtime</scope> <scope>runtime</scope>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
@ -40,6 +45,24 @@
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>javax.transaction-api</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20210307</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

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,29 @@
package ch.unisg.assignment.assignment.adapter.in.web;
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.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) {
ApplyForTaskCommand command = new ApplyForTaskCommand(executorInfo.getExecutorType(),
executorInfo.getIp(), executorInfo.getPort());
return applyForTaskUseCase.applyForTask(command);
}
}

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.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> newTaskController(@RequestBody Task task) {
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);
}
}

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,31 @@
package ch.unisg.assignment.assignment.adapter.in.web;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import ch.unisg.assignment.common.exception.ErrorResponse;
import ch.unisg.assignment.common.exception.InvalidIP4Exception;
import ch.unisg.assignment.common.exception.PortOutOfRangeException;
@ControllerAdvice
public class WebControllerExceptionHandler {
@ExceptionHandler(PortOutOfRangeException.class)
public ResponseEntity<ErrorResponse> handleException(PortOutOfRangeException e){
ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST, e.getLocalizedMessage());
return new ResponseEntity<>(error, error.getHttpStatus());
}
@ExceptionHandler(InvalidIP4Exception.class)
public ResponseEntity<ErrorResponse> handleException(InvalidIP4Exception e){
ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST, e.getLocalizedMessage());
return new ResponseEntity<>(error, error.getHttpStatus());
}
}

View File

@ -0,0 +1,44 @@
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 java.util.logging.Level;
import java.util.logging.Logger;
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.event.NewTaskEvent;
@Component
@Primary
public class PublishNewTaskEventAdapter implements NewTaskEventPort {
String server = "http://127.0.0.1:8085";
Logger logger = Logger.getLogger(PublishNewTaskEventAdapter.class.getName());
@Override
public void publishNewTaskEvent(NewTaskEvent event) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(server + "/newtask/" + event.taskType.getValue()))
.GET()
.build();
try {
client.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
logger.log(Level.SEVERE, e.getLocalizedMessage(), e);
// Restore interrupted state...
Thread.currentThread().interrupt();
}
}
}

View File

@ -0,0 +1,50 @@
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 java.util.logging.Level;
import java.util.logging.Logger;
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.event.TaskAssignedEvent;
@Component
@Primary
public class PublishTaskAssignedEventAdapter implements TaskAssignedEventPort {
String server = "http://127.0.0.1:8085";
Logger logger = Logger.getLogger(PublishTaskAssignedEventAdapter.class.getName());
@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) {
logger.log(Level.SEVERE, e.getLocalizedMessage(), e);
// Restore interrupted state...
Thread.currentThread().interrupt();
}
}
}

View File

@ -0,0 +1,53 @@
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 java.util.logging.Level;
import java.util.logging.Logger;
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.event.TaskCompletedEvent;
@Component
@Primary
public class PublishTaskCompletedEventAdapter implements TaskCompletedEventPort {
String server = "http://127.0.0.1:8081";
Logger logger = Logger.getLogger(PublishTaskCompletedEventAdapter.class.getName());
@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) {
logger.log(Level.SEVERE, e.getLocalizedMessage(), e);
// Restore interrupted state...
Thread.currentThread().interrupt();
}
}
}

View File

@ -0,0 +1,32 @@
package ch.unisg.assignment.assignment.application.port.in;
import javax.validation.constraints.NotNull;
import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType;
import ch.unisg.assignment.assignment.domain.valueobject.IP4Adress;
import ch.unisg.assignment.assignment.domain.valueobject.Port;
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 ExecutorType taskType;
@NotNull
private final IP4Adress executorIP;
@NotNull
private final Port executorPort;
public ApplyForTaskCommand(ExecutorType taskType, IP4Adress executorIP, Port 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,25 @@
package ch.unisg.assignment.assignment.application.port.in;
import javax.validation.constraints.NotNull;
import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType;
import ch.unisg.assignment.common.SelfValidating;
import lombok.EqualsAndHashCode;
import lombok.Value;
@Value
@EqualsAndHashCode(callSuper=false)
public class NewTaskCommand extends SelfValidating<NewTaskCommand> {
@NotNull
private final String taskID;
@NotNull
private final ExecutorType taskType;
public NewTaskCommand(String taskID, ExecutorType 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,34 @@
package ch.unisg.assignment.assignment.application.port.in;
import javax.validation.constraints.NotNull;
import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType;
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 ExecutorType taskType;
@NotNull
private final String taskStatus;
@NotNull
private final String taskResult;
public TaskCompletedCommand(String taskID, ExecutorType 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.event.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.event.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.event.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.event.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.Roster;
import ch.unisg.assignment.assignment.domain.Task;
import ch.unisg.assignment.assignment.domain.event.NewTaskEvent;
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().getValue())) {
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.event.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,21 @@
package ch.unisg.assignment.assignment.domain;
import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType;
import ch.unisg.assignment.assignment.domain.valueobject.IP4Adress;
import ch.unisg.assignment.assignment.domain.valueobject.Port;
import lombok.Getter;
import lombok.Setter;
public class ExecutorInfo {
@Getter
@Setter
private IP4Adress ip;
@Getter
@Setter
private Port port;
@Getter
@Setter
private ExecutorType executorType;
}

View File

@ -0,0 +1,53 @@
package ch.unisg.assignment.assignment.domain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType;
import ch.unisg.assignment.assignment.domain.valueobject.IP4Adress;
import ch.unisg.assignment.assignment.domain.valueobject.Port;
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().getValue())) {
queues.get(task.getTaskType().getValue()).add(task);
} else {
queues.put(task.getTaskType().getValue(), new ArrayList<>(Arrays.asList(task)));
}
}
public Task assignTaskToExecutor(ExecutorType taskType, IP4Adress executorIP, Port executorPort) {
if (!queues.containsKey(taskType.getValue())) {
return null;
}
if (queues.get(taskType.getValue()).isEmpty()) {
return null;
}
Task task = queues.get(taskType.getValue()).remove(0);
rosterMap.put(task.getTaskID(), new RosterItem(task.getTaskID(),
task.getTaskType().getValue(), executorIP, executorPort));
return task;
}
public void taskCompleted(String taskID) {
rosterMap.remove(taskID);
}
}

View File

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

View File

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

View File

@ -0,0 +1,11 @@
package ch.unisg.assignment.assignment.domain.event;
import ch.unisg.assignment.assignment.domain.valueobject.ExecutorType;
public class NewTaskEvent {
public final ExecutorType taskType;
public NewTaskEvent(ExecutorType taskType) {
this.taskType = taskType;
}
}

View File

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

View File

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

View File

@ -0,0 +1,12 @@
package ch.unisg.assignment.assignment.domain.valueobject;
import lombok.Value;
@Value
public class ExecutorType {
private String value;
public ExecutorType(String type) {
this.value = type.toUpperCase();
}
}

View File

@ -0,0 +1,23 @@
package ch.unisg.assignment.assignment.domain.valueobject;
import ch.unisg.assignment.common.exception.InvalidIP4Exception;
import lombok.Value;
@Value
public class IP4Adress {
private String value;
public IP4Adress(String ip4) throws InvalidIP4Exception {
if (ip4.equalsIgnoreCase("localhost") ||
ip4.matches("^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)(\\.(?!$)|$)){4}$")) {
this.value = ip4;
} else {
throw new InvalidIP4Exception();
}
}
}

View File

@ -0,0 +1,17 @@
package ch.unisg.assignment.assignment.domain.valueobject;
import ch.unisg.assignment.common.exception.PortOutOfRangeException;
import lombok.Value;
@Value
public class Port {
private int value;
public Port(int port) throws PortOutOfRangeException {
if (1024 <= port && port <= 65535) {
this.value = port;
} else {
throw new PortOutOfRangeException();
}
}
}

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

@ -0,0 +1,13 @@
package ch.unisg.assignment.common.exception;
import org.springframework.http.HttpStatus;
import lombok.Data;
import lombok.RequiredArgsConstructor;
@Data
@RequiredArgsConstructor
public class ErrorResponse {
private final HttpStatus httpStatus;
private final String message;
}

View File

@ -0,0 +1,7 @@
package ch.unisg.assignment.common.exception;
public class InvalidIP4Exception extends Exception {
public InvalidIP4Exception() {
super("IP4 is invalid");
}
}

View File

@ -0,0 +1,7 @@
package ch.unisg.assignment.common.exception;
public class PortOutOfRangeException extends Exception {
public PortOutOfRangeException() {
super("Port is out of available range (1024-65535)");
}
}

View File

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

View File

@ -1,62 +1,62 @@
version: "3.6" version: "3.6"
services: services:
tapas-tasks: tapas-tasks:
container_name: tapas-tasks container_name: tapas-tasks
build: build:
context: "./tapas-tasks" context: "./tapas-tasks"
dockerfile: "Dockerfile" dockerfile: "Dockerfile"
target: development target: development
ports: ports:
- "8081:8081" - "8081:8081"
- "5005:5005" - "5005:5005"
volumes: volumes:
- ./tapas-tasks/src:/opt/app/src - ./tapas-tasks/src:/opt/app/src
- ./tapas-tasks/target:/opt/app/target - ./tapas-tasks/target:/opt/app/target
assignment: assignment:
container_name: assignment container_name: assignment
build: build:
context: "./assignment" context: "./assignment"
dockerfile: "Dockerfile" dockerfile: "Dockerfile"
target: development target: development
ports: ports:
- "8082:8081" - "8082:8082"
- "5006:5005" - "5006:5005"
volumes: volumes:
- ./assignment/src:/opt/app/src - ./assignment/src:/opt/app/src
- ./assignment/target:/opt/app/target - ./assignment/target:/opt/app/target
executor-pool: executor-pool:
container_name: executor-pool container_name: executor-pool
build: build:
context: "./executor-pool" context: "./executor-pool"
dockerfile: "Dockerfile" dockerfile: "Dockerfile"
target: development target: development
ports: ports:
- "8083:8081" - "8083:8081"
- "5007:5005" - "5007:5005"
volumes: volumes:
- ./executor-pool/src:/opt/app/src - ./executor-pool/src:/opt/app/src
- ./executor-pool/target:/opt/app/target - ./executor-pool/target:/opt/app/target
executor1: executor1:
container_name: executor1 container_name: executor1
build: build:
context: "./executor1" context: "./executor1"
dockerfile: "Dockerfile" dockerfile: "Dockerfile"
target: development target: development
ports: ports:
- "8084:8081" - "8084:8081"
- "5008:5005" - "5008:5005"
volumes: volumes:
- ./executor1/src:/opt/app/src - ./executor1/src:/opt/app/src
- ./executor1/target:/opt/app/target - ./executor1/target:/opt/app/target
executor2: executor2:
container_name: executor2 container_name: executor2
build: build:
context: "./executor2" context: "./executor2"
dockerfile: "Dockerfile" dockerfile: "Dockerfile"
target: development target: development
ports: ports:
- "8085:8081" - "8085:8085"
- "5009:5005" - "5009:5005"
volumes: volumes:
- ./executor2/src:/opt/app/src - ./executor2/src:/opt/app/src
- ./executor2/target:/opt/app/target - ./executor2/target:/opt/app/target

View File

@ -15,8 +15,6 @@
<description>Demo project for Spring Boot</description> <description>Demo project for Spring Boot</description>
<properties> <properties>
<java.version>11</java.version> <java.version>11</java.version>
<sonar.organization>scs-asse-fs21-group1</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>
@ -56,23 +54,12 @@
<artifactId>javax.transaction-api</artifactId> <artifactId>javax.transaction-api</artifactId>
<version>1.2</version> <version>1.2</version>
</dependency> </dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20210307</version>
</dependency>
</dependencies> </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project> </project>

View File

@ -21,6 +21,7 @@ public class SelfValidating<T> {
* instance. * instance.
*/ */
protected void validateSelf() { protected void validateSelf() {
@SuppressWarnings("unchecked")
Set<ConstraintViolation<T>> violations = validator.validate((T) this); Set<ConstraintViolation<T>> violations = validator.validate((T) this);
if (!violations.isEmpty()) { if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations); throw new ConstraintViolationException(violations);

View File

@ -5,51 +5,43 @@ import java.net.URI;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.util.HashMap; import java.util.logging.Level;
import java.util.logging.Logger;
import com.fasterxml.jackson.databind.ObjectMapper; import org.json.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import ch.unisg.executorBase.executor.application.port.out.ExecutionFinishedEventPort; import ch.unisg.executorBase.executor.application.port.out.ExecutionFinishedEventPort;
import ch.unisg.executorBase.executor.domain.ExecutionFinishedEvent; import ch.unisg.executorBase.executor.domain.ExecutionFinishedEvent;
public class ExecutionFinishedEventAdapter implements ExecutionFinishedEventPort { public class ExecutionFinishedEventAdapter implements ExecutionFinishedEventPort {
//This is the base URI of the service interested in this event (in my setup, running locally as separate Spring Boot application)
String server = "http://127.0.0.1:8082"; String server = "http://127.0.0.1:8082";
Logger logger = Logger.getLogger(ExecutionFinishedEventAdapter.class.getName());
@Override @Override
public void publishExecutionFinishedEvent(ExecutionFinishedEvent event) { public void publishExecutionFinishedEvent(ExecutionFinishedEvent event) {
///Here we would need to work with DTOs in case the payload of calls becomes more complex
var values = new HashMap<String, String>() {{ String body = new JSONObject()
put("result",event.getResult()); .put("taskID", event.getTaskID())
put("status",event.getStatus()); .put("result", event.getResult())
}}; .put("status", event.getStatus())
.toString();
var objectMapper = new ObjectMapper();
String requestBody = null;
try {
requestBody = objectMapper.writeValueAsString(values);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
HttpClient client = HttpClient.newHttpClient(); HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(server+"/task/"+event.getTaskID())) .uri(URI.create(server+"/task/"+event.getTaskID()))
.PUT(HttpRequest.BodyPublishers.ofString(requestBody)) .header("Content-Type", "application/json")
.PUT(HttpRequest.BodyPublishers.ofString(body))
.build(); .build();
/** Needs the other service running
try { try {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); client.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException e) { } catch (IOException | InterruptedException e) {
e.printStackTrace(); logger.log(Level.SEVERE, e.getLocalizedMessage(), e);
} catch (InterruptedException e) { // Restore interrupted state...
e.printStackTrace(); Thread.currentThread().interrupt();
} }
**/
System.out.println("Finish execution event sent with result:" + event.getResult()); System.out.println("Finish execution event sent with result:" + event.getResult());

View File

@ -1,8 +1,12 @@
package ch.unisg.executorBase.executor.adapter.out.web; package ch.unisg.executorBase.executor.adapter.out.web;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -11,35 +15,47 @@ import ch.unisg.executorBase.executor.application.port.out.GetAssignmentPort;
import ch.unisg.executorBase.executor.domain.ExecutorType; import ch.unisg.executorBase.executor.domain.ExecutorType;
import ch.unisg.executorBase.executor.domain.Task; import ch.unisg.executorBase.executor.domain.Task;
import org.json.JSONObject;
@Component @Component
@Primary @Primary
public class GetAssignmentAdapter implements GetAssignmentPort { public class GetAssignmentAdapter implements GetAssignmentPort {
//This is the base URI of the service interested in this event (in my setup, running locally as separate Spring Boot application)
String server = "http://127.0.0.1:8082"; String server = "http://127.0.0.1:8082";
Logger logger = Logger.getLogger(GetAssignmentAdapter.class.getName());
@Override @Override
public Task getAssignment(ExecutorType executorType) { public Task getAssignment(ExecutorType executorType, String ip, int port) {
String body = new JSONObject()
.put("executorType", executorType)
.put("ip", ip)
.put("port", port)
.toString();
HttpClient client = HttpClient.newHttpClient(); HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(server+"/assignment/" + executorType)) .uri(URI.create(server+"/task/apply"))
.GET() .header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build(); .build();
/** Needs the other service running
try { try {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException e) { if (response.body().equals("")) {
e.printStackTrace(); return null;
} catch (InterruptedException e) { }
e.printStackTrace();
return new Task(new JSONObject(response.body()).getString("taskID"));
} catch (IOException | InterruptedException e) {
logger.log(Level.SEVERE, e.getLocalizedMessage(), e);
// Restore interrupted state...
Thread.currentThread().interrupt();
} }
**/
// TODO return null or a new Task here depending on the response of the http call return null;
return new Task("123");
} }
} }

View File

@ -3,11 +3,8 @@ package ch.unisg.executorBase.executor.adapter.out.web;
import java.net.URI; import java.net.URI;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.util.HashMap;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.json.JSONObject;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -18,30 +15,21 @@ import ch.unisg.executorBase.executor.domain.ExecutorType;
@Primary @Primary
public class NotifyExecutorPoolAdapter implements NotifyExecutorPoolPort { public class NotifyExecutorPoolAdapter implements NotifyExecutorPoolPort {
//This is the base URI of the service interested in this event (in my setup, running locally as separate Spring Boot application)
String server = "http://127.0.0.1:8083"; String server = "http://127.0.0.1:8083";
@Override @Override
public boolean notifyExecutorPool(String ip, int port, ExecutorType executorType) { public boolean notifyExecutorPool(String ip, int port, ExecutorType executorType) {
var values = new HashMap<String, String>() {{ String body = new JSONObject()
put("ip", ip); .put("executorType", executorType)
put("port", Integer.toString(port)); .put("ip", ip)
put("executorType", executorType.toString()); .put("port", port)
}}; .toString();
var objectMapper = new ObjectMapper();
String requestBody = null;
try {
requestBody = objectMapper.writeValueAsString(values);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
HttpClient client = HttpClient.newHttpClient(); HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(server+"/executor/new/")) .uri(URI.create(server+"/executor/new/"))
.POST(HttpRequest.BodyPublishers.ofString(requestBody)) .POST(HttpRequest.BodyPublishers.ofString(body))
.build(); .build();
/** Needs the other service running /** Needs the other service running

View File

@ -4,5 +4,5 @@ import ch.unisg.executorBase.executor.domain.ExecutorType;
import ch.unisg.executorBase.executor.domain.Task; import ch.unisg.executorBase.executor.domain.Task;
public interface GetAssignmentPort { public interface GetAssignmentPort {
Task getAssignment(ExecutorType executorType); Task getAssignment(ExecutorType executorType, String ip, int port);
} }

View File

@ -0,0 +1,20 @@
package ch.unisg.executorBase.executor.application.service;
import org.springframework.stereotype.Component;
import ch.unisg.executorBase.executor.application.port.in.TaskAvailableCommand;
import ch.unisg.executorBase.executor.application.port.in.TaskAvailableUseCase;
import lombok.RequiredArgsConstructor;
import javax.transaction.Transactional;
@RequiredArgsConstructor
@Component
@Transactional
public class TaskAvailableService implements TaskAvailableUseCase {
@Override
public void newTaskAvailable(TaskAvailableCommand command) {
// Placeholder so spring can create a bean
}
}

View File

@ -4,13 +4,6 @@ import ch.unisg.executorBase.executor.application.port.out.ExecutionFinishedEven
import ch.unisg.executorBase.executor.application.port.out.GetAssignmentPort; import ch.unisg.executorBase.executor.application.port.out.GetAssignmentPort;
import ch.unisg.executorBase.executor.application.port.out.NotifyExecutorPoolPort; import ch.unisg.executorBase.executor.application.port.out.NotifyExecutorPoolPort;
import java.util.concurrent.TimeUnit;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import ch.unisg.executorBase.executor.adapter.out.web.ExecutionFinishedEventAdapter; import ch.unisg.executorBase.executor.adapter.out.web.ExecutionFinishedEventAdapter;
import ch.unisg.executorBase.executor.adapter.out.web.GetAssignmentAdapter; import ch.unisg.executorBase.executor.adapter.out.web.GetAssignmentAdapter;
import ch.unisg.executorBase.executor.adapter.out.web.NotifyExecutorPoolAdapter; import ch.unisg.executorBase.executor.adapter.out.web.NotifyExecutorPoolAdapter;
@ -49,18 +42,14 @@ public abstract class ExecutorBase {
if(!notifyExecutorPoolService.notifyExecutorPool(this.ip, this.port, this.executorType)) { if(!notifyExecutorPoolService.notifyExecutorPool(this.ip, this.port, this.executorType)) {
System.exit(0); System.exit(0);
} else { } else {
System.out.println(true);
this.status = ExecutorStatus.IDLING; this.status = ExecutorStatus.IDLING;
getAssignment(); getAssignment();
} }
} }
// public static ExecutorBase getExecutor() {
// return executor;
// }
public void getAssignment() { public void getAssignment() {
Task newTask = getAssignmentPort.getAssignment(this.getExecutorType()); Task newTask = getAssignmentPort.getAssignment(this.getExecutorType(), this.getIp(),
this.getPort());
if (newTask != null) { if (newTask != null) {
this.executeTask(newTask); this.executeTask(newTask);
} else { } else {
@ -74,7 +63,8 @@ public abstract class ExecutorBase {
task.setResult(execution()); task.setResult(execution());
executionFinishedEventPort.publishExecutionFinishedEvent(new ExecutionFinishedEvent(task.getTaskID(), task.getResult(), "SUCCESS")); executionFinishedEventPort.publishExecutionFinishedEvent(
new ExecutionFinishedEvent(task.getTaskID(), task.getResult(), "SUCCESS"));
System.out.println("Finish execution"); System.out.println("Finish execution");
getAssignment(); getAssignment();

View File

@ -1,6 +1,5 @@
package ch.unisg.executorBase.executor.domain; package ch.unisg.executorBase.executor.domain;
import lombok.Data;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;

View File

@ -44,6 +44,13 @@
<groupId>ch.unisg</groupId> <groupId>ch.unisg</groupId>
<artifactId>executorBase</artifactId> <artifactId>executorBase</artifactId>
<version>0.0.1-SNAPSHOT</version> <version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20210307</version>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -0,0 +1,35 @@
package ch.unisg.executor2.executor.adapter.in.web;
import java.util.concurrent.CompletableFuture;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import ch.unisg.executorBase.executor.application.port.in.TaskAvailableCommand;
import ch.unisg.executorBase.executor.application.port.in.TaskAvailableUseCase;
import ch.unisg.executorBase.executor.domain.ExecutorType;
@RestController
public class TaskAvailableController {
private final TaskAvailableUseCase taskAvailableUseCase;
public TaskAvailableController(TaskAvailableUseCase taskAvailableUseCase) {
this.taskAvailableUseCase = taskAvailableUseCase;
}
@GetMapping(path = "/newtask/{taskType}")
public ResponseEntity<String> retrieveTaskFromTaskList(@PathVariable("taskType") String taskType) {
if (ExecutorType.contains(taskType.toUpperCase())) {
TaskAvailableCommand command = new TaskAvailableCommand(
ExecutorType.valueOf(taskType.toUpperCase()));
CompletableFuture.runAsync(() -> taskAvailableUseCase.newTaskAvailable(command));
}
return new ResponseEntity<>("OK", new HttpHeaders(), HttpStatus.OK);
}
}

View File

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