Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ target
.project
.settings
.DS_Store
dependency-reduced-pom.xml
dependency-reduced-pom.xml
5 changes: 5 additions & 0 deletions client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@
</properties>

<dependencies>
<dependency>
<groupId>io.split.client</groupId>
<artifactId>targeting-engine</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.split.client</groupId>
<artifactId>pluggable-storage</artifactId>
Expand Down
28 changes: 14 additions & 14 deletions client/src/main/java/io/split/client/CacheUpdaterService.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
package io.split.client;

import com.google.common.collect.Lists;
import io.split.client.dtos.ConditionType;
import io.split.client.dtos.MatcherCombiner;
import io.split.client.dtos.Partition;
import io.split.engine.experiments.ParsedCondition;
import io.split.engine.experiments.ParsedSplit;
import io.split.engine.matchers.AllKeysMatcher;
import io.split.engine.matchers.AttributeMatcher;
import io.split.engine.matchers.CombiningMatcher;
import io.split.engine.matchers.strings.WhitelistMatcher;
import io.split.rules.matchers.AllKeysMatcher;
import io.split.rules.matchers.AttributeMatcher;
import io.split.rules.matchers.CombiningMatcher;
import io.split.rules.matchers.WhitelistMatcher;
import io.split.grammar.Treatments;
import io.split.storages.SplitCacheProducer;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
Expand All @@ -22,15 +21,15 @@
import java.util.HashMap;
import java.util.stream.Collectors;

import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Objects;

