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

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

package com.launchdarkly.sdk.server;

import com.google.gson.TypeAdapter;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.launchdarkly.sdk.EvaluationReason;
import com.launchdarkly.sdk.LDValue;
import com.launchdarkly.sdk.json.JsonSerializable;
import com.launchdarkly.sdk.server.interfaces.LDClientInterface;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import static com.launchdarkly.sdk.server.JsonHelpers.gsonInstance;

/**
 * A snapshot of the state of all feature flags with regard to a specific user, generated by
 * calling {@link LDClientInterface#allFlagsState(com.launchdarkly.sdk.LDUser, FlagsStateOption...)}.
 * 

* LaunchDarkly defines a standard JSON encoding for this object, suitable for * bootstrapping * the LaunchDarkly JavaScript browser SDK. You can convert it to JSON in any of these ways: *

    *
  1. With {@link com.launchdarkly.sdk.json.JsonSerialization}. *
  2. With Gson, if and only if you configure your {@code Gson} instance with * {@link com.launchdarkly.sdk.json.LDGson}. *
  3. With Jackson, if and only if you configure your {@code ObjectMapper} instance with * {@link com.launchdarkly.sdk.json.LDJackson}. *
* * @since 4.3.0 */ @JsonAdapter(FeatureFlagsState.JsonSerialization.class) public final class FeatureFlagsState implements JsonSerializable { private final Map flagValues; private final Map flagMetadata; private final boolean valid; static class FlagMetadata { final Integer variation; final EvaluationReason reason; final Integer version; final Boolean trackEvents; final Long debugEventsUntilDate; FlagMetadata(Integer variation, EvaluationReason reason, Integer version, boolean trackEvents, Long debugEventsUntilDate) { this.variation = variation; this.reason = reason; this.version = version; this.trackEvents = trackEvents ? Boolean.TRUE : null; this.debugEventsUntilDate = debugEventsUntilDate; } @Override public boolean equals(Object other) { if (other instanceof FlagMetadata) { FlagMetadata o = (FlagMetadata)other; return Objects.equals(variation, o.variation) && Objects.equals(reason, o.reason) && Objects.equals(version, o.version) && Objects.equals(trackEvents, o.trackEvents) && Objects.equals(debugEventsUntilDate, o.debugEventsUntilDate); } return false; } @Override public int hashCode() { return Objects.hash(variation, version, trackEvents, debugEventsUntilDate); } } private FeatureFlagsState(Map flagValues, Map flagMetadata, boolean valid) { this.flagValues = Collections.unmodifiableMap(flagValues); this.flagMetadata = Collections.unmodifiableMap(flagMetadata); this.valid = valid; } /** * Returns true if this object contains a valid snapshot of feature flag state, or false if the * state could not be computed (for instance, because the client was offline or there was no user). * @return true if the state is valid */ public boolean isValid() { return valid; } /** * Returns the value of an individual feature flag at the time the state was recorded. * @param key the feature flag key * @return the flag's JSON value; {@link LDValue#ofNull()} if the flag returned the default value; * {@code null} if there was no such flag */ public LDValue getFlagValue(String key) { return flagValues.get(key); } /** * Returns the evaluation reason for an individual feature flag at the time the state was recorded. * @param key the feature flag key * @return an {@link EvaluationReason}; null if reasons were not recorded, or if there was no such flag */ public EvaluationReason getFlagReason(String key) { FlagMetadata data = flagMetadata.get(key); return data == null ? null : data.reason; } /** * Returns a map of flag keys to flag values. If a flag would have evaluated to the default value, * its value will be null. *

* Do not use this method if you are passing data to the front end to "bootstrap" the JavaScript client. * Instead, serialize the FeatureFlagsState object to JSON using {@code Gson.toJson()} or {@code Gson.toJsonTree()}. * @return an immutable map of flag keys to JSON values */ public Map toValuesMap() { return flagValues; } @Override public boolean equals(Object other) { if (other instanceof FeatureFlagsState) { FeatureFlagsState o = (FeatureFlagsState)other; return flagValues.equals(o.flagValues) && flagMetadata.equals(o.flagMetadata) && valid == o.valid; } return false; } @Override public int hashCode() { return Objects.hash(flagValues, flagMetadata, valid); } static class Builder { private Map flagValues = new HashMap<>(); private Map flagMetadata = new HashMap<>(); private final boolean saveReasons; private final boolean detailsOnlyForTrackedFlags; private boolean valid = true; Builder(FlagsStateOption... options) { saveReasons = FlagsStateOption.hasOption(options, FlagsStateOption.WITH_REASONS); detailsOnlyForTrackedFlags = FlagsStateOption.hasOption(options, FlagsStateOption.DETAILS_ONLY_FOR_TRACKED_FLAGS); } Builder valid(boolean valid) { this.valid = valid; return this; } Builder addFlag(DataModel.FeatureFlag flag, Evaluator.EvalResult eval) { flagValues.put(flag.getKey(), eval.getValue()); final boolean flagIsTracked = flag.isTrackEvents() || (flag.getDebugEventsUntilDate() != null && flag.getDebugEventsUntilDate() > System.currentTimeMillis()); final boolean wantDetails = !detailsOnlyForTrackedFlags || flagIsTracked; FlagMetadata data = new FlagMetadata( eval.isDefault() ? null : eval.getVariationIndex(), (saveReasons && wantDetails) ? eval.getReason() : null, wantDetails ? flag.getVersion() : null, flag.isTrackEvents(), flag.getDebugEventsUntilDate()); flagMetadata.put(flag.getKey(), data); return this; } FeatureFlagsState build() { return new FeatureFlagsState(flagValues, flagMetadata, valid); } } static class JsonSerialization extends TypeAdapter { @Override public void write(JsonWriter out, FeatureFlagsState state) throws IOException { out.beginObject(); for (Map.Entry entry: state.flagValues.entrySet()) { out.name(entry.getKey()); gsonInstance().toJson(entry.getValue(), LDValue.class, out); } out.name("$flagsState"); out.beginObject(); for (Map.Entry entry: state.flagMetadata.entrySet()) { out.name(entry.getKey()); FlagMetadata meta = entry.getValue(); out.beginObject(); // Here we're serializing FlagMetadata properties individually because if we rely on // Gson's reflection mechanism, it won't reliably drop null properties (that only works // if the destination really is Gson, not if a Jackson adapter is being used). if (meta.variation != null) { out.name("variation"); out.value(meta.variation.intValue()); } if (meta.reason != null) { out.name("reason"); gsonInstance().toJson(meta.reason, EvaluationReason.class, out); } if (meta.version != null) { out.name("version"); out.value(meta.version.intValue()); } if (meta.trackEvents != null) { out.name("trackEvents"); out.value(meta.trackEvents.booleanValue()); } if (meta.debugEventsUntilDate != null) { out.name("debugEventsUntilDate"); out.value(meta.debugEventsUntilDate.longValue()); } out.endObject(); } out.endObject(); out.name("$valid"); out.value(state.valid); out.endObject(); } // There isn't really a use case for deserializing this, but we have to implement it @Override public FeatureFlagsState read(JsonReader in) throws IOException { Map flagValues = new HashMap<>(); Map flagMetadata = new HashMap<>(); boolean valid = true; in.beginObject(); while (in.hasNext()) { String name = in.nextName(); if (name.equals("$flagsState")) { in.beginObject(); while (in.hasNext()) { String metaName = in.nextName(); FlagMetadata meta = gsonInstance().fromJson(in, FlagMetadata.class); flagMetadata.put(metaName, meta); } in.endObject(); } else if (name.equals("$valid")) { valid = in.nextBoolean(); } else { LDValue value = gsonInstance().fromJson(in, LDValue.class); flagValues.put(name, value); } } in.endObject(); return new FeatureFlagsState(flagValues, flagMetadata, valid); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy