Skip to content

Commit 6ce2581

Browse files
Audiences, Attributes and Events implementation (#438)
## Summary - Audiences Implementation - Audience Support added in OptimizelyConfigService - Modification of Experiments to support serialize functionality from Conditions - Attributes and Events added to OptimizelyConfig - Cleanup of parsing functions to reuse code - Additional test cases added - Add Deprecation for OptimizelyFeature.experimentsMap ## Test plan - FSC ## Issues - "OASIS-7813"
1 parent e99f26b commit 6ce2581

26 files changed

+1071
-105
lines changed

core-api/src/main/java/com/optimizely/ab/config/Experiment.java

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright 2016-2019, Optimizely and contributors
3+
* Copyright 2016-2019, 2021, Optimizely and contributors
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -18,8 +18,7 @@
1818

1919
import com.fasterxml.jackson.annotation.*;
2020
import com.optimizely.ab.annotations.VisibleForTesting;
21-
import com.optimizely.ab.config.audience.AudienceIdCondition;
22-
import com.optimizely.ab.config.audience.Condition;
21+
import com.optimizely.ab.config.audience.*;
2322

2423
import javax.annotation.Nonnull;
2524
import javax.annotation.Nullable;
@@ -43,6 +42,10 @@ public class Experiment implements IdKeyMapped {
4342
private final String layerId;
4443
private final String groupId;
4544

45+
private final String AND = "AND";
46+
private final String OR = "OR";
47+
private final String NOT = "NOT";
48+
4649
private final List<String> audienceIds;
4750
private final Condition<AudienceIdCondition> audienceConditions;
4851
private final List<Variation> variations;
@@ -173,6 +176,98 @@ public boolean isLaunched() {
173176
return status.equals(ExperimentStatus.LAUNCHED.toString());
174177
}
175178

179+
public String serializeConditions(Map<String, String> audiencesMap) {
180+
Condition condition = this.audienceConditions;
181+
return condition instanceof EmptyCondition ? "" : this.serialize(condition, audiencesMap);
182+
}
183+
184+
private String getNameFromAudienceId(String audienceId, Map<String, String> audiencesMap) {
185+
StringBuilder audienceName = new StringBuilder();
186+
if (audiencesMap != null && audiencesMap.get(audienceId) != null) {
187+
audienceName.append("\"" + audiencesMap.get(audienceId) + "\"");
188+
} else {
189+
audienceName.append("\"" + audienceId + "\"");
190+
}
191+
return audienceName.toString();
192+
}
193+
194+
private String getOperandOrAudienceId(Condition condition, Map<String, String> audiencesMap) {
195+
if (condition != null) {
196+
if (condition instanceof AudienceIdCondition) {
197+
return this.getNameFromAudienceId(condition.getOperandOrId(), audiencesMap);
198+
} else {
199+
return condition.getOperandOrId();
200+
}
201+
} else {
202+
return "";
203+
}
204+
}
205+
206+
public String serialize(Condition condition, Map<String, String> audiencesMap) {
207+
StringBuilder stringBuilder = new StringBuilder();
208+
List<Condition> conditions;
209+
210+
String operand = this.getOperandOrAudienceId(condition, audiencesMap);
211+
switch (operand){
212+
case (AND):
213+
conditions = ((AndCondition<?>) condition).getConditions();
214+
stringBuilder.append(this.getNameOrNextCondition(operand, conditions, audiencesMap));
215+
break;
216+
case (OR):
217+
conditions = ((OrCondition<?>) condition).getConditions();
218+
stringBuilder.append(this.getNameOrNextCondition(operand, conditions, audiencesMap));
219+
break;
220+
case (NOT):
221+
stringBuilder.append(operand + " ");
222+
Condition notCondition = ((NotCondition<?>) condition).getCondition();
223+
if (notCondition instanceof AudienceIdCondition) {
224+
stringBuilder.append(serialize(notCondition, audiencesMap));
225+
} else {
226+
stringBuilder.append("(" + serialize(notCondition, audiencesMap) + ")");
227+
}
228+
break;
229+
default:
230+
stringBuilder.append(operand);
231+
break;
232+
}
233+
234+
return stringBuilder.toString();
235+
}
236+
237+
public String getNameOrNextCondition(String operand, List<Condition> conditions, Map<String, String> audiencesMap) {
238+
StringBuilder stringBuilder = new StringBuilder();
239+
int index = 0;
240+
if (conditions.isEmpty()) {
241+
return "";
242+
} else if (conditions.size() == 1) {
243+
return serialize(conditions.get(0), audiencesMap);
244+
} else {
245+
for (Condition con : conditions) {
246+
index++;
247+
if (index + 1 <= conditions.size()) {
248+
if (con instanceof AudienceIdCondition) {
249+
String audienceName = this.getNameFromAudienceId(((AudienceIdCondition<?>) con).getAudienceId(),
250+
audiencesMap);
251+
stringBuilder.append( audienceName + " ");
252+
} else {
253+
stringBuilder.append("(" + serialize(con, audiencesMap) + ") ");
254+
}
255+
stringBuilder.append(operand);
256+
stringBuilder.append(" ");
257+
} else {
258+
if (con instanceof AudienceIdCondition) {
259+
String audienceName = this.getNameFromAudienceId(((AudienceIdCondition<?>) con).getAudienceId(),
260+
audiencesMap);
261+
stringBuilder.append(audienceName);
262+
} else {
263+
stringBuilder.append("(" + serialize(con, audiencesMap) + ")");
264+
}
265+
}
266+
}
267+
}
268+
return stringBuilder.toString();
269+
}
270+
176271
@Override
177272
public String toString() {
178273
return "Experiment{" +

core-api/src/main/java/com/optimizely/ab/config/audience/AndCondition.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@
2323
import javax.annotation.concurrent.Immutable;
2424
import java.util.List;
2525
import java.util.Map;
26+
import java.util.StringJoiner;
2627

2728
/**
2829
* Represents an 'And' conditions condition operation.
2930
*/
3031
public class AndCondition<T> implements Condition<T> {
3132

3233
private final List<Condition> conditions;
34+
private static final String OPERAND = "AND";
3335

3436
public AndCondition(@Nonnull List<Condition> conditions) {
3537
this.conditions = conditions;
@@ -67,6 +69,21 @@ public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
6769
return true; // otherwise, return true
6870
}
6971

72+
@Override
73+
public String getOperandOrId() {
74+
return OPERAND;
75+
}
76+
77+
@Override
78+
public String toJson() {
79+
StringJoiner s = new StringJoiner(", ", "[", "]");
80+
s.add("\"and\"");
81+
for (int i = 0; i < conditions.size(); i++) {
82+
s.add(conditions.get(i).toJson());
83+
}
84+
return s.toString();
85+
}
86+
7087
@Override
7188
public String toString() {
7289
StringBuilder s = new StringBuilder();

core-api/src/main/java/com/optimizely/ab/config/audience/AudienceIdCondition.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ public String getAudienceId() {
6464
return audienceId;
6565
}
6666

67+
@Override
68+
public String getOperandOrId() {
69+
return audienceId;
70+
}
71+
6772
@Nullable
6873
@Override
6974
public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
@@ -101,4 +106,7 @@ public int hashCode() {
101106
public String toString() {
102107
return audienceId;
103108
}
109+
110+
@Override
111+
public String toJson() { return null; }
104112
}

core-api/src/main/java/com/optimizely/ab/config/audience/Condition.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,8 @@ public interface Condition<T> {
2828

2929
@Nullable
3030
Boolean evaluate(ProjectConfig config, Map<String, ?> attributes);
31+
32+
String toJson();
33+
34+
String getOperandOrId();
3135
}

core-api/src/main/java/com/optimizely/ab/config/audience/EmptyCondition.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,11 @@ public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
2727
return true;
2828
}
2929

30+
@Override
31+
public String toJson() { return null; }
32+
33+
@Override
34+
public String getOperandOrId() {
35+
return null;
36+
}
3037
}

core-api/src/main/java/com/optimizely/ab/config/audience/NotCondition.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import javax.annotation.Nonnull;
2424

2525
import java.util.Map;
26+
import java.util.StringJoiner;
2627

2728
/**
2829
* Represents a 'Not' conditions condition operation.
@@ -31,6 +32,7 @@
3132
public class NotCondition<T> implements Condition<T> {
3233

3334
private final Condition condition;
35+
private static final String OPERAND = "NOT";
3436

3537
public NotCondition(@Nonnull Condition condition) {
3638
this.condition = condition;
@@ -47,6 +49,19 @@ public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
4749
return (conditionEval == null ? null : !conditionEval);
4850
}
4951

52+
@Override
53+
public String getOperandOrId() {
54+
return OPERAND;
55+
}
56+
57+
@Override
58+
public String toJson() {
59+
StringJoiner s = new StringJoiner(", ","[","]");
60+
s.add("\"not\"");
61+
s.add(condition.toJson());
62+
return s.toString();
63+
}
64+
5065
@Override
5166
public String toString() {
5267
StringBuilder s = new StringBuilder();

core-api/src/main/java/com/optimizely/ab/config/audience/NullCondition.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,12 @@ public class NullCondition<T> implements Condition<T> {
2626
public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
2727
return null;
2828
}
29+
30+
@Override
31+
public String toJson() { return null; }
32+
33+
@Override
34+
public String getOperandOrId() {
35+
return null;
36+
}
2937
}

core-api/src/main/java/com/optimizely/ab/config/audience/OrCondition.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@
2323
import javax.annotation.concurrent.Immutable;
2424
import java.util.List;
2525
import java.util.Map;
26+
import java.util.StringJoiner;
2627

2728
/**
2829
* Represents an 'Or' conditions condition operation.
2930
*/
3031
@Immutable
3132
public class OrCondition<T> implements Condition<T> {
3233
private final List<Condition> conditions;
34+
private static final String OPERAND = "OR";
3335

3436
public OrCondition(@Nonnull List<Condition> conditions) {
3537
this.conditions = conditions;
@@ -65,6 +67,21 @@ public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
6567
return false;
6668
}
6769

70+
@Override
71+
public String getOperandOrId() {
72+
return OPERAND;
73+
}
74+
75+
@Override
76+
public String toJson() {
77+
StringJoiner s = new StringJoiner(", ", "[", "]");
78+
s.add("\"or\"");
79+
for (int i = 0; i < conditions.size(); i++) {
80+
s.add(conditions.get(i).toJson());
81+
}
82+
return s.toString();
83+
}
84+
6885
@Override
6986
public String toString() {
7087
StringBuilder s = new StringBuilder();

core-api/src/main/java/com/optimizely/ab/config/audience/UserAttribute.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,19 +119,39 @@ public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
119119
}
120120

121121
@Override
122-
public String toString() {
122+
public String getOperandOrId() {
123+
return null;
124+
}
125+
126+
public String getValueStr() {
123127
final String valueStr;
124128
if (value == null) {
125129
valueStr = "null";
126130
} else if (value instanceof String) {
127-
valueStr = String.format("'%s'", value);
131+
valueStr = String.format("%s", value);
128132
} else {
129133
valueStr = value.toString();
130134
}
135+
return valueStr;
136+
}
137+
138+
@Override
139+
public String toJson() {
140+
StringBuilder attributes = new StringBuilder();
141+
if (name != null) attributes.append("{\"name\":\"" + name + "\"");
142+
if (type != null) attributes.append(", \"type\":\"" + type + "\"");
143+
if (match != null) attributes.append(", \"match\":\"" + match + "\"");
144+
attributes.append(", \"value\":" + ((value instanceof String) ? ("\"" + getValueStr() + "\"") : getValueStr()) + "}");
145+
146+
return attributes.toString();
147+
}
148+
149+
@Override
150+
public String toString() {
131151
return "{name='" + name + "\'" +
132152
", type='" + type + "\'" +
133153
", match='" + match + "\'" +
134-
", value=" + valueStr +
154+
", value=" + ((value instanceof String) ? ("'" + getValueStr() + "'") : getValueStr()) +
135155
"}";
136156
}
137157

core-api/src/main/java/com/optimizely/ab/config/parser/ConditionJacksonDeserializer.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright 2018-2019, Optimizely and contributors
3+
* Copyright 2018-2019, 2021, Optimizely and contributors
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -121,9 +121,6 @@ protected static <T> Condition parseConditions(Class<T> clazz, ObjectMapper obje
121121
case "and":
122122
condition = new AndCondition(conditions);
123123
break;
124-
case "or":
125-
condition = new OrCondition(conditions);
126-
break;
127124
case "not":
128125
condition = new NotCondition(conditions.isEmpty() ? new NullCondition() : conditions.get(0));
129126
break;

0 commit comments

Comments
 (0)