From 02716fe8b05873abc58329ac357e86912b77e13f Mon Sep 17 00:00:00 2001
From: Martin Vogel <martin.vogel@sinc.de>
Date: Tue, 14 Jun 2022 08:55:40 +0200
Subject: [PATCH] #414 Use WebClient to enable future async http calls

---
 .../java/fitconnect/api/package-info.java     | 11 +++
 .../de/fitconnect/client/ClientFactory.java   |  6 +-
 ...itConnectClient.java => SenderClient.java} | 18 ++---
 .../java/de/fitconnect/client/TestRunner.java |  2 +-
 impl/pom.xml                                  |  4 ++
 .../fitconnect/impl/SubmissionSender.java     |  4 +-
 .../fitconnect/impl/SubmissionSubscriber.java |  4 +-
 .../impl/auth/FitCoAuthService.java           | 70 -------------------
 .../impl/auth/MonoPublisherOAuthService.java  | 68 ++++++++++++++++++
 .../impl/auth/OAuthTokenIntegrationTest.java  |  6 +-
 pom.xml                                       |  5 ++
 11 files changed, 107 insertions(+), 91 deletions(-)
 create mode 100644 api/src/main/java/fitconnect/api/package-info.java
 rename client/src/main/java/de/fitconnect/client/{FitConnectClient.java => SenderClient.java} (75%)
 delete mode 100644 impl/src/main/java/fitconnect/impl/auth/FitCoAuthService.java
 create mode 100644 impl/src/main/java/fitconnect/impl/auth/MonoPublisherOAuthService.java

diff --git a/api/src/main/java/fitconnect/api/package-info.java b/api/src/main/java/fitconnect/api/package-info.java
new file mode 100644
index 000000000..3d1d7dbf9
--- /dev/null
+++ b/api/src/main/java/fitconnect/api/package-info.java
@@ -0,0 +1,11 @@
+/**
+ * This module is providing the internal sdk api
+ * <p>
+ *
+ * </p>
+ *
+ * @since 1.0
+ * @author FitConnect
+ * @version 1.0
+ */
+package fitconnect.api;
\ No newline at end of file
diff --git a/client/src/main/java/de/fitconnect/client/ClientFactory.java b/client/src/main/java/de/fitconnect/client/ClientFactory.java
index 1bf87706b..428157db8 100644
--- a/client/src/main/java/de/fitconnect/client/ClientFactory.java
+++ b/client/src/main/java/de/fitconnect/client/ClientFactory.java
@@ -8,7 +8,7 @@ import fitconnect.api.services.validation.CertificateValidator;
 import fitconnect.api.services.validation.MetadataValidator;
 import fitconnect.impl.SubmissionSender;
 import fitconnect.impl.SubmissionSubscriber;
-import fitconnect.impl.auth.FitCoAuthService;
+import fitconnect.impl.auth.MonoPublisherOAuthService;
 import fitconnect.impl.crypto.JWECryptoService;
 import fitconnect.impl.validation.KeyValidator;
 import fitconnect.impl.validation.MetadataSubmissionValidator;
@@ -38,7 +38,7 @@ public class ClientFactory {
         return new JWECryptoService();
     }
 
-    private static FitCoAuthService getAuthService() {
-        return new FitCoAuthService(HttpClient.newHttpClient(), TOKEN_URL);
+    private static MonoPublisherOAuthService getAuthService() {
+        return new MonoPublisherOAuthService(TOKEN_URL);
     }
 }
