Skip to content

Commit 6774992

Browse files
committed
added immutable context implementation.
Signed-off-by: thiyagu06 <[email protected]>
1 parent c2b5689 commit 6774992

File tree

4 files changed

+146
-5
lines changed

4 files changed

+146
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package dev.openfeature.sdk;
2+
3+
import lombok.Getter;
4+
import lombok.Setter;
5+
import lombok.ToString;
6+
import lombok.experimental.Delegate;
7+
8+
import java.time.Instant;
9+
import java.util.HashMap;
10+
import java.util.List;
11+
import java.util.Map;
12+
13+
/**
14+
* The EvaluationContext is a container for arbitrary contextual data
15+
* that can be used as a basis for dynamic evaluation.
16+
* The MutableContext is an EvaluationContext implementation which is not threadsafe, and whose attributes can
17+
* be modified after instantiation.
18+
*/
19+
@ToString
20+
@SuppressWarnings("PMD.BeanMembersShouldSerialize")
21+
public class ImmutableContext implements EvaluationContext {
22+
23+
@Getter
24+
private String targetingKey;
25+
@Delegate(excludes = HideDelegateAddMethods.class)
26+
private final MutableStructure structure;
27+
28+
public ImmutableContext() {
29+
this.structure = new MutableStructure();
30+
this.targetingKey = "";
31+
}
32+
33+
public ImmutableContext(Map<String, Value> attributes) {
34+
HashMap<String, Value> copy = new HashMap<>(attributes);
35+
this.structure = new MutableStructure(copy);
36+
this.targetingKey = "";
37+
}
38+
39+
public ImmutableContext(String targetingKey, Map<String, Value> attributes) {
40+
this(attributes);
41+
this.targetingKey = targetingKey;
42+
}
43+
44+
@Override
45+
public void setTargetingKey(String targetingKey) {
46+
throw new UnsupportedOperationException("changing of targeting key is not allowed");
47+
}
48+
49+
/**
50+
* Merges this EvaluationContext objects with the second overriding the this in
51+
* case of conflict.
52+
*
53+
* @param overridingContext overriding context
54+
* @return resulting merged context
55+
*/
56+
@Override
57+
public EvaluationContext merge(EvaluationContext overridingContext) {
58+
if (overridingContext == null) {
59+
return new ImmutableContext(this.asMap());
60+
}
61+
String targetingKey = "";
62+
if (this.getTargetingKey() != null && !this.getTargetingKey().trim().equals("")) {
63+
targetingKey = this.getTargetingKey();
64+
}
65+
66+
if (overridingContext.getTargetingKey() != null && !overridingContext.getTargetingKey().trim().equals("")) {
67+
targetingKey = overridingContext.getTargetingKey();
68+
}
69+
Map<String, Value> merged = new HashMap<>();
70+
71+
merged.putAll(this.asMap());
72+
merged.putAll(overridingContext.asMap());
73+
return new ImmutableContext(targetingKey, merged);
74+
}
75+
76+
/**
77+
* Hidden class to tell Lombok not to copy these methods over via delegation.
78+
*/
79+
private static class HideDelegateAddMethods {
80+
public MutableStructure add(String ignoredKey, Boolean ignoredValue) {
81+
return null;
82+
}
83+
84+
public MutableStructure add(String ignoredKey, Double ignoredValue) {
85+
return null;
86+
}
87+
88+
public MutableStructure add(String ignoredKey, String ignoredValue) {
89+
return null;
90+
}
91+
92+
public MutableStructure add(String ignoredKey, Value ignoredValue) {
93+
return null;
94+
}
95+
96+
public MutableStructure add(String ignoredKey, Integer ignoredValue) {
97+
return null;
98+
}
99+
100+
public MutableStructure add(String ignoredKey, List<Value> ignoredValue) {
101+
return null;
102+
}
103+
104+
public MutableStructure add(String ignoredKey, MutableStructure ignoredValue) {
105+
return null;
106+
}
107+
108+
public MutableStructure add(String ignoredKey, Instant ignoredValue) {
109+
return null;
110+
}
111+
}
112+
}

src/main/java/dev/openfeature/sdk/OpenFeatureClient.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key
9393
FlagEvaluationOptions flagOptions = ObjectUtils.defaultIfNull(options,
9494
() -> FlagEvaluationOptions.builder().build());
9595
Map<String, Object> hints = Collections.unmodifiableMap(flagOptions.getHookHints());
96-
ctx = ObjectUtils.defaultIfNull(ctx, () -> new MutableContext());
96+
ctx = ObjectUtils.defaultIfNull(ctx, () -> new ImmutableContext());
9797

9898

9999
FlagEvaluationDetails<T> details = null;
@@ -120,10 +120,10 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key
120120
// merge of: API.context, client.context, invocation.context
121121
apiContext = openfeatureApi.getEvaluationContext() != null
122122
? openfeatureApi.getEvaluationContext()
123-
: new MutableContext();
123+
: new ImmutableContext();
124124
clientContext = this.getEvaluationContext() != null
125125
? this.getEvaluationContext()
126-
: new MutableContext();
126+
: new ImmutableContext();
127127

128128
EvaluationContext ctxFromHook = hookSupport.beforeHooks(type, hookCtx, mergedHooks, hints);
129129

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package dev.openfeature.sdk;
2+
3+
import org.junit.jupiter.api.DisplayName;
4+
import org.junit.jupiter.api.Test;
5+
6+
import java.util.HashMap;
7+
8+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
9+
import static org.junit.jupiter.api.Assertions.assertThrows;
10+
11+
public class ImmutableContextTest {
12+
13+
@Test
14+
@DisplayName("Mutating targeting key is not allowed on Immutable Context")
15+
void shouldThrowUnsupportedExceptionWhenMutatingTargetingKey() {
16+
EvaluationContext ctx = new ImmutableContext("targeting key", new HashMap<>());
17+
assertThrows(UnsupportedOperationException.class, () -> ctx.setTargetingKey(""));
18+
}
19+
20+
@DisplayName("attributes mutation should not affect the immutable context")
21+
@Test
22+
void shouldCreateCopyOfAttributesForImmutableContext() {
23+
HashMap<String, Value> attributes = new HashMap<>();
24+
attributes.put("key1", new Value("val1"));
25+
attributes.put("key2", new Value("val2"));
26+
EvaluationContext ctx = new ImmutableContext("targeting key", attributes);
27+
attributes.put("key3", new Value("val3"));
28+
assertArrayEquals(ctx.keySet().toArray(), new Object[]{"key1", "key2"});
29+
}
30+
}

src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@ void mergeContextTest() {
3737
String flag = "feature key";
3838
boolean defaultValue = false;
3939
String targetingKey = "targeting key";
40-
EvaluationContext ctx = new MutableContext(targetingKey);
41-
40+
EvaluationContext ctx = new ImmutableContext(targetingKey, new HashMap<>());
4241
OpenFeatureAPI api = mock(OpenFeatureAPI.class);
4342
FeatureProvider mockProvider = mock(FeatureProvider.class);
4443
// this makes it so that true is returned only if the targeting key set at the client level is honored

0 commit comments

Comments
 (0)