Skip to content
Open
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
79 changes: 59 additions & 20 deletions src/main/java/com/configcat/ConfigCatClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,15 @@ 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.configCatHooks.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.configCatHooks.invokeOnFlagEvaluated(evaluationDetails);
return defaultValue;
}
}
Expand Down Expand Up @@ -135,10 +141,16 @@ 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);
this.configCatHooks.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.configCatHooks.invokeOnFlagEvaluated(evaluationDetails);
return evaluationDetails.asTypeSpecific();
}
}

Expand All @@ -156,15 +168,25 @@ 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.configCatHooks.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.configCatHooks.invokeOnFlagEvaluated(evaluationDetails);
return evaluationDetails.asTypeSpecific();
}

return this.evaluate(classOfT, checkSettingResult.value(),
key, user != null ? user : this.defaultUser, settingsResult.fetchTime(), settingsResult.settings());
});
}

Expand Down Expand Up @@ -208,8 +230,8 @@ public CompletableFuture<Map<String, Object>> getAllValuesAsync(User user) {
for (String key : keys) {
Setting setting = settings.get(key);

SettingValue evaluated = this.rolloutEvaluator.evaluate(setting, key, getEvaluateUser(user), settings, new EvaluateLogger(this.clientLogLevel)).value;
Object value = this.parseObject(this.classBySettingType(setting.getType()), evaluated, setting.getType());
Object value = this.evaluateObject(this.classBySettingType(setting.getType()), setting, key,
getEvaluateUser(user), settingResult.fetchTime(), settings).getValue();
result.put(key, value);
}

Expand Down Expand Up @@ -278,6 +300,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 @@ -295,6 +319,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 @@ -337,14 +363,22 @@ public RefreshResult forceRefresh() {
} catch (InterruptedException e) {
this.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 (this.configService == null) {
return CompletableFuture.completedFuture(new RefreshResult(false,
"The ConfigCat SDK is in local-only mode. Calling .forceRefresh() has no effect.",
RefreshErrorCode.LOCAL_ONLY_CLIENT, null));
}
return this.configService.refresh();
}

Expand Down Expand Up @@ -485,37 +519,40 @@ 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()) {
Object formattableLogMessage = ConfigCatLogMessages.getConfigJsonIsNotPresentedWithDefaultValue(key, "defaultValue", defaultValue);
this.logger.error(1000, formattableLogMessage);
return Result.error(formattableLogMessage, null);
return Result.error(formattableLogMessage, null, EvaluationErrorCode.CONFIG_JSON_NOT_AVAILABLE, null);
}

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

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

private <T> T getValueFromSettingsMap(Class<T> classOfT, SettingResult settingResult, String key, User user, T defaultValue) {
try {
Result<Setting> checkSettingResult = checkSettingAvailable(settingResult, key, defaultValue);
Result<Setting, EvaluationErrorCode> checkSettingResult = checkSettingAvailable(settingResult, key, defaultValue);
if (checkSettingResult.error() != null) {
this.configCatHooks.invokeOnFlagEvaluated(EvaluationDetails.fromError(key, defaultValue, checkSettingResult.error(), user));
this.configCatHooks.invokeOnFlagEvaluated(EvaluationDetails.fromError(key, defaultValue,
checkSettingResult.errorCode(), checkSettingResult.error(), null, user));
return defaultValue;
}

return this.evaluate(classOfT, checkSettingResult.value(), key, getEvaluateUser(user), settingResult.fetchTime(), settingResult.settings()).getValue();
} catch (Exception e) {
FormattableLogMessage formattableLogMessage = ConfigCatLogMessages.getSettingEvaluationFailedForOtherReason(key, "defaultValue", defaultValue);
this.logger.error(2001, formattableLogMessage, e);
this.configCatHooks.invokeOnFlagEvaluated(EvaluationDetails.fromError(key, defaultValue, formattableLogMessage + " " + e.getMessage(), user));
this.configCatHooks.invokeOnFlagEvaluated(EvaluationDetails.fromError(key, defaultValue,
EvaluationErrorCode.fromException(e), formattableLogMessage + " " + e.getMessage(), e,
getEvaluateUser(user)));
return defaultValue;
}
}
Expand Down Expand Up @@ -580,7 +617,7 @@ private Object parseObject(Class<?> classOfT, SettingValue settingValue, Setting
} 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/java/#setting-type-mapping");
Expand All @@ -602,7 +639,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");
}

