Skip to content

Commit 96eb952

Browse files
authored
Feature/v1.4.0 (#14)
1 parent 552adc3 commit 96eb952

19 files changed

+113
-131
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>co.featbit</groupId>
88
<artifactId>featbit-java-sdk</artifactId>
9-
<version>1.3.0</version>
9+
<version>1.4.0</version>
1010

1111
<name>featbit/featbit-java-sdk</name>
1212

src/main/java/co/featbit/commons/model/EvalDetail.java

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,45 +16,84 @@
1616
*/
1717
public final class EvalDetail<T> implements Serializable {
1818
private final T variation;
19-
19+
private final boolean isDefault;
2020
private final String reason;
21-
2221
private final String name;
23-
2422
private final String keyName;
2523

26-
private EvalDetail(T variation,
27-
String reason,
28-
String keyName,
29-
String name) {
24+
EvalDetail(T variation,
25+
boolean isDefault,
26+
String reason,
27+
String keyName,
28+
String name) {
3029
this.variation = variation;
30+
this.isDefault = isDefault;
3131
this.reason = reason;
3232
this.keyName = keyName;
3333
this.name = name;
3434
}
3535

3636
/**
37-
* build method, this method is only for internal use
37+
* build an instance
38+
*
39+
* @param variation the result of flag value
40+
* @param reason main factor that influenced the flag evaluation value
41+
* @param keyName key name of the flag
42+
* @param name name of the flag
43+
* @param <T> String/Boolean/Numeric Type
44+
* @return an EvalDetail
45+
*/
46+
public static <T> EvalDetail<T> of(T variation,
47+
String reason,
48+
String keyName,
49+
String name) {
50+
return EvalDetail.of(variation, false, reason, keyName, name);
51+
}
52+
53+
/**
54+
* build an instance from anthor EvalDetail
3855
*
3956
* @param variation the result of flag value
57+
* @param another another EvalDetail
58+
* @param <T> String/Boolean/Numeric Type
59+
* @param <S> String/Boolean/Numeric Type
60+
* @return an EvalDetail
61+
*/
62+
public static <T, S> EvalDetail<T> of(T variation, EvalDetail<S> another) {
63+
return EvalDetail.of(variation, another.isDefault, another.reason, another.keyName, another.name);
64+
}
65+
66+
/**
67+
* build an instance
68+
*
69+
* @param variation the result of flag value
70+
* @param isDefault true if the flag value is the default value
4071
* @param reason main factor that influenced the flag evaluation value
4172
* @param keyName key name of the flag
4273
* @param name name of the flag
4374
* @param <T> String/Boolean/Numeric Type
4475
* @return an EvalDetail
4576
*/
4677
public static <T> EvalDetail<T> of(T variation,
78+
boolean isDefault,
4779
String reason,
4880
String keyName,
4981
String name) {
50-
return new EvalDetail<>(variation, reason, keyName, name);
82+
return new EvalDetail<>(variation, isDefault, reason, keyName, name);
83+
}
84+
85+
/**
86+
* @return true if the flag value is the default value
87+
*/
88+
public boolean isDefaultVariation() {
89+
return isDefault;
5190
}
5291

5392
/**
5493
* build the method from a json string, this method is only for internal use
5594
*
5695
* @param json json string of an EvalDetail
57-
* @param cls raw type of flag value
96+
* @param cls raw type of flag value
5897
* @param <T> String/Boolean/Numeric Type
5998
* @return an EvalDetail
6099
*/
@@ -114,18 +153,19 @@ public boolean equals(Object o) {
114153
if (this == o) return true;
115154
if (o == null || getClass() != o.getClass()) return false;
116155
EvalDetail<?> that = (EvalDetail<?>) o;
117-
return Objects.equals(variation, that.variation) && Objects.equals(reason, that.reason) && Objects.equals(name, that.name) && Objects.equals(keyName, that.keyName);
156+
return isDefault == that.isDefault && Objects.equals(variation, that.variation) && Objects.equals(reason, that.reason) && Objects.equals(name, that.name) && Objects.equals(keyName, that.keyName);
118157
}
119158

120159
@Override
121160
public int hashCode() {
122-
return Objects.hash(variation, reason, name, keyName);
161+
return Objects.hash(variation, isDefault, reason, name, keyName);
123162
}
124163

125164
@Override
126165
public String toString() {
127166
return MoreObjects.toStringHelper(this)
128167
.add("variation", variation)
168+
.add("isDefault", isDefault)
129169
.add("reason", reason)
130170
.add("name", name)
131171
.add("keyName", keyName)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package co.featbit.server;
2+
3+
public interface EvaluationReason {
4+
String REASON_USER_NOT_SPECIFIED = "user not specified";
5+
String REASON_FLAG_OFF = "flag off";
6+
String REASON_TARGET_MATCH = "target match";
7+
String REASON_RULE_MATCH = "rule match";
8+
String REASON_FALLTHROUGH = "fall through all rules";
9+
String REASON_CLIENT_NOT_READY = "client not ready";
10+
String REASON_FLAG_NOT_FOUND = "flag not found";
11+
String REASON_WRONG_TYPE = "wrong type";
12+
String REASON_ERROR = "error in evaluation";
13+
}

src/main/java/co/featbit/server/Evaluator.java

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,13 @@
99
* Evaluation process is totally isolated from update process and data storage
1010
*/
1111

12-
abstract class Evaluator {
12+
abstract class Evaluator implements EvaluationReason {
1313

1414
protected static final Logger logger = Loggers.EVALUATION;
1515

1616
protected static final String EXPT_KEY_PREFIX = "expt";
1717
protected static final String NO_EVAL_RES = "NE";
1818
protected static final String DEFAULT_JSON_VALUE = "DJV";
19-
protected static final String REASON_USER_NOT_SPECIFIED = "user not specified";
20-
protected static final String REASON_FLAG_OFF = "flag off";
21-
protected static final String REASON_TARGET_MATCH = "target match";
22-
protected static final String REASON_RULE_MATCH = "rule match";
23-
protected static final String REASON_FALLTHROUGH = "fall through all rules";
24-
protected static final String REASON_CLIENT_NOT_READY = "client not ready";
25-
protected static final String REASON_FLAG_NOT_FOUND = "flag not found";
26-
protected static final String REASON_WRONG_TYPE = "wrong type";
27-
protected static final String REASON_ERROR = "error in evaluation";
2819
protected static final String FLAG_KEY_UNKNOWN = "flag key unknown";
2920
protected static final String FLAG_NAME_UNKNOWN = "flag name unknown";
3021
protected static final String FLAG_VALUE_UNKNOWN = "flag value unknown";
@@ -144,12 +135,12 @@ public String getName() {
144135
return name;
145136
}
146137

147-
private boolean isDefaultValue() {
138+
public boolean isDefaultValue() {
148139
return this.index.equals(NO_EVAL_RES);
149140
}
150141

151142
public <T> EvalDetail<T> toEvalDetail(T value) {
152-
return EvalDetail.of(value, this.reason, this.keyName, this.name);
143+
return EvalDetail.of(value, isDefaultValue(), this.reason, this.keyName, this.name);
153144
}
154145
}
155146

src/main/java/co/featbit/server/EventBroadcasterImpl.java

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,24 @@ public void broadcast(Event event) {
5353
return;
5454
}
5555
for (Listener listener : listeners) {
56-
executor.submit(() -> {
57-
try {
58-
broadcaster.accept(listener, event);
59-
} catch (Exception e) {
60-
logger.error("Unexpected exception in event listener", e);
61-
}
62-
});
56+
if (executor.isTerminated() || executor.isShutdown()) {
57+
_broadcast(listener, event);
58+
} else {
59+
executor.submit(() -> {
60+
_broadcast(listener, event);
61+
});
62+
}
6363
}
6464
}
6565

66+
private void _broadcast(Listener listener, Event event) {
67+
try {
68+
broadcaster.accept(listener, event);
69+
} catch (Exception e) {
70+
logger.error("Unexpected exception in event listener", e);
71+
}
72+
73+
}
74+
6675

6776
}

src/main/java/co/featbit/server/FBClientImp.java

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,8 @@ public final class FBClientImp implements FBClient {
3333
private final Status.DataUpdateStatusProvider dataUpdateStatusProvider;
3434
private final Status.DataUpdater dataUpdater;
3535
private final InsightProcessor insightProcessor;
36-
37-
private final ExecutorService sharedExecutorService;
38-
36+
private final ThreadPoolExecutor sharedExecutorService;
3937
private final Consumer<InsightTypes.Event> eventHandler;
40-
4138
private final FlagTracker flagTracker;
4239

4340
/**
@@ -169,54 +166,46 @@ public EvalDetail<String> variationDetail(String featureFlagKey, FBUser user, St
169166

170167
@Override
171168
public boolean boolVariation(String featureFlagKey, FBUser user, Boolean defaultValue) {
172-
checkNotNull(defaultValue, "null defaultValue is invalid");
173169
Evaluator.EvalResult res = evaluateInternal(featureFlagKey, user, defaultValue, Boolean.class);
174170
return BooleanUtils.toBoolean(res.getValue());
175171
}
176172

177173
@Override
178174
public EvalDetail<Boolean> boolVariationDetail(String featureFlagKey, FBUser user, Boolean defaultValue) {
179-
checkNotNull(defaultValue, "null defaultValue is invalid");
180175
Evaluator.EvalResult res = evaluateInternal(featureFlagKey, user, defaultValue, Boolean.class);
181176
return res.toEvalDetail(BooleanUtils.toBoolean(res.getValue()));
182177
}
183178

184179
public double doubleVariation(String featureFlagKey, FBUser user, Double defaultValue) {
185-
checkNotNull(defaultValue, "null defaultValue is invalid");
186180
Evaluator.EvalResult res = evaluateInternal(featureFlagKey, user, defaultValue, Double.class);
187181
return Double.parseDouble(res.getValue());
188182
}
189183

190184

191185
@Override
192186
public EvalDetail<Double> doubleVariationDetail(String featureFlagKey, FBUser user, Double defaultValue) {
193-
checkNotNull(defaultValue, "null defaultValue is invalid");
194187
Evaluator.EvalResult res = evaluateInternal(featureFlagKey, user, defaultValue, Double.class);
195188
return res.toEvalDetail(Double.parseDouble(res.getValue()));
196189
}
197190

198191
public int intVariation(String featureFlagKey, FBUser user, Integer defaultValue) {
199-
checkNotNull(defaultValue, "null defaultValue is invalid");
200192
Evaluator.EvalResult res = evaluateInternal(featureFlagKey, user, defaultValue, Integer.class);
201193
return Double.valueOf(res.getValue()).intValue();
202194
}
203195

204196
@Override
205197
public EvalDetail<Integer> intVariationDetail(String featureFlagKey, FBUser user, Integer defaultValue) {
206-
checkNotNull(defaultValue, "null defaultValue is invalid");
207198
Evaluator.EvalResult res = evaluateInternal(featureFlagKey, user, defaultValue, Integer.class);
208199
return res.toEvalDetail(Double.valueOf(res.getValue()).intValue());
209200
}
210201

211202
public long longVariation(String featureFlagKey, FBUser user, Long defaultValue) {
212-
checkNotNull(defaultValue, "null defaultValue is invalid");
213203
Evaluator.EvalResult res = evaluateInternal(featureFlagKey, user, defaultValue, Long.class);
214204
return Double.valueOf(res.getValue()).longValue();
215205
}
216206

217207
@Override
218208
public EvalDetail<Long> longVariationDetail(String featureFlagKey, FBUser user, Long defaultValue) {
219-
checkNotNull(defaultValue, "null defaultValue is invalid");
220209
Evaluator.EvalResult res = evaluateInternal(featureFlagKey, user, defaultValue, Long.class);
221210
return res.toEvalDetail(Double.valueOf(res.getValue()).longValue());
222211
}
@@ -235,36 +224,37 @@ public <T> EvalDetail<T> jsonVariationDetail(String featureFlagKey, FBUser user,
235224
}
236225

237226
private Evaluator.EvalResult evaluateInternal(String featureFlagKey, FBUser user, Object defaultValue, Class<?> requiredType) {
227+
String dv = defaultValue == null ? null : defaultValue.toString();
238228
try {
239229
if (!isInitialized()) {
240230
Loggers.EVALUATION.warn("FB JAVA SDK: evaluation is called before Java SDK client is initialized for feature flag, well using the default value");
241-
return Evaluator.EvalResult.error(defaultValue.toString(), REASON_CLIENT_NOT_READY, featureFlagKey, FLAG_NAME_UNKNOWN);
231+
return Evaluator.EvalResult.error(dv, REASON_CLIENT_NOT_READY, featureFlagKey, FLAG_NAME_UNKNOWN);
242232
}
243233
if (StringUtils.isBlank(featureFlagKey)) {
244234
Loggers.EVALUATION.warn("FB JAVA SDK: null feature flag key; returning default value");
245-
return Evaluator.EvalResult.error(defaultValue.toString(), REASON_FLAG_NOT_FOUND, featureFlagKey, FLAG_NAME_UNKNOWN);
235+
return Evaluator.EvalResult.error(dv, REASON_FLAG_NOT_FOUND, featureFlagKey, FLAG_NAME_UNKNOWN);
246236
}
247237
DataModel.FeatureFlag flag = getFlagInternal(featureFlagKey);
248238
if (flag == null) {
249239
Loggers.EVALUATION.warn("FB JAVA SDK: unknown feature flag {}; returning default value", featureFlagKey);
250-
return Evaluator.EvalResult.error(defaultValue.toString(), REASON_FLAG_NOT_FOUND, featureFlagKey, FLAG_NAME_UNKNOWN);
240+
return Evaluator.EvalResult.error(dv, REASON_FLAG_NOT_FOUND, featureFlagKey, FLAG_NAME_UNKNOWN);
251241
}
252242
if (user == null || StringUtils.isBlank(user.getKey())) {
253243
Loggers.EVALUATION.warn("FB JAVA SDK: null user for feature flag {}, returning default value", featureFlagKey);
254-
return Evaluator.EvalResult.error(defaultValue.toString(), REASON_USER_NOT_SPECIFIED, featureFlagKey, FLAG_NAME_UNKNOWN);
244+
return Evaluator.EvalResult.error(dv, REASON_USER_NOT_SPECIFIED, featureFlagKey, FLAG_NAME_UNKNOWN);
255245
}
256246

257247
InsightTypes.Event event = InsightTypes.FlagEvent.of(user);
258248
Evaluator.EvalResult res = evaluator.evaluate(flag, user, event);
259249
if (requiredType != null && !Utils.checkType(flag.getVariationType(), requiredType, res.getValue())) {
260250
Loggers.EVALUATION.warn("FB JAVA SDK: evaluation result {} didn't matched expected type {}", res.getValue(), requiredType);
261-
return Evaluator.EvalResult.error(defaultValue.toString(), REASON_WRONG_TYPE, res.getKeyName(), res.getName());
251+
return Evaluator.EvalResult.error(dv, REASON_WRONG_TYPE, res.getKeyName(), res.getName());
262252
}
263253
eventHandler.accept(event);
264254
return res;
265255
} catch (Exception ex) {
266256
logger.error("FB JAVA SDK: unexpected error in evaluation", ex);
267-
return Evaluator.EvalResult.error(defaultValue.toString(), REASON_ERROR, featureFlagKey, FLAG_NAME_UNKNOWN);
257+
return Evaluator.EvalResult.error(dv, REASON_ERROR, featureFlagKey, FLAG_NAME_UNKNOWN);
268258
}
269259

270260
}
@@ -294,7 +284,7 @@ public void close() throws IOException {
294284
this.storage.close();
295285
this.dataSynchronizer.close();
296286
this.insightProcessor.close();
297-
this.sharedExecutorService.shutdownNow();
287+
Utils.shutDownThreadPool("featbit-shared-worker", this.sharedExecutorService, Duration.ofSeconds(2));
298288
}
299289

300290
@Override

src/main/java/co/featbit/server/FBConfig.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
package co.featbit.server;
22

3-
import co.featbit.server.exterior.DataStorageFactory;
4-
import co.featbit.server.exterior.DataSynchronizer;
5-
import co.featbit.server.exterior.DataSynchronizerFactory;
6-
import co.featbit.server.exterior.FBClient;
7-
import co.featbit.server.exterior.HttpConfigFactory;
8-
import co.featbit.server.exterior.InsightProcessorFactory;
3+
import co.featbit.server.exterior.*;
94

105
import java.time.Duration;
116

src/main/java/co/featbit/server/FactoryImp.java

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,6 @@
11
package co.featbit.server;
22

3-
import co.featbit.server.exterior.BasicConfig;
4-
import co.featbit.server.exterior.Context;
5-
import co.featbit.server.exterior.DataStorage;
6-
import co.featbit.server.exterior.DataStorageFactory;
7-
import co.featbit.server.exterior.DataStorageTypes;
8-
import co.featbit.server.exterior.DataSynchronizer;
9-
import co.featbit.server.exterior.DataSynchronizerFactory;
10-
import co.featbit.server.exterior.DefaultSender;
11-
import co.featbit.server.exterior.HttpConfig;
12-
import co.featbit.server.exterior.HttpConfigurationBuilder;
13-
import co.featbit.server.exterior.InsightProcessor;
14-
import co.featbit.server.exterior.InsightProcessorFactory;
3+
import co.featbit.server.exterior.*;
154
import com.google.common.collect.ImmutableMap;
165

176
import java.time.Duration;

src/main/java/co/featbit/server/Implicits.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
package co.featbit.server;
22

3-
import co.featbit.commons.json.JsonHelper;
4-
import co.featbit.commons.json.JsonParseException;
53
import co.featbit.commons.model.AllFlagStates;
64
import co.featbit.commons.model.EvalDetail;
75
import com.google.common.collect.ImmutableMap;
86
import org.apache.commons.lang3.BooleanUtils;
97

108
import java.util.AbstractMap;
11-
import java.util.ArrayList;
129
import java.util.Map;
1310
import java.util.function.Consumer;
1411

src/main/java/co/featbit/server/InsightTypes.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
package co.featbit.server;
22

33
import co.featbit.commons.model.FBUser;
4-
import com.google.gson.JsonArray;
5-
import com.google.gson.JsonElement;
6-
import com.google.gson.JsonObject;
7-
import com.google.gson.JsonSerializationContext;
8-
import com.google.gson.JsonSerializer;
4+
import com.google.gson.*;
95
import com.google.gson.annotations.JsonAdapter;
106

117
import java.lang.reflect.Type;

0 commit comments

Comments
 (0)