Skip to content

Commit 0fdb768

Browse files
authored
Target rule module (#622)
1 parent 288e1e2 commit 0fdb768

114 files changed

Lines changed: 1937 additions & 765 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ target
99
.project
1010
.settings
1111
.DS_Store
12-
dependency-reduced-pom.xml
12+
dependency-reduced-pom.xml

client/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,11 @@
167167
</properties>
168168

169169
<dependencies>
170+
<dependency>
171+
<groupId>io.split.client</groupId>
172+
<artifactId>targeting-engine</artifactId>
173+
<version>${project.version}</version>
174+
</dependency>
170175
<dependency>
171176
<groupId>io.split.client</groupId>
172177
<artifactId>pluggable-storage</artifactId>

client/src/main/java/io/split/client/CacheUpdaterService.java

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
package io.split.client;
22

3-
import com.google.common.collect.Lists;
43
import io.split.client.dtos.ConditionType;
5-
import io.split.client.dtos.MatcherCombiner;
64
import io.split.client.dtos.Partition;
75
import io.split.engine.experiments.ParsedCondition;
86
import io.split.engine.experiments.ParsedSplit;
9-
import io.split.engine.matchers.AllKeysMatcher;
10-
import io.split.engine.matchers.AttributeMatcher;
11-
import io.split.engine.matchers.CombiningMatcher;
12-
import io.split.engine.matchers.strings.WhitelistMatcher;
7+
import io.split.rules.matchers.AllKeysMatcher;
8+
import io.split.rules.matchers.AttributeMatcher;
9+
import io.split.rules.matchers.CombiningMatcher;
10+
import io.split.rules.matchers.WhitelistMatcher;
1311
import io.split.grammar.Treatments;
1412
import io.split.storages.SplitCacheProducer;
1513

1614
import java.util.ArrayList;
15+
import java.util.Arrays;
1716
import java.util.HashSet;
1817
import java.util.List;
1918
import java.util.Map;
@@ -22,15 +21,15 @@
2221
import java.util.HashMap;
2322
import java.util.stream.Collectors;
2423

25-
import static com.google.common.base.Preconditions.checkNotNull;
24+
import java.util.Objects;
2625

2726
public final class CacheUpdaterService {
2827

2928
private static String LOCALHOST = "localhost";
3029
private SplitCacheProducer _splitCacheProducer;
3130

3231
public CacheUpdaterService(SplitCacheProducer splitCacheProducer) {
33-
_splitCacheProducer = checkNotNull(splitCacheProducer);
32+
_splitCacheProducer = Objects.requireNonNull(splitCacheProducer);
3433
}
3534

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

7978
private ParsedCondition createWhitelistCondition(String splitKey, Partition partition) {
8079
ParsedCondition parsedCondition = new ParsedCondition(ConditionType.WHITELIST,
81-
new CombiningMatcher(MatcherCombiner.AND,
82-
Lists.newArrayList(new AttributeMatcher(null, new WhitelistMatcher(Lists.newArrayList(splitKey)), false))),
83-
Lists.newArrayList(partition), splitKey);
80+
new CombiningMatcher(CombiningMatcher.Combiner.AND,
81+
new ArrayList<>(Arrays.asList(
82+
new AttributeMatcher(null, new WhitelistMatcher(Arrays.asList(splitKey)), false)))),
83+
new ArrayList<>(Arrays.asList(partition)), splitKey);
8484
return parsedCondition;
8585
}
8686

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

9696
return parsedCondition;
9797
}

client/src/main/java/io/split/client/api/SplitView.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.util.List;
1212
import java.util.Map;
1313
import java.util.Set;
14+
import java.util.stream.Collectors;
1415

1516

