Skip to content

Commit

Permalink
make .merge non-static
Browse files Browse the repository at this point in the history
Signed-off-by: Todd Baert <toddbaert@gmail.com>
  • Loading branch information
toddbaert committed Oct 5, 2022
1 parent 27b862b commit 282b9c1
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 86 deletions.
32 changes: 3 additions & 29 deletions src/main/java/dev/openfeature/javasdk/EvaluationContext.java
Original file line number Diff line number Diff line change
@@ -1,43 +1,17 @@
package dev.openfeature.javasdk;

import java.util.HashMap;
import java.util.Map;

@SuppressWarnings("PMD.BeanMembersShouldSerialize")
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 objects 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
*/
static EvaluationContext merge(EvaluationContext ctx1, EvaluationContext ctx2) {
if (ctx1 == null) {
return ctx2;
} else if (ctx2 == null) {
return ctx1;
}

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

merged.putAll(ctx1.asMap());
merged.putAll(ctx2.asMap());
EvaluationContext ec = new MutableContext(merged);

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;
}
EvaluationContext merge(EvaluationContext overridingContext);
}
43 changes: 18 additions & 25 deletions src/main/java/dev/openfeature/javasdk/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(new MutableContext(), (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(MutableContext.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 MutableContext(), EvaluationContext::merge, EvaluationContext::merge);
}
}
30 changes: 30 additions & 0 deletions src/main/java/dev/openfeature/javasdk/MutableContext.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.openfeature.javasdk;

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

