Repository: Unleash/unleash-client-java
Pull request #104 (2020-03-13 21:50:13)

fix: add a custom http headers provider for unleash config class
This will make it possible to acquire a new or refresh auth tokens when talking to the server.  This also addresses the following issue.  https://github.com/Unleash/unleash-client-java/issues/94

coveralls: 
[![Coverage Status](https://coveralls.io/builds/29340497/badge)](https://coveralls.io/builds/29340497)

Coverage decreased (-0.04%) to 92.562% when pulling **1bab6f487c5cd7402b530151be602b0306011074 on ajvasey:customHttpHeadersProvider** into **1e3f1dd7577c167c7106fe0b3de48516364aa733 on Unleash:master**.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2d4178f7..b4bc4563 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
 # Changelog
 
+## 3.3.1
+- fix: add shutdown method to enable graceful termination of Unleash
+
 ## 3.3.0
 - feat: Add support for fallback-action
 - fix: NullPointerException when no variants defined 
diff --git a/README.md b/README.md
index 59a214cc..c5c082cb 100644
--- a/README.md
+++ b/README.md
@@ -139,6 +139,29 @@ UnleashConfig unleashConfig = UnleashConfig.builder()
                 .build();
 ```
 
+###Dynamic custom HTTP headers
+If you need custom http headers that change during the lifetime of the client, a provider can be defined via the `UnleashConfig`.
+```java
+public class CustomHttpHeadersProviderImpl implements CustomHttpHeadersProvider {
+    @Override
+    public Map<String, String> getCustomHeaders() {
+        String token = "Acquire or refresh token";
+        return new HashMap() {{ put("Authorization", "Bearer "+token); }};
+    }
+}
+```
+```java
+CustomHttpHeadersProvider provider = new CustomHttpHeadersProviderImpl();
+
+UnleashConfig unleashConfig = UnleashConfig.builder()
+                .appName("my-app")
+                .instanceId("my-instance-1")
+                .unleashAPI(unleashAPI)
+                .customHttpHeadersProvider(provider)
+                .build();
+```
+
+
 ### Subscriber API
 *(Introduced in 3.2.2)*
 
diff --git a/pom.xml b/pom.xml
index e01c8c48..f0b5df87 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@
 
     <groupId>no.finn.unleash</groupId>
     <artifactId>unleash-client-java</artifactId>
-    <version>3.3.1-SNAPSHOT</version>
+    <version>3.3.2-SNAPSHOT</version>
 
     <properties>
         <version.log4j2>2.11.2</version.log4j2>
diff --git a/src/main/java/no/finn/unleash/CustomHttpHeadersProvider.java b/src/main/java/no/finn/unleash/CustomHttpHeadersProvider.java
new file mode 100644
index 00000000..bcc44872
--- /dev/null
+++ b/src/main/java/no/finn/unleash/CustomHttpHeadersProvider.java
@@ -0,0 +1,7 @@
+package no.finn.unleash;
+
+import java.util.Map;
+
+public interface CustomHttpHeadersProvider {
+    Map<String, String> getCustomHeaders();
+}
diff --git a/src/main/java/no/finn/unleash/DefaultCustomHttpHeadersProviderImpl.java b/src/main/java/no/finn/unleash/DefaultCustomHttpHeadersProviderImpl.java
new file mode 100644
index 00000000..419239cc
--- /dev/null
+++ b/src/main/java/no/finn/unleash/DefaultCustomHttpHeadersProviderImpl.java
@@ -0,0 +1,11 @@
+package no.finn.unleash;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DefaultCustomHttpHeadersProviderImpl implements CustomHttpHeadersProvider {
+    @Override
+    public Map<String, String> getCustomHeaders() {
+        return new HashMap<>();
+    }
+}
diff --git a/src/main/java/no/finn/unleash/util/UnleashConfig.java b/src/main/java/no/finn/unleash/util/UnleashConfig.java
index 79d4aa53..c343f785 100644
--- a/src/main/java/no/finn/unleash/util/UnleashConfig.java
+++ b/src/main/java/no/finn/unleash/util/UnleashConfig.java
@@ -1,5 +1,7 @@
 package no.finn.unleash.util;
 
+import no.finn.unleash.CustomHttpHeadersProvider;
+import no.finn.unleash.DefaultCustomHttpHeadersProviderImpl;
 import no.finn.unleash.UnleashContextProvider;
 import no.finn.unleash.event.NoOpSubscriber;
 import no.finn.unleash.event.UnleashSubscriber;
@@ -18,6 +20,7 @@ public class UnleashConfig {
     private final URI unleashAPI;
     private final UnleashURLs unleashURLs;
     private final Map<String, String> customHttpHeaders;
+    private final CustomHttpHeadersProvider customHttpHeadersProvider;
     private final String appName;
     private final String environment;
     private final String instanceId;
@@ -35,6 +38,7 @@ public class UnleashConfig {
     public UnleashConfig(
             URI unleashAPI,
             Map<String, String> customHttpHeaders,
+            CustomHttpHeadersProvider customHttpHeadersProvider,
             String appName,
             String environment,
             String instanceId,
@@ -76,6 +80,7 @@ public class UnleashConfig {
 
         this.unleashAPI = unleashAPI;
         this.customHttpHeaders = customHttpHeaders;
+        this.customHttpHeadersProvider = customHttpHeadersProvider;
         this.unleashURLs = new UnleashURLs(unleashAPI);
         this.appName = appName;
         this.environment = environment;
@@ -101,6 +106,7 @@ public class UnleashConfig {
         connection.setRequestProperty(UNLEASH_INSTANCE_ID_HEADER, config.getInstanceId());
         connection.setRequestProperty("User-Agent", config.getAppName());
         config.getCustomHttpHeaders().forEach(connection::setRequestProperty);
+        config.customHttpHeadersProvider.getCustomHeaders().forEach(connection::setRequestProperty);
     }
 
     private void enableProxyAuthentication() {
@@ -116,6 +122,8 @@ public class UnleashConfig {
         return customHttpHeaders;
     }
 
+    public CustomHttpHeadersProvider getCustomHttpHeadersProvider() { return customHttpHeadersProvider; }
+
     public String getAppName() {
         return appName;
     }
@@ -196,6 +204,7 @@ public class UnleashConfig {
 
         private URI unleashAPI;
         private Map<String, String> customHttpHeaders = new HashMap<>();
+        private CustomHttpHeadersProvider customHttpHeadersProvider = new DefaultCustomHttpHeadersProviderImpl();
         private String appName;
         private String environment = "default";
         private String instanceId = getDefaultInstanceId();
@@ -235,6 +244,11 @@ public class UnleashConfig {
             return this;
         }
 
+        public Builder customHttpHeadersProvider(CustomHttpHeadersProvider provider) {
+            this.customHttpHeadersProvider = provider;
+            return this;
+        }
+
         public Builder appName(String appName) {
             this.appName = appName;
             return this;
@@ -308,6 +322,7 @@ public class UnleashConfig {
             return new UnleashConfig(
                     unleashAPI,
                     customHttpHeaders,
+                    customHttpHeadersProvider,
                     appName,
                     environment,
                     instanceId,


Test patch:
diff --git a/src/test/java/no/finn/unleash/util/UnleashConfigTest.java b/src/test/java/no/finn/unleash/util/UnleashConfigTest.java
index ec0a3360..a46aedaa 100644
--- a/src/test/java/no/finn/unleash/util/UnleashConfigTest.java
+++ b/src/test/java/no/finn/unleash/util/UnleashConfigTest.java
@@ -1,5 +1,7 @@
 package no.finn.unleash.util;
 
+import no.finn.unleash.CustomHttpHeadersProvider;
+import no.finn.unleash.DefaultCustomHttpHeadersProviderImpl;
 import no.finn.unleash.util.UnleashConfig.ProxyAuthenticator;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.function.Executable;
@@ -9,12 +11,18 @@ import java.io.IOException;
 import java.lang.reflect.Field;
 import java.net.*;
 import java.net.Authenticator.RequestorType;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.mockito.Mockito;
 
 import static no.finn.unleash.util.UnleashConfig.UNLEASH_APP_NAME_HEADER;
 import static no.finn.unleash.util.UnleashConfig.UNLEASH_INSTANCE_ID_HEADER;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.Is.is;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.when;
 
 public class UnleashConfigTest {
 
@@ -145,6 +153,31 @@ public class UnleashConfigTest {
         assertThat(connection.getRequestProperty(headerName), is(headerValue));
     }
 
+    @Test
+    public void should_add_custom_headers_from_provider_to_connection_if_present() throws IOException {
+        String unleashAPI = "http://unleash.org";
+        Map<String,String> result = new HashMap() {{ put("PROVIDER-HEADER","Provider Value"); }};
+
+        CustomHttpHeadersProvider provider = Mockito.mock(DefaultCustomHttpHeadersProviderImpl.class);
+        when(provider.getCustomHeaders()).thenReturn(result);
+
+        UnleashConfig unleashConfig = UnleashConfig.builder()
+                .appName("my-app")
+                .instanceId("my-instance-1")
+                .unleashAPI(unleashAPI)
+                .customHttpHeadersProvider(provider)
+                .build();
+
+        URL someUrl = new URL(unleashAPI + "/some/arbitrary/path");
+        HttpURLConnection connection = (HttpURLConnection) someUrl.openConnection();
+
+        UnleashConfig.setRequestProperties(connection, unleashConfig);
+
+        for (String key: result.keySet()) {
+            assertThat(connection.getRequestProperty(key), is(result.get(key)));
+        }
+    }
+
     @Test
     public void should_require_instanceId() {
         Executable ex = () -> UnleashConfig.builder()