diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..74c6104 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +[*.{java}] +indent_style = space +indent_size = 4 +tab_width = 4 +trim_trailing_whitespace = true +max_line_length = 100 diff --git a/executor1/pom.xml b/executor1/pom.xml index 1534025..8df7a04 100644 --- a/executor1/pom.xml +++ b/executor1/pom.xml @@ -30,6 +30,10 @@ runtime true + + org.springframework.boot + spring-boot-starter-validation + org.projectlombok lombok @@ -40,6 +44,18 @@ spring-boot-starter-test test + + + javax.validation + validation-api + 1.1.0.Final + + + + javax.transaction + javax.transaction-api + 1.2 + diff --git a/executor1/src/main/java/ch/unisg/executor1/Executor1Application.java b/executor1/src/main/java/ch/unisg/executor1/Executor1Application.java index 4eed560..dfb8d8c 100644 --- a/executor1/src/main/java/ch/unisg/executor1/Executor1Application.java +++ b/executor1/src/main/java/ch/unisg/executor1/Executor1Application.java @@ -3,11 +3,14 @@ package ch.unisg.executor1; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import ch.unisg.executor1.executor.domain.Executor; + @SpringBootApplication public class Executor1Application { public static void main(String[] args) { SpringApplication.run(Executor1Application.class, args); + Executor.getExecutor(); } } diff --git a/executor1/src/main/java/ch/unisg/executor1/TestController.java b/executor1/src/main/java/ch/unisg/executor1/TestController.java deleted file mode 100644 index a2f32b4..0000000 --- a/executor1/src/main/java/ch/unisg/executor1/TestController.java +++ /dev/null @@ -1,12 +0,0 @@ -package ch.unisg.executor1; - -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! Executor1"; - } -} diff --git a/executor1/src/main/java/ch/unisg/executor1/common/SelfValidating.java b/executor1/src/main/java/ch/unisg/executor1/common/SelfValidating.java new file mode 100644 index 0000000..bc9816a --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/common/SelfValidating.java @@ -0,0 +1,29 @@ +package ch.unisg.executor1.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 class SelfValidating { + + private Validator validator; + + public SelfValidating() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + validator = factory.getValidator(); + } + + /** + * Evaluates all Bean Validations on the attributes of this + * instance. + */ + protected void validateSelf() { + Set> violations = validator.validate((T) this); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + } +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java new file mode 100644 index 0000000..14dc3e6 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/in/web/TaskAvailableController.java @@ -0,0 +1,32 @@ +package ch.unisg.executor1.executor.adapter.in.web; + +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.executor1.executor.application.port.in.TaskAvailableCommand; +import ch.unisg.executor1.executor.application.port.in.TaskAvailableUseCase; + +@RestController +public class TaskAvailableController { + private final TaskAvailableUseCase taskAvailableUseCase; + + public TaskAvailableController(TaskAvailableUseCase taskAvailableUseCase) { + this.taskAvailableUseCase = taskAvailableUseCase; + } + + @GetMapping(path = "/newtask/{taskType}") + public ResponseEntity retrieveTaskFromTaskList(@PathVariable("taskType") String taskType) { + TaskAvailableCommand command = new TaskAvailableCommand(taskType); + + taskAvailableUseCase.newTaskAvailable(command); + + // Add the content type as a response header + HttpHeaders responseHeaders = new HttpHeaders(); + + return new ResponseEntity<>("OK", responseHeaders, HttpStatus.OK); + } +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/ExecutionFinishedAdapter.java b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/ExecutionFinishedAdapter.java new file mode 100644 index 0000000..e1c3a5d --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/ExecutionFinishedAdapter.java @@ -0,0 +1,58 @@ +package ch.unisg.executor1.executor.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.HashMap; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.JsonProcessingException; + +import ch.unisg.executor1.executor.application.port.out.ExecutionFinishedEventPort; +import ch.unisg.executor1.executor.domain.ExecutionFinishedEvent; + +public class ExecutionFinishedAdapter 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"; + + @Override + 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() {{ + put("result",event.getResult()); + put("status",event.getStatus()); + }}; + + var objectMapper = new ObjectMapper(); + String requestBody = null; + try { + requestBody = objectMapper.writeValueAsString(values); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(server+"/task/"+event.getTaskID())) + .PUT(HttpRequest.BodyPublishers.ofString(requestBody)) + .build(); + + /** Needs the other service running + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + **/ + + System.out.println("Finish execution event sent"); + + } + +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/GetAssignmentAdapter.java b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/GetAssignmentAdapter.java new file mode 100644 index 0000000..2c32f84 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/GetAssignmentAdapter.java @@ -0,0 +1,44 @@ +package ch.unisg.executor1.executor.adapter.out.web; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; + +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +import ch.unisg.executor1.executor.application.port.out.GetAssignmentPort; +import ch.unisg.executor1.executor.domain.Task; + +@Component +@Primary +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"; + + @Override + public Task getAssignment(String executorType) { + + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(server+"/assignment/" + executorType)) + .GET() + .build(); + + /** Needs the other service running + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + **/ + + // TODO return null or a new Task here depending on the response of the http call + + return new Task("1234"); + } + +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/NotifyExecutorPoolAdapter.java b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/NotifyExecutorPoolAdapter.java new file mode 100644 index 0000000..866ab91 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/adapter/out/web/NotifyExecutorPoolAdapter.java @@ -0,0 +1,61 @@ +package ch.unisg.executor1.executor.adapter.out.web; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.util.HashMap; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +import ch.unisg.executor1.executor.application.port.out.NotifyExecutorPoolPort; + +@Component +@Primary +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"; + + @Override + public boolean notifyExecutorPool(String ip, int port, String executorType) { + + var values = new HashMap() {{ + put("ip", ip); + put("port", Integer.toString(port)); + put("executorType", executorType); + }}; + + var objectMapper = new ObjectMapper(); + String requestBody = null; + try { + requestBody = objectMapper.writeValueAsString(values); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(server+"/executor/new/")) + .POST(HttpRequest.BodyPublishers.ofString(requestBody)) + .build(); + + /** Needs the other service running + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + **/ + + // TODO return true or false depending on result of http request; + + return true; + } + +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableCommand.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableCommand.java new file mode 100644 index 0000000..a5d530d --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableCommand.java @@ -0,0 +1,19 @@ +package ch.unisg.executor1.executor.application.port.in; + +import ch.unisg.executor1.common.SelfValidating; + +import javax.validation.constraints.NotNull; + +import lombok.Value; + +@Value +public class TaskAvailableCommand extends SelfValidating { + + @NotNull + private final String taskType; + + public TaskAvailableCommand(String taskType) { + this.taskType = taskType; + this.validateSelf(); + } +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableUseCase.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableUseCase.java new file mode 100644 index 0000000..d8184c1 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/in/TaskAvailableUseCase.java @@ -0,0 +1,5 @@ +package ch.unisg.executor1.executor.application.port.in; + +public interface TaskAvailableUseCase { + void newTaskAvailable(TaskAvailableCommand command); +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/ExecutionFinishedEventPort.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/ExecutionFinishedEventPort.java new file mode 100644 index 0000000..6bfea70 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/ExecutionFinishedEventPort.java @@ -0,0 +1,7 @@ +package ch.unisg.executor1.executor.application.port.out; + +import ch.unisg.executor1.executor.domain.ExecutionFinishedEvent; + +public interface ExecutionFinishedEventPort { + void publishExecutionFinishedEvent(ExecutionFinishedEvent event); +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/GetAssignmentPort.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/GetAssignmentPort.java new file mode 100644 index 0000000..bbf9124 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/GetAssignmentPort.java @@ -0,0 +1,7 @@ +package ch.unisg.executor1.executor.application.port.out; + +import ch.unisg.executor1.executor.domain.Task; + +public interface GetAssignmentPort { + Task getAssignment(String executorType); +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/NotifyExecutorPoolPort.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/NotifyExecutorPoolPort.java new file mode 100644 index 0000000..856163f --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/port/out/NotifyExecutorPoolPort.java @@ -0,0 +1,5 @@ +package ch.unisg.executor1.executor.application.port.out; + +public interface NotifyExecutorPoolPort { + boolean notifyExecutorPool(String ip, int port, String executorType); +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/service/NotifyExecutorPoolService.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/service/NotifyExecutorPoolService.java new file mode 100644 index 0000000..0c05fda --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/service/NotifyExecutorPoolService.java @@ -0,0 +1,17 @@ +package ch.unisg.executor1.executor.application.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import ch.unisg.executor1.executor.application.port.out.NotifyExecutorPoolPort; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class NotifyExecutorPoolService { + + private final NotifyExecutorPoolPort notifyExecutorPoolPort; + + public boolean notifyExecutorPool(String ip, int port, String executorType) { + return notifyExecutorPoolPort.notifyExecutorPool(ip, port, executorType); + } +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java b/executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java new file mode 100644 index 0000000..795cd8b --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/application/service/TaskAvailableService.java @@ -0,0 +1,27 @@ +package ch.unisg.executor1.executor.application.service; + +import org.springframework.stereotype.Component; + +import ch.unisg.executor1.executor.application.port.in.TaskAvailableCommand; +import ch.unisg.executor1.executor.application.port.in.TaskAvailableUseCase; +import ch.unisg.executor1.executor.domain.Executor; +import ch.unisg.executor1.executor.domain.ExecutorStatus; +import lombok.RequiredArgsConstructor; + +import javax.transaction.Transactional; + +@RequiredArgsConstructor +@Component +@Transactional +public class TaskAvailableService implements TaskAvailableUseCase { + + @Override + public void newTaskAvailable(TaskAvailableCommand command) { + Executor executor = Executor.getExecutor(); + + if (executor.getExecutorType().equalsIgnoreCase(command.getTaskType()) && + executor.getStatus() == ExecutorStatus.IDLING) { + executor.getAssignment(); + } + } +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutionFinishedEvent.java b/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutionFinishedEvent.java new file mode 100644 index 0000000..4455572 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutionFinishedEvent.java @@ -0,0 +1,21 @@ +package ch.unisg.executor1.executor.domain; + +import lombok.Getter; + +public class ExecutionFinishedEvent { + + @Getter + private String taskID; + + @Getter + private String result; + + @Getter + private String status; + + public ExecutionFinishedEvent(String taskID, String result, String status) { + this.taskID = taskID; + this.result = result; + this.status = status; + } +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java b/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java new file mode 100644 index 0000000..6329f29 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/domain/Executor.java @@ -0,0 +1,93 @@ +package ch.unisg.executor1.executor.domain; + +import ch.unisg.executor1.executor.application.port.out.ExecutionFinishedEventPort; +import ch.unisg.executor1.executor.application.port.out.GetAssignmentPort; +import ch.unisg.executor1.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.executor1.executor.adapter.out.web.ExecutionFinishedAdapter; +import ch.unisg.executor1.executor.adapter.out.web.GetAssignmentAdapter; +import ch.unisg.executor1.executor.adapter.out.web.NotifyExecutorPoolAdapter; +import ch.unisg.executor1.executor.application.service.NotifyExecutorPoolService; +import lombok.Getter; + +public class Executor { + + @Getter + private String ip; + + @Getter + private String executorType = "addition"; + + @Getter + private int port; + + @Getter + private ExecutorStatus status; + + private static final Executor executor = new Executor(); + + // TODO Violation of the Dependency Inversion Principle?, but we havn't really got a better solutions to send a http request / access a service from a domain model + // TODO I guess we can somehow autowire this but I don't know why it's not working :D + private final NotifyExecutorPoolPort notifyExecutorPoolPort = new NotifyExecutorPoolAdapter(); + private final NotifyExecutorPoolService notifyExecutorPoolService = new NotifyExecutorPoolService(notifyExecutorPoolPort); + private final GetAssignmentPort getAssignmentPort = new GetAssignmentAdapter(); + private final ExecutionFinishedEventPort executionFinishedEventPort = new ExecutionFinishedAdapter(); + + private Executor() { + System.out.println("Starting Executor"); + // TODO set this automaticly + this.ip = "localhost"; + this.port = 8084; + + this.status = ExecutorStatus.STARTING_UP; + if(!notifyExecutorPoolService.notifyExecutorPool(this.ip, this.port, this.executorType)) { + System.exit(0); + } else { + System.out.println(true); + this.status = ExecutorStatus.IDLING; + getAssignment(); + } + } + + public static Executor getExecutor() { + return executor; + } + + public void getAssignment() { + Task newTask = getAssignmentPort.getAssignment(this.getExecutorType()); + if (newTask != null) { + this.executeTask(newTask); + } else { + this.status = ExecutorStatus.IDLING; + } + } + + private void executeTask(Task task) { + System.out.println("Starting execution"); + this.status = ExecutorStatus.EXECUTING; + int a = 10; + int b = 20; + try { + TimeUnit.SECONDS.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + int result = a + b; + + task.setResult(Integer.toString(result)); + + executionFinishedEventPort.publishExecutionFinishedEvent(new ExecutionFinishedEvent(task.getTaskID(), task.getResult(), "SUCCESS")); + + System.out.println("Finish execution"); + getAssignment(); + } + +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorStatus.java b/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorStatus.java new file mode 100644 index 0000000..92af024 --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/domain/ExecutorStatus.java @@ -0,0 +1,7 @@ +package ch.unisg.executor1.executor.domain; + +public enum ExecutorStatus { + STARTING_UP, + EXECUTING, + IDLING, +} diff --git a/executor1/src/main/java/ch/unisg/executor1/executor/domain/Task.java b/executor1/src/main/java/ch/unisg/executor1/executor/domain/Task.java new file mode 100644 index 0000000..2287b6c --- /dev/null +++ b/executor1/src/main/java/ch/unisg/executor1/executor/domain/Task.java @@ -0,0 +1,20 @@ +package ch.unisg.executor1.executor.domain; + +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +public class Task { + + @Getter + private String taskID; + + @Getter + @Setter + private String result; + + public Task(String taskID) { + this.taskID = taskID; + } + +} diff --git a/tapas-tasks/src/main/java/ch/unisg/tapastasks/TapasTasksApplication.java b/tapas-tasks/src/main/java/ch/unisg/tapastasks/TapasTasksApplication.java index 40fa5da..90d1716 100644 --- a/tapas-tasks/src/main/java/ch/unisg/tapastasks/TapasTasksApplication.java +++ b/tapas-tasks/src/main/java/ch/unisg/tapastasks/TapasTasksApplication.java @@ -10,8 +10,8 @@ public class TapasTasksApplication { public static void main(String[] args) { - SpringApplication tapasTasksApp = new SpringApplication(TapasTasksApplication.class); - tapasTasksApp.run(args); + SpringApplication tapasTasksApp = new SpringApplication(TapasTasksApplication.class); + tapasTasksApp.run(args); } }