Skip to content

Commit ad474c8

Browse files
update: refactor DefaultCmabService to use a generic Cache interface and enhance builder methods for cache configuration
1 parent ba575ce commit ad474c8

File tree

5 files changed

+190
-13
lines changed

5 files changed

+190
-13
lines changed

core-api/src/main/java/com/optimizely/ab/Optimizely.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1988,12 +1988,8 @@ public Builder withODPManager(ODPManager odpManager) {
19881988
return this;
19891989
}
19901990

1991-
public Builder withCmabClient(CmabClient cmabClient) {
1992-
int DEFAULT_MAX_SIZE = 1000;
1993-
int DEFAULT_CMAB_CACHE_TIMEOUT = 30 * 60;
1994-
DefaultLRUCache<CmabCacheValue> cmabCache = new DefaultLRUCache<>(DEFAULT_MAX_SIZE, DEFAULT_CMAB_CACHE_TIMEOUT);
1995-
DefaultCmabService defaultCmabService = new DefaultCmabService(cmabClient, cmabCache, logger);
1996-
this.cmabService = defaultCmabService;
1991+
public Builder withCmabService(CmabService cmabService) {
1992+
this.cmabService = cmabService;
19971993
return this;
19981994
}
19991995

core-api/src/main/java/com/optimizely/ab/cmab/client/RetryConfig.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ public RetryConfig(int maxRetries, long backoffBaseMs, double backoffMultiplier,
5252
}
5353

5454
/**
55-
* Creates a RetryConfig with default backoff settings and timeout (1 second base, 2x multiplier, 10 second timeout).
55+
* Creates a RetryConfig with default backoff settings and timeout (100 millisecond base, 2x multiplier, 10 second timeout).
5656
*
5757
* @param maxRetries Maximum number of retry attempts
5858
*/
5959
public RetryConfig(int maxRetries) {
60-
this(maxRetries, 1000, 2.0, 10000); // Default: 1 second base, exponential backoff, 10 second timeout
60+
this(maxRetries, 100, 2.0, 10000);
6161
}
6262

6363
/**
@@ -66,7 +66,7 @@ public RetryConfig(int maxRetries) {
6666
* @return Retry config with default settings
6767
*/
6868
public static RetryConfig defaultConfig() {
69-
return new RetryConfig(3);
69+
return new RetryConfig(1);
7070
}
7171

7272
/**

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

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,33 @@
2222
import java.util.TreeMap;
2323

2424
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
2526

2627
import com.optimizely.ab.OptimizelyUserContext;
2728
import com.optimizely.ab.bucketing.internal.MurmurHash3;
2829
import com.optimizely.ab.cmab.client.CmabClient;
2930
import com.optimizely.ab.config.Attribute;
3031
import com.optimizely.ab.config.Experiment;
3132
import com.optimizely.ab.config.ProjectConfig;
33+
import com.optimizely.ab.internal.Cache;
3234
import com.optimizely.ab.internal.DefaultLRUCache;
3335
import com.optimizely.ab.optimizelydecision.OptimizelyDecideOption;
3436

3537
public class DefaultCmabService implements CmabService {
36-
37-
private final DefaultLRUCache<CmabCacheValue> cmabCache;
38+
public static final int DEFAULT_CMAB_CACHE_SIZE = 1000;
39+
public static final int DEFAULT_CMAB_CACHE_TIMEOUT_SECS = 300; // 5 minutes
40+
41+
private final Cache<CmabCacheValue> cmabCache;
3842
private final CmabClient cmabClient;
3943
private final Logger logger;
4044

41-
public DefaultCmabService(CmabClient cmabClient, DefaultLRUCache<CmabCacheValue> cmabCache, Logger logger) {
45+
// public DefaultCmabService(CmabClient cmabClient, DefaultLRUCache<CmabCacheValue> cmabCache, Logger logger) {
46+
// this.cmabCache = cmabCache;
47+
// this.cmabClient = cmabClient;
48+
// this.logger = logger;
49+
// }
50+
51+
public DefaultCmabService(CmabClient cmabClient, Cache<CmabCacheValue> cmabCache, Logger logger) {
4252
this.cmabCache = cmabCache;
4353
this.cmabClient = cmabClient;
4454
this.logger = logger;
@@ -182,4 +192,97 @@ private String hashAttributes(Map<String, Object> attributes) {
182192
// Convert to hex string to match your existing pattern
183193
return Integer.toHexString(hash);
184194
}
195+
196+
public static Builder builder() {
197+
return new Builder();
198+
}
199+
200+
public static class Builder {
201+
private int cmabCacheSize = DEFAULT_CMAB_CACHE_SIZE;
202+
private int cmabCacheTimeoutInSecs = DEFAULT_CMAB_CACHE_TIMEOUT_SECS;
203+
private Cache<CmabCacheValue> customCache;
204+
private CmabClient client;
205+
private Logger logger;
206+
207+
/**
208+
* Set the maximum size of the CMAB cache.
209+
*
210+
* Default value is 1000 entries.
211+
*
212+
* @param cacheSize The maximum number of entries to store in the cache
213+
* @return Builder instance
214+
*/
215+
public Builder withCmabCacheSize(int cacheSize) {
216+
this.cmabCacheSize = cacheSize;
217+
return this;
218+
}
219+
220+
/**
221+
* Set the timeout duration for cached CMAB decisions.
222+
*
223+
* Default value is 300 seconds (5 minutes).
224+
*
225+
* @param timeoutInSecs The timeout in seconds before cached entries expire
226+
* @return Builder instance
227+
*/
228+
public Builder withCmabCacheTimeoutInSecs(int timeoutInSecs) {
229+
this.cmabCacheTimeoutInSecs = timeoutInSecs;
230+
return this;
231+
}
232+
233+
/**
234+
* Provide a custom {@link CmabClient} instance which makes HTTP calls to fetch CMAB decisions.
235+
*
236+
* A Default CmabClient implementation is required for CMAB functionality.
237+
*
238+
* @param client The implementation of {@link CmabClient}
239+
* @return Builder instance
240+
*/
241+
public Builder withClient(CmabClient client) {
242+
this.client = client;
243+
return this;
244+
}
245+
246+
/**
247+
* Provide a custom {@link Cache} instance for caching CMAB decisions.
248+
*
249+
* If provided, this will override the cache size and timeout settings.
250+
*
251+
* @param cache The custom cache instance implementing {@link Cache}
252+
* @return Builder instance
253+
*/
254+
public Builder withCustomCache(Cache<CmabCacheValue> cache) {
255+
this.customCache = cache;
256+
return this;
257+
}
258+
259+
/**
260+
* Provide a custom {@link Logger} instance for logging CMAB service operations.
261+
*
262+
* If not provided, a default SLF4J logger will be used.
263+
*
264+
* @param logger The logger instance
265+
* @return Builder instance
266+
*/
267+
public Builder withLogger(Logger logger) {
268+
this.logger = logger;
269+
return this;
270+
}
271+
272+
public DefaultCmabService build() {
273+
if (client == null) {
274+
throw new IllegalStateException("CmabClient is required");
275+
}
276+
277+
if (logger == null) {
278+
logger = LoggerFactory.getLogger(DefaultCmabService.class);
279+
}
280+
281+
Cache<CmabCacheValue> cache = customCache != null ? customCache :
282+
new DefaultLRUCache<>(cmabCacheSize, cmabCacheTimeoutInSecs);
283+
284+
285+
return new DefaultCmabService(client, cache, logger);
286+
}
287+
}
185288
}

