From f96efe84cc9f809b0bf594947310aaa31aede600 Mon Sep 17 00:00:00 2001
From: Ernst Riemer <eriemer@vmware.com>
Date: Wed, 2 Aug 2023 17:00:41 -0600
Subject: [PATCH 01/16] WIP on starting CSP auth work for the java wavefront
 sdk

Signed-off-by: Glenn Oppegard <goppegard@vmware.com>
---
 .../sdk/common/clients/WavefrontClient.java   | 55 +++++++++++++++++--
 .../clients/service/CSPTokenService.java      | 21 +++++++
 .../clients/service/NoopTokenService.java     | 13 +++++
 .../clients/service/ReportingService.java     | 16 ++++--
 .../common/clients/service/TokenService.java  |  7 +++
 .../service/WavefrontTokenService.java        | 15 +++++
 6 files changed, 119 insertions(+), 8 deletions(-)
 create mode 100644 src/main/java/com/wavefront/sdk/common/clients/service/CSPTokenService.java
 create mode 100644 src/main/java/com/wavefront/sdk/common/clients/service/NoopTokenService.java
 create mode 100644 src/main/java/com/wavefront/sdk/common/clients/service/TokenService.java
 create mode 100644 src/main/java/com/wavefront/sdk/common/clients/service/WavefrontTokenService.java

diff --git a/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java b/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java
index 9e7afd29..30ba8117 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java
@@ -11,7 +11,7 @@
 import com.wavefront.sdk.common.WavefrontSender;
 import com.wavefront.sdk.common.annotation.NonNull;
 import com.wavefront.sdk.common.annotation.Nullable;
-import com.wavefront.sdk.common.clients.service.ReportingService;
+import com.wavefront.sdk.common.clients.service.*;
 import com.wavefront.sdk.common.logging.MessageDedupingLogger;
 import com.wavefront.sdk.common.metrics.WavefrontSdkDeltaCounter;
 import com.wavefront.sdk.common.metrics.WavefrontSdkMetricsRegistry;
