Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ logback = "1.5.19"
mockwebserver = "4.12.0"
mockito = "5.14.2"
android-retrofuture = "1.7.4"
android-gradle = "8.0.0"
android-gradle = "8.2.0"

[libraries]
slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j-api" }
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Thu Aug 04 20:00:01 CEST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
70 changes: 46 additions & 24 deletions src/main/java/com/configcat/ConfigCatClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ private ConfigCatClient(String sdkKey, Options options) throws IllegalArgumentEx
monitor = options.context != null ? new AppStateMonitor(options.context, logger) : null;
this.configService = new ConfigService(sdkKey, monitor, options.pollingMode, options.cache, logger, fetcher, options.hooks, options.offline);
} catch (Exception e) {
if(fetcher != null) fetcher.close();
if(monitor != null) monitor.close();
if (fetcher != null) fetcher.close();
if (monitor != null) monitor.close();
throw e;
}
} else {
Expand All @@ -83,9 +83,13 @@ public <T> T getValue(Class<T> classOfT, String key, User user, T defaultValue)
} catch (InterruptedException e) {
this.logger.error(0, "Thread interrupted.", e);
Thread.currentThread().interrupt();
EvaluationDetails<Object> evaluationDetails = EvaluationDetails.fromError(key, defaultValue, EvaluationErrorCode.UNEXPECTED_ERROR, e.getMessage(), e, user);
this.hooks.invokeOnFlagEvaluated(evaluationDetails);
return defaultValue;
} catch (Exception e) {
this.logger.error(1002, ConfigCatLogMessages.getSettingEvaluationErrorWithDefaultValue("getValue", key, "defaultValue", defaultValue.toString()), e);
EvaluationDetails<Object> evaluationDetails = EvaluationDetails.fromError(key, defaultValue, EvaluationErrorCode.fromException(e), e.getMessage(), e, user);
this.hooks.invokeOnFlagEvaluated(evaluationDetails);
return defaultValue;
}
}
Expand Down Expand Up @@ -130,10 +134,14 @@ public <T> EvaluationDetails<T> getValueDetails(Class<T> classOfT, String key, U
String error = "Thread interrupted.";
this.logger.error(0, error, e);
Thread.currentThread().interrupt();
return EvaluationDetails.fromError(key, defaultValue, error + ": " + e.getMessage(), user);
EvaluationDetails<Object> evaluationDetails = EvaluationDetails.fromError(key, defaultValue, EvaluationErrorCode.UNEXPECTED_ERROR, error + ": " + e.getMessage(), e, user);
Comment thread
adams85 marked this conversation as resolved.
this.hooks.invokeOnFlagEvaluated(evaluationDetails);
return evaluationDetails.asTypeSpecific();
} catch (Exception e) {
this.logger.error(1002, ConfigCatLogMessages.getSettingEvaluationErrorWithDefaultValue("getValueDetails", key, "defaultValue", defaultValue), e);
return EvaluationDetails.fromError(key, defaultValue, e.getMessage(), user);
EvaluationDetails<Object> evaluationDetails = EvaluationDetails.fromError(key, defaultValue, EvaluationErrorCode.fromException(e), e.getMessage(), e, user);
this.hooks.invokeOnFlagEvaluated(evaluationDetails);
return evaluationDetails.asTypeSpecific();
}
}

