-
Notifications
You must be signed in to change notification settings - Fork 55
More v2 changes #55
More v2 changes #55
Changes from all commits
34670e3
f2524eb
52b0d89
64c0304
913bbdd
0d1b5d4
a52e009
0cbeb86
2d6b5ea
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 |
|---|---|---|
|
|
@@ -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,36 @@ 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', 'com.launchdarkly.shaded.com') { | ||
|
Contributor
Author
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. The potential problem with shading Gson is that we return a Gson type (JsonElement) from our public interface: https://github.com/launchdarkly/java-client/blob/fa61ceb5f343c53db01f88b417a623575ab9fc58/src/main/java/com/launchdarkly/client/LDClient.java#L298-L298 This means that if a user's code already imports Gson this JsonElement is a different type than they might be expecting.
Contributor
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. Hmm. I hadn't considered that when I made the suggestion to shade Gson. I think we should not shade it then-- leaking a shaded class into an external interface isn't a good practice. In addition to just having the wrong type, interacting with the JsonElement is going to be hard-- you'll have to manipulate the shaded API, which exposes more shaded footprint. 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. It might be appropriate to reconsider this approach. In my project, SBT (sbt-assembly) got confused by |
||
| 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') | ||
| relocate('org', 'com.launchdarkly.shaded.org') { | ||
| exclude("org.slf4j.*") | ||
|
Contributor
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. Good catch on excluding the logging facade. |
||
| exclude("org.slf4j.event.*") | ||
| exclude("org.slf4j.helpers.*") | ||
| exclude("org.slf4j.spi.*") | ||
| } | ||
| relocate('redis', 'com.launchdarkly.shaded.redis') | ||
|
|
||
| manifest { | ||
| attributes("Implementation-Version": version) | ||
| } | ||
| } | ||
|
|
||
| test { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| #Mon Aug 11 16:15:27 PDT 2014 | ||
| #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.0-all.zip | ||
| distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-rc-3-all.zip |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,12 +6,30 @@ | |
| ## | ||
| ############################################################################## | ||
|
|
||
| # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||
| DEFAULT_JVM_OPTS="" | ||
| # Attempt to set APP_HOME | ||
|
Contributor
Author
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. Updated gradle wrapper |
||
| # 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\"`/" >/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` | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,7 +17,7 @@ class Clause { | |
| private boolean negate; | ||
|
|
||
| boolean matchesUser(LDUser user) { | ||
| JsonElement userValue = valueOf(user, attribute); | ||
| JsonElement userValue = user.getValueForEvaluation(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); | ||
| } | ||
|
|
||
|
Contributor
Author
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. Moved this behavior to the UserAttribute enum + LDUser class |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,7 +23,7 @@ class FeatureFlag { | |
| private final String salt; | ||
| private final List<Target> targets; | ||
| private final List<Rule> rules; | ||
| private final Rule fallthrough; | ||
| private final VariationOrRollout fallthrough; | ||
|
Contributor
Author
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. Because fallthrough isn't a rule. It should never have clauses |
||
| private final Integer offVariation; //optional | ||
| private final List<JsonElement> variations; | ||
| private final boolean deleted; | ||
|
|
@@ -36,7 +36,7 @@ static Map<String, FeatureFlag> fromJsonMap(String json) { | |
| return gson.fromJson(json, mapType); | ||
| } | ||
|
|
||
| FeatureFlag(String key, int version, boolean on, List<Prerequisite> prerequisites, String salt, List<Target> targets, List<Rule> rules, Rule fallthrough, Integer offVariation, List<JsonElement> variations, boolean deleted) { | ||
| FeatureFlag(String key, int version, boolean on, List<Prerequisite> prerequisites, String salt, List<Target> targets, List<Rule> rules, VariationOrRollout fallthrough, Integer offVariation, List<JsonElement> variations, boolean deleted) { | ||
| this.key = key; | ||
| this.version = version; | ||
| this.on = on; | ||
|
|
@@ -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<FeatureRequestEvent> events, Set<String> visited) { | ||
|
Contributor
Author
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. Changed logic a bit so we always send feature request events for all prereqs unless there is a data integrity problem: ie prereq cycle, or a prereq is not found in the feature store. |
||
| 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) { | ||
|
|
@@ -163,7 +164,7 @@ List<Rule> getRules() { | |
| return rules; | ||
| } | ||
|
|
||
| Rule getFallthrough() { | ||
| VariationOrRollout getFallthrough() { | ||
| return fallthrough; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,6 +64,14 @@ public LDUser(String key) { | |
| this.custom = new HashMap<>(); | ||
| } | ||
|
|
||
| protected JsonElement getValueForEvaluation(String attribute) { | ||
|
Contributor
Author
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. Moved this to LDUser since it is user behavior |
||
| try { | ||
| return UserAttribute.valueOf(attribute).get(this); | ||
| } catch (IllegalArgumentException expected) { | ||
| return getCustom(attribute); | ||
| } | ||
| } | ||
|
|
||
| JsonPrimitive getKey() { | ||
| return key; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,10 @@ enum Operator { | |
| in { | ||
| @Override | ||
| public boolean apply(JsonPrimitive uValue, JsonPrimitive cValue) { | ||
| if (uValue.equals(cValue)) { | ||
|
Contributor
Author
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. Without this we were failing to match on basic equality for boolean values. |
||
| return true; | ||
| } | ||
|
|
||
| if (uValue.isString() && cValue.isString()) { | ||
| if (uValue.getAsString().equals(cValue.getAsString())) | ||
| return true; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,13 @@ | ||
| package com.launchdarkly.client; | ||
|
|
||
| class Prerequisite { | ||
| private String key; | ||
| private int variation; | ||
| private final String key; | ||
|
Contributor
Author
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. made this and many more things private + final |
||
| private final int variation; | ||
|
|
||
| Prerequisite(String key, int variation) { | ||
| this.key = key; | ||
| this.variation = variation; | ||
| } | ||
|
|
||
| String getKey() { | ||
| return key; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the version mentioned here: https://github.com/johnrengelman/shadow