Skip to content

Commit 95b0f2d

Browse files
Merge pull request #89 from Flagsmith/release/5.1.0
Release 5.1.0
2 parents 75f4450 + 4c2adf7 commit 95b0f2d

9 files changed

Lines changed: 139 additions & 13 deletions

File tree

pom.xml

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

77
<groupId>com.flagsmith</groupId>
88
<artifactId>flagsmith-java-client</artifactId>
9-
<version>5.0.6</version>
9+
<version>5.1.0</version>
1010
<packaging>jar</packaging>
1111

1212
<name>Flagsmith Java Client</name>

src/main/java/com/flagsmith/config/FlagsmithConfig.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import com.flagsmith.FlagsmithFlagDefaults;
55
import com.flagsmith.interfaces.DefaultFlagHandler;
66
import com.flagsmith.threads.AnalyticsProcessor;
7+
8+
import java.net.Proxy;
79
import java.util.ArrayList;
810
import java.util.List;
911
import java.util.concurrent.TimeUnit;
@@ -57,6 +59,9 @@ protected FlagsmithConfig(Builder builder) {
5759
for (final Interceptor interceptor : builder.interceptors) {
5860
httpBuilder = httpBuilder.addInterceptor(interceptor);
5961
}
62+
if (builder.proxy != null) {
63+
httpBuilder = httpBuilder.proxy(builder.proxy);
64+
}
6065
this.httpClient = httpBuilder.build();
6166

6267
this.retries = builder.retries;
@@ -79,6 +84,7 @@ public static FlagsmithConfig.Builder newBuilder() {
7984
public static class Builder {
8085

8186
private final List<Interceptor> interceptors = new ArrayList<>();
87+
private Proxy proxy;
8288
private HttpUrl baseUri = DEFAULT_BASE_URI;
8389
private int connectTimeoutMillis = DEFAULT_CONNECT_TIMEOUT_MILLIS;
8490
private int writeTimeoutMillis = DEFAULT_WRITE_TIMEOUT_MILLIS;
@@ -167,6 +173,17 @@ public Builder addHttpInterceptor(Interceptor interceptor) {
167173
return this;
168174
}
169175

176+
/**
177+
* Add a Proxy to the HttpClient.
178+
*
179+
* @param proxy the proxy
180+
* @return the Builder
181+
*/
182+
public Builder withProxy(Proxy proxy) {
183+
this.proxy = proxy;
184+
return this;
185+
}
186+
170187
/**
171188
* Add retries for HTTP request to the builder.
172189
* @param retries no of retries for requests

src/main/java/com/flagsmith/flagengine/segments/SegmentEvaluator.java

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,27 @@ private static Boolean traitsMatchSegmentCondition(List<TraitModel> identityTrai
140140
.filter((trait) -> trait.getTraitKey().equals(condition.getProperty_()))
141141
.findFirst();
142142

143-
if (matchingTrait.isPresent()) {
144-
return traitsMatchValue(condition, matchingTrait.get().getTraitValue());
145-
}
143+
return traitMatchesSegmentCondition(matchingTrait, condition);
144+
}
145+
146+
return condition.getOperator().equals(SegmentConditions.IS_NOT_SET);
147+
}
148+
149+
/**
150+
* Evaluate a single trait and compare it with condition.
151+
* @param trait Trait to match against.
152+
* @param condition Condition to evaluate with.
153+
* @return
154+
*/
155+
private static Boolean traitMatchesSegmentCondition(Optional<TraitModel> trait,
156+
SegmentConditionModel condition) {
157+
if (condition.getOperator().equals(SegmentConditions.IS_NOT_SET)) {
158+
return !trait.isPresent();
159+
} else if (condition.getOperator().equals(SegmentConditions.IS_SET)) {
160+
return trait.isPresent();
146161
}
147162

148-
return false;
163+
return trait.isPresent() && conditionMatchesTraitValue(condition, trait.get().getTraitValue());
149164
}
150165

151166
/**
@@ -154,7 +169,7 @@ private static Boolean traitsMatchSegmentCondition(List<TraitModel> identityTrai
154169
* @param value Trait value to compare with.
155170
* @return
156171
*/
157-
public static Boolean traitsMatchValue(SegmentConditionModel condition, Object value) {
172+
public static Boolean conditionMatchesTraitValue(SegmentConditionModel condition, Object value) {
158173
SegmentConditions operator = condition.getOperator();
159174
if (operator.equals(SegmentConditions.NOT_CONTAINS)) {
160175
return ((String) value).indexOf(condition.getValue()) == -1;

src/main/java/com/flagsmith/flagengine/segments/constants/SegmentConditions.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22

33
public enum SegmentConditions {
44
EQUAL, GREATER_THAN, LESS_THAN, LESS_THAN_INCLUSIVE, CONTAINS,
5-
GREATER_THAN_INCLUSIVE, NOT_CONTAINS, NOT_EQUAL, REGEX, PERCENTAGE_SPLIT;
5+
GREATER_THAN_INCLUSIVE, NOT_CONTAINS, NOT_EQUAL, REGEX, PERCENTAGE_SPLIT,
6+
MODULO, IS_SET, IS_NOT_SET;
67
}

src/main/java/com/flagsmith/flagengine/utils/types/TypeCasting.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ public class TypeCasting {
1616
*/
1717
public static Boolean compare(SegmentConditions condition, Object value1, Object value2) {
1818

19+
if (condition.equals(SegmentConditions.MODULO)) {
20+
return compareModulo(String.valueOf(value2), value1);
21+
}
22+
1923
if (isInteger(value1) && isInteger(value2)) {
2024
return compare(condition, toInteger(value1), toInteger(value2));
2125
} else if (isFloat(value1) && isFloat(value2)) {
@@ -65,7 +69,8 @@ public static Boolean compare(SegmentConditions condition, Comparable value1, Co
6569
*/
6670
public static Double toDouble(Object number) {
6771
try {
68-
return number instanceof Double ? ((Double) number) : Double.parseDouble((String) number);
72+
String asString = String.valueOf(number);
73+
return number instanceof Double ? ((Double) number) : Double.parseDouble(asString);
6974
} catch (Exception nfe) {
7075
return null;
7176
}
@@ -87,7 +92,7 @@ public static Boolean isDouble(Object number) {
8792
*/
8893
public static Float toFloat(Object number) {
8994
try {
90-
return number instanceof Float ? ((Float) number) : Float.parseFloat((String) number);
95+
return number instanceof Float ? ((Float) number) : Float.parseFloat(String.valueOf(number));
9196
} catch (Exception nfe) {
9297
return null;
9398
}
@@ -109,7 +114,8 @@ public static Boolean isFloat(Object number) {
109114
*/
110115
public static Integer toInteger(Object number) {
111116
try {
112-
return number instanceof Integer ? ((Integer) number) : Integer.valueOf((String) number);
117+
String asString = String.valueOf(number);
118+
return number instanceof Integer ? ((Integer) number) : Integer.valueOf(asString);
113119
} catch (Exception nfe) {
114120
return null;
115121
}
@@ -172,4 +178,26 @@ public static ComparableVersion toSemver(Object str) {
172178
public static Boolean isSemver(Object str) {
173179
return SemanticVersioning.isSemver((String) str);
174180
}
181+
182+
/**
183+
* Modulo is a special case as the condition value holds both the divisor and remainder.
184+
* This method compares the conditionValue and the traitValue by dividing the traitValue
185+
* by the divisor and verifying that it correctly equals the remainder.
186+
*
187+
* @param conditionValue conditionValue in the format 'divisor|remainder'
188+
* @param traitValue the value of the matched trait
189+
* @return true if expression evaluates to true, false if unable to evaluate expression or
190+
* it evaluates to false
191+
*/
192+
public static Boolean compareModulo(String conditionValue, Object traitValue) {
193+
try {
194+
String[] divisorAndRemainder = conditionValue.split("\\|");
195+
Float divisor = toFloat(divisorAndRemainder[0]);
196+
Float remainder = toFloat(divisorAndRemainder[1]);
197+
198+
return toFloat(traitValue) % divisor == remainder;
199+
} catch (NumberFormatException | NullPointerException e) {
200+
return false; // indicates that one of the values could not be case to Float.
201+
}
202+
}
175203
}

src/test/java/com/flagsmith/config/FlagsmithConfigTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package com.flagsmith.config;
22

3+
import static org.junit.Assert.assertNull;
34
import static org.testng.Assert.assertEquals;
45
import static org.testng.Assert.assertTrue;
56

67
import com.flagsmith.config.FlagsmithConfig;
8+
9+
import java.net.InetSocketAddress;
10+
import java.net.Proxy;
711
import okhttp3.mock.MockInterceptor;
812
import org.testng.annotations.Test;
913

@@ -17,21 +21,26 @@ public void configTest_defaults() {
1721
assertEquals(5000, flagsmithConfig.getHttpClient().readTimeoutMillis());
1822
assertEquals(5000, flagsmithConfig.getHttpClient().writeTimeoutMillis());
1923
assertEquals(2000, flagsmithConfig.getHttpClient().connectTimeoutMillis());
24+
assertNull(flagsmithConfig.getHttpClient().proxy());
2025
}
2126

2227
@Test(groups = "unit")
2328
public void configTest_custom() {
29+
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy", 1234));
30+
2431
final FlagsmithConfig flagsmithConfig = FlagsmithConfig.newBuilder()
2532
.addHttpInterceptor(new MockInterceptor())
2633
.connectTimeout(1234)
2734
.readTimeout(3333)
2835
.writeTimeout(6666)
36+
.withProxy(proxy)
2937
.build();
3038

3139
assertEquals(1, flagsmithConfig.getHttpClient().interceptors().size());
3240
assertEquals(3333, flagsmithConfig.getHttpClient().readTimeoutMillis());
3341
assertEquals(6666, flagsmithConfig.getHttpClient().writeTimeoutMillis());
3442
assertEquals(1234, flagsmithConfig.getHttpClient().connectTimeoutMillis());
43+
assertEquals(proxy, flagsmithConfig.getHttpClient().proxy());
3544
}
3645

3746
@Test(groups = "unit")

src/test/java/com/flagsmith/flagengine/unit/segments/SegmentEvaluatorTest.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@
22

33
import com.flagsmith.flagengine.identities.IdentityModel;
44
import com.flagsmith.flagengine.identities.traits.TraitModel;
5+
import com.flagsmith.flagengine.segments.SegmentConditionModel;
56
import com.flagsmith.flagengine.segments.SegmentEvaluator;
67
import com.flagsmith.flagengine.segments.SegmentModel;
8+
import com.flagsmith.flagengine.segments.SegmentRuleModel;
9+
import com.flagsmith.flagengine.segments.constants.SegmentConditions;
10+
import com.flagsmith.flagengine.segments.constants.SegmentRules;
711

812
import static com.flagsmith.flagengine.unit.segments.IdentitySegmentFixtures.*;
913

1014
import org.testng.Assert;
1115
import org.testng.annotations.DataProvider;
1216
import org.testng.annotations.Test;
1317

18+
import java.util.ArrayList;
1419
import java.util.Arrays;
1520
import java.util.List;
1621

@@ -52,4 +57,49 @@ public void testIdentityInSegment(SegmentModel segment, List<TraitModel> identit
5257

5358
Assert.assertTrue(actualResult.equals(expectedResponse));
5459
}
60+
61+
@DataProvider(name = "traitExistenceChecks")
62+
public Object[][] traitExistenceChecks() {
63+
return new Object[][] {
64+
new Object[] {SegmentConditions.IS_SET, "foo", new ArrayList<>(), false},
65+
new Object[] {SegmentConditions.IS_NOT_SET, "foo", new ArrayList<>(), true},
66+
new Object[] {SegmentConditions.IS_SET, "foo", new ArrayList<>(Arrays.asList(
67+
new TraitModel("foo", "bar"))), true},
68+
new Object[] {SegmentConditions.IS_NOT_SET, "foo", new ArrayList<>(Arrays.asList(
69+
new TraitModel("foo", "bar"))), false},
70+
};
71+
}
72+
73+
@Test(dataProvider = "traitExistenceChecks")
74+
public void testTraitExistenceConditions(SegmentConditions conditionOperator, String conditionProperty,
75+
List<TraitModel> traitModels, Boolean expectedResult) {
76+
// Given
77+
// An identity to test with which has the traits as defined in the DataProvider
78+
IdentityModel identityModel = new IdentityModel();
79+
identityModel.setIdentifier("foo");
80+
identityModel.setIdentityTraits(traitModels);
81+
identityModel.setEnvironmentApiKey("api-key");
82+
83+
// And a segment which has the operator and property value as defined in the DataProvider
84+
SegmentConditionModel segmentCondition = new SegmentConditionModel();
85+
segmentCondition.setOperator(conditionOperator);
86+
segmentCondition.setProperty_(conditionProperty);
87+
segmentCondition.setValue(null);
88+
89+
SegmentRuleModel segmentRule = new SegmentRuleModel();
90+
segmentRule.setConditions(new ArrayList<>(Arrays.asList(segmentCondition)));
91+
segmentRule.setType(SegmentRules.ALL_RULE.getRule());
92+
93+
SegmentModel segment = new SegmentModel();
94+
segment.setName("testSegment");
95+
segment.setRules(new ArrayList<>(Arrays.asList(segmentRule)));
96+
97+
// When
98+
// We evaluate whether the identity is in the segment
99+
Boolean inSegment = SegmentEvaluator.evaluateIdentityInSegment(identityModel, segment, null);
100+
101+
// Then
102+
// The result is as we expect from the DataProvider definition
103+
Assert.assertEquals(inSegment, expectedResult);
104+
}
55105
}

src/test/java/com/flagsmith/flagengine/unit/segments/SegmentModelTest.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ public Object[][] conditionTestData() {
6262
new Object[] {SegmentConditions.NOT_CONTAINS, "bar", "baz", true},
6363
new Object[] {SegmentConditions.REGEX, "foo", "[a-z]+", true},
6464
new Object[] {SegmentConditions.REGEX, "FOO", "[a-z]+", false},
65+
new Object[] {SegmentConditions.MODULO, 2, "2|0", true},
66+
new Object[] {SegmentConditions.MODULO, 3, "2|0", false},
67+
new Object[] {SegmentConditions.MODULO, 2.0, "2|0", true},
68+
new Object[] {SegmentConditions.MODULO, 2.0, "2.0|0.0", true},
69+
new Object[] {SegmentConditions.MODULO, "foo", "2|0", false},
70+
new Object[] {SegmentConditions.MODULO, "foo", "foo|bar", false},
6571
};
6672
}
6773

@@ -77,7 +83,7 @@ public void testSegmentConditionMatchesTraitValue(
7783
conditionModel.setOperator(condition);
7884
conditionModel.setProperty_("foo");
7985

80-
Boolean actualResult = SegmentEvaluator.traitsMatchValue(conditionModel, traitValue);
86+
Boolean actualResult = SegmentEvaluator.conditionMatchesTraitValue(conditionModel, traitValue);
8187

8288
Assert.assertTrue(actualResult.equals(expectedResponse));
8389
}
@@ -119,7 +125,7 @@ public void testSemverMatchesTraitValue(
119125
conditionModel.setOperator(condition);
120126
conditionModel.setProperty_("foo");
121127

122-
Boolean actualResult = SegmentEvaluator.traitsMatchValue(conditionModel, traitValue);
128+
Boolean actualResult = SegmentEvaluator.conditionMatchesTraitValue(conditionModel, traitValue);
123129

124130
Assert.assertTrue(actualResult.equals(expectedResponse));
125131
}

0 commit comments

Comments
 (0)