Expand All @@ -151,15 +159,23 @@ public <T> CompletableFuture<EvaluationDetails<T>> getValueDetailsAsync(Class<T>

return this.getSettingsAsync()
.thenApply(settingsResult -> {
Result<Setting> checkSettingResult = checkSettingAvailable(settingsResult, key, defaultValue);
if (checkSettingResult.error() != null) {
EvaluationDetails<Object> evaluationDetails = EvaluationDetails.fromError(key, defaultValue, checkSettingResult.error(), user);
try {
Result<Setting, EvaluationErrorCode> checkSettingResult = checkSettingAvailable(settingsResult, key, defaultValue);
if (checkSettingResult.error() != null) {
EvaluationDetails<Object> evaluationDetails = EvaluationDetails.fromError(key, defaultValue, checkSettingResult.errorCode(), checkSettingResult.error(), null, user);
this.hooks.invokeOnFlagEvaluated(evaluationDetails);
return evaluationDetails.asTypeSpecific();
}
return this.evaluate(classOfT, checkSettingResult.value(),
key, user != null ? user : this.defaultUser, settingsResult.fetchTime(), settingsResult.settings());
} catch (Exception e) {
this.logger.error(1002, ConfigCatLogMessages.getSettingEvaluationErrorWithDefaultValue("getValueDetailsAsync", key, "defaultValue", defaultValue), e);
EvaluationDetails<Object> evaluationDetails = EvaluationDetails.fromError(key, defaultValue, EvaluationErrorCode.fromException(e), e.getMessage(), e, user);
this.hooks.invokeOnFlagEvaluated(evaluationDetails);
return evaluationDetails.asTypeSpecific();
}
return this.evaluate(classOfT, checkSettingResult.value(),
key, user != null ? user : this.defaultUser, settingsResult.fetchTime(), settingsResult.settings());
});

}

@Override
Expand Down Expand Up @@ -270,6 +286,8 @@ public <T> Map.Entry<String, T> getKeyAndValue(Class<T> classOfT, String variati
if (variationId == null || variationId.isEmpty())
throw new IllegalArgumentException("'variationId' cannot be null or empty.");

validateReturnType(classOfT);

try {
return this.getKeyAndValueAsync(classOfT, variationId).get();
} catch (InterruptedException e) {
Expand All @@ -287,6 +305,8 @@ public <T> CompletableFuture<Map.Entry<String, T>> getKeyAndValueAsync(Class<T>
if (variationId == null || variationId.isEmpty())
throw new IllegalArgumentException("'variationId' cannot be null or empty.");

validateReturnType(classOfT);

return this.getSettingsAsync()
.thenApply(settingsResult -> this.getKeyAndValueFromSettingsMap(classOfT, settingsResult, variationId));
}
Expand Down Expand Up @@ -328,17 +348,19 @@ public RefreshResult forceRefresh() {
} catch (InterruptedException e) {
logger.error(0, "Thread interrupted.", e);
Thread.currentThread().interrupt();
return new RefreshResult(false, "An error occurred during the refresh.", RefreshErrorCode.UNEXPECTED_ERROR, e);
} catch (Exception e) {
this.logger.error(1003, ConfigCatLogMessages.getForceRefreshError("forceRefresh"), e);
return new RefreshResult(false, "An error occurred during the refresh.", RefreshErrorCode.UNEXPECTED_ERROR, e);
}
return new RefreshResult(false, "An error occurred during the refresh.");

}

@Override
public CompletableFuture<RefreshResult> forceRefreshAsync() {
if (configService == null) {
return CompletableFuture.completedFuture(new RefreshResult(false,
"The ConfigCat SDK is in local-only mode. Calling .forceRefresh() has no effect."));
"The ConfigCat SDK is in local-only mode. Calling .forceRefresh() has no effect.", RefreshErrorCode.LOCAL_ONLY_CLIENT, null));
}

return configService.refresh();
Expand Down Expand Up @@ -439,16 +461,16 @@ private CompletableFuture<SettingResult> getSettingsAsync() {
private <T> T getValueFromSettingsMap(Class<T> classOfT, SettingResult settingResult, String key, User user, T defaultValue) {
User userObject = user != null ? user : this.defaultUser;
try {
Result<Setting> checkSettingResult = checkSettingAvailable(settingResult, key, defaultValue);
Result<Setting, EvaluationErrorCode> checkSettingResult = checkSettingAvailable(settingResult, key, defaultValue);
if (checkSettingResult.error() != null) {
this.hooks.invokeOnFlagEvaluated(EvaluationDetails.fromError(key, defaultValue, checkSettingResult.error(), user));
this.hooks.invokeOnFlagEvaluated(EvaluationDetails.fromError(key, defaultValue, checkSettingResult.errorCode(), checkSettingResult.error(), null, user));
return defaultValue;
}
return this.evaluate(classOfT, checkSettingResult.value(), key, userObject, settingResult.fetchTime(), settingResult.settings()).getValue();
} catch (Exception | NoSuchMethodError e) {
FormattableLogMessage error = ConfigCatLogMessages.getSettingEvaluationFailedForOtherReason(key, "defaultValue", defaultValue);
this.hooks.invokeOnFlagEvaluated(EvaluationDetails.fromError(key, defaultValue, error + " " + e.getMessage(), userObject));
this.logger.error(2001, error, e);
this.hooks.invokeOnFlagEvaluated(EvaluationDetails.fromError(key, defaultValue, EvaluationErrorCode.fromException(e), error + " " + e.getMessage(), e, userObject));
return defaultValue;
}
}
Expand All @@ -466,7 +488,7 @@ private <T> Map.Entry<String, T> getKeyAndValueFromSettingsMap(Class<T> classOfT
return new AbstractMap.SimpleEntry<>(settingKey, (T) this.parseObject(classOfT, setting.getSettingsValue(), setting.getType()));
}

if(setting.getTargetingRules() != null) {
if (setting.getTargetingRules() != null) {
for (TargetingRule targetingRule : setting.getTargetingRules()) {
if (targetingRule.getSimpleValue() != null) {
if (variationId.equals(targetingRule.getSimpleValue().getVariationId())) {
Expand All @@ -485,7 +507,7 @@ private <T> Map.Entry<String, T> getKeyAndValueFromSettingsMap(Class<T> classOfT
}
}

if( setting.getPercentageOptions() != null) {
if (setting.getPercentageOptions() != null) {
for (PercentageOption percentageOption : setting.getPercentageOptions()) {
if (variationId.equals(percentageOption.getVariationId())) {
return new AbstractMap.SimpleEntry<>(settingKey, (T) this.parseObject(classOfT, percentageOption.getValue(), setting.getType()));
Expand All @@ -510,6 +532,8 @@ private <T> EvaluationDetails<T> evaluate(Class<T> classOfT, Setting setting, St
user,
false,
null,
EvaluationErrorCode.NONE,
null,
fetchTime,
evaluationResult.targetingRule,
evaluationResult.percentageOption);
Expand All @@ -518,8 +542,6 @@ private <T> EvaluationDetails<T> evaluate(Class<T> classOfT, Setting setting, St
}

private Object parseObject(Class<?> classOfT, SettingValue settingValue, SettingType settingType) {
validateReturnType(classOfT);

if (classOfT == String.class && settingValue.getStringValue() != null && SettingType.STRING.equals(settingType))
return settingValue.getStringValue();
else if ((classOfT == Integer.class || classOfT == int.class) && settingValue.getIntegerValue() != null && SettingType.INT.equals(settingType))
Expand All @@ -529,7 +551,7 @@ else if ((classOfT == Double.class || classOfT == double.class) && settingValue.
else if ((classOfT == Boolean.class || classOfT == boolean.class) && settingValue.getBooleanValue() != null && SettingType.BOOLEAN.equals(settingType))
return settingValue.getBooleanValue();

throw new IllegalArgumentException("The type of a setting must match the type of the specified default value. "
throw new EvaluationException("The type of a setting must match the type of the specified default value. "
+ "Setting's type was {" + settingType + "} but the default value's type was {" + classOfT + "}. "
+ "Please use a default value which corresponds to the setting type {" + settingType + "}."
+ "Learn more: https://configcat.com/docs/sdk-reference/android/#setting-type-mapping");
Expand All @@ -545,7 +567,7 @@ else if (settingType == SettingType.INT)
else if (settingType == SettingType.DOUBLE)
return double.class;
else
throw new IllegalArgumentException("Only String, Integer, Double or Boolean types are supported");
throw new InvalidConfigModelException("Only String, Integer, Double or Boolean types are supported");
}

private boolean checkSettingsAvailable(SettingResult settingResult, String emptyResult) {
Expand All @@ -557,22 +579,22 @@ private boolean checkSettingsAvailable(SettingResult settingResult, String empty
return true;
}

private <T> Result<Setting> checkSettingAvailable(SettingResult settingResult, String key, T defaultValue) {
private <T> Result<Setting, EvaluationErrorCode> checkSettingAvailable(SettingResult settingResult, String key, T defaultValue) {
if (settingResult.isEmpty()) {
FormattableLogMessage errorMessage = ConfigCatLogMessages.getConfigJsonIsNotPresentedWithDefaultValue(key, "defaultValue", defaultValue);
this.logger.error(1000, errorMessage);
return Result.error(errorMessage, null);
return Result.error(errorMessage, null, EvaluationErrorCode.CONFIG_JSON_NOT_AVAILABLE, null);
}

Map<String, Setting> settings = settingResult.settings();
Setting setting = settings.get(key);
if (setting == null) {
FormattableLogMessage errorMessage = ConfigCatLogMessages.getSettingEvaluationFailedDueToMissingKey(key, "defaultValue", defaultValue, settings.keySet());
this.logger.error(1001, errorMessage);
return Result.error(errorMessage, null);
return Result.error(errorMessage, null, EvaluationErrorCode.SETTING_KEY_MISSING, null);
}

return Result.success(setting);
return Result.success(setting, EvaluationErrorCode.NONE);
}

/**
Expand Down
Loading