diff --git a/CHANGES.txt b/CHANGES.txt
index 0d1f612ea..a99b79df4 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,25 @@
+4.16.0 (May 28, 2025)
+- Added support for rule-based segments. These segments determine membership at runtime by evaluating their configured rules against the user attributes provided to the SDK.
+- Added support for feature flag prerequisites. This allows customers to define dependency conditions between flags, which are evaluated before any allowlists or targeting rules.
+
+4.15.0 (Apr 18, 2025)
+- Prevent polling threads from starting when the SDK calls destroy method.
+- Added a new optional argument to the client `getTreatment` methods to allow passing additional evaluation options, such as a map of properties to append to the generated impressions sent to Split backend. Read more in our docs.
+
+4.14.0 (Jan 17, 2025)
+- Added support for the new impressions tracking toggle available on feature flags, both respecting the setting and including the new field being returned on SplitView type objects. Read more in our docs.
+- Cleaned unused imports to fix a collision issue.
+
+4.13.1 (Dec 5, 2024)
+- Updated `org.apache.httpcomponents.client5` dependency to 5.4.1 to fix vulnerabilities.
+- Updated `redis.clients` dependency to 4.4.8 to fix vulnerabilities.
+
+4.13.0 (Sep 13, 2024)
+- Added support for Kerberos Proxy authentication.
+
+4.12.1 (Jun 10, 2024)
+- Fixed deadlock for virtual thread in Push Manager and SSE Client.
+
4.12.0 (May 15, 2024)
- Added support for targeting rules based on semantic versions (https://semver.org/).
- Added the logic to handle correctly when the SDK receives an unsupported Matcher type.
diff --git a/LICENSE b/LICENSE
index c022e9200..df08de3fb 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright © 2024 Split Software, Inc.
+Copyright © 2025 Split Software, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/client/pom.xml b/client/pom.xml
index b48aa1100..d0ae4a789 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -5,8 +5,9 @@
io.split.client
java-client-parent
- 4.12.0
+ 4.16.0
+ 4.16.0
java-client
jar
Java Client
@@ -64,7 +65,9 @@
io.split.schemas:*
io.codigo.grammar:*
org.apache.httpcomponents.*
- com.google.*
+ org.apache.hc.*
+ com.google.code.gson:gson
+ com.google.guava:guava
org.yaml:snakeyaml:*
@@ -165,7 +168,7 @@
org.apache.httpcomponents.client5
httpclient5
- 5.0.3
+ 5.4.4
com.google.code.gson
@@ -238,5 +241,17 @@
4.0.3
test
+
+ org.powermock
+ powermock-module-junit4
+ 1.7.4
+ test
+
+
+ org.powermock
+ powermock-api-mockito
+ 1.7.4
+ test
+
diff --git a/client/src/main/java/io/split/Spec.java b/client/src/main/java/io/split/Spec.java
index 9e03a59ab..b2c7de4b3 100644
--- a/client/src/main/java/io/split/Spec.java
+++ b/client/src/main/java/io/split/Spec.java
@@ -6,6 +6,7 @@ private Spec() {
// restrict instantiation
}
- public static final String SPEC_VERSION = "1.1";
+ 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/CacheUpdaterService.java b/client/src/main/java/io/split/client/CacheUpdaterService.java
index 231757ae7..63b426634 100644
--- a/client/src/main/java/io/split/client/CacheUpdaterService.java
+++ b/client/src/main/java/io/split/client/CacheUpdaterService.java
@@ -11,7 +11,6 @@
import io.split.engine.matchers.CombiningMatcher;
import io.split.engine.matchers.strings.WhitelistMatcher;
import io.split.grammar.Treatments;
-import io.split.storages.SplitCacheConsumer;
import io.split.storages.SplitCacheProducer;
import java.util.ArrayList;
@@ -52,7 +51,7 @@ public void updateCache(Map map) {
String treatment = conditions.size() > 0 ? Treatments.CONTROL : localhostSplit.treatment;
configurations.put(localhostSplit.treatment, localhostSplit.config);
- split = new ParsedSplit(splitName, 0, false, treatment,conditions, LOCALHOST, 0, 100, 0, 0, configurations, new HashSet<>());
+ split = new ParsedSplit(splitName, 0, false, treatment,conditions, LOCALHOST, 0, 100, 0, 0, configurations, new HashSet<>(), true, null);
parsedSplits.removeIf(parsedSplit -> parsedSplit.feature().equals(splitName));
parsedSplits.add(split);
}
diff --git a/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java b/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java
index 9f8d2036b..49eb66a99 100644
--- a/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java
+++ b/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java
@@ -2,8 +2,10 @@
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.dtos.SplitChangesOldPayloadDto;
import io.split.client.exceptions.UriTooLongException;
import io.split.client.utils.Json;
import io.split.client.utils.Utils;
@@ -22,7 +24,8 @@
import java.net.URISyntaxException;
import static com.google.common.base.Preconditions.checkNotNull;
-import static io.split.Spec.SPEC_VERSION;
+import static io.split.Spec.SPEC_1_3;
+import static io.split.Spec.SPEC_1_1;
/**
* Created by adilaijaz on 5/30/15.
@@ -31,23 +34,30 @@ 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";
+ private String specVersion = SPEC_1_3;
+ private int PROXY_CHECK_INTERVAL_MILLISECONDS_SS = 24 * 60 * 60 * 1000;
+ private Long _lastProxyCheckTimestamp = 0L;
private final SplitHttpClient _client;
private final URI _target;
private final TelemetryRuntimeProducer _telemetryRuntimeProducer;
+ private final boolean _rootURIOverriden;
- public static HttpSplitChangeFetcher create(SplitHttpClient client, URI root, TelemetryRuntimeProducer telemetryRuntimeProducer)
+ public static HttpSplitChangeFetcher create(SplitHttpClient client, URI root, TelemetryRuntimeProducer telemetryRuntimeProducer,
+ boolean rootURIOverriden)
throws URISyntaxException {
- return new HttpSplitChangeFetcher(client, Utils.appendPath(root, "api/splitChanges"), telemetryRuntimeProducer);
+ return new HttpSplitChangeFetcher(client, Utils.appendPath(root, "api/splitChanges"), telemetryRuntimeProducer, rootURIOverriden);
}
- private HttpSplitChangeFetcher(SplitHttpClient client, URI uri, TelemetryRuntimeProducer telemetryRuntimeProducer) {
+ private HttpSplitChangeFetcher(SplitHttpClient client, URI uri, TelemetryRuntimeProducer telemetryRuntimeProducer, boolean rootURIOverriden) {
_client = client;
_target = uri;
checkNotNull(_target);
_telemetryRuntimeProducer = checkNotNull(telemetryRuntimeProducer);
+ _rootURIOverriden = rootURIOverriden;
}
long makeRandomTill() {
@@ -56,39 +66,66 @@ long makeRandomTill() {
}
@Override
- public SplitChange fetch(long since, FetchOptions options) {
-
+ public SplitChange fetch(long since, long sinceRBS, FetchOptions options) {
long start = System.currentTimeMillis();
-
try {
- URIBuilder uriBuilder = new URIBuilder(_target).addParameter(SPEC, "" + SPEC_VERSION);
- uriBuilder.addParameter(SINCE, "" + since);
- if (!options.flagSetsFilter().isEmpty()) {
- uriBuilder.addParameter(SETS, "" + options.flagSetsFilter());
+ URI uri = buildURL(options, since, sinceRBS);
+ if (specVersion.equals(SPEC_1_1) && (System.currentTimeMillis() - _lastProxyCheckTimestamp >= PROXY_CHECK_INTERVAL_MILLISECONDS_SS)) {
+ _log.info("Switching to new Feature flag spec ({}) and fetching.", SPEC_1_3);
+ specVersion = SPEC_1_3;
+ uri = buildURL(options, -1,-1);
}
- if (options.hasCustomCN()) {
- uriBuilder.addParameter(TILL, "" + options.targetCN());
- }
- URI uri = uriBuilder.build();
- SplitHttpResponse response = _client.get(uri, options, null);
+ 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_BAD_REQUEST && specVersion.equals(Spec.SPEC_1_3) && _rootURIOverriden) {
+ specVersion = Spec.SPEC_1_1;
+ _log.warn("Detected proxy without support for Feature flags spec {} version, will switch to spec version {}",
+ SPEC_1_3, SPEC_1_1);
+ _lastProxyCheckTimestamp = System.currentTimeMillis();
+ return fetch(since, sinceRBS, options);
+ }
+
_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);
+ if (specVersion.equals(Spec.SPEC_1_1)) {
+ return Json.fromJson(response.body(), SplitChangesOldPayloadDto.class).toSplitChange();
+ }
+
+ SplitChange splitChange = Json.fromJson(response.body(), SplitChange.class);
+ splitChange.clearCache = _lastProxyCheckTimestamp != 0;
+ _lastProxyCheckTimestamp = 0L;
+ return splitChange;
} 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);
+ _telemetryRuntimeProducer.recordSyncLatency(HTTPLatenciesEnum.SPLITS, System.currentTimeMillis() - start);
+ }
+ }
+
+
+ private URI buildURL(FetchOptions options, long since, long sinceRBS) throws URISyntaxException {
+ URIBuilder uriBuilder = new URIBuilder(_target).addParameter(SPEC, "" + specVersion);
+ uriBuilder.addParameter(SINCE, "" + since);
+ if (specVersion.equals(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());
}
+ return uriBuilder.build();
}
@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..03530d099 100644
--- a/client/src/main/java/io/split/client/JsonLocalhostSplitChangeFetcher.java
+++ b/client/src/main/java/io/split/client/JsonLocalhostSplitChangeFetcher.java
@@ -1,12 +1,15 @@
package io.split.client;
+import com.google.gson.JsonObject;
import com.google.gson.stream.JsonReader;
import io.split.client.dtos.SplitChange;
+import io.split.client.dtos.SplitChangesOldPayloadDto;
import io.split.client.utils.InputStreamProvider;
import io.split.client.utils.Json;
import io.split.client.utils.LocalhostSanitizer;
import io.split.engine.common.FetchOptions;
import io.split.engine.experiments.SplitChangeFetcher;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -17,47 +20,71 @@
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
+import static io.split.client.utils.Utils.checkExitConditions;
+
public class JsonLocalhostSplitChangeFetcher implements SplitChangeFetcher {
private static final Logger _log = LoggerFactory.getLogger(JsonLocalhostSplitChangeFetcher.class);
private final InputStreamProvider _inputStreamProvider;
- private byte [] lastHash;
+ private byte [] lastHashFeatureFlags;
+ private byte [] lastHashRuleBasedSegments;
public JsonLocalhostSplitChangeFetcher(InputStreamProvider inputStreamProvider) {
_inputStreamProvider = inputStreamProvider;
- lastHash = new byte[0];
+ lastHashFeatureFlags = new byte[0];
+ lastHashRuleBasedSegments = new byte[0];
}
@Override
- public SplitChange fetch(long since, FetchOptions options) {
+ public SplitChange fetch(long since, long sinceRBS, FetchOptions options) {
try {
JsonReader jsonReader = new JsonReader(new BufferedReader(new InputStreamReader(_inputStreamProvider.get(), StandardCharsets.UTF_8)));
+ if (checkOldSpec(new JsonReader(new BufferedReader(new InputStreamReader(_inputStreamProvider.get(), StandardCharsets.UTF_8))))) {
+ return Json.fromJson(jsonReader, SplitChangesOldPayloadDto.class).toSplitChange();
+ }
SplitChange splitChange = Json.fromJson(jsonReader, SplitChange.class);
- return processSplitChange(splitChange, since);
+ return processSplitChange(splitChange, since, sinceRBS);
} catch (Exception e) {
throw new IllegalStateException("Problem fetching splitChanges: " + e.getMessage(), e);
}
}
- private SplitChange processSplitChange(SplitChange splitChange, long changeNumber) throws NoSuchAlgorithmException {
+ private boolean checkOldSpec(JsonReader jsonReader) {
+ return Json.fromJson(jsonReader, JsonObject.class).has("splits");
+ }
+
+ private SplitChange processSplitChange(SplitChange splitChange, long changeNumber, long changeNumberRBS) throws NoSuchAlgorithmException {
SplitChange splitChangeToProcess = LocalhostSanitizer.sanitization(splitChange);
// if the till is less than storage CN and different from the default till ignore the change
- if (splitChangeToProcess.till < changeNumber && splitChangeToProcess.till != -1) {
+ if (checkExitConditions(splitChangeToProcess.featureFlags, changeNumber) ||
+ checkExitConditions(splitChangeToProcess.ruleBasedSegments, changeNumberRBS)) {
_log.warn("The till is lower than the change number or different to -1");
return null;
}
- String splitJson = splitChange.splits.toString();
- MessageDigest digest = MessageDigest.getInstance("SHA-1");
- digest.reset();
- digest.update(splitJson.getBytes());
- // calculate the json sha
- byte [] currHash = digest.digest();
+
+ byte [] currHashFeatureFlags = getStringDigest(splitChange.featureFlags.d.toString());
+ byte [] currHashRuleBasedSegments = getStringDigest(splitChange.ruleBasedSegments.d.toString());
//if sha exist and is equal to before sha, or if till is equal to default till returns the same segmentChange with till equals to storage CN
- if (Arrays.equals(lastHash, currHash) || splitChangeToProcess.till == -1) {
- splitChangeToProcess.till = changeNumber;
+ if (Arrays.equals(lastHashFeatureFlags, currHashFeatureFlags) || splitChangeToProcess.featureFlags.t == -1) {
+ splitChangeToProcess.featureFlags.t = changeNumber;
+ }
+ if (Arrays.equals(lastHashRuleBasedSegments, currHashRuleBasedSegments) || splitChangeToProcess.ruleBasedSegments.t == -1) {
+ splitChangeToProcess.ruleBasedSegments.t = changeNumberRBS;
}
- lastHash = currHash;
- splitChangeToProcess.since = changeNumber;
+
+ lastHashFeatureFlags = currHashFeatureFlags;
+ lastHashRuleBasedSegments = currHashRuleBasedSegments;
+ splitChangeToProcess.featureFlags.s = changeNumber;
+ splitChangeToProcess.ruleBasedSegments.s = changeNumberRBS;
+
return splitChangeToProcess;
}
+
+ private byte[] getStringDigest(String json) throws NoSuchAlgorithmException {
+ MessageDigest digest = MessageDigest.getInstance("SHA-1");
+ digest.reset();
+ digest.update(json.getBytes());
+ // calculate the json sha
+ return digest.digest();
+ }
}
\ No newline at end of file
diff --git a/client/src/main/java/io/split/client/LegacyLocalhostSplitChangeFetcher.java b/client/src/main/java/io/split/client/LegacyLocalhostSplitChangeFetcher.java
index a35c92cfe..c67055ec8 100644
--- a/client/src/main/java/io/split/client/LegacyLocalhostSplitChangeFetcher.java
+++ b/client/src/main/java/io/split/client/LegacyLocalhostSplitChangeFetcher.java
@@ -5,6 +5,7 @@
import io.split.client.dtos.Split;
import io.split.client.dtos.SplitChange;
import io.split.client.dtos.Status;
+import io.split.client.dtos.ChangeDto;
import io.split.client.utils.LocalhostConstants;
import io.split.client.utils.LocalhostSanitizer;
import io.split.engine.common.FetchOptions;
@@ -34,11 +35,12 @@ 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();
- splitChange.splits = new ArrayList<>();
+ splitChange.featureFlags = new ChangeDto<>();
+ splitChange.featureFlags.d = new ArrayList<>();
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
String lineTrim = line.trim();
if (lineTrim.isEmpty() || lineTrim.startsWith("#")) {
@@ -51,7 +53,8 @@ public SplitChange fetch(long since, FetchOptions options) {
_log.info("Ignoring line since it does not have 2 or 3 columns: " + lineTrim);
continue;
}
- Optional splitOptional = splitChange.splits.stream().filter(split -> split.name.equals(featureTreatment[0])).findFirst();
+ Optional splitOptional = splitChange.featureFlags.d.stream().
+ filter(split -> split.name.equals(featureTreatment[0])).findFirst();
Split split = splitOptional.orElse(null);
if(split == null) {
split = new Split();
@@ -59,7 +62,7 @@ public SplitChange fetch(long since, FetchOptions options) {
split.configurations = new HashMap<>();
split.conditions = new ArrayList<>();
} else {
- splitChange.splits.remove(split);
+ splitChange.featureFlags.d.remove(split);
}
split.status = Status.ACTIVE;
split.defaultTreatment = featureTreatment[1];
@@ -67,21 +70,20 @@ public SplitChange fetch(long since, FetchOptions options) {
split.trafficAllocation = LocalhostConstants.SIZE_100;
split.trafficAllocationSeed = LocalhostConstants.SIZE_1;
- Condition condition;
- if (featureTreatment.length == 2) {
- condition = LocalhostSanitizer.createCondition(null, featureTreatment[1]);
- } else {
- condition = LocalhostSanitizer.createCondition(featureTreatment[2], featureTreatment[1]);
- }
+ Condition condition = checkCondition(featureTreatment);
if(condition.conditionType != ConditionType.ROLLOUT){
split.conditions.add(0, condition);
} else {
split.conditions.add(condition);
}
- splitChange.splits.add(split);
+ splitChange.featureFlags.d.add(split);
}
- splitChange.till = since;
- splitChange.since = since;
+ splitChange.featureFlags.t = since;
+ splitChange.featureFlags.s = since;
+ splitChange.ruleBasedSegments = new ChangeDto<>();
+ splitChange.ruleBasedSegments.s = -1;
+ splitChange.ruleBasedSegments.t = -1;
+ splitChange.ruleBasedSegments.d = new ArrayList<>();
return splitChange;
} catch (FileNotFoundException f) {
_log.warn("There was no file named " + _splitFile.getPath() + " found. " +
@@ -96,4 +98,14 @@ public SplitChange fetch(long since, FetchOptions options) {
throw new IllegalStateException("Problem fetching splitChanges: " + e.getMessage(), e);
}
}
+
+ private Condition checkCondition(String[] featureTreatment) {
+ Condition condition;
+ if (featureTreatment.length == 2) {
+ condition = LocalhostSanitizer.createCondition(null, featureTreatment[1]);
+ } else {
+ condition = LocalhostSanitizer.createCondition(featureTreatment[2], featureTreatment[1]);
+ }
+ return condition;
+ }
}
\ No newline at end of file
diff --git a/client/src/main/java/io/split/client/RequestDecorator.java b/client/src/main/java/io/split/client/RequestDecorator.java
index 1572463ef..33059e617 100644
--- a/client/src/main/java/io/split/client/RequestDecorator.java
+++ b/client/src/main/java/io/split/client/RequestDecorator.java
@@ -2,16 +2,12 @@
import io.split.client.dtos.RequestContext;
-import org.apache.hc.core5.http.HttpRequest;
-import org.apache.hc.core5.http.Header;
import java.util.HashSet;
-import java.util.HashMap;
import java.util.Map;
import java.util.Arrays;
-import java.util.ArrayList;
import java.util.Set;
-import java.util.List;
+import java.util.stream.Collectors;
public final class RequestDecorator {
CustomHeaderDecorator _headerDecorator;
@@ -36,42 +32,16 @@ public RequestDecorator(CustomHeaderDecorator headerDecorator) {
: headerDecorator;
}
- public HttpRequest decorateHeaders(HttpRequest request) {
+ public RequestContext decorateHeaders(RequestContext request) {
try {
- Map> headers = _headerDecorator
- .getHeaderOverrides(new RequestContext(convertToMap(request.getHeaders())));
- for (Map.Entry> entry : headers.entrySet()) {
- if (isHeaderAllowed(entry.getKey())) {
- List values = entry.getValue();
- for (int i = 0; i < values.size(); i++) {
- if (i == 0) {
- request.setHeader(entry.getKey(), values.get(i));
- } else {
- request.addHeader(entry.getKey(), values.get(i));
- }
- }
- }
- }
+ return new RequestContext(_headerDecorator.getHeaderOverrides(request)
+ .entrySet()
+ .stream()
+ .filter(e -> !forbiddenHeaders.contains(e.getKey().toLowerCase()))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
} catch (Exception e) {
throw new IllegalArgumentException(
String.format("Problem adding custom headers to request decorator: %s", e), e);
}
-
- return request;
- }
-
- private boolean isHeaderAllowed(String headerName) {
- return !forbiddenHeaders.contains(headerName.toLowerCase());
- }
-
- private Map> convertToMap(Header[] to_convert) {
- Map> to_return = new HashMap>();
- for (Integer i = 0; i < to_convert.length; i++) {
- if (!to_return.containsKey(to_convert[i].getName())) {
- to_return.put(to_convert[i].getName(), new ArrayList());
- }
- to_return.get(to_convert[i].getName()).add(to_convert[i].getValue());
- }
- return to_return;
}
}
diff --git a/client/src/main/java/io/split/client/SplitClient.java b/client/src/main/java/io/split/client/SplitClient.java
index a07718b1f..3a21ca1b0 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 for evaluation.
+ * @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 for evaluation
+ * @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 for evaluation
+ *
+ * @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 for evaluation
+ * @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 for evaluation
+ * @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 for evaluation
+ *
+ * @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 for evaluation
+ * @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 for evaluation
+ *
+ * @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 for evaluation
+ * @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 for evaluation
+ * @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 for evaluation
+ * @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 for evaluation
+ * @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 for evaluation
+ * @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 for evaluation
+ * @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 for evaluation
+ * @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 for evaluation
+ * @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 for evaluation
+ * @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 for evaluation
+ * @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 for evaluation
+ *
+ * @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 for evaluation
+ * @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 for evaluation
+ * @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 for evaluation
+ * @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 for evaluation
+ * @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 for evaluation
+ * @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/SplitClientConfig.java b/client/src/main/java/io/split/client/SplitClientConfig.java
index 2f29c1719..fd312c3b2 100644
--- a/client/src/main/java/io/split/client/SplitClientConfig.java
+++ b/client/src/main/java/io/split/client/SplitClientConfig.java
@@ -4,6 +4,7 @@
import io.split.client.impressions.ImpressionsManager;
import io.split.client.utils.FileTypeEnum;
import io.split.integrations.IntegrationsConfig;
+import io.split.service.CustomHttpModule;
import io.split.storages.enums.OperationMode;
import io.split.storages.enums.StorageMode;
import org.apache.hc.core5.http.HttpHost;
@@ -91,7 +92,7 @@ public class SplitClientConfig {
private final HashSet _flagSetsFilter;
private final int _invalidSets;
private final CustomHeaderDecorator _customHeaderDecorator;
-
+ private final CustomHttpModule _alternativeHTTPModule;
public static Builder builder() {
return new Builder();
@@ -148,7 +149,8 @@ private SplitClientConfig(String endpoint,
ThreadFactory threadFactory,
HashSet flagSetsFilter,
int invalidSets,
- CustomHeaderDecorator customHeaderDecorator) {
+ CustomHeaderDecorator customHeaderDecorator,
+ CustomHttpModule alternativeHTTPModule) {
_endpoint = endpoint;
_eventsEndpoint = eventsEndpoint;
_featuresRefreshRate = pollForFeatureChangesEveryNSeconds;
@@ -201,6 +203,7 @@ private SplitClientConfig(String endpoint,
_flagSetsFilter = flagSetsFilter;
_invalidSets = invalidSets;
_customHeaderDecorator = customHeaderDecorator;
+ _alternativeHTTPModule = alternativeHTTPModule;
Properties props = new Properties();
try {
@@ -409,6 +412,11 @@ public CustomHeaderDecorator customHeaderDecorator() {
return _customHeaderDecorator;
}
+ public boolean isSdkEndpointOverridden() {
+ return !_endpoint.equals(SDK_ENDPOINT);
+ }
+
+ public CustomHttpModule alternativeHTTPModule() { return _alternativeHTTPModule; }
public static final class Builder {
private String _endpoint = SDK_ENDPOINT;
@@ -466,6 +474,7 @@ public static final class Builder {
private HashSet _flagSetsFilter = new HashSet<>();
private int _invalidSetsCount = 0;
private CustomHeaderDecorator _customHeaderDecorator = null;
+ private CustomHttpModule _alternativeHTTPModule = null;
public Builder() {
}
@@ -960,6 +969,17 @@ public Builder customHeaderDecorator(CustomHeaderDecorator customHeaderDecorator
return this;
}
+ /**
+ * Alternative Http Client
+ *
+ * @param alternativeHTTPModule
+ * @return this builder
+ */
+ public Builder alternativeHTTPModule(CustomHttpModule alternativeHTTPModule) {
+ _alternativeHTTPModule = alternativeHTTPModule;
+ return this;
+ }
+
/**
* Thread Factory
*
@@ -971,7 +991,7 @@ public Builder threadFactory(ThreadFactory threadFactory) {
return this;
}
- public SplitClientConfig build() {
+ private void verifyRates() {
if (_featuresRefreshRate < 5 ) {
throw new IllegalArgumentException("featuresRefreshRate must be >= 5: " + _featuresRefreshRate);
}
@@ -980,15 +1000,6 @@ public SplitClientConfig build() {
throw new IllegalArgumentException("segmentsRefreshRate must be >= 30: " + _segmentsRefreshRate);
}
- switch (_impressionsMode) {
- case OPTIMIZED:
- _impressionsRefreshRate = (_impressionsRefreshRate <= 0) ? 300 : Math.max(60, _impressionsRefreshRate);
- break;
- case DEBUG:
- _impressionsRefreshRate = (_impressionsRefreshRate <= 0) ? 60 : _impressionsRefreshRate;
- break;
- }
-
if (_eventSendIntervalInMillis < 1000) {
throw new IllegalArgumentException("_eventSendIntervalInMillis must be >= 1000: " + _eventSendIntervalInMillis);
}
@@ -996,19 +1007,12 @@ public SplitClientConfig build() {
if (_metricsRefreshRate < 30) {
throw new IllegalArgumentException("metricsRefreshRate must be >= 30: " + _metricsRefreshRate);
}
-
- if (_impressionsQueueSize <=0 ) {
- throw new IllegalArgumentException("impressionsQueueSize must be > 0: " + _impressionsQueueSize);
- }
-
- if (_connectionTimeout <= 0) {
- throw new IllegalArgumentException("connectionTimeOutInMs must be > 0: " + _connectionTimeout);
- }
-
- if (_readTimeout <= 0) {
- throw new IllegalArgumentException("readTimeout must be > 0: " + _readTimeout);
+ if(_telemetryRefreshRate < 60) {
+ throw new IllegalStateException("_telemetryRefreshRate must be >= 60");
}
+ }
+ private void verifyEndPoints() {
if (_endpoint == null) {
throw new IllegalArgumentException("endpoint must not be null");
}
@@ -1021,18 +1025,6 @@ public SplitClientConfig build() {
throw new IllegalArgumentException("If endpoint is set, you must also set the events endpoint");
}
- if (_numThreadsForSegmentFetch <= 0) {
- throw new IllegalArgumentException("Number of threads for fetching segments MUST be greater than zero");
- }
-
- if (_authRetryBackoffBase <= 0) {
- throw new IllegalArgumentException("authRetryBackoffBase: must be >= 1");
- }
-
- if (_streamingReconnectBackoffBase <= 0) {
- throw new IllegalArgumentException("streamingReconnectBackoffBase: must be >= 1");
- }
-
if (_authServiceURL == null) {
throw new IllegalArgumentException("authServiceURL must not be null");
}
@@ -1044,22 +1036,26 @@ public SplitClientConfig build() {
if (_telemetryURl == null) {
throw new IllegalArgumentException("telemetryURl must not be null");
}
+ }
- if (_onDemandFetchRetryDelayMs <= 0) {
- throw new IllegalStateException("streamingRetryDelay must be > 0");
+ private void verifyAllModes() {
+ switch (_impressionsMode) {
+ case OPTIMIZED:
+ _impressionsRefreshRate = (_impressionsRefreshRate <= 0) ? 300 : Math.max(60, _impressionsRefreshRate);
+ break;
+ case DEBUG:
+ _impressionsRefreshRate = (_impressionsRefreshRate <= 0) ? 60 : _impressionsRefreshRate;
+ break;
+ case NONE:
+ break;
}
- if(_onDemandFetchMaxRetries <= 0) {
- throw new IllegalStateException("_onDemandFetchMaxRetries must be > 0");
+ if (_impressionsQueueSize <=0 ) {
+ throw new IllegalArgumentException("impressionsQueueSize must be > 0: " + _impressionsQueueSize);
}
-
if(_storageMode == null) {
_storageMode = StorageMode.MEMORY;
}
-
- if(_telemetryRefreshRate < 60) {
- throw new IllegalStateException("_telemetryRefreshRate must be >= 60");
- }
if(OperationMode.CONSUMER.equals(_operationMode)){
if(_customStorageWrapper == null) {
@@ -1067,8 +1063,56 @@ public SplitClientConfig build() {
}
_storageMode = StorageMode.PLUGGABLE;
}
+ }
+
+ private void verifyNetworkParams() {
+ if (_connectionTimeout <= 0) {
+ throw new IllegalArgumentException("connectionTimeOutInMs must be > 0: " + _connectionTimeout);
+ }
+
+ if (_readTimeout <= 0) {
+ throw new IllegalArgumentException("readTimeout must be > 0: " + _readTimeout);
+ }
+ if (_authRetryBackoffBase <= 0) {
+ throw new IllegalArgumentException("authRetryBackoffBase: must be >= 1");
+ }
+
+ if (_streamingReconnectBackoffBase <= 0) {
+ throw new IllegalArgumentException("streamingReconnectBackoffBase: must be >= 1");
+ }
+
+ if (_onDemandFetchRetryDelayMs <= 0) {
+ throw new IllegalStateException("streamingRetryDelay must be > 0");
+ }
+
+ if(_onDemandFetchMaxRetries <= 0) {
+ throw new IllegalStateException("_onDemandFetchMaxRetries must be > 0");
+ }
+ }
+
+ private void verifyAlternativeClient() {
+ if (_alternativeHTTPModule != null && _streamingEnabled) {
+ throw new IllegalArgumentException("Streaming feature is not supported with Alternative HTTP Client");
+ }
+ }
+
+ public SplitClientConfig build() {
+
+ verifyRates();
+
+ verifyAllModes();
+
+ verifyEndPoints();
+
+ verifyNetworkParams();
+
+ verifyAlternativeClient();
+
+ if (_numThreadsForSegmentFetch <= 0) {
+ throw new IllegalArgumentException("Number of threads for fetching segments MUST be greater than zero");
+ }
- return new SplitClientConfig(
+ return new SplitClientConfig(
_endpoint,
_eventsEndpoint,
_featuresRefreshRate,
@@ -1120,7 +1164,8 @@ public SplitClientConfig build() {
_threadFactory,
_flagSetsFilter,
_invalidSetsCount,
- _customHeaderDecorator);
+ _customHeaderDecorator,
+ _alternativeHTTPModule);
}
}
}
\ No newline at end of file
diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java
index e876871e3..9f4b8ff9e 100644
--- a/client/src/main/java/io/split/client/SplitClientImpl.java
+++ b/client/src/main/java/io/split/client/SplitClientImpl.java
@@ -1,7 +1,10 @@
package io.split.client;
+import com.google.gson.GsonBuilder;
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;
@@ -16,6 +19,7 @@
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;
@@ -92,27 +96,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
@@ -122,111 +129,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
@@ -312,7 +485,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));
@@ -335,7 +508,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);
@@ -356,7 +528,9 @@ private SplitResult getTreatmentWithConfigInternal(String matchingKey, String bu
String.format("sdk.%s", methodEnum.getMethod()),
_config.labelsEnabled() ? result.label : null,
result.changeNumber,
- attributes
+ attributes,
+ result.track,
+ validateProperties(evaluationOptions.getProperties())
);
_telemetryEvaluationProducer.recordLatency(methodEnum, System.currentTimeMillis() - initTime);
return new SplitResult(result.treatment, result.configurations);
@@ -371,8 +545,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());
+ }
+
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()));
@@ -387,7 +572,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);
@@ -400,7 +587,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()) {
@@ -421,7 +610,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);
@@ -434,8 +625,8 @@ private Map getTreatmentsBySetsWithConfigInternal(String ma
}
private Map processEvaluatorResult(Map evaluatorResult,
MethodEnum methodEnum, String matchingKey, String bucketingKey, Map attributes, long initTime){
- List impressions = new ArrayList<>();
+ Object> attributes, long initTime, String properties){
+ List decoratedImpressions = new ArrayList<>();
Map result = new HashMap<>();
evaluatorResult.keySet().forEach(t -> {
if (evaluatorResult.get(t).treatment.equals(Treatments.CONTROL) && evaluatorResult.get(t).label.
@@ -445,13 +636,16 @@ private Map processEvaluatorResult(Map 0) {
- _impressionManager.track(impressions);
+ if (!decoratedImpressions.isEmpty()) {
+ _impressionManager.track(decoratedImpressions);
}
return result;
}
@@ -501,10 +695,13 @@ private Set 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) {
+ String operation, String label, Long changeNumber, Map attributes, boolean track, String properties) {
try {
- _impressionManager.track(Stream.of(new Impression(matchingKey, bucketingKey, featureFlagName, result, System.currentTimeMillis(),
- label, changeNumber, attributes)).collect(Collectors.toList()));
+ _impressionManager.track(Stream.of(
+ new DecoratedImpression(
+ new Impression(matchingKey, bucketingKey, featureFlagName, result, System.currentTimeMillis(),
+ 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 a783b1445..9932cbf8c 100644
--- a/client/src/main/java/io/split/client/SplitFactoryImpl.java
+++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java
@@ -54,26 +54,33 @@
import io.split.engine.experiments.SplitFetcherImp;
import io.split.engine.experiments.SplitParser;
import io.split.engine.experiments.SplitSynchronizationTask;
+import io.split.engine.experiments.RuleBasedSegmentParser;
import io.split.engine.segments.SegmentChangeFetcher;
import io.split.engine.segments.SegmentSynchronizationTaskImp;
import io.split.integrations.IntegrationsConfig;
-import io.split.service.SplitHttpClient;
import io.split.service.SplitHttpClientImpl;
+import io.split.service.SplitHttpClient;
+
import io.split.storages.SegmentCache;
import io.split.storages.SegmentCacheConsumer;
import io.split.storages.SegmentCacheProducer;
import io.split.storages.SplitCache;
import io.split.storages.SplitCacheConsumer;
import io.split.storages.SplitCacheProducer;
+import io.split.storages.RuleBasedSegmentCache;
+import io.split.storages.RuleBasedSegmentCacheProducer;
+import io.split.storages.RuleBasedSegmentCacheConsumer;
import io.split.storages.enums.OperationMode;
import io.split.storages.memory.InMemoryCacheImp;
import io.split.storages.memory.SegmentCacheInMemoryImpl;
+import io.split.storages.memory.RuleBasedSegmentCacheInMemoryImp;
import io.split.storages.pluggable.adapters.UserCustomEventAdapterProducer;
import io.split.storages.pluggable.adapters.UserCustomImpressionAdapterConsumer;
import io.split.storages.pluggable.adapters.UserCustomImpressionAdapterProducer;
import io.split.storages.pluggable.adapters.UserCustomSegmentAdapterConsumer;
import io.split.storages.pluggable.adapters.UserCustomSplitAdapterConsumer;
import io.split.storages.pluggable.adapters.UserCustomTelemetryAdapterProducer;
+import io.split.storages.pluggable.adapters.UserCustomRuleBasedSegmentAdapterConsumer;
import io.split.storages.pluggable.domain.UserStorageWrapper;
import io.split.storages.pluggable.synchronizer.TelemetryConsumerSubmitter;
import io.split.telemetry.storage.InMemoryTelemetryStorage;
@@ -83,6 +90,7 @@
import io.split.telemetry.synchronizer.TelemetryInMemorySubmitter;
import io.split.telemetry.synchronizer.TelemetrySyncTask;
import io.split.telemetry.synchronizer.TelemetrySynchronizer;
+
import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.Credentials;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
@@ -102,7 +110,6 @@
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
-import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pluggable.CustomStorageWrapper;
@@ -111,19 +118,19 @@
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ArrayList;
import static io.split.client.utils.SplitExecutorFactory.buildExecutorService;
public class SplitFactoryImpl implements SplitFactory {
- private static final Logger _log = LoggerFactory.getLogger(SplitFactory.class);
+ 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 are not added to the config.";
private final static long SSE_CONNECT_TIMEOUT = 30000;
private final static long SSE_SOCKET_TIMEOUT = 70000;
@@ -155,15 +162,16 @@ public class SplitFactoryImpl implements SplitFactory {
private final SplitSynchronizationTask _splitSynchronizationTask;
private final EventsTask _eventsTask;
private final SyncManager _syncManager;
- private final SplitHttpClient _splitHttpClient;
+ private SplitHttpClient _splitHttpClient;
private final UserStorageWrapper _userStorageWrapper;
private final ImpressionsSender _impressionsSender;
private final URI _rootTarget;
private final URI _eventsRootTarget;
private final UniqueKeysTracker _uniqueKeysTracker;
+ private RequestDecorator _requestDecorator;
// Constructor for standalone mode
- public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyntaxException {
+ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyntaxException, IOException {
_userStorageWrapper = null;
_operationMode = config.operationMode();
_startTime = System.currentTimeMillis();
@@ -186,9 +194,13 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn
// SDKReadinessGates
_gates = new SDKReadinessGates();
+ _requestDecorator = new RequestDecorator(config.customHeaderDecorator());
// HttpClient
- RequestDecorator requestDecorator = new RequestDecorator(config.customHeaderDecorator());
- _splitHttpClient = buildSplitHttpClient(apiToken, config, _sdkMetadata, requestDecorator);
+ if (config.alternativeHTTPModule() == null) {
+ _splitHttpClient = buildSplitHttpClient(apiToken, config, _sdkMetadata, _requestDecorator);
+ } else {
+ _splitHttpClient = config.alternativeHTTPModule().createClient(apiToken, _sdkMetadata, _requestDecorator);
+ }
// Roots
_rootTarget = URI.create(config.endpoint());
@@ -196,6 +208,7 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn
// Cache Initialisations
SegmentCache segmentCache = new SegmentCacheInMemoryImpl();
+ RuleBasedSegmentCache ruleBasedSegmentCache = new RuleBasedSegmentCacheInMemoryImp();
FlagSetsFilter flagSetsFilter = new FlagSetsFilterImpl(config.getSetsFilter());
SplitCache splitCache = new InMemoryCacheImp(flagSetsFilter);
ImpressionsStorage impressionsStorage = new InMemoryImpressionsStorage(config.impressionsQueueSize());
@@ -206,11 +219,13 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn
splitCache, _segmentCache, telemetryStorage, _startTime);
// Segments
- _segmentSynchronizationTaskImp = buildSegments(config, segmentCache, splitCache);
+ _segmentSynchronizationTaskImp = buildSegments(config, segmentCache, splitCache, ruleBasedSegmentCache);
SplitParser splitParser = new SplitParser();
+ RuleBasedSegmentParser ruleBasedSegmentParser = new RuleBasedSegmentParser();
// SplitFetcher
- _splitFetcher = buildSplitFetcher(splitCache, splitParser, flagSetsFilter);
+ _splitFetcher = buildSplitFetcher(splitCache, splitParser, flagSetsFilter,
+ ruleBasedSegmentParser, ruleBasedSegmentCache, config.isSdkEndpointOverridden());
// SplitSynchronizationTask
_splitSynchronizationTask = new SplitSynchronizationTask(_splitFetcher,
@@ -234,10 +249,11 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn
EventsSender eventsSender = EventsSender.create(_splitHttpClient, _eventsRootTarget, _telemetryStorageProducer);
_eventsTask = EventsTask.create(config.eventSendIntervalInMillis(), eventsStorage, eventsSender,
config.getThreadFactory());
- _telemetrySyncTask = new TelemetrySyncTask(config.getTelemetryRefreshRate(), _telemetrySynchronizer, config.getThreadFactory());
+ _telemetrySyncTask = new TelemetrySyncTask(config.getTelemetryRefreshRate(), _telemetrySynchronizer,
+ config.getThreadFactory());
// Evaluator
- _evaluator = new EvaluatorImp(splitCache, segmentCache);
+ _evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache);
// SplitClient
_client = new SplitClientImpl(this,
@@ -257,11 +273,12 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn
// SyncManager
SplitTasks splitTasks = SplitTasks.build(_splitSynchronizationTask, _segmentSynchronizationTaskImp,
_impressionsManager, _eventsTask, _telemetrySyncTask, _uniqueKeysTracker);
- SplitAPI splitAPI = SplitAPI.build(_splitHttpClient, buildSSEdHttpClient(apiToken, config, _sdkMetadata), requestDecorator);
+ SplitAPI splitAPI = SplitAPI.build(_splitHttpClient, buildSSEdHttpClient(apiToken, config, _sdkMetadata),
+ _requestDecorator);
_syncManager = SyncManagerImp.build(splitTasks, _splitFetcher, splitCache, splitAPI,
segmentCache, _gates, _telemetryStorageProducer, _telemetrySynchronizer, config, splitParser,
- flagSetsFilter);
+ ruleBasedSegmentParser, flagSetsFilter, ruleBasedSegmentCache);
_syncManager.start();
// DestroyOnShutDown
@@ -325,11 +342,15 @@ protected SplitFactoryImpl(String apiToken, SplitClientConfig config, CustomStor
_gates = new SDKReadinessGates();
_telemetrySynchronizer = new TelemetryConsumerSubmitter(customStorageWrapper, _sdkMetadata);
- _evaluator = new EvaluatorImp(userCustomSplitAdapterConsumer, userCustomSegmentAdapterConsumer);
+ UserCustomRuleBasedSegmentAdapterConsumer userCustomRuleBasedSegmentAdapterConsumer =
+ new UserCustomRuleBasedSegmentAdapterConsumer(customStorageWrapper);
+ _evaluator = new EvaluatorImp(userCustomSplitAdapterConsumer, userCustomSegmentAdapterConsumer, userCustomRuleBasedSegmentAdapterConsumer);
_impressionsSender = PluggableImpressionSender.create(customStorageWrapper);
_uniqueKeysTracker = createUniqueKeysTracker(config);
- _impressionsManager = buildImpressionsManager(config, userCustomImpressionAdapterConsumer, userCustomImpressionAdapterProducer);
- _telemetrySyncTask = new TelemetrySyncTask(config.getTelemetryRefreshRate(), _telemetrySynchronizer, config.getThreadFactory());
+ _impressionsManager = buildImpressionsManager(config, userCustomImpressionAdapterConsumer,
+ userCustomImpressionAdapterProducer);
+ _telemetrySyncTask = new TelemetrySyncTask(config.getTelemetryRefreshRate(), _telemetrySynchronizer,
+ config.getThreadFactory());
SplitTasks splitTasks = SplitTasks.build(null, null,
_impressionsManager, null, _telemetrySyncTask, _uniqueKeysTracker);
@@ -382,6 +403,7 @@ protected SplitFactoryImpl(SplitClientConfig config) {
SegmentCache segmentCache = new SegmentCacheInMemoryImpl();
FlagSetsFilter flagSetsFilter = new FlagSetsFilterImpl(config.getSetsFilter());
SplitCache splitCache = new InMemoryCacheImp(flagSetsFilter);
+ RuleBasedSegmentCache ruleBasedSegmentCache = new RuleBasedSegmentCacheInMemoryImp();
_splitCache = splitCache;
_gates = new SDKReadinessGates();
_segmentCache = segmentCache;
@@ -399,14 +421,16 @@ protected SplitFactoryImpl(SplitClientConfig config) {
segmentCache,
_telemetryStorageProducer,
_splitCache,
- config.getThreadFactory());
+ config.getThreadFactory(),
+ ruleBasedSegmentCache);
// SplitFetcher
SplitChangeFetcher splitChangeFetcher = createSplitChangeFetcher(config);
SplitParser splitParser = new SplitParser();
+ RuleBasedSegmentParser ruleBasedSegmentParser = new RuleBasedSegmentParser();
_splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, splitCache, _telemetryStorageProducer,
- flagSetsFilter);
+ flagSetsFilter, ruleBasedSegmentParser, ruleBasedSegmentCache);
// SplitSynchronizationTask
_splitSynchronizationTask = new SplitSynchronizationTask(_splitFetcher, splitCache,
@@ -418,7 +442,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();
@@ -491,7 +515,7 @@ public boolean isDestroyed() {
return isTerminated;
}
- private static SplitHttpClient buildSplitHttpClient(String apiToken, SplitClientConfig config,
+ protected static SplitHttpClient buildSplitHttpClient(String apiToken, SplitClientConfig config,
SDKMetadata sdkMetadata, RequestDecorator requestDecorator)
throws URISyntaxException {
SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create()
@@ -585,7 +609,7 @@ private static HttpClientBuilder setupProxy(HttpClientBuilder httpClientbuilder,
private SegmentSynchronizationTaskImp buildSegments(SplitClientConfig config,
SegmentCacheProducer segmentCacheProducer,
- SplitCacheConsumer splitCacheConsumer) throws URISyntaxException {
+ SplitCacheConsumer splitCacheConsumer, RuleBasedSegmentCacheConsumer ruleBasedSegmentCache) throws URISyntaxException {
SegmentChangeFetcher segmentChangeFetcher = HttpSegmentChangeFetcher.create(_splitHttpClient, _rootTarget,
_telemetryStorageProducer);
@@ -595,15 +619,17 @@ private SegmentSynchronizationTaskImp buildSegments(SplitClientConfig config,
segmentCacheProducer,
_telemetryStorageProducer,
splitCacheConsumer,
- config.getThreadFactory());
+ config.getThreadFactory(),
+ ruleBasedSegmentCache);
}
private SplitFetcher buildSplitFetcher(SplitCacheProducer splitCacheProducer, SplitParser splitParser,
- FlagSetsFilter flagSetsFilter) throws URISyntaxException {
+ FlagSetsFilter flagSetsFilter, RuleBasedSegmentParser ruleBasedSegmentParser,
+ RuleBasedSegmentCacheProducer ruleBasedSegmentCache, boolean isRootURIOverriden) throws URISyntaxException {
SplitChangeFetcher splitChangeFetcher = HttpSplitChangeFetcher.create(_splitHttpClient, _rootTarget,
- _telemetryStorageProducer);
+ _telemetryStorageProducer, isRootURIOverriden);
return new SplitFetcherImp(splitChangeFetcher, splitParser, splitCacheProducer, _telemetryStorageProducer,
- flagSetsFilter);
+ flagSetsFilter, ruleBasedSegmentParser, ruleBasedSegmentCache);
}
private ImpressionsManagerImpl buildImpressionsManager(SplitClientConfig config,
@@ -620,13 +646,14 @@ private ImpressionsManagerImpl buildImpressionsManager(SplitClientConfig config,
.collect(Collectors.toCollection(() -> impressionListeners));
}
ProcessImpressionStrategy processImpressionStrategy = null;
- ImpressionCounter counter = null;
+ ImpressionCounter counter = new ImpressionCounter();
ImpressionListener listener = !impressionListeners.isEmpty()
? new ImpressionListener.FederatedImpressionListener(impressionListeners)
: null;
+ ProcessImpressionNone processImpressionNone = new ProcessImpressionNone(listener != null, _uniqueKeysTracker, counter);
+
switch (config.impressionsMode()) {
case OPTIMIZED:
- counter = new ImpressionCounter();
ImpressionObserver impressionObserver = new ImpressionObserver(config.getLastSeenCacheSize());
processImpressionStrategy = new ProcessImpressionOptimized(listener != null, impressionObserver,
counter, _telemetryStorageProducer);
@@ -636,13 +663,12 @@ private ImpressionsManagerImpl buildImpressionsManager(SplitClientConfig config,
processImpressionStrategy = new ProcessImpressionDebug(listener != null, impressionObserver);
break;
case NONE:
- counter = new ImpressionCounter();
- processImpressionStrategy = new ProcessImpressionNone(listener != null, _uniqueKeysTracker, counter);
+ processImpressionStrategy = processImpressionNone;
break;
}
return ImpressionsManagerImpl.instance(config, _telemetryStorageProducer, impressionsStorageConsumer,
impressionsStorageProducer,
- _impressionsSender, processImpressionStrategy, counter, listener);
+ _impressionsSender, processImpressionNone, processImpressionStrategy, counter, listener);
}
private SDKMetadata createSdkMetadata(boolean ipAddressEnabled, String splitSdkVersion) {
@@ -680,15 +706,12 @@ private void manageSdkReady(SplitClientConfig config) {
}
private UniqueKeysTracker createUniqueKeysTracker(SplitClientConfig config) {
- if (config.impressionsMode().equals(ImpressionsManager.Mode.NONE)) {
- int uniqueKeysRefreshRate = config.operationMode().equals(OperationMode.STANDALONE)
- ? config.uniqueKeysRefreshRateInMemory()
- : config.uniqueKeysRefreshRateRedis();
- return new UniqueKeysTrackerImp(_telemetrySynchronizer, uniqueKeysRefreshRate,
- config.filterUniqueKeysRefreshRate(),
- config.getThreadFactory());
- }
- return null;
+ int uniqueKeysRefreshRate = config.operationMode().equals(OperationMode.STANDALONE)
+ ? config.uniqueKeysRefreshRateInMemory()
+ : config.uniqueKeysRefreshRateRedis();
+ return new UniqueKeysTrackerImp(_telemetrySynchronizer, uniqueKeysRefreshRate,
+ config.filterUniqueKeysRefreshRate(),
+ config.getThreadFactory());
}
private SplitChangeFetcher createSplitChangeFetcher(SplitClientConfig splitClientConfig) {
diff --git a/client/src/main/java/io/split/client/YamlLocalhostSplitChangeFetcher.java b/client/src/main/java/io/split/client/YamlLocalhostSplitChangeFetcher.java
index e90ca1389..b2dccfdca 100644
--- a/client/src/main/java/io/split/client/YamlLocalhostSplitChangeFetcher.java
+++ b/client/src/main/java/io/split/client/YamlLocalhostSplitChangeFetcher.java
@@ -5,6 +5,7 @@
import io.split.client.dtos.Split;
import io.split.client.dtos.SplitChange;
import io.split.client.dtos.Status;
+import io.split.client.dtos.ChangeDto;
import io.split.client.utils.InputStreamProvider;
import io.split.client.utils.LocalhostConstants;
import io.split.engine.common.FetchOptions;
@@ -32,17 +33,19 @@ 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