From 34670e3077b5ea3023e1090822e72ff0231ed967 Mon Sep 17 00:00:00 2001 From: Dan Richelson Date: Thu, 2 Jun 2016 10:15:10 -0700 Subject: [PATCH 1/9] Make more things private/final --- .../com/launchdarkly/client/Prerequisite.java | 9 ++++++-- .../java/com/launchdarkly/client/Rule.java | 22 +++++++++---------- .../java/com/launchdarkly/client/Target.java | 9 ++++++-- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/launchdarkly/client/Prerequisite.java b/src/main/java/com/launchdarkly/client/Prerequisite.java index 99004a7e1..d934b7c22 100644 --- a/src/main/java/com/launchdarkly/client/Prerequisite.java +++ b/src/main/java/com/launchdarkly/client/Prerequisite.java @@ -1,8 +1,13 @@ package com.launchdarkly.client; class Prerequisite { - private String key; - private int variation; + private final String key; + private final int variation; + + Prerequisite(String key, int variation) { + this.key = key; + this.variation = variation; + } String getKey() { return key; diff --git a/src/main/java/com/launchdarkly/client/Rule.java b/src/main/java/com/launchdarkly/client/Rule.java index f0dcd2610..822d38e07 100644 --- a/src/main/java/com/launchdarkly/client/Rule.java +++ b/src/main/java/com/launchdarkly/client/Rule.java @@ -15,9 +15,9 @@ class Rule { private static final float long_scale = (float) 0xFFFFFFFFFFFFFFFL; - private List clauses; - private Integer variation; - private Rollout rollout; + private final List clauses; + private final Integer variation; + private final Rollout rollout; Rule(List clauses, Integer variation, Rollout rollout) { this.clauses = clauses; @@ -42,7 +42,7 @@ Integer variationIndexForUser(LDUser user, String key, String salt) { Float bucket = bucketUser(user, key, bucketBy, salt); Float sum = 0F; for (WeightedVariation wv : rollout.variations) { - sum += (float)wv.weight / 100000F; + sum += (float) wv.weight / 100000F; if (bucket < sum) { return wv.variation; } @@ -51,7 +51,7 @@ Integer variationIndexForUser(LDUser user, String key, String salt) { return null; } - Float bucketUser(LDUser user, String key, String attr, String salt) { + private Float bucketUser(LDUser user, String key, String attr, String salt) { JsonElement userValue = valueOf(user, attr); String idHash; if (userValue != null) { @@ -69,20 +69,20 @@ Float bucketUser(LDUser user, String key, String attr, String salt) { } static class Rollout { - private List variations; - private String bucketBy; + private final List variations; + private final String bucketBy; - public Rollout(List variations, String bucketBy) { + Rollout(List variations, String bucketBy) { this.variations = variations; this.bucketBy = bucketBy; } } static class WeightedVariation { - private int variation; - private int weight; + private final int variation; + private final int weight; - public WeightedVariation(int variation, int weight) { + WeightedVariation(int variation, int weight) { this.variation = variation; this.weight = weight; } diff --git a/src/main/java/com/launchdarkly/client/Target.java b/src/main/java/com/launchdarkly/client/Target.java index 3cd7cab0d..a6c5a648e 100644 --- a/src/main/java/com/launchdarkly/client/Target.java +++ b/src/main/java/com/launchdarkly/client/Target.java @@ -3,8 +3,13 @@ import java.util.List; class Target { - private List values; - private int variation; + private final List values; + private final int variation; + + Target(List values, int variation) { + this.values = values; + this.variation = variation; + } List getValues() { return values; From f2524eb034c04aed3d0afc3e8fd692ca6187cdb8 Mon Sep 17 00:00:00 2001 From: Dan Richelson Date: Thu, 2 Jun 2016 10:29:22 -0700 Subject: [PATCH 2/9] Remove switch statement in favor of enum and method on user. --- .../java/com/launchdarkly/client/Clause.java | 26 +------- .../java/com/launchdarkly/client/LDUser.java | 8 +++ .../java/com/launchdarkly/client/Rule.java | 4 +- .../launchdarkly/client/UserAttribute.java | 64 ++++++++++++++++--- 4 files changed, 65 insertions(+), 37 deletions(-) diff --git a/src/main/java/com/launchdarkly/client/Clause.java b/src/main/java/com/launchdarkly/client/Clause.java index 335549d25..d00ac2588 100644 --- a/src/main/java/com/launchdarkly/client/Clause.java +++ b/src/main/java/com/launchdarkly/client/Clause.java @@ -17,7 +17,7 @@ class Clause { private boolean negate; boolean matchesUser(LDUser user) { - JsonElement userValue = valueOf(user, attribute); + JsonElement userValue = user.getValueOf(attribute); if (userValue == null) { return false; } @@ -58,27 +58,5 @@ private boolean maybeNegate(boolean b) { return b; } - static JsonElement valueOf(LDUser user, String attribute) { - switch (attribute) { - case "key": - return user.getKey(); - case "ip": - return user.getIp(); - case "country": - return user.getCountry(); - case "email": - return user.getEmail(); - case "firstName": - return user.getFirstName(); - case "lastName": - return user.getLastName(); - case "avatar": - return user.getAvatar(); - case "name": - return user.getName(); - case "anonymous": - return user.getAnonymous(); - } - return user.getCustom(attribute); - } + } diff --git a/src/main/java/com/launchdarkly/client/LDUser.java b/src/main/java/com/launchdarkly/client/LDUser.java index c10404f9d..398e8a3aa 100644 --- a/src/main/java/com/launchdarkly/client/LDUser.java +++ b/src/main/java/com/launchdarkly/client/LDUser.java @@ -64,6 +64,14 @@ public LDUser(String key) { this.custom = new HashMap<>(); } + protected JsonElement getValueOf(String attribute) { + try { + return UserAttribute.valueOf(attribute).get(this); + } catch (IllegalArgumentException expected) { + return getCustom(attribute); + } + } + JsonPrimitive getKey() { return key; } diff --git a/src/main/java/com/launchdarkly/client/Rule.java b/src/main/java/com/launchdarkly/client/Rule.java index 822d38e07..9075bf416 100644 --- a/src/main/java/com/launchdarkly/client/Rule.java +++ b/src/main/java/com/launchdarkly/client/Rule.java @@ -5,8 +5,6 @@ import java.util.List; -import static com.launchdarkly.client.Clause.valueOf; - /** * Expresses a set of AND-ed matching conditions for a user, along with either the fixed variation or percent rollout * to serve if the conditions match. @@ -52,7 +50,7 @@ Integer variationIndexForUser(LDUser user, String key, String salt) { } private Float bucketUser(LDUser user, String key, String attr, String salt) { - JsonElement userValue = valueOf(user, attr); + JsonElement userValue = user.getValueOf(attr); String idHash; if (userValue != null) { if (userValue.isJsonPrimitive() && userValue.getAsJsonPrimitive().isString()) { diff --git a/src/main/java/com/launchdarkly/client/UserAttribute.java b/src/main/java/com/launchdarkly/client/UserAttribute.java index 8b024e741..c66309377 100644 --- a/src/main/java/com/launchdarkly/client/UserAttribute.java +++ b/src/main/java/com/launchdarkly/client/UserAttribute.java @@ -1,14 +1,58 @@ package com.launchdarkly.client; +import com.google.gson.JsonElement; + enum UserAttribute { - key, - secondary, - ip, - email, - name, - avatar, - firstName, - lastName, - anonymous, - country + key { + JsonElement get(LDUser user) { + return user.getKey(); + } + }, + secondary { + JsonElement get(LDUser user) { + return user.getSecondary(); + } + }, + ip { + JsonElement get(LDUser user) { + return user.getIp(); + } + }, + email { + JsonElement get(LDUser user) { + return user.getEmail(); + } + }, + name { + JsonElement get(LDUser user) { + return user.getName(); + } + }, + avatar { + JsonElement get(LDUser user) { + return user.getAvatar(); + } + }, + firstName { + JsonElement get(LDUser user) { + return user.getFirstName(); + } + }, + lastName { + JsonElement get(LDUser user) { + return user.getLastName(); + } + }, + anonymous { + JsonElement get(LDUser user) { + return user.getAnonymous(); + } + }, + country { + JsonElement get(LDUser user) { + return user.getCountry(); + } + }; + + abstract JsonElement get(LDUser user); } From 52b0d89cfa3f063bd9e9fda69a8ff89b1c91aed2 Mon Sep 17 00:00:00 2001 From: Dan Richelson Date: Thu, 2 Jun 2016 11:00:57 -0700 Subject: [PATCH 3/9] Rename some things. Fix bug when evaluating boolean values. --- .../java/com/launchdarkly/client/Clause.java | 2 +- .../java/com/launchdarkly/client/LDUser.java | 2 +- .../com/launchdarkly/client/Operator.java | 4 ++++ .../java/com/launchdarkly/client/Rule.java | 2 +- .../launchdarkly/client/UserAttribute.java | 22 ++++++++++++------- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/launchdarkly/client/Clause.java b/src/main/java/com/launchdarkly/client/Clause.java index d00ac2588..53c315bb5 100644 --- a/src/main/java/com/launchdarkly/client/Clause.java +++ b/src/main/java/com/launchdarkly/client/Clause.java @@ -17,7 +17,7 @@ class Clause { private boolean negate; boolean matchesUser(LDUser user) { - JsonElement userValue = user.getValueOf(attribute); + JsonElement userValue = user.getValueForEvaluation(attribute); if (userValue == null) { return false; } diff --git a/src/main/java/com/launchdarkly/client/LDUser.java b/src/main/java/com/launchdarkly/client/LDUser.java index 398e8a3aa..270d9a978 100644 --- a/src/main/java/com/launchdarkly/client/LDUser.java +++ b/src/main/java/com/launchdarkly/client/LDUser.java @@ -64,7 +64,7 @@ public LDUser(String key) { this.custom = new HashMap<>(); } - protected JsonElement getValueOf(String attribute) { + protected JsonElement getValueForEvaluation(String attribute) { try { return UserAttribute.valueOf(attribute).get(this); } catch (IllegalArgumentException expected) { diff --git a/src/main/java/com/launchdarkly/client/Operator.java b/src/main/java/com/launchdarkly/client/Operator.java index d2112b968..980e32a84 100644 --- a/src/main/java/com/launchdarkly/client/Operator.java +++ b/src/main/java/com/launchdarkly/client/Operator.java @@ -14,6 +14,10 @@ enum Operator { in { @Override public boolean apply(JsonPrimitive uValue, JsonPrimitive cValue) { + if (uValue.equals(cValue)) { + return true; + } + if (uValue.isString() && cValue.isString()) { if (uValue.getAsString().equals(cValue.getAsString())) return true; diff --git a/src/main/java/com/launchdarkly/client/Rule.java b/src/main/java/com/launchdarkly/client/Rule.java index 9075bf416..93a64eeb2 100644 --- a/src/main/java/com/launchdarkly/client/Rule.java +++ b/src/main/java/com/launchdarkly/client/Rule.java @@ -50,7 +50,7 @@ Integer variationIndexForUser(LDUser user, String key, String salt) { } private Float bucketUser(LDUser user, String key, String attr, String salt) { - JsonElement userValue = user.getValueOf(attr); + JsonElement userValue = user.getValueForEvaluation(attr); String idHash; if (userValue != null) { if (userValue.isJsonPrimitive() && userValue.getAsJsonPrimitive().isString()) { diff --git a/src/main/java/com/launchdarkly/client/UserAttribute.java b/src/main/java/com/launchdarkly/client/UserAttribute.java index c66309377..40f3ce44f 100644 --- a/src/main/java/com/launchdarkly/client/UserAttribute.java +++ b/src/main/java/com/launchdarkly/client/UserAttribute.java @@ -10,7 +10,7 @@ JsonElement get(LDUser user) { }, secondary { JsonElement get(LDUser user) { - return user.getSecondary(); + return null; //Not used for evaluation. } }, ip { @@ -23,11 +23,6 @@ JsonElement get(LDUser user) { return user.getEmail(); } }, - name { - JsonElement get(LDUser user) { - return user.getName(); - } - }, avatar { JsonElement get(LDUser user) { return user.getAvatar(); @@ -43,16 +38,27 @@ JsonElement get(LDUser user) { return user.getLastName(); } }, - anonymous { + name { JsonElement get(LDUser user) { - return user.getAnonymous(); + return user.getName(); } }, country { JsonElement get(LDUser user) { return user.getCountry(); } + }, + anonymous { + JsonElement get(LDUser user) { + return user.getAnonymous(); + } }; + /** + * Gets value for Rule evaluation for a user. + * + * @param user + * @return + */ abstract JsonElement get(LDUser user); } From 64c0304d28e6f7bd01e005e8aae5776cd533e2a0 Mon Sep 17 00:00:00 2001 From: Dan Richelson Date: Thu, 2 Jun 2016 11:54:57 -0700 Subject: [PATCH 4/9] Fallthrough is no longer a regular rule. --- .../com/launchdarkly/client/FeatureFlag.java | 6 +- .../client/FeatureFlagBuilder.java | 4 +- .../java/com/launchdarkly/client/Rule.java | 66 +--------------- .../client/VariationOrRollout.java | 77 +++++++++++++++++++ 4 files changed, 84 insertions(+), 69 deletions(-) create mode 100644 src/main/java/com/launchdarkly/client/VariationOrRollout.java diff --git a/src/main/java/com/launchdarkly/client/FeatureFlag.java b/src/main/java/com/launchdarkly/client/FeatureFlag.java index 6488b5372..53bf9b1db 100644 --- a/src/main/java/com/launchdarkly/client/FeatureFlag.java +++ b/src/main/java/com/launchdarkly/client/FeatureFlag.java @@ -23,7 +23,7 @@ class FeatureFlag { private final String salt; private final List targets; private final List rules; - private final Rule fallthrough; + private final VariationOrRollout fallthrough; private final Integer offVariation; //optional private final List variations; private final boolean deleted; @@ -36,7 +36,7 @@ static Map fromJsonMap(String json) { return gson.fromJson(json, mapType); } - FeatureFlag(String key, int version, boolean on, List prerequisites, String salt, List targets, List rules, Rule fallthrough, Integer offVariation, List variations, boolean deleted) { + FeatureFlag(String key, int version, boolean on, List prerequisites, String salt, List targets, List rules, VariationOrRollout fallthrough, Integer offVariation, List variations, boolean deleted) { this.key = key; this.version = version; this.on = on; @@ -163,7 +163,7 @@ List getRules() { return rules; } - Rule getFallthrough() { + VariationOrRollout getFallthrough() { return fallthrough; } diff --git a/src/main/java/com/launchdarkly/client/FeatureFlagBuilder.java b/src/main/java/com/launchdarkly/client/FeatureFlagBuilder.java index 0369fd735..e9a2ee642 100644 --- a/src/main/java/com/launchdarkly/client/FeatureFlagBuilder.java +++ b/src/main/java/com/launchdarkly/client/FeatureFlagBuilder.java @@ -12,7 +12,7 @@ class FeatureFlagBuilder { private String salt; private List targets; private List rules; - private Rule fallthrough; + private VariationOrRollout fallthrough; private Integer offVariation; private List variations; private boolean deleted; @@ -68,7 +68,7 @@ FeatureFlagBuilder rules(List rules) { return this; } - FeatureFlagBuilder fallthrough(Rule fallthrough) { + FeatureFlagBuilder fallthrough(VariationOrRollout fallthrough) { this.fallthrough = fallthrough; return this; } diff --git a/src/main/java/com/launchdarkly/client/Rule.java b/src/main/java/com/launchdarkly/client/Rule.java index 93a64eeb2..b2095452d 100644 --- a/src/main/java/com/launchdarkly/client/Rule.java +++ b/src/main/java/com/launchdarkly/client/Rule.java @@ -1,8 +1,5 @@ package com.launchdarkly.client; -import com.google.gson.JsonElement; -import org.apache.commons.codec.digest.DigestUtils; - import java.util.List; /** @@ -10,17 +7,12 @@ * to serve if the conditions match. * Invariant: one of the variation or rollout must be non-nil. */ -class Rule { - private static final float long_scale = (float) 0xFFFFFFFFFFFFFFFL; - +class Rule extends VariationOrRollout { private final List clauses; - private final Integer variation; - private final Rollout rollout; Rule(List clauses, Integer variation, Rollout rollout) { + super(variation, rollout); this.clauses = clauses; - this.variation = variation; - this.rollout = rollout; } boolean matchesUser(LDUser user) { @@ -31,58 +23,4 @@ boolean matchesUser(LDUser user) { } return true; } - - Integer variationIndexForUser(LDUser user, String key, String salt) { - if (variation != null) { - return variation; - } else if (rollout != null) { - String bucketBy = rollout.bucketBy == null ? "key" : rollout.bucketBy; - Float bucket = bucketUser(user, key, bucketBy, salt); - Float sum = 0F; - for (WeightedVariation wv : rollout.variations) { - sum += (float) wv.weight / 100000F; - if (bucket < sum) { - return wv.variation; - } - } - } - return null; - } - - private Float bucketUser(LDUser user, String key, String attr, String salt) { - JsonElement userValue = user.getValueForEvaluation(attr); - String idHash; - if (userValue != null) { - if (userValue.isJsonPrimitive() && userValue.getAsJsonPrimitive().isString()) { - idHash = userValue.getAsString(); - if (user.getSecondary() != null) { - idHash = idHash + "." + user.getSecondary().getAsString(); - } - String hash = DigestUtils.sha1Hex(key + "." + salt + "." + idHash).substring(0, 15); - long longVal = Long.parseLong(hash, 16); - return (float) longVal / long_scale; - } - } - return null; - } - - static class Rollout { - private final List variations; - private final String bucketBy; - - Rollout(List variations, String bucketBy) { - this.variations = variations; - this.bucketBy = bucketBy; - } - } - - static class WeightedVariation { - private final int variation; - private final int weight; - - WeightedVariation(int variation, int weight) { - this.variation = variation; - this.weight = weight; - } - } } diff --git a/src/main/java/com/launchdarkly/client/VariationOrRollout.java b/src/main/java/com/launchdarkly/client/VariationOrRollout.java new file mode 100644 index 000000000..c1696c8ae --- /dev/null +++ b/src/main/java/com/launchdarkly/client/VariationOrRollout.java @@ -0,0 +1,77 @@ +package com.launchdarkly.client; + + +import com.google.gson.JsonElement; +import org.apache.commons.codec.digest.DigestUtils; + +import java.util.List; + +/** + * Contains either a fixed variation or percent rollout to serve. + * Invariant: one of the variation or rollout must be non-nil. + */ +class VariationOrRollout { + private static final float long_scale = (float) 0xFFFFFFFFFFFFFFFL; + + private final Integer variation; + private final Rollout rollout; + + VariationOrRollout(Integer variation, Rollout rollout) { + this.variation = variation; + this.rollout = rollout; + } + + Integer variationIndexForUser(LDUser user, String key, String salt) { + if (variation != null) { + return variation; + } else if (rollout != null) { + String bucketBy = rollout.bucketBy == null ? "key" : rollout.bucketBy; + float bucket = bucketUser(user, key, bucketBy, salt); + float sum = 0F; + for (WeightedVariation wv : rollout.variations) { + sum += (float) wv.weight / 100000F; + if (bucket < sum) { + return wv.variation; + } + } + } + return null; + } + + private float bucketUser(LDUser user, String key, String attr, String salt) { + JsonElement userValue = user.getValueForEvaluation(attr); + String idHash; + if (userValue != null) { + if (userValue.isJsonPrimitive() && userValue.getAsJsonPrimitive().isString()) { + idHash = userValue.getAsString(); + if (user.getSecondary() != null) { + idHash = idHash + "." + user.getSecondary().getAsString(); + } + String hash = DigestUtils.sha1Hex(key + "." + salt + "." + idHash).substring(0, 15); + long longVal = Long.parseLong(hash, 16); + return (float) longVal / long_scale; + } + } + return 0F; + } + + static class Rollout { + private final List variations; + private final String bucketBy; + + Rollout(List variations, String bucketBy) { + this.variations = variations; + this.bucketBy = bucketBy; + } + } + + static class WeightedVariation { + private final int variation; + private final int weight; + + WeightedVariation(int variation, int weight) { + this.variation = variation; + this.weight = weight; + } + } +} From 913bbddbcf44b4f11762a488c91903ac04c2d95a Mon Sep 17 00:00:00 2001 From: Dan Richelson Date: Thu, 2 Jun 2016 12:39:38 -0700 Subject: [PATCH 5/9] Add prereq cycle detection test --- .../launchdarkly/client/FeatureFlagTest.java | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/test/java/com/launchdarkly/client/FeatureFlagTest.java diff --git a/src/test/java/com/launchdarkly/client/FeatureFlagTest.java b/src/test/java/com/launchdarkly/client/FeatureFlagTest.java new file mode 100644 index 000000000..2e7fa1dc4 --- /dev/null +++ b/src/test/java/com/launchdarkly/client/FeatureFlagTest.java @@ -0,0 +1,82 @@ +package com.launchdarkly.client; + + +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; + +import static java.util.Collections.singletonList; + +public class FeatureFlagTest { + + private FeatureStore featureStore; + + @Before + public void before() { + featureStore = new InMemoryFeatureStore(); + } + + @Test + public void testPrereqSelfCycle() { + String keyA = "keyA"; + FeatureFlag f = newFlagWithPrereq(keyA, keyA); + + featureStore.upsert(keyA, f); + LDUser user = new LDUser.Builder("userKey").build(); + Assert.assertNull(f.evaluate(user, featureStore)); + } + + @Test + public void testPrereqSimpleCycle() { + String keyA = "keyA"; + String keyB = "keyB"; + FeatureFlag f1 = newFlagWithPrereq(keyA, keyB); + FeatureFlag f2 = newFlagWithPrereq(keyB, keyA); + + featureStore.upsert(f1.getKey(), f1); + featureStore.upsert(f2.getKey(), f2); + LDUser user = new LDUser.Builder("userKey").build(); + Assert.assertNull(f1.evaluate(user, featureStore)); + Assert.assertNull(f2.evaluate(user, featureStore)); + } + + @Test + public void testPrereqCycle() { + String keyA = "keyA"; + String keyB = "keyB"; + String keyC = "keyC"; + FeatureFlag f1 = newFlagWithPrereq(keyA, keyB); + FeatureFlag f2 = newFlagWithPrereq(keyB, keyC); + FeatureFlag f3 = newFlagWithPrereq(keyC, keyA); + + featureStore.upsert(f1.getKey(), f1); + featureStore.upsert(f2.getKey(), f2); + featureStore.upsert(f3.getKey(), f3); + LDUser user = new LDUser.Builder("userKey").build(); + Assert.assertNull(f1.evaluate(user, featureStore)); + Assert.assertNull(f2.evaluate(user, featureStore)); + Assert.assertNull(f3.evaluate(user, featureStore)); + } + + @Test + public void testPrereqDoesNotExist() { + String keyA = "keyA"; + String keyB = "keyB"; + FeatureFlag f1 = newFlagWithPrereq(keyA, keyB); + + featureStore.upsert(f1.getKey(), f1); + LDUser user = new LDUser.Builder("userKey").build(); + Assert.assertNull(f1.evaluate(user, featureStore)); + } + + private FeatureFlag newFlagWithPrereq(String featureKey, String prereqKey) { + return new FeatureFlagBuilder(featureKey) + .prerequisites(singletonList(new Prerequisite(prereqKey, 0))) + .variations(Arrays.asList(new JsonPrimitive(0), new JsonPrimitive(1))) + .build(); + } +} From 0d1b5d4625c2a124cae170352a87f448c0ef4200 Mon Sep 17 00:00:00 2001 From: Dan Richelson Date: Thu, 2 Jun 2016 14:14:35 -0700 Subject: [PATCH 6/9] Always send events when evaluating prereqs --- .../com/launchdarkly/client/FeatureFlag.java | 35 ++++++------- .../client/FeatureFlagBuilder.java | 9 ++-- .../launchdarkly/client/FeatureFlagTest.java | 51 +++++++++++++++++-- 3 files changed, 69 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/launchdarkly/client/FeatureFlag.java b/src/main/java/com/launchdarkly/client/FeatureFlag.java index 53bf9b1db..40f708113 100644 --- a/src/main/java/com/launchdarkly/client/FeatureFlag.java +++ b/src/main/java/com/launchdarkly/client/FeatureFlag.java @@ -70,36 +70,37 @@ EvalResult evaluate(LDUser user, FeatureStore featureStore) { return evaluate(user, featureStore, prereqEvents, visited); } + // Returning either a nil EvalResult or EvalResult.value indicates prereq failure/error. private EvalResult evaluate(LDUser user, FeatureStore featureStore, List events, Set visited) { + boolean prereqOk = true; + EvalResult evalResult = new EvalResult(null, events, visited); for (Prerequisite prereq : prerequisites) { - visited.add(key); - if (visited.contains(prereq.getKey())) { + evalResult.visitedFeatureKeys.add(key); + if (evalResult.visitedFeatureKeys.contains(prereq.getKey())) { logger.error("Prerequisite cycle detected when evaluating feature flag: " + key); return null; } + JsonElement prereqEvalResultValue = null; FeatureFlag prereqFeatureFlag = featureStore.get(prereq.getKey()); if (prereqFeatureFlag == null) { logger.error("Could not retrieve prerequisite flag: " + prereq.getKey() + " when evaluating: " + key); return null; - } - JsonElement prereqValue; - if (prereqFeatureFlag.isOn()) { - EvalResult prereqEvalResult = prereqFeatureFlag.evaluate(user, featureStore, events, visited); - if (prereqEvalResult == null) { - return null; - } - prereqValue = prereqEvalResult.value; - visited = prereqEvalResult.visitedFeatureKeys; - events = prereqEvalResult.prerequisiteEvents; - events.add(new FeatureRequestEvent(prereqFeatureFlag.getKey(), user, prereqValue, null)); - if (prereqValue == null || !prereqValue.equals(prereqFeatureFlag.getVariation(prereq.getVariation()))) { - return new EvalResult(null, events, visited); + } else if (prereqFeatureFlag.isOn()) { + EvalResult prereqEvalResult = prereqFeatureFlag.evaluate(user, featureStore, evalResult.prerequisiteEvents, evalResult.visitedFeatureKeys); + if (prereqEvalResult == null || prereqEvalResult.getValue() == null || !prereqEvalResult.value.equals(prereqFeatureFlag.getVariation(prereq.getVariation()))) { + prereqOk = false; } + prereqEvalResultValue = prereqEvalResult != null ? prereqEvalResult.getValue() : null; } else { - return null; + prereqOk = false; } + //We don't short circuit and also send events for each prereq. + evalResult.prerequisiteEvents.add(new FeatureRequestEvent(prereqFeatureFlag.getKey(), user, prereqEvalResultValue, null)); + } + if (prereqOk) { + evalResult.value = getVariation(evaluateIndex(user)); } - return new EvalResult(getVariation(evaluateIndex(user)), events, visited); + return evalResult; } private Integer evaluateIndex(LDUser user) { diff --git a/src/main/java/com/launchdarkly/client/FeatureFlagBuilder.java b/src/main/java/com/launchdarkly/client/FeatureFlagBuilder.java index e9a2ee642..120764862 100644 --- a/src/main/java/com/launchdarkly/client/FeatureFlagBuilder.java +++ b/src/main/java/com/launchdarkly/client/FeatureFlagBuilder.java @@ -2,19 +2,20 @@ import com.google.gson.JsonElement; +import java.util.ArrayList; import java.util.List; class FeatureFlagBuilder { private String key; private int version; private boolean on; - private List prerequisites; + private List prerequisites = new ArrayList<>(); private String salt; - private List targets; - private List rules; + private List targets = new ArrayList<>(); + private List rules = new ArrayList<>(); private VariationOrRollout fallthrough; private Integer offVariation; - private List variations; + private List variations = new ArrayList<>(); private boolean deleted; FeatureFlagBuilder(String key) { diff --git a/src/test/java/com/launchdarkly/client/FeatureFlagTest.java b/src/test/java/com/launchdarkly/client/FeatureFlagTest.java index 2e7fa1dc4..e12a0b1bd 100644 --- a/src/test/java/com/launchdarkly/client/FeatureFlagTest.java +++ b/src/test/java/com/launchdarkly/client/FeatureFlagTest.java @@ -40,8 +40,8 @@ public void testPrereqSimpleCycle() { featureStore.upsert(f1.getKey(), f1); featureStore.upsert(f2.getKey(), f2); LDUser user = new LDUser.Builder("userKey").build(); - Assert.assertNull(f1.evaluate(user, featureStore)); - Assert.assertNull(f2.evaluate(user, featureStore)); + Assert.assertNull(f1.evaluate(user, featureStore).getValue()); + Assert.assertNull(f2.evaluate(user, featureStore).getValue()); } @Test @@ -57,9 +57,9 @@ public void testPrereqCycle() { featureStore.upsert(f2.getKey(), f2); featureStore.upsert(f3.getKey(), f3); LDUser user = new LDUser.Builder("userKey").build(); - Assert.assertNull(f1.evaluate(user, featureStore)); - Assert.assertNull(f2.evaluate(user, featureStore)); - Assert.assertNull(f3.evaluate(user, featureStore)); + Assert.assertNull(f1.evaluate(user, featureStore).getValue()); + Assert.assertNull(f2.evaluate(user, featureStore).getValue()); + Assert.assertNull(f3.evaluate(user, featureStore).getValue()); } @Test @@ -73,10 +73,51 @@ public void testPrereqDoesNotExist() { Assert.assertNull(f1.evaluate(user, featureStore)); } + @Test + public void testPrereqCollectsEventsForPrereqs() { + String keyA = "keyA"; + String keyB = "keyB"; + String keyC = "keyC"; + FeatureFlag flagA = newFlagWithPrereq(keyA, keyB); + FeatureFlag flagB = newFlagWithPrereq(keyB, keyC); + FeatureFlag flagC = newFlagOff(keyC); + + featureStore.upsert(flagA.getKey(), flagA); + featureStore.upsert(flagB.getKey(), flagB); + featureStore.upsert(flagC.getKey(), flagC); + + LDUser user = new LDUser.Builder("userKey").build(); + + FeatureFlag.EvalResult flagAResult = flagA.evaluate(user, featureStore); + Assert.assertNotNull(flagAResult); + Assert.assertNull(flagAResult.getValue()); + Assert.assertEquals(2, flagAResult.getPrerequisiteEvents().size()); + + FeatureFlag.EvalResult flagBResult = flagB.evaluate(user, featureStore); + Assert.assertNotNull(flagBResult); + Assert.assertNull(flagBResult.getValue()); + Assert.assertEquals(1, flagBResult.getPrerequisiteEvents().size()); + + FeatureFlag.EvalResult flagCResult = flagC.evaluate(user, featureStore); + Assert.assertNotNull(flagCResult); + Assert.assertEquals(new JsonPrimitive(0), flagCResult.getValue()); + Assert.assertEquals(0, flagCResult.getPrerequisiteEvents().size()); + } + private FeatureFlag newFlagWithPrereq(String featureKey, String prereqKey) { return new FeatureFlagBuilder(featureKey) .prerequisites(singletonList(new Prerequisite(prereqKey, 0))) .variations(Arrays.asList(new JsonPrimitive(0), new JsonPrimitive(1))) + .fallthrough(new VariationOrRollout(0, null)) + .on(true) + .build(); + } + + private FeatureFlag newFlagOff(String featureKey) { + return new FeatureFlagBuilder(featureKey) + .variations(Arrays.asList(new JsonPrimitive(0), new JsonPrimitive(1))) + .fallthrough(new VariationOrRollout(0, null)) + .on(false) .build(); } } From a52e009ab8f151123a09536102d6828899e8426a Mon Sep 17 00:00:00 2001 From: Dan Richelson Date: Thu, 2 Jun 2016 15:34:09 -0700 Subject: [PATCH 7/9] Build and deploy fat shaded jar as default artifact. Update gradle + wrapper. --- build.gradle | 28 +++++++++--- gradle/wrapper/gradle-wrapper.jar | Bin 51017 -> 53328 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 52 +++++++++++------------ gradlew.bat | 8 ++-- 5 files changed, 54 insertions(+), 38 deletions(-) diff --git a/build.gradle b/build.gradle index 30f6c0d05..df536256d 100644 --- a/build.gradle +++ b/build.gradle @@ -40,14 +40,17 @@ dependencies { } jar { - baseName = 'launchdarkly-client' - manifest { - attributes("Implementation-Version": version) - } + baseName = 'launchdarkly-client' + // thin classifier means that the non-shaded non-fat jar is still available + // but is opt-in since users will have to specify it. + classifier = 'thin' + manifest { + attributes("Implementation-Version": version) + } } task wrapper(type: Wrapper) { - gradleVersion = '2.0' + gradleVersion = '2.14-rc-3' } buildscript { @@ -90,7 +93,20 @@ githubPages { } shadowJar { - classifier = 'all' + baseName = 'launchdarkly-client' + //no classifier means that the fat + shaded jar becomes the default artifact + classifier = '' + + // Shade all jars except for launchdarkly + relocate('com.google', 'com.launchdarkly.shaded.com.google') + relocate('okhttp3', 'com.launchdarkly.shaded.okhttp3') + relocate('okio', 'com.launchdarkly.shaded.okio') + relocate('org', 'com.launchdarkly.shaded.org') + relocate('redis', 'com.launchdarkly.shaded.redis') + + manifest { + attributes("Implementation-Version": version) + } } test { diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index b7612167031001b7b84baf2a959e8ea8ad03c011..3baa6058a97c40591863ceeac6e73fc8cb53c1e4 100644 GIT binary patch delta 28972 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 G?f(HPS(lsu delta 26707 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,31 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -90,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -114,6 +113,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` diff --git a/gradlew.bat b/gradlew.bat index aec99730b..f6d5974e7 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,7 +46,7 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args From 0cbeb86605666bc53939c29a31a9fb53eda191b3 Mon Sep 17 00:00:00 2001 From: Dan Richelson Date: Thu, 2 Jun 2016 16:14:09 -0700 Subject: [PATCH 8/9] Better shading --- build.gradle | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index df536256d..837bce896 100644 --- a/build.gradle +++ b/build.gradle @@ -98,10 +98,18 @@ shadowJar { classifier = '' // Shade all jars except for launchdarkly - relocate('com.google', 'com.launchdarkly.shaded.com.google') + relocate('com', 'com.launchdarkly.shaded.com') { + exclude("com.launchdarkly.client.*") + exclude("com.launchdarkly.eventsource.*") + } relocate('okhttp3', 'com.launchdarkly.shaded.okhttp3') relocate('okio', 'com.launchdarkly.shaded.okio') - relocate('org', 'com.launchdarkly.shaded.org') + relocate('org', 'com.launchdarkly.shaded.org') { + exclude("org.slf4j.*") + exclude("org.slf4j.event.*") + exclude("org.slf4j.helpers.*") + exclude("org.slf4j.spi.*") + } relocate('redis', 'com.launchdarkly.shaded.redis') manifest { From 2d6b5ea6b83a68785ba55b3cada1f1ecfc67de0d Mon Sep 17 00:00:00 2001 From: Dan Richelson Date: Tue, 7 Jun 2016 10:11:48 -0700 Subject: [PATCH 9/9] Don't shade Gson since we expose it via our public API. --- build.gradle | 8 ++++++++ gradle/wrapper/gradle-wrapper.properties | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 837bce896..2e9b5b961 100644 --- a/build.gradle +++ b/build.gradle @@ -101,6 +101,14 @@ shadowJar { relocate('com', 'com.launchdarkly.shaded.com') { exclude("com.launchdarkly.client.*") exclude("com.launchdarkly.eventsource.*") + exclude("com.google.gson.*") + exclude("com.google.gson.annotations.*") + exclude("com.google.gson.internal.*") + exclude("com.google.gson.internal.bind.*") + exclude("com.google.gson.internal.bind.util.*") + exclude("com.google.gson.internal.bind.util.*") + exclude("com.google.gson.reflect.*") + exclude("com.google.gson.stream.*") } relocate('okhttp3', 'com.launchdarkly.shaded.okhttp3') relocate('okio', 'com.launchdarkly.shaded.okio') diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3871b3a86..24b15b161 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Jun 02 15:33:07 PDT 2016 +#Tue Jun 07 10:02:02 PDT 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-rc-3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-rc-3-all.zip