Skip to content

Commit 11a0ccb

Browse files
committed
add wrapper around EventProvider, to manage and hide the provider state
Signed-off-by: christian.lutnik <[email protected]>
1 parent d0a2027 commit 11a0ccb

16 files changed

+472
-273
lines changed

src/main/java/dev/openfeature/sdk/EventProvider.java

Lines changed: 18 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package dev.openfeature.sdk;
22

3-
import dev.openfeature.sdk.exceptions.GeneralError;
4-
import dev.openfeature.sdk.exceptions.OpenFeatureError;
53
import dev.openfeature.sdk.internal.TriConsumer;
64

5+
import javax.annotation.Nullable;
6+
77
/**
88
* Abstract EventProvider. Providers must extend this class to support events.
99
* Emit events with {@link #emit(ProviderEvent, ProviderEventDetails)}. Please
@@ -17,47 +17,10 @@
1717
* @see FeatureProvider
1818
*/
1919
public abstract class EventProvider implements FeatureProvider {
20+
private @Nullable EventProviderListener eventProviderListener;
2021

21-
private ProviderState providerState = ProviderState.NOT_READY;
22-
23-
/**
24-
* {@inheritDoc}
25-
*/
26-
@Override
27-
public final ProviderState getState() {
28-
return providerState;
29-
}
30-
31-
@Override
32-
public final void initialize(EvaluationContext evaluationContext) throws Exception {
33-
try {
34-
doInitialization(evaluationContext);
35-
providerState = ProviderState.READY;
36-
} catch (OpenFeatureError openFeatureError) {
37-
if (ErrorCode.PROVIDER_FATAL.equals(openFeatureError.getErrorCode())) {
38-
providerState = ProviderState.FATAL;
39-
} else {
40-
providerState = ProviderState.ERROR;
41-
}
42-
throw openFeatureError;
43-
} catch (Exception e) {
44-
providerState = ProviderState.ERROR;
45-
throw new GeneralError(e);
46-
}
47-
}
48-
49-
protected void doInitialization(EvaluationContext evaluationContext) throws Exception {
50-
// Intentionally left blank, to be implemented by inheritors
51-
}
52-
53-
@Override
54-
public final void shutdown() {
55-
providerState = ProviderState.NOT_READY;
56-
doShutdown();
57-
}
58-
59-
protected void doShutdown() {
60-
// Intentionally left blank, to be implemented by inheritors
22+
void setEventProviderListener(@Nullable EventProviderListener eventProviderListener) {
23+
this.eventProviderListener = eventProviderListener;
6124
}
6225

6326
private TriConsumer<EventProvider, ProviderEvent, ProviderEventDetails> onEmit = null;
@@ -92,12 +55,8 @@ void detach() {
9255
* @param details The details of the event
9356
*/
9457
public void emit(ProviderEvent event, ProviderEventDetails details) {
95-
if (ProviderEvent.PROVIDER_ERROR.equals(event)) {
96-
providerState = ProviderState.ERROR;
97-
} else if (ProviderEvent.PROVIDER_STALE.equals(event)) {
98-
providerState = ProviderState.STALE;
99-
} else if (ProviderEvent.PROVIDER_READY.equals(event)) {
100-
providerState = ProviderState.READY;
58+
if (eventProviderListener != null) {
59+
eventProviderListener.onEmit(event, details);
10160
}
10261
if (this.onEmit != null) {
10362
this.onEmit.accept(this, event, details);
@@ -144,4 +103,15 @@ public void emitProviderStale(ProviderEventDetails details) {
144103
public void emitProviderError(ProviderEventDetails details) {
145104
emit(ProviderEvent.PROVIDER_ERROR, details);
146105
}
106+
107+
@Override
108+
public boolean equals(Object obj) {
109+
if (obj == null) {
110+
return false;
111+
}
112+
if (obj instanceof FeatureProviderWrapper) {
113+
return this == ((FeatureProviderWrapper) obj).getDelegate();
114+
}
115+
return this == obj;
116+
}
147117
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package dev.openfeature.sdk;
2+
3+
@FunctionalInterface
4+
interface EventProviderListener {
5+
void onEmit(ProviderEvent event, ProviderEventDetails details);
6+
}

src/main/java/dev/openfeature/sdk/FeatureProvider.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ default void shutdown() {
5656
}
5757

5858
/**
59+
* @deprecated State is handled by the SDK internally.
60+
* <p>
5961
* Returns a representation of the current readiness of the provider.
6062
* If the provider needs to be initialized, it should return {@link ProviderState#NOT_READY}.
6163
* If the provider is in an error state, it should return {@link ProviderState#ERROR}.
@@ -65,6 +67,7 @@ default void shutdown() {
6567
*
6668
* @return ProviderState
6769
*/
70+
@Deprecated
6871
default ProviderState getState() {
6972
return ProviderState.READY;
7073
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package dev.openfeature.sdk;
2+
3+
import dev.openfeature.sdk.exceptions.OpenFeatureError;
4+
5+
import java.util.List;
6+
import java.util.concurrent.atomic.AtomicBoolean;
7+
8+
public class FeatureProviderWrapper implements FeatureProvider, EventProviderListener {
9+
private final FeatureProvider delegate;
10+
private final AtomicBoolean isInitialized = new AtomicBoolean();
11+
private ProviderState state = ProviderState.NOT_READY;
12+
13+
public FeatureProviderWrapper(FeatureProvider delegate) {
14+
this.delegate = delegate;
15+
if (delegate instanceof EventProvider) {
16+
((EventProvider) delegate).setEventProviderListener(this);
17+
}
18+
}
19+
20+
@Override
21+
public Metadata getMetadata() {
22+
return delegate.getMetadata();
23+
}
24+
25+
@Override
26+
public List<Hook> getProviderHooks() {
27+
return delegate.getProviderHooks();
28+
}
29+
30+
@Override
31+
public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
32+
return delegate.getBooleanEvaluation(key, defaultValue, ctx);
33+
}
34+
35+
@Override
36+
public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
37+
return delegate.getStringEvaluation(key, defaultValue, ctx);
38+
}
39+
40+
@Override
41+
public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) {
42+
return delegate.getIntegerEvaluation(key, defaultValue, ctx);
43+
}
44+
45+
@Override
46+
public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) {
47+
return delegate.getDoubleEvaluation(key, defaultValue, ctx);
48+
}
49+
50+
@Override
51+
public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) {
52+
return delegate.getObjectEvaluation(key, defaultValue, ctx);
53+
}
54+
55+
@Override
56+
public void initialize(EvaluationContext evaluationContext) throws Exception {
57+
if (isInitialized.getAndSet(true)) {
58+
return;
59+
}
60+
try {
61+
delegate.initialize(evaluationContext);
62+
state = ProviderState.READY;
63+
} catch (OpenFeatureError openFeatureError) {
64+
if (ErrorCode.PROVIDER_FATAL.equals(openFeatureError.getErrorCode())) {
65+
state = ProviderState.FATAL;
66+
} else {
67+
state = ProviderState.ERROR;
68+
}
69+
isInitialized.set(false);
70+
throw openFeatureError;
71+
} catch (Exception e) {
72+
state = ProviderState.ERROR;
73+
isInitialized.set(false);
74+
throw e;
75+
}
76+
}
77+
78+
@Override
79+
public void shutdown() {
80+
delegate.shutdown();
81+
state = ProviderState.NOT_READY;
82+
isInitialized.set(false);
83+
}
84+
85+
@Override
86+
public ProviderState getState() {
87+
return state;
88+
}
89+
90+
@Override
91+
public int hashCode() {
92+
return delegate.hashCode();
93+
}
94+
95+
@Override
96+
public boolean equals(Object obj) {
97+
if (this == obj) return true;
98+
if (obj instanceof FeatureProviderWrapper) {
99+
return delegate.equals(((FeatureProviderWrapper) obj).delegate);
100+
}
101+
return delegate.equals(obj);
102+
}
103+
104+
@Override
105+
public void onEmit(ProviderEvent event, ProviderEventDetails details) {
106+
if (ProviderEvent.PROVIDER_ERROR.equals(event)) {
107+
state = ProviderState.ERROR;
108+
} else if (ProviderEvent.PROVIDER_STALE.equals(event)) {
109+
state = ProviderState.STALE;
110+
} else if (ProviderEvent.PROVIDER_READY.equals(event)) {
111+
state = ProviderState.READY;
112+
}
113+
}
114+
115+
FeatureProvider getDelegate(){
116+
return delegate;
117+
}
118+
}

src/main/java/dev/openfeature/sdk/NoOpProvider.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,22 @@ public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double default
5959

6060
@Override
6161
public ProviderEvaluation<Value> getObjectEvaluation(String key, Value defaultValue,
62-
EvaluationContext invocationContext) {
62+
EvaluationContext invocationContext) {
6363
return ProviderEvaluation.<Value>builder()
6464
.value(defaultValue)
6565
.variant(PASSED_IN_DEFAULT)
6666
.reason(Reason.DEFAULT.toString())
6767
.build();
6868
}
69+
70+
@Override
71+
public boolean equals(Object obj) {
72+
if (obj == null) {
73+
return false;
74+
}
75+
if (obj instanceof FeatureProviderWrapper) {
76+
return ((FeatureProviderWrapper) obj).getDelegate() == this;
77+
}
78+
return obj == this;
79+
}
6980
}

src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,13 @@ public FeatureProvider getProvider() {
275275
return providerRepository.getProvider();
276276
}
277277

278+
/**
279+
* Return the state of the default provider.
280+
*/
281+
public ProviderState getProviderState() {
282+
return providerRepository.getProviderState();
283+
}
284+
278285
/**
279286
* Fetch a provider for a domain. If not found, return the default.
280287
*
@@ -285,6 +292,17 @@ public FeatureProvider getProvider(String domain) {
285292
return providerRepository.getProvider(domain);
286293
}
287294

295+
/**
296+
* Get the state of the provider for a domain. If no provider with the domain is found, returns the state of the
297+
* default provider.
298+
*
299+
* @param domain The domain to look for.
300+
* @return A named {@link FeatureProvider}
301+
*/
302+
public ProviderState getProviderState(String domain) {
303+
return providerRepository.getProviderState(domain);
304+
}
305+
288306
/**
289307
* Adds hooks for globally, used for all evaluations.
290308
* Hooks are run in the order they're added in the before stage. They are run in reverse order for all other stages.

0 commit comments

Comments
 (0)