1617
/**
@@ -51,7 +52,13 @@ public static SplitView fromParsedSplit(ParsedSplit parsedSplit) {
5152
splitView.configs = parsedSplit.configurations() == null? Collections.<String, String>emptyMap() : parsedSplit.configurations() ;
5253
splitView.impressionsDisabled = parsedSplit.impressionsDisabled();
5354
splitView.prerequisites = parsedSplit.prerequisitesMatcher() != null ?
54-
parsedSplit.prerequisitesMatcher().getPrerequisites(): new ArrayList<>();
55+
parsedSplit.prerequisitesMatcher().getPrerequisites().stream()
56+
.map(p -> {
57+
Prerequisites prereq = new Prerequisites();
58+
prereq.featureFlagName = p.featureFlagName();
59+
prereq.treatments = p.treatments();
60+
return prereq;
61+
}).collect(Collectors.toList()) : new ArrayList<>();
5562

5663
return splitView;
5764
}

client/src/main/java/io/split/client/impressions/ImpressionHasher.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package io.split.client.impressions;
22

3-
import io.split.client.utils.MurmurHash3;
3+
import io.split.rules.bucketing.MurmurHash3;
44

55
public class ImpressionHasher {
66

client/src/main/java/io/split/engine/evaluator/EvaluationContext.java

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
package io.split.engine.evaluator;
22

3+
import io.split.client.dtos.ExcludedSegments;
4+
import io.split.engine.experiments.ParsedCondition;
5+
import io.split.engine.experiments.ParsedRuleBasedSegment;
6+
import io.split.rules.engine.EvaluationResult;
37
import io.split.storages.RuleBasedSegmentCacheConsumer;
48
import io.split.storages.SegmentCacheConsumer;
59

6-
import static com.google.common.base.Preconditions.checkNotNull;
10+
import java.util.List;
11+
import java.util.Map;
12+
import java.util.Objects;
713

8-
public class EvaluationContext {
14+
public class EvaluationContext implements io.split.rules.engine.EvaluationContext {
915
private final Evaluator _evaluator;
1016
private final SegmentCacheConsumer _segmentCacheConsumer;
1117
private final RuleBasedSegmentCacheConsumer _ruleBasedSegmentCacheConsumer;
1218

1319
public EvaluationContext(Evaluator evaluator, SegmentCacheConsumer segmentCacheConsumer,
1420
RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer) {
15-
_evaluator = checkNotNull(evaluator);
16-
_segmentCacheConsumer = checkNotNull(segmentCacheConsumer);
17-
_ruleBasedSegmentCacheConsumer = checkNotNull(ruleBasedSegmentCacheConsumer);
21+
_evaluator = Objects.requireNonNull(evaluator);
22+
_segmentCacheConsumer = Objects.requireNonNull(segmentCacheConsumer);
23+
_ruleBasedSegmentCacheConsumer = Objects.requireNonNull(ruleBasedSegmentCacheConsumer);
1824
}
1925

2026
public Evaluator getEvaluator() {
@@ -28,4 +34,40 @@ public SegmentCacheConsumer getSegmentCache() {
2834
public RuleBasedSegmentCacheConsumer getRuleBasedSegmentCache() {
2935
return _ruleBasedSegmentCacheConsumer;
3036
}
37+
38+
@Override
39+
public EvaluationResult evaluate(String matchingKey, String bucketingKey, String ruleName, Map<String, Object> attributes) {
40+
EvaluatorImp.TreatmentLabelAndChangeNumber r = _evaluator.evaluateFeature(matchingKey, bucketingKey, ruleName, attributes);
41+
return new EvaluationResult(r.treatment, r.label);
42+
}
43+
44+
@Override
45+
public boolean isInSegment(String segmentName, String key) {
46+
return _segmentCacheConsumer.isInSegment(segmentName, key);
47+
}
48+
49+
@Override
50+
public boolean isInRuleBasedSegment(String segmentName, String key, String bucketingKey, Map<String, Object> attributes) {
51+
ParsedRuleBasedSegment parsedRuleBasedSegment = _ruleBasedSegmentCacheConsumer.get(segmentName);
52+
if (parsedRuleBasedSegment == null) {
53+
return false;
54+
}
55+
if (parsedRuleBasedSegment.excludedKeys().contains(key)) {
56+
return false;
57+
}
58+
for (ExcludedSegments excludedSegment : parsedRuleBasedSegment.excludedSegments()) {
59+
if (excludedSegment.isStandard() && _segmentCacheConsumer.isInSegment(excludedSegment.name, key)) {
60+
return false;
61+
}
62+
if (excludedSegment.isRuleBased() && isInRuleBasedSegment(excludedSegment.name, key, bucketingKey, attributes)) {
63+
return false;
64+
}
65+
}
66+
for (ParsedCondition condition : parsedRuleBasedSegment.parsedConditions()) {
67+
if (condition.matcher().match(key, bucketingKey, attributes, this)) {
68+
return true;
69+
}
70+
}
71+
return false;
72+
}
3173
}

client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java

Lines changed: 21 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
package io.split.engine.evaluator;
22

3-
import io.split.client.dtos.ConditionType;
43
import io.split.client.dtos.FallbackTreatment;
54
import io.split.client.dtos.FallbackTreatmentCalculator;
65
import io.split.client.exceptions.ChangeNumberExceptionWrapper;
7-
import io.split.engine.experiments.ParsedCondition;
86
import io.split.engine.experiments.ParsedSplit;
9-
import io.split.engine.splitter.Splitter;
7+
import io.split.rules.engine.EvaluationResult;
8+
import io.split.rules.engine.TargetingEngine;
9+
import io.split.rules.engine.TargetingEngineImpl;
10+
import io.split.rules.exceptions.VersionedExceptionWrapper;
1011
import io.split.storages.RuleBasedSegmentCacheConsumer;
1112
import io.split.storages.SegmentCacheConsumer;
1213
import io.split.storages.SplitCacheConsumer;
@@ -19,7 +20,7 @@
1920
import java.util.List;
2021
import java.util.Map;
2122

22-
import static com.google.common.base.Preconditions.checkNotNull;
23+
import java.util.Objects;
2324

2425
public class EvaluatorImp implements Evaluator {
2526
private static final Logger _log = LoggerFactory.getLogger(EvaluatorImp.class);
@@ -28,15 +29,17 @@ public class EvaluatorImp implements Evaluator {
2829
private final EvaluationContext _evaluationContext;
2930
private final SplitCacheConsumer _splitCacheConsumer;
3031
private final FallbackTreatmentCalculator _fallbackTreatmentCalculator;
32+
private final TargetingEngine _targetingEngine;
3133
private final String _evaluatorException = "Evaluator Exception";
3234

3335
public EvaluatorImp(SplitCacheConsumer splitCacheConsumer, SegmentCacheConsumer segmentCache,
3436
RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer,
3537
FallbackTreatmentCalculator fallbackTreatmentCalculator) {
36-
_splitCacheConsumer = checkNotNull(splitCacheConsumer);
37-
_segmentCacheConsumer = checkNotNull(segmentCache);
38+
_splitCacheConsumer = Objects.requireNonNull(splitCacheConsumer);
39+
_segmentCacheConsumer = Objects.requireNonNull(segmentCache);
3840
_evaluationContext = new EvaluationContext(this, _segmentCacheConsumer, ruleBasedSegmentCacheConsumer);
3941
_fallbackTreatmentCalculator = fallbackTreatmentCalculator;
42+
_targetingEngine = new TargetingEngineImpl();
4043
}
4144

4245
@Override
@@ -102,100 +105,26 @@ private List<String> getFeatureFlagNamesByFlagSets(List<String> flagSets) {
102105

103106
/**
104107
* @param matchingKey MUST NOT be null
105-
* @param bucketingKey
108+
* @param bucketingKey may be null
106109
* @param parsedSplit MUST NOT be null
107-
* @param attributes MUST NOT be null
110+
* @param attributes may be null
108111
* @return
109112
* @throws ChangeNumberExceptionWrapper
110113
*/
111-
private TreatmentLabelAndChangeNumber getTreatment(String matchingKey, String bucketingKey, ParsedSplit parsedSplit, Map<String,
112-
Object> attributes) throws ChangeNumberExceptionWrapper {
114+
private TreatmentLabelAndChangeNumber getTreatment(String matchingKey, String bucketingKey, ParsedSplit parsedSplit,
115+
Map<String, Object> attributes) throws ChangeNumberExceptionWrapper {
113116
try {
114-
String config = getConfig(parsedSplit, parsedSplit.defaultTreatment());
115-
if (parsedSplit.killed()) {
116-
return new TreatmentLabelAndChangeNumber(
117-
parsedSplit.defaultTreatment(),
118-
Labels.KILLED,
119-
parsedSplit.changeNumber(),
120-
config,
121-
parsedSplit.impressionsDisabled());
122-
}
123-
124-
String bk = getBucketingKey(bucketingKey, matchingKey);
125-
126-
if (!parsedSplit.prerequisitesMatcher().match(matchingKey, bk, attributes, _evaluationContext)) {
127-
return new TreatmentLabelAndChangeNumber(
128-
parsedSplit.defaultTreatment(),
129-
Labels.PREREQUISITES_NOT_MET,
130-
parsedSplit.changeNumber(),
131-
config,
132-
parsedSplit.impressionsDisabled());
133-
}
134-
135-
/*
136-
* There are three parts to a single Feature flag: 1) Whitelists 2) Traffic Allocation
137-
* 3) Rollout. The flag inRollout is there to understand when we move into the Rollout
138-
* section. This is because we need to make sure that the Traffic Allocation
139-
* computation happens after the whitelist but before the rollout.
140-
*/
141-
boolean inRollout = false;
142-
143-
for (ParsedCondition parsedCondition : parsedSplit.parsedConditions()) {
144-
145-
if (checkRollout(inRollout, parsedCondition)) {
146-
147-
if (parsedSplit.trafficAllocation() < 100) {
148-
// if the traffic allocation is 100%, no need to do anything special.
149-
int bucket = Splitter.getBucket(bk, parsedSplit.trafficAllocationSeed(), parsedSplit.algo());
150-
151-
if (bucket > parsedSplit.trafficAllocation()) {
152-
// out of split
153-
config = getConfig(parsedSplit, parsedSplit.defaultTreatment());
154-
return new TreatmentLabelAndChangeNumber(parsedSplit.defaultTreatment(), Labels.NOT_IN_SPLIT,
155-
parsedSplit.changeNumber(), config, parsedSplit.impressionsDisabled());
156-
}
157-
158-
}
159-
inRollout = true;
160-
}
161-
162-
if (parsedCondition.matcher().match(matchingKey, bucketingKey, attributes, _evaluationContext)) {
163-
String treatment = Splitter.getTreatment(bk, parsedSplit.seed(), parsedCondition.partitions(), parsedSplit.algo());
164-
config = getConfig(parsedSplit, treatment);
165-
return new TreatmentLabelAndChangeNumber(
166-
treatment,
167-
parsedCondition.label(),
168-
parsedSplit.changeNumber(),
169-
config,
170-
parsedSplit.impressionsDisabled());
171-
}
172-
}
173-
174-
config = getConfig(parsedSplit, parsedSplit.defaultTreatment());
175-
176-
return new TreatmentLabelAndChangeNumber(
177-
parsedSplit.defaultTreatment(),
178-
Labels.DEFAULT_RULE,
179-
parsedSplit.changeNumber(),
180-
config,
181-
parsedSplit.impressionsDisabled());
182-
} catch (Exception e) {
183-
throw new ChangeNumberExceptionWrapper(e, parsedSplit.changeNumber());
117+
EvaluationResult r = _targetingEngine.evaluate(matchingKey, bucketingKey,
118+
parsedSplit.targetingRule(), attributes, _evaluationContext);
119+
String config = parsedSplit.configurations() != null
120+
? parsedSplit.configurations().get(r.treatment) : null;
121+
return new TreatmentLabelAndChangeNumber(r.treatment, r.label,
122+
parsedSplit.changeNumber(), config, parsedSplit.impressionsDisabled());
123+
} catch (VersionedExceptionWrapper e) {
124+
throw new ChangeNumberExceptionWrapper(e.wrappedException(), parsedSplit.changeNumber());
184125
}
185126
}
186127

187-
private boolean checkRollout(boolean inRollout, ParsedCondition parsedCondition) {
188-
return (!inRollout && parsedCondition.conditionType() == ConditionType.ROLLOUT);
189-
}
190-
191-
private String getBucketingKey(String bucketingKey, String matchingKey) {
192-
return (bucketingKey == null) ? matchingKey : bucketingKey;
193-
}
194-
195-
private String getConfig(ParsedSplit parsedSplit, String returnedTreatment) {
196-
return parsedSplit.configurations() != null ? parsedSplit.configurations().get(returnedTreatment) : null;
197-
}
198-
199128
private String getFallbackConfig(FallbackTreatment fallbackTreatment) {
200129
if (fallbackTreatment.getConfig() != null) {
201130
return fallbackTreatment.getConfig();

client/src/main/java/io/split/engine/experiments/ParsedCondition.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import io.split.client.dtos.ConditionType;
44
import io.split.client.dtos.Partition;
5-
import io.split.engine.matchers.CombiningMatcher;
5+
import io.split.rules.matchers.CombiningMatcher;
66

77
import java.util.List;
88

client/src/main/java/io/split/engine/experiments/ParsedRuleBasedSegment.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
package io.split.engine.experiments;
22

3-
import com.google.common.collect.ImmutableList;
43
import io.split.client.dtos.ExcludedSegments;
5-
import io.split.engine.matchers.AttributeMatcher;
6-
import io.split.engine.matchers.UserDefinedSegmentMatcher;
4+
import io.split.rules.matchers.AttributeMatcher;
5+
import io.split.rules.matchers.UserDefinedSegmentMatcher;
76

7+
import java.util.ArrayList;
8+
import java.util.Collections;
89
import java.util.List;
910
import java.util.Set;
1011
import java.util.stream.Collectors;
1112

1213
public class ParsedRuleBasedSegment {
1314

1415
private final String _ruleBasedSegment;
15-
private final ImmutableList<ParsedCondition> _parsedCondition;
16+
private final List<ParsedCondition> _parsedCondition;
1617
private final String _trafficTypeName;
1718
private final long _changeNumber;
1819
private final List<String> _excludedKeys;
@@ -45,7 +46,7 @@ public ParsedRuleBasedSegment(
4546
List<ExcludedSegments> excludedSegments
4647
) {
4748
_ruleBasedSegment = ruleBasedSegment;
48-
_parsedCondition = ImmutableList.copyOf(matcherAndSplits);
49+
_parsedCondition = Collections.unmodifiableList(new ArrayList<>(matcherAndSplits));
4950
_trafficTypeName = trafficTypeName;
5051
_changeNumber = changeNumber;
5152
_excludedKeys = excludedKeys;

0 commit comments

Comments
 (0)