|
1 | 1 | package com.optimizely.ab.cmab.service; |
2 | 2 |
|
3 | | -import java.security.MessageDigest; |
4 | | -import java.security.NoSuchAlgorithmException; |
5 | 3 | import java.util.Collections; |
6 | 4 | import java.util.HashMap; |
7 | 5 | import java.util.List; |
|
11 | 9 | import org.slf4j.Logger; |
12 | 10 |
|
13 | 11 | import com.optimizely.ab.OptimizelyUserContext; |
| 12 | +import com.optimizely.ab.bucketing.internal.MurmurHash3; |
14 | 13 | import com.optimizely.ab.cmab.client.CmabClient; |
15 | 14 | import com.optimizely.ab.config.Attribute; |
16 | 15 | import com.optimizely.ab.config.Experiment; |
@@ -131,48 +130,41 @@ private String getCacheKey(String userId, String ruleId) { |
131 | 130 | } |
132 | 131 |
|
133 | 132 | private String hashAttributes(Map<String, Object> attributes) { |
134 | | - try { |
135 | | - // Sort attributes to ensure consistent hashing |
136 | | - TreeMap<String, Object> sortedAttributes = new TreeMap<>(attributes); |
137 | | - |
138 | | - // Create a simple string representation |
139 | | - StringBuilder sb = new StringBuilder(); |
140 | | - sb.append("{"); |
141 | | - boolean first = true; |
142 | | - for (Map.Entry<String, Object> entry : sortedAttributes.entrySet()) { |
143 | | - if (!first) { |
144 | | - sb.append(","); |
145 | | - } |
146 | | - sb.append("\"").append(entry.getKey()).append("\":"); |
147 | | - if (entry.getValue() instanceof String) { |
148 | | - sb.append("\"").append(entry.getValue()).append("\""); |
149 | | - } else { |
150 | | - sb.append(entry.getValue()); |
151 | | - } |
152 | | - first = false; |
153 | | - } |
154 | | - sb.append("}"); |
155 | | - |
156 | | - // Generate MD5 hash |
157 | | - MessageDigest md = MessageDigest.getInstance("MD5"); |
158 | | - byte[] hash = md.digest(sb.toString().getBytes()); |
159 | | - |
160 | | - // Convert to hex string |
161 | | - StringBuilder hexString = new StringBuilder(); |
162 | | - for (byte b : hash) { |
163 | | - String hex = Integer.toHexString(0xff & b); |
164 | | - if (hex.length() == 1) { |
165 | | - hexString.append('0'); |
166 | | - } |
167 | | - hexString.append(hex); |
| 133 | + if (attributes == null || attributes.isEmpty()) { |
| 134 | + return "empty"; |
| 135 | + } |
| 136 | + |
| 137 | + // Sort attributes to ensure consistent hashing |
| 138 | + TreeMap<String, Object> sortedAttributes = new TreeMap<>(attributes); |
| 139 | + |
| 140 | + // Create a simple string representation |
| 141 | + StringBuilder sb = new StringBuilder(); |
| 142 | + sb.append("{"); |
| 143 | + boolean first = true; |
| 144 | + for (Map.Entry<String, Object> entry : sortedAttributes.entrySet()) { |
| 145 | + if (entry.getKey() == null) continue; // Skip null keys |
| 146 | + |
| 147 | + if (!first) { |
| 148 | + sb.append(","); |
168 | 149 | } |
169 | | - return hexString.toString(); |
170 | | - |
171 | | - } catch (NoSuchAlgorithmException e) { |
172 | | - if (logger != null) { |
173 | | - logger.warn("Failed to hash attributes", e); |
| 150 | + sb.append("\"").append(entry.getKey()).append("\":"); |
| 151 | + |
| 152 | + Object value = entry.getValue(); |
| 153 | + if (value == null) { |
| 154 | + sb.append("null"); |
| 155 | + } else if (value instanceof String) { |
| 156 | + sb.append("\"").append(value).append("\""); |
| 157 | + } else { |
| 158 | + sb.append(value.toString()); |
174 | 159 | } |
175 | | - throw new RuntimeException("MD5 algorithm not available", e); |
| 160 | + first = false; |
176 | 161 | } |
| 162 | + sb.append("}"); |
| 163 | + |
| 164 | + String attributesString = sb.toString(); |
| 165 | + int hash = MurmurHash3.murmurhash3_x86_32(attributesString, 0, attributesString.length(), 0); |
| 166 | + |
| 167 | + // Convert to hex string to match your existing pattern |
| 168 | + return Integer.toHexString(hash); |
177 | 169 | } |
178 | 170 | } |
0 commit comments