Skip to content

Commit 65a508c

Browse files
committed
fixup! feat: Update in-process resolver to support flag metadata open-feature#1102
Signed-off-by: christian.lutnik <[email protected]>
1 parent f8e69aa commit 65a508c

File tree

11 files changed

+222
-76
lines changed

11 files changed

+222
-76
lines changed

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java

+43-33
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import dev.openfeature.contrib.providers.flagd.resolver.process.model.FeatureFlag;
1010
import dev.openfeature.contrib.providers.flagd.resolver.process.storage.FlagStore;
1111
import dev.openfeature.contrib.providers.flagd.resolver.process.storage.Storage;
12+
import dev.openfeature.contrib.providers.flagd.resolver.process.storage.StorageQueryResult;
1213
import dev.openfeature.contrib.providers.flagd.resolver.process.storage.StorageStateChange;
1314
import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.Connector;
1415
import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.file.FileConnector;
@@ -46,7 +47,7 @@ public class InProcessResolver implements Resolver {
4647
* Resolves flag values using https://buf.build/open-feature/flagd/docs/main:flagd.sync.v1. Flags
4748
* are evaluated locally.
4849
*
49-
* @param options flagd options
50+
* @param options flagd options
5051
* @param connectedSupplier lambda providing current connection status from caller
5152
* @param onConnectionEvent lambda which handles changes in the connection/stream
5253
*/
@@ -161,14 +162,15 @@ static Connector getConnector(final FlagdOptions options) {
161162
}
162163

163164
private <T> ProviderEvaluation<T> resolve(Class<T> type, String key, EvaluationContext ctx) {
164-
final FeatureFlag flag = flagStore.getFlag(key);
165+
final StorageQueryResult storageQueryResult = flagStore.getFlag(key);
166+
final FeatureFlag flag = storageQueryResult.getFeatureFlag();
165167

166168
// missing flag
167169
if (flag == null) {
168170
return ProviderEvaluation.<T>builder()
169171
.errorMessage("flag: " + key + " not found")
170172
.errorCode(ErrorCode.FLAG_NOT_FOUND)
171-
.flagMetadata(fallBackMetadata)
173+
.flagMetadata(getFlagMetadata(storageQueryResult))
172174
.build();
173175
}
174176

@@ -177,7 +179,7 @@ private <T> ProviderEvaluation<T> resolve(Class<T> type, String key, EvaluationC
177179
return ProviderEvaluation.<T>builder()
178180
.errorMessage("flag: " + key + " is disabled")
179181
.errorCode(ErrorCode.FLAG_NOT_FOUND)
180-
.flagMetadata(getFlagMetadata(flag))
182+
.flagMetadata(getFlagMetadata(storageQueryResult))
181183
.build();
182184
}
183185

@@ -228,47 +230,55 @@ private <T> ProviderEvaluation<T> resolve(Class<T> type, String key, EvaluationC
228230
.value((T) value)
229231
.variant(resolvedVariant)
230232
.reason(reason)
231-
.flagMetadata(getFlagMetadata(flag))
233+
.flagMetadata(getFlagMetadata(storageQueryResult))
232234
.build();
233235
}
234236

235-
private ImmutableMetadata getFlagMetadata(FeatureFlag flag) {
236-
if (flag == null) {
237-
return fallBackMetadata;
237+
private ImmutableMetadata getFlagMetadata(StorageQueryResult storageQueryResult) {
238+
ImmutableMetadata.ImmutableMetadataBuilder metadataBuilder = ImmutableMetadata.builder();
239+
for (Map.Entry<String, Object> entry :
240+
storageQueryResult.getGlobalFlagMetadata().entrySet()) {
241+
addEntryToMetadataBuilder(metadataBuilder, entry.getKey(), entry.getValue());
238242
}
239243

240-
ImmutableMetadata.ImmutableMetadataBuilder metadataBuilder = ImmutableMetadata.builder();
241244
if (scope != null) {
242245
metadataBuilder.addString("scope", scope);
243246
}
244247

245-
for (Map.Entry<String, Object> entry : flag.getMetadata().entrySet()) {
246-
Object value = entry.getValue();
247-
if (value instanceof Number) {
248-
if (value instanceof Long) {
249-
metadataBuilder.addLong(entry.getKey(), (Long) value);
250-
continue;
251-
} else if (value instanceof Double) {
252-
metadataBuilder.addDouble(entry.getKey(), (Double) value);
253-
continue;
254-
} else if (value instanceof Integer) {
255-
metadataBuilder.addInteger(entry.getKey(), (Integer) value);
256-
continue;
257-
} else if (value instanceof Float) {
258-
metadataBuilder.addFloat(entry.getKey(), (Float) value);
259-
continue;
260-
}
261-
} else if (value instanceof Boolean) {
262-
metadataBuilder.addBoolean(entry.getKey(), (Boolean) value);
263-
continue;
264-
} else if (value instanceof String) {
265-
metadataBuilder.addString(entry.getKey(), (String) value);
266-
continue;
248+
FeatureFlag flag = storageQueryResult.getFeatureFlag();
249+
if (flag != null) {
250+
for (Map.Entry<String, Object> entry : flag.getMetadata().entrySet()) {
251+
addEntryToMetadataBuilder(metadataBuilder, entry.getKey(), entry.getValue());
267252
}
268-
throw new IllegalArgumentException("The type of the Metadata entry with key " + entry.getKey()
269-
+ " and value " + entry.getValue() + " is not supported");
270253
}
271254

272255
return metadataBuilder.build();
273256
}
257+
258+
private void addEntryToMetadataBuilder(
259+
ImmutableMetadata.ImmutableMetadataBuilder metadataBuilder, String key, Object value) {
260+
if (value instanceof Number) {
261+
if (value instanceof Long) {
262+
metadataBuilder.addLong(key, (Long) value);
263+
return;
264+
} else if (value instanceof Double) {
265+
metadataBuilder.addDouble(key, (Double) value);
266+
return;
267+
} else if (value instanceof Integer) {
268+
metadataBuilder.addInteger(key, (Integer) value);
269+
return;
270+
} else if (value instanceof Float) {
271+
metadataBuilder.addFloat(key, (Float) value);
272+
return;
273+
}
274+
} else if (value instanceof Boolean) {
275+
metadataBuilder.addBoolean(key, (Boolean) value);
276+
return;
277+
} else if (value instanceof String) {
278+
metadataBuilder.addString(key, (String) value);
279+
return;
280+
}
281+
throw new IllegalArgumentException(
282+
"The type of the Metadata entry with key " + key + " and value " + value + " is not supported");
283+
}
274284
}

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/model/FeatureFlag.java

-12
Original file line numberDiff line numberDiff line change
@@ -54,18 +54,6 @@ public FeatureFlag(String state, String defaultVariant, Map<String, Object> vari
5454
this.metadata = new HashMap<>();
5555
}
5656

57-
/**
58-
* Add global metadata to this FeatureFlag. Keys that already exist in the metadata of this flag are not
59-
* overwritten.
60-
*
61-
* @param metadata The metadata to add to this flag
62-
*/
63-
public void addMetadata(Map<String, Object> metadata) {
64-
for (Map.Entry<String, Object> entry : metadata.entrySet()) {
65-
this.metadata.putIfAbsent(entry.getKey(), entry.getValue());
66-
}
67-
}
68-
6957
/** Get targeting rule of the flag. */
7058
public String getTargeting() {
7159
return this.targeting == null ? EMPTY_TARGETING_STRING : this.targeting;

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/model/FlagParser.java

+5-8
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,7 @@ private FlagParser() {}
5353
}
5454

5555
/** Parse {@link String} for feature flags. */
56-
public static Map<String, FeatureFlag> parseString(final String configuration, boolean throwIfInvalid)
57-
throws IOException {
56+
public static ParsingResult parseString(final String configuration, boolean throwIfInvalid) throws IOException {
5857
if (SCHEMA_VALIDATOR != null) {
5958
try (JsonParser parser = MAPPER.createParser(configuration)) {
6059
Set<ValidationMessage> validationMessages = SCHEMA_VALIDATOR.validate(parser.readValueAsTree());
@@ -72,12 +71,12 @@ public static Map<String, FeatureFlag> parseString(final String configuration, b
7271
final String transposedConfiguration = transposeEvaluators(configuration);
7372

7473
final Map<String, FeatureFlag> flagMap = new HashMap<>();
75-
74+
final Map<String, Object> metadata;
7675
try (JsonParser parser = MAPPER.createParser(transposedConfiguration)) {
7776
final TreeNode treeNode = parser.readValueAsTree();
7877
final TreeNode flagNode = treeNode.get(FLAG_KEY);
7978
final TreeNode metadataNode = treeNode.get(METADATA_KEY);
80-
final Map<String, Object> metadata = parseMetadata(metadataNode);
79+
metadata = parseMetadata(metadataNode);
8180

8281
if (flagNode == null) {
8382
throw new IllegalArgumentException("No flag configurations found in the payload");
@@ -86,13 +85,11 @@ public static Map<String, FeatureFlag> parseString(final String configuration, b
8685
final Iterator<String> it = flagNode.fieldNames();
8786
while (it.hasNext()) {
8887
final String key = it.next();
89-
FeatureFlag flag = MAPPER.treeToValue(flagNode.get(key), FeatureFlag.class);
90-
flag.addMetadata(metadata);
91-
flagMap.put(key, flag);
88+
flagMap.put(key, MAPPER.treeToValue(flagNode.get(key), FeatureFlag.class));
9289
}
9390
}
9491

95-
return flagMap;
92+
return new ParsingResult(flagMap, metadata);
9693
}
9794

9895
private static Map<String, Object> parseMetadata(TreeNode metadataNode) throws JsonProcessingException {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package dev.openfeature.contrib.providers.flagd.resolver.process.model;
2+
3+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
4+
import java.util.Map;
5+
import lombok.Getter;
6+
7+
/**
8+
* The result of the parsing of a json string containing feature flag definitions.
9+
*/
10+
@Getter
11+
@SuppressFBWarnings(
12+
value = {"EI_EXPOSE_REP"},
13+
justification = "Feature flag comes as a Json configuration, hence they must be exposed")
14+
public class ParsingResult {
15+
private final Map<String, FeatureFlag> flags;
16+
private final Map<String, Object> globalFlagMetadata;
17+
18+
public ParsingResult(Map<String, FeatureFlag> flags, Map<String, Object> globalFlagMetadata) {
19+
this.flags = flags;
20+
this.globalFlagMetadata = globalFlagMetadata;
21+
}
22+
}

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/storage/FlagStore.java

+19-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import dev.openfeature.contrib.providers.flagd.resolver.process.model.FeatureFlag;
66
import dev.openfeature.contrib.providers.flagd.resolver.process.model.FlagParser;
7+
import dev.openfeature.contrib.providers.flagd.resolver.process.model.ParsingResult;
78
import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.Connector;
89
import dev.openfeature.contrib.providers.flagd.resolver.process.storage.connector.QueuePayload;
910
import dev.openfeature.flagd.grpc.sync.Sync.GetMetadataResponse;
@@ -35,6 +36,7 @@ public class FlagStore implements Storage {
3536
private final AtomicBoolean shutdown = new AtomicBoolean(false);
3637
private final BlockingQueue<StorageStateChange> stateBlockingQueue = new LinkedBlockingQueue<>(1);
3738
private final Map<String, FeatureFlag> flags = new HashMap<>();
39+
private final Map<String, Object> globalFlagMetadata = new HashMap<>();
3840

3941
private final Connector connector;
4042
private final boolean throwIfInvalid;
@@ -49,6 +51,7 @@ public FlagStore(final Connector connector, final boolean throwIfInvalid) {
4951
}
5052

5153
/** Initialize storage layer. */
54+
@Override
5255
public void init() throws Exception {
5356
connector.init();
5457
Thread streamer = new Thread(() -> {
@@ -68,6 +71,7 @@ public void init() throws Exception {
6871
*
6972
* @throws InterruptedException if stream can't be closed within deadline.
7073
*/
74+
@Override
7175
public void shutdown() throws InterruptedException {
7276
if (shutdown.getAndSet(true)) {
7377
return;
@@ -76,17 +80,23 @@ public void shutdown() throws InterruptedException {
7680
connector.shutdown();
7781
}
7882

79-
/** Retrieve flag for the given key. */
80-
public FeatureFlag getFlag(final String key) {
83+
/** Retrieve flag for the given key and the global flag metadata. */
84+
@Override
85+
public StorageQueryResult getFlag(final String key) {
8186
readLock.lock();
87+
FeatureFlag flag;
88+
Map<String, Object> metadata;
8289
try {
83-
return flags.get(key);
90+
flag = flags.get(key);
91+
metadata = new HashMap<>(globalFlagMetadata);
8492
} finally {
8593
readLock.unlock();
8694
}
95+
return new StorageQueryResult(flag, metadata);
8796
}
8897

8998
/** Retrieve blocking queue to check storage status. */
99+
@Override
90100
public BlockingQueue<StorageStateChange> getStateQueue() {
91101
return stateBlockingQueue;
92102
}
@@ -100,14 +110,18 @@ private void streamerListener(final Connector connector) throws InterruptedExcep
100110
case DATA:
101111
try {
102112
List<String> changedFlagsKeys;
103-
Map<String, FeatureFlag> flagMap =
104-
FlagParser.parseString(payload.getFlagData(), throwIfInvalid);
113+
ParsingResult parsingResult = FlagParser.parseString(payload.getFlagData(), throwIfInvalid);
114+
Map<String, FeatureFlag> flagMap = parsingResult.getFlags();
115+
Map<String, Object> globalFlagMetadataMap = parsingResult.getGlobalFlagMetadata();
116+
105117
Structure metadata = parseSyncMetadata(payload.getMetadataResponse());
106118
writeLock.lock();
107119
try {
108120
changedFlagsKeys = getChangedFlagsKeys(flagMap);
109121
flags.clear();
110122
flags.putAll(flagMap);
123+
globalFlagMetadata.clear();
124+
globalFlagMetadata.putAll(globalFlagMetadataMap);
111125
} finally {
112126
writeLock.unlock();
113127
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package dev.openfeature.contrib.providers.flagd.resolver.process.storage;
22

3-
import dev.openfeature.contrib.providers.flagd.resolver.process.model.FeatureFlag;
43
import java.util.concurrent.BlockingQueue;
54

65
/** Storage abstraction for resolver. */
@@ -9,7 +8,7 @@ public interface Storage {
98

109
void shutdown() throws InterruptedException;
1110

12-
FeatureFlag getFlag(final String key);
11+
StorageQueryResult getFlag(final String key);
1312

1413
BlockingQueue<StorageStateChange> getStateQueue();
1514
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package dev.openfeature.contrib.providers.flagd.resolver.process.storage;
2+
3+
import dev.openfeature.contrib.providers.flagd.resolver.process.model.FeatureFlag;
4+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
5+
import java.util.Map;
6+
import lombok.Getter;
7+
8+
/**
9+
* To be returned by the storage when a flag is queried. Contains the flag (iff a flag associated with the given key
10+
* exists, null otherwise) and global flag metadata
11+
*/
12+
@Getter
13+
@SuppressFBWarnings(
14+
value = {"EI_EXPOSE_REP"},
15+
justification = "The storage provides access to both feature flags and global metadata")
16+
public class StorageQueryResult {
17+
private final FeatureFlag featureFlag;
18+
private final Map<String, Object> globalFlagMetadata;
19+
20+
public StorageQueryResult(FeatureFlag featureFlag, Map<String, Object> globalFlagMetadata) {
21+
this.featureFlag = featureFlag;
22+
this.globalFlagMetadata = globalFlagMetadata;
23+
}
24+
}

0 commit comments

Comments
 (0)