Skip to content

Commit 59c871d

Browse files
add: enhance getDecision method in DefaultCmabService with caching logic and attribute filtering
1 parent 1269180 commit 59c871d

1 file changed

Lines changed: 49 additions & 15 deletions

File tree

core-api/src/main/java/com/optimizely/ab/cmab/service/DefaultCmabService.java

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

33
import java.security.MessageDigest;
44
import java.security.NoSuchAlgorithmException;
5+
import java.util.Collections;
56
import java.util.HashMap;
67
import java.util.List;
78
import java.util.Map;
@@ -18,19 +19,52 @@
1819
import com.optimizely.ab.optimizelydecision.OptimizelyDecideOption;
1920

2021
public class DefaultCmabService implements CmabService {
22+
2123
private final DefaultLRUCache<CmabCacheValue> cmabCache;
2224
private final CmabClient cmabClient;
2325
private final Logger logger;
2426

2527
public DefaultCmabService(CmabServiceOptions options) {
2628
this.cmabCache = options.getCmabCache();
2729
this.cmabClient = options.getCmabClient();
28-
this.logger = options.getLogger();
30+
this.logger = options.getLogger();
2931
}
30-
32+
3133
@Override
3234
public CmabDecision getDecision(ProjectConfig projectConfig, OptimizelyUserContext userContext, String ruleId, List<OptimizelyDecideOption> options) {
33-
return null;
35+
options = options == null ? Collections.emptyList() : options;
36+
String userId = userContext.getUserId();
37+
Map<String, Object> filteredAttributes = filterAttributes(projectConfig, userContext, ruleId);
38+
39+
if (options.contains(OptimizelyDecideOption.IGNORE_CMAB_CACHE)) {
40+
return fetchDecision(ruleId, userId, filteredAttributes);
41+
}
42+
43+
if (options.contains(OptimizelyDecideOption.RESET_CMAB_CACHE)) {
44+
cmabCache.reset();
45+
}
46+
47+
String cacheKey = getCacheKey(userContext.getUserId(), ruleId);
48+
if (options.contains(OptimizelyDecideOption.INVALIDATE_USER_CMAB_CACHE)) {
49+
cmabCache.remove(cacheKey);
50+
}
51+
52+
CmabCacheValue cachedValue = cmabCache.lookup(cacheKey);
53+
54+
String attributesHash = hashAttributes(filteredAttributes);
55+
56+
if (cachedValue != null) {
57+
if (cachedValue.getAttributesHash().equals(attributesHash)) {
58+
return new CmabDecision(cachedValue.getVariationId(), cachedValue.getCmabUuid());
59+
} else {
60+
cmabCache.remove(cacheKey);
61+
}
62+
}
63+
64+
CmabDecision cmabDecision = fetchDecision(ruleId, userId, filteredAttributes);
65+
cmabCache.save(cacheKey, new CmabCacheValue(attributesHash, cmabDecision.getVariationId(), cmabDecision.getCmabUUID()));
66+
67+
return cmabDecision;
3468
}
3569

3670
private CmabDecision fetchDecision(String ruleId, String userId, Map<String, Object> attributes) {
@@ -42,7 +76,7 @@ private CmabDecision fetchDecision(String ruleId, String userId, Map<String, Obj
4276
private Map<String, Object> filterAttributes(ProjectConfig projectConfig, OptimizelyUserContext userContext, String ruleId) {
4377
Map<String, Object> userAttributes = userContext.getAttributes();
4478
Map<String, Object> filteredAttributes = new HashMap<>();
45-
79+
4680
// Get experiment by rule ID
4781
Experiment experiment = projectConfig.getExperimentIdMapping().get(ruleId);
4882
if (experiment == null) {
@@ -51,7 +85,7 @@ private Map<String, Object> filterAttributes(ProjectConfig projectConfig, Optimi
5185
}
5286
return filteredAttributes;
5387
}
54-
88+
5589
// Check if experiment has CMAB configuration
5690
// Add null check for getCmab()
5791
if (experiment.getCmab() == null) {
@@ -65,16 +99,16 @@ private Map<String, Object> filterAttributes(ProjectConfig projectConfig, Optimi
6599
if (cmabAttributeIds == null || cmabAttributeIds.isEmpty()) {
66100
return filteredAttributes;
67101
}
68-
69-
Map<String,Attribute> attributeIdMapping = projectConfig.getAttributeIdMapping();
102+
103+
Map<String, Attribute> attributeIdMapping = projectConfig.getAttributeIdMapping();
70104
// Add null check for attributeIdMapping
71105
if (attributeIdMapping == null) {
72106
if (logger != null) {
73107
logger.debug("No attribute mapping found in project config for rule ID: {}", ruleId);
74108
}
75109
return filteredAttributes;
76110
}
77-
111+
78112
// Filter attributes based on CMAB configuration
79113
for (String attributeId : cmabAttributeIds) {
80114
Attribute attribute = attributeIdMapping.get(attributeId);
@@ -88,19 +122,19 @@ private Map<String, Object> filterAttributes(ProjectConfig projectConfig, Optimi
88122
logger.debug("Attribute configuration not found for ID: {}", attributeId);
89123
}
90124
}
91-
125+
92126
return filteredAttributes;
93127
}
94128

95129
private String getCacheKey(String userId, String ruleId) {
96130
return userId.length() + "-" + userId + "-" + ruleId;
97131
}
98-
132+
99133
private String hashAttributes(Map<String, Object> attributes) {
100134
try {
101135
// Sort attributes to ensure consistent hashing
102136
TreeMap<String, Object> sortedAttributes = new TreeMap<>(attributes);
103-
137+
104138
// Create a simple string representation
105139
StringBuilder sb = new StringBuilder();
106140
sb.append("{");
@@ -118,11 +152,11 @@ private String hashAttributes(Map<String, Object> attributes) {
118152
first = false;
119153
}
120154
sb.append("}");
121-
155+
122156
// Generate MD5 hash
123157
MessageDigest md = MessageDigest.getInstance("MD5");
124158
byte[] hash = md.digest(sb.toString().getBytes());
125-
159+
126160
// Convert to hex string
127161
StringBuilder hexString = new StringBuilder();
128162
for (byte b : hash) {
@@ -133,12 +167,12 @@ private String hashAttributes(Map<String, Object> attributes) {
133167
hexString.append(hex);
134168
}
135169
return hexString.toString();
136-
170+
137171
} catch (NoSuchAlgorithmException e) {
138172
if (logger != null) {
139173
logger.warn("Failed to hash attributes", e);
140174
}
141-
return "";
175+
throw new RuntimeException("MD5 algorithm not available", e);
142176
}
143177
}
144178
}

0 commit comments

Comments
 (0)