-
Notifications
You must be signed in to change notification settings - Fork 58
feat: supply bound domain to provider initialization #1982
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
87f226e
f6854ac
2a8d369
4e73b99
cc1cec2
1fc14b6
4a23ee9
620d811
7791a42
68fa5da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ | |
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import javax.annotation.Nullable; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We removed jsr305 is abandoned, so I'd rather not have I'd suggest dropping the annotation here (and the internal ones in |
||
|
|
||
| /** | ||
| * The interface implemented by upstream flag providers to resolve flags for | ||
|
|
@@ -41,6 +42,38 @@ default void initialize(EvaluationContext evaluationContext) throws Exception { | |
| // Intentionally left blank | ||
| } | ||
|
|
||
| /** | ||
| * This method is called before a provider is used to evaluate flags, with the | ||
| * bound domain supplied when the provider is registered to a named client. | ||
| * | ||
| * <p> | ||
| * The default provider is initialized with a {@code null} domain. Providers that | ||
| * maintain per-domain state (for example a persistent cache) should override this | ||
| * method and declare themselves {@linkplain #isDomainScoped() domain-scoped}. | ||
| * </p> | ||
| * | ||
| * @param evaluationContext the global evaluation context | ||
| * @param domain the bound domain, or {@code null} for the default provider | ||
| */ | ||
| default void initialize(EvaluationContext evaluationContext, @Nullable String domain) throws Exception { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as JS, we could consider using an object here to future-proof (something like |
||
| initialize(evaluationContext); | ||
| } | ||
|
|
||
| /** | ||
| * Returns whether this provider maintains state specific to a single domain that | ||
| * cannot be shared across domains. | ||
| * | ||
| * <p> | ||
| * Domain-scoped providers may only be bound to one domain within a single API | ||
| * instance. | ||
| * </p> | ||
| * | ||
| * @return {@code true} if this provider is domain-scoped | ||
| */ | ||
| default boolean isDomainScoped() { | ||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * This method is called when a new provider is about to be used to evaluate | ||
| * flags, or the SDK is shut down. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ | |
| import java.util.function.Consumer; | ||
| import java.util.stream.Collectors; | ||
| import java.util.stream.Stream; | ||
| import javax.annotation.Nullable; | ||
| import lombok.extern.slf4j.Slf4j; | ||
|
|
||
| @Slf4j | ||
|
|
@@ -154,7 +155,7 @@ public void setProvider( | |
| } | ||
|
|
||
| private void prepareAndInitializeProvider( | ||
| String domain, | ||
| @Nullable String domain, | ||
| FeatureProvider newProvider, | ||
| Consumer<FeatureProvider> afterSet, | ||
| Consumer<FeatureProvider> afterInit, | ||
|
|
@@ -169,6 +170,7 @@ private void prepareAndInitializeProvider( | |
| throw new IllegalStateException("Provider cannot be set while repository is shutting down"); | ||
| } | ||
| FeatureProviderStateManager existing = getExistingStateManagerForProvider(newProvider); | ||
| validateDomainScopedBinding(domain, newProvider); | ||
| if (existing == null) { | ||
| openFeatureAPI.registerGlobalProvider(newProvider); | ||
| newStateManager = new FeatureProviderStateManager(newProvider); | ||
|
|
@@ -185,37 +187,82 @@ private void prepareAndInitializeProvider( | |
| } | ||
|
|
||
| if (waitForInit) { | ||
| initializeProvider(newStateManager, afterInit, afterShutdown, afterError, oldStateManager); | ||
| initializeProvider(domain, newStateManager, afterInit, afterShutdown, afterError, oldStateManager); | ||
| } else { | ||
| taskExecutor.submit(() -> { | ||
| // initialization happens in a different thread if we're not waiting for it | ||
| initializeProvider(newStateManager, afterInit, afterShutdown, afterError, oldStateManager); | ||
| initializeProvider(domain, newStateManager, afterInit, afterShutdown, afterError, oldStateManager); | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| private void validateDomainScopedBinding(@Nullable String domain, FeatureProvider newProvider) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, same as JS, might be better to extract this and implement it in one check. Something like a single upfront predicate that inspects |
||
| if (!newProvider.isDomainScoped()) { | ||
| return; | ||
| } | ||
|
|
||
| boolean currentlyDefault = isDefaultProviderInstance(newProvider); | ||
| List<String> boundNamedDomains = getBoundDomainsForProviderInstance(newProvider); | ||
|
|
||
| if (!currentlyDefault && boundNamedDomains.isEmpty()) { | ||
| return; | ||
| } | ||
|
|
||
| if (domain == null) { | ||
| if (!currentlyDefault) { | ||
| throw new IllegalArgumentException("Domain-scoped provider cannot be bound to more than one domain"); | ||
| } | ||
| return; | ||
| } | ||
| if (boundNamedDomains.contains(domain)) { | ||
| return; | ||
| } | ||
| if (!boundNamedDomains.isEmpty() || currentlyDefault) { | ||
| throw new IllegalArgumentException("Domain-scoped provider cannot be bound to more than one domain"); | ||
| } | ||
| } | ||
|
|
||
| private boolean isDefaultProviderInstance(FeatureProvider provider) { | ||
| return defaultStateManger.get().getProvider() == provider; | ||
| } | ||
|
|
||
| private List<String> getBoundDomainsForProviderInstance(FeatureProvider provider) { | ||
| return stateManagers.entrySet().stream() | ||
| .filter(entry -> entry.getValue().getProvider() == provider) | ||
| .map(Map.Entry::getKey) | ||
| .collect(Collectors.toList()); | ||
| } | ||
|
|
||
| private FeatureProviderStateManager getExistingStateManagerForProvider(FeatureProvider provider) { | ||
| for (FeatureProviderStateManager stateManager : stateManagers.values()) { | ||
| if (stateManager.hasSameProvider(provider)) { | ||
| if (matchesProvider(stateManager.getProvider(), provider)) { | ||
| return stateManager; | ||
| } | ||
| } | ||
| FeatureProviderStateManager defaultFeatureProviderStateManager = defaultStateManger.get(); | ||
| if (defaultFeatureProviderStateManager.hasSameProvider(provider)) { | ||
| if (matchesProvider(defaultFeatureProviderStateManager.getProvider(), provider)) { | ||
| return defaultFeatureProviderStateManager; | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| private boolean matchesProvider(FeatureProvider registered, FeatureProvider candidate) { | ||
| if (candidate.isDomainScoped()) { | ||
| return registered == candidate; | ||
| } | ||
| return registered.equals(candidate); | ||
| } | ||
|
|
||
| private void initializeProvider( | ||
| @Nullable String domain, | ||
| FeatureProviderStateManager newManager, | ||
| Consumer<FeatureProvider> afterInit, | ||
| Consumer<FeatureProvider> afterShutdown, | ||
| BiConsumer<FeatureProvider, OpenFeatureError> afterError, | ||
| FeatureProviderStateManager oldManager) { | ||
| try { | ||
| if (ProviderState.NOT_READY.equals(newManager.getState())) { | ||
| newManager.initialize(openFeatureAPI.getEvaluationContext()); | ||
| newManager.initialize(openFeatureAPI.getEvaluationContext(), domain); | ||
| afterInit.accept(newManager.getProvider()); | ||
| } | ||
| shutDownOld(oldManager, afterShutdown); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.