Implemented auctioning of tasks workflow in auction house

This commit is contained in:
reynisson 2021-11-16 17:42:14 +01:00
parent 5400798e9c
commit 8fba9136b2
12 changed files with 474 additions and 4 deletions

View File

@ -8,6 +8,7 @@ import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.text.SimpleDateFormat;
/** /**
* Used to expose a representation of the state of an auction through an interface. This class is * Used to expose a representation of the state of an auction through an interface. This class is
@ -15,7 +16,7 @@ import java.sql.Timestamp;
* to modify this class as you see fit! * to modify this class as you see fit!
*/ */
public class AuctionJsonRepresentation { public class AuctionJsonRepresentation {
public static final String MEDIA_TYPE = "application/json"; public static final String MEDIA_TYPE = "application/auction+json";
@Getter @Setter @Getter @Setter
private String auctionId; private String auctionId;
@ -56,7 +57,7 @@ public class AuctionJsonRepresentation {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
return mapper.writeValueAsString(representation); return mapper.writeValueAsString(representation);
} }
} }

View File

@ -0,0 +1,43 @@
package ch.unisg.tapas.auctionhouse.adapter.common.formats;
import ch.unisg.tapas.auctionhouse.domain.Bid;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import lombok.Setter;
import java.text.SimpleDateFormat;
public class BidJsonRepresentation {
public static final String MEDIA_TYPE = "application/bid+json";
@Getter @Setter
private String auctionId;
@Getter @Setter
private String bidderName;
@Getter @Setter
private String bidderAuctionHouseUri;
@Getter @Setter
private String bidderTaskListUri;
public BidJsonRepresentation() {}
public BidJsonRepresentation(Bid bid){
this.auctionId = bid.getAuctionId().getValue();
this.bidderName = bid.getBidderName().getValue();
this.bidderAuctionHouseUri = bid.getBidderAuctionHouseUri().getValue().toString();
this.bidderTaskListUri = bid.getBidderTaskListUri().getValue().toString();
}
public static String serialize(Bid bid) throws JsonProcessingException {
BidJsonRepresentation representation = new BidJsonRepresentation(bid);
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper.writeValueAsString(representation);
}
}

View File

@ -0,0 +1,115 @@
package ch.unisg.tapas.auctionhouse.adapter.common.formats;
import ch.unisg.tapas.auctionhouse.domain.Task;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import lombok.Setter;
/**
* This class is used to expose and consume representations of tasks through the HTTP interface. The
* representations conform to the custom JSON-based media type "application/task+json". The media type
* is just an identifier and can be registered with
* <a href="https://www.iana.org/assignments/media-types/">IANA</a> to promote interoperability.
*/
final public class TaskJsonRepresentation {
// The media type used for this task representation format
public static final String MEDIA_TYPE = "application/task+json";
// A task identifier specific to our implementation (e.g., a UUID). This identifier is then used
// to generate the task's URI. URIs are standard uniform identifiers and use a universal syntax
// that can be referenced (and dereferenced) independent of context. In our uniform HTTP API,
// we identify tasks via URIs and not implementation-specific identifiers.
@Getter @Setter
private String taskId;
// A string that represents the task's name
@Getter
private final String taskName;
// A string that identifies the task's type. This string could also be a URI (e.g., defined in some
// Web ontology, as we shall see later in the course), but it's not constrained to be a URI.
// The task's type can be used to assign executors to tasks, to decide what tasks to bid for, etc.
@Getter
private final String taskType;
// The task's status: OPEN, ASSIGNED, RUNNING, or EXECUTED (see Task.Status)
@Getter @Setter
private String taskStatus;
// If this task is a delegated task (i.e., a shadow of another task), this URI points to the
// original task. Because URIs are standard and uniform, we can just dereference this URI to
// retrieve a representation of the original task.
@Getter @Setter
private String originalTaskUri;
// The service provider who executes this task. The service provider is a any string that identifies
// a TAPAS group (e.g., tapas-group1). This identifier could also be a URI (if we have a good reason
// for it), but it's not constrained to be a URI.
@Getter @Setter
private String serviceProvider;
// A string that provides domain-specific input data for this task. In the context of this project,
// we can parse and interpret the input data based on the task's type.
@Getter @Setter
private String inputData;
// A string that provides domain-specific output data for this task. In the context of this project,
// we can parse and interpret the output data based on the task's type.
@Getter @Setter
private String outputData;
/**
* Instantiate a task representation with a task name and type.
*
* @param taskName string that represents the task's name
* @param taskType string that represents the task's type
*/
public TaskJsonRepresentation(String taskName, String taskType) {
this.taskName = taskName;
this.taskType = taskType;
this.taskStatus = null;
this.originalTaskUri = null;
this.serviceProvider = null;
this.inputData = null;
this.outputData = null;
}
/**
* Instantiate a task representation from a domain concept.
*
* @param task the task
*/
public TaskJsonRepresentation(Task task) {
this(task.getTaskName().getValue(), task.getTaskType().getValue());
this.taskId = task.getTaskId().getValue();
this.taskStatus = task.getTaskStatus().getValue().name();
this.originalTaskUri = (task.getOriginalTaskUri() == null) ?
null : task.getOriginalTaskUri().getValue();
this.serviceProvider = (task.getProvider() == null) ? null : task.getProvider().getValue();
this.inputData = (task.getInputData() == null) ? null : task.getInputData().getValue();
this.outputData = (task.getOutputData() == null) ? null : task.getOutputData().getValue();
}
/**
* Convenience method used to serialize a task provided as a domain concept in the format exposed
* through the uniform HTTP API.
*
* @param task the task as defined in the domain
* @return a string serialization using the JSON-based representation format defined for tasks
* @throws JsonProcessingException if a runtime exception occurs during object serialization
*/
public static String serialize(Task task) throws JsonProcessingException {
TaskJsonRepresentation representation = new TaskJsonRepresentation(task);
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper.writeValueAsString(representation);
}
}

