io.getunleash.variant.VariantUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of unleash-client-java Show documentation
Show all versions of unleash-client-java Show documentation
A client library for Unleash
package io.getunleash.variant;
import io.getunleash.FeatureToggle;
import io.getunleash.UnleashContext;
import io.getunleash.Variant;
import io.getunleash.lang.Nullable;
import io.getunleash.strategy.StrategyUtils;
import java.util.*;
import java.util.List;
import java.util.function.Predicate;
public final class VariantUtil {
static final String GROUP_ID_KEY = "groupId";
// To avoid using the same seed for gradual rollout and variant selection.
// This caused an unfortunate bias since we'd already excluded x % of the hash results.
// This is the 5.000.001st prime.
public static final Long VARIANT_NORMALIZATION_SEED = 86028157L;
private VariantUtil() {}
private static Predicate overrideMatchesContext(UnleashContext context) {
return (override) -> {
Optional contextValue;
switch (override.getContextName()) {
case "userId":
{
contextValue = context.getUserId();
break;
}
case "sessionId":
{
contextValue = context.getSessionId();
break;
}
case "remoteAddress":
{
contextValue = context.getRemoteAddress();
break;
}
default:
contextValue =
Optional.ofNullable(
context.getProperties().get(override.getContextName()));
break;
}
return override.getValues().contains(contextValue.orElse(""));
};
}
private static Optional getOverride(
List variants, UnleashContext context) {
return variants.stream()
.filter(
variant ->
variant.getOverrides().stream()
.anyMatch(overrideMatchesContext(context)))
.findFirst();
}
private static String getIdentifier(UnleashContext context) {
return context.getUserId()
.orElse(
context.getSessionId()
.orElse(
context.getRemoteAddress()
.orElse(Double.toString(Math.random()))));
}
private static String randomString() {
int randSeed = new Random().nextInt(100000);
return "" + randSeed;
}
private static String getSeed(UnleashContext unleashContext, Optional stickiness) {
return stickiness
.map(s -> unleashContext.getByName(s).orElse(randomString()))
.orElse(getIdentifier(unleashContext));
}
public static Variant selectVariant(
@Nullable FeatureToggle featureToggle, UnleashContext context, Variant defaultVariant) {
if (featureToggle == null) {
return defaultVariant;
}
Variant variant =
selectVariant(
Collections.singletonMap("groupId", featureToggle.getName()),
featureToggle.getVariants(),
context);
return variant != null ? variant : defaultVariant;
}
public static @Nullable Variant selectVariant(
Map parameters,
@Nullable List variants,
UnleashContext context,
@Nullable String strategyStickiness) {
if (variants != null) {
int totalWeight = variants.stream().mapToInt(VariantDefinition::getWeight).sum();
if (totalWeight <= 0) {
return null;
}
Optional variantOverride = getOverride(variants, context);
if (variantOverride.isPresent()) {
return variantOverride.get().toVariant();
}
Optional variantCustomStickiness =
variants.stream()
.filter(
f ->
f.getStickiness() != null
&& !"default".equals(f.getStickiness()))
.map(VariantDefinition::getStickiness)
.findFirst();
Optional customStickiness;
if (!variantCustomStickiness.isPresent()) {
customStickiness =
Optional.ofNullable(strategyStickiness)
.filter(stickiness -> !stickiness.equalsIgnoreCase("default"));
} else {
customStickiness = variantCustomStickiness;
}
int target =
StrategyUtils.getNormalizedNumber(
getSeed(context, customStickiness),
parameters.get(GROUP_ID_KEY),
totalWeight,
VARIANT_NORMALIZATION_SEED);
int counter = 0;
for (VariantDefinition variant : variants) {
if (variant.getWeight() != 0) {
counter += variant.getWeight();
if (counter >= target) {
return variant.toVariant();
}
}
}
}
return null;
}
public static @Nullable Variant selectVariant(
Map parameters,
@Nullable List variants,
UnleashContext context) {
return selectVariant(parameters, variants, context, null);
}
/**
* Uses the old pre 9.0.0 way of hashing for finding the Variant to return
*
* @deprecated
* @param parameters
* @param variants
* @param context
* @return
*/
public static @Nullable Variant selectDeprecatedVariantHashingAlgo(
Map parameters,
@Nullable List variants,
UnleashContext context) {
if (variants != null) {
int totalWeight = variants.stream().mapToInt(VariantDefinition::getWeight).sum();
if (totalWeight <= 0) {
return null;
}
Optional variantOverride = getOverride(variants, context);
if (variantOverride.isPresent()) {
return variantOverride.get().toVariant();
}
Optional customStickiness =
variants.stream()
.filter(
f ->
f.getStickiness() != null
&& !"default".equals(f.getStickiness()))
.map(VariantDefinition::getStickiness)
.findFirst();
int target =
StrategyUtils.getNormalizedNumber(
getSeed(context, customStickiness),
parameters.get(GROUP_ID_KEY),
totalWeight,
0);
int counter = 0;
for (VariantDefinition variant : variants) {
if (variant.getWeight() != 0) {
counter += variant.getWeight();
if (counter >= target) {
return variant.toVariant();
}
}
}
}
return null;
}
/**
* Uses the old pre 9.0.0 way of hashing for finding the Variant to return
*
* @deprecated
* @param featureToggle
* @param context
* @param defaultVariant
* @return
*/
public static @Nullable Variant selectDeprecatedVariantHashingAlgo(
FeatureToggle featureToggle, UnleashContext context, Variant defaultVariant) {
if (featureToggle == null) {
return defaultVariant;
}
Variant variant =
selectDeprecatedVariantHashingAlgo(
Collections.singletonMap("groupId", featureToggle.getName()),
featureToggle.getVariants(),
context);
return variant != null ? variant : defaultVariant;
}
}