Expand Down Expand Up @@ -72,6 +73,35 @@ public MutableContext add(String key, List<Value> 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
*/
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.
*/
Expand Down
59 changes: 30 additions & 29 deletions src/main/java/dev/openfeature/javasdk/OpenFeatureClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import lombok.extern.slf4j.Slf4j;

@Slf4j
@SuppressWarnings({"PMD.DataflowAnomalyAnalysis", "PMD.BeanMembersShouldSerialize", "unchecked", "rawtypes"})
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.BeanMembersShouldSerialize", "unchecked", "rawtypes" })
public class OpenFeatureClient implements Client {

private final OpenFeatureAPI openfeatureApi;
Expand All @@ -31,10 +31,12 @@ public class OpenFeatureClient implements Client {
private EvaluationContext evaluationContext;

/**
* Client for evaluating the flag. There may be multiples of these floating around.
* Client for evaluating the flag. There may be multiples of these floating
* around.
*
* @param openFeatureAPI Backing global singleton
* @param name Name of the client (used by observability tools).
* @param version Version of the client (used by observability tools).
* @param name Name of the client (used by observability tools).
* @param version Version of the client (used by observability tools).
*/
public OpenFeatureClient(OpenFeatureAPI openFeatureAPI, String name, String version) {
this.openfeatureApi = openFeatureAPI;
Expand All @@ -50,9 +52,9 @@ public void addHooks(Hook... hooks) {
}

private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key, T defaultValue,
EvaluationContext ctx, FlagEvaluationOptions options) {
EvaluationContext ctx, FlagEvaluationOptions options) {
FlagEvaluationOptions flagOptions = ObjectUtils.defaultIfNull(options,
() -> FlagEvaluationOptions.builder().build());
() -> FlagEvaluationOptions.builder().build());
Map<String, Object> hints = Collections.unmodifiableMap(flagOptions.getHookHints());
ctx = ObjectUtils.defaultIfNull(ctx, () -> new MutableContext());
FeatureProvider provider = ObjectUtils.defaultIfNull(openfeatureApi.getProvider(), () -> {
Expand All @@ -67,23 +69,23 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key
try {

hookCtx = HookContext.from(key, type, this.getMetadata(),
openfeatureApi.getProvider().getMetadata(), ctx, defaultValue);
openfeatureApi.getProvider().getMetadata(), ctx, defaultValue);

mergedHooks = ObjectUtils.merge(provider.getProviderHooks(), flagOptions.getHooks(), clientHooks,
openfeatureApi.getApiHooks());
openfeatureApi.getApiHooks());

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

EvaluationContext invocationCtx = EvaluationContext.merge(ctx, ctxFromHook);
EvaluationContext invocationCtx = ctx.merge(ctxFromHook);

// merge of: API.context, client.context, invocation.context
EvaluationContext mergedCtx = EvaluationContext.merge(
EvaluationContext.merge(
openfeatureApi.getEvaluationContext(),
this.getEvaluationContext()
),
invocationCtx
);
EvaluationContext apiContext = openfeatureApi.getEvaluationContext() != null
? openfeatureApi.getEvaluationContext()
: new MutableContext();
EvaluationContext clientContext = openfeatureApi.getEvaluationContext() != null
? this.getEvaluationContext()
: new MutableContext();
EvaluationContext mergedCtx = apiContext.merge(clientContext.merge(invocationCtx));

ProviderEvaluation<T> providerEval = (ProviderEvaluation<T>) createProviderEvaluation(type, key,
defaultValue, provider, mergedCtx);
Expand All @@ -96,7 +98,7 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key
details = FlagEvaluationDetails.<T>builder().build();
}
if (e instanceof OpenFeatureError) {
details.setErrorCode(((OpenFeatureError)e).getErrorCode());
details.setErrorCode(((OpenFeatureError) e).getErrorCode());
} else {
details.setErrorCode(ErrorCode.GENERAL);
}
Expand All @@ -116,8 +118,7 @@ private <T> ProviderEvaluation<?> createProviderEvaluation(
String key,
T defaultValue,
FeatureProvider provider,
EvaluationContext invocationContext
) {
EvaluationContext invocationContext) {
switch (type) {
case BOOLEAN:
return provider.getBooleanEvaluation(key, (Boolean) defaultValue, invocationContext);
Expand Down Expand Up @@ -146,7 +147,7 @@ public Boolean getBooleanValue(String key, Boolean defaultValue, EvaluationConte

@Override
public Boolean getBooleanValue(String key, Boolean defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return getBooleanDetails(key, defaultValue, ctx, options).getValue();
}

Expand All @@ -162,7 +163,7 @@ public FlagEvaluationDetails<Boolean> getBooleanDetails(String key, Boolean defa

@Override
public FlagEvaluationDetails<Boolean> getBooleanDetails(String key, Boolean defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return this.evaluateFlag(FlagValueType.BOOLEAN, key, defaultValue, ctx, options);
}

Expand All @@ -178,7 +179,7 @@ public String getStringValue(String key, String defaultValue, EvaluationContext

@Override
public String getStringValue(String key, String defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return getStringDetails(key, defaultValue, ctx, options).getValue();
}

Expand All @@ -194,7 +195,7 @@ public FlagEvaluationDetails<String> getStringDetails(String key, String default

@Override
public FlagEvaluationDetails<String> getStringDetails(String key, String defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return this.evaluateFlag(FlagValueType.STRING, key, defaultValue, ctx, options);
}

Expand All @@ -210,7 +211,7 @@ public Integer getIntegerValue(String key, Integer defaultValue, EvaluationConte

@Override
public Integer getIntegerValue(String key, Integer defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return getIntegerDetails(key, defaultValue, ctx, options).getValue();
}

Expand All @@ -226,7 +227,7 @@ public FlagEvaluationDetails<Integer> getIntegerDetails(String key, Integer defa

@Override
public FlagEvaluationDetails<Integer> getIntegerDetails(String key, Integer defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return this.evaluateFlag(FlagValueType.INTEGER, key, defaultValue, ctx, options);
}

Expand All @@ -242,7 +243,7 @@ public Double getDoubleValue(String key, Double defaultValue, EvaluationContext

@Override
public Double getDoubleValue(String key, Double defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return this.evaluateFlag(FlagValueType.DOUBLE, key, defaultValue, ctx, options).getValue();
}

Expand All @@ -258,7 +259,7 @@ public FlagEvaluationDetails<Double> getDoubleDetails(String key, Double default

@Override
public FlagEvaluationDetails<Double> getDoubleDetails(String key, Double defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return this.evaluateFlag(FlagValueType.DOUBLE, key, defaultValue, ctx, options);
}

Expand All @@ -285,13 +286,13 @@ public FlagEvaluationDetails<Value> getObjectDetails(String key, Value defaultVa

@Override
public FlagEvaluationDetails<Value> getObjectDetails(String key, Value defaultValue,
EvaluationContext ctx) {
EvaluationContext ctx) {
return getObjectDetails(key, defaultValue, ctx, FlagEvaluationOptions.builder().build());
}

@Override
public FlagEvaluationDetails<Value> getObjectDetails(String key, Value defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return this.evaluateFlag(FlagValueType.OBJECT, key, defaultValue, ctx, options);
}

Expand Down
6 changes: 3 additions & 3 deletions src/test/java/dev/openfeature/javasdk/EvalContextTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,16 +137,16 @@ public class EvalContextTest {
EvaluationContext ctx1 = new MutableContext(key1);
EvaluationContext ctx2 = new MutableContext();

EvaluationContext ctxMerged = EvaluationContext.merge(ctx1, ctx2);
EvaluationContext ctxMerged = ctx1.merge(ctx2);
assertEquals(key1, ctxMerged.getTargetingKey());

String key2 = "key2";
ctx2.setTargetingKey(key2);
ctxMerged = EvaluationContext.merge(ctx1, ctx2);
ctxMerged = ctx1.merge(ctx2);
assertEquals(key2, ctxMerged.getTargetingKey());

ctx2.setTargetingKey(" ");
ctxMerged = EvaluationContext.merge(ctx1, ctx2);
ctxMerged = ctx1.merge(ctx2);
assertEquals(key1, ctxMerged.getTargetingKey());
}

Expand Down

0 comments on commit 282b9c1

Please sign in to comment.