diff --git a/README.md b/README.md
index b431c36..e3fae68 100644
--- a/README.md
+++ b/README.md
@@ -45,7 +45,7 @@ install the sdk in using maven
co.featbitFeatbit-Java-SDK
- 1.0.3
+ 1.0.4
```
diff --git a/pom.xml b/pom.xml
index e6d48ae..69bd76e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
co.featbitfeatbit-java-sdk
- 1.0.3
+ 1.0.4featbit/featbit-java-sdk
diff --git a/src/main/java/co/featbit/commons/model/EvalDetail.java b/src/main/java/co/featbit/commons/model/EvalDetail.java
index 1193ee6..79528be 100644
--- a/src/main/java/co/featbit/commons/model/EvalDetail.java
+++ b/src/main/java/co/featbit/commons/model/EvalDetail.java
@@ -15,13 +15,8 @@
* @param - String/Boolean/Numeric Type
*/
public final class EvalDetail implements Serializable {
-
- private static final String NO_VARIATION = "NE";
-
private final T variation;
- private final String id;
-
private final String reason;
private final String name;
@@ -29,12 +24,10 @@ public final class EvalDetail implements Serializable {
private final String keyName;
private EvalDetail(T variation,
- String id,
String reason,
String keyName,
String name) {
this.variation = variation;
- this.id = id;
this.reason = reason;
this.keyName = keyName;
this.name = name;
@@ -43,27 +36,25 @@ private EvalDetail(T variation,
/**
* build method, this method is only for internal use
*
- * @param variation
- * @param id
- * @param reason
- * @param keyName
- * @param name
+ * @param variation the result of flag value
+ * @param reason main factor that influenced the flag evaluation value
+ * @param keyName key name of the flag
+ * @param name name of the flag
* @param String/Boolean/Numeric Type
* @return an EvalDetail
*/
public static EvalDetail of(T variation,
- String id,
String reason,
String keyName,
String name) {
- return new EvalDetail<>(variation, id, reason, keyName, name);
+ return new EvalDetail<>(variation, reason, keyName, name);
}
/**
* build the method from a json string, this method is only for internal use
*
- * @param json
- * @param cls
+ * @param json json string of an EvalDetail
+ * @param cls raw type of flag value
* @param String/Boolean/Numeric Type
* @return an EvalDetail
*/
@@ -82,16 +73,6 @@ public T getVariation() {
return variation;
}
- /**
- * The id of the returned value within the flag's list of variations
- * In fact this value is an index, this value is only for internal use
- *
- * @return a integer value
- */
- public String getId() {
- return id;
- }
-
/**
* get the reason that evaluate the flag value.
*
@@ -119,16 +100,6 @@ public String getKeyName() {
return keyName;
}
- /**
- * Returns true if the flag evaluation returned a good value,
- * false if the default value returned
- *
- * @return Returns true if the flag evaluation returned a good value, false if the default value returned
- */
- public boolean isSuccess() {
- return !id.equals(NO_VARIATION);
- }
-
/**
* object converted to json string
*
@@ -143,27 +114,21 @@ public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EvalDetail> that = (EvalDetail>) o;
- return id == that.id && Objects.equals(variation, that.variation) && Objects.equals(reason, that.reason) && Objects.equals(name, that.name) && Objects.equals(keyName, that.keyName);
+ return Objects.equals(variation, that.variation) && Objects.equals(reason, that.reason) && Objects.equals(name, that.name) && Objects.equals(keyName, that.keyName);
}
@Override
public int hashCode() {
- return Objects.hash(variation, id, reason, name, keyName);
+ return Objects.hash(variation, reason, name, keyName);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("variation", variation)
- .add("id", id)
.add("reason", reason)
.add("name", name)
.add("keyName", keyName)
.toString();
}
-
- public FlagState toFlagState() {
- return FlagState.of(this);
- }
-
}
diff --git a/src/main/java/co/featbit/commons/model/FBUser.java b/src/main/java/co/featbit/commons/model/FBUser.java
index 75e19d4..b35ff81 100644
--- a/src/main/java/co/featbit/commons/model/FBUser.java
+++ b/src/main/java/co/featbit/commons/model/FBUser.java
@@ -24,7 +24,7 @@ public final class FBUser implements Serializable {
private final static Function USERNAME = u -> u.userName;
private final static Function KEY = u -> u.key;
- private final static Map> BUILTINS = ImmutableMap.of("name", USERNAME, "keyid", KEY);
+ private final static Map> BUILTINS = ImmutableMap.of("name", USERNAME, "keyid", KEY, "key", KEY);
private final String userName;
private final String key;
private final Map custom;
diff --git a/src/main/java/co/featbit/commons/model/FlagState.java b/src/main/java/co/featbit/commons/model/FlagState.java
index 99170d6..b431e44 100644
--- a/src/main/java/co/featbit/commons/model/FlagState.java
+++ b/src/main/java/co/featbit/commons/model/FlagState.java
@@ -27,10 +27,8 @@ private FlagState(boolean success, String message, EvalDetail data) {
* @param String/Boolean/Numeric Type
* @return a FlagState
*/
- public static FlagState of(EvalDetail data) {
- return new FlagState<>(data.isSuccess(),
- data.isSuccess() ? "OK" : data.getReason(),
- data);
+ public static FlagState of(EvalDetail data, boolean success) {
+ return new FlagState<>(success, success ? "OK" : data.getReason(), data);
}
/**
diff --git a/src/main/java/co/featbit/server/Evaluator.java b/src/main/java/co/featbit/server/Evaluator.java
index 7db6a24..83a037a 100644
--- a/src/main/java/co/featbit/server/Evaluator.java
+++ b/src/main/java/co/featbit/server/Evaluator.java
@@ -1,6 +1,8 @@
package co.featbit.server;
+import co.featbit.commons.model.EvalDetail;
import co.featbit.commons.model.FBUser;
+import co.featbit.commons.model.FlagState;
import co.featbit.server.exterior.DataStoreTypes;
import org.slf4j.Logger;
@@ -132,6 +134,19 @@ public String getKeyName() {
public String getName() {
return name;
}
+
+ private boolean isDefaultValue() {
+ return this.index.equals(NO_EVAL_RES);
+ }
+
+ public EvalDetail toEvalDetail(T value) {
+ return EvalDetail.of(value, this.reason, this.keyName, this.name);
+ }
+
+ public FlagState toFlagState(T value) {
+ return FlagState.of(EvalDetail.of(value, this.reason, this.keyName, this.name), !isDefaultValue());
+ }
+
}
}
diff --git a/src/main/java/co/featbit/server/EvaluatorImp.java b/src/main/java/co/featbit/server/EvaluatorImp.java
index 93e2904..a792abd 100644
--- a/src/main/java/co/featbit/server/EvaluatorImp.java
+++ b/src/main/java/co/featbit/server/EvaluatorImp.java
@@ -4,7 +4,6 @@
import co.featbit.commons.model.FBUser;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
-import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import java.math.BigDecimal;
@@ -51,7 +50,7 @@ private EvalResult matchUserVariation(DataModel.FeatureFlag flag, FBUser user, I
return er;
} finally {
if (er != null) {
- logger.info("FFC JAVA SDK: User {}, Feature Flag {}, Flag Value {}", user.getKey(), flag.getKey(), er.getValue());
+ logger.info("FB JAVA SDK: User {}, Feature Flag {}, Flag Value {}", user.getKey(), flag.getKey(), er.getValue());
if (event != null) {
event.add(InsightTypes.FlagEventVariation.of(flag.getKey(), er));
}
@@ -124,7 +123,7 @@ private boolean ifUserMatchClause(FBUser user, DataModel.Condition condition) {
} else if (op.equals(IS_TRUE_CLAUSE)) {
return trueClause(user, condition);
} else if (op.equals(IS_FALSE_CLAUSE)) {
- return !trueClause(user, condition);
+ return falseClause(user, condition);
} else if (op.equals(MATCH_REGEX_CLAUSE)) {
return matchRegExClause(user, condition);
} else if (op.equals(NOT_MATCH_REGEX_CLAUSE)) {
@@ -162,13 +161,18 @@ private boolean inSegmentClause(FBUser user, DataModel.Condition condition) {
private boolean trueClause(FBUser user, DataModel.Condition condition) {
String pv = user.getProperty(condition.getProperty());
- return pv != null && BooleanUtils.toBoolean(pv);
+ return pv != null && pv.toLowerCase().equals("true");
+ }
+
+ private boolean falseClause(FBUser user, DataModel.Condition condition) {
+ String pv = user.getProperty(condition.getProperty());
+ return pv != null && pv.toLowerCase().equals("false");
}
private boolean matchRegExClause(FBUser user, DataModel.Condition condition) {
String pv = user.getProperty(condition.getProperty());
String condValue = condition.getValue();
- return pv != null && Pattern.compile(condValue).matcher(pv).matches();
+ return pv != null && condValue != null && Pattern.compile(condValue).matcher(pv).matches();
}
private boolean endsWithClause(FBUser user, DataModel.Condition condition) {
@@ -208,7 +212,7 @@ private boolean thanClause(FBUser user, DataModel.Condition condition) {
private boolean equalsClause(FBUser user, DataModel.Condition condition) {
String pv = user.getProperty(condition.getProperty());
String condValue = condition.getValue();
- return condValue.equals(pv);
+ return condValue != null && condValue.equals(pv);
}
private boolean containsClause(FBUser user, DataModel.Condition condition) {
diff --git a/src/main/java/co/featbit/server/FBClientImp.java b/src/main/java/co/featbit/server/FBClientImp.java
index bee4b12..267d18b 100644
--- a/src/main/java/co/featbit/server/FBClientImp.java
+++ b/src/main/java/co/featbit/server/FBClientImp.java
@@ -138,24 +138,26 @@ public FBClientImp(String envSecret, FBConfig config) {
if (!startWait.isZero() && !startWait.isNegative()) {
try {
if (!(config.getDataSynchronizerFactory() instanceof FactoryImp.NullDataSynchronizerFactory)) {
- logger.info("FFC JAVA SDK: waiting for Client initialization in {} milliseconds", startWait.toMillis());
+ logger.info("FB JAVA SDK: waiting for Client initialization in {} milliseconds", startWait.toMillis());
}
if (config.getDataStorageFactory() instanceof FactoryImp.NullDataStorageFactory) {
- logger.info("FFC JAVA SDK: SDK just returns default variation");
+ logger.info("FB JAVA SDK: SDK just returns default variation");
}
boolean initResult = initFuture.get(startWait.toMillis(), TimeUnit.MILLISECONDS);
if (initResult && !offline) {
- logger.info("FFC JAVA SDK: the initialization completed");
+ logger.info("FB JAVA SDK: SDK initialization is completed");
}
} catch (TimeoutException e) {
- logger.error("FFC JAVA SDK: timeout encountered when waiting for data update");
+ logger.error("FB JAVA SDK: timeout encountered when waiting for data update");
} catch (Exception e) {
- logger.error("FFC JAVA SDK: exception encountered when waiting for data update", e);
+ logger.error("FB JAVA SDK: exception encountered when waiting for data update", e);
}
if (!this.dataSynchronizer.isInitialized() && !offline) {
- logger.info("FFC JAVA SDK: SDK was not successfully initialized");
+ logger.warn("FB JAVA SDK: SDK was not successfully initialized");
}
+ } else {
+ logger.info("FB JAVA SDK: SDK starts in asynchronous mode");
}
}
@@ -173,7 +175,7 @@ public String variation(String featureFlagKey, FBUser user, String defaultValue)
@Override
public FlagState variationDetail(String featureFlagKey, FBUser user, String defaultValue) {
Evaluator.EvalResult res = evaluateInternal(featureFlagKey, user, defaultValue, null);
- return EvalDetail.of(res.getValue(), res.getIndex(), res.getReason(), featureFlagKey, featureFlagKey).toFlagState();
+ return res.toFlagState(res.getValue());
}
@Override
@@ -192,7 +194,7 @@ public boolean isEnabled(String featureFlagKey, FBUser user) {
public FlagState boolVariationDetail(String featureFlagKey, FBUser user, Boolean defaultValue) {
checkNotNull(defaultValue, "null defaultValue is invalid");
Evaluator.EvalResult res = evaluateInternal(featureFlagKey, user, defaultValue, Boolean.class);
- return EvalDetail.of(BooleanUtils.toBoolean(res.getValue()), res.getIndex(), res.getReason(), featureFlagKey, featureFlagKey).toFlagState();
+ return res.toFlagState(BooleanUtils.toBoolean(res.getValue()));
}
public double doubleVariation(String featureFlagKey, FBUser user, Double defaultValue) {
@@ -206,7 +208,7 @@ public double doubleVariation(String featureFlagKey, FBUser user, Double default
public FlagState doubleVariationDetail(String featureFlagKey, FBUser user, Double defaultValue) {
checkNotNull(defaultValue, "null defaultValue is invalid");
Evaluator.EvalResult res = evaluateInternal(featureFlagKey, user, defaultValue, Double.class);
- return EvalDetail.of(Double.parseDouble(res.getValue()), res.getIndex(), res.getReason(), featureFlagKey, featureFlagKey).toFlagState();
+ return res.toFlagState(Double.parseDouble(res.getValue()));
}
public int intVariation(String featureFlagKey, FBUser user, Integer defaultValue) {
@@ -219,7 +221,7 @@ public int intVariation(String featureFlagKey, FBUser user, Integer defaultValue
public FlagState intVariationDetail(String featureFlagKey, FBUser user, Integer defaultValue) {
checkNotNull(defaultValue, "null defaultValue is invalid");
Evaluator.EvalResult res = evaluateInternal(featureFlagKey, user, defaultValue, Integer.class);
- return EvalDetail.of(Double.valueOf(res.getValue()).intValue(), res.getIndex(), res.getReason(), featureFlagKey, featureFlagKey).toFlagState();
+ return res.toFlagState(Double.valueOf(res.getValue()).intValue());
}
public long longVariation(String featureFlagKey, FBUser user, Long defaultValue) {
@@ -232,7 +234,7 @@ public long longVariation(String featureFlagKey, FBUser user, Long defaultValue)
public FlagState longVariationDetail(String featureFlagKey, FBUser user, Long defaultValue) {
checkNotNull(defaultValue, "null defaultValue is invalid");
Evaluator.EvalResult res = evaluateInternal(featureFlagKey, user, defaultValue, Long.class);
- return EvalDetail.of(Double.valueOf(res.getValue()).longValue(), res.getIndex(), res.getReason(), featureFlagKey, featureFlagKey).toFlagState();
+ return res.toFlagState(Double.valueOf(res.getValue()).longValue());
}
@Override
@@ -242,7 +244,7 @@ public T jsonVariation(String featureFlagKey, FBUser user, Class clazz, T
try {
return JsonHelper.deserialize(json, clazz);
} catch (JsonParseException ex) {
- logger.error("FFC JAVA SDK: json value can't be parsed", ex);
+ logger.error("FB JAVA SDK: json value can't be parsed", ex);
return defaultValue;
}
@@ -258,43 +260,43 @@ public FlagState jsonVariationDetail(String featureFlagKey, FBUser user,
try {
value = JsonHelper.deserialize(res.getValue(), clazz);
} catch (JsonParseException ex) {
- logger.error("FFC JAVA SDK: unexpected error in evaluation", ex);
+ logger.error("FB JAVA SDK: unexpected error in evaluation", ex);
value = defaultValue;
}
}
- return EvalDetail.of(value, res.getIndex(), res.getReason(), featureFlagKey, featureFlagKey).toFlagState();
+ return res.toFlagState(value);
}
private Evaluator.EvalResult evaluateInternal(String featureFlagKey, FBUser user, Object defaultValue, Class> requiredType) {
try {
if (!isInitialized()) {
- Loggers.EVALUATION.warn("FFC JAVA SDK: evaluation is called before Java SDK client is initialized for feature flag, well using the default value");
+ Loggers.EVALUATION.warn("FB JAVA SDK: evaluation is called before Java SDK client is initialized for feature flag, well using the default value");
return Evaluator.EvalResult.error(defaultValue.toString(), REASON_CLIENT_NOT_READY, featureFlagKey, FLAG_NAME_UNKNOWN);
}
if (StringUtils.isBlank(featureFlagKey)) {
- Loggers.EVALUATION.warn("FFC JAVA SDK: null feature flag key; returning default value");
+ Loggers.EVALUATION.warn("FB JAVA SDK: null feature flag key; returning default value");
return Evaluator.EvalResult.error(defaultValue.toString(), REASON_FLAG_NOT_FOUND, featureFlagKey, FLAG_NAME_UNKNOWN);
}
DataModel.FeatureFlag flag = getFlagInternal(featureFlagKey);
if (flag == null) {
- Loggers.EVALUATION.warn("FFC JAVA SDK: unknown feature flag {}; returning default value", featureFlagKey);
+ Loggers.EVALUATION.warn("FB JAVA SDK: unknown feature flag {}; returning default value", featureFlagKey);
return Evaluator.EvalResult.error(defaultValue.toString(), REASON_FLAG_NOT_FOUND, featureFlagKey, FLAG_NAME_UNKNOWN);
}
if (user == null || StringUtils.isBlank(user.getKey())) {
- Loggers.EVALUATION.warn("FFC JAVA SDK: null user for feature flag {}, returning default value", featureFlagKey);
+ Loggers.EVALUATION.warn("FB JAVA SDK: null user for feature flag {}, returning default value", featureFlagKey);
return Evaluator.EvalResult.error(defaultValue.toString(), REASON_USER_NOT_SPECIFIED, featureFlagKey, FLAG_NAME_UNKNOWN);
}
InsightTypes.Event event = InsightTypes.FlagEvent.of(user);
Evaluator.EvalResult res = evaluator.evaluate(flag, user, event);
if (requiredType != null && !Utils.checkType(flag.getVariationType(), requiredType, res.getValue())) {
- Loggers.EVALUATION.warn("FFC JAVA SDK: evaluation result {} didn't matched expected type {}", res.getValue(), requiredType);
+ Loggers.EVALUATION.warn("FB JAVA SDK: evaluation result {} didn't matched expected type {}", res.getValue(), requiredType);
return Evaluator.EvalResult.error(defaultValue.toString(), REASON_WRONG_TYPE, res.getKeyName(), res.getName());
}
eventHandler.accept(event);
return res;
} catch (Exception ex) {
- logger.error("FFC JAVA SDK: unexpected error in evaluation", ex);
+ logger.error("FB JAVA SDK: unexpected error in evaluation", ex);
return Evaluator.EvalResult.error(defaultValue.toString(), REASON_ERROR, featureFlagKey, FLAG_NAME_UNKNOWN);
}
@@ -308,12 +310,12 @@ private DataModel.FeatureFlag getFlagInternal(String featureFlagKey) {
public boolean isFlagKnown(String featureKey) {
try {
if (!isInitialized()) {
- logger.warn("FFC JAVA SDK: isFlagKnown is called before Java SDK client is initialized for feature flag");
+ logger.warn("FB JAVA SDK: isFlagKnown is called before Java SDK client is initialized for feature flag");
return false;
}
return getFlagInternal(featureKey) != null;
} catch (Exception ex) {
- logger.error("FFC JAVA SDK: unexpected error in isFlagKnown", ex);
+ logger.error("FB JAVA SDK: unexpected error in isFlagKnown", ex);
}
return false;
@@ -321,7 +323,7 @@ public boolean isFlagKnown(String featureKey) {
public void close() throws IOException {
- logger.info("FFC JAVA SDK: Java SDK client is closing...");
+ logger.info("FB JAVA SDK: Java SDK client is closing...");
this.storage.close();
this.dataSynchronizer.close();
this.insightProcessor.close();
@@ -358,18 +360,18 @@ public boolean initializeFromExternalJson(String json) {
public AllFlagStates getAllLatestFlagsVariations(FBUser user) {
ImmutableMap.Builder, InsightTypes.Event> builder = ImmutableMap.builder();
boolean success = true;
- String errorString = null;
+ String errorString = "";
EvalDetail ed;
try {
if (!isInitialized()) {
- Loggers.EVALUATION.warn("FFC JAVA SDK: Evaluation is called before Java SDK client is initialized for feature flag");
- ed = EvalDetail.of(FLAG_VALUE_UNKNOWN, NO_EVAL_RES, REASON_CLIENT_NOT_READY, FLAG_KEY_UNKNOWN, FLAG_NAME_UNKNOWN);
+ Loggers.EVALUATION.warn("FB JAVA SDK: Evaluation is called before Java SDK client is initialized for feature flag");
+ ed = EvalDetail.of(FLAG_VALUE_UNKNOWN, REASON_CLIENT_NOT_READY, FLAG_KEY_UNKNOWN, FLAG_NAME_UNKNOWN);
builder.put(ed, InsightTypes.NullEvent.INSTANCE);
success = false;
errorString = REASON_CLIENT_NOT_READY;
} else if (user == null || StringUtils.isBlank(user.getKey())) {
- Loggers.EVALUATION.warn("FFC JAVA SDK: null user or feature flag");
- ed = EvalDetail.of(FLAG_VALUE_UNKNOWN, NO_EVAL_RES, REASON_USER_NOT_SPECIFIED, FLAG_KEY_UNKNOWN, FLAG_NAME_UNKNOWN);
+ Loggers.EVALUATION.warn("FB JAVA SDK: null user or feature flag");
+ ed = EvalDetail.of(FLAG_VALUE_UNKNOWN, REASON_USER_NOT_SPECIFIED, FLAG_KEY_UNKNOWN, FLAG_NAME_UNKNOWN);
builder.put(ed, InsightTypes.NullEvent.INSTANCE);
success = false;
errorString = REASON_USER_NOT_SPECIFIED;
@@ -379,13 +381,13 @@ public AllFlagStates getAllLatestFlagsVariations(FBUser user) {
InsightTypes.Event event = InsightTypes.FlagEvent.of(user);
DataModel.FeatureFlag flag = (DataModel.FeatureFlag) item;
Evaluator.EvalResult res = evaluator.evaluate(flag, user, event);
- ed = EvalDetail.of(res.getValue(), res.getIndex(), res.getReason(), res.getKeyName(), res.getName());
+ ed = res.toEvalDetail(res.getValue());
builder.put(ed, event);
}
}
} catch (Exception ex) {
- logger.error("FFC JAVA SDK: unexpected error in evaluation", ex);
- ed = EvalDetail.of(FLAG_VALUE_UNKNOWN, NO_EVAL_RES, REASON_ERROR, FLAG_KEY_UNKNOWN, FLAG_NAME_UNKNOWN);
+ logger.error("FB JAVA SDK: unexpected error in evaluation", ex);
+ ed = EvalDetail.of(FLAG_VALUE_UNKNOWN, REASON_ERROR, FLAG_KEY_UNKNOWN, FLAG_NAME_UNKNOWN);
builder.put(ed, InsightTypes.NullEvent.INSTANCE);
success = false;
errorString = REASON_ERROR;
@@ -401,7 +403,7 @@ public void flush() {
@Override
public void identify(FBUser user) {
if (user == null) {
- Loggers.CLIENT.warn("FFC JAVA SDK: user invalid");
+ Loggers.CLIENT.warn("FB JAVA SDK: user invalid");
return;
}
InsightTypes.Event event = InsightTypes.UserEvent.of(user);
@@ -416,7 +418,7 @@ public void trackMetric(FBUser user, String eventName) {
@Override
public void trackMetric(FBUser user, String eventName, double metricValue) {
if (user == null || StringUtils.isBlank(eventName) || metricValue <= 0) {
- Loggers.CLIENT.warn("FFC JAVA SDK: event/user/metric invalid");
+ Loggers.CLIENT.warn("FB JAVA SDK: event/user/metric invalid");
return;
}
InsightTypes.Event event = InsightTypes.MetricEvent.of(user).add(InsightTypes.Metric.of(eventName, metricValue));
@@ -426,7 +428,7 @@ public void trackMetric(FBUser user, String eventName, double metricValue) {
@Override
public void trackMetrics(FBUser user, String... eventNames) {
if (user == null || eventNames == null || eventNames.length == 0) {
- Loggers.CLIENT.warn("FFC JAVA SDK: user/events invalid");
+ Loggers.CLIENT.warn("FB JAVA SDK: user/events invalid");
return;
}
InsightTypes.Event event = InsightTypes.MetricEvent.of(user);
@@ -441,7 +443,7 @@ public void trackMetrics(FBUser user, String... eventNames) {
@Override
public void trackMetrics(FBUser user, Map metrics) {
if (user == null || metrics == null || metrics.isEmpty()) {
- Loggers.CLIENT.warn("FFC JAVA SDK: user/metrics invalid");
+ Loggers.CLIENT.warn("FB JAVA SDK: user/metrics invalid");
return;
}
InsightTypes.Event event = InsightTypes.MetricEvent.of(user);
diff --git a/src/main/java/co/featbit/server/FBConfig.java b/src/main/java/co/featbit/server/FBConfig.java
index af7e430..6333bbd 100644
--- a/src/main/java/co/featbit/server/FBConfig.java
+++ b/src/main/java/co/featbit/server/FBConfig.java
@@ -70,7 +70,7 @@ public FBConfig(Builder builder) {
this.eventURL = builder.eventURL;
this.startWaitTime = builder.startWaitTime == null ? DEFAULT_START_WAIT_TIME : builder.startWaitTime;
if (builder.offline) {
- Loggers.CLIENT.info("FFC JAVA SDK: SDK is in offline mode");
+ Loggers.CLIENT.info("FB JAVA SDK: SDK is in offline mode");
this.dataSynchronizerFactory = Factory.externalDataSynchronization();
this.insightProcessorFactory = Factory.externalEventTrack();
} else {
diff --git a/src/main/java/co/featbit/server/FactoryImp.java b/src/main/java/co/featbit/server/FactoryImp.java
index 2b591ed..bc95606 100644
--- a/src/main/java/co/featbit/server/FactoryImp.java
+++ b/src/main/java/co/featbit/server/FactoryImp.java
@@ -40,7 +40,6 @@ static final class StreamingBuilderImpl extends StreamingBuilder {
@Override
public DataSynchronizer createDataSynchronizer(Context config, Status.DataUpdater dataUpdater) {
Loggers.UPDATE_PROCESSOR.debug("Choose Streaming Update Processor");
- firstRetryDelay = firstRetryDelay == null ? DEFAULT_FIRST_RETRY_DURATION : firstRetryDelay;
return new Streaming(dataUpdater, config, firstRetryDelay, maxRetryTimes);
}
}
@@ -146,9 +145,9 @@ public void close() {
static final class InsightProcessBuilderImpl extends InsightProcessorBuilder {
@Override
public DefaultSender createInsightEventSender(Context context) {
- maxRetryTimes = maxRetryTimes < 0 ? DEFAULT_RETRY_TIMES : maxRetryTimes;
- retryIntervalInMilliseconds = retryIntervalInMilliseconds <= 0 ? DEFAULT_RETRY_DELAY : retryIntervalInMilliseconds;
- return new Senders.InsightEventSenderImp(context.http(), maxRetryTimes, Duration.ofMillis(retryIntervalInMilliseconds));
+ return new Senders.InsightEventSenderImp(context.http(),
+ Math.min(maxRetryTimes, 3),
+ Duration.ofMillis(Math.min(retryIntervalInMilliseconds, Duration.ofSeconds(1).toMillis())));
}
@Override
@@ -156,8 +155,8 @@ public InsightProcessor createInsightProcessor(Context context) {
DefaultSender sender = createInsightEventSender(context);
return new Insights.InsightProcessorImpl(context.basicConfig().getEventURI(),
sender,
- Math.max(DEFAULT_FLUSH_INTERVAL, flushInterval),
- Math.max(DEFAULT_CAPACITY, capacity));
+ Math.min(flushIntervalInMilliseconds, Duration.ofSeconds(3).toMillis()),
+ Math.min(capacity, DEFAULT_CAPACITY));
}
}
diff --git a/src/main/java/co/featbit/server/InsightProcessorBuilder.java b/src/main/java/co/featbit/server/InsightProcessorBuilder.java
index 2663ab1..099aa63 100644
--- a/src/main/java/co/featbit/server/InsightProcessorBuilder.java
+++ b/src/main/java/co/featbit/server/InsightProcessorBuilder.java
@@ -34,28 +34,52 @@ public abstract class InsightProcessorBuilder implements InsightEventSenderFacto
protected final static int DEFAULT_RETRY_TIMES = 1;
protected final static long DEFAULT_FLUSH_INTERVAL = Duration.ofSeconds(1).toMillis();
- protected int capacity;
- protected long retryIntervalInMilliseconds;
- protected int maxRetryTimes;
- protected long flushInterval;
+ protected int capacity = DEFAULT_CAPACITY;
+ protected long retryIntervalInMilliseconds = DEFAULT_RETRY_DELAY;
+ protected int maxRetryTimes = DEFAULT_RETRY_TIMES;
+ protected long flushIntervalInMilliseconds = DEFAULT_FLUSH_INTERVAL;
+ /**
+ * the capacity of message inbox which stores temporarily insight messages, default value is 10000
+ *
+ * @param capacityOfInbox
+ * @return InsightProcessorBuilder
+ */
public InsightProcessorBuilder capacity(int capacityOfInbox) {
- this.capacity = capacityOfInbox;
+ this.capacity = (capacityOfInbox < 0) ? DEFAULT_CAPACITY : capacityOfInbox;
return this;
}
- public InsightProcessorBuilder flushInterval(int flushIntervalInSecond) {
- this.flushInterval = (flushIntervalInSecond < 0) ? DEFAULT_FLUSH_INTERVAL : Duration.ofSeconds(flushIntervalInSecond).toMillis();
+ /**
+ * the interval to flush automatically insight messages, the default value is 1 seconds
+ *
+ * @param flushIntervalInMilliseconds
+ * @return
+ */
+ public InsightProcessorBuilder flushInterval(long flushIntervalInMilliseconds) {
+ this.flushIntervalInMilliseconds = (flushIntervalInMilliseconds < 0) ? DEFAULT_FLUSH_INTERVAL : flushIntervalInMilliseconds;
return this;
}
+ /**
+ * retry interval for sending failure, the default value is 0.1 seconds
+ *
+ * @param retryIntervalInMilliseconds
+ * @return
+ */
public InsightProcessorBuilder retryInterval(long retryIntervalInMilliseconds) {
- this.retryIntervalInMilliseconds = retryIntervalInMilliseconds;
+ this.retryIntervalInMilliseconds = (retryIntervalInMilliseconds < 0) ? DEFAULT_RETRY_DELAY : retryIntervalInMilliseconds;
return this;
}
+ /**
+ * max number of retries for sending failure, default value is 1 time
+ *
+ * @param maxRetryTimes
+ * @return
+ */
public InsightProcessorBuilder maxRetryTimes(int maxRetryTimes) {
- this.maxRetryTimes = maxRetryTimes;
+ this.maxRetryTimes = (maxRetryTimes < 0) ? DEFAULT_RETRY_TIMES : maxRetryTimes;
return this;
}
diff --git a/src/main/java/co/featbit/server/Insights.java b/src/main/java/co/featbit/server/Insights.java
index bd31edd..5476c28 100644
--- a/src/main/java/co/featbit/server/Insights.java
+++ b/src/main/java/co/featbit/server/Insights.java
@@ -67,7 +67,7 @@ public void flush() {
@Override
public void close() {
if (closed.compareAndSet(false, true)) {
- Loggers.EVENTS.info("FFC JAVA SDK: insight processor is stopping");
+ Loggers.EVENTS.info("FB JAVA SDK: insight processor is stopping");
Utils.shutDownThreadPool("insight-periodic-flush-worker", flushScheduledExecutor, AWAIT_TERMINATION);
//flush all the left events
putEventAsync(InsightTypes.InsightMessageType.FLUSH, null);
@@ -106,7 +106,7 @@ private boolean putMsgToInbox(InsightTypes.InsightMessage msg) {
// if it reaches here, it means the application is probably doing tons of flag evaluations across many threads.
// So if we wait for a space in the inbox, we risk a very serious slowdown of the app.
// To avoid that, we'll just drop the event or you can increase the capacity of inbox
- Loggers.EVENTS.warn("FFC JAVA SDK: events are being produced faster than they can be processed; some events will be dropped");
+ Loggers.EVENTS.warn("FB JAVA SDK: events are being produced faster than they can be processed; some events will be dropped");
return false;
}
@@ -135,7 +135,7 @@ public Boolean run() {
Loggers.EVENTS.debug("paload size: {}", partition.size());
});
} catch (Exception unexpected) {
- Loggers.EVENTS.error("FFC JAVA SDK: unexpected error in sending payload", unexpected);
+ Loggers.EVENTS.error("FB JAVA SDK: unexpected error in sending payload", unexpected);
return false;
}
return true;
@@ -202,12 +202,12 @@ private void dispatchEvents() {
}
message.completed();
} catch (Exception unexpected) {
- Loggers.EVENTS.error("FFC JAVA SDK: unexpected error in event dispatcher", unexpected);
+ Loggers.EVENTS.error("FB JAVA SDK: unexpected error in event dispatcher", unexpected);
}
}
} catch (InterruptedException ignore) {
} catch (Exception unexpected) {
- Loggers.EVENTS.error("FFC JAVA SDK: unexpected error in event dispatcher", unexpected);
+ Loggers.EVENTS.error("FB JAVA SDK: unexpected error in event dispatcher", unexpected);
}
}
}
@@ -269,7 +269,7 @@ private void shutdown() {
config.getRight().close();
}
} catch (Exception unexpected) {
- Loggers.EVENTS.error("FFC JAVA SDK: unexpected error when closing event dispatcher", unexpected);
+ Loggers.EVENTS.error("FB JAVA SDK: unexpected error when closing event dispatcher", unexpected);
}
}
diff --git a/src/main/java/co/featbit/server/Senders.java b/src/main/java/co/featbit/server/Senders.java
index bced115..257505f 100644
--- a/src/main/java/co/featbit/server/Senders.java
+++ b/src/main/java/co/featbit/server/Senders.java
@@ -105,7 +105,7 @@ public String postJson(String eventUrl, String json) {
break;
}
} catch (Exception ex) {
- Loggers.EVENTS.error("FFC JAVA SDK: events sending error: {}", ex.getMessage());
+ Loggers.EVENTS.error("FB JAVA SDK: events sending error: {}", ex.getMessage());
}
}
return null;
diff --git a/src/main/java/co/featbit/server/Status.java b/src/main/java/co/featbit/server/Status.java
index 6748c4e..9f75424 100644
--- a/src/main/java/co/featbit/server/Status.java
+++ b/src/main/java/co/featbit/server/Status.java
@@ -23,8 +23,6 @@ public abstract class Status {
public static final String UNKNOWN_CLOSE_CODE = "Unknown close code";
public static final String WEBSOCKET_ERROR = "WebSocket error";
- public static final String DATA_SYNC_ERROR = "Data Sync error";
-
/**
* possible values for {@link DataSynchronizer}
*/
@@ -274,7 +272,7 @@ public DataUpdaterImpl(DataStorage storage) {
}
private void handleErrorFromStorage(Exception ex, ErrorTrack errorTrack) {
- Loggers.DATA_STORAGE.error("FFC JAVA SDK: Data Storage error: {}, UpdateProcessor will attempt to receive the data", ex.getMessage());
+ Loggers.DATA_STORAGE.error("FB JAVA SDK: Data Storage error: {}, UpdateProcessor will attempt to receive the data", ex.getMessage());
updateStatus(State.interruptedState(errorTrack));
}
@@ -308,21 +306,16 @@ public void updateStatus(State newState) {
StateType oldStateType = currentState.getStateType();
StateType newStateType = newState.getStateType();
ErrorTrack error = newState.getErrorTrack();
- Instant stateSince;
// interrupted state is only meaningful after initialization
if (newStateType == StateType.INTERRUPTED && oldStateType == StateType.INITIALIZING) {
newStateType = StateType.INITIALIZING;
}
- if (newStateType != oldStateType) {
- stateSince = Instant.now();
- } else if (error != null) {
- stateSince = currentState.getStateSince();
- } else {
- return;
+ if (newStateType != oldStateType || error != null) {
+ Instant stateSince = (newStateType != oldStateType) ? Instant.now() : currentState.getStateSince();
+ currentState = new State(newStateType, stateSince, error);
+ lockObject.notifyAll();
}
- currentState = new State(newStateType, stateSince, error);
- lockObject.notifyAll();
}
}
diff --git a/src/main/java/co/featbit/server/Streaming.java b/src/main/java/co/featbit/server/Streaming.java
index b8d80c1..be7337e 100644
--- a/src/main/java/co/featbit/server/Streaming.java
+++ b/src/main/java/co/featbit/server/Streaming.java
@@ -37,7 +37,6 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
-import static co.featbit.server.Status.DATA_SYNC_ERROR;
import static co.featbit.server.Status.REQUEST_INVALID_ERROR;
import static co.featbit.server.Status.UNKNOWN_CLOSE_CODE;
import static co.featbit.server.Streaming.StreamingOps.isReconnOnClose;
@@ -110,7 +109,7 @@ public boolean isInitialized() {
@Override
public void close() {
- logger.info("FFC JAVA SDK: streaming is stopping...");
+ logger.info("FB JAVA SDK: streaming is stopping...");
if (webSocket != null) {
forceToCloseWS.compareAndSet(false, true);
webSocket.close(NORMAL_CLOSE, NORMAL_CLOSE_REASON);
@@ -138,12 +137,12 @@ private void clearExecutor() {
private void connect() {
if (isWSConnected.get() || forceToCloseWS.get()) {
- logger.error("FFC JAVA SDK: streaming websocket is already Connected or Closed");
+ logger.error("FB JAVA SDK: streaming websocket is already Connected or Closed");
return;
}
int count = connCount.getAndIncrement();
if (count >= maxRetryTimes) {
- logger.error("FFC JAVA SDK: streaming websocket have reached max retry");
+ logger.error("FB JAVA SDK: streaming websocket have reached max retry");
return;
}
@@ -213,10 +212,9 @@ static boolean isReconnOnClose(Status.DataUpdater updater, int code, String reas
message = StringUtils.isEmpty(reason) ? "unexpected close" : reason;
}
logger.debug("Streaming WebSocket close reason: {}", message);
- if (isReconn) {
+ if (isReconn && code != GOING_AWAY_CLOSE) {
// if code is not 1001, it's an unknown close code received by server
- String errorType = (code == GOING_AWAY_CLOSE) ? DATA_SYNC_ERROR : UNKNOWN_CLOSE_CODE;
- updater.updateStatus(Status.State.interruptedState(errorType, message));
+ updater.updateStatus(Status.State.interruptedState(UNKNOWN_CLOSE_CODE, message));
} else if (code == INVALID_REQUEST_CLOSE) {
// authorization error
updater.updateStatus(Status.State.errorOFFState(REQUEST_INVALID_ERROR, message));
@@ -247,10 +245,10 @@ static boolean isReconnOnFailure(Status.DataUpdater updater, Throwable t) {
}
}
if (isReconn) {
- logger.warn("FFC JAVA SDK: streaming webSocket will reconnect because of {}", t.getMessage());
+ logger.warn("FB JAVA SDK: streaming webSocket will reconnect because of {}", t.getMessage());
updater.updateStatus(Status.State.interruptedState(errorType, message));
} else {
- logger.error("FFC JAVA SDK: streaming webSocket Failure", t);
+ logger.error("FB JAVA SDK: streaming webSocket Failure", t);
updater.updateStatus(Status.State.errorOFFState(errorType, message));
}
return isReconn;
diff --git a/src/main/java/co/featbit/server/StreamingBuilder.java b/src/main/java/co/featbit/server/StreamingBuilder.java
index b7b986f..45e241e 100644
--- a/src/main/java/co/featbit/server/StreamingBuilder.java
+++ b/src/main/java/co/featbit/server/StreamingBuilder.java
@@ -17,10 +17,14 @@
* .build();
* FBClient client = new FBClientImp(envSecret, config);
*
+ *
+ * Note that this class is in fact only internal use, it's not recommended to customize any behavior in this configuration.
+ * We just keep the same design pattern in the SDK
*/
public abstract class StreamingBuilder implements DataSynchronizerFactory {
protected static final Duration DEFAULT_FIRST_RETRY_DURATION = Duration.ofSeconds(1);
- protected Duration firstRetryDelay;
+ private static final Duration MAX_RETRY_DURATION = Duration.ofSeconds(60);
+ protected Duration firstRetryDelay = DEFAULT_FIRST_RETRY_DURATION;
protected Integer maxRetryTimes = 0;
/**
@@ -34,8 +38,8 @@ public abstract class StreamingBuilder implements DataSynchronizerFactory {
* @return the builder
*/
public StreamingBuilder firstRetryDelay(Duration duration) {
- this.firstRetryDelay =
- (duration == null || duration.minusSeconds(1).isNegative()) ? DEFAULT_FIRST_RETRY_DURATION : duration;
+ this.firstRetryDelay = (duration == null || duration.minusSeconds(1).isNegative() || MAX_RETRY_DURATION.minus(duration).isNegative())
+ ? DEFAULT_FIRST_RETRY_DURATION : duration;
return this;
}
@@ -45,8 +49,8 @@ public StreamingBuilder firstRetryDelay(Duration duration) {
* @param maxRetryTimes an int value if less than or equals to 0, use the default
* @return the builder
*/
- public StreamingBuilder maxRetryTimes(Integer maxRetryTimes) {
- this.maxRetryTimes = maxRetryTimes;
+ public StreamingBuilder maxRetryTimes(int maxRetryTimes) {
+ this.maxRetryTimes = (maxRetryTimes <= 0) ? Integer.MAX_VALUE : maxRetryTimes;
return this;
}
}
diff --git a/src/main/java/co/featbit/server/exterior/DataStoreTypes.java b/src/main/java/co/featbit/server/exterior/DataStoreTypes.java
index 3101f1a..39eb824 100644
--- a/src/main/java/co/featbit/server/exterior/DataStoreTypes.java
+++ b/src/main/java/co/featbit/server/exterior/DataStoreTypes.java
@@ -35,8 +35,7 @@ public abstract class DataStoreTypes {
* Applications should not need to reference this object directly. It is public so that custom data storage
* implementations can determine what kinds of model objects may need to be stored.
*/
-
- public final List FFC_ALL_CATS = ImmutableList.of(FEATURES, SEGMENTS, DATATEST);
+ public final List ALL_CATS = ImmutableList.of(FEATURES, SEGMENTS, DATATEST);
private DataStoreTypes() {
}
diff --git a/src/main/java/co/featbit/server/exterior/FBClient.java b/src/main/java/co/featbit/server/exterior/FBClient.java
index 34aafd5..d4c3687 100644
--- a/src/main/java/co/featbit/server/exterior/FBClient.java
+++ b/src/main/java/co/featbit/server/exterior/FBClient.java
@@ -145,6 +145,7 @@ public interface FBClient extends Closeable {
/**
* Calculates the value of a feature flag for a given user, and returns an object that describes the
* way the value was determined.
+ * Note that this method does not cast the result of flag evaluation, any flag value is a string type
*
*
* @param featureFlagKey the unique key for the feature flag
diff --git a/src/main/java/co/featbit/server/exterior/HttpConfigurationBuilder.java b/src/main/java/co/featbit/server/exterior/HttpConfigurationBuilder.java
index f9c326f..9c90d92 100644
--- a/src/main/java/co/featbit/server/exterior/HttpConfigurationBuilder.java
+++ b/src/main/java/co/featbit/server/exterior/HttpConfigurationBuilder.java
@@ -39,7 +39,7 @@ public abstract class HttpConfigurationBuilder implements HttpConfigFactory {
/**
* Sets the connection timeout. This is the time allowed for the SDK to make a socket connection to
- * any of API. The default value is 10s
+ * any of API. The default value is 5s
*
* @param duration the connection timeout; null to use the default
* @return the builder
@@ -51,7 +51,7 @@ public HttpConfigurationBuilder connectTime(Duration duration) {
/**
* Sets the read and write timeout. This is the time allowed for the SDK to read/write
- * any of API. The default value is 15s
+ * any of API. The default value is 10s
*
* @param duration the read/write timeout; null to use the default
* @return the builder
diff --git a/src/test/java/co/featbit/server/StreamingOpsTest.java b/src/test/java/co/featbit/server/StreamingOpsTest.java
index 71b1306..cb4791b 100644
--- a/src/test/java/co/featbit/server/StreamingOpsTest.java
+++ b/src/test/java/co/featbit/server/StreamingOpsTest.java
@@ -16,6 +16,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
import static co.featbit.server.Status.DATA_INVALID_ERROR;
+import static co.featbit.server.Status.DATA_STORAGE_INIT_ERROR;
import static co.featbit.server.Status.NETWORK_ERROR;
import static co.featbit.server.Status.RUNTIME_ERROR;
import static co.featbit.server.Status.StateType.INITIALIZING;
@@ -103,6 +104,7 @@ void testProcessDataThrowException() throws Exception {
assertFalse(initialized.get());
assertFalse(dataUpdaterImpl.storageInitialized());
assertEquals(INITIALIZING, dataUpdaterImpl.getCurrentState().getStateType());
+ assertEquals(DATA_STORAGE_INIT_ERROR, dataUpdaterImpl.getCurrentState().getErrorTrack().getErrorType());
support.verifyAll();
}
@@ -144,18 +146,7 @@ void testStreamingOnInvalidRequest() {
void testStreamingOnDataSyncError() {
int code = 1001;
String reason = "data sync error";
- final List