8
8
9
9
import dev .openfeature .sdk .exceptions .GeneralError ;
10
10
import dev .openfeature .sdk .exceptions .OpenFeatureError ;
11
+ import dev .openfeature .sdk .internal .AutoCloseableLock ;
12
+ import dev .openfeature .sdk .internal .AutoCloseableReentrantReadWriteLock ;
11
13
import dev .openfeature .sdk .internal .ObjectUtils ;
12
14
import lombok .Getter ;
13
- import lombok .Setter ;
14
15
import lombok .extern .slf4j .Slf4j ;
15
16
16
17
@ Slf4j
@@ -22,12 +23,10 @@ public class OpenFeatureClient implements Client {
22
23
private final String name ;
23
24
@ Getter
24
25
private final String version ;
25
- @ Getter
26
26
private final List <Hook > clientHooks ;
27
27
private final HookSupport hookSupport ;
28
-
29
- @ Getter
30
- @ Setter
28
+ AutoCloseableReentrantReadWriteLock hooksLock = new AutoCloseableReentrantReadWriteLock ();
29
+ AutoCloseableReentrantReadWriteLock contextLock = new AutoCloseableReentrantReadWriteLock ();
31
30
private EvaluationContext evaluationContext ;
32
31
33
32
/**
@@ -46,9 +45,44 @@ public OpenFeatureClient(OpenFeatureAPI openFeatureAPI, String name, String vers
46
45
this .hookSupport = new HookSupport ();
47
46
}
48
47
48
+ /**
49
+ * {@inheritDoc}
50
+ */
49
51
@ Override
50
52
public void addHooks (Hook ... hooks ) {
51
- this .clientHooks .addAll (Arrays .asList (hooks ));
53
+ try (AutoCloseableLock __ = this .hooksLock .writeLockAutoCloseable ()) {
54
+ this .clientHooks .addAll (Arrays .asList (hooks ));
55
+ }
56
+ }
57
+
58
+ /**
59
+ * {@inheritDoc}
60
+ */
61
+ @ Override
62
+ public List <Hook > getHooks () {
63
+ try (AutoCloseableLock __ = this .hooksLock .readLockAutoCloseable ()) {
64
+ return this .clientHooks ;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * {@inheritDoc}
70
+ */
71
+ @ Override
72
+ public void setEvaluationContext (EvaluationContext evaluationContext ) {
73
+ try (AutoCloseableLock __ = contextLock .writeLockAutoCloseable ()) {
74
+ this .evaluationContext = evaluationContext ;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * {@inheritDoc}
80
+ */
81
+ @ Override
82
+ public EvaluationContext getEvaluationContext () {
83
+ try (AutoCloseableLock __ = contextLock .readLockAutoCloseable ()) {
84
+ return this .evaluationContext ;
85
+ }
52
86
}
53
87
54
88
private <T > FlagEvaluationDetails <T > evaluateFlag (FlagValueType type , String key , T defaultValue ,
@@ -57,34 +91,41 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key
57
91
() -> FlagEvaluationOptions .builder ().build ());
58
92
Map <String , Object > hints = Collections .unmodifiableMap (flagOptions .getHookHints ());
59
93
ctx = ObjectUtils .defaultIfNull (ctx , () -> new MutableContext ());
60
- FeatureProvider provider = ObjectUtils .defaultIfNull (openfeatureApi .getProvider (), () -> {
61
- log .debug ("No provider configured, using no-op provider." );
62
- return new NoOpProvider ();
63
- });
94
+
64
95
65
96
FlagEvaluationDetails <T > details = null ;
66
97
List <Hook > mergedHooks = null ;
67
98
HookContext <T > hookCtx = null ;
99
+ FeatureProvider provider = null ;
68
100
69
101
try {
102
+ final EvaluationContext apiContext ;
103
+ final EvaluationContext clientContext ;
70
104
71
- hookCtx = HookContext .from (key , type , this .getMetadata (),
72
- openfeatureApi .getProvider ().getMetadata (), ctx , defaultValue );
105
+ // openfeatureApi.getProvider() must be called once to maintain a consistent reference
106
+ provider = ObjectUtils .defaultIfNull (openfeatureApi .getProvider (), () -> {
107
+ log .debug ("No provider configured, using no-op provider." );
108
+ return new NoOpProvider ();
109
+ });
73
110
74
111
mergedHooks = ObjectUtils .merge (provider .getProviderHooks (), flagOptions .getHooks (), clientHooks ,
75
- openfeatureApi .getApiHooks ());
112
+ openfeatureApi .getHooks ());
76
113
77
- EvaluationContext ctxFromHook = hookSupport .beforeHooks (type , hookCtx , mergedHooks , hints );
78
-
79
- EvaluationContext invocationCtx = ctx .merge (ctxFromHook );
114
+ hookCtx = HookContext .from (key , type , this .getMetadata (),
115
+ provider .getMetadata (), ctx , defaultValue );
80
116
81
117
// merge of: API.context, client.context, invocation.context
82
- EvaluationContext apiContext = openfeatureApi .getEvaluationContext () != null
118
+ apiContext = openfeatureApi .getEvaluationContext () != null
83
119
? openfeatureApi .getEvaluationContext ()
84
120
: new MutableContext ();
85
- EvaluationContext clientContext = openfeatureApi .getEvaluationContext () != null
121
+ clientContext = openfeatureApi .getEvaluationContext () != null
86
122
? this .getEvaluationContext ()
87
123
: new MutableContext ();
124
+
125
+ EvaluationContext ctxFromHook = hookSupport .beforeHooks (type , hookCtx , mergedHooks , hints );
126
+
127
+ EvaluationContext invocationCtx = ctx .merge (ctxFromHook );
128
+
88
129
EvaluationContext mergedCtx = apiContext .merge (clientContext .merge (invocationCtx ));
89
130
90
131
ProviderEvaluation <T > providerEval = (ProviderEvaluation <T >) createProviderEvaluation (type , key ,
0 commit comments