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

com.launchdarkly.client.FeatureFlag Maven / Gradle / Ivy

There is a newer version: 4.61
Show newest version
package com.launchdarkly.client;

import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.launchdarkly.client.VersionedDataKind.FEATURES;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

class FeatureFlag implements VersionedData {
  private final static Logger logger = LoggerFactory.getLogger(FeatureFlag.class);

  private static final Type mapType = new TypeToken>() {
  }.getType();

  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 deleted;

  static FeatureFlag fromJson(LDConfig config, String json) {
    return config.gson.fromJson(json, FeatureFlag.class);
  }

  static Map fromJsonMap(LDConfig config, String json) {
    return config.gson.fromJson(json, mapType);
  }

  // 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 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.deleted = deleted;
  }

  EvalResult evaluate(LDUser user, FeatureStore featureStore) throws EvaluationException {
    List prereqEvents = new ArrayList<>();

    if (user == null || user.getKey() == null) {
      logger.warn("Null user or null user key when evaluating flag: " + key + "; returning null");
      return new EvalResult(null, prereqEvents);
    }

    if (isOn()) {
      JsonElement value = evaluate(user, featureStore, prereqEvents);
      if (value != null) {
        return new EvalResult(value, prereqEvents);
      }
    }
    JsonElement offVariation = getOffVariationValue();
    return new EvalResult(offVariation, prereqEvents);
  }

  // Returning either a JsonElement or null indicating prereq failure/error.
  private JsonElement evaluate(LDUser user, FeatureStore featureStore, List events) throws EvaluationException {
    boolean prereqOk = true;
    if (prerequisites != null) {
      for (Prerequisite prereq : prerequisites) {
        FeatureFlag prereqFeatureFlag = featureStore.get(FEATURES, prereq.getKey());
        JsonElement prereqEvalResult = null;
        if (prereqFeatureFlag == null) {
          logger.error("Could not retrieve prerequisite flag: " + prereq.getKey() + " when evaluating: " + key);
          return null;
        } else if (prereqFeatureFlag.isOn()) {
          prereqEvalResult = prereqFeatureFlag.evaluate(user, featureStore, events);
          try {
            JsonElement variation = prereqFeatureFlag.getVariation(prereq.getVariation());
            if (prereqEvalResult == null || variation == null || !prereqEvalResult.equals(variation)) {
              prereqOk = false;
            }
          } catch (EvaluationException err) {
            logger.warn("Error evaluating prerequisites: " + err.getMessage());
            prereqOk = false;
          }
        } else {
          prereqOk = false;
        }
        //We don't short circuit and also send events for each prereq.
        events.add(new FeatureRequestEvent(prereqFeatureFlag.getKey(), user, prereqEvalResult, null, prereqFeatureFlag.getVersion(), key));
      }
    }
    if (prereqOk) {
      return getVariation(evaluateIndex(user, featureStore));
    }
    return null;
  }

  private Integer evaluateIndex(LDUser user, FeatureStore store) {
    // Check to see if targets match
    if (targets != null) {
      for (Target target : targets) {
        for (String v : target.getValues()) {
          if (v.equals(user.getKey().getAsString())) {
            return target.getVariation();
          }
        }
      }
    }
    // Now walk through the rules and see if any match
    if (rules != null) {
      for (Rule rule : rules) {
        if (rule.matchesUser(store, user)) {
          return rule.variationIndexForUser(user, key, salt);
        }
      }
    }
    // Walk through the fallthrough and see if it matches
    return fallthrough.variationIndexForUser(user, key, salt);
  }

  JsonElement getOffVariationValue() throws EvaluationException {
    if (offVariation == null) {
      return null;
    }

    if (offVariation >= variations.size()) {
      throw new EvaluationException("Invalid off variation index");
    }

    return variations.get(offVariation);
  }

  private JsonElement getVariation(Integer index) throws EvaluationException {
    // If the supplied index is null, then rules didn't match, and we want to return
    // the off variation
    if (index == null) {
      return null;
    }
    // If the index doesn't refer to a valid variation, that's an unexpected exception and we will
    // return the default variation
    else if (index >= variations.size()) {
      throw new EvaluationException("Invalid index");
    }
    else {
      return variations.get(index);
    }
  }

  public int getVersion() {
    return version;
  }

  public String getKey() {
    return key;
  }

  public boolean isDeleted() {
    return deleted;
  }

  boolean isOn() {
    return on;
  }

  List getPrerequisites() {
    return prerequisites;
  }

  String getSalt() {
    return salt;
  }

  List getTargets() {
    return targets;
  }

  List getRules() {
    return rules;
  }

  VariationOrRollout getFallthrough() {
    return fallthrough;
  }

  List getVariations() {
    return variations;
  }

  Integer getOffVariation() { return offVariation; }

  static class EvalResult {
    private final JsonElement value;
    private final List prerequisiteEvents;

    private EvalResult(JsonElement value, List prerequisiteEvents) {
      this.value = value;
      this.prerequisiteEvents = prerequisiteEvents;
    }

    JsonElement getValue() {
      return value;
    }

    List getPrerequisiteEvents() {
      return prerequisiteEvents;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy