All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.launchdarkly.sdk.server.DataModel Maven / Gradle / Ivy

package com.launchdarkly.sdk.server;

import com.google.common.collect.ImmutableList;
import com.google.gson.annotations.JsonAdapter;
import com.launchdarkly.sdk.EvaluationReason;
import com.launchdarkly.sdk.LDValue;
import com.launchdarkly.sdk.UserAttribute;
import com.launchdarkly.sdk.server.interfaces.DataStoreTypes.DataKind;
import com.launchdarkly.sdk.server.interfaces.DataStoreTypes.ItemDescriptor;

import java.util.Collection;
import java.util.List;
import java.util.Set;

import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;

/**
 * Contains information about the internal data model for feature flags and user segments.
 * 

* The details of the data model are not public to application code (although of course developers can easily * look at the code or the data) so that changes to LaunchDarkly SDK implementation details will not be breaking * changes to the application. Therefore, most of the members of this class are package-private. The public * members provide a high-level description of model objects so that custom integration code or test code can * store or serialize them. */ public abstract class DataModel { private DataModel() {} /** * The {@link DataKind} instance that describes feature flag data. *

* Applications should not need to reference this object directly. It is public so that custom integrations * and test code can serialize or deserialize data or inject it into a data store. */ public static DataKind FEATURES = new DataKind("features", DataModel::serializeItem, s -> deserializeItem(s, FeatureFlag.class)); /** * The {@link DataKind} instance that describes user segment data. *

* Applications should not need to reference this object directly. It is public so that custom integrations * and test code can serialize or deserialize data or inject it into a data store. */ public static DataKind SEGMENTS = new DataKind("segments", DataModel::serializeItem, s -> deserializeItem(s, Segment.class)); /** * An enumeration of all supported {@link DataKind} types. *

* Applications should not need to reference this object directly. It is public so that custom data store * implementations can determine ahead of time what kinds of model objects may need to be stored, if * necessary. */ public static Iterable ALL_DATA_KINDS = ImmutableList.of(FEATURES, SEGMENTS); private static ItemDescriptor deserializeItem(String s, Class itemClass) { VersionedData o = JsonHelpers.deserialize(s, itemClass); return o.isDeleted() ? ItemDescriptor.deletedItem(o.getVersion()) : new ItemDescriptor(o.getVersion(), o); } private static String serializeItem(ItemDescriptor item) { Object o = item.getItem(); if (o != null) { return JsonHelpers.serialize(o); } return "{\"version\":" + item.getVersion() + ",\"deleted\":true}"; } // All of these inner data model classes should have package-private scope. They should have only property // accessors; the evaluator logic is in Evaluator, EvaluatorBucketing, and EvaluatorOperators. /** * Common interface for FeatureFlag and Segment, for convenience in accessing their common properties. * @since 3.0.0 */ interface VersionedData { String getKey(); int getVersion(); /** * True if this is a placeholder for a deleted item. * @return true if deleted */ boolean isDeleted(); } @JsonAdapter(JsonHelpers.PostProcessingDeserializableTypeAdapterFactory.class) static final class FeatureFlag implements VersionedData, JsonHelpers.PostProcessingDeserializable { private String key; private int version; private boolean on; private List prerequisites; private String salt; private List targets; private List rules; private VariationOrRollout fallthrough; private Integer offVariation; //optional private List variations; private boolean clientSide; private boolean trackEvents; private boolean trackEventsFallthrough; private Long debugEventsUntilDate; private boolean deleted; // We need this so Gson doesn't complain in certain java environments that restrict unsafe allocation FeatureFlag() {} FeatureFlag(String key, int version, boolean on, List prerequisites, String salt, List targets, List rules, VariationOrRollout fallthrough, Integer offVariation, List variations, boolean clientSide, boolean trackEvents, boolean trackEventsFallthrough, Long debugEventsUntilDate, boolean deleted) { this.key = key; this.version = version; this.on = on; this.prerequisites = prerequisites; this.salt = salt; this.targets = targets; this.rules = rules; this.fallthrough = fallthrough; this.offVariation = offVariation; this.variations = variations; this.clientSide = clientSide; this.trackEvents = trackEvents; this.trackEventsFallthrough = trackEventsFallthrough; this.debugEventsUntilDate = debugEventsUntilDate; this.deleted = deleted; } public int getVersion() { return version; } public String getKey() { return key; } boolean isTrackEvents() { return trackEvents; } boolean isTrackEventsFallthrough() { return trackEventsFallthrough; } Long getDebugEventsUntilDate() { return debugEventsUntilDate; } public boolean isDeleted() { return deleted; } boolean isOn() { return on; } List getPrerequisites() { return prerequisites == null ? emptyList() : prerequisites; } String getSalt() { return salt; } // Guaranteed non-null List getTargets() { return targets == null ? emptyList() : targets; } // Guaranteed non-null List getRules() { return rules == null ? emptyList() : rules; } VariationOrRollout getFallthrough() { return fallthrough; } // Guaranteed non-null List getVariations() { return variations == null ? emptyList() : variations; } Integer getOffVariation() { return offVariation; } boolean isClientSide() { return clientSide; } // Precompute some invariant values for improved efficiency during evaluations - called from JsonHelpers.PostProcessingDeserializableTypeAdapter public void afterDeserialized() { EvaluatorPreprocessing.preprocessFlag(this); } } static final class Prerequisite { private String key; private int variation; private transient EvaluationReason prerequisiteFailedReason; Prerequisite() {} Prerequisite(String key, int variation) { this.key = key; this.variation = variation; } String getKey() { return key; } int getVariation() { return variation; } // This value is precomputed when we deserialize a FeatureFlag from JSON EvaluationReason getPrerequisiteFailedReason() { return prerequisiteFailedReason; } void setPrerequisiteFailedReason(EvaluationReason prerequisiteFailedReason) { this.prerequisiteFailedReason = prerequisiteFailedReason; } } static final class Target { private Set values; private int variation; Target() {} Target(Set values, int variation) { this.values = values; this.variation = variation; } // Guaranteed non-null Collection getValues() { return values == null ? emptySet() : values; } int getVariation() { return variation; } } /** * Expresses a set of AND-ed matching conditions for a user, along with either the fixed variation or percent rollout * to serve if the conditions match. * Invariant: one of the variation or rollout must be non-nil. */ static final class Rule extends VariationOrRollout { private String id; private List clauses; private boolean trackEvents; private transient EvaluationReason ruleMatchReason; Rule() { super(); } Rule(String id, List clauses, Integer variation, Rollout rollout, boolean trackEvents) { super(variation, rollout); this.id = id; this.clauses = clauses; this.trackEvents = trackEvents; } String getId() { return id; } // Guaranteed non-null List getClauses() { return clauses == null ? emptyList() : clauses; } boolean isTrackEvents() { return trackEvents; } // This value is precomputed when we deserialize a FeatureFlag from JSON EvaluationReason getRuleMatchReason() { return ruleMatchReason; } void setRuleMatchReason(EvaluationReason ruleMatchReason) { this.ruleMatchReason = ruleMatchReason; } } static final class Clause { private UserAttribute attribute; private Operator op; private List values; //interpreted as an OR of values private boolean negate; // The following property is marked transient because it is not to be serialized or deserialized; // it is (if necessary) precomputed in FeatureFlag.afterDeserialized() to speed up evaluations. transient EvaluatorPreprocessing.ClauseExtra preprocessed; Clause() { } Clause(UserAttribute attribute, Operator op, List values, boolean negate) { this.attribute = attribute; this.op = op; this.values = values; this.negate = negate; } UserAttribute getAttribute() { return attribute; } Operator getOp() { return op; } // Guaranteed non-null List getValues() { return values == null ? emptyList() : values; } boolean isNegate() { return negate; } EvaluatorPreprocessing.ClauseExtra getPreprocessed() { return preprocessed; } void setPreprocessed(EvaluatorPreprocessing.ClauseExtra preprocessed) { this.preprocessed = preprocessed; } } static final class Rollout { private List variations; private UserAttribute bucketBy; Rollout() {} Rollout(List variations, UserAttribute bucketBy) { this.variations = variations; this.bucketBy = bucketBy; } // Guaranteed non-null List getVariations() { return variations == null ? emptyList() : variations; } UserAttribute getBucketBy() { return bucketBy; } } /** * Contains either a fixed variation or percent rollout to serve. * Invariant: one of the variation or rollout must be non-nil. */ static class VariationOrRollout { private Integer variation; private Rollout rollout; VariationOrRollout() {} VariationOrRollout(Integer variation, Rollout rollout) { this.variation = variation; this.rollout = rollout; } Integer getVariation() { return variation; } Rollout getRollout() { return rollout; } } static final class WeightedVariation { private int variation; private int weight; WeightedVariation() {} WeightedVariation(int variation, int weight) { this.variation = variation; this.weight = weight; } int getVariation() { return variation; } int getWeight() { return weight; } } @JsonAdapter(JsonHelpers.PostProcessingDeserializableTypeAdapterFactory.class) static final class Segment implements VersionedData, JsonHelpers.PostProcessingDeserializable { private String key; private Set included; private Set excluded; private String salt; private List rules; private int version; private boolean deleted; Segment() {} Segment(String key, Set included, Set excluded, String salt, List rules, int version, boolean deleted) { this.key = key; this.included = included; this.excluded = excluded; this.salt = salt; this.rules = rules; this.version = version; this.deleted = deleted; } public String getKey() { return key; } // Guaranteed non-null Collection getIncluded() { return included == null ? emptySet() : included; } // Guaranteed non-null Collection getExcluded() { return excluded == null ? emptySet() : excluded; } String getSalt() { return salt; } // Guaranteed non-null List getRules() { return rules == null ? emptyList() : rules; } public int getVersion() { return version; } public boolean isDeleted() { return deleted; } // Precompute some invariant values for improved efficiency during evaluations - called from JsonHelpers.PostProcessingDeserializableTypeAdapter public void afterDeserialized() { EvaluatorPreprocessing.preprocessSegment(this); } } static final class SegmentRule { private final List clauses; private final Integer weight; private final UserAttribute bucketBy; SegmentRule(List clauses, Integer weight, UserAttribute bucketBy) { this.clauses = clauses; this.weight = weight; this.bucketBy = bucketBy; } // Guaranteed non-null List getClauses() { return clauses == null ? emptyList() : clauses; } Integer getWeight() { return weight; } UserAttribute getBucketBy() { return bucketBy; } } /** * This enum can be directly deserialized from JSON, avoiding the need for a mapping of strings to * operators. The implementation of each operator is in EvaluatorOperators. */ static enum Operator { in, endsWith, startsWith, matches, contains, lessThan, lessThanOrEqual, greaterThan, greaterThanOrEqual, before, after, semVerEqual, semVerLessThan, semVerGreaterThan, segmentMatch } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy