diff --git a/client/src/main/java/io/split/Spec.java b/client/src/main/java/io/split/Spec.java
index 9e03a59ab..847e19e1c 100644
--- a/client/src/main/java/io/split/Spec.java
+++ b/client/src/main/java/io/split/Spec.java
@@ -6,6 +6,8 @@ private Spec() {
// restrict instantiation
}
- public static final String SPEC_VERSION = "1.1";
+ public static String SPEC_VERSION = "1.3";
+ public static final String SPEC_1_3 = "1.3";
+ public static final String SPEC_1_1 = "1.1";
}
diff --git a/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java b/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java
index a3e234a3e..980bc067c 100644
--- a/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java
+++ b/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java
@@ -2,6 +2,7 @@
import com.google.common.annotations.VisibleForTesting;
+import io.split.Spec;
import io.split.client.dtos.SplitChange;
import io.split.client.dtos.SplitHttpResponse;
import io.split.client.exceptions.UriTooLongException;
@@ -31,6 +32,7 @@ public final class HttpSplitChangeFetcher implements SplitChangeFetcher {
private static final Logger _log = LoggerFactory.getLogger(HttpSplitChangeFetcher.class);
private static final String SINCE = "since";
+ private static final String RB_SINCE = "rbSince";
private static final String TILL = "till";
private static final String SETS = "sets";
private static final String SPEC = "s";
@@ -56,38 +58,48 @@ long makeRandomTill() {
}
@Override
- public SplitChange fetch(long since, FetchOptions options) {
+ public SplitChange fetch(long since, long sinceRBS, FetchOptions options) {
long start = System.currentTimeMillis();
+ for (int i=0; i<2; i++) {
+ try {
+ URIBuilder uriBuilder = new URIBuilder(_target).addParameter(SPEC, "" + SPEC_VERSION);
+ uriBuilder.addParameter(SINCE, "" + since);
+ if (SPEC_VERSION.equals(Spec.SPEC_1_3)) {
+ uriBuilder.addParameter(RB_SINCE, "" + sinceRBS);
+ }
+ if (!options.flagSetsFilter().isEmpty()) {
+ uriBuilder.addParameter(SETS, "" + options.flagSetsFilter());
+ }
+ if (options.hasCustomCN()) {
+ uriBuilder.addParameter(TILL, "" + options.targetCN());
+ }
+ URI uri = uriBuilder.build();
+ SplitHttpResponse response = _client.get(uri, options, null);
- try {
- URIBuilder uriBuilder = new URIBuilder(_target).addParameter(SPEC, "" + SPEC_VERSION);
- uriBuilder.addParameter(SINCE, "" + since);
- if (!options.flagSetsFilter().isEmpty()) {
- uriBuilder.addParameter(SETS, "" + options.flagSetsFilter());
- }
- if (options.hasCustomCN()) {
- uriBuilder.addParameter(TILL, "" + options.targetCN());
- }
- URI uri = uriBuilder.build();
- SplitHttpResponse response = _client.get(uri, options, null);
-
- if (response.statusCode() < HttpStatus.SC_OK || response.statusCode() >= HttpStatus.SC_MULTIPLE_CHOICES) {
- if (response.statusCode() == HttpStatus.SC_REQUEST_URI_TOO_LONG) {
- _log.error("The amount of flag sets provided are big causing uri length error.");
- throw new UriTooLongException(String.format("Status code: %s. Message: %s", response.statusCode(), response.statusMessage()));
+ if (response.statusCode() < HttpStatus.SC_OK || response.statusCode() >= HttpStatus.SC_MULTIPLE_CHOICES) {
+ if (response.statusCode() == HttpStatus.SC_REQUEST_URI_TOO_LONG) {
+ _log.error("The amount of flag sets provided are big causing uri length error.");
+ throw new UriTooLongException(String.format("Status code: %s. Message: %s", response.statusCode(), response.statusMessage()));
+ }
+ if (response.statusCode() == HttpStatus.SC_BAD_REQUEST && response.statusMessage().equals("unknown spec")) {
+ _log.warn(String.format("Detected old spec response, falling back to spec 1.1"));
+ SPEC_VERSION = Spec.SPEC_1_1;
+ continue;
+ }
+ _telemetryRuntimeProducer.recordSyncError(ResourceEnum.SPLIT_SYNC, response.statusCode());
+ throw new IllegalStateException(
+ String.format("Could not retrieve splitChanges since %s; http return code %s", since, response.statusCode())
+ );
}
- _telemetryRuntimeProducer.recordSyncError(ResourceEnum.SPLIT_SYNC, response.statusCode());
- throw new IllegalStateException(
- String.format("Could not retrieve splitChanges since %s; http return code %s", since, response.statusCode())
- );
+ return Json.fromJson(response.body(), SplitChange.class);
+ } catch (Exception e) {
+ throw new IllegalStateException(String.format("Problem fetching splitChanges since %s: %s", since, e), e);
+ } finally {
+ _telemetryRuntimeProducer.recordSyncLatency(HTTPLatenciesEnum.SPLITS, System.currentTimeMillis() - start);
}
- return Json.fromJson(response.body(), SplitChange.class);
- } catch (Exception e) {
- throw new IllegalStateException(String.format("Problem fetching splitChanges since %s: %s", since, e), e);
- } finally {
- _telemetryRuntimeProducer.recordSyncLatency(HTTPLatenciesEnum.SPLITS, System.currentTimeMillis()-start);
}
+ return null;
}
@VisibleForTesting
diff --git a/client/src/main/java/io/split/client/JsonLocalhostSplitChangeFetcher.java b/client/src/main/java/io/split/client/JsonLocalhostSplitChangeFetcher.java
index e2cb5d5c9..502d44182 100644
--- a/client/src/main/java/io/split/client/JsonLocalhostSplitChangeFetcher.java
+++ b/client/src/main/java/io/split/client/JsonLocalhostSplitChangeFetcher.java
@@ -29,7 +29,7 @@ public JsonLocalhostSplitChangeFetcher(InputStreamProvider inputStreamProvider)
}
@Override
- public SplitChange fetch(long since, FetchOptions options) {
+ public SplitChange fetch(long since, long RBsince, FetchOptions options) {
try {
JsonReader jsonReader = new JsonReader(new BufferedReader(new InputStreamReader(_inputStreamProvider.get(), StandardCharsets.UTF_8)));
SplitChange splitChange = Json.fromJson(jsonReader, SplitChange.class);
diff --git a/client/src/main/java/io/split/client/LegacyLocalhostSplitChangeFetcher.java b/client/src/main/java/io/split/client/LegacyLocalhostSplitChangeFetcher.java
index a35c92cfe..f2f83d653 100644
--- a/client/src/main/java/io/split/client/LegacyLocalhostSplitChangeFetcher.java
+++ b/client/src/main/java/io/split/client/LegacyLocalhostSplitChangeFetcher.java
@@ -34,7 +34,7 @@ public LegacyLocalhostSplitChangeFetcher(String directory) {
}
@Override
- public SplitChange fetch(long since, FetchOptions options) {
+ public SplitChange fetch(long since, long sinceRBS, FetchOptions options) {
try (BufferedReader reader = new BufferedReader(new FileReader(_splitFile))) {
SplitChange splitChange = new SplitChange();
diff --git a/client/src/main/java/io/split/client/SplitClient.java b/client/src/main/java/io/split/client/SplitClient.java
index a07718b1f..e12b5319f 100644
--- a/client/src/main/java/io/split/client/SplitClient.java
+++ b/client/src/main/java/io/split/client/SplitClient.java
@@ -2,6 +2,7 @@
import io.split.client.api.Key;
import io.split.client.api.SplitResult;
+import io.split.client.dtos.EvaluationOptions;
import java.util.List;
import java.util.Map;
@@ -96,7 +97,7 @@ public interface SplitClient {
/**
* Same as {@link #getTreatment(String, String)} but it returns the configuration associated to the
- * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
*
*
* Examples include showing a different treatment to users on trial plan
@@ -112,7 +113,7 @@ public interface SplitClient {
/**
* Same as {@link #getTreatment(String, String, Map)} but it returns the configuration associated to the
- * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
*
*
* Examples include showing a different treatment to users on trial plan
@@ -129,7 +130,7 @@ public interface SplitClient {
/**
* Same as {@link #getTreatment(Key, String, Map)} but it returns the configuration associated to the
- * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
*
* @param key the matching and bucketing keys. MUST NOT be null.
* @param featureFlagName the name of the feature flag we want to evaluate. MUST NOT be null.
@@ -224,7 +225,7 @@ public interface SplitClient {
/**
* Same as {@link #getTreatments(String, List)} but it returns the configuration associated to the
- * matching treatments if any. Otherwise {@link SplitResult.configurations()} will be null.
+ * matching treatments if any. Otherwise {@link SplitResult.config()} will be null.
*
*
* Examples include showing a different treatment to users on trial plan
@@ -240,7 +241,7 @@ public interface SplitClient {
/**
* Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
- * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
*
*
* Examples include showing a different treatment to users on trial plan
@@ -257,7 +258,7 @@ public interface SplitClient {
/**
* Same as {@link #getTreatments(Key, List, Map)} but it returns for each feature flag the configuration associated to the
- * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
*
* @param key the matching and bucketing keys. MUST NOT be null.
* @param featureFlagNames the names of the feature flags we want to evaluate. MUST NOT be null.
@@ -270,7 +271,7 @@ public interface SplitClient {
/**
* Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
- * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
*
*
* Examples include showing a different treatment to users on trial plan
@@ -285,7 +286,7 @@ public interface SplitClient {
/**
* Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
- * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
*
*
* Examples include showing a different treatment to users on trial plan
@@ -301,7 +302,7 @@ public interface SplitClient {
/**
* Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
- * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
*
*
* Examples include showing a different treatment to users on trial plan
@@ -317,7 +318,7 @@ public interface SplitClient {
/**
* Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
- * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
*
*
* Examples include showing a different treatment to users on trial plan
@@ -332,7 +333,7 @@ public interface SplitClient {
/**
* Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
- * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
*
*
* Examples include showing a different treatment to users on trial plan
@@ -348,7 +349,7 @@ public interface SplitClient {
/**
* Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
- * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
*
*
* Examples include showing a different treatment to users on trial plan
@@ -364,7 +365,7 @@ public interface SplitClient {
/**
* Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
- * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
*
*
* Examples include showing a different treatment to users on trial plan
@@ -380,7 +381,7 @@ public interface SplitClient {
/**
* Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
- * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
*
*
* Examples include showing a different treatment to users on trial plan
@@ -397,7 +398,7 @@ public interface SplitClient {
/**
* Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
- * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
*
*
* Examples include showing a different treatment to users on trial plan
@@ -414,7 +415,7 @@ public interface SplitClient {
/**
* Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
- * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
*
*
* Examples include showing a different treatment to users on trial plan
@@ -430,7 +431,7 @@ public interface SplitClient {
/**
* Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
- * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
*
*
* Examples include showing a different treatment to users on trial plan
@@ -447,7 +448,7 @@ public interface SplitClient {
/**
* Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
- * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null.
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
*
*
* Examples include showing a different treatment to users on trial plan
@@ -462,6 +463,488 @@ public interface SplitClient {
*/
Map getTreatmentsWithConfigByFlagSets(Key key, List flagSets, Map attributes);
+ /**
+ * Returns the treatment to show this key for this feature flag. The set of treatments
+ * for a feature flag can be configured on the Split user interface.
+ *
+ *
+ * This method returns the string 'control' if:
+ *
+ * - Any of the parameters were null
+ * - There was an exception in evaluating the treatment
+ * - The SDK does not know of the existence of this feature flag
+ * - The feature flag was deleted through the Split user interface.
+ *
+ * 'control' is a reserved treatment (you cannot create a treatment with the
+ * same name) to highlight these exceptional circumstances.
+ *
+ *
+ * The sdk returns the default treatment of this feature flag if:
+ *
+ * - The feature flag was killed
+ * - The key did not match any of the conditions in the feature flag roll-out plan
+ *
+ * The default treatment of a feature flag is set on the Split user interface.
+ *
+ *
+ * This method does not throw any exceptions. It also never returns null.
+ *
+ * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null.
+ * @param featureFlagName the name of the feature flag we want to evaluate. MUST NOT be null.
+ * @param evaluationOptions additional data to attach to the impression.
+ * @return the evaluated treatment, the default treatment of this feature flag, or 'control'.
+ */
+ String getTreatment(String key, String featureFlagName, EvaluationOptions evaluationOptions);
+
+ /**
+ * This method is useful when you want to determine the treatment to show
+ * to an customer (user, account etc.) based on an attribute of that customer
+ * instead of it's key.
+ *
+ *
+ * Examples include showing a different treatment to users on trial plan
+ * vs. premium plan. Another example is to show a different treatment
+ * to users created after a certain date.
+ *
+ * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null.
+ * @param featureFlagName the name of the feature flag we want to evaluate. MUST NOT be null.
+ * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
+ * @param evaluationOptions additional data to attach to the impression.
+ * @return the evaluated treatment, the default treatment of this feature flag, or 'control'.
+ */
+ String getTreatment(String key, String featureFlagName, Map attributes, EvaluationOptions evaluationOptions);
+
+ /**
+ * To understand why this method is useful, consider the following simple Feature Flag as an example:
+ *
+ * if user is in segment employees then feature flag 100%:on
+ * else if user is in segment all then feature flag 20%:on,80%:off
+ *
+ * There are two concepts here: matching and bucketing. Matching
+ * refers to ‘user is in segment employees’ or ‘user is in segment
+ * all’ whereas bucketing refers to ‘100%:on’ or ‘20%:on,80%:off’.
+ *
+ * By default, the same customer key is used for both matching and
+ * bucketing. However, for some advanced use cases, you may want
+ * to use different keys. For such cases, use this method.
+ *
+ * As an example, suppose you want to rollout to percentages of
+ * users in specific accounts. You can achieve that by matching
+ * via account id, but bucketing by user id.
+ *
+ * Another example is when you want to ensure that a user continues to get
+ * the same treatment after they sign up for your product that they used
+ * to get when they were simply a visitor to your site. In that case,
+ * before they sign up, you can use their visitor id for both matching and bucketing, but
+ * post log-in you can use their user id for matching and visitor id for bucketing.
+ *
+ *
+ * @param key the matching and bucketing keys. MUST NOT be null.
+ * @param featureFlagName the name of the feature flag we want to evaluate. MUST NOT be null.
+ * @param attributes of the entity (user, account etc.) to use in evaluation. Can be null or empty.
+ * @param evaluationOptions additional data to attach to the impression.
+ *
+ * @return the evaluated treatment, the default treatment of this feature flag, or 'control'.
+ */
+ String getTreatment(Key key, String featureFlagName, Map attributes, EvaluationOptions evaluationOptions);
+
+ /**
+ * Returns a map of feature flag name and treatments to show this key for these feature flags. The set of treatments
+ * for a feature flag can be configured on the Split user interface.
+ *
+ *
+ * This method returns for each feature flag the string 'control' if:
+ *
+ * - Any of the parameters were null
+ * - There was an exception in evaluating the treatment
+ * - The SDK does not know of the existence of this feature flag
+ * - The feature flag was deleted through the Split user interface.
+ *
+ * 'control' is a reserved treatment (you cannot create a treatment with the
+ * same name) to highlight these exceptional circumstances.
+ *
+ *
+ * The sdk returns for each feature flag the default treatment of this feature flag if:
+ *
+ * - The feature flag was killed
+ * - The key did not match any of the conditions in the feature flag roll-out plan
+ *
+ * The default treatment of a feature flag is set on the Split user interface.
+ *
+ *
+ * This method does not throw any exceptions. It also never returns null.
+ *
+ * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null.
+ * @param featureFlagNames the names of the feature flags we want to evaluate. MUST NOT be null.
+ * @param evaluationOptions additional data to attach to the impression.
+ * @return for each feature flag the evaluated treatment, the default treatment for each feature flag, or 'control'.
+ */
+ Map getTreatments(String key, List featureFlagNames, EvaluationOptions evaluationOptions);
+
+ /**
+ * This method is useful when you want to determine the treatments to show
+ * to a customer (user, account etc.) based on an attribute of that customer
+ * instead of their key.
+ *
+ *
+ * Examples include showing different treatments to users on trial plan
+ * vs. premium plan. Another example is to show different treatments
+ * to users created after a certain date.
+ *
+ * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null.
+ * @param featureFlagNames the names of the feature flags we want to evaluate. MUST NOT be null.
+ * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
+ * @param evaluationOptions additional data to attach to the impression.
+ * @return the evaluated treatment, the default treatment of this feature flag, or 'control'.
+ */
+ Map getTreatments(String key, List featureFlagNames, Map attributes, EvaluationOptions evaluationOptions);
+
+ /**
+ * To understand why this method is useful, consider the following simple Feature Flag as an example:
+ *
+ * if user is in segment employees then feature flag 100%:on
+ * else if user is in segment all then feature flag 20%:on,80%:off
+ *
+ * There are two concepts here: matching and bucketing. Matching
+ * refers to ‘user is in segment employees’ or ‘user is in segment
+ * all’ whereas bucketing refers to ‘100%:on’ or ‘20%:on,80%:off’.
+ *
+ * By default, the same customer key is used for both matching and
+ * bucketing. However, for some advanced use cases, you may want
+ * to use different keys. For such cases, use this method.
+ *
+ * As an example, suppose you want to rollout to percentages of
+ * users in specific accounts. You can achieve that by matching
+ * via account id, but bucketing by user id.
+ *
+ * Another example is when you want to ensure that a user continues to get
+ * the same treatment after they sign up for your product that they used
+ * to get when they were simply a visitor to your site. In that case,
+ * before they sign up, you can use their visitor id for both matching and bucketing, but
+ * post log-in you can use their user id for matching and visitor id for bucketing.
+ *
+ *
+ * @param key the matching and bucketing keys. MUST NOT be null.
+ * @param featureFlagNames the names of the feature flags we want to evaluate. MUST NOT be null.
+ * @param attributes of the entity (user, account etc.) to use in evaluation. Can be null or empty.
+ * @param evaluationOptions additional data to attach to the impression.
+ *
+ * @return for each feature flag the evaluated treatment, the default treatment of the feature flag, or 'control'.
+ */
+ Map getTreatments(Key key, List featureFlagNames, Map attributes, EvaluationOptions evaluationOptions);
+
+ /**
+ * Same as {@link #getTreatment(String, String)} but it returns the configuration associated to the
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
+ *
+ *
+ * Examples include showing a different treatment to users on trial plan
+ * vs. premium plan. Another example is to show a different treatment
+ * to users created after a certain date.
+ *
+ * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null.
+ * @param featureFlagName the name of the feature flag we want to evaluate. MUST NOT be null.
+ * @param evaluationOptions additional data to attach to the impression.
+ * @return SplitResult containing the evaluated treatment (the default treatment of this feature flag, or 'control') and
+ * a configuration associated to this treatment if set.
+ */
+ SplitResult getTreatmentWithConfig(String key, String featureFlagName, EvaluationOptions evaluationOptions);
+
+ /**
+ * Same as {@link #getTreatment(Key, String, Map)} but it returns the configuration associated to the
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
+ *
+ * @param key the matching and bucketing keys. MUST NOT be null.
+ * @param featureFlagName the name of the feature flag we want to evaluate. MUST NOT be null.
+ * @param attributes of the entity (user, account etc.) to use in evaluation. Can be null or empty.
+ * @param evaluationOptions additional data to attach to the impression.
+ *
+ * @return SplitResult containing the evaluated treatment (the default treatment of this feature flag, or 'control') and
+ * a configuration associated to this treatment if set.
+ */
+ SplitResult getTreatmentWithConfig(Key key, String featureFlagName, Map attributes, EvaluationOptions evaluationOptions);
+
+ /**
+ * Same as {@link #getTreatment(String, String, Map)} but it returns the configuration associated to the
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
+ *
+ *
+ * Examples include showing a different treatment to users on trial plan
+ * vs. premium plan. Another example is to show a different treatment
+ * to users created after a certain date.
+ *
+ * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null.
+ * @param featureFlagName the name of the feature flag we want to evaluate. MUST NOT be null.
+ * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
+ * @param evaluationOptions additional data to attach to the impression.
+ * @return SplitResult containing the evaluated treatment (the default treatment of this feature flag, or 'control') and
+ * a configuration associated to this treatment if set.
+ */
+ SplitResult getTreatmentWithConfig(String key, String featureFlagName, Map attributes, EvaluationOptions evaluationOptions);
+
+ /**
+ * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
+ *
+ *
+ * Examples include showing a different treatment to users on trial plan
+ * vs. premium plan. Another example is to show a different treatment
+ * to users created after a certain date.
+ *
+ * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null.
+ * @param featureFlagNames the names of the feature flags we want to evaluate. MUST NOT be null.
+ * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
+ * @param evaluationOptions additional data to attach to the impression.
+ * @return for each feature flag a SplitResult containing the evaluated treatment (the default treatment of this feature flag, or 'control') and
+ * a configuration associated to this treatment if set.
+ */
+ Map getTreatmentsWithConfig(String key, List featureFlagNames, Map attributes,
+ EvaluationOptions evaluationOptions);
+
+ /**
+ * Same as {@link #getTreatments(String, List)} but it returns the configuration associated to the
+ * matching treatments if any. Otherwise {@link SplitResult.config()} will be null.
+ *
+ *
+ * Examples include showing a different treatment to users on trial plan
+ * vs. premium plan. Another example is to show a different treatment
+ * to users created after a certain date.
+ *
+ * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null.
+ * @param featureFlagNames the names of the feature flags we want to evaluate. MUST NOT be null.
+ * @param evaluationOptions additional data to attach to the impression.
+ * @return Map containing for each feature flag the evaluated treatment (the default treatment of
+ * this feature flag, or 'control') and a configuration associated to this treatment if set.
+ */
+ Map getTreatmentsWithConfig(String key, List featureFlagNames, EvaluationOptions evaluationOptions);
+
+ /**
+ * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
+ *
+ *
+ * Examples include showing a different treatment to users on trial plan
+ * vs. premium plan. Another example is to show a different treatment
+ * to users created after a certain date.
+ *
+ * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty.
+ * @param flagSet the Flag Set name that you want to evaluate. MUST not be null or empty.
+ * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
+ * @param evaluationOptions additional data to attach to the impression.
+ * @return for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control'.
+ */
+ Map getTreatmentsByFlagSet(String key, String flagSet, Map attributes, EvaluationOptions evaluationOptions);
+
+ /**
+ * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
+ *
+ *
+ * Examples include showing a different treatment to users on trial plan
+ * vs. premium plan. Another example is to show a different treatment
+ * to users created after a certain date.
+ *
+ * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty.
+ * @param flagSets the names of Flag Sets that you want to evaluate. MUST not be null or empty.
+ * @param evaluationOptions additional data to attach to the impression.
+ * @return for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control'.
+ */
+ Map getTreatmentsByFlagSets(String key, List flagSets, EvaluationOptions evaluationOptions);
+
+ /**
+ * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
+ *
+ *
+ * Examples include showing a different treatment to users on trial plan
+ * vs. premium plan. Another example is to show a different treatment
+ * to users created after a certain date.
+ *
+ * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty.
+ * @param flagSets the names of Flag Sets that you want to evaluate. MUST not be null or empty.
+ * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
+ * @param evaluationOptions additional data to attach to the impression.
+ * @return for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control'.
+ */
+ Map getTreatmentsByFlagSets(String key, List flagSets, Map attributes,
+ EvaluationOptions evaluationOptions);
+
+ /**
+ * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
+ *
+ *
+ * Examples include showing a different treatment to users on trial plan
+ * vs. premium plan. Another example is to show a different treatment
+ * to users created after a certain date.
+ *
+ * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty.
+ * @param flagSet the Flag Set name that you want to evaluate. MUST not be null or empty.
+ * @param evaluationOptions additional data to attach to the impression.
+ * @return for each feature flag the evaluated treatment (the default treatment of this feature flag, or 'control') and a configuration
+ * associated to this treatment if set.
+ */
+ Map getTreatmentsWithConfigByFlagSet(String key, String flagSet, EvaluationOptions evaluationOptions);
+
+ /**
+ * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
+ *
+ *
+ * Examples include showing a different treatment to users on trial plan
+ * vs. premium plan. Another example is to show a different treatment
+ * to users created after a certain date.
+ *
+ * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty.
+ * @param flagSets the names of Flag Sets that you want to evaluate. MUST not be null or empty.
+ * @param evaluationOptions additional data to attach to the impression.
+ * @return for each feature flag the evaluated treatment (the default treatment of this feature flag, or 'control') and a configuration
+ * associated to this treatment if set.
+ */
+ Map getTreatmentsWithConfigByFlagSets(String key, List flagSets, EvaluationOptions evaluationOptions);
+
+ /**
+ * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
+ *
+ *
+ * Examples include showing a different treatment to users on trial plan
+ * vs. premium plan. Another example is to show a different treatment
+ * to users created after a certain date.
+ *
+ * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty.
+ * @param flagSets the names of Flag Sets that you want to evaluate. MUST not be null or empty.
+ * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
+ * @param evaluationOptions additional data to attach to the impression.
+ * @return for each feature flag the evaluated treatment (the default treatment of this feature flag, or 'control') and a configuration
+ * associated to this treatment if set.
+ */
+ Map getTreatmentsWithConfigByFlagSets(String key, List flagSets, Map attributes,
+ EvaluationOptions evaluationOptions);
+
+ /**
+ * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
+ *
+ *
+ * Examples include showing a different treatment to users on trial plan
+ * vs. premium plan. Another example is to show a different treatment
+ * to users created after a certain date.
+ *
+ * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty.
+ * @param flagSet the Flag Set name that you want to evaluate. MUST not be null or empty.
+ * @param evaluationOptions additional data to attach to the impression.
+ * @return for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control'.
+ */
+ Map getTreatmentsByFlagSet(String key, String flagSet, EvaluationOptions evaluationOptions);
+
+ /**
+ * Same as {@link #getTreatments(Key, List, Map)} but it returns for each feature flag the configuration associated to the
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
+ *
+ * @param key the matching and bucketing keys. MUST NOT be null.
+ * @param featureFlagNames the names of the feature flags we want to evaluate. MUST NOT be null.
+ * @param attributes of the entity (user, account etc.) to use in evaluation. Can be null or empty.
+ * @param evaluationOptions additional data to attach to the impression.
+ *
+ * @return for each feature flag a SplitResult containing the evaluated treatment (the default treatment of this feature flag, or 'control') and
+ * a configuration associated to this treatment if set.
+ */
+ Map getTreatmentsWithConfig(Key key, List featureFlagNames, Map attributes,
+ EvaluationOptions evaluationOptions);
+
+ /**
+ * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
+ *
+ *
+ * Examples include showing a different treatment to users on trial plan
+ * vs. premium plan. Another example is to show a different treatment
+ * to users created after a certain date.
+ *
+ * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty.
+ * @param flagSet the Flag Set name that you want to evaluate. MUST not be null or empty.
+ * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
+ * @param evaluationOptions additional data to attach to the impression.
+ * @return for each feature flag the evaluated treatment (the default treatment of this feature flag, or 'control') and a configuration
+ * associated to this treatment if set.
+ */
+ Map getTreatmentsWithConfigByFlagSet(String key, String flagSet, Map attributes,
+ EvaluationOptions evaluationOptions);
+
+ /**
+ * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
+ *
+ *
+ * Examples include showing a different treatment to users on trial plan
+ * vs. premium plan. Another example is to show a different treatment
+ * to users created after a certain date.
+ *
+ * @param key the matching and bucketing keys. MUST not be null or empty.
+ * @param flagSet the Flag Set name that you want to evaluate. MUST not be null or empty.
+ * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
+ * @param evaluationOptions additional data to attach to the impression.
+ * @return for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control'.
+ */
+ Map getTreatmentsByFlagSet(Key key, String flagSet, Map attributes, EvaluationOptions evaluationOptions);
+
+ /**
+ * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
+ *
+ *
+ * Examples include showing a different treatment to users on trial plan
+ * vs. premium plan. Another example is to show a different treatment
+ * to users created after a certain date.
+ *
+ * @param key the matching and bucketing keys. MUST not be null or empty.
+ * @param flagSets the names of Flag Sets that you want to evaluate. MUST not be null or empty.
+ * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
+ * @param evaluationOptions additional data to attach to the impression.
+ * @return for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control'.
+ */
+ Map getTreatmentsByFlagSets(Key key, List flagSets, Map attributes,
+ EvaluationOptions evaluationOptions);
+
+ /**
+ * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
+ *
+ *
+ * Examples include showing a different treatment to users on trial plan
+ * vs. premium plan. Another example is to show a different treatment
+ * to users created after a certain date.
+ *
+ * @param key the matching and bucketing keys. MUST not be null or empty.
+ * @param flagSet the Flag Set name that you want to evaluate. MUST not be null or empty.
+ * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
+ * @param evaluationOptions additional data to attach to the impression.
+ * @return for each feature flag the evaluated treatment (the default treatment of this feature flag, or 'control') and a configuration
+ * associated to this treatment if set.
+ */
+ Map getTreatmentsWithConfigByFlagSet(Key key, String flagSet, Map attributes,
+ EvaluationOptions evaluationOptions);
+
+ /**
+ * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the
+ * matching treatment if any. Otherwise {@link SplitResult.config()} will be null.
+ *
+ *
+ * Examples include showing a different treatment to users on trial plan
+ * vs. premium plan. Another example is to show a different treatment
+ * to users created after a certain date.
+ *
+ * @param key the matching and bucketing keys. MUST not be null or empty.
+ * @param flagSets the names of Flag Sets that you want to evaluate. MUST not be null or empty.
+ * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty.
+ * @param evaluationOptions additional data to attach to the impression.
+ * @return for each feature flag the evaluated treatment (the default treatment of this feature flag, or 'control') and a configuration
+ * associated to this treatment if set.
+ */
+ Map getTreatmentsWithConfigByFlagSets(Key key, List flagSets, Map attributes,
+ EvaluationOptions evaluationOptions);
+
/**
* Destroys the background processes and clears the cache, releasing the resources used by
* the any instances of SplitClient or SplitManager generated by the client's parent SplitFactory
diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java
index 1ff143ed2..b73a2c24a 100644
--- a/client/src/main/java/io/split/client/SplitClientImpl.java
+++ b/client/src/main/java/io/split/client/SplitClientImpl.java
@@ -1,8 +1,12 @@
package io.split.client;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonParser;
import io.split.client.api.Key;
import io.split.client.api.SplitResult;
import io.split.client.dtos.DecoratedImpression;
+import io.split.client.dtos.EvaluationOptions;
import io.split.client.dtos.Event;
import io.split.client.events.EventsStorageProducer;
import io.split.client.impressions.Impression;
@@ -17,10 +21,12 @@
import io.split.inputValidation.KeyValidator;
import io.split.inputValidation.SplitNameValidator;
import io.split.inputValidation.TrafficTypeValidator;
+import io.split.inputValidation.ImpressionPropertiesValidator;
import io.split.storages.SplitCacheConsumer;
import io.split.telemetry.domain.enums.MethodEnum;
import io.split.telemetry.storage.TelemetryConfigProducer;
import io.split.telemetry.storage.TelemetryEvaluationProducer;
+import io.split.client.utils.Json;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -93,27 +99,30 @@ public String getTreatment(String key, String featureFlagName) {
@Override
public String getTreatment(String key, String featureFlagName, Map attributes) {
- return getTreatmentWithConfigInternal(key, null, featureFlagName, attributes, MethodEnum.TREATMENT).treatment();
+ return getTreatmentWithConfigInternal(key, null, featureFlagName, attributes, new EvaluationOptions(null), MethodEnum.TREATMENT).treatment();
}
@Override
public String getTreatment(Key key, String featureFlagName, Map attributes) {
- return getTreatmentWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagName, attributes, MethodEnum.TREATMENT).treatment();
+ return getTreatmentWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagName, attributes, new EvaluationOptions(null),
+ MethodEnum.TREATMENT).treatment();
}
@Override
public SplitResult getTreatmentWithConfig(String key, String featureFlagName) {
- return getTreatmentWithConfigInternal(key, null, featureFlagName, Collections.emptyMap(), MethodEnum.TREATMENT_WITH_CONFIG);
+ return getTreatmentWithConfigInternal(key, null, featureFlagName, Collections.emptyMap(), new EvaluationOptions(null),
+ MethodEnum.TREATMENT_WITH_CONFIG);
}
@Override
public SplitResult getTreatmentWithConfig(String key, String featureFlagName, Map attributes) {
- return getTreatmentWithConfigInternal(key, null, featureFlagName, attributes, MethodEnum.TREATMENT_WITH_CONFIG);
+ return getTreatmentWithConfigInternal(key, null, featureFlagName, attributes, new EvaluationOptions(null), MethodEnum.TREATMENT_WITH_CONFIG);
}
@Override
public SplitResult getTreatmentWithConfig(Key key, String featureFlagName, Map attributes) {
- return getTreatmentWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagName, attributes, MethodEnum.TREATMENT_WITH_CONFIG);
+ return getTreatmentWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagName, attributes, new EvaluationOptions(null),
+ MethodEnum.TREATMENT_WITH_CONFIG);
}
@Override
@@ -123,111 +132,277 @@ public Map getTreatments(String key, List featureFlagNam
@Override
public Map getTreatments(String key, List featureFlagNames, Map attributes) {
- return getTreatmentsWithConfigInternal(key, null, featureFlagNames, attributes, MethodEnum.TREATMENTS)
+ return getTreatmentsWithConfigInternal(key, null, featureFlagNames, attributes, new EvaluationOptions(null), MethodEnum.TREATMENTS)
.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment()));
}
@Override
public Map getTreatments(Key key, List featureFlagNames, Map attributes) {
- return getTreatmentsWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagNames, attributes, MethodEnum.TREATMENTS)
+ return getTreatmentsWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagNames, attributes,
+ new EvaluationOptions(null), MethodEnum.TREATMENTS)
.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment()));
}
@Override
public Map getTreatmentsWithConfig(String key, List featureFlagNames) {
- return getTreatmentsWithConfigInternal(key, null, featureFlagNames, Collections.emptyMap(),
+ return getTreatmentsWithConfigInternal(key, null, featureFlagNames, Collections.emptyMap(), new EvaluationOptions(null),
MethodEnum.TREATMENTS_WITH_CONFIG);
}
@Override
public Map getTreatmentsWithConfig(String key, List featureFlagNames, Map attributes) {
- return getTreatmentsWithConfigInternal(key, null, featureFlagNames, attributes, MethodEnum.TREATMENTS_WITH_CONFIG);
+ return getTreatmentsWithConfigInternal(key, null, featureFlagNames, attributes,
+ new EvaluationOptions(null), MethodEnum.TREATMENTS_WITH_CONFIG);
}
@Override
public Map getTreatmentsWithConfig(Key key, List featureFlagNames, Map attributes) {
- return getTreatmentsWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagNames, attributes,
+ return getTreatmentsWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagNames, attributes, new EvaluationOptions(null),
MethodEnum.TREATMENTS_WITH_CONFIG);
}
@Override
public Map getTreatmentsByFlagSet(String key, String flagSet) {
return getTreatmentsBySetsWithConfigInternal(key, null, new ArrayList<>(Arrays.asList(flagSet)),
- null, MethodEnum.TREATMENTS_BY_FLAG_SET).entrySet().stream()
+ null, new EvaluationOptions(null), MethodEnum.TREATMENTS_BY_FLAG_SET).entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment()));
}
@Override
public Map getTreatmentsByFlagSet(String key, String flagSet, Map attributes) {
return getTreatmentsBySetsWithConfigInternal(key, null, new ArrayList<>(Arrays.asList(flagSet)),
- attributes, MethodEnum.TREATMENTS_BY_FLAG_SET).entrySet().stream()
+ attributes, new EvaluationOptions(null), MethodEnum.TREATMENTS_BY_FLAG_SET).entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment()));
}
@Override
public Map getTreatmentsByFlagSet(Key key, String flagSet, Map attributes) {
return getTreatmentsBySetsWithConfigInternal(key.matchingKey(), key.bucketingKey(), new ArrayList<>(Arrays.asList(flagSet)),
- attributes, MethodEnum.TREATMENTS_BY_FLAG_SET).entrySet().stream()
+ attributes, new EvaluationOptions(null), MethodEnum.TREATMENTS_BY_FLAG_SET).entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment()));
}
@Override
public Map getTreatmentsByFlagSets(String key, List flagSets) {
return getTreatmentsBySetsWithConfigInternal(key, null, flagSets,
- null, MethodEnum.TREATMENTS_BY_FLAG_SETS).entrySet().stream()
+ null, new EvaluationOptions(null), MethodEnum.TREATMENTS_BY_FLAG_SETS).entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment()));
}
@Override
public Map getTreatmentsByFlagSets(String key, List flagSets, Map attributes) {
return getTreatmentsBySetsWithConfigInternal(key, null, flagSets,
- attributes, MethodEnum.TREATMENTS_BY_FLAG_SETS).entrySet().stream()
+ attributes, new EvaluationOptions(null), MethodEnum.TREATMENTS_BY_FLAG_SETS).entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment()));
}
@Override
public Map getTreatmentsByFlagSets(Key key, List flagSets, Map attributes) {
return getTreatmentsBySetsWithConfigInternal(key.matchingKey(), key.bucketingKey(), flagSets,
- attributes, MethodEnum.TREATMENTS_BY_FLAG_SETS).entrySet().stream()
+ attributes, new EvaluationOptions(null), MethodEnum.TREATMENTS_BY_FLAG_SETS).entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment()));
}
@Override
public Map getTreatmentsWithConfigByFlagSet(String key, String flagSet) {
return getTreatmentsBySetsWithConfigInternal(key, null, new ArrayList<>(Arrays.asList(flagSet)),
- null, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SET);
+ null, new EvaluationOptions(null), MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SET);
}
@Override
public Map getTreatmentsWithConfigByFlagSet(String key, String flagSet, Map attributes) {
return getTreatmentsBySetsWithConfigInternal(key, null, new ArrayList<>(Arrays.asList(flagSet)),
- attributes, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SET);
+ attributes, new EvaluationOptions(null), MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SET);
}
@Override
public Map getTreatmentsWithConfigByFlagSet(Key key, String flagSet, Map attributes) {
return getTreatmentsBySetsWithConfigInternal(key.matchingKey(), key.bucketingKey(), new ArrayList<>(Arrays.asList(flagSet)),
- attributes, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SET);
+ attributes, new EvaluationOptions(null), MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SET);
}
@Override
public Map getTreatmentsWithConfigByFlagSets(String key, List flagSets) {
return getTreatmentsBySetsWithConfigInternal(key, null, flagSets,
- null, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS);
+ null, new EvaluationOptions(null), MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS);
}
@Override
public Map getTreatmentsWithConfigByFlagSets(String key, List flagSets, Map attributes) {
return getTreatmentsBySetsWithConfigInternal(key, null, flagSets,
- attributes, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS);
+ attributes, new EvaluationOptions(null), MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS);
}
@Override
public Map getTreatmentsWithConfigByFlagSets(Key key, List flagSets, Map attributes) {
return getTreatmentsBySetsWithConfigInternal(key.matchingKey(), key.bucketingKey(), flagSets,
- attributes, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS);
+ attributes, new EvaluationOptions(null), MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS);
+ }
+
+ @Override
+ public String getTreatment(String key, String featureFlagName, EvaluationOptions evaluationOptions) {
+ return getTreatment(key, featureFlagName, Collections.emptyMap(), evaluationOptions);
+ }
+
+ @Override
+ public String getTreatment(String key, String featureFlagName, Map attributes, EvaluationOptions evaluationOptions) {
+ return getTreatmentWithConfigInternal(key, null, featureFlagName, attributes, evaluationOptions, MethodEnum.TREATMENT).treatment();
+ }
+
+ @Override
+ public String getTreatment(Key key, String featureFlagName, Map attributes, EvaluationOptions evaluationOptions) {
+ return getTreatmentWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagName, attributes, evaluationOptions,
+ MethodEnum.TREATMENT).treatment();
+ }
+
+ @Override
+ public Map getTreatments(String key, List featureFlagNames,
+ EvaluationOptions evaluationOptions) {
+ return getTreatments(key, featureFlagNames, Collections.emptyMap(), evaluationOptions);
+ }
+
+ @Override
+ public Map getTreatments(String key, List featureFlagNames, Map attributes,
+ EvaluationOptions evaluationOptions) {
+ return getTreatmentsWithConfigInternal(key, null, featureFlagNames, attributes, evaluationOptions, MethodEnum.TREATMENTS)
+ .entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment()));
+ }
+
+ @Override
+ public Map getTreatments(Key key, List featureFlagNames, Map attributes,
+ EvaluationOptions evaluationOptions) {
+ return getTreatmentsWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagNames, attributes, evaluationOptions,
+ MethodEnum.TREATMENTS)
+ .entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment()));
+ }
+
+ @Override
+ public SplitResult getTreatmentWithConfig(String key, String featureFlagName, EvaluationOptions evaluationOptions) {
+ return getTreatmentWithConfigInternal(key, null, featureFlagName, Collections.emptyMap(), evaluationOptions,
+ MethodEnum.TREATMENT_WITH_CONFIG);
+ }
+
+ @Override
+ public SplitResult getTreatmentWithConfig(Key key, String featureFlagName, Map attributes,
+ EvaluationOptions evaluationOptions) {
+ return getTreatmentWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagName, attributes, evaluationOptions,
+ MethodEnum.TREATMENT_WITH_CONFIG);
+ }
+
+ @Override
+ public SplitResult getTreatmentWithConfig(String key, String featureFlagName, Map attributes,
+ EvaluationOptions evaluationOptions) {
+ return getTreatmentWithConfigInternal(key, null, featureFlagName, attributes, evaluationOptions,
+ MethodEnum.TREATMENT_WITH_CONFIG);
+ }
+
+ @Override
+ public Map getTreatmentsWithConfig(String key, List featureFlagNames, Map attributes,
+ EvaluationOptions evaluationOptions) {
+ return getTreatmentsWithConfigInternal(key, null, featureFlagNames, attributes, evaluationOptions,
+ MethodEnum.TREATMENTS_WITH_CONFIG);
+ }
+
+ @Override
+ public Map getTreatmentsWithConfig(String key, List featureFlagNames, EvaluationOptions evaluationOptions) {
+ return getTreatmentsWithConfigInternal(key, null, featureFlagNames, null, evaluationOptions,
+ MethodEnum.TREATMENTS_WITH_CONFIG);
+ }
+
+ @Override
+ public Map getTreatmentsByFlagSet(String key, String flagSet, Map attributes,
+ EvaluationOptions evaluationOptions) {
+ return getTreatmentsBySetsWithConfigInternal(key, null, new ArrayList<>(Arrays.asList(flagSet)),
+ attributes, evaluationOptions, MethodEnum.TREATMENTS_BY_FLAG_SET).entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment()));
+ }
+
+ @Override
+ public Map getTreatmentsByFlagSets(String key, List flagSets, EvaluationOptions evaluationOptions) {
+ return getTreatmentsBySetsWithConfigInternal(key, null, flagSets,
+ null, evaluationOptions, MethodEnum.TREATMENTS_BY_FLAG_SETS).entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment()));
+ }
+
+ @Override
+ public Map getTreatmentsByFlagSets(String key, List flagSets, Map attributes,
+ EvaluationOptions evaluationOptions) {
+ return getTreatmentsBySetsWithConfigInternal(key, null, flagSets,
+ attributes, evaluationOptions, MethodEnum.TREATMENTS_BY_FLAG_SETS).entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment()));
+ }
+
+ @Override
+ public Map getTreatmentsWithConfigByFlagSet(String key, String flagSet, EvaluationOptions evaluationOptions) {
+ return getTreatmentsBySetsWithConfigInternal(key, null, new ArrayList<>(Arrays.asList(flagSet)),
+ null, evaluationOptions, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SET);
+ }
+
+ @Override
+ public Map getTreatmentsWithConfigByFlagSet(String key, String flagSet, Map attributes,
+ EvaluationOptions evaluationOptions) {
+ return getTreatmentsBySetsWithConfigInternal(key, null, new ArrayList<>(Arrays.asList(flagSet)),
+ attributes, evaluationOptions, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SET);
+ }
+
+ @Override
+ public Map getTreatmentsWithConfigByFlagSets(String key, List flagSets, EvaluationOptions evaluationOptions) {
+ return getTreatmentsBySetsWithConfigInternal(key, null, flagSets,
+ null, evaluationOptions, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS);
+ }
+
+ @Override
+ public Map getTreatmentsWithConfigByFlagSets(String key, List flagSets, Map attributes,
+ EvaluationOptions evaluationOptions) {
+ return getTreatmentsBySetsWithConfigInternal(key, null, flagSets,
+ attributes, evaluationOptions, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS);
+ }
+
+ @Override
+ public Map getTreatmentsByFlagSet(String key, String flagSet, EvaluationOptions evaluationOptions) {
+ return getTreatmentsBySetsWithConfigInternal(key, null, new ArrayList<>(Arrays.asList(flagSet)),
+ null, evaluationOptions, MethodEnum.TREATMENTS_BY_FLAG_SET).entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment()));
+ }
+
+ @Override
+ public Map getTreatmentsWithConfig(Key key, List featureFlagNames, Map attributes,
+ EvaluationOptions evaluationOptions) {
+ return getTreatmentsWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagNames, attributes, evaluationOptions,
+ MethodEnum.TREATMENTS_WITH_CONFIG);
+ }
+
+ @Override
+ public Map getTreatmentsByFlagSet(Key key, String flagSet, Map attributes, EvaluationOptions evaluationOptions) {
+ return getTreatmentsBySetsWithConfigInternal(key.matchingKey(), key.bucketingKey(), new ArrayList<>(Arrays.asList(flagSet)),
+ attributes, evaluationOptions, MethodEnum.TREATMENTS_BY_FLAG_SET).entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment()));
+ }
+
+ @Override
+ public Map getTreatmentsByFlagSets(Key key, List flagSets, Map attributes,
+ EvaluationOptions evaluationOptions) {
+ return getTreatmentsBySetsWithConfigInternal(key.matchingKey(), key.bucketingKey(), flagSets,
+ attributes, evaluationOptions, MethodEnum.TREATMENTS_BY_FLAG_SETS).entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment()));
+ }
+
+ @Override
+ public Map getTreatmentsWithConfigByFlagSet(Key key, String flagSet, Map attributes,
+ EvaluationOptions evaluationOptions) {
+ return getTreatmentsBySetsWithConfigInternal(key.matchingKey(), key.bucketingKey(), new ArrayList<>(Arrays.asList(flagSet)),
+ attributes, evaluationOptions, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SET);
+ }
+
+ @Override
+ public Map getTreatmentsWithConfigByFlagSets(Key key, List flagSets, Map attributes,
+ EvaluationOptions evaluationOptions) {
+ return getTreatmentsBySetsWithConfigInternal(key.matchingKey(), key.bucketingKey(), flagSets,
+ attributes, evaluationOptions, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS);
}
@Override
@@ -313,7 +488,7 @@ private boolean track(Event event) {
}
private SplitResult getTreatmentWithConfigInternal(String matchingKey, String bucketingKey, String featureFlag, Map attributes, MethodEnum methodEnum) {
+ Object> attributes, EvaluationOptions evaluationOptions, MethodEnum methodEnum) {
long initTime = System.currentTimeMillis();
try {
checkSDKReady(methodEnum, Arrays.asList(featureFlag));
@@ -336,7 +511,6 @@ private SplitResult getTreatmentWithConfigInternal(String matchingKey, String bu
return SPLIT_RESULT_CONTROL;
}
featureFlag = splitNameResult.get();
-
long start = System.currentTimeMillis();
EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(matchingKey, bucketingKey, featureFlag, attributes);
@@ -358,7 +532,8 @@ private SplitResult getTreatmentWithConfigInternal(String matchingKey, String bu
_config.labelsEnabled() ? result.label : null,
result.changeNumber,
attributes,
- result.track
+ result.track,
+ validateProperties(evaluationOptions.getProperties())
);
_telemetryEvaluationProducer.recordLatency(methodEnum, System.currentTimeMillis() - initTime);
return new SplitResult(result.treatment, result.configurations);
@@ -373,8 +548,19 @@ private SplitResult getTreatmentWithConfigInternal(String matchingKey, String bu
}
}
+ private String validateProperties(Map properties) {
+ if (properties == null){
+ return null;
+ }
+
+ ImpressionPropertiesValidator.ImpressionPropertiesValidatorResult iPValidatorResult = ImpressionPropertiesValidator.propertiesAreValid(
+ properties);
+ return new GsonBuilder().create().toJson(iPValidatorResult.getValue()).toString();
+ }
+
private Map getTreatmentsWithConfigInternal(String matchingKey, String bucketingKey, List featureFlagNames,
- Map attributes, MethodEnum methodEnum) {
+ Map attributes,
+ EvaluationOptions evaluationOptions, MethodEnum methodEnum) {
long initTime = System.currentTimeMillis();
if (featureFlagNames == null) {
_log.error(String.format("%s: featureFlagNames must be a non-empty array", methodEnum.getMethod()));
@@ -389,7 +575,9 @@ private Map getTreatmentsWithConfigInternal(String matching
featureFlagNames = SplitNameValidator.areValid(featureFlagNames, methodEnum.getMethod());
Map evaluatorResult = _evaluator.evaluateFeatures(matchingKey,
bucketingKey, featureFlagNames, attributes);
- return processEvaluatorResult(evaluatorResult, methodEnum, matchingKey, bucketingKey, attributes, initTime);
+
+ return processEvaluatorResult(evaluatorResult, methodEnum, matchingKey, bucketingKey, attributes, initTime,
+ validateProperties(evaluationOptions.getProperties()));
} catch (Exception e) {
try {
_telemetryEvaluationProducer.recordException(methodEnum);
@@ -402,7 +590,9 @@ private Map getTreatmentsWithConfigInternal(String matching
}
private Map getTreatmentsBySetsWithConfigInternal(String matchingKey, String bucketingKey,
- List sets, Map attributes, MethodEnum methodEnum) {
+ List sets, Map attributes,
+ EvaluationOptions evaluationOptions,
+ MethodEnum methodEnum) {
long initTime = System.currentTimeMillis();
if (sets == null || sets.isEmpty()) {
@@ -423,7 +613,9 @@ private Map getTreatmentsBySetsWithConfigInternal(String ma
}
Map evaluatorResult = _evaluator.evaluateFeaturesByFlagSets(matchingKey,
bucketingKey, new ArrayList<>(cleanFlagSets), attributes);
- return processEvaluatorResult(evaluatorResult, methodEnum, matchingKey, bucketingKey, attributes, initTime);
+
+ return processEvaluatorResult(evaluatorResult, methodEnum, matchingKey, bucketingKey, attributes, initTime,
+ validateProperties(evaluationOptions.getProperties()));
} catch (Exception e) {
try {
_telemetryEvaluationProducer.recordException(methodEnum);
@@ -436,7 +628,7 @@ private Map getTreatmentsBySetsWithConfigInternal(String ma
}
private Map processEvaluatorResult(Map evaluatorResult,
MethodEnum methodEnum, String matchingKey, String bucketingKey, Map attributes, long initTime){
+ Object> attributes, long initTime, String properties){
List decoratedImpressions = new ArrayList<>();
Map result = new HashMap<>();
evaluatorResult.keySet().forEach(t -> {
@@ -450,7 +642,7 @@ private Map processEvaluatorResult(Map filterSetsAreInConfig(Set sets, MethodEnum methodEnu
return setsToReturn;
}
private void recordStats(String matchingKey, String bucketingKey, String featureFlagName, long start, String result,
- String operation, String label, Long changeNumber, Map attributes, boolean track) {
+ String operation, String label, Long changeNumber, Map attributes, boolean track, String properties) {
try {
_impressionManager.track(Stream.of(
new DecoratedImpression(
new Impression(matchingKey, bucketingKey, featureFlagName, result, System.currentTimeMillis(),
- label, changeNumber, attributes),
+ label, changeNumber, attributes, properties),
track)).collect(Collectors.toList()));
} catch (Throwable t) {
_log.error("Exception", t);
diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java
index 848b50e86..454206d80 100644
--- a/client/src/main/java/io/split/client/SplitFactoryImpl.java
+++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java
@@ -53,6 +53,7 @@
import io.split.engine.experiments.SplitFetcher;
import io.split.engine.experiments.SplitFetcherImp;
import io.split.engine.experiments.SplitParser;
+import io.split.engine.experiments.RuleBasedSegmentParser;
import io.split.engine.experiments.SplitSynchronizationTask;
import io.split.engine.segments.SegmentChangeFetcher;
import io.split.engine.segments.SegmentSynchronizationTaskImp;
@@ -66,8 +67,11 @@
import io.split.storages.SplitCache;
import io.split.storages.SplitCacheConsumer;
import io.split.storages.SplitCacheProducer;
+import io.split.storages.RuleBasedSegmentCacheConsumer;
+import io.split.storages.RuleBasedSegmentCache;
import io.split.storages.enums.OperationMode;
import io.split.storages.memory.InMemoryCacheImp;
+import io.split.storages.memory.RuleBasedSegmentCacheInMemoryImp;
import io.split.storages.memory.SegmentCacheInMemoryImpl;
import io.split.storages.pluggable.adapters.UserCustomEventAdapterProducer;
import io.split.storages.pluggable.adapters.UserCustomImpressionAdapterConsumer;
@@ -123,8 +127,7 @@
public class SplitFactoryImpl implements SplitFactory {
private static final org.slf4j.Logger _log = LoggerFactory.getLogger(SplitFactoryImpl.class);
private static final String LEGACY_LOG_MESSAGE = "The sdk initialize in localhost mode using Legacy file. The splitFile or "
- +
- "inputStream doesn't add it to the config.";
+ + "inputStream were not added to the config.";
private final static long SSE_CONNECT_TIMEOUT = 30000;
private final static long SSE_SOCKET_TIMEOUT = 70000;
@@ -204,6 +207,7 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn
SegmentCache segmentCache = new SegmentCacheInMemoryImpl();
FlagSetsFilter flagSetsFilter = new FlagSetsFilterImpl(config.getSetsFilter());
SplitCache splitCache = new InMemoryCacheImp(flagSetsFilter);
+ RuleBasedSegmentCache ruleBasedSegmentCache = new RuleBasedSegmentCacheInMemoryImp();
ImpressionsStorage impressionsStorage = new InMemoryImpressionsStorage(config.impressionsQueueSize());
_splitCache = splitCache;
_segmentCache = segmentCache;
@@ -215,8 +219,11 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn
_segmentSynchronizationTaskImp = buildSegments(config, segmentCache, splitCache);
SplitParser splitParser = new SplitParser();
+ RuleBasedSegmentParser ruleBasedSegmentParser = new RuleBasedSegmentParser();
+
// SplitFetcher
- _splitFetcher = buildSplitFetcher(splitCache, splitParser, flagSetsFilter);
+ _splitFetcher = buildSplitFetcher(splitCache, splitParser, flagSetsFilter, ruleBasedSegmentParser, ruleBasedSegmentCache);
+
// SplitSynchronizationTask
_splitSynchronizationTask = new SplitSynchronizationTask(_splitFetcher,
@@ -244,7 +251,7 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn
config.getThreadFactory());
// Evaluator
- _evaluator = new EvaluatorImp(splitCache, segmentCache);
+ _evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache);
// SplitClient
_client = new SplitClientImpl(this,
@@ -301,6 +308,9 @@ protected SplitFactoryImpl(String apiToken, SplitClientConfig config, CustomStor
customStorageWrapper);
UserCustomSplitAdapterConsumer userCustomSplitAdapterConsumer = new UserCustomSplitAdapterConsumer(
customStorageWrapper);
+ // TODO Update the instance to UserCustomRuleBasedSegmentAdapterConsumer
+ RuleBasedSegmentCacheConsumer userCustomRuleBasedSegmentAdapterConsumer = new RuleBasedSegmentCacheInMemoryImp();
+
// TODO migrate impressions sender to Task instead manager and not instantiate
// Producer here.
UserCustomImpressionAdapterConsumer userCustomImpressionAdapterConsumer = new UserCustomImpressionAdapterConsumer();
@@ -333,7 +343,7 @@ protected SplitFactoryImpl(String apiToken, SplitClientConfig config, CustomStor
_gates = new SDKReadinessGates();
_telemetrySynchronizer = new TelemetryConsumerSubmitter(customStorageWrapper, _sdkMetadata);
- _evaluator = new EvaluatorImp(userCustomSplitAdapterConsumer, userCustomSegmentAdapterConsumer);
+ _evaluator = new EvaluatorImp(userCustomSplitAdapterConsumer, userCustomSegmentAdapterConsumer, userCustomRuleBasedSegmentAdapterConsumer);
_impressionsSender = PluggableImpressionSender.create(customStorageWrapper);
_uniqueKeysTracker = createUniqueKeysTracker(config);
_impressionsManager = buildImpressionsManager(config, userCustomImpressionAdapterConsumer,
@@ -391,6 +401,7 @@ protected SplitFactoryImpl(SplitClientConfig config) {
SegmentCache segmentCache = new SegmentCacheInMemoryImpl();
FlagSetsFilter flagSetsFilter = new FlagSetsFilterImpl(config.getSetsFilter());
+ RuleBasedSegmentCache ruleBasedSegmentCache = new RuleBasedSegmentCacheInMemoryImp();
SplitCache splitCache = new InMemoryCacheImp(flagSetsFilter);
_splitCache = splitCache;
_gates = new SDKReadinessGates();
@@ -413,10 +424,11 @@ protected SplitFactoryImpl(SplitClientConfig config) {
// SplitFetcher
SplitChangeFetcher splitChangeFetcher = createSplitChangeFetcher(config);
+ RuleBasedSegmentParser ruleBasedSegmentParser = new RuleBasedSegmentParser();
SplitParser splitParser = new SplitParser();
_splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, splitCache, _telemetryStorageProducer,
- flagSetsFilter);
+ flagSetsFilter, ruleBasedSegmentParser, ruleBasedSegmentCache);
// SplitSynchronizationTask
_splitSynchronizationTask = new SplitSynchronizationTask(_splitFetcher, splitCache,
@@ -428,7 +440,7 @@ protected SplitFactoryImpl(SplitClientConfig config) {
_impressionsManager, null, null, null);
// Evaluator
- _evaluator = new EvaluatorImp(splitCache, segmentCache);
+ _evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache);
EventsStorage eventsStorage = new NoopEventsStorageImp();
@@ -609,11 +621,12 @@ private SegmentSynchronizationTaskImp buildSegments(SplitClientConfig config,
}
private SplitFetcher buildSplitFetcher(SplitCacheProducer splitCacheProducer, SplitParser splitParser,
- FlagSetsFilter flagSetsFilter) throws URISyntaxException {
+ FlagSetsFilter flagSetsFilter, RuleBasedSegmentParser ruleBasedSegmentParser,
+ RuleBasedSegmentCache ruleBasedSegmentCache) throws URISyntaxException {
SplitChangeFetcher splitChangeFetcher = HttpSplitChangeFetcher.create(_splitHttpClient, _rootTarget,
_telemetryStorageProducer);
return new SplitFetcherImp(splitChangeFetcher, splitParser, splitCacheProducer, _telemetryStorageProducer,
- flagSetsFilter);
+ flagSetsFilter,ruleBasedSegmentParser, ruleBasedSegmentCache);
}
private ImpressionsManagerImpl buildImpressionsManager(SplitClientConfig config,
diff --git a/client/src/main/java/io/split/client/YamlLocalhostSplitChangeFetcher.java b/client/src/main/java/io/split/client/YamlLocalhostSplitChangeFetcher.java
index e90ca1389..5e6836579 100644
--- a/client/src/main/java/io/split/client/YamlLocalhostSplitChangeFetcher.java
+++ b/client/src/main/java/io/split/client/YamlLocalhostSplitChangeFetcher.java
@@ -32,7 +32,7 @@ public YamlLocalhostSplitChangeFetcher(InputStreamProvider inputStreamProvider)
}
@Override
- public SplitChange fetch(long since, FetchOptions options) {
+ public SplitChange fetch(long since, long sinceRBS, FetchOptions options) {
try {
Yaml yaml = new Yaml();
List