public final class CacheUpdaterService {

private static String LOCALHOST = "localhost";
private SplitCacheProducer _splitCacheProducer;

public CacheUpdaterService(SplitCacheProducer splitCacheProducer) {
_splitCacheProducer = checkNotNull(splitCacheProducer);
_splitCacheProducer = Objects.requireNonNull(splitCacheProducer);
}

public void updateCache(Map<SplitAndKey, LocalhostSplit> map) {
Expand Down Expand Up @@ -78,9 +77,10 @@ private List<ParsedCondition> getConditions(String splitKey, ParsedSplit split,

private ParsedCondition createWhitelistCondition(String splitKey, Partition partition) {
ParsedCondition parsedCondition = new ParsedCondition(ConditionType.WHITELIST,
new CombiningMatcher(MatcherCombiner.AND,
Lists.newArrayList(new AttributeMatcher(null, new WhitelistMatcher(Lists.newArrayList(splitKey)), false))),
Lists.newArrayList(partition), splitKey);
new CombiningMatcher(CombiningMatcher.Combiner.AND,
new ArrayList<>(Arrays.asList(
new AttributeMatcher(null, new WhitelistMatcher(Arrays.asList(splitKey)), false)))),
new ArrayList<>(Arrays.asList(partition)), splitKey);
return parsedCondition;
}

Expand All @@ -89,9 +89,9 @@ private ParsedCondition createRolloutCondition(Partition partition) {
rolloutPartition.treatment = "-";
rolloutPartition.size = 0;
ParsedCondition parsedCondition = new ParsedCondition(ConditionType.ROLLOUT,
new CombiningMatcher(MatcherCombiner.AND,
Lists.newArrayList(new AttributeMatcher(null, new AllKeysMatcher(), false))),
Lists.newArrayList(partition, rolloutPartition), "LOCAL");
new CombiningMatcher(CombiningMatcher.Combiner.AND,
new ArrayList<>(Arrays.asList(new AttributeMatcher(null, new AllKeysMatcher(), false)))),
new ArrayList<>(Arrays.asList(partition, rolloutPartition)), "LOCAL");

return parsedCondition;
}
Expand Down
9 changes: 8 additions & 1 deletion client/src/main/java/io/split/client/api/SplitView.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;


/**
Expand Down Expand Up @@ -51,7 +52,13 @@ public static SplitView fromParsedSplit(ParsedSplit parsedSplit) {
splitView.configs = parsedSplit.configurations() == null? Collections.<String, String>emptyMap() : parsedSplit.configurations() ;
splitView.impressionsDisabled = parsedSplit.impressionsDisabled();
splitView.prerequisites = parsedSplit.prerequisitesMatcher() != null ?
parsedSplit.prerequisitesMatcher().getPrerequisites(): new ArrayList<>();
parsedSplit.prerequisitesMatcher().getPrerequisites().stream()
.map(p -> {
Prerequisites prereq = new Prerequisites();
prereq.featureFlagName = p.featureFlagName();
prereq.treatments = p.treatments();
return prereq;
}).collect(Collectors.toList()) : new ArrayList<>();

return splitView;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.split.client.impressions;

import io.split.client.utils.MurmurHash3;
import io.split.rules.bucketing.MurmurHash3;

public class ImpressionHasher {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
package io.split.engine.evaluator;

import io.split.client.dtos.ExcludedSegments;
import io.split.engine.experiments.ParsedCondition;
import io.split.engine.experiments.ParsedRuleBasedSegment;
import io.split.rules.engine.EvaluationResult;
import io.split.storages.RuleBasedSegmentCacheConsumer;
import io.split.storages.SegmentCacheConsumer;

import static com.google.common.base.Preconditions.checkNotNull;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class EvaluationContext {
public class EvaluationContext implements io.split.rules.engine.EvaluationContext {
private final Evaluator _evaluator;
private final SegmentCacheConsumer _segmentCacheConsumer;
private final RuleBasedSegmentCacheConsumer _ruleBasedSegmentCacheConsumer;

public EvaluationContext(Evaluator evaluator, SegmentCacheConsumer segmentCacheConsumer,
RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer) {
_evaluator = checkNotNull(evaluator);
_segmentCacheConsumer = checkNotNull(segmentCacheConsumer);
_ruleBasedSegmentCacheConsumer = checkNotNull(ruleBasedSegmentCacheConsumer);
_evaluator = Objects.requireNonNull(evaluator);
_segmentCacheConsumer = Objects.requireNonNull(segmentCacheConsumer);
_ruleBasedSegmentCacheConsumer = Objects.requireNonNull(ruleBasedSegmentCacheConsumer);
}

public Evaluator getEvaluator() {
Expand All @@ -28,4 +34,40 @@ public SegmentCacheConsumer getSegmentCache() {
public RuleBasedSegmentCacheConsumer getRuleBasedSegmentCache() {
return _ruleBasedSegmentCacheConsumer;
}

@Override
public EvaluationResult evaluate(String matchingKey, String bucketingKey, String ruleName, Map<String, Object> attributes) {
EvaluatorImp.TreatmentLabelAndChangeNumber r = _evaluator.evaluateFeature(matchingKey, bucketingKey, ruleName, attributes);
return new EvaluationResult(r.treatment, r.label);
}

@Override
public boolean isInSegment(String segmentName, String key) {
return _segmentCacheConsumer.isInSegment(segmentName, key);
}

@Override
public boolean isInRuleBasedSegment(String segmentName, String key, String bucketingKey, Map<String, Object> attributes) {
ParsedRuleBasedSegment parsedRuleBasedSegment = _ruleBasedSegmentCacheConsumer.get(segmentName);
if (parsedRuleBasedSegment == null) {
return false;
}
if (parsedRuleBasedSegment.excludedKeys().contains(key)) {
return false;
}
for (ExcludedSegments excludedSegment : parsedRuleBasedSegment.excludedSegments()) {
if (excludedSegment.isStandard() && _segmentCacheConsumer.isInSegment(excludedSegment.name, key)) {
return false;
}
if (excludedSegment.isRuleBased() && isInRuleBasedSegment(excludedSegment.name, key, bucketingKey, attributes)) {
return false;
}
}
for (ParsedCondition condition : parsedRuleBasedSegment.parsedConditions()) {
if (condition.matcher().match(key, bucketingKey, attributes, this)) {
return true;
}
}
return false;
}
}
113 changes: 21 additions & 92 deletions client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package io.split.engine.evaluator;

import io.split.client.dtos.ConditionType;
import io.split.client.dtos.FallbackTreatment;
import io.split.client.dtos.FallbackTreatmentCalculator;
import io.split.client.exceptions.ChangeNumberExceptionWrapper;
import io.split.engine.experiments.ParsedCondition;
import io.split.engine.experiments.ParsedSplit;
import io.split.engine.splitter.Splitter;
import io.split.rules.engine.EvaluationResult;
import io.split.rules.engine.TargetingEngine;
import io.split.rules.engine.TargetingEngineImpl;
import io.split.rules.exceptions.VersionedExceptionWrapper;
import io.split.storages.RuleBasedSegmentCacheConsumer;
import io.split.storages.SegmentCacheConsumer;
import io.split.storages.SplitCacheConsumer;
Expand All @@ -19,7 +20,7 @@
import java.util.List;
import java.util.Map;

import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Objects;

public class EvaluatorImp implements Evaluator {
private static final Logger _log = LoggerFactory.getLogger(EvaluatorImp.class);
Expand All @@ -28,15 +29,17 @@ public class EvaluatorImp implements Evaluator {
private final EvaluationContext _evaluationContext;
private final SplitCacheConsumer _splitCacheConsumer;
private final FallbackTreatmentCalculator _fallbackTreatmentCalculator;
private final TargetingEngine _targetingEngine;
private final String _evaluatorException = "Evaluator Exception";

public EvaluatorImp(SplitCacheConsumer splitCacheConsumer, SegmentCacheConsumer segmentCache,
RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer,
FallbackTreatmentCalculator fallbackTreatmentCalculator) {
_splitCacheConsumer = checkNotNull(splitCacheConsumer);
_segmentCacheConsumer = checkNotNull(segmentCache);
_splitCacheConsumer = Objects.requireNonNull(splitCacheConsumer);
_segmentCacheConsumer = Objects.requireNonNull(segmentCache);
_evaluationContext = new EvaluationContext(this, _segmentCacheConsumer, ruleBasedSegmentCacheConsumer);
_fallbackTreatmentCalculator = fallbackTreatmentCalculator;
_targetingEngine = new TargetingEngineImpl();
}

@Override
Expand Down Expand Up @@ -102,100 +105,26 @@ private List<String> getFeatureFlagNamesByFlagSets(List<String> flagSets) {

/**
* @param matchingKey MUST NOT be null
* @param bucketingKey
* @param bucketingKey may be null
* @param parsedSplit MUST NOT be null
* @param attributes MUST NOT be null
* @param attributes may be null
* @return
* @throws ChangeNumberExceptionWrapper
*/
private TreatmentLabelAndChangeNumber getTreatment(String matchingKey, String bucketingKey, ParsedSplit parsedSplit, Map<String,
Object> attributes) throws ChangeNumberExceptionWrapper {
private TreatmentLabelAndChangeNumber getTreatment(String matchingKey, String bucketingKey, ParsedSplit parsedSplit,
Map<String, Object> attributes) throws ChangeNumberExceptionWrapper {
try {
String config = getConfig(parsedSplit, parsedSplit.defaultTreatment());
if (parsedSplit.killed()) {
return new TreatmentLabelAndChangeNumber(
parsedSplit.defaultTreatment(),
Labels.KILLED,
parsedSplit.changeNumber(),
config,
parsedSplit.impressionsDisabled());
}

String bk = getBucketingKey(bucketingKey, matchingKey);

if (!parsedSplit.prerequisitesMatcher().match(matchingKey, bk, attributes, _evaluationContext)) {
return new TreatmentLabelAndChangeNumber(
parsedSplit.defaultTreatment(),
Labels.PREREQUISITES_NOT_MET,
parsedSplit.changeNumber(),
config,
parsedSplit.impressionsDisabled());
}

/*
* There are three parts to a single Feature flag: 1) Whitelists 2) Traffic Allocation
* 3) Rollout. The flag inRollout is there to understand when we move into the Rollout
* section. This is because we need to make sure that the Traffic Allocation
* computation happens after the whitelist but before the rollout.
*/
boolean inRollout = false;

for (ParsedCondition parsedCondition : parsedSplit.parsedConditions()) {

if (checkRollout(inRollout, parsedCondition)) {

if (parsedSplit.trafficAllocation() < 100) {
// if the traffic allocation is 100%, no need to do anything special.
int bucket = Splitter.getBucket(bk, parsedSplit.trafficAllocationSeed(), parsedSplit.algo());

if (bucket > parsedSplit.trafficAllocation()) {
// out of split
config = getConfig(parsedSplit, parsedSplit.defaultTreatment());
return new TreatmentLabelAndChangeNumber(parsedSplit.defaultTreatment(), Labels.NOT_IN_SPLIT,
parsedSplit.changeNumber(), config, parsedSplit.impressionsDisabled());
}

}
inRollout = true;
}

if (parsedCondition.matcher().match(matchingKey, bucketingKey, attributes, _evaluationContext)) {
String treatment = Splitter.getTreatment(bk, parsedSplit.seed(), parsedCondition.partitions(), parsedSplit.algo());
config = getConfig(parsedSplit, treatment);
return new TreatmentLabelAndChangeNumber(
treatment,
parsedCondition.label(),
parsedSplit.changeNumber(),
config,
parsedSplit.impressionsDisabled());
}
}

config = getConfig(parsedSplit, parsedSplit.defaultTreatment());

return new TreatmentLabelAndChangeNumber(
parsedSplit.defaultTreatment(),
Labels.DEFAULT_RULE,
parsedSplit.changeNumber(),
config,
parsedSplit.impressionsDisabled());
} catch (Exception e) {
throw new ChangeNumberExceptionWrapper(e, parsedSplit.changeNumber());
EvaluationResult r = _targetingEngine.evaluate(matchingKey, bucketingKey,
parsedSplit.targetingRule(), attributes, _evaluationContext);
String config = parsedSplit.configurations() != null
? parsedSplit.configurations().get(r.treatment) : null;
return new TreatmentLabelAndChangeNumber(r.treatment, r.label,
parsedSplit.changeNumber(), config, parsedSplit.impressionsDisabled());
} catch (VersionedExceptionWrapper e) {
throw new ChangeNumberExceptionWrapper(e.wrappedException(), parsedSplit.changeNumber());
}
}

private boolean checkRollout(boolean inRollout, ParsedCondition parsedCondition) {
return (!inRollout && parsedCondition.conditionType() == ConditionType.ROLLOUT);
}

private String getBucketingKey(String bucketingKey, String matchingKey) {
return (bucketingKey == null) ? matchingKey : bucketingKey;
}

private String getConfig(ParsedSplit parsedSplit, String returnedTreatment) {
return parsedSplit.configurations() != null ? parsedSplit.configurations().get(returnedTreatment) : null;
}

private String getFallbackConfig(FallbackTreatment fallbackTreatment) {
if (fallbackTreatment.getConfig() != null) {
return fallbackTreatment.getConfig();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import io.split.client.dtos.ConditionType;
import io.split.client.dtos.Partition;
import io.split.engine.matchers.CombiningMatcher;
import io.split.rules.matchers.CombiningMatcher;

import java.util.List;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
package io.split.engine.experiments;

import com.google.common.collect.ImmutableList;
import io.split.client.dtos.ExcludedSegments;
import io.split.engine.matchers.AttributeMatcher;
import io.split.engine.matchers.UserDefinedSegmentMatcher;
import io.split.rules.matchers.AttributeMatcher;
import io.split.rules.matchers.UserDefinedSegmentMatcher;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class ParsedRuleBasedSegment {

private final String _ruleBasedSegment;
private final ImmutableList<ParsedCondition> _parsedCondition;
private final List<ParsedCondition> _parsedCondition;
private final String _trafficTypeName;
private final long _changeNumber;
private final List<String> _excludedKeys;
Expand Down Expand Up @@ -45,7 +46,7 @@ public ParsedRuleBasedSegment(
List<ExcludedSegments> excludedSegments
) {
_ruleBasedSegment = ruleBasedSegment;
_parsedCondition = ImmutableList.copyOf(matcherAndSplits);
_parsedCondition = Collections.unmodifiableList(new ArrayList<>(matcherAndSplits));
_trafficTypeName = trafficTypeName;
_changeNumber = changeNumber;
_excludedKeys = excludedKeys;
Expand Down
Loading
Loading