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

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

package com.launchdarkly.client;

import com.google.common.collect.ImmutableMap;
import com.launchdarkly.client.interfaces.DiagnosticDescription;
import com.launchdarkly.client.value.LDValue;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * A thread-safe, versioned store for feature flags and related data based on a
 * {@link HashMap}. This is the default implementation of {@link FeatureStore}.
 */
public class InMemoryFeatureStore implements FeatureStore, DiagnosticDescription {
  private static final Logger logger = LoggerFactory.getLogger(InMemoryFeatureStore.class);

  private volatile ImmutableMap, Map> allData = ImmutableMap.of();
  private volatile boolean initialized = false;
  private Object writeLock = new Object();

  @Override
  public  T get(VersionedDataKind kind, String key) {
    Map items = allData.get(kind);
    if (items == null) {
      logger.debug("[get] no objects exist for \"{}\". Returning null", kind.getNamespace());
      return null;
    }
    Object o = items.get(key);
    if (o == null) {
      logger.debug("[get] Key: {} not found in \"{}\". Returning null", key, kind.getNamespace());
      return null;
    }
    if (!kind.getItemClass().isInstance(o)) {
      logger.warn("[get] Unexpected object class {} found for key: {} in \"{}\". Returning null",
          o.getClass().getName(), key, kind.getNamespace());
      return null;
    }
    T item = kind.getItemClass().cast(o);
    if (item.isDeleted()) {
      logger.debug("[get] Key: {} has been deleted. Returning null", key);
      return null;
    }
    logger.debug("[get] Key: {} with version: {} found in \"{}\".", key, item.getVersion(), kind.getNamespace());
    return item;
  }

  @Override
  public  Map all(VersionedDataKind kind) {
    Map fs = new HashMap<>();
    Map items = allData.get(kind);
    if (items != null) {
      for (Map.Entry entry : items.entrySet()) {
        if (!entry.getValue().isDeleted()) {
          fs.put(entry.getKey(), kind.getItemClass().cast(entry.getValue()));
        }
      }
    }
    return fs;
  }

  @Override
  public void init(Map, Map> allData) {
    synchronized (writeLock) {
      ImmutableMap.Builder, Map> newData = ImmutableMap.builder();
      for (Map.Entry, Map> entry: allData.entrySet()) {
        // Note, the FeatureStore contract specifies that we should clone all of the maps. This doesn't
        // really make a difference in regular use of the SDK, but not doing it could cause unexpected
        // behavior in tests.
        newData.put(entry.getKey(), ImmutableMap.copyOf(entry.getValue()));
      }
      this.allData = newData.build(); // replaces the entire map atomically
      this.initialized = true;
    }
  }

  @Override
  public  void delete(VersionedDataKind kind, String key, int version) {
    upsert(kind, kind.makeDeletedItem(key, version));
  }

  @Override
  public  void upsert(VersionedDataKind kind, T item) {
    String key = item.getKey();
    synchronized (writeLock) {
      Map existingItems = this.allData.get(kind);
      VersionedData oldItem = null;
      if (existingItems != null) {
        oldItem = existingItems.get(key);
        if (oldItem != null && oldItem.getVersion() >= item.getVersion()) {
          return;
        }
      }
      // The following logic is necessary because ImmutableMap.Builder doesn't support overwriting an existing key
      ImmutableMap.Builder, Map> newData = ImmutableMap.builder();
      for (Map.Entry, Map> e: this.allData.entrySet()) {
        if (!e.getKey().equals(kind)) {
          newData.put(e.getKey(), e.getValue());
        }
      }
      if (existingItems == null) {
        newData.put(kind, ImmutableMap.of(key, item));
      } else {
        ImmutableMap.Builder itemsBuilder = ImmutableMap.builder();
        if (oldItem == null) {
          itemsBuilder.putAll(existingItems);
        } else {
          for (Map.Entry e: existingItems.entrySet()) {
            if (!e.getKey().equals(key)) {
              itemsBuilder.put(e.getKey(), e.getValue());
            }
          }
        }
        itemsBuilder.put(key, item);
        newData.put(kind, itemsBuilder.build());
      }
      this.allData = newData.build(); // replaces the entire map atomically
    }
  }

  @Override
  public boolean initialized() {
    return initialized;
  }

  /**
   * Does nothing; this class does not have any resources to release
   *
   * @throws IOException will never happen
   */
  @Override
  public void close() throws IOException {
    return;
  }

  @Override
  public LDValue describeConfiguration(LDConfig config) {
    return LDValue.of("memory");
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy