Skip to content

Commit ed2451d

Browse files
gangb-techjianggang
andauthored
feat: support segment (#5)
* feat: support segment * feat: release 1.1.0 package Co-authored-by: jianggang <[email protected]>
1 parent 5384518 commit ed2451d

File tree

11 files changed

+163
-54
lines changed

11 files changed

+163
-54
lines changed

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.featureprobe</groupId>
88
<artifactId>server-sdk-java</artifactId>
9-
<version>1.0.3</version>
9+
<version>1.1.0</version>
1010
<name>server-sdk-java</name>
1111
<url>https://github.com/FeatureProbe/server-sdk-java</url>
1212
<description>FeatureProbe Server Side SDK for Java</description>
@@ -317,4 +317,4 @@
317317
</plugin>
318318
</plugins>
319319
</reporting>
320-
</project>
320+
</project>

src/main/java/com/featureprobe/sdk/server/FeatureProbe.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
import com.fasterxml.jackson.core.JsonProcessingException;
44
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import com.featureprobe.sdk.server.model.Segment;
56
import com.featureprobe.sdk.server.model.Toggle;
67
import com.google.common.annotations.VisibleForTesting;
78
import org.apache.commons.lang3.StringUtils;
89
import org.slf4j.Logger;
910

11+
import java.util.Map;
1012
import java.util.Objects;
1113
import java.util.Optional;
1214

@@ -89,8 +91,9 @@ public void flush() {
8991
private <T> T jsonEvaluate(String toggleKey, FPUser user, T defaultValue, Class<T> clazz) {
9092
try {
9193
Toggle toggle = dataRepository.getToggle(toggleKey);
94+
Map<String, Segment> segments = dataRepository.getAllSegment();
9295
if (Objects.nonNull(toggle)) {
93-
EvaluationResult evalResult = toggle.eval(user, defaultValue);
96+
EvaluationResult evalResult = toggle.eval(user, segments, defaultValue);
9497
String value = mapper.writeValueAsString(evalResult.getValue());
9598
AccessEvent accessEvent = new AccessEvent(System.currentTimeMillis(), user,
9699
toggleKey, String.valueOf(evalResult.getValue()), evalResult.getVersion(),
@@ -109,8 +112,9 @@ private <T> T jsonEvaluate(String toggleKey, FPUser user, T defaultValue, Class<
109112
private <T> T genericEvaluate(String toggleKey, FPUser user, T defaultValue, Class<T> clazz) {
110113
try {
111114
Toggle toggle = dataRepository.getToggle(toggleKey);
115+
Map<String, Segment> segments = dataRepository.getAllSegment();
112116
if (Objects.nonNull(toggle)) {
113-
EvaluationResult evalResult = toggle.eval(user, defaultValue);
117+
EvaluationResult evalResult = toggle.eval(user, segments, defaultValue);
114118
AccessEvent accessEvent = new AccessEvent(System.currentTimeMillis(), user,
115119
toggleKey, String.valueOf(evalResult.getValue()), evalResult.getVersion(),
116120
evalResult.getVariationIndex().get());
@@ -161,8 +165,9 @@ private <T> FPDetail<T> getEvaluateDetail(String toggleKey, FPUser user, T defau
161165
FPDetail<T> detail = new FPDetail<>();
162166
if (this.dataRepository.initialized()) {
163167
Toggle toggle = dataRepository.getToggle(toggleKey);
168+
Map<String, Segment> segments = dataRepository.getAllSegment();
164169
if (Objects.nonNull(toggle)) {
165-
EvaluationResult evalResult = toggle.eval(user, defaultValue);
170+
EvaluationResult evalResult = toggle.eval(user, segments, defaultValue);
166171
if (isJson) {
167172
String res = mapper.writeValueAsString(evalResult.getValue());
168173
detail.setValue(mapper.readValue(res, clazz));
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.featureprobe.sdk.server;
2+
3+
import com.featureprobe.sdk.server.model.Segment;
4+
5+
import java.util.List;
6+
import java.util.Map;
7+
8+
@FunctionalInterface
9+
public interface SegmentMatcher {
10+
11+
boolean match(FPUser user, Map<String, Segment> segments, List<String> objects);
12+
13+
}

src/main/java/com/featureprobe/sdk/server/Matcher.java renamed to src/main/java/com/featureprobe/sdk/server/StringMatcher.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import java.util.List;
44

55
@FunctionalInterface
6-
public interface Matcher {
6+
public interface StringMatcher {
77

88
boolean match(String target, List<String> objects);
99

src/main/java/com/featureprobe/sdk/server/model/Condition.java

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package com.featureprobe.sdk.server.model;
22

33
import com.featureprobe.sdk.server.FPUser;
4-
import com.featureprobe.sdk.server.Matcher;
4+
import com.featureprobe.sdk.server.StringMatcher;
5+
import com.featureprobe.sdk.server.SegmentMatcher;
56
import org.apache.commons.lang3.StringUtils;
67

78
import java.util.HashMap;
@@ -20,42 +21,51 @@ public final class Condition {
2021

2122
private List<String> objects;
2223

23-
private static final Map<PredicateType, Matcher> matchers = new HashMap<>(PredicateType.values().length);
24+
private static final Map<PredicateType, StringMatcher> stringMatchers =
25+
new HashMap<>(PredicateType.values().length);
2426

27+
private static final Map<PredicateType, SegmentMatcher> segmentMatchers =
28+
new HashMap<>(PredicateType.values().length);
2529

2630
static {
27-
matchers.put(PredicateType.IS_ONE_OF, (target, objects) ->
31+
stringMatchers.put(PredicateType.IS_ONE_OF, (target, objects) ->
2832
objects.contains(target));
29-
matchers.put(PredicateType.ENDS_WITH, (target, objects) ->
33+
stringMatchers.put(PredicateType.ENDS_WITH, (target, objects) ->
3034
objects.stream().anyMatch(target::endsWith));
31-
matchers.put(PredicateType.STARTS_WITH, (target, objects) ->
35+
stringMatchers.put(PredicateType.STARTS_WITH, (target, objects) ->
3236
objects.stream().anyMatch(target::startsWith));
33-
matchers.put(PredicateType.CONTAINS, (target, objects) ->
37+
stringMatchers.put(PredicateType.CONTAINS, (target, objects) ->
3438
objects.stream().anyMatch(target::contains));
35-
matchers.put(PredicateType.MATCHES_REGEX, (target, objects) ->
39+
stringMatchers.put(PredicateType.MATCHES_REGEX, (target, objects) ->
3640
objects.stream().anyMatch(s -> Pattern.compile(s).matcher(target).find()));
37-
matchers.put(PredicateType.IS_NOT_ANY_OF, (target, objects) ->
41+
stringMatchers.put(PredicateType.IS_NOT_ANY_OF, (target, objects) ->
3842
!objects.contains(target));
39-
matchers.put(PredicateType.DOES_NOT_END_WITH, (target, objects) ->
43+
stringMatchers.put(PredicateType.DOES_NOT_END_WITH, (target, objects) ->
4044
objects.stream().noneMatch(target::endsWith));
41-
matchers.put(PredicateType.DOES_NOT_START_WITH, (target, objects) ->
45+
stringMatchers.put(PredicateType.DOES_NOT_START_WITH, (target, objects) ->
4246
objects.stream().noneMatch(target::startsWith));
43-
matchers.put(PredicateType.DOES_NOT_CONTAIN, (target, objects) ->
47+
stringMatchers.put(PredicateType.DOES_NOT_CONTAIN, (target, objects) ->
4448
objects.stream().noneMatch(target::contains));
45-
matchers.put(PredicateType.DOES_NOT_MATCH_REGEX, (target, objects) ->
49+
stringMatchers.put(PredicateType.DOES_NOT_MATCH_REGEX, (target, objects) ->
4650
objects.stream().noneMatch(s -> Pattern.compile(s).matcher(target).find()));
51+
52+
segmentMatchers.put(PredicateType.IS_IN, (user, segments, objects) ->
53+
objects.stream().anyMatch(s -> segments.get(s).contains(user, segments)));
54+
segmentMatchers.put(PredicateType.IS_NOT_IN, (user, segments, objects) ->
55+
objects.stream().noneMatch(s -> segments.get(s).contains(user, segments)));
56+
4757
}
4858

49-
public boolean matchObjects(FPUser user) {
50-
String subjectValue = user.getAttrs().get(subject);
51-
if (StringUtils.isBlank(subjectValue)) {
52-
return false;
53-
}
59+
public boolean matchObjects(FPUser user, Map<String, Segment> segments) {
5460
switch (type) {
5561
case STRING:
62+
String subjectValue = user.getAttrs().get(subject);
63+
if (StringUtils.isBlank(subjectValue)) {
64+
return false;
65+
}
5666
return matchStringCondition(subjectValue);
5767
case SEGMENT:
58-
// TODO
68+
return matchSegmentCondition(user, segments);
5969
case DATE:
6070
// TODO
6171
default:
@@ -64,9 +74,17 @@ public boolean matchObjects(FPUser user) {
6474
}
6575

6676
private boolean matchStringCondition(String subjectValue) {
67-
Matcher matcher = matchers.get(this.predicate);
68-
if (Objects.nonNull(matcher)) {
69-
return matcher.match(subjectValue, this.objects);
77+
StringMatcher stringMatcher = stringMatchers.get(this.predicate);
78+
if (Objects.nonNull(stringMatcher)) {
79+
return stringMatcher.match(subjectValue, this.objects);
80+
}
81+
return false;
82+
}
83+
84+
private boolean matchSegmentCondition(FPUser user, Map<String, Segment> segments) {
85+
SegmentMatcher segmentMatcher = segmentMatchers.get(this.predicate);
86+
if (Objects.nonNull(segmentMatcher)) {
87+
return segmentMatcher.match(user, segments, this.objects);
7088
}
7189
return false;
7290
}

src/main/java/com/featureprobe/sdk/server/model/PredicateType.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@ public enum PredicateType {
1313
STARTS_WITH("starts with"),
1414
CONTAINS("contains"),
1515
MATCHES_REGEX("matches regex"),
16-
1716
IS_NOT_ANY_OF("is not any of"),
1817
DOES_NOT_END_WITH("does not end with"),
1918
DOES_NOT_START_WITH("does not start with"),
2019
DOES_NOT_CONTAIN("does not contain"),
21-
DOES_NOT_MATCH_REGEX("does not match regex");
20+
DOES_NOT_MATCH_REGEX("does not match regex"),
21+
22+
IS_IN("is in"),
23+
IS_NOT_IN("is not in");
24+
2225

2326
private final String value;
2427

src/main/java/com/featureprobe/sdk/server/model/Rule.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import org.apache.commons.lang3.StringUtils;
66

77
import java.util.List;
8+
import java.util.Map;
89
import java.util.Optional;
910

1011
public final class Rule {
@@ -13,14 +14,14 @@ public final class Rule {
1314

1415
private List<Condition> conditions;
1516

16-
public HitResult hit(FPUser user, String toggleKey) {
17+
public HitResult hit(FPUser user, Map<String, Segment> segments, String toggleKey) {
1718
for (Condition condition : conditions) {
18-
if (!user.containAttr(condition.getSubject())) {
19+
if (condition.getType() != ConditionType.SEGMENT && !user.containAttr(condition.getSubject())) {
1920
return new HitResult(false,
2021
Optional.of(String.format("Warning: User with key '%s' does not have attribute name '%s'",
2122
user.getKey(), condition.getSubject())));
2223
}
23-
if (!condition.matchObjects(user)) {
24+
if (!condition.matchObjects(user, segments)) {
2425
return new HitResult(false);
2526
}
2627
}

src/main/java/com/featureprobe/sdk/server/model/Segment.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package com.featureprobe.sdk.server.model;
22

3+
import com.featureprobe.sdk.server.FPUser;
4+
import com.featureprobe.sdk.server.HitResult;
5+
36
import java.util.List;
7+
import java.util.Map;
48

59
public class Segment {
610

@@ -10,6 +14,16 @@ public class Segment {
1014

1115
private List<SegmentRule> rules;
1216

17+
public boolean contains(FPUser user, Map<String, Segment> segments) {
18+
for (SegmentRule rule : rules) {
19+
HitResult hitResult = rule.hit(user, segments);
20+
if (hitResult.isHit()) {
21+
return true;
22+
}
23+
}
24+
return false;
25+
}
26+
1327
public String getUniqueId() {
1428
return uniqueId;
1529
}

src/main/java/com/featureprobe/sdk/server/model/SegmentRule.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,30 @@
11
package com.featureprobe.sdk.server.model;
22

3+
import com.featureprobe.sdk.server.FPUser;
4+
import com.featureprobe.sdk.server.HitResult;
5+
36
import java.util.List;
7+
import java.util.Map;
8+
import java.util.Optional;
49

510
public class SegmentRule {
611

712
private List<Condition> conditions;
813

14+
public HitResult hit(FPUser user, Map<String, Segment> segments) {
15+
for (Condition condition : conditions) {
16+
if (condition.getType() != ConditionType.SEGMENT && !user.containAttr(condition.getSubject())) {
17+
return new HitResult(false,
18+
Optional.of(String.format("Warning: User with key '%s' does not have attribute name '%s'",
19+
user.getKey(), condition.getSubject())));
20+
}
21+
if (!condition.matchObjects(user, segments)) {
22+
return new HitResult(false);
23+
}
24+
}
25+
return new HitResult(true);
26+
}
27+
928
public List<Condition> getConditions() {
1029
return conditions;
1130
}

src/main/java/com/featureprobe/sdk/server/model/Toggle.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.featureprobe.sdk.server.HitResult;
66

77
import java.util.List;
8+
import java.util.Map;
89
import java.util.Optional;
910

1011
public final class Toggle {
@@ -25,7 +26,7 @@ public final class Toggle {
2526

2627
private Boolean forClient;
2728

28-
public EvaluationResult eval(FPUser user, Object defaultValue) {
29+
public EvaluationResult eval(FPUser user, Map<String, Segment> segments, Object defaultValue) {
2930
String warning = "";
3031

3132
if (!enabled) {
@@ -35,7 +36,7 @@ public EvaluationResult eval(FPUser user, Object defaultValue) {
3536
if (rules != null && rules.size() > 0) {
3637
for (int i = 0; i < rules.size(); i++) {
3738
Rule rule = rules.get(i);
38-
HitResult hitResult = rule.hit(user, this.key);
39+
HitResult hitResult = rule.hit(user, segments, this.key);
3940
if (hitResult.isHit()) {
4041
return hitValue(hitResult, defaultValue, Optional.of(i));
4142
}

0 commit comments

Comments
 (0)