Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ jobs:

steps:
- uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
Expand Down
7 changes: 4 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[submodule "src/test/resources/test/server-sdk-specification"]
path = src/test/resources/test/server-sdk-specification
url = https://github.com/FeatureProbe/server-sdk-specification.git

[submodule "src/test/resources/test"]
path = src/test/resources/test
url = git@github.com:FeatureProbe/server-sdk-specification.git
48 changes: 19 additions & 29 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>com.featureprobe</groupId>
<artifactId>server-sdk-java</artifactId>
<version>2.1.0-SNAPSHOT</version>
<version>2.1.0</version>
<name>server-sdk-java</name>
<description>FeatureProbe Server Side SDK for Java</description>

Expand Down Expand Up @@ -218,6 +218,9 @@
<includes>
<include>**/*.md</include>
</includes>
<excludes>
<exclude>src/test/resources/test/**</exclude>
</excludes>
<flexmark />
</markdown>
</configuration>
Expand Down Expand Up @@ -297,6 +300,21 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>analyze-only-in-package</id>
<goals>
<goal>analyze-only</goal>
</goals>
<phase>package</phase>
<configuration />
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
Expand Down Expand Up @@ -330,34 +348,6 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.4.1</version>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.featureprobe.sdk.example.FeatureProbeDemo</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<goals>
<goal>single</goal>
</goals>
<phase>package</phase>

</execution>
</executions>
</plugin>
</plugins>
</build>
<reporting>
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/com/featureprobe/sdk/server/FPConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public final class FPConfig {

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

static final Integer DEFAULT_MAX_DEPENDENT_DEEP = 20;

protected static final FPConfig DEFAULT = new Builder().build();

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

final URI remoteUri;

final Integer prerequisiteDeep;

URL synchronizerUrl;

URL eventUrl;
Expand Down Expand Up @@ -71,6 +75,8 @@ protected FPConfig(Builder builder) {
this.eventUrl = builder.eventUrl;
this.realtimeUri = builder.realtimeUri;
this.startWait = builder.startWait == null ? DEFAULT_START_WAIT : builder.startWait;
this.prerequisiteDeep =
builder.prerequisiteDeep == null ? DEFAULT_MAX_DEPENDENT_DEEP : builder.prerequisiteDeep;
}

public static Builder builder() {
Expand Down Expand Up @@ -99,6 +105,8 @@ public static class Builder {

private Long startWait;

private Integer prerequisiteDeep;

public Builder() {
}

Expand Down Expand Up @@ -177,6 +185,11 @@ public Builder startWait(Long startWaitTime, TimeUnit unit) {
return this;
}

public Builder prerequisiteDeep(Integer prerequisiteDeep) {
this.prerequisiteDeep = prerequisiteDeep;
return this;
}

public FPConfig build() {
return new FPConfig(this);
}
Expand Down
21 changes: 18 additions & 3 deletions src/main/java/com/featureprobe/sdk/server/FeatureProbe.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.featureprobe.sdk.server.exceptions.PrerequisitesDeepOverflowException;
import com.featureprobe.sdk.server.model.Segment;
import com.featureprobe.sdk.server.model.Toggle;
import com.google.common.annotations.VisibleForTesting;
Expand Down Expand Up @@ -58,10 +59,14 @@ public final class FeatureProbe {
@VisibleForTesting
EventProcessor eventProcessor;

@VisibleForTesting
FPConfig config;

@VisibleForTesting
private FeatureProbe(DataRepository dataRepository) {
this.dataRepository = dataRepository;
FPConfig config = FPConfig.DEFAULT;
this.config = config;
final FPContext context = new FPContext("test", config);
eventProcessor = config.eventProcessorFactory.createEventProcessor(context);
}
Expand All @@ -86,6 +91,7 @@ public FeatureProbe(String serverSDKKey, FPConfig config) {
throw new IllegalArgumentException("serverSDKKey must not be blank");
}
final FPContext context = new FPContext(serverSDKKey, config);
this.config = config;
this.eventProcessor = config.eventProcessorFactory.createEventProcessor(context);
this.dataRepository = config.dataRepositoryFactory.createDataRepository(context);
this.synchronizer = config.synchronizerFactory.createSynchronizer(context, dataRepository);
Expand Down Expand Up @@ -255,8 +261,10 @@ private <T> T jsonEvaluate(String toggleKey, FPUser user, T defaultValue, Class<
try {
Toggle toggle = dataRepository.getToggle(toggleKey);
Map<String, Segment> segments = dataRepository.getAllSegment();
Map<String, Toggle> toggles = dataRepository.getAllToggle();
if (Objects.nonNull(toggle)) {
EvaluationResult evalResult = toggle.eval(user, segments, defaultValue);
EvaluationResult evalResult = toggle.eval(user, toggles, segments, defaultValue,
config.prerequisiteDeep);
String value = mapper.writeValueAsString(evalResult.getValue());
eventProcessor.push(buildAccessEvent(toggle, evalResult, user));
return mapper.readValue(value, clazz);
Expand All @@ -273,8 +281,10 @@ private <T> T genericEvaluate(String toggleKey, FPUser user, T defaultValue, Cla
try {
Toggle toggle = dataRepository.getToggle(toggleKey);
Map<String, Segment> segments = dataRepository.getAllSegment();
Map<String, Toggle> toggles = dataRepository.getAllToggle();
if (Objects.nonNull(toggle)) {
EvaluationResult evalResult = toggle.eval(user, segments, defaultValue);
EvaluationResult evalResult = toggle.eval(user, toggles, segments, defaultValue,
config.prerequisiteDeep);
eventProcessor.push(buildAccessEvent(toggle, evalResult, user));
return clazz.cast(evalResult.getValue());
}
Expand Down Expand Up @@ -308,6 +318,9 @@ private <T> FPDetail<T> genericEvaluateDetail(String toggleKey, FPUser user, T d
} catch (ClassCastException | JsonProcessingException e) {
logger.error(LOG_CONVERSION_ERROR, toggleKey, e);
detail.setReason(REASON_TYPE_MISMATCH);
} catch (PrerequisitesDeepOverflowException e) {
logger.error(e.getMessage(), toggleKey, e);
detail.setReason(e.getMessage());
} catch (Exception e) {
logger.error(LOG_HANDLE_ERROR, toggleKey, e);
detail.setReason(REASON_HANDLE_ERROR);
Expand All @@ -323,8 +336,10 @@ private <T> FPDetail<T> getEvaluateDetail(String toggleKey, FPUser user, T defau
if (this.dataRepository.initialized()) {
Toggle toggle = dataRepository.getToggle(toggleKey);
Map<String, Segment> segments = dataRepository.getAllSegment();
Map<String, Toggle> toggles = dataRepository.getAllToggle();
if (Objects.nonNull(toggle)) {
EvaluationResult evalResult = toggle.eval(user, segments, defaultValue);
EvaluationResult evalResult = toggle.eval(user, toggles, segments, defaultValue,
config.prerequisiteDeep);
if (isJson) {
String res = mapper.writeValueAsString(evalResult.getValue());
detail.setValue(mapper.readValue(res, clazz));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.featureprobe.sdk.server.exceptions;

public class PrerequisitesDeepOverflowException extends RuntimeException {

public PrerequisitesDeepOverflowException(String message) {
super(message);
}

}
5 changes: 4 additions & 1 deletion src/main/java/com/featureprobe/sdk/server/model/Split.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,10 @@ private int hash(String hashKey, String hashSalt, int bucketSize) {
}

private String getHashSalt(String toggleKey) {
return StringUtils.defaultString(salt, toggleKey);
if (StringUtils.isNotBlank(salt)) {
return salt;
}
return toggleKey;
}

public List<List<List<Integer>>> getDistribution() {
Expand Down
35 changes: 34 additions & 1 deletion src/main/java/com/featureprobe/sdk/server/model/Toggle.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
import com.featureprobe.sdk.server.EvaluationResult;
import com.featureprobe.sdk.server.FPUser;
import com.featureprobe.sdk.server.HitResult;
import com.featureprobe.sdk.server.exceptions.PrerequisitesDeepOverflowException;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

public final class Toggle {
Expand All @@ -49,13 +51,23 @@ public final class Toggle {

private Boolean forClient;

public EvaluationResult eval(FPUser user, Map<String, Segment> segments, Object defaultValue) {
public EvaluationResult eval(FPUser user, Map<String, Toggle> toggles, Map<String, Segment> segments,
Object defaultValue, int deep) {

String warning = "";

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

if (deep <= 0) {
throw new PrerequisitesDeepOverflowException("prerequisite deep overflow");
}

if (!prerequisite(user, toggles, segments, deep)) {
return createDefaultResult(user, key, defaultValue, warning);
}

if (rules != null && rules.size() > 0) {
for (int i = 0; i < rules.size(); i++) {
Rule rule = rules.get(i);
Expand Down Expand Up @@ -84,6 +96,27 @@ private EvaluationResult createDefaultResult(FPUser user, String toggleKey, Obje
return defaultResult;
}

private boolean prerequisite(FPUser user, Map<String, Toggle> toggles, Map<String, Segment> segments, int deep) {
if (Objects.isNull(prerequisites) || prerequisites.isEmpty()) {
return true;
}
try {
for (Prerequisite prerequisite : prerequisites) {
Toggle toggle = toggles.get(prerequisite.getKey());
if (Objects.isNull(toggle))
return false;
EvaluationResult eval = toggle.eval(user, toggles, segments, null, deep - 1);
if (Objects.isNull(eval.getValue()))
return false;
if (!eval.getValue().equals(prerequisite.getValue()))
return false;
}
} catch (PrerequisitesDeepOverflowException e) {
throw e;
}
return true;
}

private EvaluationResult hitValue(HitResult hitResult, Object defaultValue, Optional<Integer> ruleIndex) {
EvaluationResult res = new EvaluationResult(defaultValue, ruleIndex, hitResult.getIndex(),
this.version, hitResult.getReason().orElse(""));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import java.nio.charset.Charset
class FeatureProbeSpec extends Specification {


def test_data_local = "test/server-sdk-specification/spec/toggle_simple_spec.json";
def test_data_local = "test/spec/toggle_simple_spec.json";
def FeatureProbe featureProbe
def ObjectMapper mapper
def JsonNode testCase
Expand Down
7 changes: 7 additions & 0 deletions src/test/groovy/com/featureprobe/sdk/server/SplitSpec.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ class SplitSpec extends Specification {
}
}

def "Get hash key" () {
when:
def hash = split.hash("13", "tutorial_rollout", 10000)
then:
hash == 9558
}

def "User not has key"() {
when:
user = new FPUser()
Expand Down
1 change: 1 addition & 0 deletions src/test/resources/test
Submodule test added at 4c3139
Loading