Skip to content

Commit 19a98c5

Browse files
authored
✨ Prerequisite support (#37)
* ✨ feat: support Prerequisite
1 parent 7d0e40f commit 19a98c5

File tree

12 files changed

+129
-1076
lines changed

12 files changed

+129
-1076
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ jobs:
1313

1414
steps:
1515
- uses: actions/checkout@v3
16+
with:
17+
submodules: 'recursive'
1618
- name: Set up JDK 11
1719
uses: actions/setup-java@v3
1820
with:

.gitmodules

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
[submodule "src/test/resources/test/server-sdk-specification"]
2-
path = src/test/resources/test/server-sdk-specification
3-
url = https://github.com/FeatureProbe/server-sdk-specification.git
1+
2+
[submodule "src/test/resources/test"]
3+
path = src/test/resources/test
4+
url = [email protected]:FeatureProbe/server-sdk-specification.git

pom.xml

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>com.featureprobe</groupId>
77
<artifactId>server-sdk-java</artifactId>
8-
<version>2.1.0-SNAPSHOT</version>
8+
<version>2.1.0</version>
99
<name>server-sdk-java</name>
1010
<description>FeatureProbe Server Side SDK for Java</description>
1111

@@ -218,6 +218,9 @@
218218
<includes>
219219
<include>**/*.md</include>
220220
</includes>
221+
<excludes>
222+
<exclude>src/test/resources/test/**</exclude>
223+
</excludes>
221224
<flexmark />
222225
</markdown>
223226
</configuration>
@@ -297,6 +300,21 @@
297300
</execution>
298301
</executions>
299302
</plugin>
303+
<plugin>
304+
<groupId>org.apache.maven.plugins</groupId>
305+
<artifactId>maven-dependency-plugin</artifactId>
306+
<version>3.3.0</version>
307+
<executions>
308+
<execution>
309+
<id>analyze-only-in-package</id>
310+
<goals>
311+
<goal>analyze-only</goal>
312+
</goals>
313+
<phase>package</phase>
314+
<configuration />
315+
</execution>
316+
</executions>
317+
</plugin>
300318
<plugin>
301319
<groupId>org.apache.maven.plugins</groupId>
302320
<artifactId>maven-javadoc-plugin</artifactId>
@@ -330,34 +348,6 @@
330348
</execution>
331349
</executions>
332350
</plugin>
333-
<plugin>
334-
<groupId>org.apache.maven.plugins</groupId>
335-
<artifactId>maven-assembly-plugin</artifactId>
336-
<version>3.4.1</version>
337-
<configuration>
338-
<appendAssemblyId>false</appendAssemblyId>
339-
<descriptorRefs>
340-
<descriptorRef>jar-with-dependencies</descriptorRef>
341-
</descriptorRefs>
342-
<archive>
343-
<manifest>
344-
<addClasspath>true</addClasspath>
345-
<classpathPrefix>lib/</classpathPrefix>
346-
<mainClass>com.featureprobe.sdk.example.FeatureProbeDemo</mainClass>
347-
</manifest>
348-
</archive>
349-
</configuration>
350-
<executions>
351-
<execution>
352-
<id>make-assembly</id>
353-
<goals>
354-
<goal>single</goal>
355-
</goals>
356-
<phase>package</phase>
357-
358-
</execution>
359-
</executions>
360-
</plugin>
361351
</plugins>
362352
</build>
363353
<reporting>

src/main/java/com/featureprobe/sdk/server/FPConfig.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ public final class FPConfig {
3232

3333
static final Long DEFAULT_START_WAIT = TimeUnit.SECONDS.toNanos(5);
3434

35+
static final Integer DEFAULT_MAX_DEPENDENT_DEEP = 20;
36+
3537
protected static final FPConfig DEFAULT = new Builder().build();
3638

3739
final Duration refreshInterval;
@@ -40,6 +42,8 @@ public final class FPConfig {
4042

4143
final URI remoteUri;
4244

45+
final Integer prerequisiteDeep;
46+
4347
URL synchronizerUrl;
4448

4549
URL eventUrl;
@@ -71,6 +75,8 @@ protected FPConfig(Builder builder) {
7175
this.eventUrl = builder.eventUrl;
7276
this.realtimeUri = builder.realtimeUri;
7377
this.startWait = builder.startWait == null ? DEFAULT_START_WAIT : builder.startWait;
78+
this.prerequisiteDeep =
79+
builder.prerequisiteDeep == null ? DEFAULT_MAX_DEPENDENT_DEEP : builder.prerequisiteDeep;
7480
}
7581

7682
public static Builder builder() {
@@ -99,6 +105,8 @@ public static class Builder {
99105

100106
private Long startWait;
101107

108+
private Integer prerequisiteDeep;
109+
102110
public Builder() {
103111
}
104112

@@ -177,6 +185,11 @@ public Builder startWait(Long startWaitTime, TimeUnit unit) {
177185
return this;
178186
}
179187

188+
public Builder prerequisiteDeep(Integer prerequisiteDeep) {
189+
this.prerequisiteDeep = prerequisiteDeep;
190+
return this;
191+
}
192+
180193
public FPConfig build() {
181194
return new FPConfig(this);
182195
}

src/main/java/com/featureprobe/sdk/server/FeatureProbe.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import com.fasterxml.jackson.core.JsonProcessingException;
2121
import com.fasterxml.jackson.databind.ObjectMapper;
22+
import com.featureprobe.sdk.server.exceptions.PrerequisitesDeepOverflowException;
2223
import com.featureprobe.sdk.server.model.Segment;
2324
import com.featureprobe.sdk.server.model.Toggle;
2425
import com.google.common.annotations.VisibleForTesting;
@@ -58,10 +59,14 @@ public final class FeatureProbe {
5859
@VisibleForTesting
5960
EventProcessor eventProcessor;
6061

62+
@VisibleForTesting
63+
FPConfig config;
64+
6165
@VisibleForTesting
6266
private FeatureProbe(DataRepository dataRepository) {
6367
this.dataRepository = dataRepository;
6468
FPConfig config = FPConfig.DEFAULT;
69+
this.config = config;
6570
final FPContext context = new FPContext("test", config);
6671
eventProcessor = config.eventProcessorFactory.createEventProcessor(context);
6772
}
@@ -86,6 +91,7 @@ public FeatureProbe(String serverSDKKey, FPConfig config) {
8691
throw new IllegalArgumentException("serverSDKKey must not be blank");
8792
}
8893
final FPContext context = new FPContext(serverSDKKey, config);
94+
this.config = config;
8995
this.eventProcessor = config.eventProcessorFactory.createEventProcessor(context);
9096
this.dataRepository = config.dataRepositoryFactory.createDataRepository(context);
9197
this.synchronizer = config.synchronizerFactory.createSynchronizer(context, dataRepository);
@@ -255,8 +261,10 @@ private <T> T jsonEvaluate(String toggleKey, FPUser user, T defaultValue, Class<
255261
try {
256262
Toggle toggle = dataRepository.getToggle(toggleKey);
257263
Map<String, Segment> segments = dataRepository.getAllSegment();
264+
Map<String, Toggle> toggles = dataRepository.getAllToggle();
258265
if (Objects.nonNull(toggle)) {
259-
EvaluationResult evalResult = toggle.eval(user, segments, defaultValue);
266+
EvaluationResult evalResult = toggle.eval(user, toggles, segments, defaultValue,
267+
config.prerequisiteDeep);
260268
String value = mapper.writeValueAsString(evalResult.getValue());
261269
eventProcessor.push(buildAccessEvent(toggle, evalResult, user));
262270
return mapper.readValue(value, clazz);
@@ -273,8 +281,10 @@ private <T> T genericEvaluate(String toggleKey, FPUser user, T defaultValue, Cla
273281
try {
274282
Toggle toggle = dataRepository.getToggle(toggleKey);
275283
Map<String, Segment> segments = dataRepository.getAllSegment();
284+
Map<String, Toggle> toggles = dataRepository.getAllToggle();
276285
if (Objects.nonNull(toggle)) {
277-
EvaluationResult evalResult = toggle.eval(user, segments, defaultValue);
286+
EvaluationResult evalResult = toggle.eval(user, toggles, segments, defaultValue,
287+
config.prerequisiteDeep);
278288
eventProcessor.push(buildAccessEvent(toggle, evalResult, user));
279289
return clazz.cast(evalResult.getValue());
280290
}
@@ -308,6 +318,9 @@ private <T> FPDetail<T> genericEvaluateDetail(String toggleKey, FPUser user, T d
308318
} catch (ClassCastException | JsonProcessingException e) {
309319
logger.error(LOG_CONVERSION_ERROR, toggleKey, e);
310320
detail.setReason(REASON_TYPE_MISMATCH);
321+
} catch (PrerequisitesDeepOverflowException e) {
322+
logger.error(e.getMessage(), toggleKey, e);
323+
detail.setReason(e.getMessage());
311324
} catch (Exception e) {
312325
logger.error(LOG_HANDLE_ERROR, toggleKey, e);
313326
detail.setReason(REASON_HANDLE_ERROR);
@@ -323,8 +336,10 @@ private <T> FPDetail<T> getEvaluateDetail(String toggleKey, FPUser user, T defau
323336
if (this.dataRepository.initialized()) {
324337
Toggle toggle = dataRepository.getToggle(toggleKey);
325338
Map<String, Segment> segments = dataRepository.getAllSegment();
339+
Map<String, Toggle> toggles = dataRepository.getAllToggle();
326340
if (Objects.nonNull(toggle)) {
327-
EvaluationResult evalResult = toggle.eval(user, segments, defaultValue);
341+
EvaluationResult evalResult = toggle.eval(user, toggles, segments, defaultValue,
342+
config.prerequisiteDeep);
328343
if (isJson) {
329344
String res = mapper.writeValueAsString(evalResult.getValue());
330345
detail.setValue(mapper.readValue(res, clazz));
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package com.featureprobe.sdk.server.exceptions;
19+
20+
public class PrerequisitesDeepOverflowException extends RuntimeException {
21+
22+
public PrerequisitesDeepOverflowException(String message) {
23+
super(message);
24+
}
25+
26+
}

src/main/java/com/featureprobe/sdk/server/model/Split.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,10 @@ private int hash(String hashKey, String hashSalt, int bucketSize) {
9696
}
9797

9898
private String getHashSalt(String toggleKey) {
99-
return StringUtils.defaultString(salt, toggleKey);
99+
if (StringUtils.isNotBlank(salt)) {
100+
return salt;
101+
}
102+
return toggleKey;
100103
}
101104

102105
public List<List<List<Integer>>> getDistribution() {

src/main/java/com/featureprobe/sdk/server/model/Toggle.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
import com.featureprobe.sdk.server.EvaluationResult;
2121
import com.featureprobe.sdk.server.FPUser;
2222
import com.featureprobe.sdk.server.HitResult;
23+
import com.featureprobe.sdk.server.exceptions.PrerequisitesDeepOverflowException;
2324

2425
import java.util.List;
2526
import java.util.Map;
27+
import java.util.Objects;
2628
import java.util.Optional;
2729

2830
public final class Toggle {
@@ -49,13 +51,23 @@ public final class Toggle {
4951

5052
private Boolean forClient;
5153

52-
public EvaluationResult eval(FPUser user, Map<String, Segment> segments, Object defaultValue) {
54+
public EvaluationResult eval(FPUser user, Map<String, Toggle> toggles, Map<String, Segment> segments,
55+
Object defaultValue, int deep) {
56+
5357
String warning = "";
5458

5559
if (!enabled) {
5660
return createDisabledResult(user, this.key, defaultValue);
5761
}
5862

63+
if (deep <= 0) {
64+
throw new PrerequisitesDeepOverflowException("prerequisite deep overflow");
65+
}
66+
67+
if (!prerequisite(user, toggles, segments, deep)) {
68+
return createDefaultResult(user, key, defaultValue, warning);
69+
}
70+
5971
if (rules != null && rules.size() > 0) {
6072
for (int i = 0; i < rules.size(); i++) {
6173
Rule rule = rules.get(i);
@@ -84,6 +96,27 @@ private EvaluationResult createDefaultResult(FPUser user, String toggleKey, Obje
8496
return defaultResult;
8597
}
8698

99+
private boolean prerequisite(FPUser user, Map<String, Toggle> toggles, Map<String, Segment> segments, int deep) {
100+
if (Objects.isNull(prerequisites) || prerequisites.isEmpty()) {
101+
return true;
102+
}
103+
try {
104+
for (Prerequisite prerequisite : prerequisites) {
105+
Toggle toggle = toggles.get(prerequisite.getKey());
106+
if (Objects.isNull(toggle))
107+
return false;
108+
EvaluationResult eval = toggle.eval(user, toggles, segments, null, deep - 1);
109+
if (Objects.isNull(eval.getValue()))
110+
return false;
111+
if (!eval.getValue().equals(prerequisite.getValue()))
112+
return false;
113+
}
114+
} catch (PrerequisitesDeepOverflowException e) {
115+
throw e;
116+
}
117+
return true;
118+
}
119+
87120
private EvaluationResult hitValue(HitResult hitResult, Object defaultValue, Optional<Integer> ruleIndex) {
88121
EvaluationResult res = new EvaluationResult(defaultValue, ruleIndex, hitResult.getIndex(),
89122
this.version, hitResult.getReason().orElse(""));

src/test/groovy/com/featureprobe/sdk/server/FeatureProbeSpec.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import java.nio.charset.Charset
1010
class FeatureProbeSpec extends Specification {
1111

1212

13-
def test_data_local = "test/server-sdk-specification/spec/toggle_simple_spec.json";
13+
def test_data_local = "test/spec/toggle_simple_spec.json";
1414
def FeatureProbe featureProbe
1515
def ObjectMapper mapper
1616
def JsonNode testCase

src/test/groovy/com/featureprobe/sdk/server/SplitSpec.groovy

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ class SplitSpec extends Specification {
2727
}
2828
}
2929

30+
def "Get hash key" () {
31+
when:
32+
def hash = split.hash("13", "tutorial_rollout", 10000)
33+
then:
34+
hash == 9558
35+
}
36+
3037
def "User not has key"() {
3138
when:
3239
user = new FPUser()

0 commit comments

Comments
 (0)