diff --git a/src/examples/java/com/devcycle/examples/CloudExample.java b/src/examples/java/com/devcycle/examples/CloudExample.java index e238e54..ebf4770 100644 --- a/src/examples/java/com/devcycle/examples/CloudExample.java +++ b/src/examples/java/com/devcycle/examples/CloudExample.java @@ -2,7 +2,6 @@ import com.devcycle.sdk.server.cloud.api.DevCycleCloudClient; import com.devcycle.sdk.server.cloud.model.DevCycleCloudOptions; -import com.devcycle.sdk.server.common.exception.DevCycleException; import com.devcycle.sdk.server.common.model.DevCycleUser; public class CloudExample { @@ -34,7 +33,7 @@ public static void main(String[] args) throws InterruptedException { Boolean variableValue = false; try { variableValue = client.variableValue(user, VARIABLE_KEY, defaultValue); - } catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { System.err.println("Error fetching variable value: " + e.getMessage()); System.exit(1); } diff --git a/src/examples/java/com/devcycle/examples/LocalExample.java b/src/examples/java/com/devcycle/examples/LocalExample.java index dd41e65..8c04229 100644 --- a/src/examples/java/com/devcycle/examples/LocalExample.java +++ b/src/examples/java/com/devcycle/examples/LocalExample.java @@ -1,8 +1,8 @@ package com.devcycle.examples; +import com.devcycle.sdk.server.common.model.DevCycleUser; import com.devcycle.sdk.server.local.api.DevCycleLocalClient; import com.devcycle.sdk.server.local.model.DevCycleLocalOptions; -import com.devcycle.sdk.server.common.model.DevCycleUser; public class LocalExample { public static String VARIABLE_KEY = "test-boolean-variable"; @@ -29,7 +29,7 @@ public static void main(String[] args) throws InterruptedException { DevCycleLocalClient client = new DevCycleLocalClient(server_sdk_key, options); for (int i = 0; i < 10; i++) { - if(client.isInitialized()) { + if (client.isInitialized()) { break; } Thread.sleep(500); diff --git a/src/examples/java/com/devcycle/examples/OpenFeatureExample.java b/src/examples/java/com/devcycle/examples/OpenFeatureExample.java index 29f6121..391504e 100644 --- a/src/examples/java/com/devcycle/examples/OpenFeatureExample.java +++ b/src/examples/java/com/devcycle/examples/OpenFeatureExample.java @@ -21,35 +21,37 @@ public static void main(String[] args) throws InterruptedException { System.exit(1); } - DevCycleLocalOptions options = DevCycleLocalOptions.builder().configPollingIntervalMs(60000) + DevCycleLocalOptions options = DevCycleLocalOptions.builder().configPollingIntervalMS(60000) .disableAutomaticEventLogging(false).disableCustomEventLogging(false).build(); // Initialize DevCycle Client DevCycleLocalClient devCycleClient = new DevCycleLocalClient(server_sdk_key, options); - OpenFeatureAPI api = OpenFeatureAPI.getInstance(); for (int i = 0; i < 10; i++) { - if(devCycleClient.isInitialized()) { + if (devCycleClient.isInitialized()) { break; } Thread.sleep(500); } - Map apiAttrs = new LinkedHashMap(); + // Setup OpenFeature with the DevCycle Provider + OpenFeatureAPI api = OpenFeatureAPI.getInstance(); + api.setProvider(new DevCycleProvider(devCycleClient)); + + // Create the evaluation context to use for fetching variable values + Map apiAttrs = new LinkedHashMap<>(); apiAttrs.put("email", new Value("test-user@domain.com")); apiAttrs.put("country", new Value("US")); - EvaluationContext ctx = new ImmutableContext("test-1234", apiAttrs); + EvaluationContext context = new ImmutableContext("test-1234", apiAttrs); // The default value can be of type string, boolean, number, or JSON Boolean defaultValue = false; - api.setProvider(new DevCycleProvider(devCycleClient)); - // Fetch variable values using the identifier key, with a default value and user // object. The default value can be of type string, boolean, number, or JSON - Boolean variableValue = api.getClient().getBooleanValue(VARIABLE_KEY, defaultValue, ctx); + Boolean variableValue = api.getClient().getBooleanValue(VARIABLE_KEY, defaultValue, context); // Use variable value if (variableValue) { diff --git a/src/main/java/com/devcycle/sdk/server/common/api/IDevCycleClient.java b/src/main/java/com/devcycle/sdk/server/common/api/IDevCycleClient.java index 4799e16..609c515 100644 --- a/src/main/java/com/devcycle/sdk/server/common/api/IDevCycleClient.java +++ b/src/main/java/com/devcycle/sdk/server/common/api/IDevCycleClient.java @@ -1,15 +1,38 @@ package com.devcycle.sdk.server.common.api; -import com.devcycle.sdk.server.common.exception.DevCycleException; import com.devcycle.sdk.server.common.model.DevCycleUser; import com.devcycle.sdk.server.common.model.Variable; +/** + * Base interface for DevCycle clients that can be used to evaluate Features and retrieve variables values. + */ public interface IDevCycleClient { - public boolean isInitialized(); + /** + * @return true if the client is initialized and ready to be used. Clients should + * return a default value if they are not initialized. + */ + boolean isInitialized(); - public T variableValue(DevCycleUser user, String key, T defaultValue); + /** + * @param user (required) The user context for the evaluation. + * @param key (required) The key of the feature variable to evaluate. + * @param defaultValue (required) The default value to return if the feature variable is not found or the user + * does not segment into the feature + * @return the value of the variable for the given user, or the default value if the variable is not found. + */ + T variableValue(DevCycleUser user, String key, T defaultValue); - public Variable variable(DevCycleUser user, String key, T defaultValue); + /** + * @param user (required) The user context for the evaluation. + * @param key (required) The key of the feature variable to evaluate. + * @param defaultValue (required) The default value to return if the feature variable is not found or the user + * does not segment into the feature + * @return the variable for the given user, or the default variable if the variable is not found. + */ + Variable variable(DevCycleUser user, String key, T defaultValue); - public void close(); + /** + * Close the client and release any resources. + */ + void close(); } diff --git a/src/main/java/com/devcycle/sdk/server/openfeature/DevCycleProvider.java b/src/main/java/com/devcycle/sdk/server/openfeature/DevCycleProvider.java index 5db63e2..5840216 100644 --- a/src/main/java/com/devcycle/sdk/server/openfeature/DevCycleProvider.java +++ b/src/main/java/com/devcycle/sdk/server/openfeature/DevCycleProvider.java @@ -1,12 +1,12 @@ package com.devcycle.sdk.server.openfeature; -import dev.openfeature.sdk.*; import com.devcycle.sdk.server.common.api.IDevCycleClient; -import com.devcycle.sdk.server.common.model.*; +import com.devcycle.sdk.server.common.model.DevCycleUser; +import com.devcycle.sdk.server.common.model.Variable; +import dev.openfeature.sdk.*; import dev.openfeature.sdk.exceptions.ProviderNotReadyError; -public class DevCycleProvider implements FeatureProvider -{ +public class DevCycleProvider implements FeatureProvider { private static final String PROVIDER_NAME = "DevCycleProvider"; private final IDevCycleClient devcycleClient; @@ -51,13 +51,13 @@ public void shutdown() { } ProviderEvaluation resolve(String key, T defaultValue, EvaluationContext ctx) { - if(devcycleClient.isInitialized()) { + if (devcycleClient.isInitialized()) { try { - DevCycleUser user = UserFactory.createUser(ctx); + DevCycleUser user = DevCycleUserFactory.createUser(ctx); Variable variable = devcycleClient.variable(user, key, defaultValue); - if(variable == null || variable.getIsDefaulted()) { + if (variable == null || variable.getIsDefaulted()) { return ProviderEvaluation.builder() .value(defaultValue) .reason(Reason.DEFAULT.toString()) @@ -76,8 +76,7 @@ ProviderEvaluation resolve(String key, T defaultValue, EvaluationContext .errorMessage(e.getMessage()) .build(); } - } - else { + } else { throw new ProviderNotReadyError("DevCycle client not initialized"); } } diff --git a/src/main/java/com/devcycle/sdk/server/openfeature/UserFactory.java b/src/main/java/com/devcycle/sdk/server/openfeature/DevCycleUserFactory.java similarity index 56% rename from src/main/java/com/devcycle/sdk/server/openfeature/UserFactory.java rename to src/main/java/com/devcycle/sdk/server/openfeature/DevCycleUserFactory.java index 1d032b2..6e93e40 100644 --- a/src/main/java/com/devcycle/sdk/server/openfeature/UserFactory.java +++ b/src/main/java/com/devcycle/sdk/server/openfeature/DevCycleUserFactory.java @@ -9,100 +9,81 @@ import java.util.LinkedHashMap; import java.util.Map; -public class UserFactory { +/** + * Utility class for creating a DevCycleUser from an EvaluationContext + */ +class DevCycleUserFactory { static void setCustomValue(Map customData, String key, Value value) { // Only support boolean, number, and string types for custom data values // ignore all other data - if(customData != null && key != null && value != null) - { - if(value.isBoolean()){ + if (customData != null && key != null && value != null) { + if (value.isBoolean()) { customData.put(key, value.asBoolean()); - } - else if(value.isNumber()) - { + } else if (value.isNumber()) { customData.put(key, value.asDouble()); - } - else if(value.isString()) - { + } else if (value.isString()) { customData.put(key, value.asString()); } } } - public static DevCycleUser createUser(EvaluationContext ctx) { + static DevCycleUser createUser(EvaluationContext ctx) { String userId = ""; if (ctx != null && ctx.getTargetingKey() != null) { userId = ctx.getTargetingKey(); - } - else if(ctx != null && ctx.getValue("user_id") != null) { + } else if (ctx != null && ctx.getValue("user_id") != null) { userId = ctx.getValue("user_id").asString(); } - if(userId == null || userId.isEmpty()) { + if (userId == null || userId.isEmpty()) { throw new TargetingKeyMissingError(); } DevCycleUser user = DevCycleUser.builder().userId(userId).build(); - Map customData = new LinkedHashMap(); - Map privateCustomData = new LinkedHashMap(); + Map customData = new LinkedHashMap<>(); + Map privateCustomData = new LinkedHashMap<>(); - for(String key : ctx.keySet()) { - if(key.equals("user_id")) { + for (String key : ctx.keySet()) { + if (key.equals("user_id")) { continue; } Value value = ctx.getValue(key); - if(key == "email" && value.isString()) - { + if (key.equals("email") && value.isString()) { user.setEmail(value.asString()); - } - else if(key == "name" && value.isString()) - { + } else if (key.equals("name") && value.isString()) { user.setName(value.asString()); - } - else if(key == "language" && value.isString()) - { + } else if (key.equals("language") && value.isString()) { user.setLanguage(value.asString()); - } - else if(key == "country" && value.isString()) - { + } else if (key.equals("country") && value.isString()) { user.setCountry(value.asString()); - } - else if(key == "appVersion" && value.isString()) - { + } else if (key.equals("appVersion") && value.isString()) { user.setAppVersion(value.asString()); - } - else if(key == "appBuild" && value.isString()) - { + } else if (key.equals("appBuild") && value.isString()) { user.setAppBuild(value.asString()); - } - else if(key == "customData" && value.isStructure()) - { + } else if (key.equals("customData") && value.isStructure()) { Structure customDataStructure = value.asStructure(); - for(String dataKey : customDataStructure.keySet()) { + for (String dataKey : customDataStructure.keySet()) { setCustomValue(customData, dataKey, customDataStructure.getValue(dataKey)); } - } - else if(key == "privateCustomData" && value.isStructure()) - { + } else if (key.equals("privateCustomData") && value.isStructure()) { Structure privateDataStructure = value.asStructure(); - for(String dataKey : privateDataStructure.keySet()) { + for (String dataKey : privateDataStructure.keySet()) { setCustomValue(privateCustomData, dataKey, privateDataStructure.getValue(dataKey)); } - } - else { + } else { setCustomValue(customData, key, value); } } - if(customData.size() > 0){ + if (!customData.isEmpty()) { user.setCustomData(customData); } - if(privateCustomData.size() > 0){ + if (!privateCustomData.isEmpty()) { user.setPrivateCustomData(privateCustomData); } diff --git a/src/test/java/com/devcycle/sdk/server/openfeature/UserFactoryTest.java b/src/test/java/com/devcycle/sdk/server/openfeature/DevCycleUserFactoryTest.java similarity index 83% rename from src/test/java/com/devcycle/sdk/server/openfeature/UserFactoryTest.java rename to src/test/java/com/devcycle/sdk/server/openfeature/DevCycleUserFactoryTest.java index d3f4a41..25a0572 100644 --- a/src/test/java/com/devcycle/sdk/server/openfeature/UserFactoryTest.java +++ b/src/test/java/com/devcycle/sdk/server/openfeature/DevCycleUserFactoryTest.java @@ -14,14 +14,14 @@ import java.util.*; @RunWith(MockitoJUnitRunner.class) -public class UserFactoryTest { +public class DevCycleUserFactoryTest { @Test public void testCreateUserNoUserID() { EvaluationContext ctx = new ImmutableContext(); try{ - UserFactory.createUser(ctx); + DevCycleUserFactory.createUser(ctx); Assert.fail("Expected exception"); } catch (TargetingKeyMissingError e) { // expected @@ -31,7 +31,7 @@ public void testCreateUserNoUserID() { ctx = new ImmutableContext(null, attribs); try{ - UserFactory.createUser(ctx); + DevCycleUserFactory.createUser(ctx); Assert.fail("Expected exception"); } catch (TargetingKeyMissingError e) { // expected @@ -41,14 +41,14 @@ public void testCreateUserNoUserID() { @Test public void testCreateUserOnlyUserId() { EvaluationContext ctx = new ImmutableContext("test-1234"); - DevCycleUser user = UserFactory.createUser(ctx); + DevCycleUser user = DevCycleUserFactory.createUser(ctx); Assert.assertEquals(user.getUserId(), "test-1234"); Map apiAttrs = new LinkedHashMap(); apiAttrs.put("user_id", new Value("test-6789")); ctx = new ImmutableContext(null, apiAttrs); - user = UserFactory.createUser(ctx); + user = DevCycleUserFactory.createUser(ctx); Assert.assertEquals(user.getUserId(), "test-6789"); } @@ -65,7 +65,7 @@ public void testCreateUserWithAttributes() { EvaluationContext ctx = new ImmutableContext("test-1234", apiAttrs); - DevCycleUser user = UserFactory.createUser(ctx); + DevCycleUser user = DevCycleUserFactory.createUser(ctx); Assert.assertEquals(user.getUserId(), "test-1234"); Assert.assertEquals(user.getEmail(), "test-user@domain.com"); Assert.assertEquals(user.getCountry(), "US"); @@ -96,7 +96,7 @@ public void testCreateUserWithCustomData() { EvaluationContext ctx = new ImmutableContext("test-1234", apiAttrs); - DevCycleUser user = UserFactory.createUser(ctx); + DevCycleUser user = DevCycleUserFactory.createUser(ctx); Assert.assertEquals(user.getUserId(), "test-1234"); Assert.assertEquals(user.getCustomData().size(), 4); @@ -116,28 +116,28 @@ public void testCreateUserWithCustomData() { public void testSetCustomValueBadData() { Map customData = null; - UserFactory.setCustomValue(customData, "test", new Value(true)); + DevCycleUserFactory.setCustomValue(customData, "test", new Value(true)); Assert.assertNull(customData); customData = new HashMap(); - UserFactory.setCustomValue(customData, null, new Value(true)); + DevCycleUserFactory.setCustomValue(customData, null, new Value(true)); Assert.assertEquals(customData.size(), 0); - UserFactory.setCustomValue(customData, "test", null); + DevCycleUserFactory.setCustomValue(customData, "test", null); Assert.assertEquals(customData.size(), 0); List list = new ArrayList(); list.add("one"); list.add("two"); list.add("three"); - UserFactory.setCustomValue(customData, "test", new Value(list)); + DevCycleUserFactory.setCustomValue(customData, "test", new Value(list)); Assert.assertEquals(customData.size(), 0); Map map = new HashMap(); map.put("p1", "one"); map.put("p2", "two"); map.put("p3", "three"); - UserFactory.setCustomValue(customData, "test", new Value(Structure.mapToStructure(map))); + DevCycleUserFactory.setCustomValue(customData, "test", new Value(Structure.mapToStructure(map))); Assert.assertEquals(customData.size(), 0); } @@ -145,7 +145,7 @@ public void testSetCustomValueBadData() { public void testSetCustomValueBoolean() { Map customData = new HashMap(); - UserFactory.setCustomValue(customData, "test", new Value(true)); + DevCycleUserFactory.setCustomValue(customData, "test", new Value(true)); Assert.assertEquals(customData.size(), 1); Assert.assertEquals(customData.get("test"), true); } @@ -154,7 +154,7 @@ public void testSetCustomValueBoolean() { public void testSetCustomValueString() { Map customData = new HashMap(); - UserFactory.setCustomValue(customData, "test", new Value("some string")); + DevCycleUserFactory.setCustomValue(customData, "test", new Value("some string")); Assert.assertEquals(customData.size(), 1); Assert.assertEquals(customData.get("test"), "some string"); } @@ -163,7 +163,7 @@ public void testSetCustomValueString() { public void testSetCustomValueInt() { Map customData = new HashMap(); - UserFactory.setCustomValue(customData, "test", new Value(999)); + DevCycleUserFactory.setCustomValue(customData, "test", new Value(999)); Assert.assertEquals(customData.size(), 1); Assert.assertEquals(customData.get("test"), 999.0); } @@ -172,7 +172,7 @@ public void testSetCustomValueInt() { public void testSetCustomValueDouble() { Map customData = new HashMap(); - UserFactory.setCustomValue(customData, "test", new Value(3.14)); + DevCycleUserFactory.setCustomValue(customData, "test", new Value(3.14)); Assert.assertEquals(customData.size(), 1); Assert.assertEquals(customData.get("test"), 3.14); }