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

io.getunleash.variant.VariantUtil Maven / Gradle / Ivy

There is a newer version: 9.2.4
Show newest version
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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy