{
protected OpenFeatureAPI() {
apiHooks = new ArrayList<>();
- providerRepository = new ProviderRepository();
+ providerRepository = new ProviderRepository(this);
eventSupport = new EventSupport();
transactionContextPropagator = new NoOpTransactionContextPropagator();
}
@@ -69,7 +68,7 @@ public Metadata getProviderMetadata(String domain) {
}
/**
- * A factory function for creating new, OpenFeature clients.
+ * A factory function for creating new, OpenFeature client.
* Clients can contain their own state (e.g. logger, hook, context).
* Multiple clients can be used to segment feature flag configuration.
* All un-named or unbound clients use the default provider.
@@ -81,12 +80,12 @@ public Client getClient() {
}
/**
- * A factory function for creating new domainless OpenFeature clients.
+ * A factory function for creating new domainless OpenFeature client.
* Clients can contain their own state (e.g. logger, hook, context).
* Multiple clients can be used to segment feature flag configuration.
* If there is already a provider bound to this domain, this provider will be used.
* Otherwise, the default provider is used until a provider is assigned to that domain.
- *
+ *
* @param domain an identifier which logically binds clients with providers
* @return a new client instance
*/
@@ -95,20 +94,18 @@ public Client getClient(String domain) {
}
/**
- * A factory function for creating new domainless OpenFeature clients.
+ * A factory function for creating new domainless OpenFeature client.
* Clients can contain their own state (e.g. logger, hook, context).
* Multiple clients can be used to segment feature flag configuration.
* If there is already a provider bound to this domain, this provider will be used.
* Otherwise, the default provider is used until a provider is assigned to that domain.
- *
- * @param domain a identifier which logically binds clients with providers
+ *
+ * @param domain a identifier which logically binds clients with providers
* @param version a version identifier
* @return a new client instance
*/
public Client getClient(String domain, String version) {
- return new OpenFeatureClient(this,
- domain,
- version);
+ return new OpenFeatureClient(this, domain, version);
}
/**
@@ -193,12 +190,13 @@ public void setProvider(FeatureProvider provider) {
/**
* Add a provider for a domain.
*
- * @param domain The domain to bind the provider to.
- * @param provider The provider to set.
+ * @param domain The domain to bind the provider to.
+ * @param provider The provider to set.
*/
public void setProvider(String domain, FeatureProvider provider) {
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
- providerRepository.setProvider(domain,
+ providerRepository.setProvider(
+ domain,
provider,
this::attachEventProvider,
this::emitReady,
@@ -209,7 +207,13 @@ public void setProvider(String domain, FeatureProvider provider) {
}
/**
- * Set the default provider and wait for initialization to finish.
+ * Sets the default provider and waits for its initialization to complete.
+ *
+ * Note: If the provider fails during initialization, an {@link OpenFeatureError} will be thrown.
+ * It is recommended to wrap this call in a try-catch block to handle potential initialization failures gracefully.
+ *
+ * @param provider the {@link FeatureProvider} to set as the default.
+ * @throws OpenFeatureError if the provider fails during initialization.
*/
public void setProviderAndWait(FeatureProvider provider) throws OpenFeatureError {
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
@@ -226,12 +230,17 @@ public void setProviderAndWait(FeatureProvider provider) throws OpenFeatureError
/**
* Add a provider for a domain and wait for initialization to finish.
*
- * @param domain The domain to bind the provider to.
- * @param provider The provider to set.
+ *
Note: If the provider fails during initialization, an {@link OpenFeatureError} will be thrown.
+ * It is recommended to wrap this call in a try-catch block to handle potential initialization failures gracefully.
+ *
+ * @param domain The domain to bind the provider to.
+ * @param provider The provider to set.
+ * @throws OpenFeatureError if the provider fails during initialization.
*/
public void setProviderAndWait(String domain, FeatureProvider provider) throws OpenFeatureError {
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
- providerRepository.setProvider(domain,
+ providerRepository.setProvider(
+ domain,
provider,
this::attachEventProvider,
this::emitReady,
@@ -250,7 +259,10 @@ private void attachEventProvider(FeatureProvider provider) {
}
private void emitReady(FeatureProvider provider) {
- runHandlersForProvider(provider, ProviderEvent.PROVIDER_READY, ProviderEventDetails.builder().build());
+ runHandlersForProvider(
+ provider,
+ ProviderEvent.PROVIDER_READY,
+ ProviderEventDetails.builder().build());
}
private void detachEventProvider(FeatureProvider provider) {
@@ -260,7 +272,9 @@ private void detachEventProvider(FeatureProvider provider) {
}
private void emitError(FeatureProvider provider, OpenFeatureError exception) {
- runHandlersForProvider(provider, ProviderEvent.PROVIDER_ERROR,
+ runHandlersForProvider(
+ provider,
+ ProviderEvent.PROVIDER_ERROR,
ProviderEventDetails.builder().message(exception.getMessage()).build());
}
@@ -300,6 +314,7 @@ public void addHooks(Hook... hooks) {
/**
* Fetch the hooks associated to this client.
+ *
* @return A list of {@link Hook}s.
*/
public List getHooks() {
@@ -328,7 +343,7 @@ public void shutdown() {
providerRepository.shutdown();
eventSupport.shutdown();
- providerRepository = new ProviderRepository();
+ providerRepository = new ProviderRepository(this);
eventSupport = new EventSupport();
}
}
@@ -394,17 +409,23 @@ void removeHandler(String domain, ProviderEvent event, Consumer ha
void addHandler(String domain, ProviderEvent event, Consumer handler) {
try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) {
// if the provider is in the state associated with event, run immediately
- if (Optional.ofNullable(this.providerRepository.getProvider(domain).getState())
- .orElse(ProviderState.READY).matchesEvent(event)) {
- eventSupport.runHandler(handler, EventDetails.builder().domain(domain).build());
+ if (Optional.ofNullable(this.providerRepository.getProviderState(domain))
+ .orElse(ProviderState.READY)
+ .matchesEvent(event)) {
+ eventSupport.runHandler(
+ handler, EventDetails.builder().domain(domain).build());
}
eventSupport.addClientHandler(domain, event, handler);
}
}
+ FeatureProviderStateManager getFeatureProviderStateManager(String domain) {
+ return providerRepository.getFeatureProviderStateManager(domain);
+ }
+
/**
* Runs the handlers associated with a particular provider.
- *
+ *
* @param provider the provider from where this event originated
* @param event the event type
* @param details the event details
@@ -412,8 +433,7 @@ void addHandler(String domain, ProviderEvent event, Consumer handl
private void runHandlersForProvider(FeatureProvider provider, ProviderEvent event, ProviderEventDetails details) {
try (AutoCloseableLock __ = lock.readLockAutoCloseable()) {
- List domainsForProvider = providerRepository
- .getDomainsForProvider(provider);
+ List domainsForProvider = providerRepository.getDomainsForProvider(provider);
final String providerName = Optional.ofNullable(provider.getMetadata())
.map(metadata -> metadata.getName())
@@ -424,8 +444,8 @@ private void runHandlersForProvider(FeatureProvider provider, ProviderEvent even
// run the handlers associated with domains for this provider
domainsForProvider.forEach(domain -> {
- eventSupport.runClientHandlers(domain, event,
- EventDetails.fromProviderEventDetails(details, providerName, domain));
+ eventSupport.runClientHandlers(
+ domain, event, EventDetails.fromProviderEventDetails(details, providerName, domain));
});
if (providerRepository.isDefaultProvider(provider)) {
@@ -434,8 +454,8 @@ private void runHandlersForProvider(FeatureProvider provider, ProviderEvent even
Set boundDomains = providerRepository.getAllBoundDomains();
allDomainNames.removeAll(boundDomains);
allDomainNames.forEach(domain -> {
- eventSupport.runClientHandlers(domain, event,
- EventDetails.fromProviderEventDetails(details, providerName, domain));
+ eventSupport.runClientHandlers(
+ domain, event, EventDetails.fromProviderEventDetails(details, providerName, domain));
});
}
}
diff --git a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java
index d8004e5d..e68d28f7 100644
--- a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java
+++ b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java
@@ -1,18 +1,21 @@
package dev.openfeature.sdk;
+import dev.openfeature.sdk.exceptions.ExceptionUtils;
+import dev.openfeature.sdk.exceptions.FatalError;
+import dev.openfeature.sdk.exceptions.GeneralError;
+import dev.openfeature.sdk.exceptions.OpenFeatureError;
+import dev.openfeature.sdk.exceptions.ProviderNotReadyError;
+import dev.openfeature.sdk.internal.AutoCloseableLock;
+import dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock;
+import dev.openfeature.sdk.internal.ObjectUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.function.Consumer;
-
-import dev.openfeature.sdk.exceptions.GeneralError;
-import dev.openfeature.sdk.exceptions.OpenFeatureError;
-import dev.openfeature.sdk.internal.AutoCloseableLock;
-import dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock;
-import dev.openfeature.sdk.exceptions.ExceptionUtils;
-import dev.openfeature.sdk.internal.ObjectUtils;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@@ -20,21 +23,29 @@
* OpenFeature Client implementation.
* You should not instantiate this or reference this class.
* Use the dev.openfeature.sdk.Client interface instead.
+ *
* @see Client
- *
* @deprecated // TODO: eventually we will make this non-public. See issue #872
*/
@Slf4j
-@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.BeanMembersShouldSerialize",
- "PMD.UnusedLocalVariable", "unchecked", "rawtypes" })
+@SuppressWarnings({
+ "PMD.DataflowAnomalyAnalysis",
+ "PMD.BeanMembersShouldSerialize",
+ "PMD.UnusedLocalVariable",
+ "unchecked",
+ "rawtypes"
+})
@Deprecated() // TODO: eventually we will make this non-public. See issue #872
public class OpenFeatureClient implements Client {
private final OpenFeatureAPI openfeatureApi;
+
@Getter
private final String domain;
+
@Getter
private final String version;
+
private final List clientHooks;
private final HookSupport hookSupport;
AutoCloseableReentrantReadWriteLock hooksLock = new AutoCloseableReentrantReadWriteLock();
@@ -45,11 +56,12 @@ public class OpenFeatureClient implements Client {
* Deprecated public constructor. Use OpenFeature.API.getClient() instead.
*
* @param openFeatureAPI Backing global singleton
- * @param domain An identifier which logically binds clients with providers (used by observability tools).
+ * @param domain An identifier which logically binds clients with
+ * providers (used by observability tools).
* @param version Version of the client (used by observability tools).
* @deprecated Do not use this constructor. It's for internal use only.
- * Clients created using it will not run event handlers.
- * Use the OpenFeatureAPI's getClient factory method instead.
+ * Clients created using it will not run event handlers.
+ * Use the OpenFeatureAPI's getClient factory method instead.
*/
@Deprecated() // TODO: eventually we will make this non-public. See issue #872
public OpenFeatureClient(OpenFeatureAPI openFeatureAPI, String domain, String version) {
@@ -60,6 +72,54 @@ public OpenFeatureClient(OpenFeatureAPI openFeatureAPI, String domain, String ve
this.hookSupport = new HookSupport();
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ProviderState getProviderState() {
+ return openfeatureApi.getFeatureProviderStateManager(domain).getState();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void track(String trackingEventName) {
+ validateTrackingEventName(trackingEventName);
+ invokeTrack(trackingEventName, null, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void track(String trackingEventName, EvaluationContext context) {
+ validateTrackingEventName(trackingEventName);
+ Objects.requireNonNull(context);
+ invokeTrack(trackingEventName, context, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void track(String trackingEventName, TrackingEventDetails details) {
+ validateTrackingEventName(trackingEventName);
+ Objects.requireNonNull(details);
+ invokeTrack(trackingEventName, null, details);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void track(String trackingEventName, EvaluationContext context, TrackingEventDetails details) {
+ validateTrackingEventName(trackingEventName);
+ Objects.requireNonNull(context);
+ Objects.requireNonNull(details);
+ invokeTrack(trackingEventName, mergeEvaluationContext(context), details);
+ }
+
/**
* {@inheritDoc}
*/
@@ -102,12 +162,11 @@ public EvaluationContext getEvaluationContext() {
}
}
- private FlagEvaluationDetails evaluateFlag(FlagValueType type, String key, T defaultValue,
- EvaluationContext ctx, FlagEvaluationOptions options) {
- FlagEvaluationOptions flagOptions = ObjectUtils.defaultIfNull(options,
- () -> FlagEvaluationOptions.builder().build());
+ private FlagEvaluationDetails evaluateFlag(
+ FlagValueType type, String key, T defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) {
+ FlagEvaluationOptions flagOptions = ObjectUtils.defaultIfNull(
+ options, () -> FlagEvaluationOptions.builder().build());
Map hints = Collections.unmodifiableMap(flagOptions.getHookHints());
- ctx = ObjectUtils.defaultIfNull(ctx, () -> new ImmutableContext());
FlagEvaluationDetails details = null;
List mergedHooks = null;
@@ -115,30 +174,52 @@ private FlagEvaluationDetails evaluateFlag(FlagValueType type, String key
FeatureProvider provider;
try {
- // openfeatureApi.getProvider() must be called once to maintain a consistent reference
- provider = openfeatureApi.getProvider(this.domain);
-
- mergedHooks = ObjectUtils.merge(provider.getProviderHooks(), flagOptions.getHooks(), clientHooks,
- openfeatureApi.getHooks());
-
- EvaluationContext mergedCtx = hookSupport.beforeHooks(type, HookContext.from(key, type, this.getMetadata(),
- provider.getMetadata(), mergeEvaluationContext(ctx), defaultValue), mergedHooks, hints);
-
- afterHookContext = HookContext.from(key, type, this.getMetadata(),
- provider.getMetadata(), mergedCtx, defaultValue);
+ FeatureProviderStateManager stateManager = openfeatureApi.getFeatureProviderStateManager(this.domain);
+ // provider must be accessed once to maintain a consistent reference
+ provider = stateManager.getProvider();
+ ProviderState state = stateManager.getState();
+
+ mergedHooks = ObjectUtils.merge(
+ provider.getProviderHooks(), flagOptions.getHooks(), clientHooks, openfeatureApi.getHooks());
+
+ EvaluationContext mergedCtx = hookSupport.beforeHooks(
+ type,
+ HookContext.from(
+ key,
+ type,
+ this.getMetadata(),
+ provider.getMetadata(),
+ mergeEvaluationContext(ctx),
+ defaultValue),
+ mergedHooks,
+ hints);
+
+ afterHookContext =
+ HookContext.from(key, type, this.getMetadata(), provider.getMetadata(), mergedCtx, defaultValue);
+
+ // "short circuit" if the provider is in NOT_READY or FATAL state
+ if (ProviderState.NOT_READY.equals(state)) {
+ throw new ProviderNotReadyError("Provider not yet initialized");
+ }
+ if (ProviderState.FATAL.equals(state)) {
+ throw new FatalError("Provider is in an irrecoverable error state");
+ }
- ProviderEvaluation providerEval = (ProviderEvaluation) createProviderEvaluation(type, key,
- defaultValue, provider, mergedCtx);
+ ProviderEvaluation providerEval =
+ (ProviderEvaluation) createProviderEvaluation(type, key, defaultValue, provider, mergedCtx);
details = FlagEvaluationDetails.from(providerEval, key);
if (details.getErrorCode() != null) {
- throw ExceptionUtils.instantiateErrorByErrorCode(details.getErrorCode(), details.getErrorMessage());
+ OpenFeatureError error =
+ ExceptionUtils.instantiateErrorByErrorCode(details.getErrorCode(), details.getErrorMessage());
+ enrichDetailsWithErrorDefaults(defaultValue, details);
+ hookSupport.errorHooks(type, afterHookContext, error, mergedHooks, hints);
} else {
hookSupport.afterHooks(type, afterHookContext, details, mergedHooks, hints);
}
} catch (Exception e) {
if (details == null) {
- details = FlagEvaluationDetails.builder().build();
+ details = FlagEvaluationDetails.builder().flagKey(key).build();
}
if (e instanceof OpenFeatureError) {
details.setErrorCode(((OpenFeatureError) e).getErrorCode());
@@ -146,16 +227,34 @@ private FlagEvaluationDetails evaluateFlag(FlagValueType type, String key
details.setErrorCode(ErrorCode.GENERAL);
}
details.setErrorMessage(e.getMessage());
- details.setValue(defaultValue);
- details.setReason(Reason.ERROR.toString());
+ enrichDetailsWithErrorDefaults(defaultValue, details);
hookSupport.errorHooks(type, afterHookContext, e, mergedHooks, hints);
} finally {
- hookSupport.afterAllHooks(type, afterHookContext, mergedHooks, hints);
+ hookSupport.afterAllHooks(type, afterHookContext, details, mergedHooks, hints);
}
return details;
}
+ private static void enrichDetailsWithErrorDefaults(T defaultValue, FlagEvaluationDetails details) {
+ details.setValue(defaultValue);
+ details.setReason(Reason.ERROR.toString());
+ }
+
+ private static void validateTrackingEventName(String str) {
+ Objects.requireNonNull(str);
+ if (str.isEmpty()) {
+ throw new IllegalArgumentException("trackingEventName cannot be empty");
+ }
+ }
+
+ private void invokeTrack(String trackingEventName, EvaluationContext context, TrackingEventDetails details) {
+ openfeatureApi
+ .getFeatureProviderStateManager(domain)
+ .getProvider()
+ .track(trackingEventName, mergeEvaluationContext(context), details);
+ }
+
/**
* Merge invocation contexts with API, transaction and client contexts.
* Does not merge before context.
@@ -164,17 +263,22 @@ private FlagEvaluationDetails evaluateFlag(FlagValueType type, String key
* @return merged evaluation context
*/
private EvaluationContext mergeEvaluationContext(EvaluationContext invocationContext) {
- final EvaluationContext apiContext = openfeatureApi.getEvaluationContext() != null
- ? openfeatureApi.getEvaluationContext()
- : new ImmutableContext();
- final EvaluationContext clientContext = this.getEvaluationContext() != null
- ? this.getEvaluationContext()
- : new ImmutableContext();
- final EvaluationContext transactionContext = openfeatureApi.getTransactionContext() != null
- ? openfeatureApi.getTransactionContext()
- : new ImmutableContext();
-
- return apiContext.merge(transactionContext.merge(clientContext.merge(invocationContext)));
+ final EvaluationContext apiContext = openfeatureApi.getEvaluationContext();
+ final EvaluationContext clientContext = this.getEvaluationContext();
+ final EvaluationContext transactionContext = openfeatureApi.getTransactionContext();
+ return mergeContextMaps(apiContext, transactionContext, clientContext, invocationContext);
+ }
+
+ private EvaluationContext mergeContextMaps(EvaluationContext... contexts) {
+ // avoid any unnecessary context instantiations and stream usage here; this is
+ // called with every evaluation.
+ Map merged = new HashMap<>();
+ for (EvaluationContext evaluationContext : contexts) {
+ if (evaluationContext != null && !evaluationContext.isEmpty()) {
+ EvaluationContext.mergeMaps(ImmutableStructure::new, merged, evaluationContext.asUnmodifiableMap());
+ }
+ }
+ return new ImmutableContext(merged);
}
private ProviderEvaluation> createProviderEvaluation(
@@ -210,8 +314,8 @@ public Boolean getBooleanValue(String key, Boolean defaultValue, EvaluationConte
}
@Override
- public Boolean getBooleanValue(String key, Boolean defaultValue, EvaluationContext ctx,
- FlagEvaluationOptions options) {
+ public Boolean getBooleanValue(
+ String key, Boolean defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) {
return getBooleanDetails(key, defaultValue, ctx, options).getValue();
}
@@ -222,12 +326,13 @@ public FlagEvaluationDetails getBooleanDetails(String key, Boolean defa
@Override
public FlagEvaluationDetails getBooleanDetails(String key, Boolean defaultValue, EvaluationContext ctx) {
- return getBooleanDetails(key, defaultValue, ctx, FlagEvaluationOptions.builder().build());
+ return getBooleanDetails(
+ key, defaultValue, ctx, FlagEvaluationOptions.builder().build());
}
@Override
- public FlagEvaluationDetails getBooleanDetails(String key, Boolean defaultValue, EvaluationContext ctx,
- FlagEvaluationOptions options) {
+ public FlagEvaluationDetails getBooleanDetails(
+ String key, Boolean defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) {
return this.evaluateFlag(FlagValueType.BOOLEAN, key, defaultValue, ctx, options);
}
@@ -242,8 +347,8 @@ public String getStringValue(String key, String defaultValue, EvaluationContext
}
@Override
- public String getStringValue(String key, String defaultValue, EvaluationContext ctx,
- FlagEvaluationOptions options) {
+ public String getStringValue(
+ String key, String defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) {
return getStringDetails(key, defaultValue, ctx, options).getValue();
}
@@ -254,12 +359,13 @@ public FlagEvaluationDetails getStringDetails(String key, String default
@Override
public FlagEvaluationDetails getStringDetails(String key, String defaultValue, EvaluationContext ctx) {
- return getStringDetails(key, defaultValue, ctx, FlagEvaluationOptions.builder().build());
+ return getStringDetails(
+ key, defaultValue, ctx, FlagEvaluationOptions.builder().build());
}
@Override
- public FlagEvaluationDetails getStringDetails(String key, String defaultValue, EvaluationContext ctx,
- FlagEvaluationOptions options) {
+ public FlagEvaluationDetails getStringDetails(
+ String key, String defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) {
return this.evaluateFlag(FlagValueType.STRING, key, defaultValue, ctx, options);
}
@@ -274,8 +380,8 @@ public Integer getIntegerValue(String key, Integer defaultValue, EvaluationConte
}
@Override
- public Integer getIntegerValue(String key, Integer defaultValue, EvaluationContext ctx,
- FlagEvaluationOptions options) {
+ public Integer getIntegerValue(
+ String key, Integer defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) {
return getIntegerDetails(key, defaultValue, ctx, options).getValue();
}
@@ -286,12 +392,13 @@ public FlagEvaluationDetails getIntegerDetails(String key, Integer defa
@Override
public FlagEvaluationDetails getIntegerDetails(String key, Integer defaultValue, EvaluationContext ctx) {
- return getIntegerDetails(key, defaultValue, ctx, FlagEvaluationOptions.builder().build());
+ return getIntegerDetails(
+ key, defaultValue, ctx, FlagEvaluationOptions.builder().build());
}
@Override
- public FlagEvaluationDetails getIntegerDetails(String key, Integer defaultValue, EvaluationContext ctx,
- FlagEvaluationOptions options) {
+ public FlagEvaluationDetails getIntegerDetails(
+ String key, Integer defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) {
return this.evaluateFlag(FlagValueType.INTEGER, key, defaultValue, ctx, options);
}
@@ -306,9 +413,10 @@ public Double getDoubleValue(String key, Double defaultValue, EvaluationContext
}
@Override
- public Double getDoubleValue(String key, Double defaultValue, EvaluationContext ctx,
- FlagEvaluationOptions options) {
- return this.evaluateFlag(FlagValueType.DOUBLE, key, defaultValue, ctx, options).getValue();
+ public Double getDoubleValue(
+ String key, Double defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) {
+ return this.evaluateFlag(FlagValueType.DOUBLE, key, defaultValue, ctx, options)
+ .getValue();
}
@Override
@@ -322,8 +430,8 @@ public FlagEvaluationDetails getDoubleDetails(String key, Double default
}
@Override
- public FlagEvaluationDetails