Skip to content

Commit 3ab4324

Browse files
add: implement DefaultCmabService with decision retrieval and attribute filtering logic
1 parent 0201c53 commit 3ab4324

4 files changed

Lines changed: 150 additions & 16 deletions

File tree

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package com.optimizely.ab.cmab.service;
2+
3+
import java.security.MessageDigest;
4+
import java.security.NoSuchAlgorithmException;
5+
import java.util.HashMap;
6+
import java.util.List;
7+
import java.util.Map;
8+
import java.util.TreeMap;
9+
10+
import org.slf4j.Logger;
11+
12+
import com.optimizely.ab.OptimizelyUserContext;
13+
import com.optimizely.ab.cmab.client.CmabClient;
14+
import com.optimizely.ab.config.Attribute;
15+
import com.optimizely.ab.config.Experiment;
16+
import com.optimizely.ab.config.ProjectConfig;
17+
import com.optimizely.ab.internal.DefaultLRUCache;
18+
import com.optimizely.ab.optimizelydecision.OptimizelyDecideOption;
19+
20+
public class DefaultCmabService implements CmabService {
21+
private final DefaultLRUCache<CmabCacheValue> cmabCache;
22+
private final CmabClient cmabClient;
23+
private final Logger logger;
24+
25+
public DefaultCmabService(CmabServiceOptions options) {
26+
this.cmabCache = options.getCmabCache();
27+
this.cmabClient = options.getCmabClient();
28+
this.logger = options.getLogger();
29+
}
30+
31+
@Override
32+
public CmabDecision getDecision(ProjectConfig projectConfig, OptimizelyUserContext userContext, String ruleId, List<OptimizelyDecideOption> options) {
33+
return null;
34+
}
35+
36+
private Map<String, Object> filterAttributes(ProjectConfig projectConfig, OptimizelyUserContext userContext, String ruleId) {
37+
Map<String, Object> userAttributes = userContext.getAttributes();
38+
Map<String, Object> filteredAttributes = new HashMap<>();
39+
40+
// Get experiment by rule ID
41+
Experiment experiment = projectConfig.getExperimentIdMapping().get(ruleId);
42+
if (experiment == null) {
43+
if (logger != null) {
44+
logger.debug("Experiment not found for rule ID: {}", ruleId);
45+
}
46+
return filteredAttributes;
47+
}
48+
49+
// Check if experiment has CMAB configuration
50+
// Add null check for getCmab()
51+
if (experiment.getCmab() == null) {
52+
if (logger != null) {
53+
logger.debug("No CMAB configuration found for experiment: {}", ruleId);
54+
}
55+
return filteredAttributes;
56+
}
57+
58+
List<String> cmabAttributeIds = experiment.getCmab().getAttributeIds();
59+
if (cmabAttributeIds == null || cmabAttributeIds.isEmpty()) {
60+
return filteredAttributes;
61+
}
62+
63+
Map<String,Attribute> attributeIdMapping = projectConfig.getAttributeIdMapping();
64+
// Add null check for attributeIdMapping
65+
if (attributeIdMapping == null) {
66+
if (logger != null) {
67+
logger.debug("No attribute mapping found in project config for rule ID: {}", ruleId);
68+
}
69+
return filteredAttributes;
70+
}
71+
72+
// Filter attributes based on CMAB configuration
73+
for (String attributeId : cmabAttributeIds) {
74+
Attribute attribute = attributeIdMapping.get(attributeId);
75+
if (attribute != null) {
76+
if (userAttributes.containsKey(attribute.getKey())) {
77+
filteredAttributes.put(attribute.getKey(), userAttributes.get(attribute.getKey()));
78+
} else if (logger != null) {
79+
logger.debug("User attribute '{}' not found for attribute ID '{}'", attribute.getKey(), attributeId);
80+
}
81+
} else if (logger != null) {
82+
logger.debug("Attribute configuration not found for ID: {}", attributeId);
83+
}
84+
}
85+
86+
return filteredAttributes;
87+
}
88+
89+
private String getCacheKey(String userId, String ruleId) {
90+
return userId.length() + "-" + userId + "-" + ruleId;
91+
}
92+
93+
/**
94+
* Hash attributes using MD5
95+
*/
96+
private String hashAttributes(Map<String, Object> attributes) {
97+
try {
98+
// Sort attributes to ensure consistent hashing
99+
TreeMap<String, Object> sortedAttributes = new TreeMap<>(attributes);
100+
101+
// Create a simple string representation
102+
StringBuilder sb = new StringBuilder();
103+
sb.append("{");
104+
boolean first = true;
105+
for (Map.Entry<String, Object> entry : sortedAttributes.entrySet()) {
106+
if (!first) {
107+
sb.append(",");
108+
}
109+
sb.append("\"").append(entry.getKey()).append("\":");
110+
if (entry.getValue() instanceof String) {
111+
sb.append("\"").append(entry.getValue()).append("\"");
112+
} else {
113+
sb.append(entry.getValue());
114+
}
115+
first = false;
116+
}
117+
sb.append("}");
118+
119+
// Generate MD5 hash
120+
MessageDigest md = MessageDigest.getInstance("MD5");
121+
byte[] hash = md.digest(sb.toString().getBytes());
122+
123+
// Convert to hex string
124+
StringBuilder hexString = new StringBuilder();
125+
for (byte b : hash) {
126+
String hex = Integer.toHexString(0xff & b);
127+
if (hex.length() == 1) {
128+
hexString.append('0');
129+
}
130+
hexString.append(hex);
131+
}
132+
return hexString.toString();
133+
134+
} catch (NoSuchAlgorithmException e) {
135+
if (logger != null) {
136+
logger.warn("Failed to hash attributes", e);
137+
}
138+
return "";
139+
}
140+
}
141+
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ public class DatafileProjectConfig implements ProjectConfig {
9292
private final Map<String, Group> groupIdMapping;
9393
private final Map<String, Rollout> rolloutIdMapping;
9494
private final Map<String, List<String>> experimentFeatureKeyMapping;
95+
private final Map<String, Attribute> attributeIdMapping;
9596

9697
// other mappings
9798
private final Map<String, Experiment> variationIdToExperimentMapping;
@@ -253,6 +254,7 @@ public DatafileProjectConfig(String accountId,
253254
this.experimentIdMapping = ProjectConfigUtils.generateIdMapping(this.experiments);
254255
this.groupIdMapping = ProjectConfigUtils.generateIdMapping(groups);
255256
this.rolloutIdMapping = ProjectConfigUtils.generateIdMapping(this.rollouts);
257+
this.attributeIdMapping = ProjectConfigUtils.generateIdMapping(this.attributes);
256258

257259
// Generate experiment to featureFlag list mapping to identify if experiment is AB-Test experiment or Feature-Test Experiment.
258260
this.experimentFeatureKeyMapping = ProjectConfigUtils.generateExperimentFeatureMapping(this.featureFlags);
@@ -539,6 +541,11 @@ public Map<String, Attribute> getAttributeKeyMapping() {
539541
return attributeKeyMapping;
540542
}
541543

544+
@Override
545+
public Map<String, Attribute> getAttributeIdMapping() {
546+
return this.attributeIdMapping;
547+
}
548+
542549
@Override
543550
public Map<String, EventType> getEventNameMapping() {
544551
return eventNameMapping;

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ Experiment getExperimentForKey(@Nonnull String experimentKey,
101101

102102
Map<String, Attribute> getAttributeKeyMapping();
103103

104+
Map<String, Attribute> getAttributeIdMapping();
105+
104106
Map<String, EventType> getEventNameMapping();
105107

106108
Map<String, Audience> getAudienceIdMapping();

core-httpclient-impl/src/main/java/com/optimizely/ab/cmab/DefaultCmabService.java

Lines changed: 0 additions & 16 deletions
This file was deleted.

0 commit comments

Comments
 (0)