diff --git a/client/src/main/java/de/fitconnect/client/FitConnectClient.java b/client/src/main/java/de/fitconnect/client/SenderClient.java
similarity index 75%
rename from client/src/main/java/de/fitconnect/client/FitConnectClient.java
rename to client/src/main/java/de/fitconnect/client/SenderClient.java
index 9ff95f54f..015319557 100644
--- a/client/src/main/java/de/fitconnect/client/FitConnectClient.java
+++ b/client/src/main/java/de/fitconnect/client/SenderClient.java
@@ -13,30 +13,30 @@ import java.util.Optional;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-public class FitConnectClient {
+public class SenderClient {
 
-    private static final Logger logger = SdkLogger.customLogger(FitConnectClient.class);
+    private static final Logger logger = SdkLogger.customLogger(SenderClient.class);
 
     private final Sender sender;
     private Optional<OAuthToken> token = Optional.empty();
 
-    public FitConnectClient(){
+    public SenderClient(){
         this.sender = ClientFactory.submissionSender();
     }
     
-    public FitConnectClient authenticate(String clientId, String secret, String...scope){
+    public SenderClient authenticate(String clientId, String secret, String...scope){
         this.token = sender.retrieveOAuthToken(clientId,secret,scope);
         return this;
     }
 
-    public FitConnectClient createSubmission(final Submission submission){
+    public SenderClient createSubmission(final Submission submission){
         checkIfAuthenticated();
         // TODO implement sendSubmission
         logger.log(Level.INFO, "created new submission");
         return this;
     }
 
-    public FitConnectClient sendSubmission(final Metadata metadata, final Data data){
+    public SenderClient sendSubmission(final Metadata metadata, final Data data){
         checkIfAuthenticated();
         // TODO implement
         logger.log(Level.INFO, "successfully sent submission");
@@ -44,14 +44,14 @@ public class FitConnectClient {
         return this;
     }
 
-    public FitConnectClient uploadAttachments(final List<Attachment> attachments){
+    public SenderClient uploadAttachments(final List<Attachment> attachments){
         checkIfAuthenticated();
         // TODO implement
         logger.log(Level.INFO, "uploaded " + attachments.size() + " attachments");
         return this;
     }
 
-    public FitConnectClient printToken(){
+    public SenderClient printToken(){
         checkIfAuthenticated();
         logger.log(Level.INFO, "retrieved access token: " + this.token.get().getAccessToken());
         return this;
@@ -59,7 +59,7 @@ public class FitConnectClient {
 
     private void checkIfAuthenticated() {
         if(token.isEmpty()){
-            throw new IllegalStateException("not authenticated, please authenticate first with authenticate(client, secret)");
+            throw new IllegalStateException("not authenticated, please authenticate first");
         }
     }
 
diff --git a/client/src/main/java/de/fitconnect/client/TestRunner.java b/client/src/main/java/de/fitconnect/client/TestRunner.java
index 8d9f175b0..2f95ebec3 100644
--- a/client/src/main/java/de/fitconnect/client/TestRunner.java
+++ b/client/src/main/java/de/fitconnect/client/TestRunner.java
@@ -13,7 +13,7 @@ public class TestRunner {
         var clientId = "781f6213-0f0f-4a79-9372-e7187ffda98b";
         var secret = "PnzR8Vbmhpv_VwTkT34wponqXWK8WBm-LADlryYdV4o";
 
-        FitConnectClient client = new FitConnectClient();
+        SenderClient client = new SenderClient();
 
         // sample high -level- api calls to send a submission
         client.authenticate(clientId,secret)
diff --git a/impl/pom.xml b/impl/pom.xml
index def6e1125..3608d6846 100644
--- a/impl/pom.xml
+++ b/impl/pom.xml
@@ -32,6 +32,10 @@
             <groupId>com.github.erosb</groupId>
             <artifactId>everit-json-schema</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-webflux</artifactId>
+        </dependency>
     </dependencies>
 
 
diff --git a/impl/src/main/java/fitconnect/impl/SubmissionSender.java b/impl/src/main/java/fitconnect/impl/SubmissionSender.java
index dc441e79c..ecbb4ff54 100644
--- a/impl/src/main/java/fitconnect/impl/SubmissionSender.java
+++ b/impl/src/main/java/fitconnect/impl/SubmissionSender.java
@@ -13,7 +13,7 @@ import fitconnect.api.exceptions.EncryptionException;
 import fitconnect.impl.logger.SdkLogger;
 import fitconnect.api.services.validation.CertificateValidator;
 import fitconnect.api.domain.validation.ValidationResult;
-import fitconnect.impl.auth.FitCoAuthService;
+import fitconnect.impl.auth.MonoPublisherOAuthService;
 
 import java.nio.charset.StandardCharsets;
 import java.util.List;
@@ -24,7 +24,7 @@ import java.util.logging.Logger;
 
 public class SubmissionSender implements Sender {
 
-    private static final Logger logger = SdkLogger.defaultLogger(FitCoAuthService.class);
+    private static final Logger logger = SdkLogger.defaultLogger(MonoPublisherOAuthService.class);
 
     private final OAuthService authService;
     private final CertificateValidator certificateValidator;
diff --git a/impl/src/main/java/fitconnect/impl/SubmissionSubscriber.java b/impl/src/main/java/fitconnect/impl/SubmissionSubscriber.java
index 4e0c68c33..0159508a4 100644
--- a/impl/src/main/java/fitconnect/impl/SubmissionSubscriber.java
+++ b/impl/src/main/java/fitconnect/impl/SubmissionSubscriber.java
@@ -13,7 +13,7 @@ import fitconnect.api.exceptions.DecryptionException;
 import fitconnect.impl.logger.SdkLogger;
 import fitconnect.api.services.validation.MetadataValidator;
 import fitconnect.api.domain.validation.ValidationResult;
-import fitconnect.impl.auth.FitCoAuthService;
+import fitconnect.impl.auth.MonoPublisherOAuthService;
 
 import java.util.Optional;
 import java.util.logging.Level;
@@ -22,7 +22,7 @@ import java.util.logging.Logger;
 
 public class SubmissionSubscriber implements Subscriber {
 
-    private static final Logger logger = SdkLogger.defaultLogger(FitCoAuthService.class);
+    private static final Logger logger = SdkLogger.defaultLogger(MonoPublisherOAuthService.class);
 
     private final OAuthService authService;
     private final CryptoService cryptoService;
diff --git a/impl/src/main/java/fitconnect/impl/auth/FitCoAuthService.java b/impl/src/main/java/fitconnect/impl/auth/FitCoAuthService.java
deleted file mode 100644
index 4b73d3d46..000000000
--- a/impl/src/main/java/fitconnect/impl/auth/FitCoAuthService.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package fitconnect.impl.auth;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import fitconnect.api.services.auth.OAuthService;
-import fitconnect.api.domain.auth.OAuthToken;
-import fitconnect.api.exceptions.AuthenticationException;
-import fitconnect.impl.logger.SdkLogger;
-
-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.Arrays;
-import java.util.HashMap;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import static java.util.stream.Collectors.joining;
-
-public class FitCoAuthService implements OAuthService {
-
-    private static final Logger logger = SdkLogger.defaultLogger(FitCoAuthService.class);
-    private static final ObjectMapper mapper = new ObjectMapper();
-
-    private final HttpClient client;
-    private final String tokenUrl;
-
-    public FitCoAuthService(final HttpClient client, final String tokenUrl) {
-        this.client = client;
-        this.tokenUrl = tokenUrl;
-    }
-
-    @Override
-    public OAuthToken authenticate(String clientId, String clientSecret, String... scope) throws AuthenticationException {
-        final String requestBody = buildRequestBody(clientId, clientSecret, scope);
-        try {
-            return performTokenRequest(requestBody);
-        } catch (IOException | InterruptedException e) {
-            logger.log(Level.SEVERE, "error retrieving access token", e);
-            throw new AuthenticationException(e.getMessage(),e);
-        }
-    }
-
-    private String buildRequestBody(String clientId, String clientSecret, String... scope) {
-        var data = new HashMap<String, String>() {{
-            put("grant_type", "client_credentials");
-            put("client_id", clientId);
-            put("client_secret", clientSecret);
-        }};
-
-        Arrays.stream(scope).forEach(s -> data.put("scope", s));
-
-        return data.entrySet()
-                .stream()
-                .map(e -> e.getKey() + "=" + e.getValue())
-                .collect(joining("&"));
-    }
-
-    private OAuthToken performTokenRequest(final String requestBody) throws IOException, InterruptedException {
-        HttpRequest request = HttpRequest.newBuilder()
-                .header("Accept", "application/json")
-                .header("Content-Type","application/x-www-form-urlencoded;charset=UTF-8")
-                .uri(URI.create(tokenUrl))
-                .POST(HttpRequest.BodyPublishers.ofString(requestBody))
-                .build();
-        var responseBody = client.send(request, HttpResponse.BodyHandlers.ofString()).body();
-        return mapper.readValue(responseBody,OAuthToken.class);
-    }
-}
diff --git a/impl/src/main/java/fitconnect/impl/auth/MonoPublisherOAuthService.java b/impl/src/main/java/fitconnect/impl/auth/MonoPublisherOAuthService.java
new file mode 100644
index 000000000..3981b148a
--- /dev/null
+++ b/impl/src/main/java/fitconnect/impl/auth/MonoPublisherOAuthService.java
@@ -0,0 +1,68 @@
+package fitconnect.impl.auth;
+
+import fitconnect.api.domain.auth.OAuthToken;
+import fitconnect.api.exceptions.AuthenticationException;
+import fitconnect.api.services.auth.OAuthService;
+import fitconnect.impl.logger.SdkLogger;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Mono;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.logging.Logger;
+
+import static java.util.stream.Collectors.joining;
+
+public class MonoPublisherOAuthService implements OAuthService {
+
+    private static final Logger logger = SdkLogger.defaultLogger(MonoPublisherOAuthService.class);
+
+    private final String tokenUrl;
+
+    public MonoPublisherOAuthService(final String tokenUrl) {
+        this.tokenUrl = tokenUrl;
+    }
+
+    @Override
+    public OAuthToken authenticate(String clientId, String clientSecret, String... scope) throws AuthenticationException {
+        final String requestBody = buildRequestBody(clientId, clientSecret, scope);
+
+        return performTokenRequestAsync(requestBody).block();
+    }
+
+    private String buildRequestBody(String clientId, String clientSecret, String... scope) {
+        var data = new HashMap<String, String>() {{
+            put("grant_type", "client_credentials");
+            put("client_id", clientId);
+            put("client_secret", clientSecret);
+        }};
+
+        Arrays.stream(scope).forEach(s -> data.put("scope", s));
+
+        return data.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(joining("&"));
+    }
+
+    // TODO generalize HTTP requests in separate common service
+    private Mono<OAuthToken> performTokenRequestAsync(final String requestBody){
+        return WebClient.builder()
+                .defaultHeaders(this::setHeaders)
+                .build()
+                .post()
+                .uri(tokenUrl)
+                .bodyValue(requestBody)
+                .retrieve()
+                .bodyToMono(OAuthToken.class);
+    }
+
+    private void setHeaders(HttpHeaders headers) {
+        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
+        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        headers.setAcceptCharset(List.of(StandardCharsets.UTF_8));
+    }
+
+}
diff --git a/impl/src/test/java/fitconnect/impl/auth/OAuthTokenIntegrationTest.java b/impl/src/test/java/fitconnect/impl/auth/OAuthTokenIntegrationTest.java
index e282aff6b..2d82a9172 100644
--- a/impl/src/test/java/fitconnect/impl/auth/OAuthTokenIntegrationTest.java
+++ b/impl/src/test/java/fitconnect/impl/auth/OAuthTokenIntegrationTest.java
@@ -1,11 +1,10 @@
 package fitconnect.impl.auth;
 
-import fitconnect.api.services.Sender;
 import fitconnect.api.domain.auth.OAuthToken;
+import fitconnect.api.services.Sender;
 import fitconnect.impl.SubmissionSender;
 import org.junit.jupiter.api.Test;
 
-import java.net.http.HttpClient;
 import java.util.Optional;
 
 import static org.junit.jupiter.api.Assertions.*;
@@ -21,8 +20,7 @@ class OAuthTokenIntegrationTest {
         var secret = "PnzR8Vbmhpv_VwTkT34wponqXWK8WBm-LADlryYdV4o";
         var scope = "send:region:DE";
 
-        var httpClient = HttpClient.newHttpClient();
-        var authService = new FitCoAuthService(httpClient, tokenUrl);
+        var authService = new MonoPublisherOAuthService(tokenUrl);
         final Sender sender = new SubmissionSender(authService, null, null);
 
         // When
diff --git a/pom.xml b/pom.xml
index 0981ba0ec..ead23af03 100644
--- a/pom.xml
+++ b/pom.xml
@@ -73,6 +73,11 @@
                 <artifactId>everit-json-schema</artifactId>
                 <version>1.14.1</version>
             </dependency>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-webflux</artifactId>
+                <version>2.7.0</version>
+            </dependency>
             <dependency>
                 <groupId>org.projectlombok</groupId>
                 <artifactId>lombok</artifactId>
-- 
GitLab