Skip to content

Commit

Permalink
feat: spec 1.1.8 - setProviderAndWait
Browse files Browse the repository at this point in the history
The API SHOULD provide functions to set a provider and wait for the initialize function to return or throw

Signed-off-by: liran2000 <[email protected]>
  • Loading branch information
liran2000 committed Aug 16, 2023
1 parent 94a5a86 commit 229c4b1
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 40 deletions.
41 changes: 36 additions & 5 deletions src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}

Expand All @@ -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) -> {
Expand Down
108 changes: 83 additions & 25 deletions src/main/java/dev/openfeature/sdk/ProviderRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ public FeatureProvider getProvider(String name) {

public List<String> 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<String> getAllBoundClientNames() {
Expand All @@ -55,6 +55,18 @@ public boolean isDefaultProvider(FeatureProvider provider) {
return this.getProvider().equals(provider);
}

private void setProvider(FeatureProvider provider,
Consumer<FeatureProvider> afterSet,
Consumer<FeatureProvider> afterInit,
Consumer<FeatureProvider> afterShutdown,
BiConsumer<FeatureProvider, String> 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.
*/
Expand All @@ -63,10 +75,23 @@ public void setProvider(FeatureProvider provider,
Consumer<FeatureProvider> afterInit,
Consumer<FeatureProvider> afterShutdown,
BiConsumer<FeatureProvider, String> afterError) {
setProvider(provider, afterSet, afterInit, afterShutdown, afterError, false);
}

private void setProvider(String clientName,
FeatureProvider provider,
Consumer<FeatureProvider> afterSet,
Consumer<FeatureProvider> afterInit,
Consumer<FeatureProvider> afterShutdown,
BiConsumer<FeatureProvider, String> 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);
}

/**
Expand All @@ -81,39 +106,72 @@ public void setProvider(String clientName,
Consumer<FeatureProvider> afterInit,
Consumer<FeatureProvider> afterShutdown,
BiConsumer<FeatureProvider, String> 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<FeatureProvider> afterSet,
Consumer<FeatureProvider> afterInit,
Consumer<FeatureProvider> afterShutdown,
BiConsumer<FeatureProvider, String> 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<FeatureProvider> afterSet,
Consumer<FeatureProvider> afterInit,
Consumer<FeatureProvider> afterShutdown,
BiConsumer<FeatureProvider, String> afterError) {
setProvider(clientName, provider, afterSet, afterInit, afterShutdown, afterError, true);
}

private void initializeProvider(@Nullable String clientName,
FeatureProvider newProvider,
Consumer<FeatureProvider> afterSet,
Consumer<FeatureProvider> afterInit,
Consumer<FeatureProvider> afterShutdown,
BiConsumer<FeatureProvider, String> afterError) {
BiConsumer<FeatureProvider, String> 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<FeatureProvider> afterInit,
Consumer<FeatureProvider> afterShutdown,
BiConsumer<FeatureProvider, String> 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<FeatureProvider> afterShutdown) {
Expand Down
30 changes: 30 additions & 0 deletions src/test/java/dev/openfeature/sdk/FlagEvaluationSpecTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand Down
6 changes: 1 addition & 5 deletions src/test/java/dev/openfeature/sdk/e2e/StepDefinitions.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,7 @@ public class StepDefinitions {
public static void setup() {
Map<String, Flag<?>> 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();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,7 @@ static void beforeAll() {
Map<String, Flag<?>> 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()
Expand Down

0 comments on commit 229c4b1

Please sign in to comment.