com.launchdarkly.client.FeatureFlagsState Maven / Gradle / Ivy
Show all versions of launchdarkly-client Show documentation
package com.launchdarkly.client;
import com.google.common.base.Objects;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.TypeAdapter;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* A snapshot of the state of all feature flags with regard to a specific user, generated by
* calling {@link LDClientInterface#allFlagsState(LDUser, FlagsStateOption...)}.
*
* Serializing this object to JSON using Gson will produce the appropriate data structure for
* bootstrapping the LaunchDarkly JavaScript client.
*
* @since 4.3.0
*/
@JsonAdapter(FeatureFlagsState.JsonSerialization.class)
public class FeatureFlagsState {
private static final Gson gson = new Gson();
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.equal(variation, o.variation) &&
Objects.equal(version, o.version) &&
Objects.equal(trackEvents, o.trackEvents) &&
Objects.equal(debugEventsUntilDate, o.debugEventsUntilDate);
}
return false;
}
@Override
public int hashCode() {
return Objects.hashCode(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; null if the flag returned the default value, or if there was no such flag
*/
public JsonElement 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.hashCode(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(FeatureFlag flag, EvaluationDetail 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.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());
gson.toJson(entry.getValue(), out);
}
out.name("$flagsState");
gson.toJson(state.flagMetadata, Map.class, out);
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 = gson.fromJson(in, FlagMetadata.class);
flagMetadata.put(metaName, meta);
}
in.endObject();
} else if (name.equals("$valid")) {
valid = in.nextBoolean();
} else {
JsonElement value = gson.fromJson(in, JsonElement.class);
flagValues.put(name, value);
}
}
in.endObject();
return new FeatureFlagsState(flagValues, flagMetadata, valid);
}
}
}