View File

@ -0,0 +1,52 @@
package ch.unisg.tapas.auctionhouse.adapter.in.messaging.mqtt;
import ch.unisg.tapas.auctionhouse.application.handler.BidReceivedHandler;
import ch.unisg.tapas.auctionhouse.application.handler.ExecutorAddedHandler;
import ch.unisg.tapas.auctionhouse.application.port.in.ExecutorAddedEvent;
import ch.unisg.tapas.auctionhouse.domain.Auction;
import ch.unisg.tapas.auctionhouse.domain.Bid;
import ch.unisg.tapas.auctionhouse.application.port.in.BidReceivedEvent;
import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import java.net.URI;
public class BidReceivedEventListenerMqttAdapter extends AuctionEventMqttListener {
private static final Logger LOGGER = LogManager.getLogger(BidReceivedEventListenerMqttAdapter.class);
@Override
public boolean handleEvent(MqttMessage message){
String payload = new String(message.getPayload());
try {
// Note: this message representation is provided only as an example. You should use a
// representation that makes sense in the context of your application.
JsonNode data = new ObjectMapper().readTree(payload);
String auctionId = data.get("auctionId").asText();
String bidderName = data.get("bidderName").asText();
String bidderAuctionHouseUri = data.get("bidderAuctionHouseUri").asText();
String bidderTaskListUri = data.get("bidderTaskListUri").asText();
BidReceivedEvent bidReceivedEvent = new BidReceivedEvent( new Bid(
new Auction.AuctionId(auctionId),
new Bid.BidderName(bidderName),
new Bid.BidderAuctionHouseUri(URI.create(bidderAuctionHouseUri)),
new Bid.BidderTaskListUri(URI.create(bidderTaskListUri))
));
BidReceivedHandler bidReceivedHandler = new BidReceivedHandler();
bidReceivedHandler.handleNewBidReceivedEvent(bidReceivedEvent);
} catch (JsonProcessingException | NullPointerException e) {
LOGGER.error(e.getMessage(), e);
return false;
}
return true;
}
}

View File

@ -0,0 +1,38 @@
package ch.unisg.tapas.auctionhouse.adapter.in.web;
import ch.unisg.tapas.auctionhouse.adapter.common.formats.AuctionJsonRepresentation;
import ch.unisg.tapas.auctionhouse.adapter.common.formats.BidJsonRepresentation;
import ch.unisg.tapas.auctionhouse.application.handler.BidReceivedHandler;
import ch.unisg.tapas.auctionhouse.application.port.in.BidReceivedEvent;
import ch.unisg.tapas.auctionhouse.domain.Auction;
import ch.unisg.tapas.auctionhouse.domain.Bid;
import org.springframework.http.HttpHeaders;
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 java.net.URI;
// TODO Fix structure due to MQTT
@RestController
public class AddBidWebController {
@PostMapping(path = "/bid", consumes = BidJsonRepresentation.MEDIA_TYPE)
public ResponseEntity<String> addBid(@RequestBody BidJsonRepresentation payload) {
BidReceivedEvent bidReceivedEvent = new BidReceivedEvent(new Bid(
new Auction.AuctionId(payload.getAuctionId()),
new Bid.BidderName(payload.getBidderName()),
new Bid.BidderAuctionHouseUri(URI.create(payload.getBidderAuctionHouseUri())),
new Bid.BidderTaskListUri(URI.create(payload.getBidderTaskListUri()))
));
BidReceivedHandler bidReceivedHandler = new BidReceivedHandler();
bidReceivedHandler.handleNewBidReceivedEvent(bidReceivedEvent);
HttpHeaders responseHeaders = new HttpHeaders();
return new ResponseEntity<>(responseHeaders, HttpStatus.NO_CONTENT);
}
}

