com.launchdarkly.sdk.server.EvaluatorPreprocessing Maven / Gradle / Ivy
package com.launchdarkly.sdk.server;
import com.google.common.collect.ImmutableSet;
import com.launchdarkly.sdk.EvaluationReason;
import com.launchdarkly.sdk.LDValue;
import com.launchdarkly.sdk.server.DataModel.Clause;
import com.launchdarkly.sdk.server.DataModel.FeatureFlag;
import com.launchdarkly.sdk.server.DataModel.Operator;
import com.launchdarkly.sdk.server.DataModel.Prerequisite;
import com.launchdarkly.sdk.server.DataModel.Rule;
import com.launchdarkly.sdk.server.DataModel.Segment;
import com.launchdarkly.sdk.server.DataModel.SegmentRule;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Pattern;
/**
* These methods precompute data that may help to reduce the overhead of feature flag evaluations. They
* are called from the afterDeserialized() methods of FeatureFlag and Segment, after those objects have
* been deserialized from JSON but before they have been made available to any other code (so these
* methods do not need to be thread-safe).
*
* If for some reason these methods have not been called before an evaluation happens, the evaluation
* logic must still be able to work without the precomputed data.
*/
abstract class EvaluatorPreprocessing {
private EvaluatorPreprocessing() {}
static final class ClauseExtra {
final Set valuesSet;
final List valuesExtra;
ClauseExtra(Set valuesSet, List valuesExtra) {
this.valuesSet = valuesSet;
this.valuesExtra = valuesExtra;
}
static final class ValueExtra {
final ZonedDateTime parsedDate;
final Pattern parsedRegex;
final SemanticVersion parsedSemVer;
ValueExtra(ZonedDateTime parsedDate, Pattern parsedRegex, SemanticVersion parsedSemVer) {
this.parsedDate = parsedDate;
this.parsedRegex = parsedRegex;
this.parsedSemVer = parsedSemVer;
}
}
}
static void preprocessFlag(FeatureFlag f) {
for (Prerequisite p: f.getPrerequisites()) {
EvaluatorPreprocessing.preprocessPrerequisite(p);
}
List rules = f.getRules();
int n = rules.size();
for (int i = 0; i < n; i++) {
preprocessFlagRule(rules.get(i), i);
}
}
static void preprocessSegment(Segment s) {
List rules = s.getRules();
int n = rules.size();
for (int i = 0; i < n; i++) {
preprocessSegmentRule(rules.get(i), i);
}
}
static void preprocessPrerequisite(Prerequisite p) {
// Precompute an immutable EvaluationReason instance that will be used if the prerequisite fails.
p.setPrerequisiteFailedReason(EvaluationReason.prerequisiteFailed(p.getKey()));
}
static void preprocessFlagRule(Rule r, int ruleIndex) {
// Precompute an immutable EvaluationReason instance that will be used if a user matches this rule.
r.setRuleMatchReason(EvaluationReason.ruleMatch(ruleIndex, r.getId()));
for (Clause c: r.getClauses()) {
preprocessClause(c);
}
}
static void preprocessSegmentRule(SegmentRule r, int ruleIndex) {
for (Clause c: r.getClauses()) {
preprocessClause(c);
}
}
static void preprocessClause(Clause c) {
Operator op = c.getOp();
if (op == null) {
return;
}
switch (op) {
case in:
// This is a special case where the clause is testing for an exact match against any of the
// clause values. Converting the value list to a Set allows us to do a fast lookup instead of
// a linear search. We do not do this for other operators (or if there are fewer than two
// values) because the slight extra overhead of a Set is not worthwhile in those case.
List values = c.getValues();
if (values.size() > 1) {
c.setPreprocessed(new ClauseExtra(ImmutableSet.copyOf(values), null));
}
break;
case matches:
c.setPreprocessed(preprocessClauseValues(c.getValues(), v ->
new ClauseExtra.ValueExtra(null, EvaluatorTypeConversion.valueToRegex(v), null)
));
break;
case after:
case before:
c.setPreprocessed(preprocessClauseValues(c.getValues(), v ->
new ClauseExtra.ValueExtra(EvaluatorTypeConversion.valueToDateTime(v), null, null)
));
break;
case semVerEqual:
case semVerGreaterThan:
case semVerLessThan:
c.setPreprocessed(preprocessClauseValues(c.getValues(), v ->
new ClauseExtra.ValueExtra(null, null, EvaluatorTypeConversion.valueToSemVer(v))
));
break;
default:
break;
}
}
private static ClauseExtra preprocessClauseValues(
List values,
Function f
) {
List valuesExtra = new ArrayList<>(values.size());
for (LDValue v: values) {
valuesExtra.add(f.apply(v));
}
return new ClauseExtra(null, valuesExtra);
}
}