Skip to content
This repository was archived by the owner on May 30, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
98555fc
change FeatureStore to a generic store with multiple namespaces
eli-darkly Jan 22, 2018
e31cef4
Merge branch 'eb/ch10839/fix-upsert-after-delete' into eb/spike-gener…
eli-darkly Jan 22, 2018
9e9d077
comments
eli-darkly Jan 23, 2018
3a8e58c
comments
eli-darkly Jan 23, 2018
d1803be
support segments
eli-darkly Jan 24, 2018
46e6791
fix logger params
eli-darkly Jan 24, 2018
b37cb05
Merge branch 'eb/spike-generic-store' into eb/segments-with-generic-s…
eli-darkly Jan 24, 2018
0d84dd1
don't need MatchResult
eli-darkly Jan 24, 2018
0306be2
simplify path parsing logic
eli-darkly Jan 24, 2018
0bcde0d
Merge branch 'master' into eb/segments-with-generic-store
eli-darkly Jan 25, 2018
198b42e
version, changelog
eli-darkly Jan 25, 2018
7aca37c
whitespace
eli-darkly Jan 25, 2018
84e0006
don't update changelog yet
eli-darkly Jan 25, 2018
abd8b21
don't bump version yet
eli-darkly Jan 26, 2018
7217900
make internal-use method non-public, but document it too
eli-darkly Jan 29, 2018
63f5d82
Merge pull request #43 from launchdarkly/eb/segments-with-generic-store
eli-darkly Jan 30, 2018
a7641fe
Merge branch 'master' into segments
eli-darkly Feb 5, 2018
f9efb7c
make Segment class package-private
eli-darkly Feb 6, 2018
c7af3bb
add unit tests for fetching segments during flag eval
eli-darkly Feb 6, 2018
073793f
undo import order changes
eli-darkly Feb 7, 2018
2310855
Merge pull request #47 from launchdarkly/eb/segments-fixes
eli-darkly Feb 7, 2018
c2ce883
misc javadoc fixes
eli-darkly Feb 12, 2018
7919ac2
link to LD-relay docs
eli-darkly Feb 12, 2018
ca35d98
Merge pull request #49 from launchdarkly/eb/javadoc-fixes
eli-darkly Feb 12, 2018
8ffa0db
cleanup doc comments and remove deprecated members
eli-darkly Feb 21, 2018
e90c4aa
Merge pull request #50 from launchdarkly/eb/doc-cleanup-and-deprecations
eli-darkly Feb 21, 2018
77514d9
Merge branch 'master' into segments
eli-darkly Feb 21, 2018
9a1c8ea
bump version
eli-darkly Feb 21, 2018
77e737d
make a few more classes package-private
eli-darkly Feb 21, 2018
8b9de3d
one more thing to make package-private
eli-darkly Feb 21, 2018
0189ca0
Merge pull request #51 from launchdarkly/eb/package-private
eli-darkly Feb 21, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
version=2.6.0
version=3.0.0
ossrhUsername=
ossrhPassword=
36 changes: 34 additions & 2 deletions src/main/java/com/launchdarkly/client/Clause.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.launchdarkly.client.VersionedDataKind.SEGMENTS;

import java.util.List;

