Skip to content

Commit f8794cf

Browse files
committed
wip: java custom strategies evaluator, remove need for jackson
1 parent 30f4366 commit f8794cf

27 files changed

+124
-146
lines changed

java-engine/build.gradle.kts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
plugins {
2+
java
23
`java-library`
34
`maven-publish`
45
signing
@@ -30,16 +31,17 @@ dependencies {
3031
testImplementation("org.junit.platform:junit-platform-launcher")
3132
testImplementation("org.junit.jupiter:junit-jupiter-engine")
3233
testImplementation("org.junit.jupiter:junit-jupiter-params")
34+
testImplementation(libs.assert4j.core)
3335
testImplementation(libs.mockito.core)
3436
testImplementation(libs.slf4j.simple)
3537
implementation(libs.slf4j.api)
3638
implementation(libs.jna)
37-
implementation(libs.jackson.core)
38-
implementation(libs.jackson.databind)
39-
implementation(libs.jackson.jsr310)
4039
implementation(libs.flatbuffers)
4140
}
4241

42+
tasks.withType<Javadoc> {
43+
exclude("io/getunleash/messaging/**")
44+
}
4345
tasks.jar {
4446
manifest {
4547
attributes(

java-engine/gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[versions]
2+
assertj = "3.27.6"
23
flatbuffers = "25.2.10"
34
jackson = "2.20.0"
45
jmh = "1.37"
@@ -19,3 +20,4 @@ junit-bom = { group = "org.junit", name = "junit-bom", version.ref = "junit" }
1920
mockito-core = { group = "org.mockito", name = "mockito-core", version.ref = "mockito" }
2021
slf4j-api = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" }
2122
slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" }
23+
assert4j-core = { group = "org.assertj", name = "assertj-core", version.ref = "assertj" }

java-engine/src/main/java/io/getunleash/engine/CustomStrategiesEvaluator.java

Lines changed: 52 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@
66
import java.util.*;
77
import java.util.stream.Collectors;
88
import java.util.stream.Stream;
9+
10+
import io.getunleash.messaging.StrategyDefinition;
11+
import io.getunleash.messaging.StrategyFeature;
12+
import io.getunleash.messaging.StrategyParameter;
13+
import io.getunleash.messaging.TakeStateResponse;
914
import org.slf4j.Logger;
1015
import org.slf4j.LoggerFactory;
1116

1217
class CustomStrategiesEvaluator {
18+
1319
private static final Logger log = LoggerFactory.getLogger(CustomStrategiesEvaluator.class);
1420
static final Map<String, Boolean> EMPTY_STRATEGY_RESULTS = new HashMap<>();
1521
private final Map<String, IStrategy> registeredStrategies;
@@ -34,68 +40,75 @@ public CustomStrategiesEvaluator(
3440
this.fallbackStrategy = fallbackStrategy;
3541
}
3642

37-
public void loadStrategiesFor(String toggles) {
43+
public void loadStrategiesFor(TakeStateResponse response) {
3844
if (this.registeredStrategies.isEmpty() && this.fallbackStrategy == null) {
3945
return;
4046
}
4147

42-
if (toggles == null || toggles.isEmpty()) {
48+
if (response.featuresVector() == null || response.featuresLength() == 0) {
4349
return;
4450
}
45-
46-
try {
47-
VersionedFeatures wrapper =
48-
mapper.readValue(toggles, new TypeReference<VersionedFeatures>() {});
49-
if (wrapper.features != null) {
50-
featureStrategies =
51-
wrapper.features.stream()
52-
.collect(toMap(feature -> feature.name, this::getFeatureStrategies));
51+
Map<String, List<MappedStrategy>> featureStrategies = new HashMap<>();
52+
for (int i = 0; i < response.featuresLength(); i++) {
53+
StrategyFeature feature = response.features(i);
54+
String featureName = feature.featureName();
55+
if (feature.strategiesLength() == 0) {
56+
List<MappedStrategy> mappedStrategies = new ArrayList<>();
57+
for (int j = 0; j < feature.strategiesLength(); j++) {
58+
var strategy = feature.strategies(j);
59+
if (builtinStrategies.contains(strategy.name())) {
60+
continue;
61+
}
62+
featureStrategies.put(featureName, getFeatureStrategies(feature));
63+
}
5364
}
54-
} catch (JsonProcessingException e) {
55-
log.warn(
56-
"Error processing features. This means custom strategies will return false every time they're used",
57-
e);
5865
}
66+
this.featureStrategies = featureStrategies;
5967
}
6068

61-
List<MappedStrategy> getFeatureStrategies(FeatureDefinition feature) {
69+
List<MappedStrategy> getFeatureStrategies(StrategyFeature feature) {
6270
List<MappedStrategy> mappedStrategies = new ArrayList<>();
6371
int index = 1;
64-
for (StrategyDefinition strategyDefinition : feature.strategies) {
65-
if (builtinStrategies.contains(strategyDefinition.name)) {
66-
continue;
72+
if (feature.strategiesLength() > 0) {
73+
for (int i = 0; i < feature.strategiesLength(); i++) {
74+
io.getunleash.messaging.StrategyDefinition strategy = feature.strategies(i);
75+
if (builtinStrategies.contains(strategy.name())) {
76+
continue;
77+
}
78+
IStrategy impl =
79+
Optional.ofNullable(registeredStrategies.get(strategy.name()))
80+
.orElseGet(() -> alwaysFalseStrategy(strategy.name()));
81+
StrategyDefinition def =
82+
new StrategyDefinition(strategy.name(), getStrategyParameters(strategy));
83+
mappedStrategies.add(new MappedStrategy("customStrategy" + (index++), impl, def));
6784
}
68-
IStrategy impl =
69-
Optional.ofNullable(registeredStrategies.get(strategyDefinition.name))
70-
.orElseGet(() -> alwaysFalseStrategy(strategyDefinition.name));
71-
mappedStrategies.add(
72-
new MappedStrategy("customStrategy" + (index++), impl, strategyDefinition));
73-
}
74-
if (fallbackStrategy != null) {
75-
mappedStrategies.add(
76-
new MappedStrategy(
77-
"customStrategy" + index,
78-
fallbackStrategy,
79-
new StrategyDefinition("fallback", Collections.emptyMap())));
8085
}
8186
return mappedStrategies;
8287
}
8388

89+
Map<String, String> getStrategyParameters(io.getunleash.messaging.StrategyDefinition strategy) {
90+
Map<String, String> parameters = new HashMap<>();
91+
if (strategy.parametersLength() > 0) {
92+
for (int i = 0; i < strategy.parametersLength(); i++) {
93+
StrategyParameter parameter = strategy.parameters(i);
94+
parameters.put(parameter.key(), parameter.value());
95+
}
96+
}
97+
return parameters;
98+
}
99+
84100
public Map<String, Boolean> eval(String name, Context context) {
85101

86102
List<MappedStrategy> mappedStrategies = featureStrategies.get(name);
87103
if (mappedStrategies == null || mappedStrategies.isEmpty()) {
88104
return Collections.emptyMap();
89105
}
90106

91-
Map<String, Boolean> results =
92-
mappedStrategies.stream()
93-
.collect(
94-
Collectors.toMap(
95-
mappedStrategy -> mappedStrategy.resultName,
96-
mappedStrategy -> tryIsEnabled(context, mappedStrategy).orElse(false)));
97-
98-
return results;
107+
return mappedStrategies.stream()
108+
.collect(
109+
Collectors.toMap(
110+
mappedStrategy -> mappedStrategy.resultName,
111+
mappedStrategy -> tryIsEnabled(context, mappedStrategy).orElse(false)));
99112
}
100113

101114
private static Optional<Boolean> tryIsEnabled(Context context, MappedStrategy mappedStrategy) {
@@ -109,36 +122,11 @@ private static Optional<Boolean> tryIsEnabled(Context context, MappedStrategy ma
109122
}
110123
}
111124

112-
private static class VersionedFeatures {
113-
private final List<FeatureDefinition> features;
114-
115-
@JsonCreator
116-
private VersionedFeatures(@JsonProperty("features") List<FeatureDefinition> features) {
117-
this.features = features;
118-
}
119-
}
120-
121-
static class FeatureDefinition {
122-
private final String name;
123-
private final List<StrategyDefinition> strategies;
124-
125-
@JsonCreator
126-
FeatureDefinition(
127-
@JsonProperty("name") String name,
128-
@JsonProperty("strategies") List<StrategyDefinition> strategies) {
129-
this.name = name;
130-
this.strategies = strategies;
131-
}
132-
}
133-
134125
static class StrategyDefinition {
135126
private final String name;
136127
private final Map<String, String> parameters;
137128

138-
@JsonCreator
139-
StrategyDefinition(
140-
@JsonProperty("name") String name,
141-
@JsonProperty("parameters") Map<String, String> parameters) {
129+
StrategyDefinition(String name, Map<String, String> parameters) {
142130
this.name = name;
143131
this.parameters = parameters;
144132
}

java-engine/src/main/java/io/getunleash/engine/FlatInterface.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ public void freeEngine() {
2525

2626
@Override
2727
public TakeStateResponse takeState(String toggles) {
28-
UnleashFFI.Buf.ByValue buf = this.unleashFFI.takeState(enginePointer, toUtf8Pointer(toggles));
28+
UnleashFFI.Buf.ByValue buf = this.unleashFFI.flatTakeState(enginePointer, toUtf8Pointer(toggles));
2929
long len = buf.len.longValue();
3030
byte[] out = buf.ptr.getByteArray(0, (int) len);
31-
TakeStateResponse
31+
this.unleashFFI.flatBufFree(buf);
32+
return TakeStateResponse.getRootAsTakeStateResponse(ByteBuffer.wrap(out));
3233
}
3334

3435
@Override

java-engine/src/main/java/io/getunleash/engine/UnleashEngine.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ public UnleashEngine(List<IStrategy> customStrategies, IStrategy fallbackStrateg
4141
List<String> builtInStrategies = getBuiltInStrategies();
4242
this.customStrategiesEvaluator =
4343
new CustomStrategiesEvaluator(
44-
customStrategies.stream(), fallbackStrategy, new HashSet<String>(builtInStrategies));
44+
customStrategies.stream(), fallbackStrategy, new HashSet<>(builtInStrategies));
4545
} else {
4646
this.customStrategiesEvaluator =
47-
new CustomStrategiesEvaluator(Stream.empty(), fallbackStrategy, new HashSet<String>());
47+
new CustomStrategiesEvaluator(Stream.empty(), fallbackStrategy, new HashSet<>());
4848
}
4949

5050
cleaner.register(this, nativeEngine::freeEngine);
@@ -161,8 +161,8 @@ private static ByteBuffer buildMessage(
161161

162162
public void takeState(String clientFeatures) throws YggdrasilInvalidInputException {
163163
try {
164-
customStrategiesEvaluator.loadStrategiesFor(clientFeatures);
165-
this.nativeEngine.takeState(clientFeatures);
164+
TakeStateResponse takeStateResponse = this.nativeEngine.takeState(clientFeatures);
165+
customStrategiesEvaluator.loadStrategiesFor(takeStateResponse);
166166
} catch (RuntimeException e) {
167167
throw new YggdrasilInvalidInputException("Failed to take state:", e);
168168
}
@@ -235,7 +235,7 @@ public List<String> getBuiltInStrategies() {
235235
}
236236
return builtInStrategiesNames;
237237
} else {
238-
return List.of();
238+
return Collections.emptyList();
239239
}
240240
}
241241

java-engine/src/main/java/io/getunleash/engine/UnleashFFI.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public interface UnleashFFI extends Library {
1414

1515
void freeEngine(Pointer enginePointer);
1616

17-
UnleashFFI.Buf.ByValue takeState(Pointer enginePointer, Pointer toggles);
17+
UnleashFFI.Buf.ByValue flatTakeState(Pointer enginePointer, Pointer toggles);
1818

1919
Pointer getState(Pointer enginePointer);
2020

java-engine/src/main/java/io/getunleash/messaging/BuiltInStrategies.java

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

2222
@SuppressWarnings("unused")
2323
public final class BuiltInStrategies extends Table {
24-
public static void ValidateVersion() { Constants.FLATBUFFERS_25_9_23(); }
24+
public static void ValidateVersion() { Constants.FLATBUFFERS_25_2_10(); }
2525
public static BuiltInStrategies getRootAsBuiltInStrategies(ByteBuffer _bb) { return getRootAsBuiltInStrategies(_bb, new BuiltInStrategies()); }
2626
public static BuiltInStrategies getRootAsBuiltInStrategies(ByteBuffer _bb, BuiltInStrategies obj) { _bb.order(ByteOrder.LITTLE_ENDIAN); return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb)); }
2727
public void __init(int _i, ByteBuffer _bb) { __reset(_i, _bb); }

java-engine/src/main/java/io/getunleash/messaging/ContextMessage.java

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

2222
@SuppressWarnings("unused")
2323
public final class ContextMessage extends Table {
24-
public static void ValidateVersion() { Constants.FLATBUFFERS_25_9_23(); }
24+
public static void ValidateVersion() { Constants.FLATBUFFERS_25_2_10(); }
2525
public static ContextMessage getRootAsContextMessage(ByteBuffer _bb) { return getRootAsContextMessage(_bb, new ContextMessage()); }
2626
public static ContextMessage getRootAsContextMessage(ByteBuffer _bb, ContextMessage obj) { _bb.order(ByteOrder.LITTLE_ENDIAN); return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb)); }
2727
public void __init(int _i, ByteBuffer _bb) { __reset(_i, _bb); }

java-engine/src/main/java/io/getunleash/messaging/CoreVersion.java

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

2222
@SuppressWarnings("unused")
2323
public final class CoreVersion extends Table {
24-
public static void ValidateVersion() { Constants.FLATBUFFERS_25_9_23(); }
24+
public static void ValidateVersion() { Constants.FLATBUFFERS_25_2_10(); }
2525
public static CoreVersion getRootAsCoreVersion(ByteBuffer _bb) { return getRootAsCoreVersion(_bb, new CoreVersion()); }
2626
public static CoreVersion getRootAsCoreVersion(ByteBuffer _bb, CoreVersion obj) { _bb.order(ByteOrder.LITTLE_ENDIAN); return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb)); }
2727
public void __init(int _i, ByteBuffer _bb) { __reset(_i, _bb); }

java-engine/src/main/java/io/getunleash/messaging/CustomStrategyResult.java

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

2222
@SuppressWarnings("unused")
2323
public final class CustomStrategyResult extends Table {
24-
public static void ValidateVersion() { Constants.FLATBUFFERS_25_9_23(); }
24+
public static void ValidateVersion() { Constants.FLATBUFFERS_25_2_10(); }
2525
public static CustomStrategyResult getRootAsCustomStrategyResult(ByteBuffer _bb) { return getRootAsCustomStrategyResult(_bb, new CustomStrategyResult()); }
2626
public static CustomStrategyResult getRootAsCustomStrategyResult(ByteBuffer _bb, CustomStrategyResult obj) { _bb.order(ByteOrder.LITTLE_ENDIAN); return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb)); }
2727
public void __init(int _i, ByteBuffer _bb) { __reset(_i, _bb); }

0 commit comments

Comments
 (0)