Skip to content

Commit

Permalink
feat: added implementation of immutable evaluation context (#210)
Browse files Browse the repository at this point in the history
added immutable context implementation

Signed-off-by: thiyagu06 <[email protected]>
Co-authored-by: Todd Baert <[email protected]>
  • Loading branch information
thiyagu06 and toddbaert authored Jan 31, 2023
1 parent 43aea3b commit 6c14d87
Show file tree
Hide file tree
Showing 19 changed files with 619 additions and 175 deletions.
4 changes: 4 additions & 0 deletions src/main/java/dev/openfeature/sdk/EvaluationContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
public interface EvaluationContext extends Structure {
String getTargetingKey();

/**
* Mutating targeting key is not supported in all implementations and will be removed.
*/
@Deprecated
void setTargetingKey(String targetingKey);

/**
Expand Down
95 changes: 95 additions & 0 deletions src/main/java/dev/openfeature/sdk/ImmutableContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package dev.openfeature.sdk;

import java.util.HashMap;
import java.util.Map;

import lombok.Getter;
import lombok.ToString;
import lombok.experimental.Delegate;

/**
* The EvaluationContext is a container for arbitrary contextual data
* that can be used as a basis for dynamic evaluation.
* The ImmutableContext is an EvaluationContext implementation which is threadsafe, and whose attributes can
* not be modified after instantiation.
*/
@ToString
@SuppressWarnings("PMD.BeanMembersShouldSerialize")
public final class ImmutableContext implements EvaluationContext {

@Getter
private final String targetingKey;
@Delegate
private final Structure structure;

/**
* Create an immutable context with an empty targeting_key and attributes provided.
*/
public ImmutableContext() {
this("", new HashMap<>());
}

/**
* Create an immutable context with given targeting_key provided.
*
* @param targetingKey targeting key
*/
public ImmutableContext(String targetingKey) {
this(targetingKey, new HashMap<>());
}

/**
* Create an immutable context with an attributes provided.
*
* @param attributes evaluation context attributes
*/
public ImmutableContext(Map<String, Value> attributes) {
this("", attributes);
}

/**
* Create an immutable context with given targetingKey and attributes provided.
*
* @param targetingKey targeting key
* @param attributes evaluation context attributes
*/
public ImmutableContext(String targetingKey, Map<String, Value> attributes) {
this.structure = new ImmutableStructure(attributes);
this.targetingKey = targetingKey;
}

/**
* Mutating targeting key is not supported in ImmutableContext and will be removed.
*/
@Override
@Deprecated
public void setTargetingKey(String targetingKey) {
throw new UnsupportedOperationException("changing of targeting key is not allowed");
}

/**
* Merges this EvaluationContext object with the passed EvaluationContext, overriding in case of conflict.
*
* @param overridingContext overriding context
* @return resulting merged context
*/
@Override
public EvaluationContext merge(EvaluationContext overridingContext) {
if (overridingContext == null) {
return new ImmutableContext(this.targetingKey, this.asMap());
}
String newTargetingKey = "";
if (this.getTargetingKey() != null && !this.getTargetingKey().trim().equals("")) {
newTargetingKey = this.getTargetingKey();
}

if (overridingContext.getTargetingKey() != null && !overridingContext.getTargetingKey().trim().equals("")) {
newTargetingKey = overridingContext.getTargetingKey();
}
Map<String, Value> merged = new HashMap<>();

merged.putAll(this.asMap());
merged.putAll(overridingContext.asMap());
return new ImmutableContext(newTargetingKey, merged);
}
}
87 changes: 87 additions & 0 deletions src/main/java/dev/openfeature/sdk/ImmutableStructure.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package dev.openfeature.sdk;

import lombok.EqualsAndHashCode;
import lombok.ToString;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
* {@link ImmutableStructure} represents a potentially nested object type which is used to represent
* structured data.
* The ImmutableStructure is a Structure implementation which is threadsafe, and whose attributes can
* not be modified after instantiation.
*/
@ToString
@EqualsAndHashCode
@SuppressWarnings({"PMD.BeanMembersShouldSerialize", "checkstyle:MissingJavadocType"})
public final class ImmutableStructure implements Structure {

private final Map<String, Value> attributes;

/**
* create an immutable structure with the empty attributes.
*/
public ImmutableStructure() {
this(new HashMap<>());
}

/**
* create immutable structure with the given attributes.
*
* @param attributes attributes.
*/
public ImmutableStructure(Map<String, Value> attributes) {
Map<String, Value> copy = attributes.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().clone()));
this.attributes = new HashMap<>(copy);
}

@Override
public Set<String> keySet() {
return new HashSet<>(this.attributes.keySet());
}

// getters
@Override
public Value getValue(String key) {
Value value = this.attributes.get(key);
return value.clone();
}

/**
* Get all values.
*
* @return all attributes on the structure
*/
@Override
public Map<String, Value> asMap() {
return attributes
.entrySet()
.stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> getValue(e.getKey())
));
}