View File

@ -1,4 +1,4 @@
package ch.unisg.tapas.auctionhouse.adapter.out.messaging.websub; package ch.unisg.tapas.auctionhouse.adapter.out.messaging.mqtt;
import ch.unisg.tapas.auctionhouse.adapter.common.clients.TapasMqttClient; import ch.unisg.tapas.auctionhouse.adapter.common.clients.TapasMqttClient;
import ch.unisg.tapas.auctionhouse.adapter.common.formats.AuctionJsonRepresentation; import ch.unisg.tapas.auctionhouse.adapter.common.formats.AuctionJsonRepresentation;

View File

@ -1,10 +1,24 @@
package ch.unisg.tapas.auctionhouse.adapter.out.web; package ch.unisg.tapas.auctionhouse.adapter.out.web;
import ch.unisg.tapas.auctionhouse.adapter.common.formats.TaskJsonRepresentation;
import ch.unisg.tapas.auctionhouse.application.handler.AuctionStartedHandler;
import ch.unisg.tapas.auctionhouse.application.port.out.AuctionWonEventPort; import ch.unisg.tapas.auctionhouse.application.port.out.AuctionWonEventPort;
import ch.unisg.tapas.auctionhouse.domain.AuctionRegistry;
import ch.unisg.tapas.auctionhouse.domain.AuctionWonEvent; import ch.unisg.tapas.auctionhouse.domain.AuctionWonEvent;
import ch.unisg.tapas.auctionhouse.domain.Task;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
/** /**
* This class is a template for sending auction won events via HTTP. This class was created here only * This class is a template for sending auction won events via HTTP. This class was created here only
* as a placeholder, it is up to you to decide how such events should be sent (e.g., via HTTP, * as a placeholder, it is up to you to decide how such events should be sent (e.g., via HTTP,
@ -13,8 +27,54 @@ import org.springframework.stereotype.Component;
@Component @Component
@Primary @Primary
public class AuctionWonEventHttpAdapter implements AuctionWonEventPort { public class AuctionWonEventHttpAdapter implements AuctionWonEventPort {
private static final Logger LOGGER = LogManager.getLogger(AuctionWonEventHttpAdapter.class);
@Value("${tasks.list.uri}")
String server;
@Override @Override
public void publishAuctionWonEvent(AuctionWonEvent event) { public void publishAuctionWonEvent(AuctionWonEvent event) {
try{
var auction = AuctionRegistry.getInstance().getAuctionById(event.getWinningBid().getAuctionId());
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(auction.get().getTaskUri().getValue())
.GET()
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
LOGGER.info(response.body());
JSONObject responseBody = new JSONObject(response.body());
var task = new Task(
new Task.TaskName(responseBody.getString("taskName")),
new Task.TaskType(responseBody.getString("taskType")),
new Task.OriginalTaskUri(auction.get().getTaskUri().getValue().toString()),
new Task.TaskStatus(ch.unisg.tapas.auctionhouse.domain.Task.Status.ASSIGNED),
new Task.TaskId(responseBody.getString("taskId")),
new Task.InputData(responseBody.getString("inputData")),
new Task.ServiceProvider("TODO")
);
String body = TaskJsonRepresentation.serialize(task);
LOGGER.info(body);
var postURI = URI.create(auction.get().getAuctionHouseUri().getValue().toString() + "/taskwinner");
HttpRequest postRequest = HttpRequest.newBuilder()
.uri(postURI)
.header("Content-Type", TaskJsonRepresentation.MEDIA_TYPE)
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
var postResponse = client.send(request, HttpResponse.BodyHandlers.ofString());
LOGGER.info(postResponse.statusCode());
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e){
LOGGER.error(e.getMessage(), e);
}
} }
} }

View File

@ -0,0 +1,17 @@
package ch.unisg.tapas.auctionhouse.application.handler;
import ch.unisg.tapas.auctionhouse.application.port.in.BidReceivedEvent;
import ch.unisg.tapas.auctionhouse.application.port.in.BidReceivedEventHandler;
import ch.unisg.tapas.auctionhouse.domain.AuctionRegistry;
import org.springframework.stereotype.Component;
@Component
public class BidReceivedHandler implements BidReceivedEventHandler {
@Override
public boolean handleNewBidReceivedEvent(BidReceivedEvent bidReceivedEvent){
var auction = AuctionRegistry.getInstance().getAuctionById(bidReceivedEvent.bid.getAuctionId());
// TODO Handle if auction not there
auction.get().addBid(bidReceivedEvent.bid);
return true;
}
}

View File

@ -0,0 +1,17 @@
package ch.unisg.tapas.auctionhouse.application.port.in;
import ch.unisg.tapas.auctionhouse.domain.Bid;
import ch.unisg.tapas.common.SelfValidating;
import lombok.Getter;
import javax.validation.constraints.NotNull;
public class BidReceivedEvent extends SelfValidating<BidReceivedEvent> {
@NotNull
public Bid bid;
public BidReceivedEvent(Bid bid){
this.bid = bid;
validateSelf();
}
}

View File

@ -0,0 +1,5 @@
package ch.unisg.tapas.auctionhouse.application.port.in;
public interface BidReceivedEventHandler {
boolean handleNewBidReceivedEvent(BidReceivedEvent bidReceivedEvent);
}

View File

@ -0,0 +1,122 @@
package ch.unisg.tapas.auctionhouse.domain;
import lombok.Getter;
import lombok.Setter;
import lombok.Value;
import java.util.UUID;
/**This is a domain entity**/
public class Task {
public enum Status {
OPEN, ASSIGNED, RUNNING, EXECUTED
}
@Getter
private final TaskId taskId;
@Getter
private final TaskName taskName;
@Getter
private final TaskType taskType;
@Getter @Setter
public TaskStatus taskStatus; // had to make public for CompleteTaskService
@Getter
public TaskResult taskResult; // same as above
@Getter
private final OriginalTaskUri originalTaskUri;
@Getter @Setter
private ServiceProvider provider;
@Getter @Setter
private InputData inputData;
@Getter @Setter
private OutputData outputData;
public Task(TaskName taskName, TaskType taskType, OriginalTaskUri taskUri) {
this.taskName = taskName;
this.taskType = taskType;
this.taskStatus = new TaskStatus(Status.OPEN);
this.taskId = new TaskId(UUID.randomUUID().toString());
this.taskResult = new TaskResult("");
this.originalTaskUri = taskUri;
this.inputData = null;
this.outputData = null;
}
public Task(TaskName taskName, TaskType taskType, OriginalTaskUri taskUri, TaskStatus taskStatus, TaskId taskId, InputData inputData, ServiceProvider serviceProvider) {
this.taskName = taskName;
this.taskType = taskType;
this.taskStatus = taskStatus;
this.taskId = taskId;
this.taskResult = new TaskResult("");
this.originalTaskUri = taskUri;
this.provider = serviceProvider;
this.inputData = inputData;
this.outputData = new OutputData("");
}
protected static Task createTaskWithNameAndType(TaskName name, TaskType type) {
//This is a simple debug message to see that the request has reached the right method in the core
System.out.println("New Task: " + name.getValue() + " " + type.getValue());
return new Task(name, type, null);
}
protected static Task createTaskWithNameAndTypeAndOriginalTaskUri(TaskName name, TaskType type,
OriginalTaskUri originalTaskUri) {
return new Task(name, type, originalTaskUri);
}
@Value
public static class TaskId {
String value;
}
@Value
public static class TaskName {
String value;
}
@Value
public static class TaskType {
String value;
}
@Value
public static class OriginalTaskUri {
String value;
}
@Value
public static class TaskStatus {
Status value;
}
@Value
public static class ServiceProvider {
String value;
}
@Value
public static class InputData {
String value;
}
@Value
public static class OutputData {
String value;
}
@Value
public static class TaskResult{
private String value;
}
}

View File

@ -5,7 +5,7 @@ websub.hub.publish=https://websub.appspot.com/
group=tapas-group-tutors group=tapas-group-tutors
auction.house.uri=https://tapas-auction-house.86-119-34-23.nip.io/ auction.house.uri=https://tapas-auction-house.86-119-34-23.nip.io/
tasks.list.uri=https://tapas-tasks.86-119-34-23.nip.io/ tasks.list.uri=http://localhost:8081
application.environment=development application.environment=development
auctionhouse.uri=http://localhost:8086 auctionhouse.uri=http://localhost:8086