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.featbit Featbit-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.featbit featbit-java-sdk - 1.0.3 + 1.0.4 featbit/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 arguments = new ArrayList<>(); - dataUpdaterMock.updateStatus(anyObject(Status.State.class)); - expectLastCall().andAnswer(() -> { - arguments.addAll(Arrays.asList(getCurrentArguments())); - return null; - }); - support.replayAll(); assertTrue(isReconnOnClose(dataUpdaterMock, code, reason)); - Status.State state = (Status.State) (arguments.get(0)); - assertEquals(INTERRUPTED, state.getStateType()); - assertEquals(reason, state.getErrorTrack().getMessage()); - support.verifyAll(); } @Test