22
33import java .security .MessageDigest ;
44import java .security .NoSuchAlgorithmException ;
5+ import java .util .Collections ;
56import java .util .HashMap ;
67import java .util .List ;
78import java .util .Map ;
1819import com .optimizely .ab .optimizelydecision .OptimizelyDecideOption ;
1920
2021public 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