basic implementation of an executor

This commit is contained in:
Marcel 2021-10-11 19:21:22 +02:00
parent 050d11254c
commit 1d78bb63ab
21 changed files with 485 additions and 14 deletions

12
.editorconfig Normal file
View File

@ -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

View File

@ -30,6 +30,10 @@
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
@ -40,6 +44,18 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</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>
</dependencies>
<build>

View File

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

View File

@ -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";
}
}

View File

@ -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<T> {
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<ConstraintViolation<T>> violations = validator.validate((T) this);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
}
}

View File

@ -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<String> 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);
}
}

View File

@ -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<String, String>() {{
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<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
**/
System.out.println("Finish execution event sent");
}
}

View File

@ -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<String> 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");
}
}

View File

@ -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<String, String>() {{
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<String> 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;
}
}

View File

@ -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<TaskAvailableCommand> {
@NotNull
private final String taskType;
public TaskAvailableCommand(String taskType) {
this.taskType = taskType;
this.validateSelf();
}
}

View File

@ -0,0 +1,5 @@
package ch.unisg.executor1.executor.application.port.in;
public interface TaskAvailableUseCase {
void newTaskAvailable(TaskAvailableCommand command);
}

View File

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

View File

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

View File

@ -0,0 +1,5 @@
package ch.unisg.executor1.executor.application.port.out;
public interface NotifyExecutorPoolPort {
boolean notifyExecutorPool(String ip, int port, String executorType);
}

View File

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

View File

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

View File

@ -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;
}
}

View File

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

View File

@ -0,0 +1,7 @@
package ch.unisg.executor1.executor.domain;
public enum ExecutorStatus {
STARTING_UP,
EXECUTING,
IDLING,
}

View File

@ -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;
}
}