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

com.flagsense.services.impl.UserVariantServiceImpl Maven / Gradle / Ivy

There is a newer version: 1.0.6
Show newest version
package com.flagsense.services.impl;

import com.flagsense.enums.Operator;
import com.flagsense.enums.Status;
import com.flagsense.model.*;
import com.flagsense.services.UserVariantService;
import com.flagsense.util.FlagsenseException;
import com.flagsense.util.MurmurHash3;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.util.Version;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.flagsense.util.Constants.MAX_HASH_VALUE;
import static com.flagsense.util.Constants.TOTAL_THREE_DECIMAL_TRAFFIC;

public class UserVariantServiceImpl implements UserVariantService {

    private final Data data;

    public UserVariantServiceImpl(Data data) {
        this.data = data;
    }

    @Override
    public void getUserVariant(UserVariantDTO userVariantDTO) {
        if (StringUtils.isBlank(userVariantDTO.getUserId()))
            throw new FlagsenseException("Bad user");

        FlagDTO flagDTO = getFlagData(userVariantDTO.getFlagId());
        if (flagDTO == null)
            throw new FlagsenseException("Flag not found");

        if (flagDTO.getType() != userVariantDTO.getExpectedVariantType())
            throw new FlagsenseException("Bad flag type specified");

        String userVariantKey = getUserVariantKey(userVariantDTO, flagDTO);
        userVariantDTO.setKey(userVariantKey);
        userVariantDTO.setValue(flagDTO.getVariants().get(userVariantKey).getValue());
    }

    private FlagDTO getFlagData(String flagId) {
        if (this.data == null || this.data.getFlags() == null)
            return null;
        return this.data.getFlags().get(flagId);
    }

    private Map getSegmentsMap(EnvData envData) {
        if (this.data == null || this.data.getSegments() == null)
            return new HashMap<>();
        return this.data.getSegments();
    }

    private String getUserVariantKey(UserVariantDTO userVariantDTO, FlagDTO flagDTO) {
        String userId = userVariantDTO.getUserId();
        Map attributes = userVariantDTO.getAttributes();

        EnvData envData = flagDTO.getEnvData();
        if (envData.getStatus() == Status.INACTIVE)
            return envData.getOffVariant();

        Map segmentsMap = getSegmentsMap(envData);
        if (!matchesPrerequisites(userId, attributes, envData.getPrerequisites(), segmentsMap))
            return envData.getOffVariant();

        Map targetUsers = envData.getTargetUsers();
        if (targetUsers != null && targetUsers.containsKey(userId))
            return targetUsers.get(userId);

        List targetSegmentsOrder = envData.getTargetSegmentsOrder();
        if (targetSegmentsOrder != null) {
            for (String targetSegment : targetSegmentsOrder) {
                if (isUserInSegment(userId, attributes, segmentsMap.get(targetSegment)))
                    return allocateTrafficVariant(userId, flagDTO, envData.getTargetSegments().get(targetSegment));
            }
        }

        return allocateTrafficVariant(userId, flagDTO, envData.getTraffic());
    }

    private boolean matchesPrerequisites(String userId, Map attributes, List prerequisites, Map segmentsMap) {
        if (prerequisites == null)
            return true;

        for (String prerequisite : prerequisites) {
            if (!isUserInSegment(userId, attributes, segmentsMap.get(prerequisite)))
                return false;
        }

        return true;
    }

    private boolean isUserInSegment(String userId, Map attributes, SegmentDTO segmentDTO) {
        if (segmentDTO == null)
            return false;

        List> andRules = segmentDTO.getRules();
        for (List andRule : andRules) {
            if (!matchesAndRule(userId, attributes, andRule))
                return false;
        }

        return true;
    }

    private boolean matchesAndRule(String userId, Map attributes, List orRules) {
        for (Rule orRule : orRules) {
            if (matchesRule(userId, attributes, orRule))
                return true;
        }
        return false;
    }

