Skip to content

Commit 1b08e3d

Browse files
alexandraoberaignermdxabuaepflibeeme1mrchrfwow
authored
feat: add hook data (open-feature#1587)
* Added hook data Signed-off-by: mdxabu <[email protected]> * to support null values while maintaining thread safety, you must avoid ConcurrentHashMap and use a synchronized HashMap instead Signed-off-by: mdxabu <[email protected]> * Build successfully runned:spotless plugin Signed-off-by: mdxabu <[email protected]> * fixup: fix comment Co-authored-by: alexandraoberaigner <[email protected]> Signed-off-by: Simon Schrottner <[email protected]> * fixup: remove synchronized map Co-authored-by: alexandraoberaigner <[email protected]> Signed-off-by: Simon Schrottner <[email protected]> * fixup: removed unneeded import Signed-off-by: Simon Schrottner <[email protected]> * fixup: spotless Signed-off-by: Simon Schrottner <[email protected]> * Update src/main/java/dev/openfeature/sdk/HookData.java Co-authored-by: chrfwow <[email protected]> Signed-off-by: Abu <[email protected]> * Update src/main/java/dev/openfeature/sdk/HookData.java Co-authored-by: chrfwow <[email protected]> Signed-off-by: Abu <[email protected]> * feat: add support for hook data sharing between stages Signed-off-by: Alexandra Oberaigner <[email protected]> * fixup: tests Signed-off-by: Simon Schrottner <[email protected]> * fixup: pr comments and extraction of own pair class Signed-off-by: Simon Schrottner <[email protected]> * feat: add HookContext decorator Signed-off-by: Simon Schrottner <[email protected]> * fixup: add default method for interface Signed-off-by: Simon Schrottner <[email protected]> * feat: performance boost Signed-off-by: Simon Schrottner <[email protected]> * feat: performance improvements Signed-off-by: Simon Schrottner <[email protected]> * fixup: lazy init Signed-off-by: Simon Schrottner <[email protected]> * fixup: pr comments Signed-off-by: Simon Schrottner <[email protected]> --------- Signed-off-by: mdxabu <[email protected]> Signed-off-by: Simon Schrottner <[email protected]> Signed-off-by: Abu <[email protected]> Signed-off-by: Alexandra Oberaigner <[email protected]> Co-authored-by: mdxabu <[email protected]> Co-authored-by: Simon Schrottner <[email protected]> Co-authored-by: Michael Beemer <[email protected]> Co-authored-by: chrfwow <[email protected]>
1 parent 5474c73 commit 1b08e3d

File tree

12 files changed

+642
-109
lines changed

12 files changed

+642
-109
lines changed
Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,13 @@
11
package dev.openfeature.sdk;
22

3-
import lombok.Builder;
4-
import lombok.NonNull;
5-
import lombok.Value;
6-
import lombok.With;
3+
import dev.openfeature.sdk.HookContextWithoutData.HookContextWithoutDataBuilder;
74

85
/**
9-
* A data class to hold immutable context that {@link Hook} instances use.
10-
*
11-
* @param <T> the type for the flag being evaluated
6+
* A interface to hold immutable context that {@link Hook} instances use.
127
*/
13-
@Value
14-
@Builder
15-
@With
16-
public class HookContext<T> {
17-
@NonNull String flagKey;
18-
19-
@NonNull FlagValueType type;
20-
21-
@NonNull T defaultValue;
22-
23-
@NonNull EvaluationContext ctx;
24-
25-
ClientMetadata clientMetadata;
26-
Metadata providerMetadata;
27-
8+
public interface HookContext<T> {
289
/**
29-
* Builds a {@link HookContext} instances from request data.
10+
* Builds a {@link HookContextWithoutData} instances from request data.
3011
*
3112
* @param key feature flag key
3213
* @param type flag value type
@@ -36,21 +17,39 @@ public class HookContext<T> {
3617
* @param defaultValue Fallback value
3718
* @param <T> type that the flag is evaluating against
3819
* @return resulting context for hook
20+
* @deprecated this should not be instantiated outside the SDK anymore
3921
*/
40-
public static <T> HookContext<T> from(
22+
@Deprecated
23+
static <T> HookContext<T> from(
4124
String key,
4225
FlagValueType type,
4326
ClientMetadata clientMetadata,
4427
Metadata providerMetadata,
4528
EvaluationContext ctx,
4629
T defaultValue) {
47-
return HookContext.<T>builder()
48-
.flagKey(key)
49-
.type(type)
50-
.clientMetadata(clientMetadata)
51-
.providerMetadata(providerMetadata)
52-
.ctx(ctx)
53-
.defaultValue(defaultValue)
54-
.build();
30+
return new HookContextWithoutData<>(key, type, defaultValue, ctx, clientMetadata, providerMetadata);
31+
}
32+
33+
/**
34+
* Returns a builder for our default HookContext object.
35+
*/
36+
static <T> HookContextWithoutDataBuilder<T> builder() {
37+
return HookContextWithoutData.<T>builder();
38+
}
39+
40+
String getFlagKey();
41+
42+
FlagValueType getType();
43+
44+
T getDefaultValue();
45+
46+
EvaluationContext getCtx();
47+
48+
ClientMetadata getClientMetadata();
49+
50+
Metadata getProviderMetadata();
51+
52+
default HookData getHookData() {
53+
return null;
5554
}
5655
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package dev.openfeature.sdk;
2+
3+
class HookContextWithData<T> implements HookContext<T> {
4+
private final HookContext<T> context;
5+
private final HookData data;
6+
7+
private HookContextWithData(HookContext<T> context, HookData data) {
8+
this.context = context;
9+
this.data = data;
10+
}
11+
12+
public static <T> HookContextWithData<T> of(HookContext<T> context, HookData data) {
13+
return new HookContextWithData<>(context, data);
14+
}
15+
16+
@Override
17+
public String getFlagKey() {
18+
return context.getFlagKey();
19+
}
20+
21+
@Override
22+
public FlagValueType getType() {
23+
return context.getType();
24+
}
25+
26+
@Override
27+
public T getDefaultValue() {
28+
return context.getDefaultValue();
29+
}
30+
31+
@Override
32+
public EvaluationContext getCtx() {
33+
return context.getCtx();
34+
}
35+
36+
@Override
37+
public ClientMetadata getClientMetadata() {
38+
return context.getClientMetadata();
39+
}
40+
41+
@Override
42+
public Metadata getProviderMetadata() {
43+
return context.getProviderMetadata();
44+
}
45+
46+
@Override
47+
public HookData getHookData() {
48+
return data;
49+
}
50+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package dev.openfeature.sdk;
2+
3+
import lombok.AccessLevel;
4+
import lombok.Builder;
5+
import lombok.Data;
6+
import lombok.NonNull;
7+
import lombok.Setter;
8+
import lombok.With;
9+
10+
/**
11+
* A data class to hold immutable context that {@link Hook} instances use.
12+
*
13+
* @param <T> the type for the flag being evaluated
14+
*/
15+
@Data
16+
@Builder
17+
@With
18+
@Setter(AccessLevel.PRIVATE)
19+
class HookContextWithoutData<T> implements HookContext<T> {
20+
@NonNull String flagKey;
21+
22+
@NonNull FlagValueType type;
23+
24+
@NonNull T defaultValue;
25+
26+
@Setter(AccessLevel.PACKAGE)
27+
@NonNull EvaluationContext ctx;
28+
29+
ClientMetadata clientMetadata;
30+
Metadata providerMetadata;
31+
32+
/**
33+
* Builds a {@link HookContextWithoutData} instances from request data.
34+
*
35+
* @param key feature flag key
36+
* @param type flag value type
37+
* @param clientMetadata info on which client is calling
38+
* @param providerMetadata info on the provider
39+
* @param defaultValue Fallback value
40+
* @param <T> type that the flag is evaluating against
41+
* @return resulting context for hook
42+
*/
43+
static <T> HookContextWithoutData<T> from(
44+
String key, FlagValueType type, ClientMetadata clientMetadata, Metadata providerMetadata, T defaultValue) {
45+
return new HookContextWithoutData<>(
46+
key, type, defaultValue, ImmutableContext.EMPTY, clientMetadata, providerMetadata);
47+
}
48+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package dev.openfeature.sdk;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
6+
/**
7+
* Hook data provides a way for hooks to maintain state across their execution stages.
8+
* Each hook instance gets its own isolated data store that persists only for the duration
9+
* of a single flag evaluation.
10+
*/
11+
public interface HookData {
12+
13+
/**
14+
* Sets a value for the given key.
15+
*
16+
* @param key the key to store the value under
17+
* @param value the value to store
18+
*/
19+
void set(String key, Object value);
20+
21+
/**
22+
* Gets the value for the given key.
23+
*
24+
* @param key the key to retrieve the value for
25+
* @return the value, or null if not found
26+
*/
27+
Object get(String key);
28+
29+
/**
30+
* Gets the value for the given key, cast to the specified type.
31+
*
32+
* @param <T> the type to cast to
33+
* @param key the key to retrieve the value for
34+
* @param type the class to cast to
35+
* @return the value cast to the specified type, or null if not found
36+
* @throws ClassCastException if the value cannot be cast to the specified type
37+
*/
38+
<T> T get(String key, Class<T> type);
39+
40+
/**
41+
* Default implementation uses a HashMap.
42+
*/
43+
static HookData create() {
44+
return new DefaultHookData();
45+
}
46+
47+
/**
48+
* Default implementation of HookData.
49+
*/
50+
class DefaultHookData implements HookData {
51+
private Map<String, Object> data;
52+
53+
@Override
54+
public void set(String key, Object value) {
55+
if (data == null) {
56+
data = new HashMap<>();
57+
}
58+
data.put(key, value);
59+
}
60+
61+
@Override
62+
public Object get(String key) {
63+
if (data == null) {
64+
return null;
65+
}
66+
return data.get(key);
67+
}
68+
69+
@Override
70+
public <T> T get(String key, Class<T> type) {
71+
Object value = get(key);
72+
if (value == null) {
73+
return null;
74+
}
75+
if (!type.isInstance(value)) {
76+
throw new ClassCastException("Value for key '" + key + "' is not of type " + type.getName());
77+
}
78+
return type.cast(value);
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)