@@ -141,6 +141,8 @@ public static class Builder {
     // Required parameters
     private final String server;
     private final String token;
+    private final String cspClientId;
+    private final String cspClientSecret;
 
     // Optional parameters
     private int metricsPort = -1;
@@ -165,8 +167,35 @@ public static class Builder {
      * @param token  A valid API token with direct ingestion permissions
      */
     public Builder(String server, @Nullable String token) {
+      this(server, token, null, null);
+    }
+
+    /**
+     * Create a new WavefrontClient.Builder
+     *
+     * @param server A server URL of the form "https://clusterName.wavefront.com" or
+     *               "http://internal.proxy.com:port"
+     * @param token  A valid API token with direct ingestion permissions
+     * @param cspClientId  TODO
+     * @param cspClientSecret  TODO
+     */
+    public Builder(String server, @Nullable String token, @Nullable String cspClientId, @Nullable String cspClientSecret) {
       this.server = server;
       this.token = token;
+      this.cspClientId = cspClientId;
+      this.cspClientSecret = cspClientSecret;
+    }
+
+    /**
+     * Create a new WavefrontClient.Builder
+     *
+     * @param server A server URL of the form "https://clusterName.wavefront.com" or
+     *               "http://internal.proxy.com:port"
+     * @param cspClientId  TODO
+     * @param cspClientSecret  TODO
+     */
+    public Builder(String server, @Nullable String cspClientId, @Nullable String cspClientSecret) {
+      this(server, null, cspClientId, cspClientSecret);
     }
 
     /**
@@ -175,7 +204,7 @@ public Builder(String server, @Nullable String token) {
      * @param proxyServer A server URL of the the form "http://internal.proxy.com:port"
      */
     public Builder(String proxyServer) {
-      this(proxyServer, null);
+      this(proxyServer, null, null, null);
     }
 
     /**
@@ -359,6 +388,24 @@ private WavefrontClient(Builder builder) {
     }
     defaultSource = tempSource;
 
+    TokenService tokenService = null;
+
+    if (builder.token != null && !builder.token.equals("")) {
+      tokenService = new WavefrontTokenService(builder.token);
+    }
+
+    // TODO if they set one or the other, log?
+    if (builder.cspClientId != null && !builder.cspClientId.equals("") && builder.cspClientSecret != null && !builder.cspClientSecret.equals("")) {
+      // TODO URL
+      tokenService = new CSPTokenService("", builder.cspClientId, builder.cspClientSecret);
+    }
+
+    if (tokenService == null) {
+
+      // NOOP LOGS
+      tokenService = new NoopTokenService();
+    }
+
     batchSize = builder.batchSize;
     messageSizeBytes = builder.messageSizeBytes;
     metricsBuffer = new LinkedBlockingQueue<>(builder.maxQueueSize);
@@ -367,8 +414,8 @@ private WavefrontClient(Builder builder) {
     spanLogsBuffer = new LinkedBlockingQueue<>(builder.maxQueueSize);
     eventsBuffer = new LinkedBlockingQueue<>(builder.maxQueueSize);
     logsBuffer = new LinkedBlockingQueue<>(builder.maxQueueSize);
-    metricsReportingService = new ReportingService(builder.metricsUri, builder.token, builder.reportingServiceLogSuppressTimeSeconds);
-    tracesReportingService = new ReportingService(builder.tracesUri, builder.token, builder.reportingServiceLogSuppressTimeSeconds);
+    metricsReportingService = new ReportingService(builder.metricsUri, tokenService, builder.reportingServiceLogSuppressTimeSeconds);
+    tracesReportingService = new ReportingService(builder.tracesUri, tokenService, builder.reportingServiceLogSuppressTimeSeconds);
     scheduler = Executors.newScheduledThreadPool(1,
         new NamedThreadFactory("wavefrontClientSender").setDaemon(true));
     scheduler.scheduleAtFixedRate(this, 1, builder.flushInterval, builder.flushIntervalTimeUnit);
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/CSPTokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/CSPTokenService.java
new file mode 100644
index 00000000..ea551079
--- /dev/null
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/CSPTokenService.java
@@ -0,0 +1,21 @@
+package com.wavefront.sdk.common.clients.service;
+
+public class CSPTokenService implements TokenService {
+
+  private final String cspBaseURL;
+  private final String cspClientId;
+  private final String cspClientSecret;
+
+  public CSPTokenService(final String cspBaseURL, final String cspClientId, final String cspClientSecret) {
+    this.cspBaseURL = cspBaseURL;
+    this.cspClientId = cspClientId;
+    this.cspClientSecret = cspClientSecret;
+  }
+
+  // TODO - put in CSP client
+
+  @Override
+  public String getToken() {
+    return "";
+  }
+}
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/NoopTokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/NoopTokenService.java
new file mode 100644
index 00000000..e7cbc5db
--- /dev/null
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/NoopTokenService.java
@@ -0,0 +1,13 @@
+package com.wavefront.sdk.common.clients.service;
+
+// Primarily for proxy usage
+public class NoopTokenService implements TokenService {
+
+  public NoopTokenService() {
+  }
+
+  @Override
+  public String getToken() {
+    return "";
+  }
+}
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/ReportingService.java b/src/main/java/com/wavefront/sdk/common/clients/service/ReportingService.java
index 6be06407..91b70519 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/ReportingService.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/ReportingService.java
@@ -1,6 +1,7 @@
 package com.wavefront.sdk.common.clients.service;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
 import com.wavefront.sdk.common.Constants;
 import com.wavefront.sdk.common.annotation.Nullable;
 import com.wavefront.sdk.common.logging.MessageSuppressingLogger;
@@ -28,7 +29,7 @@ public class ReportingService implements ReportAPI {
   // This logger is intended to be configurable in the WavefrontClient.Builder. Given that the invoker controls the
   // configuration, this is not a static logger.
   private final MessageSuppressingLogger messageSuppressingLogger;
-  private final String token;
+  private final TokenService tokenService;
   private final URI uri;
 
   private static final int CONNECT_TIMEOUT_MILLIS = 30000;
@@ -40,12 +41,13 @@ public class ReportingService implements ReportAPI {
    * <p>Constructor for ReportingService.</p>
    *
    * @param uri a {@link java.net.URI} object
-   * @param token a {@link java.lang.String} object
+   * @param tokenService a {@link TokenService} object
    * @param reportingServiceLogSuppressTimeSeconds a long
    */
-  public ReportingService(URI uri, @Nullable String token, long reportingServiceLogSuppressTimeSeconds) {
+  public ReportingService(URI uri, TokenService tokenService, long reportingServiceLogSuppressTimeSeconds) {
     this.uri = uri;
-    this.token = token;
+    this.tokenService = tokenService;
+
     // Setting suppress time to 0 invalidates the cache used by the message suppressing logger and doesn't log anything.
     // So defaulting to the minimum of 1 second.
     reportingServiceLogSuppressTimeSeconds = reportingServiceLogSuppressTimeSeconds <= 0 ? 1 : reportingServiceLogSuppressTimeSeconds;
@@ -65,9 +67,13 @@ public int send(String format, InputStream stream) {
       urlConn.setRequestMethod("POST");
       urlConn.addRequestProperty("Content-Type", "application/octet-stream");
       urlConn.addRequestProperty("Content-Encoding", "gzip");
+
+      String token = tokenService.getToken();
+
       if (token != null && !token.equals("")) {
         urlConn.addRequestProperty("Authorization", "Bearer " + token);
       }
+
       urlConn.setConnectTimeout(CONNECT_TIMEOUT_MILLIS);
       urlConn.setReadTimeout(READ_TIMEOUT_MILLIS);
 
@@ -100,6 +106,8 @@ public int sendEvent(InputStream stream) {
       urlConn.setDoOutput(true);
       urlConn.setRequestMethod("POST");
 
+      String token = tokenService.getToken();
+
       if (token != null && !token.equals("")) {
         urlConn.addRequestProperty("Authorization", "Bearer " + token);
       }
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/TokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/TokenService.java
new file mode 100644
index 00000000..3cc81d37
--- /dev/null
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/TokenService.java
@@ -0,0 +1,7 @@
+package com.wavefront.sdk.common.clients.service;
+
+public interface TokenService {
+
+  String getToken();
+
+}
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/WavefrontTokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/WavefrontTokenService.java
new file mode 100644
index 00000000..cf8081c4
--- /dev/null
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/WavefrontTokenService.java
@@ -0,0 +1,15 @@
+package com.wavefront.sdk.common.clients.service;
+
+public class WavefrontTokenService implements TokenService {
+
+  private final String token;
+
+  public WavefrontTokenService(final String token) {
+    this.token = token;
+  }
+
+  @Override
+  public String getToken() {
+    return token;
+  }
+}

From 47dbbed8ac48e548fa5ea05c0e100a0238b14d9d Mon Sep 17 00:00:00 2001
From: Ernst Riemer <eriemer@vmware.com>
Date: Wed, 2 Aug 2023 17:55:32 -0600
Subject: [PATCH 02/16] WIP - calling csp authorize endpoint to get a
 server-to-server token back.

---
 .../java/com/wavefront/sdk/common/Utils.java  |  4 +
 .../sdk/common/clients/WavefrontClient.java   | 20 +++--
 .../common/clients/service/CSPResponse.java   | 21 ++++++
 .../clients/service/CSPTokenService.java      | 74 ++++++++++++++++++-
 .../clients/service/ReportingService.java     |  7 +-
 .../com/wavefront/sdk/common/UtilsTest.java   |  8 ++
 .../clients/service/CSPTokenServiceTest.java  | 15 ++++
 7 files changed, 137 insertions(+), 12 deletions(-)
 create mode 100644 src/main/java/com/wavefront/sdk/common/clients/service/CSPResponse.java
 create mode 100644 src/test/java/com/wavefront/sdk/common/clients/service/CSPTokenServiceTest.java

diff --git a/src/main/java/com/wavefront/sdk/common/Utils.java b/src/main/java/com/wavefront/sdk/common/Utils.java
index 72abd905..af90acc3 100644
--- a/src/main/java/com/wavefront/sdk/common/Utils.java
+++ b/src/main/java/com/wavefront/sdk/common/Utils.java
@@ -832,4 +832,8 @@ public static double convertSemVerToGauge(String version) {
     }
     return 0.0D;
   }
+
+  public static boolean isNullOrEmpty(final String string) {
+    return string == null || string.isEmpty();
+  }
 }
diff --git a/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java b/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java
index 30ba8117..3eaefaea 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java
@@ -141,6 +141,7 @@ public static class Builder {
     // Required parameters
     private final String server;
     private final String token;
+    private final String cspBaseUrl;
     private final String cspClientId;
     private final String cspClientSecret;
 
@@ -159,6 +160,9 @@ public static class Builder {
     private URI metricsUri;
     private URI tracesUri;
 
+    // TODO - tests for diff kinds of TokenService
+    // TODO - tests for diff kinds of TokenService
+
     /**
      * Create a new WavefrontClient.Builder
      *
@@ -167,7 +171,7 @@ public static class Builder {
      * @param token  A valid API token with direct ingestion permissions
      */
     public Builder(String server, @Nullable String token) {
-      this(server, token, null, null);
+      this(server, token, null, null, null);
     }
 
     /**
@@ -179,9 +183,10 @@ public Builder(String server, @Nullable String token) {
      * @param cspClientId  TODO
      * @param cspClientSecret  TODO
      */
-    public Builder(String server, @Nullable String token, @Nullable String cspClientId, @Nullable String cspClientSecret) {
+    public Builder(String server, @Nullable String token, @Nullable String cspBaseUrl, @Nullable String cspClientId, @Nullable String cspClientSecret) {
       this.server = server;
       this.token = token;
+      this.cspBaseUrl = cspBaseUrl;
       this.cspClientId = cspClientId;
       this.cspClientSecret = cspClientSecret;
     }
@@ -195,7 +200,7 @@ public Builder(String server, @Nullable String token, @Nullable String cspClient
      * @param cspClientSecret  TODO
      */
     public Builder(String server, @Nullable String cspClientId, @Nullable String cspClientSecret) {
-      this(server, null, cspClientId, cspClientSecret);
+      this(server, null, null, cspClientId, cspClientSecret);
     }
 
     /**
@@ -204,7 +209,7 @@ public Builder(String server, @Nullable String cspClientId, @Nullable String csp
      * @param proxyServer A server URL of the the form "http://internal.proxy.com:port"
      */
     public Builder(String proxyServer) {
-      this(proxyServer, null, null, null);
+      this(proxyServer, null, null, null, null);
     }
 
     /**
@@ -390,18 +395,17 @@ private WavefrontClient(Builder builder) {
 
     TokenService tokenService = null;
 
-    if (builder.token != null && !builder.token.equals("")) {
+    if (!Utils.isNullOrEmpty(builder.token)) {
       tokenService = new WavefrontTokenService(builder.token);
     }
 
     // TODO if they set one or the other, log?
-    if (builder.cspClientId != null && !builder.cspClientId.equals("") && builder.cspClientSecret != null && !builder.cspClientSecret.equals("")) {
+    if (!Utils.isNullOrEmpty(builder.cspBaseUrl) && !Utils.isNullOrEmpty(builder.cspClientId) && !Utils.isNullOrEmpty(builder.cspClientSecret)) {
       // TODO URL
-      tokenService = new CSPTokenService("", builder.cspClientId, builder.cspClientSecret);
+      tokenService = new CSPTokenService(builder.cspBaseUrl, builder.cspClientId, builder.cspClientSecret);
     }
 
     if (tokenService == null) {
-
       // NOOP LOGS
       tokenService = new NoopTokenService();
     }
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/CSPResponse.java b/src/main/java/com/wavefront/sdk/common/clients/service/CSPResponse.java
new file mode 100644
index 00000000..739f9f3f
--- /dev/null
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/CSPResponse.java
@@ -0,0 +1,21 @@
+package com.wavefront.sdk.common.clients.service;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+public class CSPResponse {
+  @JsonProperty("id_token")
+  public String idToken;
+
+  @JsonProperty("token_type")
+  public String tokenType;
+
+  @JsonProperty("expires_in")
+  public int expiresIn;
+
+  public String scope;
+
+  @JsonProperty("access_token")
+  public String accessToken;
+
+  @JsonProperty("refresh_token")
+  public String refreshToken;
+}
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/CSPTokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/CSPTokenService.java
index ea551079..3e6962a2 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/CSPTokenService.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/CSPTokenService.java
@@ -1,7 +1,20 @@
 package com.wavefront.sdk.common.clients.service;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
 public class CSPTokenService implements TokenService {
 
+  private final String OAUTH_PATH = "/csp/gateway/am/api/auth/authorize";
+  private final ObjectMapper mapper = new ObjectMapper();
+
   private final String cspBaseURL;
   private final String cspClientId;
   private final String cspClientSecret;
@@ -12,10 +25,67 @@ public CSPTokenService(final String cspBaseURL, final String cspClientId, final
     this.cspClientSecret = cspClientSecret;
   }
 
-  // TODO - put in CSP client
+  // TODO - schedule threads?
+  // TODO - cache token ish etc?
+  // TODO - error handling
+  // TODO - tests
+
+  // TODO - CLEANUP
+  private String getCSPToken() {
+    HttpURLConnection urlConn = null;
+
+    String urlParameters  = "grant_type=client_credentials";
+    byte[] postData = urlParameters.getBytes( StandardCharsets.UTF_8 );
+
+    try {
+      URL url = new URL(cspBaseURL + OAUTH_PATH);
+      urlConn = (HttpURLConnection) url.openConnection();
+      urlConn.setDoOutput(true);
+      urlConn.setRequestMethod("POST");
+      urlConn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+      urlConn.addRequestProperty("Authorization", "Basic " + buildHttpBasicToken(cspClientId, cspClientSecret));
+      urlConn.setRequestProperty("Content-Length", Integer.toString(postData.length));
+
+      urlConn.setConnectTimeout(30_000);
+      urlConn.setReadTimeout(10_000);
+
+      //Send request
+      DataOutputStream wr = new DataOutputStream (urlConn.getOutputStream());
+      wr.write(postData);
+      wr.flush();
+      wr.close();
+
+      int statusCode = urlConn.getResponseCode();
+
+      if (statusCode == 200) {
+        // final String response = readAndClose(urlConn.getInputStream());
+
+        try {
+          final CSPResponse parsedResponse = mapper.readValue(urlConn.getInputStream(), CSPResponse.class);
+
+          System.out.println(parsedResponse.accessToken);
+
+          return parsedResponse.accessToken;
+        } catch (JsonProcessingException e) {
+          e.printStackTrace();
+        }
+      }
+
+    } catch (IOException ex) {
+      if (urlConn != null) {
+        // return safeGetResponseCodeAndClose(urlConn);
+      }
+    }
+    return null;
+  }
+
+  private String buildHttpBasicToken(final String cspClientId, final String cspClientSecret) {
+    String encodeMe = cspClientId + ":" + cspClientSecret;
+    return Base64.getEncoder().encodeToString(encodeMe.getBytes());
+  }
 
   @Override
   public String getToken() {
-    return "";
+    return getCSPToken();
   }
 }
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/ReportingService.java b/src/main/java/com/wavefront/sdk/common/clients/service/ReportingService.java
index 91b70519..2b914b0c 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/ReportingService.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/ReportingService.java
@@ -3,6 +3,7 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Strings;
 import com.wavefront.sdk.common.Constants;
+import com.wavefront.sdk.common.Utils;
 import com.wavefront.sdk.common.annotation.Nullable;
 import com.wavefront.sdk.common.logging.MessageSuppressingLogger;
 
@@ -29,6 +30,8 @@ public class ReportingService implements ReportAPI {
   // This logger is intended to be configurable in the WavefrontClient.Builder. Given that the invoker controls the
   // configuration, this is not a static logger.
   private final MessageSuppressingLogger messageSuppressingLogger;
+
+  // TODO - tests for diff kinds of TokenService
   private final TokenService tokenService;
   private final URI uri;
 
@@ -70,7 +73,7 @@ public int send(String format, InputStream stream) {
 
       String token = tokenService.getToken();
 
-      if (token != null && !token.equals("")) {
+      if (!Utils.isNullOrEmpty(token)) {
         urlConn.addRequestProperty("Authorization", "Bearer " + token);
       }
 
@@ -108,7 +111,7 @@ public int sendEvent(InputStream stream) {
 
       String token = tokenService.getToken();
 
-      if (token != null && !token.equals("")) {
+      if (!Utils.isNullOrEmpty(token)) {
         urlConn.addRequestProperty("Authorization", "Bearer " + token);
       }
       urlConn.setConnectTimeout(CONNECT_TIMEOUT_MILLIS);
diff --git a/src/test/java/com/wavefront/sdk/common/UtilsTest.java b/src/test/java/com/wavefront/sdk/common/UtilsTest.java
index 4c5661df..289dc348 100644
--- a/src/test/java/com/wavefront/sdk/common/UtilsTest.java
+++ b/src/test/java/com/wavefront/sdk/common/UtilsTest.java
@@ -17,6 +17,7 @@
 import static com.wavefront.sdk.common.Utils.*;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.fail;
 
 /**
@@ -850,4 +851,11 @@ public void convertSemVerToGauge() throws IOException {
     assertEquals(1.1010D, Utils.convertSemVerToGauge("1.10.10"));
   }
 
+  @Test
+  public void testIsNullOrEmpty() {
+    assertTrue(Utils.isNullOrEmpty(null));
+    assertTrue(Utils.isNullOrEmpty(""));
+    assertFalse(Utils.isNullOrEmpty("hello"));
+  }
+
 }
diff --git a/src/test/java/com/wavefront/sdk/common/clients/service/CSPTokenServiceTest.java b/src/test/java/com/wavefront/sdk/common/clients/service/CSPTokenServiceTest.java
new file mode 100644
index 00000000..d91ab9c4
--- /dev/null
+++ b/src/test/java/com/wavefront/sdk/common/clients/service/CSPTokenServiceTest.java
@@ -0,0 +1,15 @@
+package com.wavefront.sdk.common.clients.service;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class CSPTokenServiceTest {
+
+  @Test
+  public void testTheThing() {
+    CSPTokenService cspTokenService = new CSPTokenService("https://console-stg.cloud.vmware.com", "REPLACE_ME", "REPLACE_ME");
+
+    assertNotNull(cspTokenService.getToken());
+  }
+}
\ No newline at end of file

From 0c9889c421c706825d0af2bb85ccba1a7302bf1a Mon Sep 17 00:00:00 2001
From: Ernst Riemer <eriemer@vmware.com>
Date: Wed, 9 Aug 2023 15:06:58 -0600
Subject: [PATCH 03/16] WIP - moving some classes to its own package

---
 .../clients/service/CSPTokenService.java      |  91 ------------
 .../CSPAuthorizeResponse.java}                |   1 +
 .../token/CSPServerToServerTokenService.java  | 129 ++++++++++++++++++
 .../service/{ => token}/NoopTokenService.java |   0
 .../service/{ => token}/TokenService.java     |   0
 .../{ => token}/WavefrontTokenService.java    |   0
 .../CSPServerToServerTokenServiceTest.java    |  15 ++
 .../clients/service/CSPTokenServiceTest.java  |  15 --
 8 files changed, 145 insertions(+), 106 deletions(-)
 delete mode 100644 src/main/java/com/wavefront/sdk/common/clients/service/CSPTokenService.java
 rename src/main/java/com/wavefront/sdk/common/clients/service/{CSPResponse.java => token/CSPAuthorizeResponse.java} (94%)
 create mode 100644 src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
 rename src/main/java/com/wavefront/sdk/common/clients/service/{ => token}/NoopTokenService.java (100%)
 rename src/main/java/com/wavefront/sdk/common/clients/service/{ => token}/TokenService.java (100%)
 rename src/main/java/com/wavefront/sdk/common/clients/service/{ => token}/WavefrontTokenService.java (100%)
 create mode 100644 src/test/java/com/wavefront/sdk/common/clients/service/CSPServerToServerTokenServiceTest.java
 delete mode 100644 src/test/java/com/wavefront/sdk/common/clients/service/CSPTokenServiceTest.java

diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/CSPTokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/CSPTokenService.java
deleted file mode 100644
index 3e6962a2..00000000
--- a/src/main/java/com/wavefront/sdk/common/clients/service/CSPTokenService.java
+++ /dev/null
@@ -1,91 +0,0 @@
-package com.wavefront.sdk.common.clients.service;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.util.Base64;
-
-public class CSPTokenService implements TokenService {
-
-  private final String OAUTH_PATH = "/csp/gateway/am/api/auth/authorize";
-  private final ObjectMapper mapper = new ObjectMapper();
-
-  private final String cspBaseURL;
-  private final String cspClientId;
-  private final String cspClientSecret;
-
-  public CSPTokenService(final String cspBaseURL, final String cspClientId, final String cspClientSecret) {
-    this.cspBaseURL = cspBaseURL;
-    this.cspClientId = cspClientId;
-    this.cspClientSecret = cspClientSecret;
-  }
-
-  // TODO - schedule threads?
-  // TODO - cache token ish etc?
-  // TODO - error handling
-  // TODO - tests
-
-  // TODO - CLEANUP
-  private String getCSPToken() {
-    HttpURLConnection urlConn = null;
-
-    String urlParameters  = "grant_type=client_credentials";
-    byte[] postData = urlParameters.getBytes( StandardCharsets.UTF_8 );
-
-    try {
-      URL url = new URL(cspBaseURL + OAUTH_PATH);
-      urlConn = (HttpURLConnection) url.openConnection();
-      urlConn.setDoOutput(true);
-      urlConn.setRequestMethod("POST");
-      urlConn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded");
-      urlConn.addRequestProperty("Authorization", "Basic " + buildHttpBasicToken(cspClientId, cspClientSecret));
-      urlConn.setRequestProperty("Content-Length", Integer.toString(postData.length));
-
-      urlConn.setConnectTimeout(30_000);
-      urlConn.setReadTimeout(10_000);
-
-      //Send request
-      DataOutputStream wr = new DataOutputStream (urlConn.getOutputStream());
-      wr.write(postData);
-      wr.flush();
-      wr.close();
-
-      int statusCode = urlConn.getResponseCode();
-
-      if (statusCode == 200) {
-        // final String response = readAndClose(urlConn.getInputStream());
-
-        try {
-          final CSPResponse parsedResponse = mapper.readValue(urlConn.getInputStream(), CSPResponse.class);
-
-          System.out.println(parsedResponse.accessToken);
-
-          return parsedResponse.accessToken;
-        } catch (JsonProcessingException e) {
-          e.printStackTrace();
-        }
-      }
-
-    } catch (IOException ex) {
-      if (urlConn != null) {
-        // return safeGetResponseCodeAndClose(urlConn);
-      }
-    }
-    return null;
-  }
-
-  private String buildHttpBasicToken(final String cspClientId, final String cspClientSecret) {
-    String encodeMe = cspClientId + ":" + cspClientSecret;
-    return Base64.getEncoder().encodeToString(encodeMe.getBytes());
-  }
-
-  @Override
-  public String getToken() {
-    return getCSPToken();
-  }
-}
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/CSPResponse.java b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPAuthorizeResponse.java
similarity index 94%
rename from src/main/java/com/wavefront/sdk/common/clients/service/CSPResponse.java
rename to src/main/java/com/wavefront/sdk/common/clients/service/token/CSPAuthorizeResponse.java
index 739f9f3f..819817cc 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/CSPResponse.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPAuthorizeResponse.java
@@ -8,6 +8,7 @@ public class CSPResponse {
   @JsonProperty("token_type")
   public String tokenType;
 
+  // This is a timestamp
   @JsonProperty("expires_in")
   public int expiresIn;
 
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
new file mode 100644
index 00000000..af8b1a88
--- /dev/null
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
@@ -0,0 +1,129 @@
+package com.wavefront.sdk.common.clients.service;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.wavefront.sdk.common.NamedThreadFactory;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class CSPServerToServerTokenService implements TokenService {
+  private final static String OAUTH_PATH = "/csp/gateway/am/api/auth/authorize";
+  private final static int CONNECT_TIMEOUT = 30_000;
+  private final static int READ_TIMEOUT = 10_000;
+  private final static int TEN_MINUTES = 600;
+  private final static int THIRTY_SECONDS = 30;
+  private final static int THREE_MINUTES = 180;
+
+  private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("csp-server-to-server-token-service"));
+  private final ObjectMapper mapper = new ObjectMapper();
+
+  private final String cspBaseURL;
+  private final String cspClientId;
+  private final String cspClientSecret;
+
+  private String cspAccessToken;
+  private boolean firstRun = false;
+
+  public CSPServerToServerTokenService(final String cspBaseURL, final String cspClientId, final String cspClientSecret) {
+    this.cspBaseURL = cspBaseURL;
+    this.cspClientId = cspClientId;
+    this.cspClientSecret = cspClientSecret;
+  }
+
+  // TODO - schedule threads?
+  // TODO - error handling
+  // TODO - tests
+
+  @Override
+  public String getToken() {
+    // First access gets the token and is blocking, which schedules the next token fetch.
+    if (!firstRun) {
+      run();
+      firstRun = true;
+    }
+
+    return cspAccessToken;
+  }
+
+  // TODO - CLEANUP
+  private String getCSPToken() {
+    HttpURLConnection urlConn = null;
+
+    final String urlParameters  = "grant_type=client_credentials";
+    final byte[] postData = urlParameters.getBytes( StandardCharsets.UTF_8 );
+
+    try {
+      final URL url = new URL(cspBaseURL + OAUTH_PATH);
+      urlConn = (HttpURLConnection) url.openConnection();
+      urlConn.setDoOutput(true);
+      urlConn.setRequestMethod("POST");
+      urlConn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+      urlConn.addRequestProperty("Authorization", "Basic " + buildHttpBasicToken(cspClientId, cspClientSecret));
+      urlConn.setRequestProperty("Content-Length", Integer.toString(postData.length));
+
+      urlConn.setConnectTimeout(CONNECT_TIMEOUT);
+      urlConn.setReadTimeout(READ_TIMEOUT);
+
+      //Send request
+      final DataOutputStream wr = new DataOutputStream (urlConn.getOutputStream());
+      wr.write(postData);
+      wr.flush();
+      wr.close();
+
+      final int statusCode = urlConn.getResponseCode();
+
+      if (statusCode == 200) {
+        try {
+          final CSPResponse parsedResponse = mapper.readValue(urlConn.getInputStream(), CSPResponse.class);
+
+          System.out.println(parsedResponse.accessToken);
+          System.out.println(parsedResponse.scope);
+          System.out.println(parsedResponse.expiresIn);
+
+          executor.schedule(this::run, getThreadDelay(parsedResponse.expiresIn), TimeUnit.SECONDS);
+
+          return parsedResponse.accessToken;
+        } catch (JsonProcessingException e) {
+          e.printStackTrace();
+
+          return null;
+        }
+      }
+
+    } catch (IOException ex) {
+      System.out.println("WOOOA SAD");
+      if (urlConn != null) {
+        // return safeGetResponseCodeAndClose(urlConn);
+      }
+    }
+
+    System.out.println("WOOOA NOOOO");
+    return null;
+  }
+
+  private int getThreadDelay(final int expiresIn) {
+    // TODO - what times make the most sense?
+    if (expiresIn < TEN_MINUTES) {
+      return expiresIn - THIRTY_SECONDS;
+    }
+
+    return expiresIn - THREE_MINUTES;
+  }
+
+  private void run() {
+    this.cspAccessToken = getCSPToken();
+  }
+
+  private String buildHttpBasicToken(final String cspClientId, final String cspClientSecret) {
+    final String encodeMe = cspClientId + ":" + cspClientSecret;
+    return Base64.getEncoder().encodeToString(encodeMe.getBytes());
+  }
+}
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/NoopTokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/token/NoopTokenService.java
similarity index 100%
rename from src/main/java/com/wavefront/sdk/common/clients/service/NoopTokenService.java
rename to src/main/java/com/wavefront/sdk/common/clients/service/token/NoopTokenService.java
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/TokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/token/TokenService.java
similarity index 100%
rename from src/main/java/com/wavefront/sdk/common/clients/service/TokenService.java
rename to src/main/java/com/wavefront/sdk/common/clients/service/token/TokenService.java
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/WavefrontTokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/token/WavefrontTokenService.java
similarity index 100%
rename from src/main/java/com/wavefront/sdk/common/clients/service/WavefrontTokenService.java
rename to src/main/java/com/wavefront/sdk/common/clients/service/token/WavefrontTokenService.java
diff --git a/src/test/java/com/wavefront/sdk/common/clients/service/CSPServerToServerTokenServiceTest.java b/src/test/java/com/wavefront/sdk/common/clients/service/CSPServerToServerTokenServiceTest.java
new file mode 100644
index 00000000..0e7b0751
--- /dev/null
+++ b/src/test/java/com/wavefront/sdk/common/clients/service/CSPServerToServerTokenServiceTest.java
@@ -0,0 +1,15 @@
+package com.wavefront.sdk.common.clients.service;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class CSPTokenServiceTest {
+
+  @Test
+  public void testTheThing() throws InterruptedException {
+    CSPTokenService cspTokenService = new CSPTokenService("https://console-stg.cloud.vmware.com", "cjqbsvWmbEkyNUJxqJy2jA7qEgOBFzhJdbI", "Fy1lDoRCE3ycvkFesHfzznHFjtsGKk74DEWHzfml1sDW5AT5vx");
+
+    assertNotNull(cspTokenService.getToken());
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/com/wavefront/sdk/common/clients/service/CSPTokenServiceTest.java b/src/test/java/com/wavefront/sdk/common/clients/service/CSPTokenServiceTest.java
deleted file mode 100644
index d91ab9c4..00000000
--- a/src/test/java/com/wavefront/sdk/common/clients/service/CSPTokenServiceTest.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.wavefront.sdk.common.clients.service;
-
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-class CSPTokenServiceTest {
-
-  @Test
-  public void testTheThing() {
-    CSPTokenService cspTokenService = new CSPTokenService("https://console-stg.cloud.vmware.com", "REPLACE_ME", "REPLACE_ME");
-
-    assertNotNull(cspTokenService.getToken());
-  }
-}
\ No newline at end of file

From d9316c9b021bb16a38b2b46be00765b6a811fd65 Mon Sep 17 00:00:00 2001
From: Ernst Riemer <eriemer@vmware.com>
Date: Wed, 9 Aug 2023 15:11:26 -0600
Subject: [PATCH 04/16] WIP - most of the work for CSP auth for the java SDK is
 done

---
 .../sdk/common/clients/WavefrontClient.java   | 68 ++++++++++-------
 .../clients/service/ReportingService.java     |  4 +-
 .../service/token/CSPAuthorizeResponse.java   |  4 +-
 .../token/CSPServerToServerTokenService.java  | 75 ++++++++++++-------
 .../service/token/NoopTokenService.java       |  2 +-
 .../clients/service/token/TokenService.java   |  2 +-
 .../service/token/WavefrontTokenService.java  |  2 +-
 .../common/clients/WavefrontClientTest.java   | 30 +++++++-
 .../CSPServerToServerTokenServiceTest.java    | 68 +++++++++++++++--
 9 files changed, 184 insertions(+), 71 deletions(-)

diff --git a/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java b/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java
index 3eaefaea..0c7c5a6e 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java
@@ -1,9 +1,9 @@
 package com.wavefront.sdk.common.clients;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
 import com.google.common.base.Throwables;
 import com.google.common.collect.Maps;
 
-import com.fasterxml.jackson.core.JsonProcessingException;
 import com.wavefront.sdk.common.Constants;
 import com.wavefront.sdk.common.NamedThreadFactory;
 import com.wavefront.sdk.common.Pair;
@@ -11,7 +11,11 @@
 import com.wavefront.sdk.common.WavefrontSender;
 import com.wavefront.sdk.common.annotation.NonNull;
 import com.wavefront.sdk.common.annotation.Nullable;
-import com.wavefront.sdk.common.clients.service.*;
+import com.wavefront.sdk.common.clients.service.ReportingService;
+import com.wavefront.sdk.common.clients.service.token.CSPServerToServerTokenService;
+import com.wavefront.sdk.common.clients.service.token.NoopTokenService;
+import com.wavefront.sdk.common.clients.service.token.TokenService;
+import com.wavefront.sdk.common.clients.service.token.WavefrontTokenService;
 import com.wavefront.sdk.common.logging.MessageDedupingLogger;
 import com.wavefront.sdk.common.metrics.WavefrontSdkDeltaCounter;
 import com.wavefront.sdk.common.metrics.WavefrontSdkMetricsRegistry;
@@ -137,6 +141,8 @@ public class WavefrontClient implements WavefrontSender, Runnable {
   // Flag to prevent sending after close() has been called
   private final AtomicBoolean closed = new AtomicBoolean(false);
 
+  private final TokenService tokenService;
+
   public static class Builder {
     // Required parameters
     private final String server;
@@ -160,9 +166,6 @@ public static class Builder {
     private URI metricsUri;
     private URI tracesUri;
 
-    // TODO - tests for diff kinds of TokenService
-    // TODO - tests for diff kinds of TokenService
-
     /**
      * Create a new WavefrontClient.Builder
      *
@@ -180,8 +183,9 @@ public Builder(String server, @Nullable String token) {
      * @param server A server URL of the form "https://clusterName.wavefront.com" or
      *               "http://internal.proxy.com:port"
      * @param token  A valid API token with direct ingestion permissions
-     * @param cspClientId  TODO
-     * @param cspClientSecret  TODO
+     * @param cspBaseUrl  A server URL that points to the CSP service
+     * @param cspClientId  Client ID for CSP
+     * @param cspClientSecret  Client Secret for CSP
      */
     public Builder(String server, @Nullable String token, @Nullable String cspBaseUrl, @Nullable String cspClientId, @Nullable String cspClientSecret) {
       this.server = server;
@@ -196,17 +200,18 @@ public Builder(String server, @Nullable String token, @Nullable String cspBaseUr
      *
      * @param server A server URL of the form "https://clusterName.wavefront.com" or
      *               "http://internal.proxy.com:port"
-     * @param cspClientId  TODO
-     * @param cspClientSecret  TODO
+     * @param cspBaseUrl  A server URL that points to the CSP service
+     * @param cspClientId  Client ID for CSP
+     * @param cspClientSecret  Client Secret for CSP
      */
-    public Builder(String server, @Nullable String cspClientId, @Nullable String cspClientSecret) {
-      this(server, null, null, cspClientId, cspClientSecret);
+    public Builder(String server, @Nullable String cspBaseUrl, @Nullable String cspClientId, @Nullable String cspClientSecret) {
+      this(server, null, cspBaseUrl, cspClientId, cspClientSecret);
     }
 
     /**
      * Create a new WavefrontClient.Builder
      *
-     * @param proxyServer A server URL of the the form "http://internal.proxy.com:port"
+     * @param proxyServer A server URL of the form "http://internal.proxy.com:port"
      */
     public Builder(String proxyServer) {
       this(proxyServer, null, null, null, null);
@@ -393,23 +398,24 @@ private WavefrontClient(Builder builder) {
     }
     defaultSource = tempSource;
 
-    TokenService tokenService = null;
-
     if (!Utils.isNullOrEmpty(builder.token)) {
       tokenService = new WavefrontTokenService(builder.token);
-    }
-
-    // TODO if they set one or the other, log?
-    if (!Utils.isNullOrEmpty(builder.cspBaseUrl) && !Utils.isNullOrEmpty(builder.cspClientId) && !Utils.isNullOrEmpty(builder.cspClientSecret)) {
-      // TODO URL
-      tokenService = new CSPTokenService(builder.cspBaseUrl, builder.cspClientId, builder.cspClientSecret);
-    }
-
-    if (tokenService == null) {
+    } else if (!Utils.isNullOrEmpty(builder.cspBaseUrl) && !Utils.isNullOrEmpty(builder.cspClientId) && !Utils.isNullOrEmpty(builder.cspClientSecret)) {
+      tokenService = new CSPServerToServerTokenService(builder.cspBaseUrl, builder.cspClientId, builder.cspClientSecret);
+    } else {
       // NOOP LOGS
       tokenService = new NoopTokenService();
     }
 
+    switch (tokenService.getClass().getSimpleName()) {
+      case "CSPServerToServerTokenService":
+        logger.log(Level.INFO, "The Wavefront SDK will use CSP authentication when communicating with the Wavefront Backend for Direct Ingestion.");
+      case "WavefrontTokenService":
+        logger.log(Level.INFO, "The Wavefront SDK will use an API TOKEN when communicating with the Wavefront Backend for Direct Ingestion.");
+      case "NoopTokenService":
+        logger.log(Level.INFO, "The Wavefront SDK will communicate with a Wavefront Proxy.");
+    }
+
     batchSize = builder.batchSize;
     messageSizeBytes = builder.messageSizeBytes;
     metricsBuffer = new LinkedBlockingQueue<>(builder.maxQueueSize);
@@ -497,6 +503,10 @@ public String getClientId() {
     return clientId;
   }
 
+  public TokenService getTokenService() {
+    return tokenService;
+  }
+
   /** {@inheritDoc} */
   @Override
   public void sendMetric(String name, double value, @Nullable Long timestamp,
@@ -753,6 +763,14 @@ private void internalFlush(LinkedBlockingQueue<String> buffer, String format,
                              LogMessageType permissionsMessageType,
                              LogMessageType bufferFullMessageType)
       throws IOException {
+
+    String tokenIdentifier = "";
+    if (tokenService.getClass().equals(CSPServerToServerTokenService.class)) {
+      tokenIdentifier = "CSP ACCESS TOKEN";
+    } else if (tokenService.getClass().equals(WavefrontTokenService.class)) {
+      tokenIdentifier = "API TOKEN";
+    }
+
     ReportingService entityReportingService;
     switch (format) {
       case Constants.WAVEFRONT_SPAN_LOG_FORMAT:
@@ -778,7 +796,7 @@ private void internalFlush(LinkedBlockingQueue<String> buffer, String format,
         switch (featureDisabledReason) {
           case 401:
             logger.log(permissionsMessageType.toString(), Level.SEVERE,
-                "Please verify that your API Token is correct! All " + entityType + " will be " +
+                "Please verify that your " + tokenIdentifier + " is correct! All " + entityType + " will be " +
                     "discarded until the service is restarted.");
             break;
           case 403:
@@ -808,7 +826,7 @@ private void internalFlush(LinkedBlockingQueue<String> buffer, String format,
             case 401:
               logger.log(permissionsMessageType.toString(), Level.SEVERE,
                   "Error sending " + entityType + " to Wavefront (HTTP " + statusCode + "). " +
-                      "Please verify that your API Token is correct! All " + entityType + " will " +
+                      "Please verify that your " + tokenIdentifier + " is correct! All " + entityType + " will " +
                       "be discarded until the service is restarted.");
               featureDisabledStatusCode.set(statusCode);
               dropped.inc(items.size());
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/ReportingService.java b/src/main/java/com/wavefront/sdk/common/clients/service/ReportingService.java
index 2b914b0c..345b16e4 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/ReportingService.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/ReportingService.java
@@ -1,10 +1,9 @@
 package com.wavefront.sdk.common.clients.service;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Strings;
 import com.wavefront.sdk.common.Constants;
 import com.wavefront.sdk.common.Utils;
-import com.wavefront.sdk.common.annotation.Nullable;
+import com.wavefront.sdk.common.clients.service.token.TokenService;
 import com.wavefront.sdk.common.logging.MessageSuppressingLogger;
 
 import java.io.IOException;
@@ -31,7 +30,6 @@ public class ReportingService implements ReportAPI {
   // configuration, this is not a static logger.
   private final MessageSuppressingLogger messageSuppressingLogger;
 
-  // TODO - tests for diff kinds of TokenService
   private final TokenService tokenService;
   private final URI uri;
 
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPAuthorizeResponse.java b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPAuthorizeResponse.java
index 819817cc..a6dbd3f9 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPAuthorizeResponse.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPAuthorizeResponse.java
@@ -1,7 +1,7 @@
-package com.wavefront.sdk.common.clients.service;
+package com.wavefront.sdk.common.clients.service.token;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
-public class CSPResponse {
+public class CSPAuthorizeResponse {
   @JsonProperty("id_token")
   public String idToken;
 
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
index af8b1a88..82a020c5 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
@@ -1,4 +1,4 @@
-package com.wavefront.sdk.common.clients.service;
+package com.wavefront.sdk.common.clients.service.token;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -13,47 +13,56 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Logger;
+
+public class CSPServerToServerTokenService implements TokenService, Runnable {
+  private static final Logger log = Logger.getLogger(CSPServerToServerTokenService.class.getCanonicalName());
 
-public class CSPServerToServerTokenService implements TokenService {
   private final static String OAUTH_PATH = "/csp/gateway/am/api/auth/authorize";
-  private final static int CONNECT_TIMEOUT = 30_000;
-  private final static int READ_TIMEOUT = 10_000;
   private final static int TEN_MINUTES = 600;
   private final static int THIRTY_SECONDS = 30;
   private final static int THREE_MINUTES = 180;
 
   private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("csp-server-to-server-token-service"));
   private final ObjectMapper mapper = new ObjectMapper();
+  private final AtomicBoolean tokenReady = new AtomicBoolean(false);
 
   private final String cspBaseURL;
   private final String cspClientId;
   private final String cspClientSecret;
 
+  private final int connectTimeoutMillis;
+  private final int readTimeoutMillis;
   private String cspAccessToken;
-  private boolean firstRun = false;
 
   public CSPServerToServerTokenService(final String cspBaseURL, final String cspClientId, final String cspClientSecret) {
     this.cspBaseURL = cspBaseURL;
     this.cspClientId = cspClientId;
     this.cspClientSecret = cspClientSecret;
+    this.connectTimeoutMillis = 30_000;
+    this.readTimeoutMillis = 10_000;
   }
 
-  // TODO - schedule threads?
-  // TODO - error handling
-  // TODO - tests
+  public CSPServerToServerTokenService(final String cspBaseURL, final String cspClientId, final String cspClientSecret, final int connectTimeoutMillis, final int readTimeoutMillis) {
+    this.cspBaseURL = cspBaseURL;
+    this.cspClientId = cspClientId;
+    this.cspClientSecret = cspClientSecret;
+    this.connectTimeoutMillis = connectTimeoutMillis;
+    this.readTimeoutMillis = readTimeoutMillis;
+  }
 
   @Override
-  public String getToken() {
+  public synchronized String getToken() {
     // First access gets the token and is blocking, which schedules the next token fetch.
-    if (!firstRun) {
+    if (!tokenReady.get()) {
       run();
-      firstRun = true;
+      tokenReady.set(true);
     }
 
     return cspAccessToken;
   }
 
-  // TODO - CLEANUP
   private String getCSPToken() {
     HttpURLConnection urlConn = null;
 
@@ -69,8 +78,8 @@ private String getCSPToken() {
       urlConn.addRequestProperty("Authorization", "Basic " + buildHttpBasicToken(cspClientId, cspClientSecret));
       urlConn.setRequestProperty("Content-Length", Integer.toString(postData.length));
 
-      urlConn.setConnectTimeout(CONNECT_TIMEOUT);
-      urlConn.setReadTimeout(READ_TIMEOUT);
+      urlConn.setConnectTimeout(connectTimeoutMillis);
+      urlConn.setReadTimeout(readTimeoutMillis);
 
       //Send request
       final DataOutputStream wr = new DataOutputStream (urlConn.getOutputStream());
@@ -82,35 +91,45 @@ private String getCSPToken() {
 
       if (statusCode == 200) {
         try {
-          final CSPResponse parsedResponse = mapper.readValue(urlConn.getInputStream(), CSPResponse.class);
+          final CSPAuthorizeResponse parsedResponse = mapper.readValue(urlConn.getInputStream(), CSPAuthorizeResponse.class);
 
-          System.out.println(parsedResponse.accessToken);
-          System.out.println(parsedResponse.scope);
-          System.out.println(parsedResponse.expiresIn);
+          // TODO - make sure direct ingestion is enabled in scopes?
 
+          // Schedule token refresh in the future
           executor.schedule(this::run, getThreadDelay(parsedResponse.expiresIn), TimeUnit.SECONDS);
 
           return parsedResponse.accessToken;
         } catch (JsonProcessingException e) {
-          e.printStackTrace();
+          log.info("The request to CSP returned invalid json. Please restart your app.");
+
+          return "INVALID_TOKEN";
+        }
+      } else {
+        log.info("The request to CSP returned: " + statusCode);
+
+        if (statusCode >= 500 && statusCode < 600) {
+          log.info("The Wavefront SDK will try to reauthenticate with CSP on the next request.");
+          tokenReady.set(false);
 
           return null;
         }
+
+        // Anything not 5xx will return INVALID_TOKEN
+        return "INVALID_TOKEN";
       }
 
     } catch (IOException ex) {
-      System.out.println("WOOOA SAD");
-      if (urlConn != null) {
-        // return safeGetResponseCodeAndClose(urlConn);
-      }
-    }
+      // Connection Problem
+      log.warning("Error connecting to CSP: " + ex.getLocalizedMessage());
+
+      log.info("The Wavefront SDK will try to reauthenticate with CSP on the next request.");
+      tokenReady.set(false);
 
-    System.out.println("WOOOA NOOOO");
-    return null;
+      return null;
+    }
   }
 
   private int getThreadDelay(final int expiresIn) {
-    // TODO - what times make the most sense?
     if (expiresIn < TEN_MINUTES) {
       return expiresIn - THIRTY_SECONDS;
     }
@@ -118,7 +137,7 @@ private int getThreadDelay(final int expiresIn) {
     return expiresIn - THREE_MINUTES;
   }
 
-  private void run() {
+  public synchronized void run() {
     this.cspAccessToken = getCSPToken();
   }
 
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/token/NoopTokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/token/NoopTokenService.java
index e7cbc5db..12173907 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/token/NoopTokenService.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/token/NoopTokenService.java
@@ -1,4 +1,4 @@
-package com.wavefront.sdk.common.clients.service;
+package com.wavefront.sdk.common.clients.service.token;
 
 // Primarily for proxy usage
 public class NoopTokenService implements TokenService {
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/token/TokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/token/TokenService.java
index 3cc81d37..944e1fd7 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/token/TokenService.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/token/TokenService.java
@@ -1,4 +1,4 @@
-package com.wavefront.sdk.common.clients.service;
+package com.wavefront.sdk.common.clients.service.token;
 
 public interface TokenService {
 
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/token/WavefrontTokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/token/WavefrontTokenService.java
index cf8081c4..33d1aa31 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/token/WavefrontTokenService.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/token/WavefrontTokenService.java
@@ -1,4 +1,4 @@
-package com.wavefront.sdk.common.clients.service;
+package com.wavefront.sdk.common.clients.service.token;
 
 public class WavefrontTokenService implements TokenService {
 
diff --git a/src/test/java/com/wavefront/sdk/common/clients/WavefrontClientTest.java b/src/test/java/com/wavefront/sdk/common/clients/WavefrontClientTest.java
index c08b9b33..1d545de9 100644
--- a/src/test/java/com/wavefront/sdk/common/clients/WavefrontClientTest.java
+++ b/src/test/java/com/wavefront/sdk/common/clients/WavefrontClientTest.java
@@ -3,6 +3,9 @@
 import com.github.tomakehurst.wiremock.WireMockServer;
 import com.github.tomakehurst.wiremock.client.WireMock;
 import com.wavefront.sdk.common.Pair;
+import com.wavefront.sdk.common.clients.service.token.CSPServerToServerTokenService;
+import com.wavefront.sdk.common.clients.service.token.NoopTokenService;
+import com.wavefront.sdk.common.clients.service.token.WavefrontTokenService;
 import com.wavefront.sdk.common.metrics.WavefrontSdkDeltaCounter;
 import com.wavefront.sdk.entities.histograms.HistogramGranularity;
 
@@ -22,10 +25,7 @@
 import static com.github.tomakehurst.wiremock.client.WireMock.*;
 import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
 import static org.easymock.EasyMock.*;
-import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.*;
 
 /**
  * Tests for the {@link WavefrontClient} class
@@ -214,6 +214,28 @@ void canSendTracesToDifferentPort() {
 
   @Nested
   class Builder {
+
+    @Test
+    public void tokenServiceClassTest() {
+      WavefrontClient wfClient = new WavefrontClient.Builder("", "TOKEN")
+              .build();
+      assertNotNull(wfClient);
+      assertNotNull(wfClient.getTokenService());
+      assertEquals(wfClient.getTokenService().getClass().getSimpleName(), WavefrontTokenService.class.getSimpleName());
+
+      wfClient = new WavefrontClient.Builder("")
+              .build();
+      assertNotNull(wfClient);
+      assertNotNull(wfClient.getTokenService());
+      assertEquals(wfClient.getTokenService().getClass().getSimpleName(), NoopTokenService.class.getSimpleName());
+
+      wfClient = new WavefrontClient.Builder("", "cspBaseUrl", "cspClientId", "cspClientSecret")
+              .build();
+      assertNotNull(wfClient);
+      assertNotNull(wfClient.getTokenService());
+      assertEquals(wfClient.getTokenService().getClass().getSimpleName(), CSPServerToServerTokenService.class.getSimpleName());
+    }
+
     @Nested
     class ValidateEndpoint {
       @Test
diff --git a/src/test/java/com/wavefront/sdk/common/clients/service/CSPServerToServerTokenServiceTest.java b/src/test/java/com/wavefront/sdk/common/clients/service/CSPServerToServerTokenServiceTest.java
index 0e7b0751..443ec770 100644
--- a/src/test/java/com/wavefront/sdk/common/clients/service/CSPServerToServerTokenServiceTest.java
+++ b/src/test/java/com/wavefront/sdk/common/clients/service/CSPServerToServerTokenServiceTest.java
@@ -1,15 +1,71 @@
 package com.wavefront.sdk.common.clients.service;
 
+import com.github.tomakehurst.wiremock.WireMockServer;
+import com.github.tomakehurst.wiremock.client.WireMock;
+import com.wavefront.sdk.common.clients.service.token.CSPServerToServerTokenService;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
-import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
+import static org.junit.jupiter.api.Assertions.*;
 
-class CSPTokenServiceTest {
+class CSPServerToServerTokenServiceTest {
 
-  @Test
-  public void testTheThing() throws InterruptedException {
-    CSPTokenService cspTokenService = new CSPTokenService("https://console-stg.cloud.vmware.com", "cjqbsvWmbEkyNUJxqJy2jA7qEgOBFzhJdbI", "Fy1lDoRCE3ycvkFesHfzznHFjtsGKk74DEWHzfml1sDW5AT5vx");
+  @Nested
+  class WireMockTests {
+    WireMockServer mockBackend;
 
-    assertNotNull(cspTokenService.getToken());
+    private final String MOCK_RESPONSE = "{\"scope\":\"scope\",\"id_token\":null,\"token_type\":\"bearer\",\"expires_in\":1799,\"access_token\":\"accessToken\",\"refresh_token\":null}\n";
+
+    @BeforeEach
+    void setup() {
+      mockBackend = new WireMockServer(wireMockConfig().dynamicPort());
+    }
+
+    @AfterEach
+    void teardown() {
+      mockBackend.stop();
+    }
+
+    @Test
+    void testCSPReturnsAccessToken() {
+      mockBackend.stubFor(WireMock.post(urlPathMatching("/csp/gateway/am/api/auth/authorize")).willReturn(WireMock.ok(MOCK_RESPONSE)));
+      mockBackend.start();
+
+      CSPServerToServerTokenService cspServerToServerTokenService = new CSPServerToServerTokenService(mockBackend.baseUrl(), "N/A", "N/A");
+      assertNotNull(cspServerToServerTokenService);
+      assertEquals(cspServerToServerTokenService.getToken(), "accessToken");
+    }
+
+    @Test
+    void testCSPReturns401() {
+      mockBackend.stubFor(WireMock.post(urlPathMatching("/csp/gateway/am/api/auth/authorize")).willReturn(WireMock.unauthorized()));
+      mockBackend.start();
+
+      CSPServerToServerTokenService cspServerToServerTokenService = new CSPServerToServerTokenService(mockBackend.baseUrl(), "N/A", "N/A");
+      assertEquals(cspServerToServerTokenService.getToken(), "INVALID_TOKEN");
+    }
+
+    @Test
+    void testCSPReturns500() {
+      mockBackend.stubFor(WireMock.post(urlPathMatching("/csp/gateway/am/api/auth/authorize")).willReturn(WireMock.serverError()));
+      mockBackend.start();
+
+      CSPServerToServerTokenService cspServerToServerTokenService = new CSPServerToServerTokenService(mockBackend.baseUrl(), "N/A", "N/A");
+      assertNull(cspServerToServerTokenService.getToken());
+    }
+
+    @Test
+    void testCSPConnectionError() {
+      mockBackend.stubFor(WireMock.post(urlPathMatching("/csp/gateway/am/api/auth/authorize")).willReturn(WireMock.serverError()));
+      mockBackend.setGlobalFixedDelay(5_000);
+      mockBackend.start();
+
+      CSPServerToServerTokenService cspServerToServerTokenService = new CSPServerToServerTokenService(mockBackend.baseUrl(), "N/A", "N/A", 100, 100);
+      assertNull(cspServerToServerTokenService.getToken());
+    }
   }
 }
\ No newline at end of file

From b9699a7b9c14626abdc5046fbb469e70d3ab3e87 Mon Sep 17 00:00:00 2001
From: Ernst Riemer <eriemer@vmware.com>
Date: Thu, 10 Aug 2023 10:43:47 -0600
Subject: [PATCH 05/16] Added a warning message when csp response does not have
 a direct ingest scope

---
 .../token/CSPServerToServerTokenService.java  | 22 +++++++++++++++++--
 .../CSPServerToServerTokenServiceTest.java    | 15 +++++++++++++
 2 files changed, 35 insertions(+), 2 deletions(-)

diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
index 82a020c5..b2f5415a 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
@@ -3,18 +3,22 @@
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.wavefront.sdk.common.NamedThreadFactory;
+import com.wavefront.sdk.common.Utils;
 
 import java.io.DataOutputStream;
 import java.io.IOException;
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
 import java.util.Base64;
+import java.util.List;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Logger;
+import java.util.stream.Collectors;
 
 public class CSPServerToServerTokenService implements TokenService, Runnable {
   private static final Logger log = Logger.getLogger(CSPServerToServerTokenService.class.getCanonicalName());
@@ -93,10 +97,12 @@ private String getCSPToken() {
         try {
           final CSPAuthorizeResponse parsedResponse = mapper.readValue(urlConn.getInputStream(), CSPAuthorizeResponse.class);
 
-          // TODO - make sure direct ingestion is enabled in scopes?
+          if (!hasDirectIngestScope(parsedResponse.scope)) {
+            log.warning("The CSP response did not find any scope matching 'aoa:directDataIngestion' which is required for Wavefront direct ingestion.");
+          }
 
           // Schedule token refresh in the future
-          executor.schedule(this::run, getThreadDelay(parsedResponse.expiresIn), TimeUnit.SECONDS);
+          executor.schedule(this, getThreadDelay(parsedResponse.expiresIn), TimeUnit.SECONDS);
 
           return parsedResponse.accessToken;
         } catch (JsonProcessingException e) {
@@ -141,6 +147,18 @@ public synchronized void run() {
     this.cspAccessToken = getCSPToken();
   }
 
+  private static List<String> parseScopes(final String scope) {
+    return Arrays.stream(scope.split("\\s")).collect(Collectors.toList());
+  }
+
+  public static boolean hasDirectIngestScope(final String scopeList) {
+    if (!Utils.isNullOrEmpty(scopeList)) {
+      return parseScopes(scopeList).stream().anyMatch(s -> s.contains("aoa:directDataIngestion"));
+    }
+
+    return false;
+  }
+
   private String buildHttpBasicToken(final String cspClientId, final String cspClientSecret) {
     final String encodeMe = cspClientId + ":" + cspClientSecret;
     return Base64.getEncoder().encodeToString(encodeMe.getBytes());
diff --git a/src/test/java/com/wavefront/sdk/common/clients/service/CSPServerToServerTokenServiceTest.java b/src/test/java/com/wavefront/sdk/common/clients/service/CSPServerToServerTokenServiceTest.java
index 443ec770..47cc2a77 100644
--- a/src/test/java/com/wavefront/sdk/common/clients/service/CSPServerToServerTokenServiceTest.java
+++ b/src/test/java/com/wavefront/sdk/common/clients/service/CSPServerToServerTokenServiceTest.java
@@ -8,12 +8,27 @@
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
+import java.util.UUID;
+
 import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;
 import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
+import static com.wavefront.sdk.common.clients.service.token.CSPServerToServerTokenService.hasDirectIngestScope;
 import static org.junit.jupiter.api.Assertions.*;
 
 class CSPServerToServerTokenServiceTest {
 
+  @Test
+  public void testHasDirectIngestScope() {
+    final String uuid = UUID.randomUUID().toString();
+
+    final String scopeString = "external/" + uuid + "/*/aoa:directDataIngestion external/" + uuid + "/aoa:directDataIngestion csp:org_member";
+
+    assertTrue(hasDirectIngestScope(scopeString));
+    assertFalse(hasDirectIngestScope("no direct data ingestion scope"));
+    assertFalse(hasDirectIngestScope(""));
+    assertFalse(hasDirectIngestScope(null));
+  }
+
   @Nested
   class WireMockTests {
     WireMockServer mockBackend;

From d9fb409d6cae168d533fab828b710713e0171ac3 Mon Sep 17 00:00:00 2001
From: Ernst Riemer <eriemer@vmware.com>
Date: Thu, 10 Aug 2023 10:46:46 -0600
Subject: [PATCH 06/16] Updating deps

---
 pom.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pom.xml b/pom.xml
index d1e87d96..81d39029 100644
--- a/pom.xml
+++ b/pom.xml
@@ -97,14 +97,14 @@
         <dependency>
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
-            <version>32.1.1-jre</version>
+            <version>32.1.2-jre</version>
         </dependency>
 
         <!-- Test Dependencies -->
         <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter</artifactId>
-            <version>5.9.3</version>
+            <version>5.10.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>

From 551b22461084d388ce0d853a2d2d3b25ea19524e Mon Sep 17 00:00:00 2001
From: Ernst Riemer <eriemer@vmware.com>
Date: Thu, 10 Aug 2023 11:05:23 -0600
Subject: [PATCH 07/16] Fixing typo

---
 .../sdk/common/clients/service/token/CSPAuthorizeResponse.java  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPAuthorizeResponse.java b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPAuthorizeResponse.java
index a6dbd3f9..b10e0e20 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPAuthorizeResponse.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPAuthorizeResponse.java
@@ -8,7 +8,7 @@ public class CSPAuthorizeResponse {
   @JsonProperty("token_type")
   public String tokenType;
 
-  // This is a timestamp
+  // This is in seconds
   @JsonProperty("expires_in")
   public int expiresIn;
 

From 34b995a40a202e9589bf2497b2f3f57b43c27683 Mon Sep 17 00:00:00 2001
From: Glenn Oppegard <goppegard@vmware.com>
Date: Thu, 10 Aug 2023 12:15:47 -0600
Subject: [PATCH 08/16] formatting

---
 .../sdk/common/clients/WavefrontClient.java   | 89 ++++++++++++-------
 .../clients/service/ReportingService.java     | 13 ++-
 .../service/token/CSPAuthorizeResponse.java   |  1 +
 .../token/CSPServerToServerTokenService.java  |  6 +-
 .../clients/service/token/TokenService.java   |  2 -
 .../service/token/WavefrontTokenService.java  |  1 -
 .../com/wavefront/sdk/common/UtilsTest.java   |  2 +
 .../common/clients/WavefrontClientTest.java   | 26 ++++--
 .../CSPServerToServerTokenServiceTest.java    |  7 +-
 9 files changed, 98 insertions(+), 49 deletions(-)

diff --git a/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java b/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java
index 0c7c5a6e..46d4fd48 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java
@@ -1,9 +1,9 @@
 package com.wavefront.sdk.common.clients;
 
-import com.fasterxml.jackson.core.JsonProcessingException;
 import com.google.common.base.Throwables;
 import com.google.common.collect.Maps;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
 import com.wavefront.sdk.common.Constants;
 import com.wavefront.sdk.common.NamedThreadFactory;
 import com.wavefront.sdk.common.Pair;
@@ -161,7 +161,7 @@ public static class Builder {
     private TimeUnit flushIntervalTimeUnit = TimeUnit.SECONDS;
     private int messageSizeBytes = Integer.MAX_VALUE;
     private boolean includeSdkMetrics = true;
-    private Map<String, String> tags = Maps.newHashMap();
+    private final Map<String, String> tags = Maps.newHashMap();
 
     private URI metricsUri;
     private URI tracesUri;
@@ -180,12 +180,12 @@ public Builder(String server, @Nullable String token) {
     /**
      * Create a new WavefrontClient.Builder
      *
-     * @param server A server URL of the form "https://clusterName.wavefront.com" or
-     *               "http://internal.proxy.com:port"
-     * @param token  A valid API token with direct ingestion permissions
-     * @param cspBaseUrl  A server URL that points to the CSP service
-     * @param cspClientId  Client ID for CSP
-     * @param cspClientSecret  Client Secret for CSP
+     * @param server          A server URL of the form "https://clusterName.wavefront.com" or
+     *                        "http://internal.proxy.com:port"
+     * @param token           A valid API token with direct ingestion permissions
+     * @param cspBaseUrl      A server URL that points to the CSP service
+     * @param cspClientId     Client ID for CSP
+     * @param cspClientSecret Client Secret for CSP
      */
     public Builder(String server, @Nullable String token, @Nullable String cspBaseUrl, @Nullable String cspClientId, @Nullable String cspClientSecret) {
       this.server = server;
@@ -198,11 +198,11 @@ public Builder(String server, @Nullable String token, @Nullable String cspBaseUr
     /**
      * Create a new WavefrontClient.Builder
      *
-     * @param server A server URL of the form "https://clusterName.wavefront.com" or
-     *               "http://internal.proxy.com:port"
-     * @param cspBaseUrl  A server URL that points to the CSP service
-     * @param cspClientId  Client ID for CSP
-     * @param cspClientSecret  Client Secret for CSP
+     * @param server          A server URL of the form "https://clusterName.wavefront.com" or
+     *                        "http://internal.proxy.com:port"
+     * @param cspBaseUrl      A server URL that points to the CSP service
+     * @param cspClientId     Client ID for CSP
+     * @param cspClientSecret Client Secret for CSP
      */
     public Builder(String server, @Nullable String cspBaseUrl, @Nullable String cspClientId, @Nullable String cspClientSecret) {
       this(server, null, cspBaseUrl, cspClientId, cspClientSecret);
@@ -313,6 +313,7 @@ public Builder includeSdkMetrics(boolean includeSdkMetrics) {
 
     /**
      * Set the tags to apply to the internal SDK metrics
+     *
      * @param tags a map of point tags to apply to the internal sdk metrics
      * @return {@code this}
      */
@@ -371,7 +372,7 @@ public WavefrontClient build() {
         this.metricsUri = buildUri(this.server, this.metricsPort);
         this.tracesUri = buildUri(this.server, this.tracesPort);
       } catch (URISyntaxException e) {
-          throw new IllegalStateException(e);
+        throw new IllegalStateException(e);
       }
 
       return new WavefrontClient(this);
@@ -384,7 +385,7 @@ private URI buildUri(String server, int port) throws URISyntaxException {
       }
 
       return new URI(uri.getScheme(), null, uri.getHost(), port, uri.getPath(), uri.getQuery(),
-            uri.getFragment());
+          uri.getFragment());
     }
   }
 
@@ -497,7 +498,9 @@ private WavefrontClient(Builder builder) {
     this.clientId = builder.server;
   }
 
-  /** {@inheritDoc} */
+  /**
+   * {@inheritDoc}
+   */
   @Override
   public String getClientId() {
     return clientId;
@@ -507,7 +510,9 @@ public TokenService getTokenService() {
     return tokenService;
   }
 
-  /** {@inheritDoc} */
+  /**
+   * {@inheritDoc}
+   */
   @Override
   public void sendMetric(String name, double value, @Nullable Long timestamp,
                          @Nullable String source, @Nullable Map<String, String> tags)
@@ -533,7 +538,9 @@ public void sendMetric(String name, double value, @Nullable Long timestamp,
     }
   }
 
-  /** {@inheritDoc} */
+  /**
+   * {@inheritDoc}
+   */
   @Override
   public void sendFormattedMetric(String point) throws IOException {
     if (closed.get()) {
@@ -555,7 +562,9 @@ public void sendFormattedMetric(String point) throws IOException {
     }
   }
 
-  /** {@inheritDoc} */
+  /**
+   * {@inheritDoc}
+   */
   @Override
   public void sendDistribution(String name, List<Pair<Double, Integer>> centroids,
                                Set<HistogramGranularity> histogramGranularities,
@@ -585,10 +594,12 @@ public void sendDistribution(String name, List<Pair<Double, Integer>> centroids,
     }
   }
 
-  /** {@inheritDoc} */
+  /**
+   * {@inheritDoc}
+   */
   @Override
   public void sendLog(String name, double value, Long timestamp, String source,
-                      Map<String, String> tags) throws IOException {
+      Map<String, String> tags) throws IOException {
     if (closed.get()) {
       throw new IOException("attempt to send using closed sender");
     }
@@ -611,7 +622,9 @@ public void sendLog(String name, double value, Long timestamp, String source,
     }
   }
 
-  /** {@inheritDoc} */
+  /**
+   * {@inheritDoc}
+   */
   @Override
   public void sendEvent(String name, long startMillis, long endMillis, @Nullable String source,
                         @Nullable Map<String, String> tags,
@@ -645,7 +658,9 @@ public void sendEvent(String name, long startMillis, long endMillis, @Nullable S
     }
   }
 
-  /** {@inheritDoc} */
+  /**
+   * {@inheritDoc}
+   */
   @Override
   public void sendSpan(String name, long startMillis, long durationMillis,
                        @Nullable String source, UUID traceId, UUID spanId,
@@ -672,7 +687,7 @@ public void sendSpan(String name, long startMillis, long durationMillis,
         String spanSecondaryId = null;
         if (tags != null) {
           spanSecondaryId = tags.stream().filter(pair -> pair._1.equals(SPAN_SECONDARY_ID_KEY))
-                  .map(pair -> pair._2).findFirst().orElse(null);
+              .map(pair -> pair._2).findFirst().orElse(null);
         }
         sendSpanLogs(traceId, spanId, spanLogs, span, spanSecondaryId);
       }
@@ -708,7 +723,9 @@ private void sendSpanLogs(
     }
   }
 
-  /** {@inheritDoc} */
+  /**
+   * {@inheritDoc}
+   */
   @Override
   public void run() {
     try {
@@ -719,7 +736,9 @@ public void run() {
     }
   }
 
-  /** {@inheritDoc} */
+  /**
+   * {@inheritDoc}
+   */
   @Override
   public void flush() throws IOException {
     if (closed.get()) {
@@ -893,14 +912,18 @@ private InputStream itemsToStream(List<String> items) {
     return new ByteArrayInputStream(sb.toString().getBytes(StandardCharsets.UTF_8));
   }
 
-  /** {@inheritDoc} */
+  /**
+   * {@inheritDoc}
+   */
   @Override
   public int getFailureCount() {
     return (int) (pointReportErrors.count() + histogramReportErrors.count() +
         spanReportErrors.count() + eventsReportErrors.count());
   }
 
-  /** {@inheritDoc} */
+  /**
+   * {@inheritDoc}
+   */
   @Override
   public synchronized void close() {
     if (!closed.compareAndSet(false, true)) {
@@ -928,13 +951,13 @@ public synchronized void close() {
   /**
    * Dequeue and return a batch of at most N items from buffer (where N = batchSize), broken into
    * chunks where each chunk has at most M bytes of data (where M = messageSizeBytes).
-   *
+   * <p>
    * Visible for testing.
    *
-   * @param buffer            The buffer queue to retrieve items from.
-   * @param batchSize         The maximum number of items to retrieve from the buffer.
-   * @param messageSizeBytes  The maximum number of bytes in each chunk.
-   * @param dropped           A counter counting the number of items that are dropped.
+   * @param buffer           The buffer queue to retrieve items from.
+   * @param batchSize        The maximum number of items to retrieve from the buffer.
+   * @param messageSizeBytes The maximum number of bytes in each chunk.
+   * @param dropped          A counter counting the number of items that are dropped.
    * @return A batch of items retrieved from buffer.
    */
   static List<List<String>> getBatch(LinkedBlockingQueue<String> buffer, int batchSize,
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/ReportingService.java b/src/main/java/com/wavefront/sdk/common/clients/service/ReportingService.java
index 345b16e4..0eb3868c 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/ReportingService.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/ReportingService.java
@@ -1,6 +1,7 @@
 package com.wavefront.sdk.common.clients.service;
 
 import com.google.common.annotations.VisibleForTesting;
+
 import com.wavefront.sdk.common.Constants;
 import com.wavefront.sdk.common.Utils;
 import com.wavefront.sdk.common.clients.service.token.TokenService;
@@ -41,8 +42,8 @@ public class ReportingService implements ReportAPI {
   /**
    * <p>Constructor for ReportingService.</p>
    *
-   * @param uri a {@link java.net.URI} object
-   * @param tokenService a {@link TokenService} object
+   * @param uri                                    a {@link java.net.URI} object
+   * @param tokenService                           a {@link TokenService} object
    * @param reportingServiceLogSuppressTimeSeconds a long
    */
   public ReportingService(URI uri, TokenService tokenService, long reportingServiceLogSuppressTimeSeconds) {
@@ -56,7 +57,9 @@ public ReportingService(URI uri, TokenService tokenService, long reportingServic
             ReportingService.class.getCanonicalName()), reportingServiceLogSuppressTimeSeconds, TimeUnit.SECONDS);
   }
 
-  /** {@inheritDoc} */
+  /**
+   * {@inheritDoc}
+   */
   @Override
   public int send(String format, InputStream stream) {
     HttpURLConnection urlConn = null;
@@ -96,7 +99,9 @@ public int send(String format, InputStream stream) {
     return statusCode;
   }
 
-  /** {@inheritDoc} */
+  /**
+   * {@inheritDoc}
+   */
   @Override
   public int sendEvent(InputStream stream) {
     HttpURLConnection urlConn = null;
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPAuthorizeResponse.java b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPAuthorizeResponse.java
index b10e0e20..adbe4580 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPAuthorizeResponse.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPAuthorizeResponse.java
@@ -1,6 +1,7 @@
 package com.wavefront.sdk.common.clients.service.token;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
+
 public class CSPAuthorizeResponse {
   @JsonProperty("id_token")
   public String idToken;
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
index b2f5415a..f745274e 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
@@ -70,8 +70,8 @@ public synchronized String getToken() {
   private String getCSPToken() {
     HttpURLConnection urlConn = null;
 
-    final String urlParameters  = "grant_type=client_credentials";
-    final byte[] postData = urlParameters.getBytes( StandardCharsets.UTF_8 );
+    final String urlParameters = "grant_type=client_credentials";
+    final byte[] postData = urlParameters.getBytes(StandardCharsets.UTF_8);
 
     try {
       final URL url = new URL(cspBaseURL + OAUTH_PATH);
@@ -86,7 +86,7 @@ private String getCSPToken() {
       urlConn.setReadTimeout(readTimeoutMillis);
 
       //Send request
-      final DataOutputStream wr = new DataOutputStream (urlConn.getOutputStream());
+      final DataOutputStream wr = new DataOutputStream(urlConn.getOutputStream());
       wr.write(postData);
       wr.flush();
       wr.close();
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/token/TokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/token/TokenService.java
index 944e1fd7..89bd97e9 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/token/TokenService.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/token/TokenService.java
@@ -1,7 +1,5 @@
 package com.wavefront.sdk.common.clients.service.token;
 
 public interface TokenService {
-
   String getToken();
-
 }
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/token/WavefrontTokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/token/WavefrontTokenService.java
index 33d1aa31..0a8053df 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/token/WavefrontTokenService.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/token/WavefrontTokenService.java
@@ -1,7 +1,6 @@
 package com.wavefront.sdk.common.clients.service.token;
 
 public class WavefrontTokenService implements TokenService {
-
   private final String token;
 
   public WavefrontTokenService(final String token) {
diff --git a/src/test/java/com/wavefront/sdk/common/UtilsTest.java b/src/test/java/com/wavefront/sdk/common/UtilsTest.java
index 289dc348..0a6e8e04 100644
--- a/src/test/java/com/wavefront/sdk/common/UtilsTest.java
+++ b/src/test/java/com/wavefront/sdk/common/UtilsTest.java
@@ -854,7 +854,9 @@ public void convertSemVerToGauge() throws IOException {
   @Test
   public void testIsNullOrEmpty() {
     assertTrue(Utils.isNullOrEmpty(null));
+
     assertTrue(Utils.isNullOrEmpty(""));
+
     assertFalse(Utils.isNullOrEmpty("hello"));
   }
 
diff --git a/src/test/java/com/wavefront/sdk/common/clients/WavefrontClientTest.java b/src/test/java/com/wavefront/sdk/common/clients/WavefrontClientTest.java
index 1d545de9..0a4bb89a 100644
--- a/src/test/java/com/wavefront/sdk/common/clients/WavefrontClientTest.java
+++ b/src/test/java/com/wavefront/sdk/common/clients/WavefrontClientTest.java
@@ -8,8 +8,8 @@
 import com.wavefront.sdk.common.clients.service.token.WavefrontTokenService;
 import com.wavefront.sdk.common.metrics.WavefrontSdkDeltaCounter;
 import com.wavefront.sdk.entities.histograms.HistogramGranularity;
-
 import com.wavefront.sdk.entities.tracing.SpanLog;
+
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
@@ -19,13 +19,29 @@
 import org.junit.jupiter.params.provider.ValueSource;
 
 import java.net.ServerSocket;
-import java.util.*;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.UUID;
 import java.util.concurrent.LinkedBlockingQueue;
 
-import static com.github.tomakehurst.wiremock.client.WireMock.*;
+import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;
+import static com.github.tomakehurst.wiremock.client.WireMock.matching;
+import static com.github.tomakehurst.wiremock.client.WireMock.matchingJsonPath;
+import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;
 import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
-import static org.easymock.EasyMock.*;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 /**
  * Tests for the {@link WavefrontClient} class
diff --git a/src/test/java/com/wavefront/sdk/common/clients/service/CSPServerToServerTokenServiceTest.java b/src/test/java/com/wavefront/sdk/common/clients/service/CSPServerToServerTokenServiceTest.java
index 47cc2a77..523c12aa 100644
--- a/src/test/java/com/wavefront/sdk/common/clients/service/CSPServerToServerTokenServiceTest.java
+++ b/src/test/java/com/wavefront/sdk/common/clients/service/CSPServerToServerTokenServiceTest.java
@@ -3,6 +3,7 @@
 import com.github.tomakehurst.wiremock.WireMockServer;
 import com.github.tomakehurst.wiremock.client.WireMock;
 import com.wavefront.sdk.common.clients.service.token.CSPServerToServerTokenService;
+
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
@@ -13,7 +14,11 @@
 import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;
 import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
 import static com.wavefront.sdk.common.clients.service.token.CSPServerToServerTokenService.hasDirectIngestScope;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class CSPServerToServerTokenServiceTest {
 

From f91b5312391ebec9f8336bc0d8ee9f1649292646 Mon Sep 17 00:00:00 2001
From: Glenn Oppegard <goppegard@vmware.com>
Date: Thu, 10 Aug 2023 16:10:29 -0600
Subject: [PATCH 09/16] GH Actions Housekeeping

---
 .github/dependabot.yml      | 1 +
 .github/workflows/maven.yml | 4 ++--
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 089f7736..7429f0b4 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -6,3 +6,4 @@ updates:
     directory: "/"
     schedule:
       interval: "daily"
+    open-pull-requests-limit: 10
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 38815042..5ea444d1 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -16,12 +16,12 @@ jobs:
         java: ["8", "11", "17"]
 
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
     - name: Set up JDK
       uses: actions/setup-java@v2
       with:
         java-version: ${{ matrix.java }}
-        distribution: 'temurin'
+        distribution: temurin
         cache: maven
     - name: Build with Maven
       run: mvn --batch-mode --file pom.xml package

From 2de1be08abb8fd247acdc94118f145eaa6943b2c Mon Sep 17 00:00:00 2001
From: Ernst Riemer <eriemer@vmware.com>
Date: Thu, 10 Aug 2023 17:17:11 -0600
Subject: [PATCH 10/16] Make sure thread delay is never zero or negative

---
 .../service/token/CSPServerToServerTokenService.java | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
index f745274e..7c1f83db 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
@@ -136,11 +136,19 @@ private String getCSPToken() {
   }
 
   private int getThreadDelay(final int expiresIn) {
+    int retVal;
+
     if (expiresIn < TEN_MINUTES) {
-      return expiresIn - THIRTY_SECONDS;
+      retVal = expiresIn - THIRTY_SECONDS;
+    } else {
+      retVal = expiresIn - THREE_MINUTES;
+    }
+
+    if (retVal <= 0) {
+      retVal = 60;
     }
 
-    return expiresIn - THREE_MINUTES;
+    return retVal;
   }
 
   public synchronized void run() {

From 66d03e7c0bd50aa4dba6862b104dd8f6f44ad305 Mon Sep 17 00:00:00 2001
From: Ernst Riemer <eriemer@vmware.com>
Date: Mon, 14 Aug 2023 16:36:39 -0600
Subject: [PATCH 11/16] Fix switch statement and add additional logging

---
 .../com/wavefront/sdk/common/clients/WavefrontClient.java    | 5 ++++-
 .../clients/service/token/CSPServerToServerTokenService.java | 4 ++++
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java b/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java
index 46d4fd48..51b0f495 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java
@@ -187,7 +187,7 @@ public Builder(String server, @Nullable String token) {
      * @param cspClientId     Client ID for CSP
      * @param cspClientSecret Client Secret for CSP
      */
-    public Builder(String server, @Nullable String token, @Nullable String cspBaseUrl, @Nullable String cspClientId, @Nullable String cspClientSecret) {
+    private Builder(String server, @Nullable String token, @Nullable String cspBaseUrl, @Nullable String cspClientId, @Nullable String cspClientSecret) {
       this.server = server;
       this.token = token;
       this.cspBaseUrl = cspBaseUrl;
@@ -411,10 +411,13 @@ private WavefrontClient(Builder builder) {
     switch (tokenService.getClass().getSimpleName()) {
       case "CSPServerToServerTokenService":
         logger.log(Level.INFO, "The Wavefront SDK will use CSP authentication when communicating with the Wavefront Backend for Direct Ingestion.");
+        break;
       case "WavefrontTokenService":
         logger.log(Level.INFO, "The Wavefront SDK will use an API TOKEN when communicating with the Wavefront Backend for Direct Ingestion.");
+        break;
       case "NoopTokenService":
         logger.log(Level.INFO, "The Wavefront SDK will communicate with a Wavefront Proxy.");
+      break;
     }
 
     batchSize = builder.batchSize;
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
index 7c1f83db..630baa89 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
@@ -70,6 +70,8 @@ public synchronized String getToken() {
   private String getCSPToken() {
     HttpURLConnection urlConn = null;
 
+    log.info("Attempting to get a CSP token");
+
     final String urlParameters = "grant_type=client_credentials";
     final byte[] postData = urlParameters.getBytes(StandardCharsets.UTF_8);
 
@@ -97,6 +99,8 @@ private String getCSPToken() {
         try {
           final CSPAuthorizeResponse parsedResponse = mapper.readValue(urlConn.getInputStream(), CSPAuthorizeResponse.class);
 
+          log.info("A CSP token has been received.");
+
           if (!hasDirectIngestScope(parsedResponse.scope)) {
             log.warning("The CSP response did not find any scope matching 'aoa:directDataIngestion' which is required for Wavefront direct ingestion.");
           }

From eacdc7be2320137cda95b2c1715810588de1c7e3 Mon Sep 17 00:00:00 2001
From: Ernst Riemer <eriemer@vmware.com>
Date: Tue, 15 Aug 2023 12:13:09 -0600
Subject: [PATCH 12/16] Updating logging of direct ingestion scope

---
 .../clients/service/token/CSPServerToServerTokenService.java    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
index 630baa89..dce4f41f 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
@@ -165,7 +165,7 @@ private static List<String> parseScopes(final String scope) {
 
   public static boolean hasDirectIngestScope(final String scopeList) {
     if (!Utils.isNullOrEmpty(scopeList)) {
-      return parseScopes(scopeList).stream().anyMatch(s -> s.contains("aoa:directDataIngestion"));
+      return parseScopes(scopeList).stream().anyMatch(s -> s.contains("aoa:directDataIngestion") || s.contains("aoa/*") || s.contains("aoa:*"));
     }
 
     return false;

From d4f88ee6fa9d1bf27aad90ee49d31c60d39f4b65 Mon Sep 17 00:00:00 2001
From: Ernst Riemer <eriemer@vmware.com>
Date: Tue, 15 Aug 2023 15:04:34 -0600
Subject: [PATCH 13/16] Adding test to test getting multiple csp tokens with
 background thread

---
 .../token/CSPServerToServerTokenService.java  | 11 ++++-
 .../CSPServerToServerTokenServiceTest.java    | 40 ++++++++++++++++++-
 2 files changed, 47 insertions(+), 4 deletions(-)

diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
index dce4f41f..eff20148 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
@@ -27,6 +27,7 @@ public class CSPServerToServerTokenService implements TokenService, Runnable {
   private final static int TEN_MINUTES = 600;
   private final static int THIRTY_SECONDS = 30;
   private final static int THREE_MINUTES = 180;
+  private static int DEFAULT_THREAD_DELAY = 60;
 
   private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("csp-server-to-server-token-service"));
   private final ObjectMapper mapper = new ObjectMapper();
@@ -105,8 +106,14 @@ private String getCSPToken() {
             log.warning("The CSP response did not find any scope matching 'aoa:directDataIngestion' which is required for Wavefront direct ingestion.");
           }
 
+          log.info("A CSP token has been received.");
+
           // Schedule token refresh in the future
-          executor.schedule(this, getThreadDelay(parsedResponse.expiresIn), TimeUnit.SECONDS);
+          int threadDelay = getThreadDelay(parsedResponse.expiresIn);
+
+          log.info("Will schedule the CSP token to be refreshed in: " + threadDelay + " seconds");
+
+          executor.schedule(this, threadDelay, TimeUnit.SECONDS);
 
           return parsedResponse.accessToken;
         } catch (JsonProcessingException e) {
@@ -149,7 +156,7 @@ private int getThreadDelay(final int expiresIn) {
     }
 
     if (retVal <= 0) {
-      retVal = 60;
+      retVal = DEFAULT_THREAD_DELAY;
     }
 
     return retVal;
diff --git a/src/test/java/com/wavefront/sdk/common/clients/service/CSPServerToServerTokenServiceTest.java b/src/test/java/com/wavefront/sdk/common/clients/service/CSPServerToServerTokenServiceTest.java
index 523c12aa..76d162f0 100644
--- a/src/test/java/com/wavefront/sdk/common/clients/service/CSPServerToServerTokenServiceTest.java
+++ b/src/test/java/com/wavefront/sdk/common/clients/service/CSPServerToServerTokenServiceTest.java
@@ -2,6 +2,7 @@
 
 import com.github.tomakehurst.wiremock.WireMockServer;
 import com.github.tomakehurst.wiremock.client.WireMock;
+import com.github.tomakehurst.wiremock.stubbing.Scenario;
 import com.wavefront.sdk.common.clients.service.token.CSPServerToServerTokenService;
 
 import org.junit.jupiter.api.AfterEach;
@@ -9,9 +10,10 @@
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
+import java.lang.reflect.Field;
 import java.util.UUID;
 
-import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;
+import static com.github.tomakehurst.wiremock.client.WireMock.*;
 import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
 import static com.wavefront.sdk.common.clients.service.token.CSPServerToServerTokenService.hasDirectIngestScope;
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -32,13 +34,18 @@ public void testHasDirectIngestScope() {
     assertFalse(hasDirectIngestScope("no direct data ingestion scope"));
     assertFalse(hasDirectIngestScope(""));
     assertFalse(hasDirectIngestScope(null));
+    assertTrue(hasDirectIngestScope("aoa/*"));
+    assertTrue(hasDirectIngestScope("some aoa/*"));
+    assertTrue(hasDirectIngestScope("aoa:*"));
+    assertTrue(hasDirectIngestScope("some aoa:*"));
   }
 
   @Nested
   class WireMockTests {
     WireMockServer mockBackend;
 
-    private final String MOCK_RESPONSE = "{\"scope\":\"scope\",\"id_token\":null,\"token_type\":\"bearer\",\"expires_in\":1799,\"access_token\":\"accessToken\",\"refresh_token\":null}\n";
+    private final String MOCK_RESPONSE = "{\"scope\":\"scope aoa/*\",\"id_token\":null,\"token_type\":\"bearer\",\"expires_in\":1,\"access_token\":\"accessToken\",\"refresh_token\":null}\n";
+    private final String MOCK_RESPONSE2 = "{\"scope\":\"scope aoa/*\",\"id_token\":null,\"token_type\":\"bearer\",\"expires_in\":1,\"access_token\":\"accessToken2\",\"refresh_token\":null}\n";
 
     @BeforeEach
     void setup() {
@@ -60,6 +67,24 @@ void testCSPReturnsAccessToken() {
       assertEquals(cspServerToServerTokenService.getToken(), "accessToken");
     }
 
+    @Test
+    void testCSPMultipleAccessTokens() throws InterruptedException, NoSuchFieldException, IllegalAccessException {
+      createWireMockStubWithStates("/csp/gateway/am/api/auth/authorize", Scenario.STARTED, "second", MOCK_RESPONSE);
+      createWireMockStubWithStates("/csp/gateway/am/api/auth/authorize", "second", "third", MOCK_RESPONSE2);
+      mockBackend.start();
+
+      CSPServerToServerTokenService cspServerToServerTokenService = new CSPServerToServerTokenService(mockBackend.baseUrl(), "N/A", "N/A");
+
+      Field field = CSPServerToServerTokenService.class.getDeclaredField("DEFAULT_THREAD_DELAY");
+      field.setAccessible(true);
+      field.set(cspServerToServerTokenService, 1);
+
+      assertNotNull(cspServerToServerTokenService);
+      assertEquals(cspServerToServerTokenService.getToken(), "accessToken");
+      Thread.sleep(2000);
+      assertEquals(cspServerToServerTokenService.getToken(), "accessToken2");
+    }
+
     @Test
     void testCSPReturns401() {
       mockBackend.stubFor(WireMock.post(urlPathMatching("/csp/gateway/am/api/auth/authorize")).willReturn(WireMock.unauthorized()));
@@ -87,5 +112,16 @@ void testCSPConnectionError() {
       CSPServerToServerTokenService cspServerToServerTokenService = new CSPServerToServerTokenService(mockBackend.baseUrl(), "N/A", "N/A", 100, 100);
       assertNull(cspServerToServerTokenService.getToken());
     }
+
+    private void createWireMockStubWithStates(final String url, final String currentState, final String nextState, final String responseBody) {
+      mockBackend.stubFor(post(urlEqualTo(url))
+              .inScenario("csp")
+              .whenScenarioStateIs(currentState)
+              .willSetStateTo(nextState)
+              .willReturn(aResponse()
+                      .withStatus(200)
+                      .withBody(responseBody)));
+    }
+
   }
 }
\ No newline at end of file

From 335391f2fc813ee197cca7832f382f2818810f18 Mon Sep 17 00:00:00 2001
From: Ernst Riemer <eriemer@vmware.com>
Date: Tue, 15 Aug 2023 16:25:07 -0600
Subject: [PATCH 14/16] WIP - adding option to override cspBaseUrl and removing
 the constructor that has it as an argument

---
 .../sdk/common/clients/WavefrontClient.java        | 14 ++++++++++----
 .../token/CSPServerToServerTokenService.java       |  2 --
 2 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java b/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java
index 51b0f495..d12993ef 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java
@@ -144,13 +144,15 @@ public class WavefrontClient implements WavefrontSender, Runnable {
   private final TokenService tokenService;
 
   public static class Builder {
+    private static final String CSP_DEFAULT_BASE_URL = "https://console.cloud.vmware.com/";
     // Required parameters
     private final String server;
     private final String token;
-    private final String cspBaseUrl;
     private final String cspClientId;
     private final String cspClientSecret;
 
+    private String cspBaseUrl;
+
     // Optional parameters
     private int metricsPort = -1;
     private int tracesPort = -1;
@@ -200,12 +202,11 @@ private Builder(String server, @Nullable String token, @Nullable String cspBaseU
      *
      * @param server          A server URL of the form "https://clusterName.wavefront.com" or
      *                        "http://internal.proxy.com:port"
-     * @param cspBaseUrl      A server URL that points to the CSP service
      * @param cspClientId     Client ID for CSP
      * @param cspClientSecret Client Secret for CSP
      */
-    public Builder(String server, @Nullable String cspBaseUrl, @Nullable String cspClientId, @Nullable String cspClientSecret) {
-      this(server, null, cspBaseUrl, cspClientId, cspClientSecret);
+    public Builder(String server, @Nullable String cspClientId, @Nullable String cspClientSecret) {
+      this(server, null, CSP_DEFAULT_BASE_URL, cspClientId, cspClientSecret);
     }
 
     /**
@@ -228,6 +229,11 @@ public Builder maxQueueSize(int maxQueueSize) {
       return this;
     }
 
+    public Builder cspBaseUrl(final String cspBaseUrl) {
+      this.cspBaseUrl = cspBaseUrl;
+      return this;
+    }
+
     /**
      * Set batch size to be reported during every flush.
      *
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
index eff20148..4a652faf 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
@@ -106,8 +106,6 @@ private String getCSPToken() {
             log.warning("The CSP response did not find any scope matching 'aoa:directDataIngestion' which is required for Wavefront direct ingestion.");
           }
 
-          log.info("A CSP token has been received.");
-
           // Schedule token refresh in the future
           int threadDelay = getThreadDelay(parsedResponse.expiresIn);
 

From b58d13bdbdf16930e4f6f9ac5291ec8491447a15 Mon Sep 17 00:00:00 2001
From: Devon Warshaw <warshawd@vmware.com>
Date: Wed, 16 Aug 2023 12:03:39 -0500
Subject: [PATCH 15/16] Fixed stale constructor in test

Co-authored-by: Glenn Oppegard <goppegard@vmware.com>
---
 .../com/wavefront/sdk/common/clients/WavefrontClientTest.java   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/test/java/com/wavefront/sdk/common/clients/WavefrontClientTest.java b/src/test/java/com/wavefront/sdk/common/clients/WavefrontClientTest.java
index 0a4bb89a..261f1ab8 100644
--- a/src/test/java/com/wavefront/sdk/common/clients/WavefrontClientTest.java
+++ b/src/test/java/com/wavefront/sdk/common/clients/WavefrontClientTest.java
@@ -245,7 +245,7 @@ public void tokenServiceClassTest() {
       assertNotNull(wfClient.getTokenService());
       assertEquals(wfClient.getTokenService().getClass().getSimpleName(), NoopTokenService.class.getSimpleName());
 
-      wfClient = new WavefrontClient.Builder("", "cspBaseUrl", "cspClientId", "cspClientSecret")
+      wfClient = new WavefrontClient.Builder("", "cspClientId", "cspClientSecret")
               .build();
       assertNotNull(wfClient);
       assertNotNull(wfClient.getTokenService());

From 66ecea26a6f92e78e3027ffe15451a20b08c211f Mon Sep 17 00:00:00 2001
From: Glenn Oppegard <goppegard@vmware.com>
Date: Wed, 16 Aug 2023 12:48:35 -0500
Subject: [PATCH 16/16] Cleaned up some log statements

Co-authored-by: Devon Warshaw <warshawd@vmware.com>
---
 .../wavefront/sdk/common/clients/WavefrontClient.java  |  1 -
 .../service/token/CSPServerToServerTokenService.java   | 10 +++-------
 2 files changed, 3 insertions(+), 8 deletions(-)

diff --git a/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java b/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java
index d12993ef..f8433eb6 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/WavefrontClient.java
@@ -410,7 +410,6 @@ private WavefrontClient(Builder builder) {
     } else if (!Utils.isNullOrEmpty(builder.cspBaseUrl) && !Utils.isNullOrEmpty(builder.cspClientId) && !Utils.isNullOrEmpty(builder.cspClientSecret)) {
       tokenService = new CSPServerToServerTokenService(builder.cspBaseUrl, builder.cspClientId, builder.cspClientSecret);
     } else {
-      // NOOP LOGS
       tokenService = new NoopTokenService();
     }
 
diff --git a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
index 4a652faf..40d039da 100644
--- a/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
+++ b/src/main/java/com/wavefront/sdk/common/clients/service/token/CSPServerToServerTokenService.java
@@ -71,8 +71,6 @@ public synchronized String getToken() {
   private String getCSPToken() {
     HttpURLConnection urlConn = null;
 
-    log.info("Attempting to get a CSP token");
-
     final String urlParameters = "grant_type=client_credentials";
     final byte[] postData = urlParameters.getBytes(StandardCharsets.UTF_8);
 
@@ -100,8 +98,6 @@ private String getCSPToken() {
         try {
           final CSPAuthorizeResponse parsedResponse = mapper.readValue(urlConn.getInputStream(), CSPAuthorizeResponse.class);
 
-          log.info("A CSP token has been received.");
-
           if (!hasDirectIngestScope(parsedResponse.scope)) {
             log.warning("The CSP response did not find any scope matching 'aoa:directDataIngestion' which is required for Wavefront direct ingestion.");
           }
@@ -109,18 +105,18 @@ private String getCSPToken() {
           // Schedule token refresh in the future
           int threadDelay = getThreadDelay(parsedResponse.expiresIn);
 
-          log.info("Will schedule the CSP token to be refreshed in: " + threadDelay + " seconds");
+          log.info("A CSP token has been received. Will schedule the CSP token to be refreshed in: " + threadDelay + " seconds");
 
           executor.schedule(this, threadDelay, TimeUnit.SECONDS);
 
           return parsedResponse.accessToken;
         } catch (JsonProcessingException e) {
-          log.info("The request to CSP returned invalid json. Please restart your app.");
+          log.severe("The request to CSP returned invalid json. Please restart your app.");
 
           return "INVALID_TOKEN";
         }
       } else {
-        log.info("The request to CSP returned: " + statusCode);
+        log.warning("The request to CSP returned: " + statusCode);
 
         if (statusCode >= 500 && statusCode < 600) {
           log.info("The Wavefront SDK will try to reauthenticate with CSP on the next request.");
