Skip to content

Commit 2a7a3dd

Browse files
committed
RBS matcher (#743)
1 parent e6ad338 commit 2a7a3dd

File tree

6 files changed

+300
-20
lines changed

6 files changed

+300
-20
lines changed

src/main/java/io/split/android/client/localhost/LocalhostRuleBasedSegmentsStorage.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77

88
import io.split.android.client.dtos.RuleBasedSegment;
99
import io.split.android.client.storage.rbs.RuleBasedSegmentStorage;
10+
import io.split.android.engine.experiments.ParsedRuleBasedSegment;
1011

1112
public class LocalhostRuleBasedSegmentsStorage implements RuleBasedSegmentStorage {
1213

1314
@Nullable
1415
@Override
15-
public RuleBasedSegment get(String segmentName) {
16+
public ParsedRuleBasedSegment get(String segmentName, String matchingKey) {
1617
return null;
1718
}
1819

src/main/java/io/split/android/client/storage/rbs/RuleBasedSegmentStorage.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@
77

88
import io.split.android.client.dtos.RuleBasedSegment;
99
import io.split.android.client.storage.RolloutDefinitionsCache;
10+
import io.split.android.engine.experiments.ParsedRuleBasedSegment;
1011

1112
public interface RuleBasedSegmentStorage extends RolloutDefinitionsCache {
1213

1314
@Nullable
14-
RuleBasedSegment get(String segmentName);
15+
ParsedRuleBasedSegment get(String segmentName, String matchingKey);
1516

1617
boolean update(@NonNull Set<RuleBasedSegment> toAdd, @NonNull Set<RuleBasedSegment> toRemove, long changeNumber);
1718

src/main/java/io/split/android/client/storage/rbs/RuleBasedSegmentStorageImpl.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,30 @@
1111
import java.util.concurrent.ConcurrentHashMap;
1212

1313
import io.split.android.client.dtos.RuleBasedSegment;
14+
import io.split.android.engine.experiments.ParsedRuleBasedSegment;
1415

1516
public class RuleBasedSegmentStorageImpl implements RuleBasedSegmentStorage {
1617

1718
private final ConcurrentHashMap<String, RuleBasedSegment> mInMemorySegments;
19+
private final RuleBasedSegmentParser mParser;
1820
private final PersistentRuleBasedSegmentStorage mPersistentStorage;
1921
private volatile long mChangeNumber;
2022

21-
public RuleBasedSegmentStorageImpl(@NonNull PersistentRuleBasedSegmentStorage persistentStorage) {
23+
public RuleBasedSegmentStorageImpl(@NonNull PersistentRuleBasedSegmentStorage persistentStorage, @NonNull RuleBasedSegmentParser parser) {
2224
mInMemorySegments = new ConcurrentHashMap<>();
25+
mParser = checkNotNull(parser);
2326
mPersistentStorage = checkNotNull(persistentStorage);
2427
mChangeNumber = -1;
2528
}
2629

27-
@Nullable
2830
@Override
29-
public RuleBasedSegment get(String segmentName) {
30-
return mInMemorySegments.get(segmentName);
31+
public @Nullable ParsedRuleBasedSegment get(String segmentName, String matchingKey) {
32+
RuleBasedSegment ruleBasedSegment = mInMemorySegments.get(segmentName);
33+
if (ruleBasedSegment == null) {
34+
return null;
35+
}
36+
37+
return mParser.parse(ruleBasedSegment, matchingKey);
3138
}
3239

3340
@Override
@@ -92,4 +99,11 @@ public void clear() {
9299
mChangeNumber = -1;
93100
mPersistentStorage.clear();
94101
}
102+
103+
// stub class
104+
static final class RuleBasedSegmentParser {
105+
ParsedRuleBasedSegment parse(RuleBasedSegment segment, String matchingKey) {
106+
return null;
107+
}
108+
}
95109
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package io.split.android.engine.matchers;
2+
3+
import static io.split.android.client.utils.Utils.checkNotNull;
4+
5+
import java.util.Map;
6+
7+
import io.split.android.client.Evaluator;
8+
import io.split.android.client.storage.mysegments.MySegmentsStorage;
9+
import io.split.android.client.storage.rbs.RuleBasedSegmentStorage;
10+
import io.split.android.engine.experiments.ParsedCondition;
11+
import io.split.android.engine.experiments.ParsedRuleBasedSegment;
12+
13+
public class InRuleBasedSegmentMatcher implements Matcher {
14+
15+
private final RuleBasedSegmentStorage mRuleBasedSegmentStorage;
16+
private final MySegmentsStorage mMySegmentsStorage;
17+
private final String mSegmentName;
18+
19+
public InRuleBasedSegmentMatcher(RuleBasedSegmentStorage ruleBasedSegmentStorage, MySegmentsStorage mySegmentsStorage, String segmentName) {
20+
mRuleBasedSegmentStorage = checkNotNull(ruleBasedSegmentStorage);
21+
mMySegmentsStorage = checkNotNull(mySegmentsStorage);
22+
mSegmentName = checkNotNull(segmentName);
23+
}
24+
25+
@Override
26+
public boolean match(Object matchValue, String bucketingKey, Map<String, Object> attributes, Evaluator evaluator) {
27+
if (!(matchValue instanceof String)) {
28+
return false;
29+
}
30+
31+
final String matchingKey = (String) matchValue;
32+
final ParsedRuleBasedSegment parsedRuleBasedSegment = mRuleBasedSegmentStorage.get(mSegmentName, matchingKey);
33+
34+
if (parsedRuleBasedSegment == null) {
35+
return false;
36+
}
37+
38+
if (parsedRuleBasedSegment.getExcludedKeys().contains(matchingKey)) {
39+
return false;
40+
}
41+
42+
for (String segmentName : parsedRuleBasedSegment.getExcludedSegments()) {
43+
if (mMySegmentsStorage.getAll().contains(segmentName)) {
44+
return false;
45+
}
46+
}
47+
48+
for (ParsedCondition condition : parsedRuleBasedSegment.getParsedConditions()) {
49+
if (condition.matcher().match(matchingKey, bucketingKey, attributes, evaluator)) {
50+
return true;
51+
}
52+
}
53+
54+
return false;
55+
}
56+
}

src/test/java/io/split/android/client/storage/rbs/RuleBasedSegmentStorageImplTest.java

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@
66
import static org.junit.Assert.assertNotNull;
77
import static org.junit.Assert.assertNull;
88
import static org.junit.Assert.assertTrue;
9+
import static org.mockito.ArgumentMatchers.any;
910
import static org.mockito.Mockito.mock;
1011
import static org.mockito.Mockito.verify;
1112
import static org.mockito.Mockito.when;
1213

1314
import org.junit.Before;
1415
import org.junit.Test;
16+
import org.mockito.invocation.InvocationOnMock;
17+
import org.mockito.stubbing.Answer;
1518

1619
import java.util.ArrayList;
1720
import java.util.Collections;
@@ -22,23 +25,35 @@
2225
import io.split.android.client.dtos.Excluded;
2326
import io.split.android.client.dtos.RuleBasedSegment;
2427
import io.split.android.client.dtos.Status;
28+
import io.split.android.engine.experiments.ParsedRuleBasedSegment;
2529

2630
public class RuleBasedSegmentStorageImplTest {
2731

2832
private RuleBasedSegmentStorageImpl storage;
2933
private PersistentRuleBasedSegmentStorage mPersistentStorage;
34+
private RuleBasedSegmentStorageImpl.RuleBasedSegmentParser mParser;
3035

3136
@Before
3237
public void setUp() {
3338
mPersistentStorage = mock(PersistentRuleBasedSegmentStorage.class);
34-
storage = new RuleBasedSegmentStorageImpl(mPersistentStorage);
39+
mParser = mock(RuleBasedSegmentStorageImpl.RuleBasedSegmentParser.class);
40+
when(mParser.parse(any(), any())).thenAnswer(new Answer<ParsedRuleBasedSegment>() {
41+
@Override
42+
public ParsedRuleBasedSegment answer(InvocationOnMock invocation) throws Throwable {
43+
ParsedRuleBasedSegment mockResult = mock(ParsedRuleBasedSegment.class);
44+
when(mockResult.getName()).thenReturn(((RuleBasedSegment) invocation.getArguments()[0]).getName());
45+
46+
return mockResult;
47+
}
48+
});
49+
storage = new RuleBasedSegmentStorageImpl(mPersistentStorage, mParser);
3550
}
3651

3752
@Test
3853
public void get() {
3954
RuleBasedSegment segment = createRuleBasedSegment("segment1");
4055
storage.update(Set.of(segment), null, 1);
41-
assertNotNull(storage.get("segment1"));
56+
assertNotNull(storage.get("segment1", "matchingKey"));
4257
}
4358

4459
@Test
@@ -51,15 +66,15 @@ public void sequentialUpdate() {
5166
toAdd.add(segment2);
5267

5368
storage.update(toAdd, null, 2);
54-
assertNotNull(storage.get("segment1"));
55-
assertNotNull(storage.get("segment2"));
69+
assertNotNull(storage.get("segment1", "matchingKey"));
70+
assertNotNull(storage.get("segment2", "matchingKey"));
5671
assertEquals(2, storage.getChangeNumber());
5772

5873
Set<RuleBasedSegment> toRemove = new HashSet<>();
5974
toRemove.add(segment1);
6075
storage.update(null, toRemove, 3);
61-
assertNull(storage.get("segment1"));
62-
assertNotNull(storage.get("segment2"));
76+
assertNull(storage.get("segment1", "matchingKey"));
77+
assertNotNull(storage.get("segment2", "matchingKey"));
6378
assertEquals(3, storage.getChangeNumber());
6479
}
6580

@@ -93,7 +108,7 @@ public void clearRemovesAllSegments() {
93108
RuleBasedSegment segment = createRuleBasedSegment("segment1");
94109
storage.update(Set.of(segment), null, 1);
95110
storage.clear();
96-
assertNull(storage.get("segment1"));
111+
assertNull(storage.get("segment1", "matchingKey"));
97112
}
98113

99114
@Test
@@ -126,15 +141,15 @@ public void segmentRemoval() {
126141
toAdd.add(segment2);
127142
storage.update(toAdd, null, 1);
128143

129-
assertNotNull(storage.get("segment1"));
130-
assertNotNull(storage.get("segment2"));
144+
assertNotNull(storage.get("segment1", "matchingKey"));
145+
assertNotNull(storage.get("segment2", "matchingKey"));
131146

132147
Set<RuleBasedSegment> toRemove = new HashSet<>();
133148
toRemove.add(createRuleBasedSegment("segment1"));
134149
storage.update(null, toRemove, 2);
135150

136-
assertNull(storage.get("segment1"));
137-
assertNotNull(storage.get("segment2"));
151+
assertNull(storage.get("segment1", "matchingKey"));
152+
assertNotNull(storage.get("segment2", "matchingKey"));
138153
}
139154

140155
@Test
@@ -143,7 +158,7 @@ public void segmentRemovalOfSameSegment() {
143158
Set<RuleBasedSegment> segments = Collections.singleton(segment1);
144159

145160
storage.update(segments, segments, 1);
146-
assertNull(storage.get("segment1"));
161+
assertNull(storage.get("segment1", "matchingKey"));
147162
assertEquals(1, storage.getChangeNumber());
148163
}
149164

@@ -173,12 +188,12 @@ public void loadLocalPopulatesValues() {
173188
when(mPersistentStorage.getSnapshot()).thenReturn(snapshot);
174189

175190
long initialCn = storage.getChangeNumber();
176-
RuleBasedSegment initialSegment1 = storage.get("segment1");
191+
ParsedRuleBasedSegment initialSegment1 = storage.get("segment1", "matchingKey");
177192

178193
storage.loadLocal();
179194

180195
long finalCn = storage.getChangeNumber();
181-
RuleBasedSegment finalSegment1 = storage.get("segment1");
196+
ParsedRuleBasedSegment finalSegment1 = storage.get("segment1", "matchingKey");
182197

183198
assertEquals(-1, initialCn);
184199
assertEquals(1, finalCn);

0 commit comments

Comments
 (0)