    private boolean matchesRule(String userId, Map attributes, Rule rule) {
        Object attributeValue = getAttributeValue(userId, attributes, rule.getKey());
        if (attributeValue == null)
            return false;

        try {
            boolean userMatchesRule;

            switch (rule.getType()) {
                case INT:
                    userMatchesRule = matchesIntRule(rule, (Integer) attributeValue);
                    break;

                case BOOL:
                    userMatchesRule = matchesBoolRule(rule, (Boolean) attributeValue);
                    break;

                case DOUBLE:
                    userMatchesRule = matchesDoubleRule(rule, Double.parseDouble(attributeValue.toString()));
                    break;

                case STRING:
                    userMatchesRule = matchesStringRule(rule, (String) attributeValue);
                    break;

                case VERSION:
                    userMatchesRule = matchesVersionRule(rule, (String) attributeValue);
                    break;

                default:
                    userMatchesRule = false;
            }

            return userMatchesRule == rule.getMatch();
        }
        catch (ClassCastException e) {
            // log.info("ClassCastException for type " + rule.getType() + ", key:" + rule.getKey() + ", value:" + attributeValue);
            return false;
        }
        catch (Exception e) {
            // log.error("Exception in matchesRule", e);
            return false;
        }
    }

    private Object getAttributeValue(String userId, Map attributes, String key) {
        if (StringUtils.equals(key, "id"))
            return userId;
        return attributes == null ? null : attributes.get(key);
    }

    private boolean matchesIntRule(Rule rule, Integer attributeValue) {
        List values = rule.getValues();
        switch (rule.getOperator()) {
            case LT:
                return attributeValue.compareTo(values.get(0)) < 0;

            case LTE:
                return attributeValue.compareTo(values.get(0)) <= 0;

            case EQ:
                return attributeValue.compareTo(values.get(0)) == 0;

            case GT:
                return attributeValue.compareTo(values.get(0)) > 0;

            case GTE:
                return attributeValue.compareTo(values.get(0)) >= 0;

            case IOF:
                return values.contains(attributeValue);

            default:
                return false;
        }
    }

    private boolean matchesBoolRule(Rule rule, Boolean attributeValue) {
        List values = rule.getValues();

        if (rule.getOperator() == Operator.EQ)
            return attributeValue.compareTo(values.get(0)) == 0;

        return false;
    }

    private boolean matchesDoubleRule(Rule rule, Double attributeValue) {
        List values = rule.getValues();
        switch (rule.getOperator()) {
            case LT:
                return attributeValue.compareTo(values.get(0)) < 0;

            case LTE:
                return attributeValue.compareTo(values.get(0)) <= 0;

            case EQ:
                return attributeValue.compareTo(values.get(0)) == 0;

            case GT:
                return attributeValue.compareTo(values.get(0)) > 0;

            case GTE:
                return attributeValue.compareTo(values.get(0)) >= 0;

            case IOF:
                return values.contains(attributeValue);

            default:
                return false;
        }
    }

    private boolean matchesStringRule(Rule rule, String attributeValue) {
        List values = rule.getValues();
        switch (rule.getOperator()) {
            case EQ:
                return StringUtils.equals(attributeValue, values.get(0));

            case HAS:
                return StringUtils.contains(attributeValue, values.get(0));

            case SW:
                return StringUtils.startsWith(attributeValue, values.get(0));

            case EW:
                return StringUtils.endsWith(attributeValue, values.get(0));

            case IOF:
                return values.contains(attributeValue);

            default:
                return false;
        }
    }

    private boolean matchesVersionRule(Rule rule, String attributeValue) {
        List values = rule.getValues();
        Version version = Version.parse(attributeValue);

        switch (rule.getOperator()) {
            case LT:
                return version.isLessThan(Version.parse(values.get(0)));

            case LTE:
                return version.isLessThanOrEqualTo(Version.parse(values.get(0)));

            case EQ:
                return version.equals(Version.parse(values.get(0)));

            case GT:
                return version.isGreaterThan(Version.parse(values.get(0)));

            case GTE:
                return version.isGreaterThanOrEqualTo(Version.parse(values.get(0)));

            case IOF:
                return values.contains(attributeValue);

            default:
                return false;
        }
    }

    private String allocateTrafficVariant(String userId, FlagDTO flagDTO, Map traffic) {
        if (traffic.size() == 1)
            return traffic.keySet().iterator().next();

        String bucketingId = userId + flagDTO.getId();
        List variantsOrder = flagDTO.getVariantsOrder();

        int hashCode = MurmurHash3.murmurhash3_x86_32(bucketingId, 0, bucketingId.length(), flagDTO.getSeed());
        double ratio = (double) (hashCode & 0xFFFFFFFFL) / MAX_HASH_VALUE;
        int bucketValue = (int) Math.floor(TOTAL_THREE_DECIMAL_TRAFFIC * ratio);

        int endOfRange = 0;
        for (String variant : variantsOrder) {
            endOfRange += traffic.getOrDefault(variant, 0);
            if (bucketValue < endOfRange)
                return variant;
        }

        return variantsOrder.get(variantsOrder.size() - 1);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy