Skip to content

Commit

Permalink
feat!: use evaluation context interface (#112)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
toddbaert and justinabrahms authored Oct 6, 2022
1 parent 3788a3b commit e9732b5
Show file tree
Hide file tree
Showing 17 changed files with 483 additions and 405 deletions.
131 changes: 11 additions & 120 deletions src/main/java/dev/openfeature/sdk/EvaluationContext.java
Original file line number Diff line number Diff line change
@@ -1,130 +1,21 @@
package dev.openfeature.sdk;

import java.time.Instant;
import java.util.List;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Delegate;

@ToString
/**
* The EvaluationContext is a container for arbitrary contextual data
* that can be used as a basis for dynamic evaluation.
*/
@SuppressWarnings("PMD.BeanMembersShouldSerialize")
public class EvaluationContext {

@Setter @Getter private String targetingKey;
@Delegate(excludes = HideDelegateAddMethods.class) private final Structure structure = new Structure();

public EvaluationContext() {
super();
this.targetingKey = "";
}

public EvaluationContext(String targetingKey) {
this();
this.targetingKey = targetingKey;
}
public interface EvaluationContext extends Structure {
String getTargetingKey();

void setTargetingKey(String targetingKey);

/**
* Merges two EvaluationContext objects with the second overriding the first in
* Merges this EvaluationContext object with the second overriding the this in
* case of conflict.
*
* @param ctx1 base context
* @param ctx2 overriding context
* @param overridingContext overriding context
* @return resulting merged context
*/
public static EvaluationContext merge(EvaluationContext ctx1, EvaluationContext ctx2) {
EvaluationContext ec = new EvaluationContext();
if (ctx1 == null) {
return ctx2;
} else if (ctx2 == null) {
return ctx1;
}

ec.structure.attributes.putAll(ctx1.structure.attributes);
ec.structure.attributes.putAll(ctx2.structure.attributes);

if (ctx1.getTargetingKey() != null && !ctx1.getTargetingKey().trim().equals("")) {
ec.setTargetingKey(ctx1.getTargetingKey());
}

if (ctx2.getTargetingKey() != null && !ctx2.getTargetingKey().trim().equals("")) {
ec.setTargetingKey(ctx2.getTargetingKey());
}

return ec;
}

// override @Delegate methods so that we can use "add" methods and still return EvaluationContext, not Structure
public EvaluationContext add(String key, Boolean value) {
this.structure.add(key, value);
return this;
}

public EvaluationContext add(String key, String value) {
this.structure.add(key, value);
return this;
}

public EvaluationContext add(String key, Integer value) {
this.structure.add(key, value);
return this;
}

public EvaluationContext add(String key, Double value) {
this.structure.add(key, value);
return this;
}

public EvaluationContext add(String key, Instant value) {
this.structure.add(key, value);
return this;
}

public EvaluationContext add(String key, Structure value) {
this.structure.add(key, value);
return this;
}

public EvaluationContext add(String key, List<Value> value) {
this.structure.add(key, value);
return this;
}

/**
* Hidden class to tell Lombok not to copy these methods over via delegation.
*/
private static class HideDelegateAddMethods {
public Structure add(String ignoredKey, Boolean ignoredValue) {
return null;
}

public Structure add(String ignoredKey, Double ignoredValue) {
return null;
}

public Structure add(String ignoredKey, String ignoredValue) {
return null;
}

public Structure add(String ignoredKey, Value ignoredValue) {
return null;
}

public Structure add(String ignoredKey, Integer ignoredValue) {
return null;
}

public Structure add(String ignoredKey, List<Value> ignoredValue) {
return null;
}

public Structure add(String ignoredKey, Structure ignoredValue) {
return null;
}

public Structure add(String ignoredKey, Instant ignoredValue) {
return null;
}
}
EvaluationContext merge(EvaluationContext overridingContext);
}
43 changes: 18 additions & 25 deletions src/main/java/dev/openfeature/sdk/HookSupport.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,46 +11,44 @@

@Slf4j
@RequiredArgsConstructor
@SuppressWarnings({"unchecked", "rawtypes"})
@SuppressWarnings({ "unchecked", "rawtypes" })
class HookSupport {

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

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

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

private <T> void executeHooks(
FlagValueType flagValueType, List<Hook> hooks,
String hookMethod,
Consumer<Hook<T>> hookCode
) {
Consumer<Hook<T>> hookCode) {
if (hooks != null) {
hooks
.stream()
.filter(hook -> hook.supportsFlagValueType(flagValueType))
.forEach(hook -> executeChecked(hook, hookCode, hookMethod));
.stream()
.filter(hook -> hook.supportsFlagValueType(flagValueType))
.forEach(hook -> executeChecked(hook, hookCode, hookMethod));
}
}

private <T> void executeHooksUnchecked(
FlagValueType flagValueType, List<Hook> hooks,
Consumer<Hook<T>> hookCode
) {
Consumer<Hook<T>> hookCode) {
if (hooks != null) {
hooks
.stream()
.filter(hook -> hook.supportsFlagValueType(flagValueType))
.forEach(hookCode::accept);
.stream()
.filter(hook -> hook.supportsFlagValueType(flagValueType))
.forEach(hookCode::accept);
}
}

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

public EvaluationContext beforeHooks(FlagValueType flagValueType, HookContext hookCtx, List<Hook> hooks,
Map<String, Object> hints) {
Map<String, Object> hints) {
Stream<EvaluationContext> result = callBeforeHooks(flagValueType, hookCtx, hooks, hints);
return EvaluationContext.merge(hookCtx.getCtx(), collectContexts(result));
return hookCtx.getCtx().merge(
result.reduce(hookCtx.getCtx(), (EvaluationContext accumulated, EvaluationContext current) -> {
return accumulated.merge(current);
}));
}

private Stream<EvaluationContext> callBeforeHooks(FlagValueType flagValueType, HookContext hookCtx,
List<Hook> hooks, Map<String, Object> hints) {
List<Hook> hooks, Map<String, Object> hints) {
// These traverse backwards from normal.
List<Hook> reversedHooks = IntStream
.range(0, hooks.size())
Expand All @@ -86,12 +87,4 @@ private Stream<EvaluationContext> callBeforeHooks(FlagValueType flagValueType, H
.map(Optional::get)
.map(EvaluationContext.class::cast);
}

//for whatever reason, an error `error: incompatible types: invalid method reference` is thrown on compilation
// with javac
//when the reduce call is appended directly as stream call chain above. moving it to its own method works however...
private EvaluationContext collectContexts(Stream<EvaluationContext> result) {
return result
.reduce(new EvaluationContext(), EvaluationContext::merge, EvaluationContext::merge);
}
}
148 changes: 148 additions & 0 deletions src/main/java/dev/openfeature/sdk/MutableContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package dev.openfeature.sdk;

import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Delegate;

/**
* The EvaluationContext is a container for arbitrary contextual data
* that can be used as a basis for dynamic evaluation.
* The MutableContext is an EvaluationContext implementation which is not threadsafe, and whose attributes can
* be modified after instantiation.
*/
@ToString
@SuppressWarnings("PMD.BeanMembersShouldSerialize")
public class MutableContext implements EvaluationContext {

@Setter() @Getter private String targetingKey;
@Delegate(excludes = HideDelegateAddMethods.class) private final MutableStructure structure;

public MutableContext() {
this.structure = new MutableStructure();
this.targetingKey = "";
}

public MutableContext(String targetingKey) {
this();
this.targetingKey = targetingKey;
}

public MutableContext(Map<String, Value> attributes) {
this.structure = new MutableStructure(attributes);
this.targetingKey = "";
}

public MutableContext(String targetingKey, Map<String, Value> attributes) {
this(attributes);
this.targetingKey = targetingKey;
}

// override @Delegate methods so that we can use "add" methods and still return MutableContext, not Structure
public MutableContext add(String key, Boolean value) {
this.structure.add(key, value);
return this;
}

public MutableContext add(String key, String value) {
this.structure.add(key, value);
return this;
}

public MutableContext add(String key, Integer value) {
this.structure.add(key, value);
return this;
}

public MutableContext add(String key, Double value) {
this.structure.add(key, value);
return this;
}

public MutableContext add(String key, Instant value) {
this.structure.add(key, value);
return this;
}

public MutableContext add(String key, Structure value) {
this.structure.add(key, value);
return this;
}

public MutableContext add(String key, List<Value> value) {
this.structure.add(key, value);
return this;
}

/**
* Merges this EvaluationContext objects with the second overriding the this in
* case of conflict.
*
* @param overridingContext overriding context
* @return resulting merged context
*/
@Override
public EvaluationContext merge(EvaluationContext overridingContext) {
if (overridingContext == null) {
return new MutableContext(this.asMap());
}

Map<String, Value> merged = new HashMap<String, Value>();

merged.putAll(this.asMap());
merged.putAll(overridingContext.asMap());
EvaluationContext ec = new MutableContext(merged);

if (this.getTargetingKey() != null && !this.getTargetingKey().trim().equals("")) {
ec.setTargetingKey(this.getTargetingKey());
}

if (overridingContext.getTargetingKey() != null && !overridingContext.getTargetingKey().trim().equals("")) {
ec.setTargetingKey(overridingContext.getTargetingKey());
}

return ec;
}

/**
* Hidden class to tell Lombok not to copy these methods over via delegation.
*/
private static class HideDelegateAddMethods {
public MutableStructure add(String ignoredKey, Boolean ignoredValue) {
return null;
}

public MutableStructure add(String ignoredKey, Double ignoredValue) {
return null;
}

public MutableStructure add(String ignoredKey, String ignoredValue) {
return null;
}

public MutableStructure add(String ignoredKey, Value ignoredValue) {
return null;
}

public MutableStructure add(String ignoredKey, Integer ignoredValue) {
return null;
}

public MutableStructure add(String ignoredKey, List<Value> ignoredValue) {
return null;
}

public MutableStructure add(String ignoredKey, MutableStructure ignoredValue) {
return null;
}

public MutableStructure add(String ignoredKey, Instant ignoredValue) {
return null;
}
}
}
Loading

0 comments on commit e9732b5

Please sign in to comment.