Skip to content

Commit d24380e

Browse files
author
Justin Abrahms
authored
Merge pull request #24 from lopitz/fix-classcastexceptions
Fixes ClassCastExceptions when hooks are called (address #23)
2 parents 7a9a5e9 + 3e11560 commit d24380e

19 files changed

+555
-221
lines changed

.github/workflows/pullrequest.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,4 @@ jobs:
3838
flags: unittests # optional
3939
name: pr coverage # optional
4040
fail_ci_if_error: true # optional (default = false)
41-
verbose: true # optional (default = false)
41+
verbose: true # optional (default = false)

lib/build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ dependencies {
4242
api 'org.apache.commons:commons-math3:3.6.1'
4343
}
4444

45+
java {
46+
toolchain {
47+
languageVersion.set(JavaLanguageVersion.of(8))
48+
}
49+
}
50+
4551
tasks.named('test') {
4652
// Use JUnit Platform for unit tests.
4753
useJUnitPlatform()
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package dev.openfeature.javasdk;
2+
3+
public interface BooleanHook extends Hook<Boolean> {
4+
5+
@Override
6+
default boolean supportsFlagValueType(FlagValueType flagValueType) {
7+
return FlagValueType.BOOLEAN == flagValueType;
8+
}
9+
}

lib/src/main/java/dev/openfeature/javasdk/FlagEvaluationDetails.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@
1111
*/
1212
@Data @Builder
1313
public class FlagEvaluationDetails<T> implements BaseEvaluation<T> {
14-
String flagKey;
15-
T value;
16-
@Nullable String variant;
17-
Reason reason;
18-
@Nullable String errorCode;
14+
private String flagKey;
15+
private T value;
16+
@Nullable private String variant;
17+
private Reason reason;
18+
@Nullable private String errorCode;
1919

2020
public static <T> FlagEvaluationDetails<T> from(ProviderEvaluation<T> providerEval, String flagKey) {
2121
return FlagEvaluationDetails.<T>builder()
Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
package dev.openfeature.javasdk;
22

3-
import com.google.common.collect.ImmutableMap;
4-
import lombok.Builder;
5-
import lombok.Data;
6-
import lombok.Singular;
3+
import java.util.*;
74

8-
import java.util.List;
5+
import lombok.*;
96

10-
@Data @Builder
7+
@Value
8+
@Builder
119
public class FlagEvaluationOptions {
12-
@Singular private List<Hook> hooks;
10+
@Singular
11+
List<Hook> hooks;
1312
@Builder.Default
14-
private ImmutableMap<String, Object> hookHints = ImmutableMap.of();
13+
Map<String, Object> hookHints = new HashMap<>();
1514
}
Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,55 @@
11
package dev.openfeature.javasdk;
22

3-
import com.google.common.collect.ImmutableMap;
4-
5-
import java.util.Optional;
3+
import java.util.*;
64

75
/**
86
* An extension point which can run around flag resolution. They are intended to be used as a way to add custom logic
97
* to the lifecycle of flag evaluation.
8+
*
109
* @param <T> The type of the flag being evaluated.
1110
*/
1211
public interface Hook<T> {
1312
/**
1413
* Runs before flag is resolved.
15-
* @param ctx Information about the particular flag evaluation
14+
*
15+
* @param ctx Information about the particular flag evaluation
1616
* @param hints An immutable mapping of data for users to communicate to the hooks.
1717
* @return An optional {@link EvaluationContext}. If returned, it will be merged with the EvaluationContext instances from other hooks, the client and API.
1818
*/
19-
default Optional<EvaluationContext> before(HookContext<T> ctx, ImmutableMap<String, Object> hints) {
19+
default Optional<EvaluationContext> before(HookContext<T> ctx, Map<String, Object> hints) {
2020
return Optional.empty();
2121
}
2222

2323
/**
2424
* Runs after a flag is resolved.
25-
* @param ctx Information about the particular flag evaluation
25+
*
26+
* @param ctx Information about the particular flag evaluation
2627
* @param details Information about how the flag was resolved, including any resolved values.
27-
* @param hints An immutable mapping of data for users to communicate to the hooks.
28+
* @param hints An immutable mapping of data for users to communicate to the hooks.
2829
*/
29-
default void after(HookContext<T> ctx, FlagEvaluationDetails<T> details, ImmutableMap<String, Object> hints) {}
30+
default void after(HookContext<T> ctx, FlagEvaluationDetails<T> details, Map<String, Object> hints) {
31+
}
3032

3133
/**
3234
* Run when evaluation encounters an error. This will always run. Errors thrown will be swallowed.
33-
* @param ctx Information about the particular flag evaluation
35+
*
36+
* @param ctx Information about the particular flag evaluation
3437
* @param error The exception that was thrown.
3538
* @param hints An immutable mapping of data for users to communicate to the hooks.
3639
*/
37-
default void error(HookContext<T> ctx, Exception error, ImmutableMap<String, Object> hints) {}
40+
default void error(HookContext<T> ctx, Exception error, Map<String, Object> hints) {
41+
}
3842

3943
/**
4044
* Run after flag evaluation, including any error processing. This will always run. Errors will be swallowed.
41-
* @param ctx Information about the particular flag evaluation
45+
*
46+
* @param ctx Information about the particular flag evaluation
4247
* @param hints An immutable mapping of data for users to communicate to the hooks.
4348
*/
44-
default void finallyAfter(HookContext<T> ctx, ImmutableMap<String, Object> hints) {}
49+
default void finallyAfter(HookContext<T> ctx, Map<String, Object> hints) {
50+
}
51+
52+
default boolean supportsFlagValueType(FlagValueType flagValueType) {
53+
return true;
54+
}
4555
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package dev.openfeature.javasdk;
2+
3+
import java.util.*;
4+
import java.util.function.*;
5+
import java.util.stream.Stream;
6+
7+
import com.google.common.collect.Lists;
8+
import lombok.RequiredArgsConstructor;
9+
import lombok.extern.slf4j.Slf4j;
10+
11+
@Slf4j
12+
@RequiredArgsConstructor
13+
@SuppressWarnings({"unchecked", "rawtypes"})
14+
class HookSupport {
15+
16+
public void errorHooks(FlagValueType flagValueType, HookContext hookCtx, Exception e, List<Hook> hooks, Map<String, Object> hints) {
17+
executeHooks(flagValueType, hooks, "error", hook -> hook.error(hookCtx, e, hints));
18+
}
19+
20+
public void afterAllHooks(FlagValueType flagValueType, HookContext hookCtx, List<Hook> hooks, Map<String, Object> hints) {
21+
executeHooks(flagValueType, hooks, "finally", hook -> hook.finallyAfter(hookCtx, hints));
22+
}
23+
24+
public void afterHooks(FlagValueType flagValueType, HookContext hookContext, FlagEvaluationDetails details, List<Hook> hooks, Map<String, Object> hints) {
25+
executeHooksUnchecked(flagValueType, hooks, hook -> hook.after(hookContext, details, hints));
26+
}
27+
28+
private <T> void executeHooks(
29+
FlagValueType flagValueType, List<Hook> hooks,
30+
String hookMethod,
31+
Consumer<Hook<T>> hookCode
32+
) {
33+
hooks
34+
.stream()
35+
.filter(hook -> hook.supportsFlagValueType(flagValueType))
36+
.forEach(hook -> executeChecked(hook, hookCode, hookMethod));
37+
}
38+
39+
private <T> void executeHooksUnchecked(
40+
FlagValueType flagValueType, List<Hook> hooks,
41+
Consumer<Hook<T>> hookCode
42+
) {
43+
hooks
44+
.stream()
45+
.filter(hook -> hook.supportsFlagValueType(flagValueType))
46+
.forEach(hookCode::accept);
47+
}
48+
49+
private <T> void executeChecked(Hook<T> hook, Consumer<Hook<T>> hookCode, String hookMethod) {
50+
try {
51+
hookCode.accept(hook);
52+
} catch (Exception exception) {
53+
log.error("Exception when running {} hooks {}", hookMethod, hook.getClass(), exception);
54+
}
55+
}
56+
57+
public EvaluationContext beforeHooks(FlagValueType flagValueType, HookContext hookCtx, List<Hook> hooks, Map<String, Object> hints) {
58+
Stream<EvaluationContext> result = callBeforeHooks(flagValueType, hookCtx, hooks, hints);
59+
return EvaluationContext.merge(hookCtx.getCtx(), collectContexts(result));
60+
}
61+
62+
private Stream<EvaluationContext> callBeforeHooks(FlagValueType flagValueType, HookContext hookCtx, List<Hook> hooks, Map<String, Object> hints) {
63+
// These traverse backwards from normal.
64+
return Lists
65+
.reverse(hooks)
66+
.stream()
67+
.filter(hook -> hook.supportsFlagValueType(flagValueType))
68+
.map(hook -> hook.before(hookCtx, hints))
69+
.filter(Objects::nonNull)
70+
.filter(Optional::isPresent)
71+
.map(Optional::get)
72+
.map(EvaluationContext.class::cast);
73+
}
74+
75+
//for whatever reason, an error `error: incompatible types: invalid method reference` is thrown on compilation with javac
76+
//when the reduce call is appended directly as stream call chain above. moving it to its own method works however...
77+
private EvaluationContext collectContexts(Stream<EvaluationContext> result) {
78+
return result
79+
.reduce(new EvaluationContext(), EvaluationContext::merge, EvaluationContext::merge);
80+
}
81+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package dev.openfeature.javasdk;
2+
3+
public interface IntegerHook extends Hook<Integer> {
4+
5+
@Override
6+
default boolean supportsFlagValueType(FlagValueType flagValueType) {
7+
return FlagValueType.INTEGER == flagValueType;
8+
}
9+
}

0 commit comments

Comments
 (0)