core-api/src/main/java/com/optimizely/ab/internal/Cache.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ public interface Cache<T> {
2222
void save(String key, T value);
2323
T lookup(String key);
2424
void reset();
25+
void remove(String key);
2526
}

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

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,15 @@
2424

2525
import com.optimizely.ab.cmab.DefaultCmabClient;
2626
import com.optimizely.ab.cmab.client.CmabClientConfig;
27+
import com.optimizely.ab.cmab.service.CmabCacheValue;
28+
import com.optimizely.ab.cmab.service.DefaultCmabService;
2729
import com.optimizely.ab.config.HttpProjectConfigManager;
2830
import com.optimizely.ab.config.ProjectConfig;
2931
import com.optimizely.ab.config.ProjectConfigManager;
3032
import com.optimizely.ab.event.AsyncEventHandler;
3133
import com.optimizely.ab.event.BatchEventProcessor;
3234
import com.optimizely.ab.event.EventHandler;
35+
import com.optimizely.ab.internal.Cache;
3336
import com.optimizely.ab.internal.PropertyUtils;
3437
import com.optimizely.ab.notification.NotificationCenter;
3538
import com.optimizely.ab.odp.DefaultODPApiManager;
@@ -57,6 +60,8 @@
5760
public final class OptimizelyFactory {
5861
private static final Logger logger = LoggerFactory.getLogger(OptimizelyFactory.class);
5962

63+
private static Cache<CmabCacheValue> customCmabCache;
64+
6065
/**
6166
* Convenience method for setting the maximum number of events contained within a batch.
6267
* {@link AsyncEventHandler}
@@ -205,6 +210,48 @@ public static void setDatafileAccessToken(String datafileAccessToken) {
205210
PropertyUtils.set(HttpProjectConfigManager.CONFIG_DATAFILE_AUTH_TOKEN, datafileAccessToken);
206211
}
207212

213+
/**
214+
* Convenience method for setting the CMAB cache size.
215+
* {@link DefaultCmabService.Builder#withCmabCacheSize(int)}
216+
*
217+
* @param cacheSize The maximum number of CMAB cache entries
218+
*/
219+
public static void setCmabCacheSize(int cacheSize) {
220+
if (cacheSize <= 0) {
221+
logger.warn("CMAB cache size cannot be <= 0. Reverting to default configuration.");
222+
return;
223+
}
224+
PropertyUtils.set("optimizely.cmab.cache.size", Integer.toString(cacheSize));
225+
}
226+
227+
/**
228+
* Convenience method for setting the CMAB cache timeout.
229+
* {@link DefaultCmabService.Builder#withCmabCacheTimeoutInSecs(int)}
230+
*
231+
* @param timeoutInSecs The timeout in seconds before CMAB cache entries expire
232+
*/
233+
public static void setCmabCacheTimeoutInSecs(int timeoutInSecs) {
234+
if (timeoutInSecs <= 0) {
235+
logger.warn("CMAB cache timeout cannot be <= 0. Reverting to default configuration.");
236+
return;
237+
}
238+
PropertyUtils.set("optimizely.cmab.cache.timeout", Integer.toString(timeoutInSecs));
239+
}
240+
241+
/**
242+
* Convenience method for setting a custom CMAB cache implementation.
243+
* {@link DefaultCmabService.Builder#withCustomCache(Cache)}
244+
*
245+
* @param cache The custom cache implementation
246+
*/
247+
public static void setCustomCmabCache(Cache<CmabCacheValue> cache) {
248+
if (cache == null) {
249+
logger.warn("Custom CMAB cache cannot be null. Reverting to default configuration.");
250+
return;
251+
}
252+
customCmabCache = cache;
253+
}
254+
208255
/**
209256
* Returns a new Optimizely instance based on preset configuration.
210257
*
@@ -373,13 +420,43 @@ public static Optimizely newDefaultInstance(ProjectConfigManager configManager,
373420
.build();
374421

375422
DefaultCmabClient defaultCmabClient = new DefaultCmabClient(CmabClientConfig.withDefaultRetry());
423+
424+
DefaultCmabService.Builder cmabBuilder = DefaultCmabService.builder()
425+
.withClient(defaultCmabClient);
426+
427+
// Always apply cache size from properties if set
428+
String cacheSizeStr = PropertyUtils.get("optimizely.cmab.cache.size");
429+
if (cacheSizeStr != null) {
430+
try {
431+
cmabBuilder.withCmabCacheSize(Integer.parseInt(cacheSizeStr));
432+
} catch (NumberFormatException e) {
433+
logger.warn("Invalid CMAB cache size property value: {}", cacheSizeStr);
434+
}
435+
}
436+
437+
// Always apply cache timeout from properties if set
438+
String cacheTimeoutStr = PropertyUtils.get("optimizely.cmab.cache.timeout");
439+
if (cacheTimeoutStr != null) {
440+
try {
441+
cmabBuilder.withCmabCacheTimeoutInSecs(Integer.parseInt(cacheTimeoutStr));
442+
} catch (NumberFormatException e) {
443+
logger.warn("Invalid CMAB cache timeout property value: {}", cacheTimeoutStr);
444+
}
445+
}
446+
447+
// If custom cache is provided, it overrides the size/timeout settings
448+
if (customCmabCache != null) {
449+
cmabBuilder.withCustomCache(customCmabCache);
450+
}
376451

452+
DefaultCmabService defaultCmabService = cmabBuilder.build();
453+
377454
return Optimizely.builder()
378455
.withEventProcessor(eventProcessor)
379456
.withConfigManager(configManager)
380457
.withNotificationCenter(notificationCenter)
381458
.withODPManager(odpManager)
382-
.withCmabClient(defaultCmabClient)
459+
.withCmabService(defaultCmabService)
383460
.build();
384461
}
385462
}

0 commit comments

Comments
 (0)