/**
* Get all values, with primitives types.
*
* @return all attributes on the structure into a Map
*/
@Override
public Map<String, Object> asObjectMap() {
return attributes
.entrySet()
.stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> convertValue(getValue(e.getKey()))
));
}
}
56 changes: 3 additions & 53 deletions src/main/java/dev/openfeature/sdk/MutableStructure.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package dev.openfeature.sdk;

import lombok.EqualsAndHashCode;
import lombok.ToString;

import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import dev.openfeature.sdk.exceptions.ValueNotConvertableError;
import lombok.EqualsAndHashCode;
import lombok.ToString;

/**
* {@link MutableStructure} represents a potentially nested object type which is used to represent
* structured data.
Expand Down Expand Up @@ -109,53 +108,4 @@ public Map<String, Object> asObjectMap() {
e -> convertValue(getValue(e.getKey()))
));
}

/**
* convertValue is converting the object type Value in a primitive type.
*
* @param value - Value object to convert
* @return an Object containing the primitive type.
*/
private Object convertValue(Value value) {
if (value.isBoolean()) {
return value.asBoolean();
}

if (value.isNumber()) {
Double valueAsDouble = value.asDouble();
if (valueAsDouble == Math.floor(valueAsDouble) && !Double.isInfinite(valueAsDouble)) {
return value.asInteger();
}
return valueAsDouble;
}

if (value.isString()) {
return value.asString();
}

if (value.isInstant()) {
return value.asInstant();
}

if (value.isList()) {
return value.asList()
.stream()
.map(this::convertValue)
.collect(Collectors.toList());
}

if (value.isStructure()) {
Structure s = value.asStructure();
return s.asMap()
.keySet()
.stream()
.collect(
Collectors.toMap(
key -> key,
key -> convertValue(s.getValue(key))
)
);
}
throw new ValueNotConvertableError();
}
}
6 changes: 3 additions & 3 deletions src/main/java/dev/openfeature/sdk/OpenFeatureClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key
FlagEvaluationOptions flagOptions = ObjectUtils.defaultIfNull(options,
() -> FlagEvaluationOptions.builder().build());
Map<String, Object> hints = Collections.unmodifiableMap(flagOptions.getHookHints());
ctx = ObjectUtils.defaultIfNull(ctx, () -> new MutableContext());
ctx = ObjectUtils.defaultIfNull(ctx, () -> new ImmutableContext());


FlagEvaluationDetails<T> details = null;
Expand All @@ -120,10 +120,10 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key
// merge of: API.context, client.context, invocation.context
apiContext = openfeatureApi.getEvaluationContext() != null
? openfeatureApi.getEvaluationContext()
: new MutableContext();
: new ImmutableContext();
clientContext = this.getEvaluationContext() != null
? this.getEvaluationContext()
: new MutableContext();
: new ImmutableContext();

EvaluationContext ctxFromHook = hookSupport.beforeHooks(type, hookCtx, mergedHooks, hints);

Expand Down
52 changes: 52 additions & 0 deletions src/main/java/dev/openfeature/sdk/Structure.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package dev.openfeature.sdk;

import dev.openfeature.sdk.exceptions.ValueNotConvertableError;

import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
* {@link Structure} represents a potentially nested object type which is used to represent
Expand Down Expand Up @@ -38,4 +41,53 @@ public interface Structure {
* @return all attributes on the structure into a Map
*/
Map<String, Object> asObjectMap();

/**
* convertValue is converting the object type Value in a primitive type.
*
* @param value - Value object to convert
* @return an Object containing the primitive type.
*/
default Object convertValue(Value value) {
if (value.isBoolean()) {
return value.asBoolean();
}

if (value.isNumber()) {
Double valueAsDouble = value.asDouble();
if (valueAsDouble == Math.floor(valueAsDouble) && !Double.isInfinite(valueAsDouble)) {
return value.asInteger();
}
return valueAsDouble;
}

if (value.isString()) {
return value.asString();
}

if (value.isInstant()) {
return value.asInstant();
}

if (value.isList()) {
return value.asList()
.stream()
.map(this::convertValue)
.collect(Collectors.toList());
}

if (value.isStructure()) {
Structure s = value.asStructure();
return s.asMap()
.keySet()
.stream()
.collect(
Collectors.toMap(
key -> key,
key -> convertValue(s.getValue(key))
)
);
}
throw new ValueNotConvertableError();
}
}
Loading

0 comments on commit 6c14d87

Please sign in to comment.