diff --git a/mocks/README.md b/mocks/README.md
new file mode 100644
index 0000000..7135682
--- /dev/null
+++ b/mocks/README.md
@@ -0,0 +1,29 @@
+In this directory are some files to mock an auction house to test WebSub local.
+
+To run a local WebSubHub instance
+
+1. Start a mongodb in docker:
+
+- docker run -d -p 27017:27017 -p 28017:28017 -e AUTH=no tutum/mongodb
+
+2. Install a local hub
+
+- yarn global add websub-hub
+
+3. Run the hub localy
+
+- websub-hub -l info -m mongodb://localhost:27017/hub
+
+Create an example subscription
+
+- node auction-house/subscriber.js
+
+Create an example auctionhouse
+
+- node auction-house/auctions.js
+
+Publish to the hub
+
+- node auction-house/publisher.js
+
+Mostly inspired by: https://github.com/hemerajs/websub-hub
diff --git a/mocks/auction-house/auctions.js b/mocks/auction-house/auctions.js
new file mode 100644
index 0000000..68a6aa5
--- /dev/null
+++ b/mocks/auction-house/auctions.js
@@ -0,0 +1,39 @@
+// Require the framework and instantiate it
+const fastify = require('fastify')({ logger: true })
+
+// Declare a route
+fastify.get('/auctions', async (request, reply) => {
+ console.log('content provided')
+
+ return [
+ {
+ id: '2',
+ content_text: 'This is a second item.',
+ url: 'https://example.org/second-item'
+ },
+ {
+ id: '1',
+ content_html: '
Hello, world!
',
+ url: 'https://example.org/initial-post'
+ }
+ ]
+})
+
+fastify.get('/websub', async (request, reply) => {
+ console.log('content provided')
+
+ return {
+ topic: 'http://localhost:3100/auctions'
+ }
+})
+
+// Run the server!
+const start = async () => {
+ try {
+ await fastify.listen(3100)
+ } catch (err) {
+ fastify.log.error(err)
+ process.exit(1)
+ }
+}
+start()
diff --git a/mocks/auction-house/publisher.js b/mocks/auction-house/publisher.js
new file mode 100644
index 0000000..c760820
--- /dev/null
+++ b/mocks/auction-house/publisher.js
@@ -0,0 +1,17 @@
+const axios = require('axios').default
+
+// Run the server!
+const start = async () => {
+ await axios
+ .post('http://localhost:3000/publish', {
+ 'hub.mode': 'publish',
+ 'hub.url': 'http://localhost:3100/auctions'
+ })
+ .then(response => {
+ console.log(response.data)
+ })
+ .catch(error => {
+ console.log(error)
+ })
+}
+start()
diff --git a/mocks/auction-house/subscriber.js b/mocks/auction-house/subscriber.js
new file mode 100644
index 0000000..ce27197
--- /dev/null
+++ b/mocks/auction-house/subscriber.js
@@ -0,0 +1,42 @@
+// Require the framework and instantiate it
+const fastify = require('fastify')({ logger: true })
+const axios = require('axios').default
+
+// Declare a route
+fastify.get('/auction-created', async (request, reply) => {
+ console.log('subscription verified', request.query)
+ console.log(request.query)
+ return request.query
+})
+
+fastify.post('/auction-created', async (request, reply) => {
+ console.log('received blog content', request.body)
+ reply.send()
+})
+
+// Run the server!
+const start = async () => {
+ // subscribe to the feed
+
+ try {
+ await fastify.listen(3200)
+ } catch (err) {
+ fastify.log.error(err)
+ process.exit(1)
+ }
+
+ await axios
+ .post('http://localhost:3000', {
+ 'hub.callback': 'http://localhost:3200/auction-created',
+ 'hub.mode': 'subscribe',
+ 'hub.topic': 'http://localhost:3100/auctions',
+ 'hub.ws': false
+ })
+ .then(response => {
+ console.log(response.data)
+ })
+ .catch(error => {
+ console.log(error)
+ })
+}
+start()
diff --git a/tapas-auction-house/pom.xml b/tapas-auction-house/pom.xml
index 4b9cbb6..df44681 100644
--- a/tapas-auction-house/pom.xml
+++ b/tapas-auction-house/pom.xml
@@ -58,6 +58,17 @@
validation-api
1.1.0.Final
+
+ org.json
+ json
+ 20210307
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ runtime
+ true
+
diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java
index 46dafb7..7438032 100644
--- a/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java
+++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/TapasAuctionHouseApplication.java
@@ -8,6 +8,7 @@ import ch.unisg.tapas.common.ConfigProperties;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.paho.client.mqttv3.MqttException;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -32,18 +33,16 @@ public class TapasAuctionHouseApplication {
SpringApplication tapasAuctioneerApp = new SpringApplication(TapasAuctionHouseApplication.class);
// We will use these bootstrap methods in Week 6:
- // bootstrapMarketplaceWithWebSub();
+ bootstrapMarketplaceWithWebSub();
bootstrapMarketplaceWithMqtt();
tapasAuctioneerApp.run(args);
}
-
/**
* Discovers auction houses and subscribes to WebSub notifications
*/
private static void bootstrapMarketplaceWithWebSub() {
List auctionHouseEndpoints = discoverAuctionHouseEndpoints();
- LOGGER.info("Found auction house endpoints: " + auctionHouseEndpoints);
WebSubSubscriber subscriber = new WebSubSubscriber();
diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/WebSubSubscriber.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/WebSubSubscriber.java
index da2b096..5b3fc32 100644
--- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/WebSubSubscriber.java
+++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/common/clients/WebSubSubscriber.java
@@ -1,6 +1,15 @@
package ch.unisg.tapas.auctionhouse.adapter.common.clients;
+import java.io.IOException;
import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.json.JSONObject;
+import org.springframework.http.HttpStatus;
/**
* Subscribes to the WebSub hubs of auction houses discovered at run time. This class is instantiated
@@ -9,7 +18,24 @@ import java.net.URI;
*/
public class WebSubSubscriber {
+ // TODO get this somehow from properties file. But on clue how to do this with static variables
+ static String WEBSUB_HUB_ENDPOINT = "http://localhost:3000";
+ static String AUCTION_HOUSE_ENDPOINT = "http://localhost:8086";
+
+ Logger logger = Logger.getLogger(WebSubSubscriber.class.getName());
+
public void subscribeToAuctionHouseEndpoint(URI endpoint) {
+ // TODO decide with other groups about auction house endpoint uri to discover websub topics
+ // and replace the hardcoded one with it
+ String topic = discoverWebSubTopic("http://localhost:3100/websub");
+
+ if (topic == null) {
+ return;
+ }
+
+ subscribeToWebSub(topic);
+
+ // Shoudl be done :D
// 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.
@@ -25,4 +51,61 @@ public class WebSubSubscriber {
// - W3C WebSub Recommendation: https://www.w3.org/TR/websub/
// - the implementation notes of the WebSub hub you are using to distribute events
}
+
+ private String discoverWebSubTopic(String endpoint) {
+ HttpClient client = HttpClient.newHttpClient();
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(endpoint))
+ .header("Content-Type", "application/json")
+ .GET()
+ .build();
+
+
+ try {
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ if (response.statusCode() == HttpStatus.OK.value()) {
+ // TODO decide with other groups about response structure and replace the hardcoded
+ // uri with response uri
+ JSONObject jsonObject = new JSONObject(response.body());
+ System.out.println(jsonObject);
+ return jsonObject.getString("topic");
+ } else {
+ logger.log(Level.SEVERE, "Could not find a websub uri");
+ }
+ } catch (InterruptedException e) {
+ logger.log(Level.SEVERE, e.getLocalizedMessage(), e);
+ Thread.currentThread().interrupt();
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, e.getLocalizedMessage(), e);
+ }
+ return null;
+ }
+
+ private void subscribeToWebSub(String topic) {
+ HttpClient client = HttpClient.newHttpClient();
+
+ String body = new JSONObject()
+ .put("hub.callback", AUCTION_HOUSE_ENDPOINT + "/auction-started")
+ .put("hub.mode", "subscribe")
+ .put("hub.topic", topic)
+ .put("hub.ws", false)
+ .toString();
+
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(WEBSUB_HUB_ENDPOINT))
+ .header("Content-Type", "application/json")
+ .POST(HttpRequest.BodyPublishers.ofString(body))
+ .build();
+
+
+ try {
+ client.send(request, HttpResponse.BodyHandlers.ofString());
+ } catch (InterruptedException e) {
+ logger.log(Level.SEVERE, e.getLocalizedMessage(), e);
+ Thread.currentThread().interrupt();
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, e.getLocalizedMessage(), e);
+ }
+ }
}
diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/AuctionStartedEventListenerWebSubAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/AuctionStartedEventListenerWebSubAdapter.java
index d156452..4f67dad 100644
--- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/AuctionStartedEventListenerWebSubAdapter.java
+++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/AuctionStartedEventListenerWebSubAdapter.java
@@ -1,6 +1,22 @@
package ch.unisg.tapas.auctionhouse.adapter.in.messaging.websub;
+import ch.unisg.tapas.auctionhouse.adapter.common.formats.AuctionJsonRepresentation;
import ch.unisg.tapas.auctionhouse.application.handler.AuctionStartedHandler;
+import ch.unisg.tapas.auctionhouse.application.port.in.AuctionStartedEvent;
+import ch.unisg.tapas.auctionhouse.domain.Auction;
+import ch.unisg.tapas.auctionhouse.domain.Auction.AuctionDeadline;
+import ch.unisg.tapas.auctionhouse.domain.Auction.AuctionHouseUri;
+import ch.unisg.tapas.auctionhouse.domain.Auction.AuctionId;
+import ch.unisg.tapas.auctionhouse.domain.Auction.AuctionedTaskType;
+import ch.unisg.tapas.auctionhouse.domain.Auction.AuctionedTaskUri;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collection;
+
+import org.json.JSONArray;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
@@ -13,6 +29,25 @@ public class AuctionStartedEventListenerWebSubAdapter {
public AuctionStartedEventListenerWebSubAdapter(AuctionStartedHandler auctionStartedHandler) {
this.auctionStartedHandler = auctionStartedHandler;
}
+ /**
+ * Controller which listens to auction-started callbacks
+ * @return 200 OK
+ * @throws URISyntaxException
+ **/
+ @PostMapping(path = "/auction-started")
+ public ResponseEntity handleExecutorAddedEvent(@RequestBody Collection payload) throws URISyntaxException {
- //TODO
+ for (AuctionJsonRepresentation auction : payload) {
+ auctionStartedHandler.handleAuctionStartedEvent(
+ new AuctionStartedEvent(
+ new Auction(new AuctionId(auction.getAuctionId()),
+ new AuctionHouseUri(new URI(auction.getAuctionHouseUri())),
+ new AuctionedTaskUri(new URI(auction.getTaskUri())),
+ new AuctionedTaskType(auction.getTaskType()),
+ new AuctionDeadline(auction.getDeadline()))
+ ));
+ }
+
+ return new ResponseEntity<>(HttpStatus.OK);
+ }
}
diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/ValidateIntentWebSubAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/ValidateIntentWebSubAdapter.java
new file mode 100644
index 0000000..7bfb450
--- /dev/null
+++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/in/messaging/websub/ValidateIntentWebSubAdapter.java
@@ -0,0 +1,33 @@
+package ch.unisg.tapas.auctionhouse.adapter.in.messaging.websub;
+
+import org.json.JSONObject;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * This class validates the subscription intent from the websub hub
+ */
+@RestController
+public class ValidateIntentWebSubAdapter {
+
+ @Value("${application.environment}")
+ private String environment;
+
+ @GetMapping(path = "/auction-started")
+ public ResponseEntity validateIntent(@RequestParam("hub.challenge") String challenge) {
+ // Different implementation depending on local development or production
+ if (environment.equalsIgnoreCase("development")) {
+ HttpHeaders headers = new HttpHeaders();
+ headers.add("Content-Type", "application/json");
+ String body = new JSONObject()
+ .put("hub.challenge", challenge)
+ .toString();
+ return new ResponseEntity<>(body, headers, HttpStatus.OK);
+ } else {
+ return new ResponseEntity<>(challenge, HttpStatus.OK);
+ }
+ }
+}
diff --git a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventWebSubAdapter.java b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventWebSubAdapter.java
index 73451e4..228f43b 100644
--- a/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventWebSubAdapter.java
+++ b/tapas-auction-house/src/main/java/ch/unisg/tapas/auctionhouse/adapter/out/messaging/websub/PublishAuctionStartedEventWebSubAdapter.java
@@ -4,12 +4,16 @@ 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.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import java.io.IOException;
+import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
@@ -17,6 +21,8 @@ import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import java.util.stream.Collectors;
/**
@@ -29,8 +35,38 @@ public class PublishAuctionStartedEventWebSubAdapter implements AuctionStartedEv
@Autowired
private ConfigProperties config;
+ @Value("${auctionhouse.uri}")
+ private String auctionHouseUri;
+
+ @Value("${websub.hub.uri}")
+ private String webSubHubUri;
+
+ Logger logger = Logger.getLogger(PublishAuctionStartedEventWebSubAdapter.class.getName());
+
@Override
public void publishAuctionStartedEvent(AuctionStartedEvent event) {
- // TODO
+ HttpClient client = HttpClient.newHttpClient();
+
+ String body = new JSONObject()
+ .put("hub.url", auctionHouseUri + "/auctions")
+ .put("hub.mode", "publish")
+ .toString();
+
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(webSubHubUri))
+ .header("Content-Type", "application/json")
+ .POST(HttpRequest.BodyPublishers.ofString(body))
+ .build();
+
+
+ try {
+ client.send(request, HttpResponse.BodyHandlers.ofString());
+ } catch (InterruptedException e) {
+ logger.log(Level.SEVERE, e.getLocalizedMessage(), e);
+ Thread.currentThread().interrupt();
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, e.getLocalizedMessage(), e);
+ }
}
}
diff --git a/tapas-auction-house/src/main/resources/application.properties b/tapas-auction-house/src/main/resources/application.properties
index 1ededee..706362e 100644
--- a/tapas-auction-house/src/main/resources/application.properties
+++ b/tapas-auction-house/src/main/resources/application.properties
@@ -7,5 +7,7 @@ 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/
-
+application.environment=development
+auctionhouse.uri=http://localhost:8086
+websub.hub.uri=http://localhost:3000
mqtt.broker.uri=tcp://localhost:1883