From 99dc065636753d75ca381c2927550ff5a20a7252 Mon Sep 17 00:00:00 2001 From: liran2000 Date: Sun, 20 Oct 2024 20:34:04 +0300 Subject: [PATCH 01/12] add multi-provider Signed-off-by: liran2000 --- .github/component_owners.yml | 2 + .release-please-manifest.json | 1 + providers/multi-provider/CHANGELOG.md | 81 +++++++++++++ providers/multi-provider/lombok.config | 5 + providers/multi-provider/pom.xml | 21 ++++ .../providers/multiprovider/BaseStrategy.java | 16 +++ .../FirstSuccessfulStrategy.java | 30 +++++ .../multiprovider/MultiProvider.java | 110 ++++++++++++++++++ .../providers/multiprovider/Strategy.java | 10 ++ .../src/test/resources/log4j2-test.xml | 13 +++ providers/multi-provider/version.txt | 1 + release-please-config.json | 11 ++ 12 files changed, 301 insertions(+) create mode 100644 providers/multi-provider/CHANGELOG.md create mode 100644 providers/multi-provider/lombok.config create mode 100644 providers/multi-provider/pom.xml create mode 100644 providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java create mode 100644 providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstSuccessfulStrategy.java create mode 100644 providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/MultiProvider.java create mode 100644 providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/Strategy.java create mode 100644 providers/multi-provider/src/test/resources/log4j2-test.xml create mode 100644 providers/multi-provider/version.txt diff --git a/.github/component_owners.yml b/.github/component_owners.yml index 2b280a851..572147df8 100644 --- a/.github/component_owners.yml +++ b/.github/component_owners.yml @@ -35,6 +35,8 @@ components: - novalisdenahi providers/statsig: - liran2000 + providers/multi-provider: + - liran2000 ignored-authors: - renovate-bot diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 22b80d7a8..aace4f1db 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -9,5 +9,6 @@ "providers/flipt": "0.1.0", "providers/configcat": "0.1.0", "providers/statsig": "0.1.0", + "providers/multi-provider": "0.0.1", "tools/junit-openfeature": "0.1.0" } diff --git a/providers/multi-provider/CHANGELOG.md b/providers/multi-provider/CHANGELOG.md new file mode 100644 index 000000000..ef5c266cf --- /dev/null +++ b/providers/multi-provider/CHANGELOG.md @@ -0,0 +1,81 @@ +# Changelog + +## [0.1.0](https://github.com/open-feature/java-sdk-contrib/compare/dev.openfeature.contrib.providers.statsig-v0.0.4...dev.openfeature.contrib.providers.statsig-v0.1.0) (2024-09-25) + + +### ⚠ BREAKING CHANGES + +* use sdk-maintained state, require 1.12 ([#964](https://github.com/open-feature/java-sdk-contrib/issues/964)) + +### 🐛 Bug Fixes + +* **deps:** update dependency com.statsig:serversdk to v1.13.0 ([#699](https://github.com/open-feature/java-sdk-contrib/issues/699)) ([f5c38cf](https://github.com/open-feature/java-sdk-contrib/commit/f5c38cf832e6d4970b3076811ac5b6c2b7da86ee)) +* **deps:** update dependency com.statsig:serversdk to v1.13.1 ([#704](https://github.com/open-feature/java-sdk-contrib/issues/704)) ([5d35725](https://github.com/open-feature/java-sdk-contrib/commit/5d357255f10f6608a51bf253bac5dc999fef5a83)) +* **deps:** update dependency com.statsig:serversdk to v1.14.0 ([#718](https://github.com/open-feature/java-sdk-contrib/issues/718)) ([7f76265](https://github.com/open-feature/java-sdk-contrib/commit/7f76265b4f610a171bd5bd239941cb1d1420ab55)) +* **deps:** update dependency com.statsig:serversdk to v1.15.0 ([#728](https://github.com/open-feature/java-sdk-contrib/issues/728)) ([f101e2f](https://github.com/open-feature/java-sdk-contrib/commit/f101e2f6d5b5d9486bc5b426a1f05c71b70d658c)) +* **deps:** update dependency com.statsig:serversdk to v1.16.0 ([#754](https://github.com/open-feature/java-sdk-contrib/issues/754)) ([c8e5cb6](https://github.com/open-feature/java-sdk-contrib/commit/c8e5cb66305db5b076ed4ba5f1a6d1a60b0115f3)) +* **deps:** update dependency com.statsig:serversdk to v1.17.0 ([#758](https://github.com/open-feature/java-sdk-contrib/issues/758)) ([e0c7a26](https://github.com/open-feature/java-sdk-contrib/commit/e0c7a266835ab41e9298cd9b726f696bcac21527)) +* **deps:** update dependency com.statsig:serversdk to v1.17.1 ([#768](https://github.com/open-feature/java-sdk-contrib/issues/768)) ([40d4492](https://github.com/open-feature/java-sdk-contrib/commit/40d4492372c44abccf45f03a3ec499b3727b6d0a)) +* **deps:** update dependency com.statsig:serversdk to v1.17.2 ([#770](https://github.com/open-feature/java-sdk-contrib/issues/770)) ([1da0305](https://github.com/open-feature/java-sdk-contrib/commit/1da0305d8daf35fc64d0864bf9839ac1afe88bf4)) +* **deps:** update dependency com.statsig:serversdk to v1.17.3 ([#774](https://github.com/open-feature/java-sdk-contrib/issues/774)) ([fa56579](https://github.com/open-feature/java-sdk-contrib/commit/fa56579fdebe99cbc5415e95585ba791b4e1b247)) +* **deps:** update dependency com.statsig:serversdk to v1.18.0 ([#799](https://github.com/open-feature/java-sdk-contrib/issues/799)) ([bfbdef8](https://github.com/open-feature/java-sdk-contrib/commit/bfbdef8cdfe6bdbb1015ffc65354f852b3889fa5)) +* **deps:** update dependency com.statsig:serversdk to v1.18.1 ([#806](https://github.com/open-feature/java-sdk-contrib/issues/806)) ([17c61c3](https://github.com/open-feature/java-sdk-contrib/commit/17c61c32d5669ef19e886230b95c4028e9d87d0c)) +* **deps:** update dependency com.statsig:serversdk to v1.22.0 ([#822](https://github.com/open-feature/java-sdk-contrib/issues/822)) ([a560308](https://github.com/open-feature/java-sdk-contrib/commit/a560308452ea737b41b9d19d1d0942dffa7f9e51)) +* **deps:** update dependency org.slf4j:slf4j-api to v2.0.13 ([#752](https://github.com/open-feature/java-sdk-contrib/issues/752)) ([b820fcf](https://github.com/open-feature/java-sdk-contrib/commit/b820fcf1b7ea945a8e450dcc90addb82f5fb865d)) +* **deps:** update dependency org.slf4j:slf4j-api to v2.0.14 ([#904](https://github.com/open-feature/java-sdk-contrib/issues/904)) ([028b332](https://github.com/open-feature/java-sdk-contrib/commit/028b332dc8ac3b134e5453d5449a4c11b4ef250a)) +* **deps:** update dependency org.slf4j:slf4j-api to v2.0.15 ([#910](https://github.com/open-feature/java-sdk-contrib/issues/910)) ([2f58638](https://github.com/open-feature/java-sdk-contrib/commit/2f58638eb4907c948325d1e61853e1b6eabfa4c1)) +* **deps:** update dependency org.slf4j:slf4j-api to v2.0.16 ([#912](https://github.com/open-feature/java-sdk-contrib/issues/912)) ([52571d8](https://github.com/open-feature/java-sdk-contrib/commit/52571d806e7c547006db836245b4895fe9bc4660)) + + +### ✨ New Features + +* add support for getBooleanEvaluation with default value ([#722](https://github.com/open-feature/java-sdk-contrib/issues/722)) ([835f672](https://github.com/open-feature/java-sdk-contrib/commit/835f6727d98883bb7fc351b5dd59039228fbcb2b)) +* use sdk-maintained state, require 1.12 ([#964](https://github.com/open-feature/java-sdk-contrib/issues/964)) ([4a041b0](https://github.com/open-feature/java-sdk-contrib/commit/4a041b0dda9c4e460f4c2199f3bc680df0dda621)) + + +### 🧹 Chore + +* **deps:** update dependency org.apache.logging.log4j:log4j-slf4j2-impl to v2.23.1 ([#709](https://github.com/open-feature/java-sdk-contrib/issues/709)) ([d0bc7a5](https://github.com/open-feature/java-sdk-contrib/commit/d0bc7a5aceb746d6d7c442e189a6a1e011673ba7)) +* **deps:** update dependency org.apache.logging.log4j:log4j-slf4j2-impl to v2.24.0 ([#940](https://github.com/open-feature/java-sdk-contrib/issues/940)) ([5465337](https://github.com/open-feature/java-sdk-contrib/commit/546533739b453988720bb051d5e623ac7eb0b588)) +* fix pmd violations ([#856](https://github.com/open-feature/java-sdk-contrib/issues/856)) ([f10d872](https://github.com/open-feature/java-sdk-contrib/commit/f10d87205dd6a21222de362694d208fd293d9200)) +* update failing tests ([#732](https://github.com/open-feature/java-sdk-contrib/issues/732)) ([e1eaf4e](https://github.com/open-feature/java-sdk-contrib/commit/e1eaf4e3778d11ecf25d4276d3733760fa72eb9f)) + +## [0.0.4](https://github.com/open-feature/java-sdk-contrib/compare/dev.openfeature.contrib.providers.statsig-v0.0.3...dev.openfeature.contrib.providers.statsig-v0.0.4) (2024-02-27) + + +### 🐛 Bug Fixes + +* **deps:** update dependency com.statsig:serversdk to v1.12.1 ([#687](https://github.com/open-feature/java-sdk-contrib/issues/687)) ([91da6fe](https://github.com/open-feature/java-sdk-contrib/commit/91da6fee020bdf60d51e1a6849e94201a2b04dec)) + + +### ✨ New Features + +* Statsig provider evaluate boolean updates ([#691](https://github.com/open-feature/java-sdk-contrib/issues/691)) ([04df666](https://github.com/open-feature/java-sdk-contrib/commit/04df6669264227e3c3c6165ea0d876e5d8aa8766)) + + +### 🧹 Chore + +* **deps:** update dependency org.apache.logging.log4j:log4j-slf4j2-impl to v2.23.0 ([#689](https://github.com/open-feature/java-sdk-contrib/issues/689)) ([6589871](https://github.com/open-feature/java-sdk-contrib/commit/65898713166b5d02f246302c54fd7400ee4238d5)) + +## [0.0.3](https://github.com/open-feature/java-sdk-contrib/compare/dev.openfeature.contrib.providers.statsig-v0.0.2...dev.openfeature.contrib.providers.statsig-v0.0.3) (2024-02-14) + + +### 🐛 Bug Fixes + +* null exception when no options used ([#681](https://github.com/open-feature/java-sdk-contrib/issues/681)) ([19b96f8](https://github.com/open-feature/java-sdk-contrib/commit/19b96f8c8a62dbde3ed52177953d3e9fe9398912)) + +## [0.0.2](https://github.com/open-feature/java-sdk-contrib/compare/dev.openfeature.contrib.providers.statsig-v0.0.1...dev.openfeature.contrib.providers.statsig-v0.0.2) (2024-02-09) + + +### 🐛 Bug Fixes + +* **deps:** update dependency com.statsig:serversdk to v1.11.1 ([#658](https://github.com/open-feature/java-sdk-contrib/issues/658)) ([ad668ac](https://github.com/open-feature/java-sdk-contrib/commit/ad668acd81568f86c55d3a02f3678f7169631275)) +* **deps:** update dependency com.statsig:serversdk to v1.12.0 ([#671](https://github.com/open-feature/java-sdk-contrib/issues/671)) ([4bdfb15](https://github.com/open-feature/java-sdk-contrib/commit/4bdfb157f33a5b141e7c7b2a905b91db952d7611)) +* **deps:** update dependency org.slf4j:slf4j-api to v2.0.12 ([#661](https://github.com/open-feature/java-sdk-contrib/issues/661)) ([f03d933](https://github.com/open-feature/java-sdk-contrib/commit/f03d93305bda8ea932831e81db57c989ce4e14e4)) + + +### ✨ New Features + +* Add Statsig provider ([#641](https://github.com/open-feature/java-sdk-contrib/issues/641)) ([f814696](https://github.com/open-feature/java-sdk-contrib/commit/f814696463dd742ee30d1a1e5bdc196b6689447e)) + +## Changelog diff --git a/providers/multi-provider/lombok.config b/providers/multi-provider/lombok.config new file mode 100644 index 000000000..bcd1afdae --- /dev/null +++ b/providers/multi-provider/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/multi-provider/pom.xml b/providers/multi-provider/pom.xml new file mode 100644 index 000000000..d8dc00970 --- /dev/null +++ b/providers/multi-provider/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + dev.openfeature.contrib + parent + 0.1.0 + ../../pom.xml + + dev.openfeature.contrib.providers + multi-provider + 0.0.1 + + multi-provider + OpenFeature Multi-Provider + https://github.com/open-feature/java-sdk-contrib/tree/main/providers/multi-provider + + + + diff --git a/providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java b/providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java new file mode 100644 index 000000000..8d41ebbee --- /dev/null +++ b/providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java @@ -0,0 +1,16 @@ +package dev.openfeature.contrib.providers.multiprovider; + +import dev.openfeature.sdk.FeatureProvider; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Map; + +@AllArgsConstructor +public abstract class BaseStrategy implements Strategy { + + @Getter(AccessLevel.PROTECTED) + private final Map providers; + +} diff --git a/providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstSuccessfulStrategy.java b/providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstSuccessfulStrategy.java new file mode 100644 index 000000000..b7bd90e77 --- /dev/null +++ b/providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstSuccessfulStrategy.java @@ -0,0 +1,30 @@ +package dev.openfeature.contrib.providers.multiprovider; + +import dev.openfeature.sdk.FeatureProvider; +import dev.openfeature.sdk.ProviderEvaluation; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import static dev.openfeature.sdk.ErrorCode.GENERAL; + +public class FirstSuccessfulStrategy extends BaseStrategy{ + + public FirstSuccessfulStrategy(Map providers) { + super(providers); + } + + public ProviderEvaluation evaluate(Function> providerFunction) { + for (FeatureProvider provider: getProviders().values()) { + ProviderEvaluation result = providerFunction.apply(provider); + if (result.getErrorCode() != null) { + return result; + } + } + + return ProviderEvaluation.builder() + .reason(GENERAL.name()) + .build(); + } +} diff --git a/providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/MultiProvider.java b/providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/MultiProvider.java new file mode 100644 index 000000000..f1313bc6b --- /dev/null +++ b/providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/MultiProvider.java @@ -0,0 +1,110 @@ +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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import lombok.Getter; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Provider implementation for Statsig. + */ +@Slf4j +public class MultiProvider extends EventProvider { + + @Getter + private static final String NAME = "Multi-Provider"; + private static String metadataName; + private Map providers; + private Strategy strategy; + + /** + * constructor + * @param providers providers list + */ + public MultiProvider(List providers, Strategy strategy) { + this.providers = new HashMap<>(providers.size()); + for (FeatureProvider provider: providers) { + FeatureProvider prevProvider = this.providers.put(provider.getMetadata().getName(), provider); + if (prevProvider != null) { + throw new IllegalArgumentException("duplicated provider name: " + provider.getMetadata().getName()); + } + } + List providerNames = new ArrayList<>(providers.size()); + for (FeatureProvider provider: providers) { + providerNames.add(provider.getMetadata().getName()); + } + metadataName = NAME + "[" + StringUtils.join(providerNames, ",") + "]"; + if (strategy == null) { + this.strategy = new FirstMatchStrategy(this.providers); + } else { + this.strategy = strategy; + } + } + + /** + * Initialize the provider. + * @param evaluationContext evaluation context + * @throws Exception on error + */ + @Override + public void initialize(EvaluationContext evaluationContext) throws Exception { + for (FeatureProvider provider: providers.values()) { + provider.initialize(evaluationContext); + } + } + + @Override + public Metadata getMetadata() { + return () -> metadataName; + } + + @SneakyThrows + @Override + @SuppressFBWarnings(value = {"NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"}, justification = "reason can be null") + public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) { + return strategy.evaluate(p -> p.getBooleanEvaluation(key, defaultValue, ctx)); + } + + @Override + public ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) { + return strategy.evaluate(p -> p.getStringEvaluation(key, defaultValue, ctx)); + } + + @Override + public ProviderEvaluation getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) { + return strategy.evaluate(p -> p.getIntegerEvaluation(key, defaultValue, ctx)); + } + + @Override + public ProviderEvaluation getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) { + return strategy.evaluate(p -> p.getDoubleEvaluation(key, defaultValue, ctx)); + } + + @SneakyThrows + @Override + public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) { + return strategy.evaluate(p -> p.getObjectEvaluation(key, defaultValue, ctx)); + } + + @SneakyThrows + @Override + public void shutdown() { + log.info("shutdown"); + for (FeatureProvider provider: providers.values()) { + provider.shutdown(); + } + } + +} diff --git a/providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/Strategy.java b/providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/Strategy.java new file mode 100644 index 000000000..f2cfac87a --- /dev/null +++ b/providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/Strategy.java @@ -0,0 +1,10 @@ +package dev.openfeature.contrib.providers.multiprovider; + +import dev.openfeature.sdk.FeatureProvider; +import dev.openfeature.sdk.ProviderEvaluation; + +import java.util.function.Function; + +public interface Strategy { + ProviderEvaluation evaluate(Function> providerFunction); +} diff --git a/providers/multi-provider/src/test/resources/log4j2-test.xml b/providers/multi-provider/src/test/resources/log4j2-test.xml new file mode 100644 index 000000000..aced30f8a --- /dev/null +++ b/providers/multi-provider/src/test/resources/log4j2-test.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/providers/multi-provider/version.txt b/providers/multi-provider/version.txt new file mode 100644 index 000000000..8acdd82b7 --- /dev/null +++ b/providers/multi-provider/version.txt @@ -0,0 +1 @@ +0.0.1 diff --git a/release-please-config.json b/release-please-config.json index 964a4ee8b..15face0c0 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -100,6 +100,17 @@ "README.md" ] }, + "providers/multi-provider": { + "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", From cf290bf18c28c52f508431075eba1a44b0ff8b07 Mon Sep 17 00:00:00 2001 From: liran2000 Date: Sun, 20 Oct 2024 21:19:56 +0300 Subject: [PATCH 02/12] rename module as failing with - char Signed-off-by: liran2000 --- pom.xml | 227 +++++++++--------- .../CHANGELOG.md | 0 .../lombok.config | 0 .../{multi-provider => multiprovider}/pom.xml | 4 +- .../providers/multiprovider/BaseStrategy.java | 3 + .../FirstSuccessfulStrategy.java | 5 +- .../multiprovider/MultiProvider.java | 4 +- .../providers/multiprovider/Strategy.java | 3 + .../multiprovider/MultiProviderTest.java | 82 +++++++ .../src/test/resources/log4j2-test.xml | 0 .../version.txt | 0 11 files changed, 210 insertions(+), 118 deletions(-) rename providers/{multi-provider => multiprovider}/CHANGELOG.md (100%) rename providers/{multi-provider => multiprovider}/lombok.config (100%) rename providers/{multi-provider => multiprovider}/pom.xml (91%) rename providers/{multi-provider => multiprovider}/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java (93%) rename providers/{multi-provider => multiprovider}/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstSuccessfulStrategy.java (89%) rename providers/{multi-provider => multiprovider}/src/main/java/dev/openfeature/contrib/providers/multiprovider/MultiProvider.java (98%) rename providers/{multi-provider => multiprovider}/src/main/java/dev/openfeature/contrib/providers/multiprovider/Strategy.java (93%) create mode 100644 providers/multiprovider/src/test/java/dev/openfeature/contrib/providers/multiprovider/MultiProviderTest.java rename providers/{multi-provider => multiprovider}/src/test/resources/log4j2-test.xml (100%) rename providers/{multi-provider => multiprovider}/version.txt (100%) diff --git a/pom.xml b/pom.xml index 76a6c3b15..cd42f625b 100644 --- a/pom.xml +++ b/pom.xml @@ -27,17 +27,18 @@ - hooks/open-telemetry - tools/junit-openfeature - providers/flagd - providers/flagsmith - providers/go-feature-flag - providers/jsonlogic-eval-provider - providers/env-var - providers/unleash - providers/flipt - providers/configcat - providers/statsig + + + + + + + + + + + + providers/multiprovider @@ -53,7 +54,7 @@ UTF-8 UTF-8 ${groupId}.${artifactId} - true + false @@ -228,85 +229,85 @@ 3.13.0 - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.5.0 - - checkstyle.xml - UTF-8 - true - true - false - - - - com.puppycrawl.tools - checkstyle - 8.45.1 - - - - - validate - verify - - check - - - - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.25.0 - - ${basedir}/target/generated-sources/ - - - - run-pmd - verify - - check - - - - - - - com.github.spotbugs - spotbugs-maven-plugin - 4.8.6.4 - - spotbugs-exclusions.xml - - - com.h3xstream.findsecbugs - findsecbugs-plugin - 1.13.0 - - - - - - - com.github.spotbugs - spotbugs - 4.8.6 - - - - - run-spotbugs - verify - - check - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -356,28 +357,28 @@ - - org.apache.maven.plugins - maven-javadoc-plugin - 3.10.0 - - ${javadoc.failOnWarnings} - - **/GoFeatureFlagProviderOptions.java - - dev.openfeature.flagd.grpc,dev.openfeature.contrib.providers.gofeatureflag.exception,dev.openfeature.contrib.providers.gofeatureflag.bean - all,-missing - - - - attach-javadocs - verify - - jar - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/providers/multi-provider/CHANGELOG.md b/providers/multiprovider/CHANGELOG.md similarity index 100% rename from providers/multi-provider/CHANGELOG.md rename to providers/multiprovider/CHANGELOG.md diff --git a/providers/multi-provider/lombok.config b/providers/multiprovider/lombok.config similarity index 100% rename from providers/multi-provider/lombok.config rename to providers/multiprovider/lombok.config diff --git a/providers/multi-provider/pom.xml b/providers/multiprovider/pom.xml similarity index 91% rename from providers/multi-provider/pom.xml rename to providers/multiprovider/pom.xml index d8dc00970..90647decc 100644 --- a/providers/multi-provider/pom.xml +++ b/providers/multiprovider/pom.xml @@ -9,10 +9,10 @@ ../../pom.xml dev.openfeature.contrib.providers - multi-provider + multiprovider 0.0.1 - multi-provider + multiprovider OpenFeature Multi-Provider https://github.com/open-feature/java-sdk-contrib/tree/main/providers/multi-provider diff --git a/providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java similarity index 93% rename from providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java rename to providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java index 8d41ebbee..0c0c79890 100644 --- a/providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java +++ b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java @@ -7,6 +7,9 @@ import java.util.Map; +/** + * Base strategy. + */ @AllArgsConstructor public abstract class BaseStrategy implements Strategy { diff --git a/providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstSuccessfulStrategy.java b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstSuccessfulStrategy.java similarity index 89% rename from providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstSuccessfulStrategy.java rename to providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstSuccessfulStrategy.java index b7bd90e77..def98519b 100644 --- a/providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstSuccessfulStrategy.java +++ b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstSuccessfulStrategy.java @@ -9,7 +9,10 @@ import static dev.openfeature.sdk.ErrorCode.GENERAL; -public class FirstSuccessfulStrategy extends BaseStrategy{ +/** + * First Successful Strategy. + */ +public class FirstSuccessfulStrategy extends BaseStrategy { public FirstSuccessfulStrategy(Map providers) { super(providers); diff --git a/providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/MultiProvider.java b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/MultiProvider.java similarity index 98% rename from providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/MultiProvider.java rename to providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/MultiProvider.java index f1313bc6b..a048dad67 100644 --- a/providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/MultiProvider.java +++ b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/MultiProvider.java @@ -18,7 +18,7 @@ import java.util.Map; /** - * Provider implementation for Statsig. + * Provider implementation for Multi-provider. */ @Slf4j public class MultiProvider extends EventProvider { @@ -30,7 +30,7 @@ public class MultiProvider extends EventProvider { private Strategy strategy; /** - * constructor + * constructor. * @param providers providers list */ public MultiProvider(List providers, Strategy strategy) { diff --git a/providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/Strategy.java b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/Strategy.java similarity index 93% rename from providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/Strategy.java rename to providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/Strategy.java index f2cfac87a..ce8007ddb 100644 --- a/providers/multi-provider/src/main/java/dev/openfeature/contrib/providers/multiprovider/Strategy.java +++ b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/Strategy.java @@ -5,6 +5,9 @@ import java.util.function.Function; +/** + * strategy. + */ public interface Strategy { ProviderEvaluation evaluate(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..97fdf3193 --- /dev/null +++ b/providers/multiprovider/src/test/java/dev/openfeature/contrib/providers/multiprovider/MultiProviderTest.java @@ -0,0 +1,82 @@ +package dev.openfeature.contrib.providers.multiprovider; + +import dev.openfeature.sdk.FeatureProvider; +import dev.openfeature.sdk.ProviderEvaluation; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class MultiProviderTest { + + @BeforeEach + void setUp() { + } + + // Initialize MultiProvider with a valid list of FeatureProviders and a strategy + @Test + public void testInit() { + List providers = new ArrayList<>(2); + providers.add(mock(FeatureProvider.class)); + providers.add(mock(FeatureProvider.class)); + Strategy strategy = mock(Strategy.class); + MultiProvider multiProvider = new MultiProvider(providers, strategy); + + assertNotNull(multiProvider); + assertEquals("Multi-Provider[" + providers.get(0).getMetadata().getName() + "," + + providers.get(1).getMetadata().getName() + "]", multiProvider.getMetadata().getName()); + } + + @Test + public void testDuplicateProviderNames() { + FeatureProvider provider1 = mock(FeatureProvider.class); + FeatureProvider provider2 = mock(FeatureProvider.class); + when(provider1.getMetadata().getName()).thenReturn("provider"); + when(provider2.getMetadata().getName()).thenReturn("provider"); + + List providers = new ArrayList<>(2); + providers.add(provider1); + providers.add(provider2); + + assertThrows(IllegalArgumentException.class, () -> new MultiProvider(providers, null)); + } + + @Test + public void testRetrieveMetadataName() { + // Prepare + 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); + + // Verify + assertEquals("Multi-Provider[MockProvider]", multiProvider.getMetadata().getName()); + } + + @Test + public void testEvaluateBoolean() { + + // TODO test also with InMemoryProviders + +// List providers = new ArrayList<>(); +// FeatureProvider mockProvider1 = mock(FeatureProvider.class); +// when(mockProvider1.getBooleanEvaluation("key1", true, null)).thenReturn(ProviderEvaluation.builder().value(true).build()); +// FeatureProvider mockProvider2 = mock(FeatureProvider.class); +// when(mockProvider2.getBooleanEvaluation("key1", true, null)).thenReturn(ProviderEvaluation.of(false)); +// providers.add(mockProvider1); +// providers.add(mockProvider2); +// Strategy mockStrategy = mock(Strategy.class); +// when(mockStrategy.evaluate(any())).thenReturn(ProviderEvaluation.of(true)); +// MultiProvider multiProvider = new MultiProvider(providers, mockStrategy); +// +// assertTrue(multiProvider.getBooleanEvaluation("key1", true, null).getValue()); + } +} \ No newline at end of file diff --git a/providers/multi-provider/src/test/resources/log4j2-test.xml b/providers/multiprovider/src/test/resources/log4j2-test.xml similarity index 100% rename from providers/multi-provider/src/test/resources/log4j2-test.xml rename to providers/multiprovider/src/test/resources/log4j2-test.xml diff --git a/providers/multi-provider/version.txt b/providers/multiprovider/version.txt similarity index 100% rename from providers/multi-provider/version.txt rename to providers/multiprovider/version.txt From ecf3a1f1c91b5c8227adeca997086de71e0ee0d3 Mon Sep 17 00:00:00 2001 From: liran2000 Date: Mon, 21 Oct 2024 09:25:38 +0300 Subject: [PATCH 03/12] tests Signed-off-by: liran2000 --- .../multiprovider/FirstMatchStrategy.java | 43 ++++++ .../FirstSuccessfulStrategy.java | 15 ++- .../multiprovider/MultiProvider.java | 15 ++- .../multiprovider/MultiProviderTest.java | 127 ++++++++++++++---- 4 files changed, 167 insertions(+), 33 deletions(-) create mode 100644 providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstMatchStrategy.java 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..235ff535e --- /dev/null +++ b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/FirstMatchStrategy.java @@ -0,0 +1,43 @@ +package dev.openfeature.contrib.providers.multiprovider; + +import dev.openfeature.sdk.FeatureProvider; +import dev.openfeature.sdk.ProviderEvaluation; +import dev.openfeature.sdk.exceptions.FlagNotFoundError; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import static dev.openfeature.sdk.ErrorCode.FLAG_NOT_FOUND; + +/** + * First match strategy. + */ +public class FirstMatchStrategy extends BaseStrategy { + + public FirstMatchStrategy(Map providers) { + super(providers); + } + + /** + * 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 + */ + public ProviderEvaluation evaluate(Function> providerFunction) { + for (FeatureProvider provider: getProviders().values()) { + ProviderEvaluation result; + try { + result = providerFunction.apply(provider); + } catch (FlagNotFoundError e) { + continue; + } + return result; + } + + 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 index def98519b..620cb013d 100644 --- 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 @@ -2,6 +2,8 @@ import dev.openfeature.sdk.FeatureProvider; import dev.openfeature.sdk.ProviderEvaluation; +import dev.openfeature.sdk.exceptions.FlagNotFoundError; +import dev.openfeature.sdk.exceptions.GeneralError; import java.util.List; import java.util.Map; @@ -20,14 +22,15 @@ public FirstSuccessfulStrategy(Map providers) { public ProviderEvaluation evaluate(Function> providerFunction) { for (FeatureProvider provider: getProviders().values()) { - ProviderEvaluation result = providerFunction.apply(provider); - if (result.getErrorCode() != null) { - return result; + ProviderEvaluation result; + try { + result = providerFunction.apply(provider); + } catch (Exception e) { + continue; } + return result; } - return ProviderEvaluation.builder() - .reason(GENERAL.name()) - .build(); + 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 index a048dad67..7767c559d 100644 --- 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 @@ -29,6 +29,10 @@ public class MultiProvider extends EventProvider { private Map providers; private Strategy strategy; + public MultiProvider(List providers) { + this(providers, null); + } + /** * constructor. * @param providers providers list @@ -70,7 +74,6 @@ public Metadata getMetadata() { return () -> metadataName; } - @SneakyThrows @Override @SuppressFBWarnings(value = {"NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"}, justification = "reason can be null") public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) { @@ -92,7 +95,6 @@ public ProviderEvaluation getDoubleEvaluation(String key, Double default return strategy.evaluate(p -> p.getDoubleEvaluation(key, defaultValue, ctx)); } - @SneakyThrows @Override public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) { return strategy.evaluate(p -> p.getObjectEvaluation(key, defaultValue, ctx)); @@ -101,10 +103,15 @@ public ProviderEvaluation getObjectEvaluation(String key, Value defaultVa @SneakyThrows @Override public void shutdown() { - log.info("shutdown"); + log.debug("shutdown begin"); for (FeatureProvider provider: providers.values()) { - provider.shutdown(); + try { + provider.shutdown(); + } catch (Exception e) { + log.error("error shutdown provider", provider.getMetadata().getName()); + } } + log.debug("shutdown end"); } } 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 index 97fdf3193..e402b33aa 100644 --- 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 @@ -1,12 +1,19 @@ package dev.openfeature.contrib.providers.multiprovider; -import dev.openfeature.sdk.FeatureProvider; -import dev.openfeature.sdk.ProviderEvaluation; +import dev.openfeature.sdk.*; +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.BeforeEach; 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.function.Function; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; @@ -19,12 +26,16 @@ class MultiProviderTest { void setUp() { } - // Initialize MultiProvider with a valid list of FeatureProviders and a strategy @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(mock(FeatureProvider.class)); - providers.add(mock(FeatureProvider.class)); + providers.add(provider1); + providers.add(provider2); Strategy strategy = mock(Strategy.class); MultiProvider multiProvider = new MultiProvider(providers, strategy); @@ -37,8 +48,8 @@ public void testInit() { public void testDuplicateProviderNames() { FeatureProvider provider1 = mock(FeatureProvider.class); FeatureProvider provider2 = mock(FeatureProvider.class); - when(provider1.getMetadata().getName()).thenReturn("provider"); - when(provider2.getMetadata().getName()).thenReturn("provider"); + when(provider1.getMetadata()).thenReturn(() -> "provider"); + when(provider2.getMetadata()).thenReturn(() -> "provider"); List providers = new ArrayList<>(2); providers.add(provider1); @@ -61,22 +72,92 @@ public void testRetrieveMetadataName() { assertEquals("Multi-Provider[MockProvider]", multiProvider.getMetadata().getName()); } + @SneakyThrows @Test - public void testEvaluateBoolean() { - - // TODO test also with InMemoryProviders - -// List providers = new ArrayList<>(); -// FeatureProvider mockProvider1 = mock(FeatureProvider.class); -// when(mockProvider1.getBooleanEvaluation("key1", true, null)).thenReturn(ProviderEvaluation.builder().value(true).build()); -// FeatureProvider mockProvider2 = mock(FeatureProvider.class); -// when(mockProvider2.getBooleanEvaluation("key1", true, null)).thenReturn(ProviderEvaluation.of(false)); -// providers.add(mockProvider1); -// providers.add(mockProvider2); -// Strategy mockStrategy = mock(Strategy.class); -// when(mockStrategy.evaluate(any())).thenReturn(ProviderEvaluation.of(true)); -// MultiProvider multiProvider = new MultiProvider(providers, mockStrategy); -// -// assertTrue(multiProvider.getBooleanEvaluation("key1", true, null).getValue()); + public void testEvaluations() { + Map> flags1 = new HashMap<>(); + flags1.put("b1", Flag.builder().variant("true", true).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 () -> "InMemoryProvider1"; + } + }; + Map> flags2 = new HashMap<>(); + flags2.put("b1", Flag.builder().variant("true", true).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 () -> "InMemoryProvider2"; + } + }; + List providers = new ArrayList<>(2); + providers.add(provider1); + providers.add(provider2); + MultiProvider multiProvider = new MultiProvider(providers); + multiProvider.initialize(null); + + assertEquals(true, multiProvider.getBooleanEvaluation("b1", true, 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(); + Map providersMap = new HashMap<>(2); + providersMap.put("provider1", provider1); + providersMap.put("provider2", provider2); + multiProvider = new MultiProvider(providers, new FirstSuccessfulStrategy(providersMap)); + multiProvider.initialize(null); + + assertEquals(true, multiProvider.getBooleanEvaluation("b1", true, 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 BaseStrategy(providersMap) { +// @Override +// public ProviderEvaluation evaluate(Function> providerFunction) { +// providerFunction. +// } +// }; +// multiProvider = new MultiProvider(providers, customStrategy); +// multiProvider.initialize(null); } } \ No newline at end of file From dfd1c76312a87ffbec226cabc0d039c38c50549e Mon Sep 17 00:00:00 2001 From: liran2000 Date: Mon, 21 Oct 2024 11:20:00 +0300 Subject: [PATCH 04/12] updates Signed-off-by: liran2000 --- .../multiprovider/FirstMatchStrategy.java | 4 +- .../FirstSuccessfulStrategy.java | 4 +- .../multiprovider/MultiProvider.java | 21 +++---- .../providers/multiprovider/Strategy.java | 4 +- .../multiprovider/MultiProviderTest.java | 60 ++++++++++++------- 5 files changed, 55 insertions(+), 38 deletions(-) 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 index 235ff535e..4df3cfdba 100644 --- 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 @@ -1,5 +1,6 @@ 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; @@ -27,7 +28,8 @@ public FirstMatchStrategy(Map providers) { * @param ProviderEvaluation type * @return the provider evaluation */ - public ProviderEvaluation evaluate(Function> providerFunction) { + @Override + public ProviderEvaluation evaluate(String key, T defaultValue, EvaluationContext ctx, Function> providerFunction) { for (FeatureProvider provider: getProviders().values()) { ProviderEvaluation result; try { 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 index 620cb013d..9bc1d9b00 100644 --- 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 @@ -1,5 +1,6 @@ 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; @@ -20,7 +21,8 @@ public FirstSuccessfulStrategy(Map providers) { super(providers); } - public ProviderEvaluation evaluate(Function> providerFunction) { + @Override + public ProviderEvaluation evaluate(String key, T defaultValue, EvaluationContext ctx, Function> providerFunction) { for (FeatureProvider provider: getProviders().values()) { ProviderEvaluation result; try { 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 index 7767c559d..a454aeebc 100644 --- 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 @@ -1,11 +1,6 @@ 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.*; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import lombok.Getter; import lombok.SneakyThrows; @@ -13,7 +8,7 @@ import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -38,7 +33,7 @@ public MultiProvider(List providers) { * @param providers providers list */ public MultiProvider(List providers, Strategy strategy) { - this.providers = new HashMap<>(providers.size()); + this.providers = new LinkedHashMap<>(providers.size()); for (FeatureProvider provider: providers) { FeatureProvider prevProvider = this.providers.put(provider.getMetadata().getName(), provider); if (prevProvider != null) { @@ -77,27 +72,27 @@ public Metadata getMetadata() { @Override @SuppressFBWarnings(value = {"NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"}, justification = "reason can be null") public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) { - return strategy.evaluate(p -> p.getBooleanEvaluation(key, defaultValue, ctx)); + return strategy.evaluate(key, defaultValue, ctx, p -> p.getBooleanEvaluation(key, defaultValue, ctx)); } @Override public ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) { - return strategy.evaluate(p -> p.getStringEvaluation(key, defaultValue, ctx)); + return strategy.evaluate(key, defaultValue, ctx, p -> p.getStringEvaluation(key, defaultValue, ctx)); } @Override public ProviderEvaluation getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) { - return strategy.evaluate(p -> p.getIntegerEvaluation(key, defaultValue, ctx)); + return strategy.evaluate(key, defaultValue, ctx, p -> p.getIntegerEvaluation(key, defaultValue, ctx)); } @Override public ProviderEvaluation getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) { - return strategy.evaluate(p -> p.getDoubleEvaluation(key, defaultValue, ctx)); + return strategy.evaluate(key, defaultValue, ctx, p -> p.getDoubleEvaluation(key, defaultValue, ctx)); } @Override public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) { - return strategy.evaluate(p -> p.getObjectEvaluation(key, defaultValue, ctx)); + return strategy.evaluate(key, defaultValue, ctx, p -> p.getObjectEvaluation(key, defaultValue, ctx)); } @SneakyThrows 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 index ce8007ddb..7d170d5fa 100644 --- 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 @@ -1,5 +1,6 @@ package dev.openfeature.contrib.providers.multiprovider; +import dev.openfeature.sdk.EvaluationContext; import dev.openfeature.sdk.FeatureProvider; import dev.openfeature.sdk.ProviderEvaluation; @@ -9,5 +10,6 @@ * strategy. */ public interface Strategy { - ProviderEvaluation evaluate(Function> providerFunction); + ProviderEvaluation evaluate(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 index e402b33aa..051b84b15 100644 --- 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 @@ -9,14 +9,10 @@ import org.junit.jupiter.api.BeforeEach; 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.*; import java.util.function.Function; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -76,7 +72,8 @@ public void testRetrieveMetadataName() { @Test public void testEvaluations() { Map> flags1 = new HashMap<>(); - flags1.put("b1", Flag.builder().variant("true", true).defaultVariant("true").build()); + 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()); @@ -84,11 +81,12 @@ public void testEvaluations() { .defaultVariant("v").build()); InMemoryProvider provider1 = new InMemoryProvider(flags1) { public Metadata getMetadata() { - return () -> "InMemoryProvider1"; + return () -> "old-provider"; } }; Map> flags2 = new HashMap<>(); - flags2.put("b1", Flag.builder().variant("true", true).defaultVariant("false").build()); + 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()); @@ -98,7 +96,7 @@ public Metadata getMetadata() { flags2.put("s2", Flag.builder().variant("v", "s2str2").defaultVariant("v").build()); InMemoryProvider provider2 = new InMemoryProvider(flags2) { public Metadata getMetadata() { - return () -> "InMemoryProvider2"; + return () -> "new-provider"; } }; List providers = new ArrayList<>(2); @@ -107,7 +105,7 @@ public Metadata getMetadata() { MultiProvider multiProvider = new MultiProvider(providers); multiProvider.initialize(null); - assertEquals(true, multiProvider.getBooleanEvaluation("b1", true, null) + assertEquals(true, multiProvider.getBooleanEvaluation("b1", false, null) .getValue()); assertEquals(1, multiProvider.getIntegerEvaluation("i1", 0, null) .getValue()); @@ -126,13 +124,13 @@ public Metadata getMetadata() { }); multiProvider.shutdown(); - Map providersMap = new HashMap<>(2); - providersMap.put("provider1", provider1); - providersMap.put("provider2", provider2); + Map providersMap = new LinkedHashMap<>(2); + providersMap.put(provider1.getMetadata().getName(), provider1); + providersMap.put(provider2.getMetadata().getName(), provider2); multiProvider = new MultiProvider(providers, new FirstSuccessfulStrategy(providersMap)); multiProvider.initialize(null); - assertEquals(true, multiProvider.getBooleanEvaluation("b1", true, null) + assertEquals(true, multiProvider.getBooleanEvaluation("b1", false, null) .getValue()); assertEquals(1, multiProvider.getIntegerEvaluation("i1", 0, null) .getValue()); @@ -151,13 +149,31 @@ public Metadata getMetadata() { }); multiProvider.shutdown(); -// Strategy customStrategy = new BaseStrategy(providersMap) { -// @Override -// public ProviderEvaluation evaluate(Function> providerFunction) { -// providerFunction. -// } -// }; -// multiProvider = new MultiProvider(providers, customStrategy); -// multiProvider.initialize(null); + Map finalProvidersMap = providersMap; + Strategy customStrategy = new BaseStrategy(finalProvidersMap) { + FirstMatchStrategy fallbackStrategy = new FirstMatchStrategy(finalProvidersMap); + @Override + public ProviderEvaluation evaluate(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(finalProvidersMap.get("new-provider")); + } + return fallbackStrategy.evaluate(key, defaultValue, ctx, providerFunction); + } + }; + providersMap = new LinkedHashMap<>(2); + providersMap.put(provider1.getMetadata().getName(), provider1); + providersMap.put(provider2.getMetadata().getName(), provider2); + 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 From 651bcce9699551b68cf1e59ef0a5f1ff867a35e4 Mon Sep 17 00:00:00 2001 From: liran2000 Date: Mon, 21 Oct 2024 14:44:31 +0300 Subject: [PATCH 05/12] updates 2 Signed-off-by: liran2000 --- providers/multiprovider/README.md | 103 ++++++++++++++++++ providers/multiprovider/pom.xml | 11 ++ .../multiprovider/FirstMatchStrategy.java | 21 ++-- .../FirstSuccessfulStrategy.java | 18 +-- .../multiprovider/MultiProvider.java | 50 +++++---- .../multiprovider/MultiProviderTest.java | 46 +++++--- 6 files changed, 196 insertions(+), 53 deletions(-) create mode 100644 providers/multiprovider/README.md diff --git a/providers/multiprovider/README.md b/providers/multiprovider/README.md new file mode 100644 index 000000000..c8763af36 --- /dev/null +++ b/providers/multiprovider/README.md @@ -0,0 +1,103 @@ +# 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 or not 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 + +// notice LinkedHashMap is used, since order is important +Map providersMap = new LinkedHashMap<>(2); + +providersMap.put(provider1.getMetadata().getName(), provider1); +providersMap.put(provider2.getMetadata().getName(), provider2); +multiProvider = new MultiProvider(providers, new FirstSuccessfulStrategy(providersMap)); +... +``` + +See [MultiProviderTest](./src/test/java/dev/openfeature/contrib/providers/multiprovider/MultiProviderTest.java) +for more information. + diff --git a/providers/multiprovider/pom.xml b/providers/multiprovider/pom.xml index 90647decc..63422321d 100644 --- a/providers/multiprovider/pom.xml +++ b/providers/multiprovider/pom.xml @@ -17,5 +17,16 @@ https://github.com/open-feature/java-sdk-contrib/tree/main/providers/multi-provider + + 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 index 4df3cfdba..49cc75548 100644 --- 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 @@ -4,16 +4,22 @@ import dev.openfeature.sdk.FeatureProvider; import dev.openfeature.sdk.ProviderEvaluation; import dev.openfeature.sdk.exceptions.FlagNotFoundError; +import lombok.extern.slf4j.Slf4j; -import java.util.List; 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 public class FirstMatchStrategy extends BaseStrategy { public FirstMatchStrategy(Map providers) { @@ -29,15 +35,14 @@ public FirstMatchStrategy(Map providers) { * @return the provider evaluation */ @Override - public ProviderEvaluation evaluate(String key, T defaultValue, EvaluationContext ctx, Function> providerFunction) { + public ProviderEvaluation evaluate(String key, T defaultValue, EvaluationContext ctx, + Function> providerFunction) { for (FeatureProvider provider: getProviders().values()) { - ProviderEvaluation result; try { - result = providerFunction.apply(provider); + return providerFunction.apply(provider); } catch (FlagNotFoundError e) { - continue; + log.debug("flag not found {}", e.getMessage()); } - return result; } 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 index 9bc1d9b00..7030e7df1 100644 --- 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 @@ -3,18 +3,19 @@ import dev.openfeature.sdk.EvaluationContext; import dev.openfeature.sdk.FeatureProvider; import dev.openfeature.sdk.ProviderEvaluation; -import dev.openfeature.sdk.exceptions.FlagNotFoundError; import dev.openfeature.sdk.exceptions.GeneralError; +import lombok.extern.slf4j.Slf4j; -import java.util.List; import java.util.Map; import java.util.function.Function; -import static dev.openfeature.sdk.ErrorCode.GENERAL; - /** * 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 public class FirstSuccessfulStrategy extends BaseStrategy { public FirstSuccessfulStrategy(Map providers) { @@ -22,15 +23,14 @@ public FirstSuccessfulStrategy(Map providers) { } @Override - public ProviderEvaluation evaluate(String key, T defaultValue, EvaluationContext ctx, Function> providerFunction) { + public ProviderEvaluation evaluate(String key, T defaultValue, EvaluationContext ctx, + Function> providerFunction) { for (FeatureProvider provider: getProviders().values()) { - ProviderEvaluation result; try { - result = providerFunction.apply(provider); + return providerFunction.apply(provider); } catch (Exception e) { - continue; + log.debug("flag not found {}", e.getMessage()); } - return result; } 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 index a454aeebc..e7c174672 100644 --- 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 @@ -1,13 +1,15 @@ package dev.openfeature.contrib.providers.multiprovider; -import dev.openfeature.sdk.*; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +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 lombok.Getter; -import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; +import org.json.JSONObject; -import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -19,32 +21,42 @@ public class MultiProvider extends EventProvider { @Getter - private static final String NAME = "Multi-Provider"; - private static String metadataName; - private Map providers; - private Strategy strategy; + private static final String NAME = "multiprovider"; + 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); } /** - * constructor. - * @param providers providers list + * 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 = new LinkedHashMap<>(providers.size()); + JSONObject json = new JSONObject(); + json.put("name", NAME); + JSONObject providersMetadata = new JSONObject(); + json.put("originalMetadata", providersMetadata); for (FeatureProvider provider: providers) { FeatureProvider prevProvider = this.providers.put(provider.getMetadata().getName(), provider); if (prevProvider != null) { - throw new IllegalArgumentException("duplicated provider name: " + provider.getMetadata().getName()); + log.warn("duplicated provider name: {}", provider.getMetadata().getName()); } + JSONObject providerMetadata = new JSONObject(); + providerMetadata.put("name", provider.getMetadata().getName()); + providersMetadata.put(provider.getMetadata().getName(), providerMetadata); } - List providerNames = new ArrayList<>(providers.size()); - for (FeatureProvider provider: providers) { - providerNames.add(provider.getMetadata().getName()); - } - metadataName = NAME + "[" + StringUtils.join(providerNames, ",") + "]"; + metadataName = json.toString(); if (strategy == null) { this.strategy = new FirstMatchStrategy(this.providers); } else { @@ -70,7 +82,6 @@ public Metadata getMetadata() { } @Override - @SuppressFBWarnings(value = {"NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"}, justification = "reason can be null") public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) { return strategy.evaluate(key, defaultValue, ctx, p -> p.getBooleanEvaluation(key, defaultValue, ctx)); } @@ -95,7 +106,6 @@ public ProviderEvaluation getObjectEvaluation(String key, Value defaultVa return strategy.evaluate(key, defaultValue, ctx, p -> p.getObjectEvaluation(key, defaultValue, ctx)); } - @SneakyThrows @Override public void shutdown() { log.debug("shutdown begin"); @@ -103,7 +113,7 @@ public void shutdown() { try { provider.shutdown(); } catch (Exception e) { - log.error("error shutdown provider", provider.getMetadata().getName()); + log.error("error shutdown provider {}", provider.getMetadata().getName(), e); } } log.debug("shutdown end"); 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 index 051b84b15..424293608 100644 --- 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 @@ -1,6 +1,11 @@ package dev.openfeature.contrib.providers.multiprovider; -import dev.openfeature.sdk.*; +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; @@ -9,10 +14,17 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.function.Function; -import static org.junit.jupiter.api.Assertions.*; +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.Mockito.mock; import static org.mockito.Mockito.when; @@ -22,6 +34,7 @@ class MultiProviderTest { void setUp() { } + @SneakyThrows @Test public void testInit() { FeatureProvider provider1 = mock(FeatureProvider.class); @@ -34,10 +47,12 @@ public void testInit() { providers.add(provider2); Strategy strategy = mock(Strategy.class); MultiProvider multiProvider = new MultiProvider(providers, strategy); + multiProvider.initialize(null); assertNotNull(multiProvider); - assertEquals("Multi-Provider[" + providers.get(0).getMetadata().getName() + "," + - providers.get(1).getMetadata().getName() + "]", multiProvider.getMetadata().getName()); + assertEquals("{\"originalMetadata\":{\"provider1\":{\"name\":\"provider1\"}," + + "\"provider2\":{\"name\":\"provider2\"}},\"name\":\"multiprovider\"}", + multiProvider.getMetadata().getName()); } @Test @@ -51,21 +66,22 @@ public void testDuplicateProviderNames() { providers.add(provider1); providers.add(provider2); - assertThrows(IllegalArgumentException.class, () -> new MultiProvider(providers, null)); + assertDoesNotThrow(() -> new MultiProvider(providers, null).initialize(null)); } + @SneakyThrows @Test public void testRetrieveMetadataName() { - // Prepare 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); - // Verify - assertEquals("Multi-Provider[MockProvider]", multiProvider.getMetadata().getName()); + assertEquals("{\"originalMetadata\":{\"MockProvider\":{\"name\":\"MockProvider\"}}," + + "\"name\":\"multiprovider\"}", multiProvider.getMetadata().getName()); } @SneakyThrows @@ -119,9 +135,8 @@ public Metadata getMetadata() { assertEquals("s2str2", multiProvider.getStringEvaluation("s2", "", null) .getValue()); MultiProvider finalMultiProvider1 = multiProvider; - assertThrows(FlagNotFoundError.class, () -> { - finalMultiProvider1.getStringEvaluation("non-existing", "", null); - }); + assertThrows(FlagNotFoundError.class, () -> + finalMultiProvider1.getStringEvaluation("non-existing", "", null)); multiProvider.shutdown(); Map providersMap = new LinkedHashMap<>(2); @@ -144,14 +159,13 @@ public Metadata getMetadata() { assertEquals("s2str2", multiProvider.getStringEvaluation("s2", "", null) .getValue()); MultiProvider finalMultiProvider2 = multiProvider; - assertThrows(GeneralError.class, () -> { - finalMultiProvider2.getStringEvaluation("non-existing", "", null); - }); + assertThrows(GeneralError.class, () -> + finalMultiProvider2.getStringEvaluation("non-existing", "", null)); multiProvider.shutdown(); Map finalProvidersMap = providersMap; Strategy customStrategy = new BaseStrategy(finalProvidersMap) { - FirstMatchStrategy fallbackStrategy = new FirstMatchStrategy(finalProvidersMap); + final FirstMatchStrategy fallbackStrategy = new FirstMatchStrategy(finalProvidersMap); @Override public ProviderEvaluation evaluate(String key, T defaultValue, EvaluationContext ctx, Function> providerFunction) { Value contextProvider = null; From dbf0c5cd63c4d005a481d83ecbc112ec1a554c02 Mon Sep 17 00:00:00 2001 From: liran2000 Date: Mon, 21 Oct 2024 14:51:18 +0300 Subject: [PATCH 06/12] updates 3 Signed-off-by: liran2000 --- pom.xml | 226 +++++++++--------- .../multiprovider/MultiProvider.java | 24 +- 2 files changed, 125 insertions(+), 125 deletions(-) diff --git a/pom.xml b/pom.xml index cd42f625b..ecd16528d 100644 --- a/pom.xml +++ b/pom.xml @@ -27,17 +27,17 @@ - - - - - - - - - - - + hooks/open-telemetry + tools/junit-openfeature + providers/flagd + providers/flagsmith + providers/go-feature-flag + providers/jsonlogic-eval-provider + providers/env-var + providers/unleash + providers/flipt + providers/configcat + providers/statsig providers/multiprovider @@ -54,7 +54,7 @@ UTF-8 UTF-8 ${groupId}.${artifactId} - false + true @@ -229,85 +229,85 @@ 3.13.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.5.0 + + checkstyle.xml + UTF-8 + true + true + false + + + + com.puppycrawl.tools + checkstyle + 8.45.1 + + + + + validate + verify + + check + + + + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.25.0 + + ${basedir}/target/generated-sources/ + + + + run-pmd + verify + + check + + + + + + + com.github.spotbugs + spotbugs-maven-plugin + 4.8.6.4 + + spotbugs-exclusions.xml + + + com.h3xstream.findsecbugs + findsecbugs-plugin + 1.13.0 + + + + + + + com.github.spotbugs + spotbugs + 4.8.6 + + + + + run-spotbugs + verify + + check + + + + @@ -357,28 +357,28 @@ - - - - - - - - - - - - - - - - - - - - - - + + org.apache.maven.plugins + maven-javadoc-plugin + 3.10.0 + + ${javadoc.failOnWarnings} + + **/GoFeatureFlagProviderOptions.java + + dev.openfeature.flagd.grpc,dev.openfeature.contrib.providers.gofeatureflag.exception,dev.openfeature.contrib.providers.gofeatureflag.bean + all,-missing + + + + attach-javadocs + verify + + jar + + + + 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 index e7c174672..25f3a78ed 100644 --- 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 @@ -23,7 +23,7 @@ public class MultiProvider extends EventProvider { @Getter private static final String NAME = "multiprovider"; private final Map providers; - private final Strategy strategy; + private Strategy strategy; private String metadataName; /** @@ -43,24 +43,16 @@ public MultiProvider(List providers) { */ public MultiProvider(List providers, Strategy strategy) { this.providers = new LinkedHashMap<>(providers.size()); - JSONObject json = new JSONObject(); - json.put("name", NAME); - JSONObject providersMetadata = new JSONObject(); - json.put("originalMetadata", providersMetadata); for (FeatureProvider provider: providers) { FeatureProvider prevProvider = this.providers.put(provider.getMetadata().getName(), provider); if (prevProvider != null) { log.warn("duplicated provider name: {}", provider.getMetadata().getName()); } - JSONObject providerMetadata = new JSONObject(); - providerMetadata.put("name", provider.getMetadata().getName()); - providersMetadata.put(provider.getMetadata().getName(), providerMetadata); } - metadataName = json.toString(); - if (strategy == null) { - this.strategy = new FirstMatchStrategy(this.providers); - } else { + if (strategy != null) { this.strategy = strategy; + } else { + this.strategy = new FirstMatchStrategy(this.providers); } } @@ -71,9 +63,17 @@ public MultiProvider(List providers, Strategy strategy) { */ @Override public void initialize(EvaluationContext evaluationContext) throws Exception { + JSONObject json = new JSONObject(); + json.put("name", NAME); + JSONObject providersMetadata = new JSONObject(); + json.put("originalMetadata", providersMetadata); for (FeatureProvider provider: providers.values()) { provider.initialize(evaluationContext); + JSONObject providerMetadata = new JSONObject(); + providerMetadata.put("name", provider.getMetadata().getName()); + providersMetadata.put(provider.getMetadata().getName(), providerMetadata); } + metadataName = json.toString(); } @Override From daeefe93d86910065bbdcd38f641d0d353c4c506 Mon Sep 17 00:00:00 2001 From: liran2000 Date: Mon, 21 Oct 2024 16:28:58 +0300 Subject: [PATCH 07/12] init updates Signed-off-by: liran2000 --- .../providers/multiprovider/BaseStrategy.java | 11 +++++++++- .../multiprovider/FirstMatchStrategy.java | 10 ++++++++-- .../FirstSuccessfulStrategy.java | 10 +++++++--- .../multiprovider/MultiProvider.java | 20 ++++++++++++------- .../multiprovider/MultiProviderTest.java | 15 ++++---------- 5 files changed, 42 insertions(+), 24 deletions(-) diff --git a/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java index 0c0c79890..4a5a3d71f 100644 --- a/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java +++ b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java @@ -4,16 +4,25 @@ import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import static dev.openfeature.contrib.providers.multiprovider.MultiProvider.buildProviders; + /** * Base strategy. */ -@AllArgsConstructor +@Slf4j public abstract class BaseStrategy implements Strategy { @Getter(AccessLevel.PROTECTED) private final Map providers; + public BaseStrategy(List providers) { + this.providers = buildProviders(providers); + } + } 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 index 49cc75548..847793778 100644 --- 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 @@ -6,9 +6,12 @@ import dev.openfeature.sdk.exceptions.FlagNotFoundError; import lombok.extern.slf4j.Slf4j; +import java.util.List; 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 @@ -22,7 +25,7 @@ @Slf4j public class FirstMatchStrategy extends BaseStrategy { - public FirstMatchStrategy(Map providers) { + public FirstMatchStrategy(List providers) { super(providers); } @@ -39,7 +42,10 @@ public ProviderEvaluation evaluate(String key, T defaultValue, Evaluation Function> providerFunction) { for (FeatureProvider provider: getProviders().values()) { try { - return providerFunction.apply(provider); + ProviderEvaluation res = providerFunction.apply(provider); + if (!FLAG_NOT_FOUND.equals(res.getErrorCode())) { + return res; + } } catch (FlagNotFoundError e) { log.debug("flag not found {}", e.getMessage()); } 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 index 7030e7df1..70116e12b 100644 --- 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 @@ -6,6 +6,7 @@ import dev.openfeature.sdk.exceptions.GeneralError; import lombok.extern.slf4j.Slf4j; +import java.util.List; import java.util.Map; import java.util.function.Function; @@ -18,7 +19,7 @@ @Slf4j public class FirstSuccessfulStrategy extends BaseStrategy { - public FirstSuccessfulStrategy(Map providers) { + public FirstSuccessfulStrategy(List providers) { super(providers); } @@ -27,9 +28,12 @@ public ProviderEvaluation evaluate(String key, T defaultValue, Evaluation Function> providerFunction) { for (FeatureProvider provider: getProviders().values()) { try { - return providerFunction.apply(provider); + ProviderEvaluation res = providerFunction.apply(provider); + if (res.getErrorCode() == null) { + return res; + } } catch (Exception e) { - log.debug("flag not found {}", e.getMessage()); + log.debug("evaluation exception {}", e.getMessage()); } } 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 index 25f3a78ed..824645055 100644 --- 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 @@ -10,6 +10,7 @@ import lombok.extern.slf4j.Slf4j; import org.json.JSONObject; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -42,18 +43,23 @@ public MultiProvider(List providers) { * @param strategy the strategy */ public MultiProvider(List providers, Strategy strategy) { - this.providers = new LinkedHashMap<>(providers.size()); + this.providers = buildProviders(providers); + if (strategy != null) { + this.strategy = strategy; + } else { + this.strategy = new FirstMatchStrategy(providers); + } + } + + protected static Map buildProviders(List providers) { + Map providersMap = new LinkedHashMap<>(providers.size()); for (FeatureProvider provider: providers) { - FeatureProvider prevProvider = this.providers.put(provider.getMetadata().getName(), provider); + FeatureProvider prevProvider = providersMap.put(provider.getMetadata().getName(), provider); if (prevProvider != null) { log.warn("duplicated provider name: {}", provider.getMetadata().getName()); } } - if (strategy != null) { - this.strategy = strategy; - } else { - this.strategy = new FirstMatchStrategy(this.providers); - } + return Collections.unmodifiableMap(providersMap); } /** 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 index 424293608..f48ef5392 100644 --- 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 @@ -139,10 +139,7 @@ public Metadata getMetadata() { finalMultiProvider1.getStringEvaluation("non-existing", "", null)); multiProvider.shutdown(); - Map providersMap = new LinkedHashMap<>(2); - providersMap.put(provider1.getMetadata().getName(), provider1); - providersMap.put(provider2.getMetadata().getName(), provider2); - multiProvider = new MultiProvider(providers, new FirstSuccessfulStrategy(providersMap)); + multiProvider = new MultiProvider(providers, new FirstSuccessfulStrategy(providers)); multiProvider.initialize(null); assertEquals(true, multiProvider.getBooleanEvaluation("b1", false, null) @@ -163,9 +160,8 @@ public Metadata getMetadata() { finalMultiProvider2.getStringEvaluation("non-existing", "", null)); multiProvider.shutdown(); - Map finalProvidersMap = providersMap; - Strategy customStrategy = new BaseStrategy(finalProvidersMap) { - final FirstMatchStrategy fallbackStrategy = new FirstMatchStrategy(finalProvidersMap); + Strategy customStrategy = new BaseStrategy(providers) { + final FirstMatchStrategy fallbackStrategy = new FirstMatchStrategy(providers); @Override public ProviderEvaluation evaluate(String key, T defaultValue, EvaluationContext ctx, Function> providerFunction) { Value contextProvider = null; @@ -173,14 +169,11 @@ public ProviderEvaluation evaluate(String key, T defaultValue, Evaluation contextProvider = ctx.getValue("provider"); } if (contextProvider != null && "new-provider".equals(contextProvider.asString())) { - return providerFunction.apply(finalProvidersMap.get("new-provider")); + return providerFunction.apply(getProviders().get("new-provider")); } return fallbackStrategy.evaluate(key, defaultValue, ctx, providerFunction); } }; - providersMap = new LinkedHashMap<>(2); - providersMap.put(provider1.getMetadata().getName(), provider1); - providersMap.put(provider2.getMetadata().getName(), provider2); multiProvider = new MultiProvider(providers, customStrategy); multiProvider.initialize(null); From eabbe738f74b2e199809b1302a5fb4b69a09d430 Mon Sep 17 00:00:00 2001 From: liran2000 Date: Mon, 21 Oct 2024 16:58:43 +0300 Subject: [PATCH 08/12] updates Signed-off-by: liran2000 --- .../contrib/providers/multiprovider/BaseStrategy.java | 2 -- .../contrib/providers/multiprovider/FirstMatchStrategy.java | 1 - .../providers/multiprovider/FirstSuccessfulStrategy.java | 1 - 3 files changed, 4 deletions(-) diff --git a/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java index 4a5a3d71f..179bc8760 100644 --- a/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java +++ b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java @@ -2,11 +2,9 @@ import dev.openfeature.sdk.FeatureProvider; import lombok.AccessLevel; -import lombok.AllArgsConstructor; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; 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 index 847793778..0a06cb702 100644 --- 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 @@ -7,7 +7,6 @@ import lombok.extern.slf4j.Slf4j; import java.util.List; -import java.util.Map; import java.util.function.Function; import static dev.openfeature.sdk.ErrorCode.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 index 70116e12b..f00af2447 100644 --- 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 @@ -7,7 +7,6 @@ import lombok.extern.slf4j.Slf4j; import java.util.List; -import java.util.Map; import java.util.function.Function; /** From 154e846de87d0a1040d4bb7c93f5d89c2ca5fcc0 Mon Sep 17 00:00:00 2001 From: liran2000 Date: Mon, 21 Oct 2024 18:41:34 +0300 Subject: [PATCH 09/12] naming updates Signed-off-by: liran2000 --- .github/component_owners.yml | 2 +- .release-please-manifest.json | 2 +- providers/multiprovider/CHANGELOG.md | 81 ---------------------------- providers/multiprovider/pom.xml | 2 +- release-please-config.json | 2 +- 5 files changed, 4 insertions(+), 85 deletions(-) delete mode 100644 providers/multiprovider/CHANGELOG.md diff --git a/.github/component_owners.yml b/.github/component_owners.yml index 572147df8..1f42662bc 100644 --- a/.github/component_owners.yml +++ b/.github/component_owners.yml @@ -35,7 +35,7 @@ components: - novalisdenahi providers/statsig: - liran2000 - providers/multi-provider: + providers/multiprovider: - liran2000 ignored-authors: diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 71a32b611..69acedd13 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -9,6 +9,6 @@ "providers/flipt": "0.1.1", "providers/configcat": "0.1.0", "providers/statsig": "0.1.0", - "providers/multi-provider": "0.0.1", + "providers/multiprovider": "0.0.1", "tools/junit-openfeature": "0.1.1" } diff --git a/providers/multiprovider/CHANGELOG.md b/providers/multiprovider/CHANGELOG.md deleted file mode 100644 index ef5c266cf..000000000 --- a/providers/multiprovider/CHANGELOG.md +++ /dev/null @@ -1,81 +0,0 @@ -# Changelog - -## [0.1.0](https://github.com/open-feature/java-sdk-contrib/compare/dev.openfeature.contrib.providers.statsig-v0.0.4...dev.openfeature.contrib.providers.statsig-v0.1.0) (2024-09-25) - - -### ⚠ BREAKING CHANGES - -* use sdk-maintained state, require 1.12 ([#964](https://github.com/open-feature/java-sdk-contrib/issues/964)) - -### 🐛 Bug Fixes - -* **deps:** update dependency com.statsig:serversdk to v1.13.0 ([#699](https://github.com/open-feature/java-sdk-contrib/issues/699)) ([f5c38cf](https://github.com/open-feature/java-sdk-contrib/commit/f5c38cf832e6d4970b3076811ac5b6c2b7da86ee)) -* **deps:** update dependency com.statsig:serversdk to v1.13.1 ([#704](https://github.com/open-feature/java-sdk-contrib/issues/704)) ([5d35725](https://github.com/open-feature/java-sdk-contrib/commit/5d357255f10f6608a51bf253bac5dc999fef5a83)) -* **deps:** update dependency com.statsig:serversdk to v1.14.0 ([#718](https://github.com/open-feature/java-sdk-contrib/issues/718)) ([7f76265](https://github.com/open-feature/java-sdk-contrib/commit/7f76265b4f610a171bd5bd239941cb1d1420ab55)) -* **deps:** update dependency com.statsig:serversdk to v1.15.0 ([#728](https://github.com/open-feature/java-sdk-contrib/issues/728)) ([f101e2f](https://github.com/open-feature/java-sdk-contrib/commit/f101e2f6d5b5d9486bc5b426a1f05c71b70d658c)) -* **deps:** update dependency com.statsig:serversdk to v1.16.0 ([#754](https://github.com/open-feature/java-sdk-contrib/issues/754)) ([c8e5cb6](https://github.com/open-feature/java-sdk-contrib/commit/c8e5cb66305db5b076ed4ba5f1a6d1a60b0115f3)) -* **deps:** update dependency com.statsig:serversdk to v1.17.0 ([#758](https://github.com/open-feature/java-sdk-contrib/issues/758)) ([e0c7a26](https://github.com/open-feature/java-sdk-contrib/commit/e0c7a266835ab41e9298cd9b726f696bcac21527)) -* **deps:** update dependency com.statsig:serversdk to v1.17.1 ([#768](https://github.com/open-feature/java-sdk-contrib/issues/768)) ([40d4492](https://github.com/open-feature/java-sdk-contrib/commit/40d4492372c44abccf45f03a3ec499b3727b6d0a)) -* **deps:** update dependency com.statsig:serversdk to v1.17.2 ([#770](https://github.com/open-feature/java-sdk-contrib/issues/770)) ([1da0305](https://github.com/open-feature/java-sdk-contrib/commit/1da0305d8daf35fc64d0864bf9839ac1afe88bf4)) -* **deps:** update dependency com.statsig:serversdk to v1.17.3 ([#774](https://github.com/open-feature/java-sdk-contrib/issues/774)) ([fa56579](https://github.com/open-feature/java-sdk-contrib/commit/fa56579fdebe99cbc5415e95585ba791b4e1b247)) -* **deps:** update dependency com.statsig:serversdk to v1.18.0 ([#799](https://github.com/open-feature/java-sdk-contrib/issues/799)) ([bfbdef8](https://github.com/open-feature/java-sdk-contrib/commit/bfbdef8cdfe6bdbb1015ffc65354f852b3889fa5)) -* **deps:** update dependency com.statsig:serversdk to v1.18.1 ([#806](https://github.com/open-feature/java-sdk-contrib/issues/806)) ([17c61c3](https://github.com/open-feature/java-sdk-contrib/commit/17c61c32d5669ef19e886230b95c4028e9d87d0c)) -* **deps:** update dependency com.statsig:serversdk to v1.22.0 ([#822](https://github.com/open-feature/java-sdk-contrib/issues/822)) ([a560308](https://github.com/open-feature/java-sdk-contrib/commit/a560308452ea737b41b9d19d1d0942dffa7f9e51)) -* **deps:** update dependency org.slf4j:slf4j-api to v2.0.13 ([#752](https://github.com/open-feature/java-sdk-contrib/issues/752)) ([b820fcf](https://github.com/open-feature/java-sdk-contrib/commit/b820fcf1b7ea945a8e450dcc90addb82f5fb865d)) -* **deps:** update dependency org.slf4j:slf4j-api to v2.0.14 ([#904](https://github.com/open-feature/java-sdk-contrib/issues/904)) ([028b332](https://github.com/open-feature/java-sdk-contrib/commit/028b332dc8ac3b134e5453d5449a4c11b4ef250a)) -* **deps:** update dependency org.slf4j:slf4j-api to v2.0.15 ([#910](https://github.com/open-feature/java-sdk-contrib/issues/910)) ([2f58638](https://github.com/open-feature/java-sdk-contrib/commit/2f58638eb4907c948325d1e61853e1b6eabfa4c1)) -* **deps:** update dependency org.slf4j:slf4j-api to v2.0.16 ([#912](https://github.com/open-feature/java-sdk-contrib/issues/912)) ([52571d8](https://github.com/open-feature/java-sdk-contrib/commit/52571d806e7c547006db836245b4895fe9bc4660)) - - -### ✨ New Features - -* add support for getBooleanEvaluation with default value ([#722](https://github.com/open-feature/java-sdk-contrib/issues/722)) ([835f672](https://github.com/open-feature/java-sdk-contrib/commit/835f6727d98883bb7fc351b5dd59039228fbcb2b)) -* use sdk-maintained state, require 1.12 ([#964](https://github.com/open-feature/java-sdk-contrib/issues/964)) ([4a041b0](https://github.com/open-feature/java-sdk-contrib/commit/4a041b0dda9c4e460f4c2199f3bc680df0dda621)) - - -### 🧹 Chore - -* **deps:** update dependency org.apache.logging.log4j:log4j-slf4j2-impl to v2.23.1 ([#709](https://github.com/open-feature/java-sdk-contrib/issues/709)) ([d0bc7a5](https://github.com/open-feature/java-sdk-contrib/commit/d0bc7a5aceb746d6d7c442e189a6a1e011673ba7)) -* **deps:** update dependency org.apache.logging.log4j:log4j-slf4j2-impl to v2.24.0 ([#940](https://github.com/open-feature/java-sdk-contrib/issues/940)) ([5465337](https://github.com/open-feature/java-sdk-contrib/commit/546533739b453988720bb051d5e623ac7eb0b588)) -* fix pmd violations ([#856](https://github.com/open-feature/java-sdk-contrib/issues/856)) ([f10d872](https://github.com/open-feature/java-sdk-contrib/commit/f10d87205dd6a21222de362694d208fd293d9200)) -* update failing tests ([#732](https://github.com/open-feature/java-sdk-contrib/issues/732)) ([e1eaf4e](https://github.com/open-feature/java-sdk-contrib/commit/e1eaf4e3778d11ecf25d4276d3733760fa72eb9f)) - -## [0.0.4](https://github.com/open-feature/java-sdk-contrib/compare/dev.openfeature.contrib.providers.statsig-v0.0.3...dev.openfeature.contrib.providers.statsig-v0.0.4) (2024-02-27) - - -### 🐛 Bug Fixes - -* **deps:** update dependency com.statsig:serversdk to v1.12.1 ([#687](https://github.com/open-feature/java-sdk-contrib/issues/687)) ([91da6fe](https://github.com/open-feature/java-sdk-contrib/commit/91da6fee020bdf60d51e1a6849e94201a2b04dec)) - - -### ✨ New Features - -* Statsig provider evaluate boolean updates ([#691](https://github.com/open-feature/java-sdk-contrib/issues/691)) ([04df666](https://github.com/open-feature/java-sdk-contrib/commit/04df6669264227e3c3c6165ea0d876e5d8aa8766)) - - -### 🧹 Chore - -* **deps:** update dependency org.apache.logging.log4j:log4j-slf4j2-impl to v2.23.0 ([#689](https://github.com/open-feature/java-sdk-contrib/issues/689)) ([6589871](https://github.com/open-feature/java-sdk-contrib/commit/65898713166b5d02f246302c54fd7400ee4238d5)) - -## [0.0.3](https://github.com/open-feature/java-sdk-contrib/compare/dev.openfeature.contrib.providers.statsig-v0.0.2...dev.openfeature.contrib.providers.statsig-v0.0.3) (2024-02-14) - - -### 🐛 Bug Fixes - -* null exception when no options used ([#681](https://github.com/open-feature/java-sdk-contrib/issues/681)) ([19b96f8](https://github.com/open-feature/java-sdk-contrib/commit/19b96f8c8a62dbde3ed52177953d3e9fe9398912)) - -## [0.0.2](https://github.com/open-feature/java-sdk-contrib/compare/dev.openfeature.contrib.providers.statsig-v0.0.1...dev.openfeature.contrib.providers.statsig-v0.0.2) (2024-02-09) - - -### 🐛 Bug Fixes - -* **deps:** update dependency com.statsig:serversdk to v1.11.1 ([#658](https://github.com/open-feature/java-sdk-contrib/issues/658)) ([ad668ac](https://github.com/open-feature/java-sdk-contrib/commit/ad668acd81568f86c55d3a02f3678f7169631275)) -* **deps:** update dependency com.statsig:serversdk to v1.12.0 ([#671](https://github.com/open-feature/java-sdk-contrib/issues/671)) ([4bdfb15](https://github.com/open-feature/java-sdk-contrib/commit/4bdfb157f33a5b141e7c7b2a905b91db952d7611)) -* **deps:** update dependency org.slf4j:slf4j-api to v2.0.12 ([#661](https://github.com/open-feature/java-sdk-contrib/issues/661)) ([f03d933](https://github.com/open-feature/java-sdk-contrib/commit/f03d93305bda8ea932831e81db57c989ce4e14e4)) - - -### ✨ New Features - -* Add Statsig provider ([#641](https://github.com/open-feature/java-sdk-contrib/issues/641)) ([f814696](https://github.com/open-feature/java-sdk-contrib/commit/f814696463dd742ee30d1a1e5bdc196b6689447e)) - -## Changelog diff --git a/providers/multiprovider/pom.xml b/providers/multiprovider/pom.xml index 63422321d..1ef8c52a2 100644 --- a/providers/multiprovider/pom.xml +++ b/providers/multiprovider/pom.xml @@ -14,7 +14,7 @@ multiprovider OpenFeature Multi-Provider - https://github.com/open-feature/java-sdk-contrib/tree/main/providers/multi-provider + https://github.com/open-feature/java-sdk-contrib/tree/main/providers/multiprovider diff --git a/release-please-config.json b/release-please-config.json index 15face0c0..b8a017625 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -100,7 +100,7 @@ "README.md" ] }, - "providers/multi-provider": { + "providers/multiprovider": { "package-name": "dev.openfeature.contrib.providers.multiprovider", "release-type": "simple", "bump-minor-pre-major": true, From ca8d76a53251bfd3b0ae9fc138e100a7e50bda60 Mon Sep 17 00:00:00 2001 From: liran2000 Date: Mon, 21 Oct 2024 18:57:50 +0300 Subject: [PATCH 10/12] change evaluate signature Signed-off-by: liran2000 --- .../providers/multiprovider/BaseStrategy.java | 26 ------------------- .../multiprovider/FirstMatchStrategy.java | 16 +++++------- .../FirstSuccessfulStrategy.java | 16 +++++------- .../multiprovider/MultiProvider.java | 19 +++++++++----- .../providers/multiprovider/Strategy.java | 5 ++-- .../multiprovider/MultiProviderTest.java | 12 ++++----- 6 files changed, 35 insertions(+), 59 deletions(-) delete mode 100644 providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java diff --git a/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java b/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java deleted file mode 100644 index 179bc8760..000000000 --- a/providers/multiprovider/src/main/java/dev/openfeature/contrib/providers/multiprovider/BaseStrategy.java +++ /dev/null @@ -1,26 +0,0 @@ -package dev.openfeature.contrib.providers.multiprovider; - -import dev.openfeature.sdk.FeatureProvider; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; - -import java.util.List; -import java.util.Map; - -import static dev.openfeature.contrib.providers.multiprovider.MultiProvider.buildProviders; - -/** - * Base strategy. - */ -@Slf4j -public abstract class BaseStrategy implements Strategy { - - @Getter(AccessLevel.PROTECTED) - private final Map providers; - - public BaseStrategy(List providers) { - this.providers = buildProviders(providers); - } - -} 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 index 0a06cb702..3c90b3154 100644 --- 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 @@ -4,9 +4,10 @@ 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.List; +import java.util.Map; import java.util.function.Function; import static dev.openfeature.sdk.ErrorCode.FLAG_NOT_FOUND; @@ -22,11 +23,8 @@ * the rest of the providers. */ @Slf4j -public class FirstMatchStrategy extends BaseStrategy { - - public FirstMatchStrategy(List providers) { - super(providers); - } +@NoArgsConstructor +public class FirstMatchStrategy implements Strategy { /** * Represents a strategy that evaluates providers based on a first-match approach. @@ -37,9 +35,9 @@ public FirstMatchStrategy(List providers) { * @return the provider evaluation */ @Override - public ProviderEvaluation evaluate(String key, T defaultValue, EvaluationContext ctx, - Function> providerFunction) { - for (FeatureProvider provider: getProviders().values()) { + 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())) { 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 index f00af2447..ec23746fb 100644 --- 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 @@ -4,9 +4,10 @@ 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.List; +import java.util.Map; import java.util.function.Function; /** @@ -16,16 +17,13 @@ * If no provider successfully responds, it will throw an error result. */ @Slf4j -public class FirstSuccessfulStrategy extends BaseStrategy { - - public FirstSuccessfulStrategy(List providers) { - super(providers); - } +@NoArgsConstructor +public class FirstSuccessfulStrategy implements Strategy { @Override - public ProviderEvaluation evaluate(String key, T defaultValue, EvaluationContext ctx, - Function> providerFunction) { - for (FeatureProvider provider: getProviders().values()) { + 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) { 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 index 824645055..00ed676f6 100644 --- 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 @@ -24,7 +24,7 @@ public class MultiProvider extends EventProvider { @Getter private static final String NAME = "multiprovider"; private final Map providers; - private Strategy strategy; + private final Strategy strategy; private String metadataName; /** @@ -47,7 +47,7 @@ public MultiProvider(List providers, Strategy strategy) { if (strategy != null) { this.strategy = strategy; } else { - this.strategy = new FirstMatchStrategy(providers); + this.strategy = new FirstMatchStrategy(); } } @@ -89,27 +89,32 @@ public Metadata getMetadata() { @Override public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) { - return strategy.evaluate(key, defaultValue, ctx, p -> p.getBooleanEvaluation(key, defaultValue, 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(key, defaultValue, ctx, p -> p.getStringEvaluation(key, defaultValue, 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(key, defaultValue, ctx, p -> p.getIntegerEvaluation(key, defaultValue, 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(key, defaultValue, ctx, p -> p.getDoubleEvaluation(key, defaultValue, 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(key, defaultValue, ctx, p -> p.getObjectEvaluation(key, defaultValue, ctx)); + return strategy.evaluate(providers, key, defaultValue, ctx, + p -> p.getObjectEvaluation(key, defaultValue, ctx)); } @Override 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 index 7d170d5fa..85fc06747 100644 --- 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 @@ -4,12 +4,13 @@ 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(String key, T defaultValue, EvaluationContext ctx, - Function> providerFunction); + 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 index f48ef5392..9b267e570 100644 --- 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 @@ -139,7 +139,7 @@ public Metadata getMetadata() { finalMultiProvider1.getStringEvaluation("non-existing", "", null)); multiProvider.shutdown(); - multiProvider = new MultiProvider(providers, new FirstSuccessfulStrategy(providers)); + multiProvider = new MultiProvider(providers, new FirstSuccessfulStrategy()); multiProvider.initialize(null); assertEquals(true, multiProvider.getBooleanEvaluation("b1", false, null) @@ -160,18 +160,18 @@ public Metadata getMetadata() { finalMultiProvider2.getStringEvaluation("non-existing", "", null)); multiProvider.shutdown(); - Strategy customStrategy = new BaseStrategy(providers) { - final FirstMatchStrategy fallbackStrategy = new FirstMatchStrategy(providers); + Strategy customStrategy = new Strategy() { + final FirstMatchStrategy fallbackStrategy = new FirstMatchStrategy(); @Override - public ProviderEvaluation evaluate(String key, T defaultValue, EvaluationContext ctx, Function> providerFunction) { + 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(getProviders().get("new-provider")); + return providerFunction.apply(providers.get("new-provider")); } - return fallbackStrategy.evaluate(key, defaultValue, ctx, providerFunction); + return fallbackStrategy.evaluate(providers, key, defaultValue, ctx, providerFunction); } }; multiProvider = new MultiProvider(providers, customStrategy); From 476c0bf9be74da13ebe0c96d199f61ce14bb3fcc Mon Sep 17 00:00:00 2001 From: liran2000 Date: Mon, 21 Oct 2024 19:01:36 +0300 Subject: [PATCH 11/12] update readme usage Signed-off-by: liran2000 --- providers/multiprovider/README.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/providers/multiprovider/README.md b/providers/multiprovider/README.md index c8763af36..a3a1c90d4 100644 --- a/providers/multiprovider/README.md +++ b/providers/multiprovider/README.md @@ -55,7 +55,7 @@ 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 or not to use a result +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 @@ -88,13 +88,7 @@ MultiProvider multiProvider = new MultiProvider(providers); OpenFeatureAPI.getInstance().setProviderAndWait(multiProvider); // initialize using a different strategy - -// notice LinkedHashMap is used, since order is important -Map providersMap = new LinkedHashMap<>(2); - -providersMap.put(provider1.getMetadata().getName(), provider1); -providersMap.put(provider2.getMetadata().getName(), provider2); -multiProvider = new MultiProvider(providers, new FirstSuccessfulStrategy(providersMap)); +multiProvider = new MultiProvider(providers, new FirstSuccessfulStrategy()); ... ``` From ac693cb0ac47fe532d0b5c49c4f657ff90a4bfdf Mon Sep 17 00:00:00 2001 From: liran2000 Date: Thu, 24 Oct 2024 11:39:29 +0300 Subject: [PATCH 12/12] parallel init Signed-off-by: liran2000 --- providers/multiprovider/README.md | 33 +++++++------------ .../multiprovider/MultiProvider.java | 21 +++++++++++- .../multiprovider/MultiProviderTest.java | 28 ++++++++++++---- 3 files changed, 53 insertions(+), 29 deletions(-) diff --git a/providers/multiprovider/README.md b/providers/multiprovider/README.md index a3a1c90d4..d7af9fc26 100644 --- a/providers/multiprovider/README.md +++ b/providers/multiprovider/README.md @@ -7,27 +7,21 @@ 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. + 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. + 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. + 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. +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 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. +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. @@ -40,23 +34,18 @@ Here are some standard strategies that come with the Multi-Provider: 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. +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. +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. +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. +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 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 index 00ed676f6..65d24cb41 100644 --- 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 @@ -6,14 +6,21 @@ 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. @@ -23,6 +30,7 @@ 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; @@ -73,12 +81,23 @@ public void initialize(EvaluationContext evaluationContext) throws Exception { 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()) { - provider.initialize(evaluationContext); + 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(); } 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 index 9b267e570..943728c6a 100644 --- 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 @@ -11,29 +11,26 @@ import dev.openfeature.sdk.providers.memory.Flag; import dev.openfeature.sdk.providers.memory.InMemoryProvider; import lombok.SneakyThrows; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.HashMap; -import java.util.LinkedHashMap; 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 { - @BeforeEach - void setUp() { - } - @SneakyThrows @Test public void testInit() { @@ -55,6 +52,25 @@ public void testInit() { 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);