class Clause {
Expand All @@ -16,7 +18,17 @@ class Clause {
private List<JsonPrimitive> values; //interpreted as an OR of values
private boolean negate;

boolean matchesUser(LDUser user) {
public Clause() {
}

public Clause(String attribute, Operator op, List<JsonPrimitive> values, boolean negate) {
this.attribute = attribute;
this.op = op;
this.values = values;
this.negate = negate;
}

boolean matchesUserNoSegments(LDUser user) {
JsonElement userValue = user.getValueForEvaluation(attribute);
if (userValue == null) {
return false;
Expand All @@ -42,6 +54,26 @@ boolean matchesUser(LDUser user) {
return false;
}

boolean matchesUser(FeatureStore store, LDUser user) {
// In the case of a segment match operator, we check if the user is in any of the segments,
// and possibly negate
if (op == Operator.segmentMatch) {
for (JsonPrimitive j: values) {
if (j.isString()) {
Segment segment = store.get(SEGMENTS, j.getAsString());
if (segment != null) {
if (segment.matchesUser(user)) {
return maybeNegate(true);
}
}
}
}
return maybeNegate(false);
}

return matchesUserNoSegments(user);
}

private boolean matchAny(JsonPrimitive userValue) {
for (JsonPrimitive v : values) {
if (op.apply(userValue, v)) {
Expand All @@ -59,4 +91,4 @@ private boolean maybeNegate(boolean b) {
}


}
}
18 changes: 10 additions & 8 deletions src/main/java/com/launchdarkly/client/FeatureFlag.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.launchdarkly.client.VersionedDataKind.FEATURES;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

class FeatureFlag {
class FeatureFlag implements VersionedData {
private final static Logger logger = LoggerFactory.getLogger(FeatureFlag.class);

private static final Type mapType = new TypeToken<Map<String, FeatureFlag>>() {
Expand Down Expand Up @@ -76,7 +78,7 @@ private JsonElement evaluate(LDUser user, FeatureStore featureStore, List<Featur
boolean prereqOk = true;
if (prerequisites != null) {
for (Prerequisite prereq : prerequisites) {
FeatureFlag prereqFeatureFlag = featureStore.get(prereq.getKey());
FeatureFlag prereqFeatureFlag = featureStore.get(FEATURES, prereq.getKey());
JsonElement prereqEvalResult = null;
if (prereqFeatureFlag == null) {
logger.error("Could not retrieve prerequisite flag: " + prereq.getKey() + " when evaluating: " + key);
Expand All @@ -100,12 +102,12 @@ private JsonElement evaluate(LDUser user, FeatureStore featureStore, List<Featur
}
}
if (prereqOk) {
return getVariation(evaluateIndex(user));
return getVariation(evaluateIndex(user, featureStore));
}
return null;
}

private Integer evaluateIndex(LDUser user) {
private Integer evaluateIndex(LDUser user, FeatureStore store) {
// Check to see if targets match
if (targets != null) {
for (Target target : targets) {
Expand All @@ -119,7 +121,7 @@ private Integer evaluateIndex(LDUser user) {
// Now walk through the rules and see if any match
if (rules != null) {
for (Rule rule : rules) {
if (rule.matchesUser(user)) {
if (rule.matchesUser(store, user)) {
return rule.variationIndexForUser(user, key, salt);
}
}
Expand Down Expand Up @@ -156,15 +158,15 @@ else if (index >= variations.size()) {
}
}

int getVersion() {
public int getVersion() {
return version;
}

String getKey() {
public String getKey() {
return key;
}

boolean isDeleted() {
public boolean isDeleted() {
return deleted;
}

Expand Down
40 changes: 39 additions & 1 deletion src/main/java/com/launchdarkly/client/FeatureRequestor.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,31 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.launchdarkly.client.VersionedDataKind.FEATURES;
import static com.launchdarkly.client.VersionedDataKind.SEGMENTS;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

class FeatureRequestor {
private static final Logger logger = LoggerFactory.getLogger(FeatureRequestor.class);
private static final String GET_LATEST_FLAGS_PATH = "/sdk/latest-flags";
private static final String GET_LATEST_SEGMENTS_PATH = "/sdk/latest-segments";
private static final String GET_LATEST_ALL_PATH = "/sdk/latest-all";
private final String sdkKey;
private final LDConfig config;

static class AllData {
final Map<String, FeatureFlag> flags;
final Map<String, Segment> segments;

AllData(Map<String, FeatureFlag> flags, Map<String, Segment> segments) {
this.flags = flags;
this.segments = segments;
}
}

FeatureRequestor(String sdkKey, LDConfig config) {
this.sdkKey = sdkKey;
this.config = config;
Expand All @@ -29,6 +45,28 @@ FeatureFlag getFlag(String featureKey) throws IOException, InvalidSDKKeyExceptio
return FeatureFlag.fromJson(config, body);
}

Map<String, Segment> getAllSegments() throws IOException, InvalidSDKKeyException {
String body = get(GET_LATEST_SEGMENTS_PATH);
return Segment.fromJsonMap(config, body);
}

Segment getSegment(String segmentKey) throws IOException, InvalidSDKKeyException {
String body = get(GET_LATEST_SEGMENTS_PATH + "/" + segmentKey);
return Segment.fromJson(config, body);
}

AllData getAllData() throws IOException, InvalidSDKKeyException {
String body = get(GET_LATEST_ALL_PATH);
return config.gson.fromJson(body, AllData.class);
}

static Map<VersionedDataKind<?>, Map<String, ? extends VersionedData>> toVersionedDataMap(AllData allData) {
Map<VersionedDataKind<?>, Map<String, ? extends VersionedData>> ret = new HashMap<>();
ret.put(FEATURES, allData.flags);
ret.put(SEGMENTS, allData.segments);
return ret;
}

private String get(String path) throws IOException, InvalidSDKKeyException {
Request request = config.getRequestBuilder(sdkKey)
.url(config.baseURI.toString() + path)
Expand Down Expand Up @@ -64,4 +102,4 @@ public static class InvalidSDKKeyException extends Exception {
public InvalidSDKKeyException() {
}
}
}
}
71 changes: 37 additions & 34 deletions src/main/java/com/launchdarkly/client/FeatureStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,70 +4,73 @@
import java.util.Map;

/**
* A thread-safe, versioned store for {@link FeatureFlag} objects.
* Implementations should permit concurrent access and updates.
*
* A thread-safe, versioned store for feature flags and related objects received from the
* streaming API. Implementations should permit concurrent access and updates.
* <p>
* Delete and upsert requests are versioned-- if the version number in the request is less than
* the currently stored version of the feature, the request should be ignored.
*
* the currently stored version of the object, the request should be ignored.
* <p>
* These semantics support the primary use case for the store, which synchronizes a collection
* of features based on update messages that may be received out-of-order.
*
* of objects based on update messages that may be received out-of-order.
* @since 3.0.0
*/
public interface FeatureStore extends Closeable {
/**
*
* Returns the {@link FeatureFlag} to which the specified key is mapped, or
* null if the key is not associated or the associated {@link FeatureFlag} has
* Returns the object to which the specified key is mapped, or
* null if the key is not associated or the associated object has
* been deleted.
*
* @param key the key whose associated {@link FeatureFlag} is to be returned
* @return the {@link FeatureFlag} to which the specified key is mapped, or
* null if the key is not associated or the associated {@link FeatureFlag} has
* @param <T> class of the object that will be returned
* @param kind the kind of object to get
* @param key the key whose associated object is to be returned
* @return the object to which the specified key is mapped, or
* null if the key is not associated or the associated object has
* been deleted.
*/
FeatureFlag get(String key);
<T extends VersionedData> T get(VersionedDataKind<T> kind, String key);

/**
* Returns a {@link java.util.Map} of all associated features.
*
* Returns a {@link java.util.Map} of all associated objects of a given kind.
*
* @return a map of all associated features.
* @param <T> class of the objects that will be returned in the map
* @param kind the kind of objects to get
* @return a map of all associated object.
*/
Map<String, FeatureFlag> all();
<T extends VersionedData> Map<String, T> all(VersionedDataKind<T> kind);

/**
* Initializes (or re-initializes) the store with the specified set of features. Any existing entries
* will be removed. Implementations can assume that this set of features is up to date-- there is no
* need to perform individual version comparisons between the existing features and the supplied
* Initializes (or re-initializes) the store with the specified set of objects. Any existing entries
* will be removed. Implementations can assume that this set of objects is up to date-- there is no
* need to perform individual version comparisons between the existing objects and the supplied
* features.
*
*
* @param features the features to set the store
* @param allData all objects to be stored
*/
void init(Map<String, FeatureFlag> features);
void init(Map<VersionedDataKind<?>, Map<String, ? extends VersionedData>> allData);

/**
*
* Deletes the feature associated with the specified key, if it exists and its version
* Deletes the object associated with the specified key, if it exists and its version
* is less than or equal to the specified version.
*
* @param key the key of the feature to be deleted
* @param <T> class of the object to be deleted
* @param kind the kind of object to delete
* @param key the key of the object to be deleted
* @param version the version for the delete operation
*/
void delete(String key, int version);
<T extends VersionedData> void delete(VersionedDataKind<T> kind, String key, int version);

/**
* Update or insert the feature associated with the specified key, if its version
* is less than or equal to the version specified in the argument feature.
* Update or insert the object associated with the specified key, if its version
* is less than or equal to the version specified in the argument object.
*
* @param key
* @param feature
* @param <T> class of the object to be updated
* @param kind the kind of object to update
* @param item the object to update or insert
*/
void upsert(String key, FeatureFlag feature);
<T extends VersionedData> void upsert(VersionedDataKind<T> kind, T item);

/**
* Returns true if this store has been initialized
* Returns true if this store has been initialized.
*
* @return true if this store has been initialized
*/
Expand Down
Loading