From 229c4b1c32ec81d875444ade7ce9a3f1e3e40dfb Mon Sep 17 00:00:00 2001 From: liran2000 Date: Wed, 16 Aug 2023 18:40:37 +0300 Subject: [PATCH] feat: spec 1.1.8 - setProviderAndWait The API SHOULD provide functions to set a provider and wait for the initialize function to return or throw Signed-off-by: liran2000 --- .../dev/openfeature/sdk/OpenFeatureAPI.java | 41 ++++++- .../openfeature/sdk/ProviderRepository.java | 108 ++++++++++++++---- .../sdk/FlagEvaluationSpecTest.java | 30 +++++ .../openfeature/sdk/e2e/StepDefinitions.java | 6 +- .../memory/InMemoryProviderTest.java | 6 +- 5 files changed, 151 insertions(+), 40 deletions(-) diff --git a/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java b/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java index 94047b8cf..6566021e9 100644 --- a/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java +++ b/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java @@ -100,11 +100,11 @@ public EvaluationContext getEvaluationContext() { public void setProvider(FeatureProvider provider) { try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) { providerRepository.setProvider( - provider, - (p) -> attachEventProvider(p), - (p) -> emitReady(p), - (p) -> detachEventProvider(p), - (p, message) -> emitError(p, message)); + provider, + (p) -> attachEventProvider(p), + (p) -> emitReady(p), + (p) -> detachEventProvider(p), + (p, message) -> emitError(p, message)); } } @@ -125,6 +125,37 @@ public void setProvider(String clientName, FeatureProvider provider) { } } + /** + * Set the default provider and wait for initialization to finish. + */ + public void setProviderAndWait(FeatureProvider provider) { + try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) { + providerRepository.setProviderAndWait( + provider, + (p) -> attachEventProvider(p), + (p) -> emitReady(p), + (p) -> detachEventProvider(p), + (p, message) -> emitError(p, message)); + } + } + + /** + * Add a provider for a named client and wait for initialization to finish. + * + * @param clientName The name of the client. + * @param provider The provider to set. + */ + public void setProviderAndWait(String clientName, FeatureProvider provider) { + try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) { + providerRepository.setProviderAndWait(clientName, + provider, + this::attachEventProvider, + this::emitReady, + this::detachEventProvider, + this::emitError); + } + } + private void attachEventProvider(FeatureProvider provider) { if (provider instanceof EventProvider) { ((EventProvider)provider).attach((p, event, details) -> { diff --git a/src/main/java/dev/openfeature/sdk/ProviderRepository.java b/src/main/java/dev/openfeature/sdk/ProviderRepository.java index 0ff3b70be..c9264a9b3 100644 --- a/src/main/java/dev/openfeature/sdk/ProviderRepository.java +++ b/src/main/java/dev/openfeature/sdk/ProviderRepository.java @@ -43,8 +43,8 @@ public FeatureProvider getProvider(String name) { public List getClientNamesForProvider(FeatureProvider provider) { return providers.entrySet().stream() - .filter(entry -> entry.getValue().equals(provider)) - .map(entry -> entry.getKey()).collect(Collectors.toList()); + .filter(entry -> entry.getValue().equals(provider)) + .map(entry -> entry.getKey()).collect(Collectors.toList()); } public Set getAllBoundClientNames() { @@ -55,6 +55,18 @@ public boolean isDefaultProvider(FeatureProvider provider) { return this.getProvider().equals(provider); } + private void setProvider(FeatureProvider provider, + Consumer afterSet, + Consumer afterInit, + Consumer afterShutdown, + BiConsumer afterError, + boolean waitForInit) { + if (provider == null) { + throw new IllegalArgumentException("Provider cannot be null"); + } + initializeProvider(null, provider, afterSet, afterInit, afterShutdown, afterError, waitForInit); + } + /** * Set the default provider. */ @@ -63,10 +75,23 @@ public void setProvider(FeatureProvider provider, Consumer afterInit, Consumer afterShutdown, BiConsumer afterError) { + setProvider(provider, afterSet, afterInit, afterShutdown, afterError, false); + } + + private void setProvider(String clientName, + FeatureProvider provider, + Consumer afterSet, + Consumer afterInit, + Consumer afterShutdown, + BiConsumer afterError, + boolean waitForInit) { if (provider == null) { throw new IllegalArgumentException("Provider cannot be null"); } - initializeProvider(null, provider, afterSet, afterInit, afterShutdown, afterError); + if (clientName == null) { + throw new IllegalArgumentException("clientName cannot be null"); + } + initializeProvider(clientName, provider, afterSet, afterInit, afterShutdown, afterError, waitForInit); } /** @@ -81,13 +106,33 @@ public void setProvider(String clientName, Consumer afterInit, Consumer afterShutdown, BiConsumer afterError) { - if (provider == null) { - throw new IllegalArgumentException("Provider cannot be null"); - } - if (clientName == null) { - throw new IllegalArgumentException("clientName cannot be null"); - } - initializeProvider(clientName, provider, afterSet, afterInit, afterShutdown, afterError); + setProvider(clientName, provider, afterSet, afterInit, afterShutdown, afterError, false); + } + + /** + * Set the default provider and wait for initialization to finish. + */ + public void setProviderAndWait(FeatureProvider provider, + Consumer afterSet, + Consumer afterInit, + Consumer afterShutdown, + BiConsumer afterError) { + setProvider(provider, afterSet, afterInit, afterShutdown, afterError, true); + } + + /** + * Add a provider for a named client and wait for initialization to finish. + * + * @param clientName The name of the client. + * @param provider The provider to set. + */ + public void setProviderAndWait(String clientName, + FeatureProvider provider, + Consumer afterSet, + Consumer afterInit, + Consumer afterShutdown, + BiConsumer afterError) { + setProvider(clientName, provider, afterSet, afterInit, afterShutdown, afterError, true); } private void initializeProvider(@Nullable String clientName, @@ -95,25 +140,38 @@ private void initializeProvider(@Nullable String clientName, Consumer afterSet, Consumer afterInit, Consumer afterShutdown, - BiConsumer afterError) { + BiConsumer afterError, + boolean waitForInit) { // provider is set immediately, on this thread FeatureProvider oldProvider = clientName != null - ? this.providers.put(clientName, newProvider) - : this.defaultProvider.getAndSet(newProvider); + ? this.providers.put(clientName, newProvider) + : this.defaultProvider.getAndSet(newProvider); afterSet.accept(newProvider); - taskExecutor.submit(() -> { - // initialization happens in a different thread - try { - if (ProviderState.NOT_READY.equals(newProvider.getState())) { - newProvider.initialize(OpenFeatureAPI.getInstance().getEvaluationContext()); - afterInit.accept(newProvider); - } - shutDownOld(oldProvider, afterShutdown); - } catch (Exception e) { - log.error("Exception when initializing feature provider {}", newProvider.getClass().getName(), e); - afterError.accept(newProvider, e.getMessage()); + if (waitForInit) { + initializeProvider(newProvider, afterInit, afterShutdown, afterError, oldProvider); + } else { + taskExecutor.submit(() -> { + // initialization happens in a different thread + initializeProvider(newProvider, afterInit, afterShutdown, afterError, oldProvider); + }); + } + } + + private void initializeProvider(FeatureProvider newProvider, + Consumer afterInit, + Consumer afterShutdown, + BiConsumer afterError, + FeatureProvider oldProvider) { + try { + if (ProviderState.NOT_READY.equals(newProvider.getState())) { + newProvider.initialize(OpenFeatureAPI.getInstance().getEvaluationContext()); + afterInit.accept(newProvider); } - }); + shutDownOld(oldProvider, afterShutdown); + } catch (Exception e) { + log.error("Exception when initializing feature provider {}", newProvider.getClass().getName(), e); + afterError.accept(newProvider, e.getMessage()); + } } private void shutDownOld(FeatureProvider oldProvider,Consumer afterShutdown) { diff --git a/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java b/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java index eb41fd950..68dc0b021 100644 --- a/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java +++ b/src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java @@ -17,6 +17,9 @@ import java.util.List; import java.util.Map; +import dev.openfeature.sdk.providers.memory.InMemoryProvider; +import lombok.SneakyThrows; +import org.awaitility.Awaitility; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -69,6 +72,33 @@ void getApiInstance() { assertThat(api.getProvider()).isEqualTo(mockProvider); } + @SneakyThrows + @Specification(number="1.1.8", text="The API SHOULD provide functions to set a provider and wait for the initialize function to return or throw.") + @Test + void providerAndWait() { + FeatureProvider provider = buildLongInitProvider(); + OpenFeatureAPI.getInstance().setProviderAndWait(provider); + assertThat(api.getProvider().getState()).isEqualTo(ProviderState.READY); + + provider = buildLongInitProvider(); + String providerName = "providerAndWait"; + OpenFeatureAPI.getInstance().setProviderAndWait(providerName, provider); + assertThat(api.getProvider(providerName).getState()).isEqualTo(ProviderState.READY); + } + + private static FeatureProvider buildLongInitProvider() { + FeatureProvider provider = new InMemoryProvider(new HashMap<>()) { + @Override + public void initialize(EvaluationContext evaluationContext) throws Exception { + super.initialize(evaluationContext); + + // intentionally cause slow initialize to verify setProviderAndWait waits for it. + Awaitility.await().wait(500); + } + }; + return provider; + } + @Specification(number="1.1.5", text="The API MUST provide a function for retrieving the metadata field of the configured provider.") @Test void provider_metadata() { FeatureProviderTestUtils.setFeatureProvider(new DoSomethingProvider()); diff --git a/src/test/java/dev/openfeature/sdk/e2e/StepDefinitions.java b/src/test/java/dev/openfeature/sdk/e2e/StepDefinitions.java index 650fa242b..38ae8a4d8 100644 --- a/src/test/java/dev/openfeature/sdk/e2e/StepDefinitions.java +++ b/src/test/java/dev/openfeature/sdk/e2e/StepDefinitions.java @@ -56,11 +56,7 @@ public class StepDefinitions { public static void setup() { Map> flags = buildFlags(); InMemoryProvider provider = new InMemoryProvider(flags); - OpenFeatureAPI.getInstance().setProvider(provider); - - // TODO: setProvider with wait for init, pending https://github.com/open-feature/ofep/pull/80 - Thread.sleep(500); - + OpenFeatureAPI.getInstance().setProviderAndWait(provider); client = OpenFeatureAPI.getInstance().getClient(); } diff --git a/src/test/java/dev/openfeature/sdk/providers/memory/InMemoryProviderTest.java b/src/test/java/dev/openfeature/sdk/providers/memory/InMemoryProviderTest.java index f05a6b79f..ac932e838 100644 --- a/src/test/java/dev/openfeature/sdk/providers/memory/InMemoryProviderTest.java +++ b/src/test/java/dev/openfeature/sdk/providers/memory/InMemoryProviderTest.java @@ -31,11 +31,7 @@ static void beforeAll() { Map> flags = buildFlags(); provider = spy(new InMemoryProvider(flags)); OpenFeatureAPI.getInstance().onProviderConfigurationChanged(eventDetails -> {}); - OpenFeatureAPI.getInstance().setProvider(provider); - - // TODO: setProvider with wait for init, pending https://github.com/open-feature/ofep/pull/80 - Thread.sleep(500); - + OpenFeatureAPI.getInstance().setProviderAndWait(provider); client = OpenFeatureAPI.getInstance().getClient(); provider.updateFlags(flags); provider.updateFlag("addedFlag", Flag.builder()