io.split.storages.memory.InMemoryCacheImp Maven / Gradle / Ivy
package io.split.storages.memory;
import com.google.common.collect.ConcurrentHashMultiset;
import com.google.common.collect.Maps;
import com.google.common.collect.Multiset;
import com.google.common.collect.Sets;
import io.split.client.interceptors.FlagSetsFilter;
import io.split.engine.experiments.ParsedSplit;
import io.split.storages.SplitCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
public class InMemoryCacheImp implements SplitCache {
private static final Logger _log = LoggerFactory.getLogger(InMemoryCacheImp.class);
private final ConcurrentMap _concurrentMap;
private final ConcurrentMap> _flagSets;
private final Multiset _concurrentTrafficTypeNameSet;
private final FlagSetsFilter _flagSetsFilter;
private AtomicLong _changeNumber;
public InMemoryCacheImp(FlagSetsFilter flagSets) {
this(-1, flagSets);
}
public InMemoryCacheImp(long startingChangeNumber, FlagSetsFilter flagSets) {
_concurrentMap = Maps.newConcurrentMap();
_changeNumber = new AtomicLong(startingChangeNumber);
_concurrentTrafficTypeNameSet = ConcurrentHashMultiset.create();
_flagSets = Maps.newConcurrentMap();
_flagSetsFilter = flagSets;
}
@Override
public boolean remove(String name) {
ParsedSplit removed = _concurrentMap.remove(name);
if (removed != null) {
removeFromFlagSets(removed.feature());
if (removed.trafficTypeName() != null) {
this.decreaseTrafficType(removed.trafficTypeName());
}
}
return removed != null;
}
@Override
public ParsedSplit get(String name) {
return _concurrentMap.get(name);
}
@Override
public Collection getAll() {
return _concurrentMap.values();
}
@Override
public Map fetchMany(List names) {
Map splits = new HashMap<>();
names.forEach(s -> splits.put(s, _concurrentMap.get(s)));
return splits;
}
@Override
public long getChangeNumber() {
return _changeNumber.get();
}
@Override
public void setChangeNumber(long changeNumber) {
if (changeNumber < _changeNumber.get()) {
_log.error("ChangeNumber for feature flags cache is less than previous");
}
_changeNumber.set(changeNumber);
}
@Override
public boolean trafficTypeExists(String trafficTypeName) {
// If the multiset has [{"user",2}.{"account",0}], elementSet only returns
// ["user"] (it ignores "account")
return Sets.newHashSet(_concurrentTrafficTypeNameSet.elementSet()).contains(trafficTypeName);
}
@Override
public List splitNames() {
List splitNamesList = new ArrayList<>();
for (String key: _concurrentMap.keySet()) {
splitNamesList.add(_concurrentMap.get(key).feature());
}
return splitNamesList;
}
@Override
public Map> getNamesByFlagSets(List flagSets) {
Map> toReturn = new HashMap<>();
for (String set: flagSets) {
HashSet keys = _flagSets.get(set);
toReturn.put(set, keys);
}
return toReturn;
}
@Override
public void kill(String splitName, String defaultTreatment, long changeNumber) {
ParsedSplit parsedSplit = _concurrentMap.get(splitName);
ParsedSplit updatedSplit = new ParsedSplit(parsedSplit.feature(),
parsedSplit.seed(),
true,
defaultTreatment,
parsedSplit.parsedConditions(),
parsedSplit.trafficTypeName(),
changeNumber,
parsedSplit.trafficAllocation(),
parsedSplit.trafficAllocationSeed(),
parsedSplit.algo(),
parsedSplit.configurations(),
parsedSplit.flagSets()
);
_concurrentMap.put(splitName, updatedSplit);
}
@Override
public void clear() {
_concurrentMap.clear();
_concurrentTrafficTypeNameSet.clear();
}
@Override
public void putMany(List splits) {
for (ParsedSplit split : splits) {
_concurrentMap.put(split.feature(), split);
if (split.trafficTypeName() != null) {
this.increaseTrafficType(split.trafficTypeName());
}
removeFromFlagSets(split.feature());
addToFlagSets(split);
}
}
@Override
public void increaseTrafficType(String trafficType) {
_concurrentTrafficTypeNameSet.add(trafficType);
}
@Override
public void decreaseTrafficType(String trafficType) {
_concurrentTrafficTypeNameSet.remove(trafficType);
}
@Override
public void update(List toAdd, List toRemove, long changeNumber) {
if(toAdd != null) {
putMany(toAdd);
}
if(toRemove != null) {
for(String featureFlag : toRemove) {
remove(featureFlag);
}
}
setChangeNumber(changeNumber);
}
public Set getSegments() {
return _concurrentMap.values().stream()
.flatMap(parsedSplit -> parsedSplit.getSegmentsNames().stream()).collect(Collectors.toSet());
}
private void addToFlagSets(ParsedSplit featureFlag) {
HashSet sets = featureFlag.flagSets();
if(sets == null) {
return;
}
for (String set: sets) {
if (!_flagSetsFilter.intersect(set)) {
continue;
}
HashSet features = _flagSets.get(set);
if (features == null) {
features = new HashSet<>();
}
features.add(featureFlag.feature());
_flagSets.put(set, features);
}
}
private void removeFromFlagSets(String featureFlagName) {
for (String set: _flagSets.keySet()) {
_flagSets.get(set).remove(featureFlagName);
}
}
}