Skip to content

Commit e9732b5

Browse files
feat!: use evaluation context interface (#112)
* POC - use evaluation context interface Signed-off-by: Todd Baert <[email protected]> * make .merge non-static Signed-off-by: Todd Baert <[email protected]> * improve naming Signed-off-by: Todd Baert <[email protected]> * add @OverRide Signed-off-by: Todd Baert <[email protected]> * Update src/main/java/dev/openfeature/sdk/EvaluationContext.java Co-authored-by: Justin Abrahms <[email protected]> Signed-off-by: Todd Baert <[email protected]> * Update src/main/java/dev/openfeature/sdk/MutableContext.java Co-authored-by: Justin Abrahms <[email protected]> Signed-off-by: Todd Baert <[email protected]> * address PR feedback Signed-off-by: Todd Baert <[email protected]> Signed-off-by: Todd Baert <[email protected]> Co-authored-by: Justin Abrahms <[email protected]>
1 parent 3788a3b commit e9732b5

17 files changed

+483
-405
lines changed
+11-120
Original file line numberDiff line numberDiff line change
@@ -1,130 +1,21 @@
11
package dev.openfeature.sdk;
22

3-
import java.time.Instant;
4-
import java.util.List;
5-
6-
import lombok.Getter;
7-
import lombok.Setter;
8-
import lombok.ToString;
9-
import lombok.experimental.Delegate;
10-
11-
@ToString
3+
/**
4+
* The EvaluationContext is a container for arbitrary contextual data
5+
* that can be used as a basis for dynamic evaluation.
6+
*/
127
@SuppressWarnings("PMD.BeanMembersShouldSerialize")
13-
public class EvaluationContext {
14-
15-
@Setter @Getter private String targetingKey;
16-
@Delegate(excludes = HideDelegateAddMethods.class) private final Structure structure = new Structure();
17-
18-
public EvaluationContext() {
19-
super();
20-
this.targetingKey = "";
21-
}
22-
23-
public EvaluationContext(String targetingKey) {
24-
this();
25-
this.targetingKey = targetingKey;
26-
}
8+
public interface EvaluationContext extends Structure {
9+
String getTargetingKey();
10+
11+
void setTargetingKey(String targetingKey);
2712

2813
/**
29-
* Merges two EvaluationContext objects with the second overriding the first in
14+
* Merges this EvaluationContext object with the second overriding the this in
3015
* case of conflict.
3116
*
32-
* @param ctx1 base context
33-
* @param ctx2 overriding context
17+
* @param overridingContext overriding context
3418
* @return resulting merged context
3519
*/
36-
public static EvaluationContext merge(EvaluationContext ctx1, EvaluationContext ctx2) {
37-
EvaluationContext ec = new EvaluationContext();
38-
if (ctx1 == null) {
39-
return ctx2;
40-
} else if (ctx2 == null) {
41-
return ctx1;
42-
}
43-
44-
ec.structure.attributes.putAll(ctx1.structure.attributes);
45-
ec.structure.attributes.putAll(ctx2.structure.attributes);
46-
47-
if (ctx1.getTargetingKey() != null && !ctx1.getTargetingKey().trim().equals("")) {
48-
ec.setTargetingKey(ctx1.getTargetingKey());
49-
}
50-
51-
if (ctx2.getTargetingKey() != null && !ctx2.getTargetingKey().trim().equals("")) {
52-
ec.setTargetingKey(ctx2.getTargetingKey());
53-
}
54-
55-
return ec;
56-
}
57-
58-
// override @Delegate methods so that we can use "add" methods and still return EvaluationContext, not Structure
59-
public EvaluationContext add(String key, Boolean value) {
60-
this.structure.add(key, value);
61-
return this;
62-
}
63-
64-
public EvaluationContext add(String key, String value) {
65-
this.structure.add(key, value);
66-
return this;
67-
}
68-
69-
public EvaluationContext add(String key, Integer value) {
70-
this.structure.add(key, value);
71-
return this;
72-
}
73-
74-
public EvaluationContext add(String key, Double value) {
75-
this.structure.add(key, value);
76-
return this;
77-
}
78-
79-
public EvaluationContext add(String key, Instant value) {
80-
this.structure.add(key, value);
81-
return this;
82-
}
83-
84-
public EvaluationContext add(String key, Structure value) {
85-
this.structure.add(key, value);
86-
return this;
87-
}
88-
89-
public EvaluationContext add(String key, List<Value> value) {
90-
this.structure.add(key, value);
91-
return this;
92-
}
93-
94-
/**
95-
* Hidden class to tell Lombok not to copy these methods over via delegation.
96-
*/
97-
private static class HideDelegateAddMethods {
98-
public Structure add(String ignoredKey, Boolean ignoredValue) {
99-
return null;
100-
}
101-
102-
public Structure add(String ignoredKey, Double ignoredValue) {
103-
return null;
104-
}
105-
106-
public Structure add(String ignoredKey, String ignoredValue) {
107-
return null;
108-
}
109-
110-
public Structure add(String ignoredKey, Value ignoredValue) {
111-
return null;
112-
}
113-
114-
public Structure add(String ignoredKey, Integer ignoredValue) {
115-
return null;
116-
}
117-
118-
public Structure add(String ignoredKey, List<Value> ignoredValue) {
119-
return null;
120-
}
121-
122-
public Structure add(String ignoredKey, Structure ignoredValue) {
123-
return null;
124-
}
125-
126-
public Structure add(String ignoredKey, Instant ignoredValue) {
127-
return null;
128-
}
129-
}
20+
EvaluationContext merge(EvaluationContext overridingContext);
13021
}

Diff for: src/main/java/dev/openfeature/sdk/HookSupport.java

+18-25
Original file line numberDiff line numberDiff line change
@@ -11,46 +11,44 @@
1111

1212
@Slf4j
1313
@RequiredArgsConstructor
14-
@SuppressWarnings({"unchecked", "rawtypes"})
14+
@SuppressWarnings({ "unchecked", "rawtypes" })
1515
class HookSupport {
1616

1717
public void errorHooks(FlagValueType flagValueType, HookContext hookCtx, Exception e, List<Hook> hooks,
18-
Map<String, Object> hints) {
18+
Map<String, Object> hints) {
1919
executeHooks(flagValueType, hooks, "error", hook -> hook.error(hookCtx, e, hints));
2020
}
2121

2222
public void afterAllHooks(FlagValueType flagValueType, HookContext hookCtx, List<Hook> hooks,
23-
Map<String, Object> hints) {
23+
Map<String, Object> hints) {
2424
executeHooks(flagValueType, hooks, "finally", hook -> hook.finallyAfter(hookCtx, hints));
2525
}
2626

2727
public void afterHooks(FlagValueType flagValueType, HookContext hookContext, FlagEvaluationDetails details,
28-
List<Hook> hooks, Map<String, Object> hints) {
28+
List<Hook> hooks, Map<String, Object> hints) {
2929
executeHooksUnchecked(flagValueType, hooks, hook -> hook.after(hookContext, details, hints));
3030
}
3131

3232
private <T> void executeHooks(
3333
FlagValueType flagValueType, List<Hook> hooks,
3434
String hookMethod,
35-
Consumer<Hook<T>> hookCode
36-
) {
35+
Consumer<Hook<T>> hookCode) {
3736
if (hooks != null) {
3837
hooks
39-
.stream()
40-
.filter(hook -> hook.supportsFlagValueType(flagValueType))
41-
.forEach(hook -> executeChecked(hook, hookCode, hookMethod));
38+
.stream()
39+
.filter(hook -> hook.supportsFlagValueType(flagValueType))
40+
.forEach(hook -> executeChecked(hook, hookCode, hookMethod));
4241
}
4342
}
4443

4544
private <T> void executeHooksUnchecked(
4645
FlagValueType flagValueType, List<Hook> hooks,
47-
Consumer<Hook<T>> hookCode
48-
) {
46+
Consumer<Hook<T>> hookCode) {
4947
if (hooks != null) {
5048
hooks
51-
.stream()
52-
.filter(hook -> hook.supportsFlagValueType(flagValueType))
53-
.forEach(hookCode::accept);
49+
.stream()
50+
.filter(hook -> hook.supportsFlagValueType(flagValueType))
51+
.forEach(hookCode::accept);
5452
}
5553
}
5654

@@ -63,13 +61,16 @@ private <T> void executeChecked(Hook<T> hook, Consumer<Hook<T>> hookCode, String
6361
}
6462

6563
public EvaluationContext beforeHooks(FlagValueType flagValueType, HookContext hookCtx, List<Hook> hooks,
66-
Map<String, Object> hints) {
64+
Map<String, Object> hints) {
6765
Stream<EvaluationContext> result = callBeforeHooks(flagValueType, hookCtx, hooks, hints);
68-
return EvaluationContext.merge(hookCtx.getCtx(), collectContexts(result));
66+
return hookCtx.getCtx().merge(
67+
result.reduce(hookCtx.getCtx(), (EvaluationContext accumulated, EvaluationContext current) -> {
68+
return accumulated.merge(current);
69+
}));
6970
}
7071

7172
private Stream<EvaluationContext> callBeforeHooks(FlagValueType flagValueType, HookContext hookCtx,
72-
List<Hook> hooks, Map<String, Object> hints) {
73+
List<Hook> hooks, Map<String, Object> hints) {
7374
// These traverse backwards from normal.
7475
List<Hook> reversedHooks = IntStream
7576
.range(0, hooks.size())
@@ -86,12 +87,4 @@ private Stream<EvaluationContext> callBeforeHooks(FlagValueType flagValueType, H
8687
.map(Optional::get)
8788
.map(EvaluationContext.class::cast);
8889
}
89-
90-
//for whatever reason, an error `error: incompatible types: invalid method reference` is thrown on compilation
91-
// with javac
92-
//when the reduce call is appended directly as stream call chain above. moving it to its own method works however...
93-
private EvaluationContext collectContexts(Stream<EvaluationContext> result) {
94-
return result
95-
.reduce(new EvaluationContext(), EvaluationContext::merge, EvaluationContext::merge);
96-
}
9790
}
+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package dev.openfeature.sdk;
2+
3+
import java.time.Instant;
4+
import java.util.HashMap;
5+
import java.util.List;
6+
import java.util.Map;
7+
8+
import lombok.Getter;
9+
import lombok.Setter;
10+
import lombok.ToString;
11+
import lombok.experimental.Delegate;
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 MutableContext implements EvaluationContext {
22+
23+
@Setter() @Getter private String targetingKey;
24+
@Delegate(excludes = HideDelegateAddMethods.class) private final MutableStructure structure;
25+
26+
public MutableContext() {
27+
this.structure = new MutableStructure();
28+
this.targetingKey = "";
29+
}
30+
31+
public MutableContext(String targetingKey) {
32+
this();
33+
this.targetingKey = targetingKey;
34+
}
35+
36+
public MutableContext(Map<String, Value> attributes) {
37+
this.structure = new MutableStructure(attributes);
38+
this.targetingKey = "";
39+
}
40+
41+
public MutableContext(String targetingKey, Map<String, Value> attributes) {
42+
this(attributes);
43+
this.targetingKey = targetingKey;
44+
}
45+
46+
// override @Delegate methods so that we can use "add" methods and still return MutableContext, not Structure
47+
public MutableContext add(String key, Boolean value) {
48+
this.structure.add(key, value);
49+
return this;
50+
}
51+
52+
public MutableContext add(String key, String value) {
53+
this.structure.add(key, value);
54+
return this;
55+
}
56+
57+
public MutableContext add(String key, Integer value) {
58+
this.structure.add(key, value);
59+
return this;
60+
}
61+
62+
public MutableContext add(String key, Double value) {
63+
this.structure.add(key, value);
64+
return this;
65+
}
66+
67+
public MutableContext add(String key, Instant value) {
68+
this.structure.add(key, value);
69+
return this;
70+
}
71+
72+
public MutableContext add(String key, Structure value) {
73+
this.structure.add(key, value);
74+
return this;
75+
}
76+
77+
public MutableContext add(String key, List<Value> value) {
78+
this.structure.add(key, value);
79+
return this;
80+
}
81+
82+
/**
83+
* Merges this EvaluationContext objects with the second overriding the this in
84+
* case of conflict.
85+
*
86+
* @param overridingContext overriding context
87+
* @return resulting merged context
88+
*/
89+
@Override
90+
public EvaluationContext merge(EvaluationContext overridingContext) {
91+
if (overridingContext == null) {
92+
return new MutableContext(this.asMap());
93+
}
94+
95+
Map<String, Value> merged = new HashMap<String, Value>();
96+
97+
merged.putAll(this.asMap());
98+
merged.putAll(overridingContext.asMap());
99+
EvaluationContext ec = new MutableContext(merged);
100+
101+
if (this.getTargetingKey() != null && !this.getTargetingKey().trim().equals("")) {
102+
ec.setTargetingKey(this.getTargetingKey());
103+
}
104+
105+
if (overridingContext.getTargetingKey() != null && !overridingContext.getTargetingKey().trim().equals("")) {
106+
ec.setTargetingKey(overridingContext.getTargetingKey());
107+
}
108+
109+
return ec;
110+
}
111+
112+
/**
113+
* Hidden class to tell Lombok not to copy these methods over via delegation.
114+
*/
115+
private static class HideDelegateAddMethods {
116+
public MutableStructure add(String ignoredKey, Boolean ignoredValue) {
117+
return null;
118+
}
119+
120+
public MutableStructure add(String ignoredKey, Double ignoredValue) {
121+
return null;
122+
}
123+
124+
public MutableStructure add(String ignoredKey, String ignoredValue) {
125+
return null;
126+
}
127+
128+
public MutableStructure add(String ignoredKey, Value ignoredValue) {
129+
return null;
130+
}
131+
132+
public MutableStructure add(String ignoredKey, Integer ignoredValue) {
133+
return null;
134+
}
135+
136+
public MutableStructure add(String ignoredKey, List<Value> ignoredValue) {
137+
return null;
138+
}
139+
140+
public MutableStructure add(String ignoredKey, MutableStructure ignoredValue) {
141+
return null;
142+
}
143+
144+
public MutableStructure add(String ignoredKey, Instant ignoredValue) {
145+
return null;
146+
}
147+
}
148+
}

0 commit comments

Comments
 (0)