diff --git a/providers/statsig/README.md b/providers/statsig/README.md
index a1eb57ce3..a7e804d75 100644
--- a/providers/statsig/README.md
+++ b/providers/statsig/README.md
@@ -74,4 +74,12 @@ As it is limited, evaluation context based tests are limited.
See [statsigProviderTest](./src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java)
for more information.
+## Release Notes
+### 0.3.0
+ - Migrated to Java Core according to [Migration guide](https://docs.statsig.com/server-core/migration-guides/java#java-migration-steps).
+
+ The provider usage is basically unchanged, the underlying implementation is changed.
+ As the initialization code may change, can refer to the migration guide for details.
+
+- group and secondaryExposures usage removed.
diff --git a/providers/statsig/pom.xml b/providers/statsig/pom.xml
index e7f6d79cb..b8890c5fc 100644
--- a/providers/statsig/pom.xml
+++ b/providers/statsig/pom.xml
@@ -10,7 +10,7 @@
dev.openfeature.contrib.providers
statsig
- 0.2.1
+ 0.3.0
statsig
Statsig provider for Java
@@ -19,8 +19,9 @@
com.statsig
- serversdk
- 1.18.1
+ javacore
+ 0.12.1
+ uber
@@ -36,5 +37,12 @@
test
+
+ org.mockito
+ mockito-core
+ 5.20.0
+ test
+
+
diff --git a/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/ContextTransformer.java b/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/ContextTransformer.java
index bc5d75ce5..2b0faf3d0 100644
--- a/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/ContextTransformer.java
+++ b/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/ContextTransformer.java
@@ -1,6 +1,6 @@
package dev.openfeature.contrib.providers.statsig;
-import com.statsig.sdk.StatsigUser;
+import com.statsig.StatsigUser;
import dev.openfeature.sdk.EvaluationContext;
import dev.openfeature.sdk.Structure;
import dev.openfeature.sdk.Value;
@@ -23,7 +23,7 @@ static StatsigUser transform(EvaluationContext ctx) {
if (ctx.getTargetingKey() == null) {
throw new TargetingKeyMissingError("targeting key is required.");
}
- StatsigUser user = new StatsigUser(ctx.getTargetingKey());
+ StatsigUser.Builder user = new StatsigUser.Builder().setUserID(ctx.getTargetingKey());
Map customMap = new HashMap<>();
ctx.asObjectMap().forEach((k, v) -> {
switch (k) {
@@ -64,6 +64,6 @@ static StatsigUser transform(EvaluationContext ctx) {
privateAttributesStructure.asObjectMap().forEach((k, v) -> privateMap.put(k, String.valueOf(v)));
user.setPrivateAttributes(privateMap);
}
- return user;
+ return user.build();
}
}
diff --git a/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProvider.java b/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProvider.java
index 8c1dd5a8f..70d1adef5 100644
--- a/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProvider.java
+++ b/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProvider.java
@@ -1,11 +1,10 @@
package dev.openfeature.contrib.providers.statsig;
-import com.statsig.sdk.APIFeatureGate;
-import com.statsig.sdk.DynamicConfig;
-import com.statsig.sdk.EvaluationReason;
-import com.statsig.sdk.Layer;
-import com.statsig.sdk.Statsig;
-import com.statsig.sdk.StatsigUser;
+import com.statsig.DynamicConfig;
+import com.statsig.FeatureGate;
+import com.statsig.Layer;
+import com.statsig.Statsig;
+import com.statsig.StatsigUser;
import dev.openfeature.sdk.EvaluationContext;
import dev.openfeature.sdk.EventProvider;
import dev.openfeature.sdk.Metadata;
@@ -13,15 +12,11 @@
import dev.openfeature.sdk.ProviderEvaluation;
import dev.openfeature.sdk.Structure;
import dev.openfeature.sdk.Value;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Future;
+import java.util.concurrent.CompletableFuture;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
-import org.jetbrains.annotations.NotNull;
/** Provider implementation for Statsig. */
@Slf4j
@@ -33,6 +28,9 @@ public class StatsigProvider extends EventProvider {
private static final String FEATURE_CONFIG_KEY = "feature_config";
private final StatsigProviderConfig statsigProviderConfig;
+ @Getter
+ private Statsig statsig;
+
/**
* Constructor.
*
@@ -50,8 +48,8 @@ public StatsigProvider(StatsigProviderConfig statsigProviderConfig) {
*/
@Override
public void initialize(EvaluationContext evaluationContext) throws Exception {
- Future initFuture =
- Statsig.initializeAsync(statsigProviderConfig.getSdkKey(), statsigProviderConfig.getOptions());
+ statsig = new Statsig(statsigProviderConfig.getSdkKey(), statsigProviderConfig.getOptions());
+ CompletableFuture initFuture = statsig.initialize();
initFuture.get();
statsigProviderConfig.postInit();
@@ -65,22 +63,15 @@ public Metadata getMetadata() {
@SneakyThrows
@Override
- @SuppressFBWarnings(
- value = {"NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"},
- justification = "reason can be null")
public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
StatsigUser user = ContextTransformer.transform(ctx);
Boolean evaluatedValue = defaultValue;
Value featureConfigValue = ctx.getValue(FEATURE_CONFIG_KEY);
String reason = null;
if (featureConfigValue == null) {
- APIFeatureGate featureGate = Statsig.getFeatureGate(user, key);
- reason = featureGate.getReason().getReason();
-
- // in case of evaluation failure, remain with default value.
- if (!assumeFailure(featureGate)) {
- evaluatedValue = featureGate.getValue();
- }
+ FeatureGate featureGate = statsig.getFeatureGate(user, key);
+ reason = featureGate.getEvaluationDetails().getReason();
+ evaluatedValue = featureGate.getValue();
} else {
FeatureConfig featureConfig = parseFeatureConfig(ctx);
switch (featureConfig.getType()) {
@@ -103,18 +94,6 @@ public ProviderEvaluation getBooleanEvaluation(String key, Boolean defa
.build();
}
- /*
- https://github.com/statsig-io/java-server-sdk/issues/22#issuecomment-2002346349
- failure is assumed by reason, since success status is not returned.
- */
- private boolean assumeFailure(APIFeatureGate featureGate) {
- EvaluationReason reason = featureGate.getReason();
- return EvaluationReason.DEFAULT.equals(reason)
- || EvaluationReason.UNINITIALIZED.equals(reason)
- || EvaluationReason.UNRECOGNIZED.equals(reason)
- || EvaluationReason.UNSUPPORTED.equals(reason);
- }
-
@Override
public ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
StatsigUser user = ContextTransformer.transform(ctx);
@@ -198,12 +177,12 @@ public ProviderEvaluation getObjectEvaluation(String key, Value defaultVa
@SneakyThrows
protected DynamicConfig fetchDynamicConfig(StatsigUser user, FeatureConfig featureConfig) {
- return Statsig.getConfigAsync(user, featureConfig.getName()).get();
+ return statsig.getDynamicConfig(user, featureConfig.getName());
}
@SneakyThrows
protected Layer fetchLayer(StatsigUser user, FeatureConfig featureConfig) {
- return Statsig.getLayerAsync(user, featureConfig.getName()).get();
+ return statsig.getLayer(user, featureConfig.getName());
}
private Value toValue(DynamicConfig dynamicConfig) {
@@ -211,13 +190,6 @@ private Value toValue(DynamicConfig dynamicConfig) {
mutableContext.add("name", dynamicConfig.getName());
mutableContext.add("value", Structure.mapToStructure(dynamicConfig.getValue()));
mutableContext.add("ruleID", dynamicConfig.getRuleID());
- mutableContext.add("groupName", dynamicConfig.getGroupName());
- List secondaryExposures = new ArrayList<>();
- dynamicConfig.getSecondaryExposures().forEach(secondaryExposure -> {
- Value value = Value.objectToValue(secondaryExposure);
- secondaryExposures.add(value);
- });
- mutableContext.add("secondaryExposures", secondaryExposures);
return new Value(mutableContext);
}
@@ -227,17 +199,11 @@ private Value toValue(Layer layer) {
mutableContext.add("value", Structure.mapToStructure(layer.getValue()));
mutableContext.add("ruleID", layer.getRuleID());
mutableContext.add("groupName", layer.getGroupName());
- List secondaryExposures = new ArrayList<>();
- layer.getSecondaryExposures().forEach(secondaryExposure -> {
- Value value = Value.objectToValue(secondaryExposure);
- secondaryExposures.add(value);
- });
- mutableContext.add("secondaryExposures", secondaryExposures);
- mutableContext.add("allocatedExperiment", layer.getAllocatedExperiment());
+ mutableContext.add("allocatedExperiment", layer.getAllocatedExperimentName());
return new Value(mutableContext);
}
- @NotNull private static FeatureConfig parseFeatureConfig(EvaluationContext ctx) {
+ private static FeatureConfig parseFeatureConfig(EvaluationContext ctx) {
Value featureConfigValue = ctx.getValue(FEATURE_CONFIG_KEY);
if (featureConfigValue == null) {
throw new IllegalArgumentException("feature config not found at evaluation context.");
@@ -262,8 +228,12 @@ private Value toValue(Layer layer) {
@SneakyThrows
@Override
public void shutdown() {
- log.info("shutdown");
- Statsig.shutdown();
+ log.info("shutdown begin");
+ if (statsig != null) {
+ CompletableFuture shutdownFuture = statsig.shutdown();
+ shutdownFuture.get();
+ }
+ log.info("shutdown end");
}
/** Feature config, as required for evaluation. */
diff --git a/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProviderConfig.java b/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProviderConfig.java
index 0f43402f6..bf1e90c6a 100644
--- a/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProviderConfig.java
+++ b/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProviderConfig.java
@@ -1,6 +1,6 @@
package dev.openfeature.contrib.providers.statsig;
-import com.statsig.sdk.StatsigOptions;
+import com.statsig.StatsigOptions;
import lombok.Builder;
import lombok.Getter;
@@ -10,7 +10,7 @@
public class StatsigProviderConfig {
@Builder.Default
- private StatsigOptions options = new StatsigOptions();
+ private StatsigOptions options = new StatsigOptions.Builder().build();
// Only holding temporary for initialization
private String sdkKey;
diff --git a/providers/statsig/src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java b/providers/statsig/src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java
index f13155159..bdb42e54b 100644
--- a/providers/statsig/src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java
+++ b/providers/statsig/src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java
@@ -10,14 +10,14 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
-import com.statsig.sdk.DynamicConfig;
-import com.statsig.sdk.Layer;
-import com.statsig.sdk.Statsig;
-import com.statsig.sdk.StatsigOptions;
-import com.statsig.sdk.StatsigUser;
+import com.statsig.DynamicConfig;
+import com.statsig.Layer;
+import com.statsig.StatsigOptions;
+import com.statsig.StatsigUser;
import dev.openfeature.sdk.Client;
import dev.openfeature.sdk.FlagEvaluationDetails;
import dev.openfeature.sdk.ImmutableContext;
@@ -60,12 +60,12 @@ class StatsigProviderTest {
@BeforeAll
static void setUp() {
String sdkKey = "test";
- StatsigOptions statsigOptions = new StatsigOptions();
- statsigOptions.setLocalMode(true);
+ StatsigOptions statsigOptions = new StatsigOptions.Builder().build();
StatsigProviderConfig statsigProviderConfig = StatsigProviderConfig.builder()
.sdkKey(sdkKey)
.options(statsigOptions)
.build();
+
statsigProvider = spy(new StatsigProvider(statsigProviderConfig));
OpenFeatureAPI.getInstance().setProviderAndWait(statsigProvider);
client = OpenFeatureAPI.getInstance().getClient();
@@ -74,23 +74,22 @@ static void setUp() {
@SneakyThrows
private static void buildFlags() {
- Statsig.overrideGate(FLAG_NAME, true);
+ statsigProvider.getStatsig().overrideGate(FLAG_NAME, true);
Map configMap = new HashMap<>();
configMap.put("boolean", true);
configMap.put("alias", "test");
configMap.put("revision", INT_FLAG_VALUE);
configMap.put("price", DOUBLE_FLAG_VALUE);
- Statsig.overrideConfig("product", configMap);
- Statsig.overrideLayer("product", configMap);
+ statsigProvider.getStatsig().overrideDynamicConfig("product", configMap);
+ statsigProvider.getStatsig().overrideLayer("product", configMap);
ArrayList