/**
Expand Down Expand Up @@ -688,6 +725,8 @@ private EvaluationDetails<Object> evaluateObject(Class<?> classOfT, Setting sett
user,
false,
null,
EvaluationErrorCode.NONE,
null,
fetchTime,
evaluationResult.matchedTargetingRule,
evaluationResult.matchedPercentageOption);
Expand Down
33 changes: 21 additions & 12 deletions src/main/java/com/configcat/ConfigFetcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -124,19 +124,22 @@ public void onFailure(@NotNull Call call, @NotNull IOException e) {
logger.debug(ConfigCatLogMessages.getDebugEnabledRequestFailed(requestId));
}
int logEventId = 1103;
RefreshErrorCode errorCode = RefreshErrorCode.HTTP_REQUEST_FAILURE;
Object message = ConfigCatLogMessages.getFetchFailedDueToUnexpectedError(null);
if (!isClosed.get()) {
if (e instanceof SocketTimeoutException) {
logEventId = 1102;
message = ConfigCatLogMessages.getFetchFailedDueToRequestTimeout(httpClient.connectTimeoutMillis(), httpClient.readTimeoutMillis(), httpClient.writeTimeoutMillis(), null);
errorCode = RefreshErrorCode.HTTP_REQUEST_TIMEOUT;
}
logger.error(logEventId, message, e);
}
fetchResponse = FetchResponse.failed(message, false, null, true);
fetchResponse = FetchResponse.failed(message, errorCode, e, false, null, true);
} finally {
if(fetchResponse == null) {
FormattableLogMessage formattableLogMessage = ConfigCatLogMessages.getFetchFailedDueToUnexpectedError(null);
fetchResponse = FetchResponse.failed(formattableLogMessage,false, null, false);
fetchResponse = FetchResponse.failed(formattableLogMessage, RefreshErrorCode.UNEXPECTED_ERROR,
null, false, null, false);
}
future.complete(fetchResponse);
}
Expand All @@ -159,9 +162,10 @@ public void onResponse(@NotNull Call call, @NotNull Response response) {

logger.debug(ConfigCatLogMessages.getDebugEnabledReceivedBody(requestId, content.length()));
}
Result<Config> result = deserializeConfig(content, cfRayId);
Result<Config, RefreshErrorCode> result = deserializeConfig(content, cfRayId);
if (result.error() != null) {
fetchResponse = FetchResponse.failed(result.error(), false, cfRayId, false);
fetchResponse = FetchResponse.failed(result.error(), result.errorCode(),
result.errorException(), false, cfRayId, false);
} else {
fetchResponse = FetchResponse.fetched(new Entry(result.value(), eTag, content, System.currentTimeMillis()), cfRayId);
logger.debug("Fetch was successful: new config fetched.");
Expand All @@ -175,34 +179,39 @@ public void onResponse(@NotNull Call call, @NotNull Response response) {
}
} else if (responseCode == 403 || responseCode == 404) {
FormattableLogMessage message = ConfigCatLogMessages.getFetchFailedDueToInvalidSDKKey(cfRayId);
fetchResponse = FetchResponse.failed(message, true, cfRayId, false);
fetchResponse = FetchResponse.failed(message, RefreshErrorCode.INVALID_SDK_KEY,
null, true, cfRayId, false);
logger.error(1100, message);
} else {
if (isDebugLoggingEnabled){
logger.debug(ConfigCatLogMessages.getDebugEnabledReceivedUnexpectedStatusCode(requestId));
}
FormattableLogMessage formattableLogMessage = ConfigCatLogMessages.getFetchFailedDueToUnexpectedHttpResponse(responseCode, response.message(), cfRayId);
fetchResponse = FetchResponse.failed(formattableLogMessage, false, cfRayId, true);
fetchResponse = FetchResponse.failed(formattableLogMessage,
RefreshErrorCode.UNEXPECTED_HTTP_RESPONSE, null, false, cfRayId, true);
logger.error(1101, formattableLogMessage);
}
} catch (SocketTimeoutException e) {
if (isDebugLoggingEnabled) {
logger.debug(ConfigCatLogMessages.getDebugEnabledRequestTimedOut(requestId));
}
FormattableLogMessage formattableLogMessage = ConfigCatLogMessages.getFetchFailedDueToRequestTimeout(httpClient.connectTimeoutMillis(), httpClient.readTimeoutMillis(), httpClient.writeTimeoutMillis(), cfRayId);
fetchResponse = FetchResponse.failed(formattableLogMessage, false, cfRayId, true);
fetchResponse = FetchResponse.failed(formattableLogMessage,
RefreshErrorCode.HTTP_REQUEST_TIMEOUT, e, false, cfRayId, true);
logger.error(1102, formattableLogMessage, e);
} catch (Exception e) {
if (isDebugLoggingEnabled) {
logger.debug(ConfigCatLogMessages.getDebugEnabledRequestFailed(requestId));
}
FormattableLogMessage formattableLogMessage = ConfigCatLogMessages.getFetchFailedDueToUnexpectedError(cfRayId);
fetchResponse = FetchResponse.failed(formattableLogMessage, false, cfRayId, true);
fetchResponse = FetchResponse.failed(formattableLogMessage,
RefreshErrorCode.HTTP_REQUEST_FAILURE, e, false, cfRayId, true);
logger.error(1103, formattableLogMessage, e);
} finally {
if(fetchResponse == null) {
FormattableLogMessage formattableLogMessage = ConfigCatLogMessages.getFetchFailedDueToUnexpectedError(cfRayId);
fetchResponse = FetchResponse.failed(formattableLogMessage,false, cfRayId, false);
fetchResponse = FetchResponse.failed(formattableLogMessage,
RefreshErrorCode.UNEXPECTED_ERROR, null, false, cfRayId, false);
}
future.complete(fetchResponse);
}
Expand Down Expand Up @@ -290,13 +299,13 @@ private String getProxyAddress() {
return proxy.type() + " @ " + proxy.address();
}

private Result<Config> deserializeConfig(String json, String cfRayId) {
private Result<Config, RefreshErrorCode> deserializeConfig(String json, String cfRayId) {
try {
return Result.success(Utils.deserializeConfig(json));
return Result.success(Utils.deserializeConfig(json), RefreshErrorCode.NONE);
} catch (Exception e) {
FormattableLogMessage message = ConfigCatLogMessages.getFetchReceived200WithInvalidBodyError(cfRayId);
this.logger.error(1105, message, e);
return Result.error(message, null);
return Result.error(message, null, RefreshErrorCode.INVALID_HTTP_RESPONSE_CONTENT, e);
}
}
}
Loading