Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
2640eaa
Cmab datafile parsed
FarhanAnjum-opti Aug 11, 2025
ad048df
Merge branch 'master' into farhan-anjum/FSSDK-11134-cmab-datafile-par…
FarhanAnjum-opti Aug 11, 2025
2d21ee2
Add CMAB configuration and parsing tests with cmab datafile
FarhanAnjum-opti Aug 12, 2025
0d3a88d
Add copyright notice to CmabTest and CmabParsingTest files
FarhanAnjum-opti Aug 12, 2025
50334f1
Refactor cmab parsing logic to simplify null check in JsonConfigParser
FarhanAnjum-opti Aug 12, 2025
87c553c
Merge branch 'master' into farhan-anjum/FSSDK-11134-cmab-datafile-par…
FarhanAnjum-opti Aug 12, 2025
8258bb0
update: implement remove method in DefaultLRUCache for cache entry re…
FarhanAnjum-opti Aug 12, 2025
4fa8cbe
add: implement remove method tests in DefaultLRUCacheTest for various…
FarhanAnjum-opti Aug 13, 2025
85b26d4
Merge branch 'master' into farhan-anjum/FSSDK-11152-update-lru-cache-…
FarhanAnjum-opti Aug 13, 2025
aa955eb
refactor: remove unused methods from Cache interface
FarhanAnjum-opti Aug 14, 2025
d3fc4bb
update: add reset method to Cache interface
FarhanAnjum-opti Aug 14, 2025
044c230
add: implement CmabClient, CmabClientConfig, and RetryConfig with fet…
FarhanAnjum-opti Aug 20, 2025
8eb694e
update: improve error logging in DefaultCmabClient for fetchDecision …
FarhanAnjum-opti Aug 20, 2025
50e2f7d
add: implement unit tests for DefaultCmabClient with various scenario…
FarhanAnjum-opti Aug 20, 2025
254d308
Merge branch 'master' into farhan-anjum/FSSDK-11143-implement-cmab-cl…
FarhanAnjum-opti Aug 20, 2025
04bb6f4
update: add missing license header to DefaultCmabClient.java
FarhanAnjum-opti Aug 20, 2025
7e9487c
update: add missing license headers to CmabClient, CmabClientConfig, …
FarhanAnjum-opti Aug 20, 2025
d41c775
refactor: update DefaultCmabClient to use synchronous fetchDecision m…
FarhanAnjum-opti Aug 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions core-api/src/main/java/com/optimizely/ab/cmab/CmabClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Copyright 2025, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.optimizely.ab.cmab;

import java.util.Map;
import java.util.concurrent.CompletableFuture;

public interface CmabClient {
/**
* Fetches a decision from the CMAB prediction service.
*
* @param ruleId The rule/experiment ID
* @param userId The user ID
* @param attributes User attributes
* @param cmabUuid The CMAB UUID
* @return CompletableFuture containing the variation ID as a String
*/
CompletableFuture<String> fetchDecision(String ruleId, String userId, Map<String, Object> attributes, String cmabUuid);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Copyright 2025, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.optimizely.ab.cmab;

import javax.annotation.Nullable;

/**
* Configuration for CMAB client operations.
* Contains only retry configuration since HTTP client is handled separately.
*/
public class CmabClientConfig {
private final RetryConfig retryConfig;

public CmabClientConfig(@Nullable RetryConfig retryConfig) {
this.retryConfig = retryConfig;
}

@Nullable
public RetryConfig getRetryConfig() {
return retryConfig;
}

/**
* Creates a config with default retry settings.
*/
public static CmabClientConfig withDefaultRetry() {
return new CmabClientConfig(RetryConfig.defaultConfig());
}

/**
* Creates a config with no retry.
*/
public static CmabClientConfig withNoRetry() {
return new CmabClientConfig(null);
}
}
120 changes: 120 additions & 0 deletions core-api/src/main/java/com/optimizely/ab/cmab/RetryConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/**
* Copyright 2025, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.optimizely.ab.cmab;
/**
* Configuration for retry behavior in CMAB client operations.
*/
public class RetryConfig {
private final int maxRetries;
private final long backoffBaseMs;
private final double backoffMultiplier;

/**
* Creates a RetryConfig with custom retry and backoff settings.
*
* @param maxRetries Maximum number of retry attempts
* @param backoffBaseMs Base delay in milliseconds for the first retry
* @param backoffMultiplier Multiplier for exponential backoff (e.g., 2.0 for doubling)
*/
public RetryConfig(int maxRetries, long backoffBaseMs, double backoffMultiplier) {
if (maxRetries < 0) {
throw new IllegalArgumentException("maxRetries cannot be negative");
}
if (backoffBaseMs < 0) {
throw new IllegalArgumentException("backoffBaseMs cannot be negative");
}
if (backoffMultiplier < 1.0) {
throw new IllegalArgumentException("backoffMultiplier must be >= 1.0");
}

this.maxRetries = maxRetries;
this.backoffBaseMs = backoffBaseMs;
this.backoffMultiplier = backoffMultiplier;
}

/**
* Creates a RetryConfig with default backoff settings (1 second base, 2x multiplier).
*
* @param maxRetries Maximum number of retry attempts
*/
public RetryConfig(int maxRetries) {
this(maxRetries, 1000, 2.0); // Default: 1 second base, exponential backoff
}

/**
* Creates a default RetryConfig with 3 retries and exponential backoff.
*/
public static RetryConfig defaultConfig() {
return new RetryConfig(3);
}

/**
* Creates a RetryConfig with no retries (single attempt only).
*/
public static RetryConfig noRetry() {
return new RetryConfig(0);
}

public int getMaxRetries() {
return maxRetries;
}

public long getBackoffBaseMs() {
return backoffBaseMs;
}

public double getBackoffMultiplier() {
return backoffMultiplier;
}

/**
* Calculates the delay for a specific retry attempt.
*
* @param attemptNumber The attempt number (0-based, so 0 = first retry)
* @return Delay in milliseconds
*/
public long calculateDelay(int attemptNumber) {
if (attemptNumber < 0) {
return 0;
}
return (long) (backoffBaseMs * Math.pow(backoffMultiplier, attemptNumber));
}

@Override
public String toString() {
return String.format("RetryConfig{maxRetries=%d, backoffBaseMs=%d, backoffMultiplier=%.1f}",
maxRetries, backoffBaseMs, backoffMultiplier);
}

@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;

RetryConfig that = (RetryConfig) obj;
return maxRetries == that.maxRetries &&
backoffBaseMs == that.backoffBaseMs &&
Double.compare(that.backoffMultiplier, backoffMultiplier) == 0;
}

@Override
public int hashCode() {
int result = maxRetries;
result = 31 * result + Long.hashCode(backoffBaseMs);
result = 31 * result + Double.hashCode(backoffMultiplier);
return result;
}
}
Loading
Loading