Repository: Unleash/unleash-client-java
Pull request #127 (2021-03-08 13:43:49)

feat: Add support for fallback strategy
That allows registering a fallback strategy which will support a broader idea of a 'context matching strategy'. I'll create a separate PR for that strategy to keep the PRs focused on each change.

checketts: OK. UNKNOWN_STRATEGY is now the default fallback strategy. Note I made the constant public to allow for that. I could restrict the visibility, but by making it public feels correct in this case since it allows a user to 'reset' it back as a strategy.

Patch:
diff --git a/src/main/java/no/finn/unleash/DefaultUnleash.java b/src/main/java/no/finn/unleash/DefaultUnleash.java
index baa6c427..fdb54927 100644
--- a/src/main/java/no/finn/unleash/DefaultUnleash.java
+++ b/src/main/java/no/finn/unleash/DefaultUnleash.java
@@ -38,7 +38,7 @@ public final class DefaultUnleash implements Unleash {
                     new UserWithIdStrategy(),
                     new FlexibleRolloutStrategy());
 
-    private static final UnknownStrategy UNKNOWN_STRATEGY = new UnknownStrategy();
+    public static final UnknownStrategy UNKNOWN_STRATEGY = new UnknownStrategy();
 
     private final UnleashMetricService metricService;
     private final ToggleRepository toggleRepository;
@@ -219,7 +219,7 @@ public final class DefaultUnleash implements Unleash {
     }
 
     private Strategy getStrategy(String strategy) {
-        return strategyMap.containsKey(strategy) ? strategyMap.get(strategy) : UNKNOWN_STRATEGY;
+        return strategyMap.getOrDefault(strategy, config.getFallbackStrategy());
     }
 
     @Override
diff --git a/src/main/java/no/finn/unleash/util/UnleashConfig.java b/src/main/java/no/finn/unleash/util/UnleashConfig.java
index 7f306436..a39c79f1 100644
--- a/src/main/java/no/finn/unleash/util/UnleashConfig.java
+++ b/src/main/java/no/finn/unleash/util/UnleashConfig.java
@@ -11,6 +11,9 @@ import no.finn.unleash.UnleashContextProvider;
 import no.finn.unleash.event.NoOpSubscriber;
 import no.finn.unleash.event.UnleashSubscriber;
 import no.finn.unleash.lang.Nullable;
+import no.finn.unleash.strategy.Strategy;
+
+import static no.finn.unleash.DefaultUnleash.UNKNOWN_STRATEGY;
 
 public class UnleashConfig {
 
@@ -35,6 +38,7 @@ public class UnleashConfig {
     private final boolean synchronousFetchOnInitialisation;
     private final UnleashScheduledExecutor unleashScheduledExecutor;
     private final UnleashSubscriber unleashSubscriber;
+    @Nullable private final Strategy fallbackStrategy;
 
     private UnleashConfig(
             @Nullable URI unleashAPI,
@@ -53,7 +57,8 @@ public class UnleashConfig {
             boolean isProxyAuthenticationByJvmProperties,
             boolean synchronousFetchOnInitialisation,
             @Nullable UnleashScheduledExecutor unleashScheduledExecutor,
-            @Nullable UnleashSubscriber unleashSubscriber) {
+            @Nullable UnleashSubscriber unleashSubscriber,
+            @Nullable Strategy fallbackStrategy) {
 
         if (appName == null) {
             throw new IllegalStateException("You are required to specify the unleash appName");
@@ -75,6 +80,12 @@ public class UnleashConfig {
             throw new IllegalStateException("You are required to specify a subscriber");
         }
 
+        if ( fallbackStrategy == null) {
+            this.fallbackStrategy = UNKNOWN_STRATEGY;
+        } else {
+            this.fallbackStrategy = fallbackStrategy;
+        }
+
         if (isProxyAuthenticationByJvmProperties) {
             enableProxyAuthentication();
         }
@@ -189,6 +200,11 @@ public class UnleashConfig {
         return isProxyAuthenticationByJvmProperties;
     }
 
+    @Nullable
+    public Strategy getFallbackStrategy() {
+        return fallbackStrategy;
+    }
+
     static class ProxyAuthenticator extends Authenticator {
 
         @Override
@@ -232,6 +248,7 @@ public class UnleashConfig {
         private @Nullable UnleashScheduledExecutor scheduledExecutor;
         private @Nullable UnleashSubscriber unleashSubscriber;
         private boolean isProxyAuthenticationByJvmProperties;
+        private Strategy fallbackStrategy;
 
         private static String getHostname() {
             String hostName = System.getProperty("hostname");
@@ -333,6 +350,11 @@ public class UnleashConfig {
             return this;
         }
 
+        public Builder fallbackStrategy(@Nullable Strategy fallbackStrategy) {
+            this.fallbackStrategy = fallbackStrategy;
+            return this;
+        }
+
         private String getBackupFile() {
             if (backupFile != null) {
                 return backupFile;
@@ -361,7 +383,8 @@ public class UnleashConfig {
                     synchronousFetchOnInitialisation,
                     Optional.ofNullable(scheduledExecutor)
                             .orElseGet(UnleashScheduledExecutorImpl::getInstance),
-                    Optional.ofNullable(unleashSubscriber).orElseGet(NoOpSubscriber::new));
+                    Optional.ofNullable(unleashSubscriber).orElseGet(NoOpSubscriber::new),
+                    fallbackStrategy);
         }
 
         public String getDefaultSdkVersion() {


Test patch:
diff --git a/src/test/java/no/finn/unleash/DefaultUnleashTest.java b/src/test/java/no/finn/unleash/DefaultUnleashTest.java
index e119ea3d..21a065d7 100644
--- a/src/test/java/no/finn/unleash/DefaultUnleashTest.java
+++ b/src/test/java/no/finn/unleash/DefaultUnleashTest.java
@@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.Mockito.*;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -70,4 +71,33 @@ class DefaultUnleashTest {
 
         verify(metricService).countVariant("toggle1", "variant1");
     }
+
+    @Test
+    public void should_allow_fallback_strategy() {
+        Strategy fallback = mock(Strategy.class);
+
+        UnleashConfig unleashConfigWithFallback =
+                UnleashConfig.builder()
+                        .unleashAPI("http://fakeAPI")
+                        .appName("fakeApp")
+                        .fallbackStrategy(fallback)
+                        .build();
+        sut =
+                new DefaultUnleash(
+                        unleashConfigWithFallback,
+                        toggleRepository,
+                        new HashMap<>(),
+                        contextProvider,
+                        eventDispatcher,
+                        metricService);
+
+        ActivationStrategy as = new ActivationStrategy("forFallback", new HashMap<>());
+        FeatureToggle toggle = new FeatureToggle("toggle1", true, Collections.singletonList(as));
+        when(toggleRepository.getToggle("toggle1")).thenReturn(toggle);
+        when(contextProvider.getContext()).thenReturn(UnleashContext.builder().build());
+
+        sut.isEnabled("toggle1");
+
+        verify(fallback).isEnabled(any(), any(), any());
+    }
 }