diff --git a/.github/component_owners.yml b/.github/component_owners.yml
index 2b280a851..1f42662bc 100644
--- a/.github/component_owners.yml
+++ b/.github/component_owners.yml
@@ -35,6 +35,8 @@ components:
- novalisdenahi
providers/statsig:
- liran2000
+ providers/multiprovider:
+ - liran2000
ignored-authors:
- renovate-bot
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index f818fd120..69acedd13 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -9,5 +9,6 @@
"providers/flipt": "0.1.1",
"providers/configcat": "0.1.0",
"providers/statsig": "0.1.0",
+ "providers/multiprovider": "0.0.1",
"tools/junit-openfeature": "0.1.1"
}
diff --git a/pom.xml b/pom.xml
index f481c1964..10e28c18e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -38,6 +38,7 @@
providers/flipt
providers/configcat
providers/statsig
+ providers/multiprovider
diff --git a/providers/multiprovider/README.md b/providers/multiprovider/README.md
new file mode 100644
index 000000000..d7af9fc26
--- /dev/null
+++ b/providers/multiprovider/README.md
@@ -0,0 +1,86 @@
+# OpenFeature Multi-Provider for Java
+
+The OpenFeature Multi-Provider wraps multiple underlying providers in a unified interface, allowing the SDK client to transparently interact with all those providers at once.
+This allows use cases where a single client and evaluation interface is desired, but where the flag data should come from more than one source.
+
+Some examples:
+
+- A migration from one feature flagging provider to another.
+ During that process, you may have some flags that have been ported to the new system and others that haven’t.
+ Therefore, you’d want the Multi-Provider to return the result of the “new” system if available otherwise, return the "old" system’s result.
+- Long-term use of multiple sources for flags.
+ For example, someone might want to be able to combine environment variables, database entries, and vendor feature flag results together in a single interface, and define the precedence order in which those sources should be consulted.
+- Setting a fallback for cloud providers.
+ You can use the Multi-Provider to automatically fall back to a local configuration if an external vendor provider goes down, rather than using the default values.
+ By using the FirstSuccessfulStrategy, the Multi-Provider will move on to the next provider in the list if an error is thrown.
+
+## Strategies
+
+The Multi-Provider supports multiple ways of deciding how to evaluate the set of providers it is managing, and how to deal with any errors that are thrown.
+
+Strategies must be adaptable to the various requirements that might be faced in a multi-provider situation.
+In some cases, the strategy may want to ignore errors from individual providers as long as one of them successfully responds.
+In other cases, it may want to evaluate providers in order and skip the rest if a successful result is obtained.
+In still other scenarios, it may be required to always call every provider and decide what to do with the set of results.
+
+The strategy to use is passed in to the Multi-Provider.
+
+By default, the Multi-Provider uses the “FirstMatchStrategy”.
+
+Here are some standard strategies that come with the Multi-Provider:
+
+### First Match
+
+Return the first result returned by a provider.
+Skip providers that indicate they had no value due to `FLAG_NOT_FOUND`.
+In all other cases, use the value returned by the provider.
+If any provider returns an error result other than `FLAG_NOT_FOUND`, the whole evaluation should error and “bubble up” the individual provider’s error in the result.
+
+As soon as a value is returned by a provider, the rest of the operation should short-circuit and not call the rest of the providers.
+
+### First Successful
+
+Similar to “First Match”, except that errors from evaluated providers do not halt execution.
+Instead, it will return the first successful result from a provider. If no provider successfully responds, it will throw an error result.
+
+### User Defined
+
+Rather than making assumptions about when to use a provider’s result and when not to (which may not hold across all providers) there is also a way for the user to define their own strategy that determines whether to use a result or fall through to the next one.
+
+## Installation
+
+
+
+```xml
+
+
+ dev.openfeature.contrib.providers
+ multi-provider
+ 0.0.1
+
+```
+
+
+
+## Usage
+
+Usage example:
+
+```
+...
+List providers = new ArrayList<>(2);
+providers.add(provider1);
+providers.add(provider2);
+
+// initialize using default strategy (first match)
+MultiProvider multiProvider = new MultiProvider(providers);
+OpenFeatureAPI.getInstance().setProviderAndWait(multiProvider);
+
+// initialize using a different strategy
+multiProvider = new MultiProvider(providers, new FirstSuccessfulStrategy());
+...
+```
+
+See [MultiProviderTest](./src/test/java/dev/openfeature/contrib/providers/multiprovider/MultiProviderTest.java)
+for more information.
+
diff --git a/providers/multiprovider/lombok.config b/providers/multiprovider/lombok.config
new file mode 100644
index 000000000..bcd1afdae
--- /dev/null
+++ b/providers/multiprovider/lombok.config
@@ -0,0 +1,5 @@
+# This file is needed to avoid errors throw by findbugs when working with lombok.
+lombok.addSuppressWarnings = true
+lombok.addLombokGeneratedAnnotation = true
+config.stopBubbling = true
+lombok.extern.findbugs.addSuppressFBWarnings = true
diff --git a/providers/multiprovider/pom.xml b/providers/multiprovider/pom.xml
new file mode 100644
index 000000000..1ef8c52a2
--- /dev/null
+++ b/providers/multiprovider/pom.xml
@@ -0,0 +1,32 @@
+
+
+ 4.0.0
+
+ dev.openfeature.contrib
+ parent
+ 0.1.0
+ ../../pom.xml
+
+ dev.openfeature.contrib.providers
+ multiprovider
+ 0.0.1
+
+ multiprovider
+ OpenFeature Multi-Provider
+ https://github.com/open-feature/java-sdk-contrib/tree/main/providers/multiprovider
+
+
+
+ org.json
+ json
+ 20240303
+
+
+ org.apache.logging.log4j
+ log4j-slf4j2-impl
+ 2.24.1
+ test
+
+
+
diff --git a/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstMatchStrategy.java b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstMatchStrategy.java
new file mode 100644
index 000000000..3c90b3154
--- /dev/null
+++ b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstMatchStrategy.java
@@ -0,0 +1,53 @@
+package dev.openfeature.contrib.providers.multiprovider;
+
+import dev.openfeature.sdk.EvaluationContext;
+import dev.openfeature.sdk.FeatureProvider;
+import dev.openfeature.sdk.ProviderEvaluation;
+import dev.openfeature.sdk.exceptions.FlagNotFoundError;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Map;
+import java.util.function.Function;
+
+import static dev.openfeature.sdk.ErrorCode.FLAG_NOT_FOUND;
+
+/**
+ * First match strategy.
+ * Return the first result returned by a provider. Skip providers that indicate they had no value due to
+ * FLAG_NOT_FOUND.
+ * In all other cases, use the value returned by the provider.
+ * If any provider returns an error result other than FLAG_NOT_FOUND, the whole evaluation should error and
+ * “bubble up” the individual provider’s error in the result.
+ * As soon as a value is returned by a provider, the rest of the operation should short-circuit and not call
+ * the rest of the providers.
+ */
+@Slf4j
+@NoArgsConstructor
+public class FirstMatchStrategy implements Strategy {
+
+ /**
+ * Represents a strategy that evaluates providers based on a first-match approach.
+ * Provides a method to evaluate providers using a specified function and return the evaluation result.
+ *
+ * @param providerFunction provider function
+ * @param ProviderEvaluation type
+ * @return the provider evaluation
+ */
+ @Override
+ public ProviderEvaluation evaluate(Map providers, String key, T defaultValue,
+ EvaluationContext ctx, Function> providerFunction) {
+ for (FeatureProvider provider: providers.values()) {
+ try {
+ ProviderEvaluation res = providerFunction.apply(provider);
+ if (!FLAG_NOT_FOUND.equals(res.getErrorCode())) {
+ return res;
+ }
+ } catch (FlagNotFoundError e) {
+ log.debug("flag not found {}", e.getMessage());
+ }
+ }
+
+ throw new FlagNotFoundError("flag not found");
+ }
+}
diff --git a/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstSuccessfulStrategy.java b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstSuccessfulStrategy.java
new file mode 100644
index 000000000..ec23746fb
--- /dev/null
+++ b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstSuccessfulStrategy.java
@@ -0,0 +1,39 @@
+package dev.openfeature.contrib.providers.multiprovider;
+
+import dev.openfeature.sdk.EvaluationContext;
+import dev.openfeature.sdk.FeatureProvider;
+import dev.openfeature.sdk.ProviderEvaluation;
+import dev.openfeature.sdk.exceptions.GeneralError;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * First Successful Strategy.
+ * Similar to “First Match”, except that errors from evaluated providers do not halt execution.
+ * Instead, it will return the first successful result from a provider.
+ * If no provider successfully responds, it will throw an error result.
+ */
+@Slf4j
+@NoArgsConstructor
+public class FirstSuccessfulStrategy implements Strategy {
+
+ @Override
+ public ProviderEvaluation evaluate(Map providers, String key, T defaultValue,
+ EvaluationContext ctx, Function> providerFunction) {
+ for (FeatureProvider provider: providers.values()) {
+ try {
+ ProviderEvaluation res = providerFunction.apply(provider);
+ if (res.getErrorCode() == null) {
+ return res;
+ }
+ } catch (Exception e) {
+ log.debug("evaluation exception {}", e.getMessage());
+ }
+ }
+
+ throw new GeneralError("evaluation error");
+ }
+}
diff --git a/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/MultiProvider.java b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/MultiProvider.java
new file mode 100644
index 000000000..65d24cb41
--- /dev/null
+++ b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/MultiProvider.java
@@ -0,0 +1,152 @@
+package dev.openfeature.contrib.providers.multiprovider;
+
+import dev.openfeature.sdk.EvaluationContext;
+import dev.openfeature.sdk.EventProvider;
+import dev.openfeature.sdk.FeatureProvider;
+import dev.openfeature.sdk.Metadata;
+import dev.openfeature.sdk.ProviderEvaluation;
+import dev.openfeature.sdk.Value;
+import dev.openfeature.sdk.exceptions.GeneralError;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ * Provider implementation for Multi-provider.
+ */
+@Slf4j
+public class MultiProvider extends EventProvider {
+
+ @Getter
+ private static final String NAME = "multiprovider";
+ public static final int INIT_THREADS_COUNT = 8;
+ private final Map providers;
+ private final Strategy strategy;
+ private String metadataName;
+
+ /**
+ * Constructs a MultiProvider with the given list of FeatureProviders, using a default strategy.
+ *
+ * @param providers the list of FeatureProviders to initialize the MultiProvider with
+ */
+ public MultiProvider(List providers) {
+ this(providers, null);
+ }
+
+ /**
+ * Constructs a MultiProvider with the given list of FeatureProviders and a strategy.
+ *
+ * @param providers the list of FeatureProviders to initialize the MultiProvider with
+ * @param strategy the strategy
+ */
+ public MultiProvider(List providers, Strategy strategy) {
+ this.providers = buildProviders(providers);
+ if (strategy != null) {
+ this.strategy = strategy;
+ } else {
+ this.strategy = new FirstMatchStrategy();
+ }
+ }
+
+ protected static Map buildProviders(List providers) {
+ Map providersMap = new LinkedHashMap<>(providers.size());
+ for (FeatureProvider provider: providers) {
+ FeatureProvider prevProvider = providersMap.put(provider.getMetadata().getName(), provider);
+ if (prevProvider != null) {
+ log.warn("duplicated provider name: {}", provider.getMetadata().getName());
+ }
+ }
+ return Collections.unmodifiableMap(providersMap);
+ }
+
+ /**
+ * Initialize the provider.
+ * @param evaluationContext evaluation context
+ * @throws Exception on error
+ */
+ @Override
+ public void initialize(EvaluationContext evaluationContext) throws Exception {
+ JSONObject json = new JSONObject();
+ json.put("name", NAME);
+ JSONObject providersMetadata = new JSONObject();
+ json.put("originalMetadata", providersMetadata);
+ ExecutorService initPool = Executors.newFixedThreadPool(INIT_THREADS_COUNT);
+ Collection> tasks = new ArrayList<>(providers.size());
+ for (FeatureProvider provider: providers.values()) {
+ tasks.add(() -> {
+ provider.initialize(evaluationContext);
+ return true;
+ });
+ JSONObject providerMetadata = new JSONObject();
+ providerMetadata.put("name", provider.getMetadata().getName());
+ providersMetadata.put(provider.getMetadata().getName(), providerMetadata);
+ }
+ List> results = initPool.invokeAll(tasks);
+ for (Future result: results) {
+ if (!result.get()) {
+ throw new GeneralError("init failed");
+ }
+ }
+ metadataName = json.toString();
+ }
+
+ @Override
+ public Metadata getMetadata() {
+ return () -> metadataName;
+ }
+
+ @Override
+ public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
+ return strategy.evaluate(providers, key, defaultValue, ctx,
+ p -> p.getBooleanEvaluation(key, defaultValue, ctx));
+ }
+
+ @Override
+ public ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
+ return strategy.evaluate(providers, key, defaultValue, ctx,
+ p -> p.getStringEvaluation(key, defaultValue, ctx));
+ }
+
+ @Override
+ public ProviderEvaluation getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) {
+ return strategy.evaluate(providers, key, defaultValue, ctx,
+ p -> p.getIntegerEvaluation(key, defaultValue, ctx));
+ }
+
+ @Override
+ public ProviderEvaluation getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) {
+ return strategy.evaluate(providers, key, defaultValue, ctx,
+ p -> p.getDoubleEvaluation(key, defaultValue, ctx));
+ }
+
+ @Override
+ public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) {
+ return strategy.evaluate(providers, key, defaultValue, ctx,
+ p -> p.getObjectEvaluation(key, defaultValue, ctx));
+ }
+
+ @Override
+ public void shutdown() {
+ log.debug("shutdown begin");
+ for (FeatureProvider provider: providers.values()) {
+ try {
+ provider.shutdown();
+ } catch (Exception e) {
+ log.error("error shutdown provider {}", provider.getMetadata().getName(), e);
+ }
+ }
+ log.debug("shutdown end");
+ }
+
+}
diff --git a/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/Strategy.java b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/Strategy.java
new file mode 100644
index 000000000..85fc06747
--- /dev/null
+++ b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/Strategy.java
@@ -0,0 +1,16 @@
+package dev.openfeature.contrib.providers.multiprovider;
+
+import dev.openfeature.sdk.EvaluationContext;
+import dev.openfeature.sdk.FeatureProvider;
+import dev.openfeature.sdk.ProviderEvaluation;
+
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * strategy.
+ */
+public interface Strategy {
+ ProviderEvaluation evaluate(Map providers, String key, T defaultValue,
+ EvaluationContext ctx, Function> providerFunction);
+}
diff --git a/providers/multiprovider/src/test/java/dev/openfeature/contrib/providers/multiprovider/MultiProviderTest.java b/providers/multiprovider/src/test/java/dev/openfeature/contrib/providers/multiprovider/MultiProviderTest.java
new file mode 100644
index 000000000..943728c6a
--- /dev/null
+++ b/providers/multiprovider/src/test/java/dev/openfeature/contrib/providers/multiprovider/MultiProviderTest.java
@@ -0,0 +1,202 @@
+package dev.openfeature.contrib.providers.multiprovider;
+
+import dev.openfeature.sdk.EvaluationContext;
+import dev.openfeature.sdk.FeatureProvider;
+import dev.openfeature.sdk.Metadata;
+import dev.openfeature.sdk.MutableContext;
+import dev.openfeature.sdk.ProviderEvaluation;
+import dev.openfeature.sdk.Value;
+import dev.openfeature.sdk.exceptions.FlagNotFoundError;
+import dev.openfeature.sdk.exceptions.GeneralError;
+import dev.openfeature.sdk.providers.memory.Flag;
+import dev.openfeature.sdk.providers.memory.InMemoryProvider;
+import lombok.SneakyThrows;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Function;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class MultiProviderTest {
+
+ @SneakyThrows
+ @Test
+ public void testInit() {
+ FeatureProvider provider1 = mock(FeatureProvider.class);
+ FeatureProvider provider2 = mock(FeatureProvider.class);
+ when(provider1.getMetadata()).thenReturn(() -> "provider1");
+ when(provider2.getMetadata()).thenReturn(() -> "provider2");
+
+ List providers = new ArrayList<>(2);
+ providers.add(provider1);
+ providers.add(provider2);
+ Strategy strategy = mock(Strategy.class);
+ MultiProvider multiProvider = new MultiProvider(providers, strategy);
+ multiProvider.initialize(null);
+
+ assertNotNull(multiProvider);
+ assertEquals("{\"originalMetadata\":{\"provider1\":{\"name\":\"provider1\"}," +
+ "\"provider2\":{\"name\":\"provider2\"}},\"name\":\"multiprovider\"}",
+ multiProvider.getMetadata().getName());
+ }
+
+ @SneakyThrows
+ @Test
+ public void testInitOneFails() {
+ FeatureProvider provider1 = mock(FeatureProvider.class);
+ FeatureProvider provider2 = mock(FeatureProvider.class);
+ when(provider1.getMetadata()).thenReturn(() -> "provider1");
+ when(provider2.getMetadata()).thenReturn(() -> "provider2");
+ doThrow(new GeneralError()).when(provider1).initialize(any());
+ doThrow(new GeneralError()).when(provider1).shutdown();
+
+ List providers = new ArrayList<>(2);
+ providers.add(provider1);
+ providers.add(provider2);
+ Strategy strategy = mock(Strategy.class);
+ MultiProvider multiProvider = new MultiProvider(providers, strategy);
+ assertThrows(ExecutionException.class, () -> multiProvider.initialize(null));
+ assertDoesNotThrow(() -> multiProvider.shutdown());
+ }
+
+ @Test
+ public void testDuplicateProviderNames() {
+ FeatureProvider provider1 = mock(FeatureProvider.class);
+ FeatureProvider provider2 = mock(FeatureProvider.class);
+ when(provider1.getMetadata()).thenReturn(() -> "provider");
+ when(provider2.getMetadata()).thenReturn(() -> "provider");
+
+ List providers = new ArrayList<>(2);
+ providers.add(provider1);
+ providers.add(provider2);
+
+ assertDoesNotThrow(() -> new MultiProvider(providers, null).initialize(null));
+ }
+
+ @SneakyThrows
+ @Test
+ public void testRetrieveMetadataName() {
+ List providers = new ArrayList<>();
+ FeatureProvider mockProvider = mock(FeatureProvider.class);
+ when(mockProvider.getMetadata()).thenReturn(() -> "MockProvider");
+ providers.add(mockProvider);
+ Strategy mockStrategy = mock(Strategy.class);
+ MultiProvider multiProvider = new MultiProvider(providers, mockStrategy);
+ multiProvider.initialize(null);
+
+ assertEquals("{\"originalMetadata\":{\"MockProvider\":{\"name\":\"MockProvider\"}}," +
+ "\"name\":\"multiprovider\"}", multiProvider.getMetadata().getName());
+ }
+
+ @SneakyThrows
+ @Test
+ public void testEvaluations() {
+ Map> flags1 = new HashMap<>();
+ flags1.put("b1", Flag.builder().variant("true", true)
+ .variant("false", false).defaultVariant("true").build());
+ flags1.put("i1", Flag.builder().variant("v", 1).defaultVariant("v").build());
+ flags1.put("d1", Flag.builder().variant("v", 1.0).defaultVariant("v").build());
+ flags1.put("s1", Flag.builder().variant("v", "str1").defaultVariant("v").build());
+ flags1.put("o1", Flag.builder().variant("v", new Value("v1"))
+ .defaultVariant("v").build());
+ InMemoryProvider provider1 = new InMemoryProvider(flags1) {
+ public Metadata getMetadata() {
+ return () -> "old-provider";
+ }
+ };
+ Map> flags2 = new HashMap<>();
+ flags2.put("b1", Flag.builder().variant("true", true)
+ .variant("false", false).defaultVariant("false").build());
+ flags2.put("i1", Flag.builder().variant("v", 2).defaultVariant("v").build());
+ flags2.put("d1", Flag.builder().variant("v", 2.0).defaultVariant("v").build());
+ flags2.put("s1", Flag.builder().variant("v", "str2").defaultVariant("v").build());
+ flags2.put("o1", Flag.builder().variant("v", new Value("v2"))
+ .defaultVariant("v").build());
+
+ flags2.put("s2", Flag.builder().variant("v", "s2str2").defaultVariant("v").build());
+ InMemoryProvider provider2 = new InMemoryProvider(flags2) {
+ public Metadata getMetadata() {
+ return () -> "new-provider";
+ }
+ };
+ List providers = new ArrayList<>(2);
+ providers.add(provider1);
+ providers.add(provider2);
+ MultiProvider multiProvider = new MultiProvider(providers);
+ multiProvider.initialize(null);
+
+ assertEquals(true, multiProvider.getBooleanEvaluation("b1", false, null)
+ .getValue());
+ assertEquals(1, multiProvider.getIntegerEvaluation("i1", 0, null)
+ .getValue());
+ assertEquals(1.0, multiProvider.getDoubleEvaluation("d1", 0.0, null)
+ .getValue());
+ assertEquals("str1", multiProvider.getStringEvaluation("s1", "", null)
+ .getValue());
+ assertEquals("v1", multiProvider.getObjectEvaluation("o1", null, null)
+ .getValue().asString());
+
+ assertEquals("s2str2", multiProvider.getStringEvaluation("s2", "", null)
+ .getValue());
+ MultiProvider finalMultiProvider1 = multiProvider;
+ assertThrows(FlagNotFoundError.class, () ->
+ finalMultiProvider1.getStringEvaluation("non-existing", "", null));
+
+ multiProvider.shutdown();
+ multiProvider = new MultiProvider(providers, new FirstSuccessfulStrategy());
+ multiProvider.initialize(null);
+
+ assertEquals(true, multiProvider.getBooleanEvaluation("b1", false, null)
+ .getValue());
+ assertEquals(1, multiProvider.getIntegerEvaluation("i1", 0, null)
+ .getValue());
+ assertEquals(1.0, multiProvider.getDoubleEvaluation("d1", 0.0, null)
+ .getValue());
+ assertEquals("str1", multiProvider.getStringEvaluation("s1", "", null)
+ .getValue());
+ assertEquals("v1", multiProvider.getObjectEvaluation("o1", null, null)
+ .getValue().asString());
+
+ assertEquals("s2str2", multiProvider.getStringEvaluation("s2", "", null)
+ .getValue());
+ MultiProvider finalMultiProvider2 = multiProvider;
+ assertThrows(GeneralError.class, () ->
+ finalMultiProvider2.getStringEvaluation("non-existing", "", null));
+
+ multiProvider.shutdown();
+ Strategy customStrategy = new Strategy() {
+ final FirstMatchStrategy fallbackStrategy = new FirstMatchStrategy();
+ @Override
+ public ProviderEvaluation evaluate(Map providers, String key, T defaultValue, EvaluationContext ctx, Function> providerFunction) {
+ Value contextProvider = null;
+ if (ctx != null) {
+ contextProvider = ctx.getValue("provider");
+ }
+ if (contextProvider != null && "new-provider".equals(contextProvider.asString())) {
+ return providerFunction.apply(providers.get("new-provider"));
+ }
+ return fallbackStrategy.evaluate(providers, key, defaultValue, ctx, providerFunction);
+ }
+ };
+ multiProvider = new MultiProvider(providers, customStrategy);
+ multiProvider.initialize(null);
+
+ EvaluationContext context = new MutableContext().add("provider", "new-provider");
+ assertEquals(false, multiProvider.getBooleanEvaluation("b1", true, context)
+ .getValue());
+ assertEquals(true, multiProvider.getBooleanEvaluation("b1", true, null)
+ .getValue());
+ }
+}
\ No newline at end of file
diff --git a/providers/multiprovider/src/test/resources/log4j2-test.xml b/providers/multiprovider/src/test/resources/log4j2-test.xml
new file mode 100644
index 000000000..aced30f8a
--- /dev/null
+++ b/providers/multiprovider/src/test/resources/log4j2-test.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/providers/multiprovider/version.txt b/providers/multiprovider/version.txt
new file mode 100644
index 000000000..8acdd82b7
--- /dev/null
+++ b/providers/multiprovider/version.txt
@@ -0,0 +1 @@
+0.0.1
diff --git a/release-please-config.json b/release-please-config.json
index 964a4ee8b..b8a017625 100644
--- a/release-please-config.json
+++ b/release-please-config.json
@@ -100,6 +100,17 @@
"README.md"
]
},
+ "providers/multiprovider": {
+ "package-name": "dev.openfeature.contrib.providers.multiprovider",
+ "release-type": "simple",
+ "bump-minor-pre-major": true,
+ "bump-patch-for-minor-pre-major": true,
+ "versioning": "default",
+ "extra-files": [
+ "pom.xml",
+ "README.md"
+ ]
+ },
"hooks/open-telemetry": {
"package-name": "dev.openfeature.contrib.hooks.otel",
"release-type": "simple",