From a6dcc60dd5c77dd1bbd42c7c9f2058929a6bb32a Mon Sep 17 00:00:00 2001 From: liran2000 Date: Tue, 5 Sep 2023 19:48:33 +0300 Subject: [PATCH 1/4] feat: add Togglz provider Signed-off-by: liran2000 --- pom.xml | 1 + providers/togglz/CHANGELOG.md | 0 providers/togglz/README.md | 7 ++ providers/togglz/lombok.config | 5 + providers/togglz/pom.xml | 40 ++++++++ .../providers/togglz/TogglzProvider.java | 99 +++++++++++++++++++ .../providers/togglz/TestFeatures.java | 18 ++++ .../providers/togglz/TogglzProviderTest.java | 81 +++++++++++++++ .../togglz/src/test/resources/log4j2-test.xml | 13 +++ providers/togglz/version.txt | 1 + 10 files changed, 265 insertions(+) create mode 100644 providers/togglz/CHANGELOG.md create mode 100644 providers/togglz/README.md create mode 100644 providers/togglz/lombok.config create mode 100644 providers/togglz/pom.xml create mode 100644 providers/togglz/src/main/java/dev/openfeature/contrib/providers/togglz/TogglzProvider.java create mode 100644 providers/togglz/src/test/java/dev/openfeature/contrib/providers/togglz/TestFeatures.java create mode 100644 providers/togglz/src/test/java/dev/openfeature/contrib/providers/togglz/TogglzProviderTest.java create mode 100644 providers/togglz/src/test/resources/log4j2-test.xml create mode 100644 providers/togglz/version.txt diff --git a/pom.xml b/pom.xml index 50d38c56f..ebbec1753 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,7 @@ providers/go-feature-flag providers/jsonlogic-eval-provider providers/env-var + providers/togglz diff --git a/providers/togglz/CHANGELOG.md b/providers/togglz/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb diff --git a/providers/togglz/README.md b/providers/togglz/README.md new file mode 100644 index 000000000..a37991fba --- /dev/null +++ b/providers/togglz/README.md @@ -0,0 +1,7 @@ +# Unofficial Togglz OpenFeature Provider for Java + +## Usage +See [TogglzProviderTest.java](./src/test/java/dev/openfeature/contrib/providers/togglz/TogglzProviderTest.java) + +### References +* [Togglz](https://www.togglz.org) diff --git a/providers/togglz/lombok.config b/providers/togglz/lombok.config new file mode 100644 index 000000000..bcd1afdae --- /dev/null +++ b/providers/togglz/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/togglz/pom.xml b/providers/togglz/pom.xml new file mode 100644 index 000000000..476dbe2df --- /dev/null +++ b/providers/togglz/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + dev.openfeature.contrib + parent + 0.1.0 + ../../pom.xml + + dev.openfeature.contrib.providers + togglz + 0.0.1 + + togglz + togglz provider for Java + https://www.togglz.org/ + + + + org.togglz + togglz-core + 4.3.0 + + + + org.slf4j + slf4j-api + 2.0.7 + + + + org.apache.logging.log4j + log4j-slf4j2-impl + 2.20.0 + test + + + + diff --git a/providers/togglz/src/main/java/dev/openfeature/contrib/providers/togglz/TogglzProvider.java b/providers/togglz/src/main/java/dev/openfeature/contrib/providers/togglz/TogglzProvider.java new file mode 100644 index 000000000..ce5d9df26 --- /dev/null +++ b/providers/togglz/src/main/java/dev/openfeature/contrib/providers/togglz/TogglzProvider.java @@ -0,0 +1,99 @@ +package dev.openfeature.contrib.providers.togglz; + +import dev.openfeature.sdk.EvaluationContext; +import dev.openfeature.sdk.EventProvider; +import dev.openfeature.sdk.Metadata; +import dev.openfeature.sdk.ProviderEvaluation; +import dev.openfeature.sdk.ProviderState; +import dev.openfeature.sdk.Reason; +import dev.openfeature.sdk.Value; +import dev.openfeature.sdk.exceptions.FlagNotFoundError; +import dev.openfeature.sdk.exceptions.GeneralError; +import dev.openfeature.sdk.exceptions.ProviderNotReadyError; +import dev.openfeature.sdk.exceptions.TypeMismatchError; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.togglz.core.Feature; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * Provider implementation for Togglz. + */ +@AllArgsConstructor +@Slf4j +public class TogglzProvider extends EventProvider { + + @Getter + private static final String NAME = "Togglz Provider"; + public static final String NOT_IMPLEMENTED = + "Not implemented - provider does not support this type. Only boolean is supported."; + + private Map features = new HashMap<>(); + + @Getter + private ProviderState state = ProviderState.NOT_READY; + + public TogglzProvider(Collection featuresCollection) { + featuresCollection.forEach(feature -> features.put(feature.name(), feature)); + } + + /** + * Initialize the provider. + * @param evaluationContext evaluation context + * @throws Exception on error + */ + @Override + public void initialize(EvaluationContext evaluationContext) throws Exception { + super.initialize(evaluationContext); + state = ProviderState.READY; + log.debug("finished initializing provider, state: {}", state); + } + + @Override + public Metadata getMetadata() { + return () -> NAME; + } + + @Override + public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) { + if (!ProviderState.READY.equals(state)) { + if (ProviderState.NOT_READY.equals(state)) { + throw new ProviderNotReadyError("provider not yet initialized"); + } + throw new GeneralError("unknown error"); + } + Feature feature = features.get(key); + if (feature == null) { + throw new FlagNotFoundError("flag " + key + " not found"); + } + boolean featureBooleanValue = feature.isActive(); + return ProviderEvaluation.builder() + .value(featureBooleanValue) + .reason(Reason.TARGETING_MATCH.name()) + .build(); + } + + @Override + public ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) { + throw new TypeMismatchError(NOT_IMPLEMENTED); + } + + @Override + public ProviderEvaluation getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) { + throw new TypeMismatchError(NOT_IMPLEMENTED); + } + + @Override + public ProviderEvaluation getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) { + throw new TypeMismatchError(NOT_IMPLEMENTED); + } + + @Override + public ProviderEvaluation getObjectEvaluation(String s, Value value, EvaluationContext evaluationContext) { + throw new TypeMismatchError(NOT_IMPLEMENTED); + } +} diff --git a/providers/togglz/src/test/java/dev/openfeature/contrib/providers/togglz/TestFeatures.java b/providers/togglz/src/test/java/dev/openfeature/contrib/providers/togglz/TestFeatures.java new file mode 100644 index 000000000..a3f862ac0 --- /dev/null +++ b/providers/togglz/src/test/java/dev/openfeature/contrib/providers/togglz/TestFeatures.java @@ -0,0 +1,18 @@ +package dev.openfeature.contrib.providers.togglz; + +import org.togglz.core.Feature; +import org.togglz.core.annotation.Label; +import org.togglz.core.context.FeatureContext; + +public enum TestFeatures implements Feature { + + @Label("First Feature") + FEATURE_ONE, + + @Label("Second Feature") + FEATURE_TWO; + + public boolean isActive() { + return FeatureContext.getFeatureManager().isActive(this); + } +} diff --git a/providers/togglz/src/test/java/dev/openfeature/contrib/providers/togglz/TogglzProviderTest.java b/providers/togglz/src/test/java/dev/openfeature/contrib/providers/togglz/TogglzProviderTest.java new file mode 100644 index 000000000..dfe2c23e2 --- /dev/null +++ b/providers/togglz/src/test/java/dev/openfeature/contrib/providers/togglz/TogglzProviderTest.java @@ -0,0 +1,81 @@ +package dev.openfeature.contrib.providers.togglz; + +import dev.openfeature.sdk.Client; +import dev.openfeature.sdk.FeatureProvider; +import dev.openfeature.sdk.ImmutableContext; +import dev.openfeature.sdk.OpenFeatureAPI; +import dev.openfeature.sdk.exceptions.FlagNotFoundError; +import dev.openfeature.sdk.exceptions.ProviderNotReadyError; +import dev.openfeature.sdk.exceptions.TypeMismatchError; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.togglz.core.context.StaticFeatureManagerProvider; +import org.togglz.core.manager.FeatureManager; +import org.togglz.core.manager.FeatureManagerBuilder; +import org.togglz.core.repository.FeatureState; +import org.togglz.core.repository.StateRepository; +import org.togglz.core.repository.mem.InMemoryStateRepository; +import org.togglz.core.user.NoOpUserProvider; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class TogglzProviderTest { + + private FeatureProvider featureProvider; + + private Client client; + + @BeforeEach + void setUp() throws Exception { + StateRepository stateRepository = new InMemoryStateRepository(); + stateRepository.setFeatureState(new FeatureState(TestFeatures.FEATURE_ONE, true)); + stateRepository.setFeatureState(new FeatureState(TestFeatures.FEATURE_TWO, false)); + + FeatureManager featureManager = new FeatureManagerBuilder() + .featureEnums(TestFeatures.class) + .stateRepository(stateRepository) + .userProvider(new NoOpUserProvider()) + .build(); + StaticFeatureManagerProvider.setFeatureManager(featureManager); + + OpenFeatureAPI api = OpenFeatureAPI.getInstance(); + featureProvider = new TogglzProvider(Arrays.asList(TestFeatures.values())); + api.setProviderAndWait(featureProvider); + client = api.getClient(); + } + + @Test + void getBooleanEvaluation() { + assertEquals(true, featureProvider.getBooleanEvaluation(TestFeatures.FEATURE_ONE.name(), false, new ImmutableContext()).getValue()); + assertEquals(true, client.getBooleanValue(TestFeatures.FEATURE_ONE.name(), false)); + assertEquals(false, featureProvider.getBooleanEvaluation(TestFeatures.FEATURE_TWO.name(), false, new ImmutableContext()).getValue()); + assertEquals(false, client.getBooleanValue(TestFeatures.FEATURE_TWO.name(), false)); + } + + @Test + void notFound() { + assertThrows(FlagNotFoundError.class, () -> { + featureProvider.getBooleanEvaluation("not-found-flag", false, new ImmutableContext()); + }); + } + + @Test + void typeMismatch() { + assertThrows(TypeMismatchError.class, () -> { + featureProvider.getStringEvaluation(TestFeatures.FEATURE_ONE.name(), "default_value", new ImmutableContext()); + }); + } + + @SneakyThrows + @Test + void shouldThrowIfNotInitialized() { + TogglzProvider togglzProvider = new TogglzProvider(Arrays.asList(TestFeatures.values())); + + // ErrorCode.PROVIDER_NOT_READY should be returned when evaluated via the client + assertThrows(ProviderNotReadyError.class, ()-> togglzProvider.getBooleanEvaluation("fail_not_initialized", false, new ImmutableContext())); + } +} \ No newline at end of file diff --git a/providers/togglz/src/test/resources/log4j2-test.xml b/providers/togglz/src/test/resources/log4j2-test.xml new file mode 100644 index 000000000..223d21a89 --- /dev/null +++ b/providers/togglz/src/test/resources/log4j2-test.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/providers/togglz/version.txt b/providers/togglz/version.txt new file mode 100644 index 000000000..8acdd82b7 --- /dev/null +++ b/providers/togglz/version.txt @@ -0,0 +1 @@ +0.0.1 From 10da1bc29ce1f8895069b05058a22ec155f13270 Mon Sep 17 00:00:00 2001 From: liran2000 Date: Tue, 5 Sep 2023 20:30:00 +0300 Subject: [PATCH 2/4] update togglz-core version, as 4.x requires Java 17 Signed-off-by: liran2000 --- providers/togglz/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/togglz/pom.xml b/providers/togglz/pom.xml index 476dbe2df..804eefdc0 100644 --- a/providers/togglz/pom.xml +++ b/providers/togglz/pom.xml @@ -20,7 +20,7 @@ org.togglz togglz-core - 4.3.0 + 3.3.0 From 3b208658692cd335f525bb5278a7ca55ec1228fa Mon Sep 17 00:00:00 2001 From: liran2000 Date: Tue, 5 Sep 2023 21:00:24 +0300 Subject: [PATCH 3/4] update docs, add TogglzOptions Signed-off-by: liran2000 --- providers/togglz/README.md | 39 ++++++++++++++++++- .../providers/togglz/TogglzOptions.java | 16 ++++++++ .../providers/togglz/TogglzProvider.java | 5 +-- .../providers/togglz/TogglzProviderTest.java | 11 +++--- 4 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 providers/togglz/src/main/java/dev/openfeature/contrib/providers/togglz/TogglzOptions.java diff --git a/providers/togglz/README.md b/providers/togglz/README.md index a37991fba..e7735f63e 100644 --- a/providers/togglz/README.md +++ b/providers/togglz/README.md @@ -1,7 +1,42 @@ # Unofficial Togglz OpenFeature Provider for Java +Togglz OpenFeature Provider can provide usage for Togglz via OpenFeature Java SDK. + +## Installation + + + +```xml + + + dev.openfeature.contrib.providers + togglz + 0.0.1 + +``` + + + ## Usage -See [TogglzProviderTest.java](./src/test/java/dev/openfeature/contrib/providers/togglz/TogglzProviderTest.java) +Togglz OpenFeature Provider is using Togglz Java SDK. + +### Usage Example + +``` +TogglzOptions togglzOptions = TogglzOptions.builder().features(features).build(); +FeatureProvider featureProvider = new TogglzProvider(togglzOptions); +api.setProviderAndWait(featureProvider); +client = api.getClient(); +boolean featureEnabled = client.getBooleanValue(TestFeatures.FEATURE_ONE.name(), false); + +``` + +See [TogglzProviderTest.java](./src/test/java/dev/openfeature/contrib/providers/togglz/TogglzProviderTest.java) for more information. + +## Caveats / Limitations + +* Togglz OpenFeature Provider only supports boolean feature flags. +* Evaluation does not treat evaluation context, but relies on Togglz functionalities. -### References +## References * [Togglz](https://www.togglz.org) diff --git a/providers/togglz/src/main/java/dev/openfeature/contrib/providers/togglz/TogglzOptions.java b/providers/togglz/src/main/java/dev/openfeature/contrib/providers/togglz/TogglzOptions.java new file mode 100644 index 000000000..413262d4c --- /dev/null +++ b/providers/togglz/src/main/java/dev/openfeature/contrib/providers/togglz/TogglzOptions.java @@ -0,0 +1,16 @@ +package dev.openfeature.contrib.providers.togglz; + +import lombok.Builder; +import lombok.Data; +import org.togglz.core.Feature; + +import java.util.Collection; + +/** + * Togglz Options for initializing Togglz provider. + */ +@Data +@Builder +public class TogglzOptions { + Collection features; +} diff --git a/providers/togglz/src/main/java/dev/openfeature/contrib/providers/togglz/TogglzProvider.java b/providers/togglz/src/main/java/dev/openfeature/contrib/providers/togglz/TogglzProvider.java index ce5d9df26..54bb1545a 100644 --- a/providers/togglz/src/main/java/dev/openfeature/contrib/providers/togglz/TogglzProvider.java +++ b/providers/togglz/src/main/java/dev/openfeature/contrib/providers/togglz/TogglzProvider.java @@ -16,7 +16,6 @@ import lombok.extern.slf4j.Slf4j; import org.togglz.core.Feature; -import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -37,8 +36,8 @@ public class TogglzProvider extends EventProvider { @Getter private ProviderState state = ProviderState.NOT_READY; - public TogglzProvider(Collection featuresCollection) { - featuresCollection.forEach(feature -> features.put(feature.name(), feature)); + public TogglzProvider(TogglzOptions togglzOptions) { + togglzOptions.features.forEach(feature -> features.put(feature.name(), feature)); } /** diff --git a/providers/togglz/src/test/java/dev/openfeature/contrib/providers/togglz/TogglzProviderTest.java b/providers/togglz/src/test/java/dev/openfeature/contrib/providers/togglz/TogglzProviderTest.java index dfe2c23e2..2250b2333 100644 --- a/providers/togglz/src/test/java/dev/openfeature/contrib/providers/togglz/TogglzProviderTest.java +++ b/providers/togglz/src/test/java/dev/openfeature/contrib/providers/togglz/TogglzProviderTest.java @@ -42,10 +42,10 @@ void setUp() throws Exception { .build(); StaticFeatureManagerProvider.setFeatureManager(featureManager); - OpenFeatureAPI api = OpenFeatureAPI.getInstance(); - featureProvider = new TogglzProvider(Arrays.asList(TestFeatures.values())); - api.setProviderAndWait(featureProvider); - client = api.getClient(); + TogglzOptions togglzOptions = TogglzOptions.builder().features(Arrays.asList(TestFeatures.values())).build(); + featureProvider = new TogglzProvider(togglzOptions); + OpenFeatureAPI.getInstance().setProviderAndWait(featureProvider); + client = OpenFeatureAPI.getInstance().getClient(); } @Test @@ -73,7 +73,8 @@ void typeMismatch() { @SneakyThrows @Test void shouldThrowIfNotInitialized() { - TogglzProvider togglzProvider = new TogglzProvider(Arrays.asList(TestFeatures.values())); + TogglzOptions togglzOptions = TogglzOptions.builder().features(Arrays.asList(TestFeatures.values())).build(); + FeatureProvider togglzProvider = new TogglzProvider(togglzOptions); // ErrorCode.PROVIDER_NOT_READY should be returned when evaluated via the client assertThrows(ProviderNotReadyError.class, ()-> togglzProvider.getBooleanEvaluation("fail_not_initialized", false, new ImmutableContext())); From cbd9f389e0c7528ce0b28ea058a5ea959d1e3ea5 Mon Sep 17 00:00:00 2001 From: liran2000 Date: Tue, 26 Sep 2023 08:31:23 +0300 Subject: [PATCH 4/4] Update component owners Signed-off-by: liran2000 --- .github/component_owners.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/component_owners.yml b/.github/component_owners.yml index 4372c0a2a..b2aef428d 100644 --- a/.github/component_owners.yml +++ b/.github/component_owners.yml @@ -19,6 +19,9 @@ components: - thomaspoignant providers/jsonlogic-eval-provider: - justinabrahms + providers/togglz: + - liran2000 + - bennetelli ignored-authors: - renovate-bot \ No newline at end of file