Add Auction House; Extend uniform HTTP API for TAPAS-Tasks
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
package ch.unisg.tapas;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.adapter.common.clients.TapasMqttClient;
|
||||
import ch.unisg.tapas.auctionhouse.adapter.in.messaging.mqtt.AuctionEventsMqttDispatcher;
|
||||
import ch.unisg.tapas.auctionhouse.adapter.common.clients.WebSubSubscriber;
|
||||
import ch.unisg.tapas.common.AuctionHouseResourceDirectory;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.eclipse.paho.client.mqttv3.MqttException;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Main TAPAS Auction House application.
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class TapasAuctionHouseApplication {
|
||||
private static final Logger LOGGER = LogManager.getLogger(TapasAuctionHouseApplication.class);
|
||||
|
||||
public static String RESOURCE_DIRECTORY = "https://api.interactions.ics.unisg.ch/auction-houses/";
|
||||
public static String MQTT_BROKER = "tcp://broker.hivemq.com:1883";
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication tapasAuctioneerApp = new SpringApplication(TapasAuctionHouseApplication.class);
|
||||
|
||||
// We will use these bootstrap methods in Week 6:
|
||||
// bootstrapMarketplaceWithWebSub();
|
||||
// bootstrapMarketplaceWithMqtt();
|
||||
|
||||
tapasAuctioneerApp.run(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discovers auction houses and subscribes to WebSub notifications
|
||||
*/
|
||||
private static void bootstrapMarketplaceWithWebSub() {
|
||||
List<String> auctionHouseEndpoints = discoverAuctionHouseEndpoints();
|
||||
LOGGER.info("Found auction house endpoints: " + auctionHouseEndpoints);
|
||||
|
||||
WebSubSubscriber subscriber = new WebSubSubscriber();
|
||||
|
||||
for (String endpoint : auctionHouseEndpoints) {
|
||||
subscriber.subscribeToAuctionHouseEndpoint(URI.create(endpoint));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to an MQTT broker, presumably the one used by all TAPAS groups to communicate with
|
||||
* one another
|
||||
*/
|
||||
private static void bootstrapMarketplaceWithMqtt() {
|
||||
try {
|
||||
AuctionEventsMqttDispatcher dispatcher = new AuctionEventsMqttDispatcher();
|
||||
TapasMqttClient client = TapasMqttClient.getInstance(MQTT_BROKER, dispatcher);
|
||||
client.startReceivingMessages();
|
||||
} catch (MqttException e) {
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> discoverAuctionHouseEndpoints() {
|
||||
AuctionHouseResourceDirectory rd = new AuctionHouseResourceDirectory(
|
||||
URI.create(RESOURCE_DIRECTORY)
|
||||
);
|
||||
|
||||
return rd.retrieveAuctionHouseEndpoints();
|
||||
}
|
||||
}
|
@@ -0,0 +1,94 @@
|
||||
package ch.unisg.tapas.auctionhouse.adapter.common.clients;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.adapter.in.messaging.mqtt.AuctionEventsMqttDispatcher;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.eclipse.paho.client.mqttv3.*;
|
||||
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* MQTT client for your TAPAS application. This class is defined as a singleton, but it does not have
|
||||
* to be this way. This class is only provided as an example to help you bootstrap your project.
|
||||
* You are welcomed to change this class as you see fit.
|
||||
*/
|
||||
public class TapasMqttClient {
|
||||
private static final Logger LOGGER = LogManager.getLogger(TapasMqttClient.class);
|
||||
|
||||
private static TapasMqttClient tapasClient = null;
|
||||
|
||||
private MqttClient mqttClient;
|
||||
private final String mqttClientId;
|
||||
private final String brokerAddress;
|
||||
|
||||
private final MessageReceivedCallback messageReceivedCallback;
|
||||
|
||||
private final AuctionEventsMqttDispatcher dispatcher;
|
||||
|
||||
private TapasMqttClient(String brokerAddress, AuctionEventsMqttDispatcher dispatcher) {
|
||||
this.mqttClientId = UUID.randomUUID().toString();
|
||||
this.brokerAddress = brokerAddress;
|
||||
|
||||
this.messageReceivedCallback = new MessageReceivedCallback();
|
||||
|
||||
this.dispatcher = dispatcher;
|
||||
}
|
||||
|
||||
public static synchronized TapasMqttClient getInstance(String brokerAddress,
|
||||
AuctionEventsMqttDispatcher dispatcher) {
|
||||
|
||||
if (tapasClient == null) {
|
||||
tapasClient = new TapasMqttClient(brokerAddress, dispatcher);
|
||||
}
|
||||
|
||||
return tapasClient;
|
||||
}
|
||||
|
||||
public void startReceivingMessages() throws MqttException {
|
||||
mqttClient = new org.eclipse.paho.client.mqttv3.MqttClient(brokerAddress, mqttClientId, new MemoryPersistence());
|
||||
mqttClient.connect();
|
||||
mqttClient.setCallback(messageReceivedCallback);
|
||||
|
||||
subscribeToAllTopics();
|
||||
}
|
||||
|
||||
public void stopReceivingMessages() throws MqttException {
|
||||
mqttClient.disconnect();
|
||||
}
|
||||
|
||||
private void subscribeToAllTopics() throws MqttException {
|
||||
for (String topic : dispatcher.getAllTopics()) {
|
||||
subscribeToTopic(topic);
|
||||
}
|
||||
}
|
||||
|
||||
private void subscribeToTopic(String topic) throws MqttException {
|
||||
mqttClient.subscribe(topic);
|
||||
}
|
||||
|
||||
private void publishMessage(String topic, String payload) throws MqttException {
|
||||
MqttMessage message = new MqttMessage(payload.getBytes(StandardCharsets.UTF_8));
|
||||
mqttClient.publish(topic, message);
|
||||
}
|
||||
|
||||
private class MessageReceivedCallback implements MqttCallback {
|
||||
|
||||
@Override
|
||||
public void connectionLost(Throwable cause) { }
|
||||
|
||||
@Override
|
||||
public void messageArrived(String topic, MqttMessage message) {
|
||||
LOGGER.info("Received new MQTT message for topic " + topic + ": "
|
||||
+ new String(message.getPayload()));
|
||||
|
||||
if (topic != null && !topic.isEmpty()) {
|
||||
dispatcher.dispatchEvent(topic, message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliveryComplete(IMqttDeliveryToken token) { }
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
package ch.unisg.tapas.auctionhouse.adapter.common.clients;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* Subscribes to the WebSub hubs of auction houses discovered at run time. This class is instantiated
|
||||
* from {@link ch.unisg.tapas.TapasAuctionHouseApplication} when boostraping the TAPAS marketplace
|
||||
* via WebSub.
|
||||
*/
|
||||
public class WebSubSubscriber {
|
||||
|
||||
public void subscribeToAuctionHouseEndpoint(URI endpoint) {
|
||||
// TODO Subscribe to the auction house endpoint via WebSub:
|
||||
// 1. Send a request to the auction house in order to discover the WebSub hub to subscribe to.
|
||||
// The request URI should depend on the design of the Auction House HTTP API.
|
||||
// 2. Send a subscription request to the discovered WebSub hub to subscribe to events relevant
|
||||
// for this auction house.
|
||||
// 3. Handle the validation of intent from the WebSub hub (see WebSub protocol).
|
||||
//
|
||||
// Once the subscription is activated, the hub will send "fat pings" with content updates.
|
||||
// The content received from the hub will depend primarily on the design of the Auction House
|
||||
// HTTP API.
|
||||
//
|
||||
// For further details see:
|
||||
// - W3C WebSub Recommendation: https://www.w3.org/TR/websub/
|
||||
// - the implementation notes of the WebSub hub you are using to distribute events
|
||||
}
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
package ch.unisg.tapas.auctionhouse.adapter.common.formats;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.domain.Auction;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Used to expose a representation of the state of an auction through an interface. This class is
|
||||
* only meant as a starting point when defining a uniform HTTP API for the Auction House: feel free
|
||||
* to modify this class as you see fit!
|
||||
*/
|
||||
public class AuctionJsonRepresentation {
|
||||
public static final String MEDIA_TYPE = "application/json";
|
||||
|
||||
@Getter @Setter
|
||||
private String auctionId;
|
||||
|
||||
@Getter @Setter
|
||||
private String auctionHouseUri;
|
||||
|
||||
@Getter @Setter
|
||||
private String taskUri;
|
||||
|
||||
@Getter @Setter
|
||||
private String taskType;
|
||||
|
||||
@Getter @Setter
|
||||
private Integer deadline;
|
||||
|
||||
public AuctionJsonRepresentation() { }
|
||||
|
||||
public AuctionJsonRepresentation(String auctionId, String auctionHouseUri, String taskUri,
|
||||
String taskType, Integer deadline) {
|
||||
this.auctionId = auctionId;
|
||||
this.auctionHouseUri = auctionHouseUri;
|
||||
this.taskUri = taskUri;
|
||||
this.taskType = taskType;
|
||||
this.deadline = deadline;
|
||||
}
|
||||
|
||||
public AuctionJsonRepresentation(Auction auction) {
|
||||
this.auctionId = auction.getAuctionId().getValue();
|
||||
this.auctionHouseUri = auction.getAuctionHouseUri().getValue().toString();
|
||||
this.taskUri = auction.getTaskUri().getValue().toString();
|
||||
this.taskType = auction.getTaskType().getValue();
|
||||
this.deadline = auction.getDeadline().getValue();
|
||||
}
|
||||
|
||||
public static String serialize(Auction auction) throws JsonProcessingException {
|
||||
AuctionJsonRepresentation representation = new AuctionJsonRepresentation(auction);
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
|
||||
return mapper.writeValueAsString(representation);
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
package ch.unisg.tapas.auctionhouse.adapter.in.messaging.http;
|
||||
|
||||
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.ExecutorRegistry;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/*
|
||||
THIS CLASS WILL BE PROVIDED ONLY AS A TEMPLATE; POINT OUT THE API NEEDS TO BE DEFINED
|
||||
*/
|
||||
|
||||
@RestController
|
||||
public class ExecutorAddedEventListenerHttpAdapter {
|
||||
|
||||
@PostMapping(path = "/executors/{taskType}/{executorId}")
|
||||
public ResponseEntity<String> handleExecutorAddedEvent(@PathVariable("taskType") String taskType,
|
||||
@PathVariable("executorId") String executorId) {
|
||||
|
||||
ExecutorAddedEvent executorAddedEvent = new ExecutorAddedEvent(
|
||||
new ExecutorRegistry.ExecutorIdentifier(executorId),
|
||||
new Auction.AuctionedTaskType(taskType)
|
||||
);
|
||||
|
||||
ExecutorAddedHandler newExecutorHandler = new ExecutorAddedHandler();
|
||||
newExecutorHandler.handleNewExecutorEvent(executorAddedEvent);
|
||||
|
||||
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package ch.unisg.tapas.auctionhouse.adapter.in.messaging.http;
|
||||
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Template for handling an executor removed event received via an HTTP request
|
||||
*/
|
||||
@RestController
|
||||
public class ExecutorRemovedEventListenerHttpAdapter {
|
||||
|
||||
// TODO: add annotations for request method, request URI, etc.
|
||||
public void handleExecutorRemovedEvent(@PathVariable("executorId") String executorId) {
|
||||
// TODO: implement logic
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
package ch.unisg.tapas.auctionhouse.adapter.in.messaging.mqtt;
|
||||
|
||||
import org.eclipse.paho.client.mqttv3.MqttMessage;
|
||||
|
||||
/**
|
||||
* Abstract MQTT listener for auction-related events
|
||||
*/
|
||||
public abstract class AuctionEventMqttListener {
|
||||
|
||||
public abstract boolean handleEvent(MqttMessage message);
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
package ch.unisg.tapas.auctionhouse.adapter.in.messaging.mqtt;
|
||||
|
||||
import org.eclipse.paho.client.mqttv3.*;
|
||||
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Dispatches MQTT messages for known topics to associated event listeners. Used in conjunction with
|
||||
* {@link ch.unisg.tapas.auctionhouse.adapter.common.clients.TapasMqttClient}.
|
||||
*
|
||||
* This is where you would define MQTT topics and map them to event listeners (see
|
||||
* {@link AuctionEventsMqttDispatcher#initRouter()}).
|
||||
*
|
||||
* This class is only provided as an example to help you bootstrap the project. You are welcomed to
|
||||
* change this class as you see fit.
|
||||
*/
|
||||
public class AuctionEventsMqttDispatcher {
|
||||
private final Map<String, AuctionEventMqttListener> router;
|
||||
|
||||
public AuctionEventsMqttDispatcher() {
|
||||
this.router = new Hashtable<>();
|
||||
initRouter();
|
||||
}
|
||||
|
||||
// TODO: Register here your topics and event listener adapters
|
||||
private void initRouter() {
|
||||
router.put("ch/unisg/tapas-group-tutors/executors", new ExecutorAddedEventListenerMqttAdapter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all topics registered with this dispatcher.
|
||||
*
|
||||
* @return the set of registered topics
|
||||
*/
|
||||
public Set<String> getAllTopics() {
|
||||
return router.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an event received via MQTT for a given topic.
|
||||
*
|
||||
* @param topic the topic for which the MQTT message was received
|
||||
* @param message the received MQTT message
|
||||
*/
|
||||
public void dispatchEvent(String topic, MqttMessage message) {
|
||||
AuctionEventMqttListener listener = router.get(topic);
|
||||
listener.handleEvent(message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
package ch.unisg.tapas.auctionhouse.adapter.in.messaging.mqtt;
|
||||
|
||||
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.ExecutorRegistry;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.eclipse.paho.client.mqttv3.MqttMessage;
|
||||
|
||||
/**
|
||||
* Listener that handles events when an executor was added to this TAPAS application.
|
||||
*
|
||||
* This class is only provided as an example to help you bootstrap the project.
|
||||
*/
|
||||
public class ExecutorAddedEventListenerMqttAdapter extends AuctionEventMqttListener {
|
||||
|
||||
@Override
|
||||
public boolean handleEvent(MqttMessage message) {
|
||||
String payload = new String(message.getPayload());
|
||||
|
||||
try {
|
||||
// Note: this messge 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 taskType = data.get("taskType").asText();
|
||||
String executorId = data.get("executorId").asText();
|
||||
|
||||
ExecutorAddedEvent executorAddedEvent = new ExecutorAddedEvent(
|
||||
new ExecutorRegistry.ExecutorIdentifier(executorId),
|
||||
new Auction.AuctionedTaskType(taskType)
|
||||
);
|
||||
|
||||
ExecutorAddedHandler newExecutorHandler = new ExecutorAddedHandler();
|
||||
newExecutorHandler.handleNewExecutorEvent(executorAddedEvent);
|
||||
} catch (JsonProcessingException | NullPointerException e) {
|
||||
// TODO: refactor logging
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
package ch.unisg.tapas.auctionhouse.adapter.in.messaging.websub;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.application.handler.AuctionStartedHandler;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* This class is a template for handling auction started events received via WebSub
|
||||
*/
|
||||
@RestController
|
||||
public class AuctionStartedEventListenerWebSubAdapter {
|
||||
private final AuctionStartedHandler auctionStartedHandler;
|
||||
|
||||
public AuctionStartedEventListenerWebSubAdapter(AuctionStartedHandler auctionStartedHandler) {
|
||||
this.auctionStartedHandler = auctionStartedHandler;
|
||||
}
|
||||
|
||||
//TODO
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
package ch.unisg.tapas.auctionhouse.adapter.in.web;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.adapter.common.formats.AuctionJsonRepresentation;
|
||||
import ch.unisg.tapas.auctionhouse.application.port.in.LaunchAuctionCommand;
|
||||
import ch.unisg.tapas.auctionhouse.application.port.in.LaunchAuctionUseCase;
|
||||
import ch.unisg.tapas.auctionhouse.domain.Auction;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
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 org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* Controller that handles HTTP requests for launching auctions. This controller implements the
|
||||
* {@link LaunchAuctionUseCase} use case using the {@link LaunchAuctionCommand}.
|
||||
*/
|
||||
@RestController
|
||||
public class LaunchAuctionWebController {
|
||||
private final LaunchAuctionUseCase launchAuctionUseCase;
|
||||
|
||||
/**
|
||||
* Constructs the controller.
|
||||
*
|
||||
* @param launchAuctionUseCase an implementation of the launch auction use case
|
||||
*/
|
||||
public LaunchAuctionWebController(LaunchAuctionUseCase launchAuctionUseCase) {
|
||||
this.launchAuctionUseCase = launchAuctionUseCase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles HTTP POST requests for launching auctions. Note: you are free to modify this handler
|
||||
* as you see fit to reflect the discussions for the uniform HTTP API for the auction house.
|
||||
* You should also ensure that this handler has the exact behavior you would expect from the
|
||||
* defined uniform HTTP API (status codes, returned payload, HTTP headers, etc.)
|
||||
*
|
||||
* @param payload a representation of the auction to be launched
|
||||
* @return
|
||||
*/
|
||||
@PostMapping(path = "/auctions/", consumes = AuctionJsonRepresentation.MEDIA_TYPE)
|
||||
public ResponseEntity<String> launchAuction(@RequestBody AuctionJsonRepresentation payload) {
|
||||
Auction.AuctionDeadline deadline = (payload.getDeadline() == null) ?
|
||||
null : new Auction.AuctionDeadline(payload.getDeadline());
|
||||
|
||||
LaunchAuctionCommand command = new LaunchAuctionCommand(
|
||||
new Auction.AuctionedTaskUri(URI.create(payload.getTaskUri())),
|
||||
new Auction.AuctionedTaskType(payload.getTaskType()),
|
||||
deadline
|
||||
);
|
||||
|
||||
// This command returns the created auction. We need the created auction to be able to
|
||||
// include a representation of it in the HTTP response.
|
||||
Auction auction = launchAuctionUseCase.launchAuction(command);
|
||||
|
||||
try {
|
||||
AuctionJsonRepresentation representation = new AuctionJsonRepresentation(auction);
|
||||
String auctionJson = AuctionJsonRepresentation.serialize(auction);
|
||||
|
||||
HttpHeaders responseHeaders = new HttpHeaders();
|
||||
responseHeaders.add(HttpHeaders.CONTENT_TYPE, AuctionJsonRepresentation.MEDIA_TYPE);
|
||||
|
||||
// Return a 201 Created status code and a representation of the created auction
|
||||
return new ResponseEntity<>(auctionJson, responseHeaders, HttpStatus.CREATED);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
package ch.unisg.tapas.auctionhouse.adapter.in.web;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.adapter.common.formats.AuctionJsonRepresentation;
|
||||
import ch.unisg.tapas.auctionhouse.application.port.in.RetrieveOpenAuctionsQuery;
|
||||
import ch.unisg.tapas.auctionhouse.application.port.in.RetrieveOpenAuctionsUseCase;
|
||||
import ch.unisg.tapas.auctionhouse.domain.Auction;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
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.RestController;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Controller that handles HTTP requests for retrieving auctions hosted by this auction house that
|
||||
* are open for bids. This controller implements the {@link RetrieveOpenAuctionsUseCase} use case
|
||||
* using the {@link RetrieveOpenAuctionsQuery}.
|
||||
*/
|
||||
@RestController
|
||||
public class RetrieveOpenAuctionsWebController {
|
||||
private final RetrieveOpenAuctionsUseCase retrieveAuctionListUseCase;
|
||||
|
||||
public RetrieveOpenAuctionsWebController(RetrieveOpenAuctionsUseCase retrieveAuctionListUseCase) {
|
||||
this.retrieveAuctionListUseCase = retrieveAuctionListUseCase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles HTTP GET requests to retrieve the auctions that are open. Note: you are free to modify
|
||||
* this handler as you see fit to reflect the discussions for the uniform HTTP API for the
|
||||
* auction house. You should also ensure that this handler has the exact behavior you would expect
|
||||
* from the defined uniform HTTP API (status codes, returned payload, HTTP headers, etc.).
|
||||
*
|
||||
* @return a representation of a collection with the auctions that are open for bids
|
||||
*/
|
||||
@GetMapping(path = "/auctions/")
|
||||
public ResponseEntity<String> retrieveOpenAuctions() {
|
||||
Collection<Auction> auctions =
|
||||
retrieveAuctionListUseCase.retrieveAuctions(new RetrieveOpenAuctionsQuery());
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
ArrayNode array = mapper.createArrayNode();
|
||||
|
||||
for (Auction auction : auctions) {
|
||||
AuctionJsonRepresentation representation = new AuctionJsonRepresentation(auction);
|
||||
JsonNode node = mapper.valueToTree(representation);
|
||||
array.add(node);
|
||||
}
|
||||
|
||||
HttpHeaders responseHeaders = new HttpHeaders();
|
||||
responseHeaders.add(HttpHeaders.CONTENT_TYPE, "application/json");
|
||||
|
||||
// TODO before providing to students: remove hub links
|
||||
responseHeaders.add(HttpHeaders.LINK, "<https://pubsubhubbub.appspot.com/>; rel=\"hub\"");
|
||||
responseHeaders.add(HttpHeaders.LINK, "<http://example.org/auctions/>; rel=\"self\"");
|
||||
|
||||
return new ResponseEntity<>(array.toString(), responseHeaders, HttpStatus.OK);
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
package ch.unisg.tapas.auctionhouse.adapter.out.messaging.websub;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.application.port.out.AuctionStartedEventPort;
|
||||
import ch.unisg.tapas.auctionhouse.domain.Auction;
|
||||
import ch.unisg.tapas.auctionhouse.domain.AuctionStartedEvent;
|
||||
import ch.unisg.tapas.common.ConfigProperties;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* This class is a template for publishing auction started events via WebSub.
|
||||
*/
|
||||
@Component
|
||||
@Primary
|
||||
public class PublishAuctionStartedEventWebSubAdapter implements AuctionStartedEventPort {
|
||||
// You can use this object to retrieve properties from application.properties, e.g. the
|
||||
// WebSub hub publish endpoint, etc.
|
||||
@Autowired
|
||||
private ConfigProperties config;
|
||||
|
||||
@Override
|
||||
public void publishAuctionStartedEvent(AuctionStartedEvent event) {
|
||||
// TODO
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
package ch.unisg.tapas.auctionhouse.adapter.out.web;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.application.port.out.AuctionWonEventPort;
|
||||
import ch.unisg.tapas.auctionhouse.domain.AuctionWonEvent;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 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,
|
||||
* WebSub, etc.).
|
||||
*/
|
||||
@Component
|
||||
@Primary
|
||||
public class AuctionWonEventHttpAdapter implements AuctionWonEventPort {
|
||||
@Override
|
||||
public void publishAuctionWonEvent(AuctionWonEvent event) {
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
package ch.unisg.tapas.auctionhouse.adapter.out.web;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.application.port.out.PlaceBidForAuctionCommand;
|
||||
import ch.unisg.tapas.auctionhouse.application.port.out.PlaceBidForAuctionCommandPort;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* This class is a tempalte for implementing a place bid for auction command via HTTP.
|
||||
*/
|
||||
@Component
|
||||
@Primary
|
||||
public class PlaceBidForAuctionCommandHttpAdapter implements PlaceBidForAuctionCommandPort {
|
||||
|
||||
@Override
|
||||
public void placeBid(PlaceBidForAuctionCommand command) {
|
||||
// TODO
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
package ch.unisg.tapas.auctionhouse.application.handler;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.application.port.in.AuctionStartedEvent;
|
||||
import ch.unisg.tapas.auctionhouse.application.port.in.AuctionStartedEventHandler;
|
||||
import ch.unisg.tapas.auctionhouse.application.port.out.PlaceBidForAuctionCommand;
|
||||
import ch.unisg.tapas.auctionhouse.application.port.out.PlaceBidForAuctionCommandPort;
|
||||
import ch.unisg.tapas.auctionhouse.domain.Auction;
|
||||
import ch.unisg.tapas.auctionhouse.domain.Bid;
|
||||
import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry;
|
||||
import ch.unisg.tapas.common.ConfigProperties;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Handler for auction started events. This handler will automatically bid in any auction for a
|
||||
* task of known type, i.e. a task for which the auction house knows an executor is available.
|
||||
*/
|
||||
@Component
|
||||
public class AuctionStartedHandler implements AuctionStartedEventHandler {
|
||||
private static final Logger LOGGER = LogManager.getLogger(AuctionStartedHandler.class);
|
||||
|
||||
@Autowired
|
||||
private ConfigProperties config;
|
||||
|
||||
@Autowired
|
||||
private PlaceBidForAuctionCommandPort placeBidForAuctionCommandPort;
|
||||
|
||||
/**
|
||||
* Handles an auction started event and bids in all auctions for tasks of known types.
|
||||
*
|
||||
* @param auctionStartedEvent the auction started domain event
|
||||
* @return true unless a runtime exception occurs
|
||||
*/
|
||||
@Override
|
||||
public boolean handleAuctionStartedEvent(AuctionStartedEvent auctionStartedEvent) {
|
||||
Auction auction = auctionStartedEvent.getAuction();
|
||||
|
||||
if (ExecutorRegistry.getInstance().containsTaskType(auction.getTaskType())) {
|
||||
LOGGER.info("Placing bid for task " + auction.getTaskUri() + " of type "
|
||||
+ auction.getTaskType() + " in auction " + auction.getAuctionId()
|
||||
+ " from auction house " + auction.getAuctionHouseUri().getValue().toString());
|
||||
|
||||
Bid bid = new Bid(auction.getAuctionId(),
|
||||
new Bid.BidderName(config.getGroupName()),
|
||||
new Bid.BidderAuctionHouseUri(config.getAuctionHouseUri()),
|
||||
new Bid.BidderTaskListUri(config.getTaskListUri())
|
||||
);
|
||||
|
||||
PlaceBidForAuctionCommand command = new PlaceBidForAuctionCommand(auction, bid);
|
||||
placeBidForAuctionCommandPort.placeBid(command);
|
||||
} else {
|
||||
LOGGER.info("Cannot execute this task type: " + auction.getTaskType().getValue());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package ch.unisg.tapas.auctionhouse.application.handler;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.application.port.in.ExecutorAddedEvent;
|
||||
import ch.unisg.tapas.auctionhouse.application.port.in.ExecutorAddedEventHandler;
|
||||
import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class ExecutorAddedHandler implements ExecutorAddedEventHandler {
|
||||
|
||||
@Override
|
||||
public boolean handleNewExecutorEvent(ExecutorAddedEvent executorAddedEvent) {
|
||||
return ExecutorRegistry.getInstance().addExecutor(executorAddedEvent.getTaskType(),
|
||||
executorAddedEvent.getExecutorId());
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
package ch.unisg.tapas.auctionhouse.application.handler;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.application.port.in.ExecutorRemovedEvent;
|
||||
import ch.unisg.tapas.auctionhouse.application.port.in.ExecutorRemovedEventHandler;
|
||||
import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Handler for executor removed events. It removes the executor from this auction house's executor
|
||||
* registry.
|
||||
*/
|
||||
@Component
|
||||
public class ExecutorRemovedHandler implements ExecutorRemovedEventHandler {
|
||||
|
||||
@Override
|
||||
public boolean handleExecutorRemovedEvent(ExecutorRemovedEvent executorRemovedEvent) {
|
||||
return ExecutorRegistry.getInstance().removeExecutor(executorRemovedEvent.getExecutorId());
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package ch.unisg.tapas.auctionhouse.application.port.in;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.domain.Auction;
|
||||
import ch.unisg.tapas.common.SelfValidating;
|
||||
import lombok.Value;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* Event that notifies this auction house that an auction was started by another auction house.
|
||||
*/
|
||||
@Value
|
||||
public class AuctionStartedEvent extends SelfValidating<AuctionStartedEvent> {
|
||||
@NotNull
|
||||
private final Auction auction;
|
||||
|
||||
public AuctionStartedEvent(Auction auction) {
|
||||
this.auction = auction;
|
||||
this.validateSelf();
|
||||
}
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
package ch.unisg.tapas.auctionhouse.application.port.in;
|
||||
|
||||
public interface AuctionStartedEventHandler {
|
||||
|
||||
boolean handleAuctionStartedEvent(AuctionStartedEvent auctionStartedEvent);
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
package ch.unisg.tapas.auctionhouse.application.port.in;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.domain.Auction.AuctionedTaskType;
|
||||
import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry.ExecutorIdentifier;
|
||||
import ch.unisg.tapas.common.SelfValidating;
|
||||
import lombok.Value;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* Event that notifies the auction house that an executor has been added to this TAPAS application.
|
||||
*/
|
||||
@Value
|
||||
public class ExecutorAddedEvent extends SelfValidating<ExecutorAddedEvent> {
|
||||
@NotNull
|
||||
private final ExecutorIdentifier executorId;
|
||||
|
||||
@NotNull
|
||||
private final AuctionedTaskType taskType;
|
||||
|
||||
/**
|
||||
* Constructs an executor added event.
|
||||
*
|
||||
* @param executorId the identifier of the executor that was added to this TAPAS application
|
||||
*/
|
||||
public ExecutorAddedEvent(ExecutorIdentifier executorId, AuctionedTaskType taskType) {
|
||||
this.executorId = executorId;
|
||||
this.taskType = taskType;
|
||||
|
||||
this.validateSelf();
|
||||
}
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
package ch.unisg.tapas.auctionhouse.application.port.in;
|
||||
|
||||
public interface ExecutorAddedEventHandler {
|
||||
|
||||
boolean handleNewExecutorEvent(ExecutorAddedEvent executorAddedEvent);
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package ch.unisg.tapas.auctionhouse.application.port.in;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.domain.ExecutorRegistry.ExecutorIdentifier;
|
||||
import ch.unisg.tapas.common.SelfValidating;
|
||||
import lombok.Value;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* Event that notifies the auction house that an executor has been removed from this TAPAS application.
|
||||
*/
|
||||
@Value
|
||||
public class ExecutorRemovedEvent extends SelfValidating<ExecutorRemovedEvent> {
|
||||
@NotNull
|
||||
private final ExecutorIdentifier executorId;
|
||||
|
||||
/**
|
||||
* Constructs an executor removed event.
|
||||
*
|
||||
* @param executorId the identifier of the executor that was removed from this TAPAS application
|
||||
*/
|
||||
public ExecutorRemovedEvent(ExecutorIdentifier executorId) {
|
||||
this.executorId = executorId;
|
||||
this.validateSelf();
|
||||
}
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
package ch.unisg.tapas.auctionhouse.application.port.in;
|
||||
|
||||
public interface ExecutorRemovedEventHandler {
|
||||
|
||||
boolean handleExecutorRemovedEvent(ExecutorRemovedEvent executorRemovedEvent);
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
package ch.unisg.tapas.auctionhouse.application.port.in;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.domain.Auction;
|
||||
import ch.unisg.tapas.common.SelfValidating;
|
||||
import lombok.Value;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* Command for launching an auction in this auction house.
|
||||
*/
|
||||
@Value
|
||||
public class LaunchAuctionCommand extends SelfValidating<LaunchAuctionCommand> {
|
||||
@NotNull
|
||||
private final Auction.AuctionedTaskUri taskUri;
|
||||
|
||||
@NotNull
|
||||
private final Auction.AuctionedTaskType taskType;
|
||||
|
||||
private final Auction.AuctionDeadline deadline;
|
||||
|
||||
/**
|
||||
* Constructs the launch action command.
|
||||
*
|
||||
* @param taskUri the URI of the auctioned task
|
||||
* @param taskType the type of the auctioned task
|
||||
* @param deadline the deadline by which the auction should receive bids (can be null if none)
|
||||
*/
|
||||
public LaunchAuctionCommand(Auction.AuctionedTaskUri taskUri, Auction.AuctionedTaskType taskType,
|
||||
Auction.AuctionDeadline deadline) {
|
||||
this.taskUri = taskUri;
|
||||
this.taskType = taskType;
|
||||
this.deadline = deadline;
|
||||
|
||||
this.validateSelf();
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
package ch.unisg.tapas.auctionhouse.application.port.in;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.domain.Auction;
|
||||
|
||||
public interface LaunchAuctionUseCase {
|
||||
|
||||
Auction launchAuction(LaunchAuctionCommand command);
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
package ch.unisg.tapas.auctionhouse.application.port.in;
|
||||
|
||||
/**
|
||||
* Query used to retrieve open auctions. Although this query is empty, we model it to convey the
|
||||
* domain semantics and to reduce coupling.
|
||||
*/
|
||||
public class RetrieveOpenAuctionsQuery { }
|
@@ -0,0 +1,10 @@
|
||||
package ch.unisg.tapas.auctionhouse.application.port.in;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.domain.Auction;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface RetrieveOpenAuctionsUseCase {
|
||||
|
||||
Collection<Auction> retrieveAuctions(RetrieveOpenAuctionsQuery query);
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
package ch.unisg.tapas.auctionhouse.application.port.out;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.domain.AuctionStartedEvent;
|
||||
|
||||
/**
|
||||
* Port for sending out auction started events
|
||||
*/
|
||||
public interface AuctionStartedEventPort {
|
||||
|
||||
void publishAuctionStartedEvent(AuctionStartedEvent event);
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
package ch.unisg.tapas.auctionhouse.application.port.out;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.domain.AuctionWonEvent;
|
||||
|
||||
/**
|
||||
* Port for sending out auction won events
|
||||
*/
|
||||
public interface AuctionWonEventPort {
|
||||
|
||||
void publishAuctionWonEvent(AuctionWonEvent event);
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
package ch.unisg.tapas.auctionhouse.application.port.out;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.domain.Auction;
|
||||
import ch.unisg.tapas.auctionhouse.domain.Bid;
|
||||
import ch.unisg.tapas.common.SelfValidating;
|
||||
import lombok.Value;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* Command to place a bid for a given auction.
|
||||
*/
|
||||
@Value
|
||||
public class PlaceBidForAuctionCommand extends SelfValidating<PlaceBidForAuctionCommand> {
|
||||
@NotNull
|
||||
private final Auction auction;
|
||||
@NotNull
|
||||
private final Bid bid;
|
||||
|
||||
public PlaceBidForAuctionCommand(Auction auction, Bid bid) {
|
||||
this.auction = auction;
|
||||
this.bid = bid;
|
||||
this.validateSelf();
|
||||
}
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
package ch.unisg.tapas.auctionhouse.application.port.out;
|
||||
|
||||
public interface PlaceBidForAuctionCommandPort {
|
||||
|
||||
void placeBid(PlaceBidForAuctionCommand command);
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
package ch.unisg.tapas.auctionhouse.application.service;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.application.port.in.RetrieveOpenAuctionsQuery;
|
||||
import ch.unisg.tapas.auctionhouse.application.port.in.RetrieveOpenAuctionsUseCase;
|
||||
import ch.unisg.tapas.auctionhouse.domain.Auction;
|
||||
import ch.unisg.tapas.auctionhouse.domain.AuctionRegistry;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Service that implements {@link RetrieveOpenAuctionsUseCase} to retrieve all auctions in this auction
|
||||
* house that are open for bids.
|
||||
*/
|
||||
@Component
|
||||
public class RetrieveOpenAuctionsService implements RetrieveOpenAuctionsUseCase {
|
||||
|
||||
@Override
|
||||
public Collection<Auction> retrieveAuctions(RetrieveOpenAuctionsQuery query) {
|
||||
return AuctionRegistry.getInstance().getOpenAuctions();
|
||||
}
|
||||
}
|
@@ -0,0 +1,113 @@
|
||||
package ch.unisg.tapas.auctionhouse.application.service;
|
||||
|
||||
import ch.unisg.tapas.auctionhouse.application.port.in.LaunchAuctionCommand;
|
||||
import ch.unisg.tapas.auctionhouse.application.port.in.LaunchAuctionUseCase;
|
||||
import ch.unisg.tapas.auctionhouse.application.port.out.AuctionWonEventPort;
|
||||
import ch.unisg.tapas.auctionhouse.application.port.out.AuctionStartedEventPort;
|
||||
import ch.unisg.tapas.auctionhouse.domain.*;
|
||||
import ch.unisg.tapas.common.ConfigProperties;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Service that implements the {@link LaunchAuctionUseCase} to start an auction. If a deadline is
|
||||
* specified for the auction, the service automatically closes the auction at the deadline. If a
|
||||
* deadline is not specified, the service closes the auction after 10s by default.
|
||||
*/
|
||||
@Component
|
||||
public class StartAuctionService implements LaunchAuctionUseCase {
|
||||
private static final Logger LOGGER = LogManager.getLogger(StartAuctionService.class);
|
||||
|
||||
private final static int DEFAULT_AUCTION_DEADLINE_MILLIS = 10000;
|
||||
|
||||
// Event port used to publish an auction started event
|
||||
private final AuctionStartedEventPort auctionStartedEventPort;
|
||||
// Event port used to publish an auction won event
|
||||
private final AuctionWonEventPort auctionWonEventPort;
|
||||
|
||||
private final ScheduledExecutorService service;
|
||||
private final AuctionRegistry auctions;
|
||||
|
||||
@Autowired
|
||||
private ConfigProperties config;
|
||||
|
||||
public StartAuctionService(AuctionStartedEventPort auctionStartedEventPort,
|
||||
AuctionWonEventPort auctionWonEventPort) {
|
||||
this.auctionStartedEventPort = auctionStartedEventPort;
|
||||
this.auctionWonEventPort = auctionWonEventPort;
|
||||
this.auctions = AuctionRegistry.getInstance();
|
||||
this.service = Executors.newScheduledThreadPool(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches an auction.
|
||||
*
|
||||
* @param command the domain command used to launch the auction (see {@link LaunchAuctionCommand})
|
||||
* @return the launched auction
|
||||
*/
|
||||
@Override
|
||||
public Auction launchAuction(LaunchAuctionCommand command) {
|
||||
Auction.AuctionDeadline deadline = (command.getDeadline() == null) ?
|
||||
new Auction.AuctionDeadline(DEFAULT_AUCTION_DEADLINE_MILLIS) : command.getDeadline();
|
||||
|
||||
// Create a new auction and add it to the auction registry
|
||||
Auction auction = new Auction(new Auction.AuctionHouseUri(config.getAuctionHouseUri()),
|
||||
command.getTaskUri(), command.getTaskType(), deadline);
|
||||
auctions.addAuction(auction);
|
||||
|
||||
// Schedule the closing of the auction at the deadline
|
||||
service.schedule(new CloseAuctionTask(auction.getAuctionId()), deadline.getValue(),
|
||||
TimeUnit.MILLISECONDS);
|
||||
|
||||
// Publish an auction started event
|
||||
AuctionStartedEvent auctionStartedEvent = new AuctionStartedEvent(auction);
|
||||
auctionStartedEventPort.publishAuctionStartedEvent(auctionStartedEvent);
|
||||
|
||||
return auction;
|
||||
}
|
||||
|
||||
/**
|
||||
* This task closes the auction at the deadline and selects a winner if any bids were placed. It
|
||||
* also sends out associated events and commands.
|
||||
*/
|
||||
private class CloseAuctionTask implements Runnable {
|
||||
Auction.AuctionId auctionId;
|
||||
|
||||
public CloseAuctionTask(Auction.AuctionId auctionId) {
|
||||
this.auctionId = auctionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Optional<Auction> auctionOpt = auctions.getAuctionById(auctionId);
|
||||
|
||||
if (auctionOpt.isPresent()) {
|
||||
Auction auction = auctionOpt.get();
|
||||
Optional<Bid> bid = auction.selectBid();
|
||||
|
||||
// Close the auction
|
||||
auction.close();
|
||||
|
||||
if (bid.isPresent()) {
|
||||
// Notify the bidder
|
||||
Bid.BidderName bidderName = bid.get().getBidderName();
|
||||
LOGGER.info("Auction #" + auction.getAuctionId().getValue() + " for task "
|
||||
+ auction.getTaskUri().getValue() + " won by " + bidderName.getValue());
|
||||
|
||||
// Send an auction won event for the winning bid
|
||||
auctionWonEventPort.publishAuctionWonEvent(new AuctionWonEvent(bid.get()));
|
||||
} else {
|
||||
LOGGER.info("Auction #" + auction.getAuctionId().getValue() + " ended with no bids for task "
|
||||
+ auction.getTaskUri().getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,171 @@
|
||||
package ch.unisg.tapas.auctionhouse.domain;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Value;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Domain entity that models an auction.
|
||||
*/
|
||||
public class Auction {
|
||||
// Auctions have two possible states:
|
||||
// - open: waiting for bids
|
||||
// - closed: the auction deadline has expired, there may or may not be a winning bid
|
||||
public enum Status {
|
||||
OPEN, CLOSED
|
||||
}
|
||||
|
||||
// One way to generate auction identifiers is incremental starting from 1. This makes identifiers
|
||||
// predictable, which can help with debugging when multiple parties are interacting, but it also
|
||||
// means that auction identifiers are not universally unique unless they are part of a URI.
|
||||
// An alternative would be to use UUIDs (see constructor).
|
||||
private static long AUCTION_COUNTER = 1;
|
||||
|
||||
@Getter
|
||||
private AuctionId auctionId;
|
||||
|
||||
@Getter
|
||||
private AuctionStatus auctionStatus;
|
||||
|
||||
// URI that identifies the auction house that started this auction. Given a uniform, standard
|
||||
// HTTP API for auction houses, this URI can then be used as a base URI for interacting with
|
||||
// the identified auction house.
|
||||
@Getter
|
||||
private final AuctionHouseUri auctionHouseUri;
|
||||
|
||||
// URI that identifies the task for which the auction was launched. URIs are uniform identifiers
|
||||
// and can be referenced independent of context: because we have defined a uniform HTTP API for
|
||||
// TAPAS-Tasks, we can dereference this URI to retrieve a complete representation of the
|
||||
// auctioned task.
|
||||
@Getter
|
||||
private final AuctionedTaskUri taskUri;
|
||||
|
||||
// The type of the task being auctioned. We could also retrieve the task type by dereferencing
|
||||
// the task's URI, but given that the bidding is defined primarily based on task types, knowing
|
||||
// the task type avoids an additional HTTP request.
|
||||
@Getter
|
||||
private final AuctionedTaskType taskType;
|
||||
|
||||
// The deadline by which bids can be placed. Once the deadline expires, the auction is closed.
|
||||
@Getter
|
||||
private final AuctionDeadline deadline;
|
||||
|
||||
// Available bids.
|
||||
@Getter
|
||||
private final List<Bid> bids;
|
||||
|
||||
/**
|
||||
* Constructs an auction.
|
||||
*
|
||||
* @param auctionHouseUri the URI of the auction hause that started the auction
|
||||
* @param taskUri the URI of the task being auctioned
|
||||
* @param taskType the type of the task being auctioned
|
||||
* @param deadline the deadline by which the auction is open for bids
|
||||
*/
|
||||
public Auction(AuctionHouseUri auctionHouseUri, AuctionedTaskUri taskUri,
|
||||
AuctionedTaskType taskType, AuctionDeadline deadline) {
|
||||
// Generates an incremental identifier
|
||||
this.auctionId = new AuctionId("" + AUCTION_COUNTER ++);
|
||||
// As an alternative, we could also generate an UUID
|
||||
// this.auctionId = new AuctionId(UUID.randomUUID().toString());
|
||||
|
||||
this.auctionStatus = new AuctionStatus(Status.OPEN);
|
||||
|
||||
this.auctionHouseUri = auctionHouseUri;
|
||||
this.taskUri = taskUri;
|
||||
this.taskType = taskType;
|
||||
|
||||
this.deadline = deadline;
|
||||
this.bids = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an auction.
|
||||
*
|
||||
* @param auctionId the identifier of the auction
|
||||
* @param auctionHouseUri the URI of the auction hause that started the auction
|
||||
* @param taskUri the URI of the task being auctioned
|
||||
* @param taskType the type of the task being auctioned
|
||||
* @param deadline the deadline by which the auction is open for bids
|
||||
*/
|
||||
public Auction(AuctionId auctionId, AuctionHouseUri auctionHouseUri, AuctionedTaskUri taskUri,
|
||||
AuctionedTaskType taskType, AuctionDeadline deadline) {
|
||||
this(auctionHouseUri, taskUri, taskType, deadline);
|
||||
this.auctionId = auctionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Places a bid for this auction.
|
||||
*
|
||||
* @param bid the bid
|
||||
*/
|
||||
public void addBid(Bid bid) {
|
||||
bids.add(bid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a bid randomly from the bids available for this auction.
|
||||
*
|
||||
* @return a winning bid or Optional.empty if no bid was made in this auction.
|
||||
*/
|
||||
public Optional<Bid> selectBid() {
|
||||
if (bids.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
int index = new Random().nextInt(bids.size());
|
||||
return Optional.of(bids.get(index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the auction is open for bids.
|
||||
*
|
||||
* @return true if open for bids, false if the auction is closed
|
||||
*/
|
||||
public boolean isOpen() {
|
||||
return auctionStatus.getValue() == Status.OPEN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the auction. Called by the StartAuctionService after the auction deadline has expired.
|
||||
*/
|
||||
public void close() {
|
||||
auctionStatus = new AuctionStatus(Status.CLOSED);
|
||||
}
|
||||
|
||||
/*
|
||||
* Definitions of Value Objects
|
||||
*/
|
||||
|
||||
@Value
|
||||
public static class AuctionId {
|
||||
String value;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class AuctionStatus {
|
||||
Status value;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class AuctionHouseUri {
|
||||
URI value;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class AuctionedTaskUri {
|
||||
URI value;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class AuctionedTaskType {
|
||||
String value;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class AuctionDeadline {
|
||||
int value;
|
||||
}
|
||||
}
|
@@ -0,0 +1,105 @@
|
||||
package ch.unisg.tapas.auctionhouse.domain;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Registry that keeps an in-memory history of auctions (both open for bids and closed). This class
|
||||
* is a singleton. See also {@link Auction}.
|
||||
*/
|
||||
public class AuctionRegistry {
|
||||
private static AuctionRegistry registry;
|
||||
|
||||
private final Map<Auction.AuctionId, Auction> auctions;
|
||||
|
||||
private AuctionRegistry() {
|
||||
this.auctions = new Hashtable<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a reference to the auction registry.
|
||||
*
|
||||
* @return the auction registry
|
||||
*/
|
||||
public static synchronized AuctionRegistry getInstance() {
|
||||
if (registry == null) {
|
||||
registry = new AuctionRegistry();
|
||||
}
|
||||
|
||||
return registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new auction to the registry
|
||||
* @param auction the new auction
|
||||
*/
|
||||
public void addAuction(Auction auction) {
|
||||
auctions.put(auction.getAuctionId(), auction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Places a bid. See also {@link Bid}.
|
||||
*
|
||||
* @param bid the bid to be placed.
|
||||
* @return false if the bid is for an auction with an unknown identifier, true otherwise
|
||||
*/
|
||||
public boolean placeBid(Bid bid) {
|
||||
if (!containsAuctionWithId(bid.getAuctionId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Auction auction = getAuctionById(bid.getAuctionId()).get();
|
||||
auction.addBid(bid);
|
||||
auctions.put(bid.getAuctionId(), auction);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the registry contains an auction with the given identifier.
|
||||
*
|
||||
* @param auctionId the auction's identifier
|
||||
* @return true if the registry contains an auction with the given identifier, false otherwise
|
||||
*/
|
||||
public boolean containsAuctionWithId(Auction.AuctionId auctionId) {
|
||||
return auctions.containsKey(auctionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the auction with the given identifier if it exists.
|
||||
*
|
||||
* @param auctionId the auction's identifier
|
||||
* @return the auction or Optional.empty if the identifier is unknown
|
||||
*/
|
||||
public Optional<Auction> getAuctionById(Auction.AuctionId auctionId) {
|
||||
if (containsAuctionWithId(auctionId)) {
|
||||
return Optional.of(auctions.get(auctionId));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all auctions in the registry.
|
||||
*
|
||||
* @return a collection with all auctions
|
||||
*/
|
||||
public Collection<Auction> getAllAuctions() {
|
||||
return auctions.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves only the auctions that are open for bids.
|
||||
*
|
||||
* @return a collection with all open auctions
|
||||
*/
|
||||
public Collection<Auction> getOpenAuctions() {
|
||||
return getAllAuctions()
|
||||
.stream()
|
||||
.filter(auction -> auction.isOpen())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
package ch.unisg.tapas.auctionhouse.domain;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* A domain event that models an auction has started.
|
||||
*/
|
||||
public class AuctionStartedEvent {
|
||||
@Getter
|
||||
private Auction auction;
|
||||
|
||||
public AuctionStartedEvent(Auction auction) {
|
||||
this.auction = auction;
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package ch.unisg.tapas.auctionhouse.domain;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* A domain event that models an auction was won.
|
||||
*/
|
||||
public class AuctionWonEvent {
|
||||
// The winning bid
|
||||
@Getter
|
||||
private Bid winningBid;
|
||||
|
||||
public AuctionWonEvent(Bid winningBid) {
|
||||
this.winningBid = winningBid;
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
package ch.unisg.tapas.auctionhouse.domain;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Value;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* Domain entity that models a bid.
|
||||
*/
|
||||
public class Bid {
|
||||
// The identifier of the auction for which the bid is placed
|
||||
@Getter
|
||||
private final Auction.AuctionId auctionId;
|
||||
|
||||
// The name of the bidder, i.e. the identifier of the TAPAS group
|
||||
@Getter
|
||||
private final BidderName bidderName;
|
||||
|
||||
// URI that identifies the auction house of the bidder. Given a uniform, standard HTTP API for
|
||||
// auction houses, this URI can then be used as a base URI for interacting with the auction house
|
||||
// of the bidder.
|
||||
@Getter
|
||||
private final BidderAuctionHouseUri bidderAuctionHouseUri;
|
||||
|
||||
// URI that identifies the TAPAS-Tasks task list of the bidder. Given a uniform, standard HTTP API
|
||||
// for TAPAS-Tasks, this URI can then be used as a base URI for interacting with the list of tasks
|
||||
// of the bidder, e.g. to delegate a task.
|
||||
@Getter
|
||||
private final BidderTaskListUri bidderTaskListUri;
|
||||
|
||||
/**
|
||||
* Constructs a bid.
|
||||
*
|
||||
* @param auctionId the identifier of the auction for which the bid is placed
|
||||
* @param bidderName the name of the bidder, i.e. the identifier of the TAPAS group
|
||||
* @param auctionHouseUri the URI of the bidder's auction house
|
||||
* @param taskListUri the URI fo the bidder's list of tasks
|
||||
*/
|
||||
public Bid(Auction.AuctionId auctionId, BidderName bidderName, BidderAuctionHouseUri auctionHouseUri,
|
||||
BidderTaskListUri taskListUri) {
|
||||
this.auctionId = auctionId;
|
||||
this.bidderName = bidderName;
|
||||
this.bidderAuctionHouseUri = auctionHouseUri;
|
||||
this.bidderTaskListUri = taskListUri;
|
||||
}
|
||||
|
||||
/*
|
||||
* Definitions of Value Objects
|
||||
*/
|
||||
|
||||
@Value
|
||||
public static class BidderName {
|
||||
private String value;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class BidderAuctionHouseUri {
|
||||
private URI value;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class BidderTaskListUri {
|
||||
private URI value;
|
||||
}
|
||||
}
|
@@ -0,0 +1,86 @@
|
||||
package ch.unisg.tapas.auctionhouse.domain;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Registry that keeps a track of executors internal to the TAPAS application and the types of tasks
|
||||
* they can achieve. One executor may correspond to multiple task types. This mapping is used when
|
||||
* bidding for tasks: the auction house will only bid for tasks for which there is a known executor.
|
||||
* This class is a singleton.
|
||||
*/
|
||||
public class ExecutorRegistry {
|
||||
private static ExecutorRegistry registry;
|
||||
|
||||
private final Map<Auction.AuctionedTaskType, Set<ExecutorIdentifier>> executors;
|
||||
|
||||
private ExecutorRegistry() {
|
||||
this.executors = new Hashtable<>();
|
||||
}
|
||||
|
||||
public static synchronized ExecutorRegistry getInstance() {
|
||||
if (registry == null) {
|
||||
registry = new ExecutorRegistry();
|
||||
}
|
||||
|
||||
return registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an executor to the registry for a given task type.
|
||||
*
|
||||
* @param taskType the type of the task
|
||||
* @param executorIdentifier the identifier of the executor (can be any string)
|
||||
* @return true unless a runtime exception occurs
|
||||
*/
|
||||
public boolean addExecutor(Auction.AuctionedTaskType taskType, ExecutorIdentifier executorIdentifier) {
|
||||
Set<ExecutorIdentifier> taskTypeExecs = executors.getOrDefault(taskType,
|
||||
Collections.synchronizedSet(new HashSet<>()));
|
||||
|
||||
taskTypeExecs.add(executorIdentifier);
|
||||
executors.put(taskType, taskTypeExecs);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an executor from the registry. The executor is disassociated from all known task types.
|
||||
*
|
||||
* @param executorIdentifier the identifier of the executor (can be any string)
|
||||
* @return true unless a runtime exception occurs
|
||||
*/
|
||||
public boolean removeExecutor(ExecutorIdentifier executorIdentifier) {
|
||||
Iterator<Auction.AuctionedTaskType> iterator = executors.keySet().iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
Auction.AuctionedTaskType taskType = iterator.next();
|
||||
Set<ExecutorIdentifier> set = executors.get(taskType);
|
||||
|
||||
set.remove(executorIdentifier);
|
||||
|
||||
if (set.isEmpty()) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the registry contains an executor for a given task type. Used during an auction to
|
||||
* decide if a bid should be placed.
|
||||
*
|
||||
* @param taskType the task type being auctioned
|
||||
* @return
|
||||
*/
|
||||
public boolean containsTaskType(Auction.AuctionedTaskType taskType) {
|
||||
return executors.containsKey(taskType);
|
||||
}
|
||||
|
||||
// Value Object for the executor identifier
|
||||
@Value
|
||||
public static class ExecutorIdentifier {
|
||||
String value;
|
||||
}
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
package ch.unisg.tapas.common;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
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.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class that wraps up the resource directory used to discover auction houses in Week 6.
|
||||
*/
|
||||
public class AuctionHouseResourceDirectory {
|
||||
private final URI rdEndpoint;
|
||||
|
||||
/**
|
||||
* Constructs a resource directory for auction house given a known URI.
|
||||
*
|
||||
* @param rdEndpoint the based endpoint of the resource directory
|
||||
*/
|
||||
public AuctionHouseResourceDirectory(URI rdEndpoint) {
|
||||
this.rdEndpoint = rdEndpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the endpoints of all auctions houses registered with this directory.
|
||||
* @return
|
||||
*/
|
||||
public List<String> retrieveAuctionHouseEndpoints() {
|
||||
List<String> auctionHouseEndpoints = new ArrayList<>();
|
||||
|
||||
try {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(rdEndpoint).GET().build();
|
||||
|
||||
HttpResponse<String> response = HttpClient.newBuilder().build()
|
||||
.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
// For simplicity, here we just hard code the current representation used by our
|
||||
// resource directory for auction houses
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
JsonNode payload = objectMapper.readTree(response.body());
|
||||
|
||||
for (JsonNode node : payload) {
|
||||
auctionHouseEndpoints.add(node.get("endpoint").asText());
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return auctionHouseEndpoints;
|
||||
}
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
package ch.unisg.tapas.common;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* Used to access properties provided via application.properties
|
||||
*/
|
||||
@Component
|
||||
public class ConfigProperties {
|
||||
@Autowired
|
||||
private Environment environment;
|
||||
|
||||
/**
|
||||
* Retrieves the URI of the WebSub hub. In this project, we use a single WebSub hub, but we could
|
||||
* use multiple.
|
||||
*
|
||||
* @return the URI of the WebSub hub
|
||||
*/
|
||||
public URI getWebSubHub() {
|
||||
return URI.create(environment.getProperty("websub.hub"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the URI used to publish content via WebSub. In this project, we use a single
|
||||
* WebSub hub, but we could use multiple. This URI is usually different from the WebSub hub URI.
|
||||
*
|
||||
* @return URI used to publish content via the WebSub hub
|
||||
*/
|
||||
public URI getWebSubPublishEndpoint() {
|
||||
return URI.create(environment.getProperty("websub.hub.publish"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the name of the group providing this auction house.
|
||||
*
|
||||
* @return the identifier of the group, e.g. tapas-group1
|
||||
*/
|
||||
public String getGroupName() {
|
||||
return environment.getProperty("group");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the base URI of this auction house.
|
||||
*
|
||||
* @return the base URI of this auction house
|
||||
*/
|
||||
public URI getAuctionHouseUri() {
|
||||
return URI.create(environment.getProperty("auction.house.uri"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the URI of the TAPAS-Tasks task list of this TAPAS applicatoin. This is used, e.g.,
|
||||
* when placing a bid during the auction (see also {@link ch.unisg.tapas.auctionhouse.domain.Bid}).
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public URI getTaskListUri() {
|
||||
return URI.create(environment.getProperty("tasks.list.uri"));
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
package ch.unisg.tapas.common;
|
||||
|
||||
import javax.validation.*;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
server.port=8082
|
||||
|
||||
websub.hub=https://websub.appspot.com/
|
||||
websub.hub.publish=https://websub.appspot.com/
|
||||
|
||||
group=tapas-group-tutors
|
||||
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/
|
@@ -0,0 +1,13 @@
|
||||
package ch.unisg.tapas;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class TapasAuctionHouseApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user