Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
package com.azure.spring.cloud.feature.management;

import java.util.HashSet;
import java.util.Map;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
Expand All @@ -17,7 +17,7 @@
import com.azure.spring.cloud.feature.management.filters.FeatureFilter;
import com.azure.spring.cloud.feature.management.implementation.FeatureManagementConfigProperties;
import com.azure.spring.cloud.feature.management.implementation.FeatureManagementProperties;
import com.azure.spring.cloud.feature.management.implementation.models.Feature;
import com.azure.spring.cloud.feature.management.models.Feature;
import com.azure.spring.cloud.feature.management.models.FeatureFilterEvaluationContext;
import com.azure.spring.cloud.feature.management.models.FilterNotFoundException;

Expand Down Expand Up @@ -51,9 +51,9 @@ public class FeatureManager {
}

/**
* Checks to see if the feature is enabled. If enabled it check each filter, once a single filter
* returns true it returns true. If no filter returns true, it returns false. If there are no
* filters, it returns true. If feature isn't found it returns false.
* Checks to see if the feature is enabled. If enabled it check each filter, once a single filter returns true it
* returns true. If no filter returns true, it returns false. If there are no filters, it returns true. If feature
* isn't found it returns false.
*
* @param feature Feature being checked.
* @return state of the feature
Expand All @@ -64,9 +64,9 @@ public Mono<Boolean> isEnabledAsync(String feature) {
}

/**
* Checks to see if the feature is enabled. If enabled it check each filter, once a single filter
* returns true it returns true. If no filter returns true, it returns false. If there are no
* filters, it returns true. If feature isn't found it returns false.
* Checks to see if the feature is enabled. If enabled it check each filter, once a single filter returns true it
* returns true. If no filter returns true, it returns false. If there are no filters, it returns true. If feature
* isn't found it returns false.
*
* @param feature Feature being checked.
* @return state of the feature
Expand All @@ -76,34 +76,28 @@ public Boolean isEnabled(String feature) throws FilterNotFoundException {
return checkFeature(feature);
}

private boolean checkFeature(String feature) throws FilterNotFoundException {
if (featureManagementConfigurations.getFeatureManagement() == null
|| featureManagementConfigurations.getOnOff() == null) {
return false;
}

Boolean boolFeature = featureManagementConfigurations.getOnOff().get(feature);

if (boolFeature != null) {
return boolFeature;
}

Feature featureItem = featureManagementConfigurations.getFeatureManagement().get(feature);
private boolean checkFeature(String featureName) throws FilterNotFoundException {
Feature featureFlag = featureManagementConfigurations.getFeatureFlags().stream()
.filter(feature -> feature.getId().equals(featureName)).findAny().orElse(null);

if (featureItem == null || !featureItem.getEvaluate()) {
if (featureFlag == null) {
return false;
}

Stream<FeatureFilterEvaluationContext> filters = featureItem.getEnabledFor().values().stream()
Stream<FeatureFilterEvaluationContext> filters = featureFlag.getConditions().getClientFilters().stream()
.filter(Objects::nonNull).filter(featureFilter -> featureFilter.getName() != null);

if (featureFlag.getConditions().getClientFilters().size() == 0) {
return true;
}

// All Filters must be true
if (featureItem.getRequirementType().equals("All")) {
return filters.allMatch(featureFilter -> isFeatureOn(featureFilter, feature));
if (featureFlag.getConditions().getRequirementType().equals("All")) {
return filters.allMatch(featureFilter -> isFeatureOn(featureFilter, featureName));
}

// Any Filter must be true
return filters.anyMatch(featureFilter -> isFeatureOn(featureFilter, feature));
return filters.anyMatch(featureFilter -> isFeatureOn(featureFilter, featureName));
}

private boolean isFeatureOn(FeatureFilterEvaluationContext filter, String feature) {
Expand All @@ -129,25 +123,15 @@ private boolean isFeatureOn(FeatureFilterEvaluationContext filter, String featur
* @return a set of all feature names
*/
public Set<String> getAllFeatureNames() {
Set<String> allFeatures = new HashSet<>();

allFeatures.addAll(featureManagementConfigurations.getOnOff().keySet());
allFeatures.addAll(featureManagementConfigurations.getFeatureManagement().keySet());
return allFeatures;
return new HashSet<String>(
featureManagementConfigurations.getFeatureFlags().stream().map(feature -> feature.getId()).toList());
}

/**
* @return the featureManagement
*/
Map<String, Feature> getFeatureManagement() {
return featureManagementConfigurations.getFeatureManagement();
}

/**
* @return the onOff
*/
Map<String, Boolean> getOnOff() {
return featureManagementConfigurations.getOnOff();
List<Feature> getFeatureManagement() {
Comment thread
mrm9084 marked this conversation as resolved.
Outdated
return featureManagementConfigurations.getFeatureFlags();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,38 @@

package com.azure.spring.cloud.feature.management.implementation;

import org.springframework.util.StringUtils;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;

import org.springframework.util.StringUtils;

import com.azure.spring.cloud.feature.management.models.TargetingException;

public class FeatureFilterUtils {

public static void updateValueFromMapToList(Map<String, Object> parameters, String key) {
Comment thread
mrm9084 marked this conversation as resolved.
Outdated
updateValueFromMapToList(parameters, key, false);
}
/**
* Looks at the given key in the parameters and coverts it to a list if it is currently a map.
*
* @param parameters map of generic objects
* @param key key of object int the parameters map
*/
@SuppressWarnings("unchecked")
public static void updateValueFromMapToList(Map<String, Object> parameters, String key) {
public static void updateValueFromMapToList(Map<String, Object> parameters, String key, boolean fixNull) {
Comment thread
rossgrambo marked this conversation as resolved.
Outdated
Object objectMap = parameters.get(key);
if (objectMap instanceof Map) {
Collection<Object> toType = ((Map<String, Object>) objectMap).values();
parameters.put(key, toType);
} else if ((objectMap != null && objectMap.equals("")) || (objectMap == null && fixNull)) {
parameters.put(key, new ArrayList<Object>());
} else if (objectMap != null) {
parameters.put(key, objectMap);
}
}

Expand All @@ -30,4 +44,41 @@ public static String getKeyCase(Map<String, Object> parameters, String key) {
}
return StringUtils.uncapitalize(key);
}

/**
* Computes the percentage that the contextId falls into.
*
* @param contextId Id of the context being targeted
* @return the bucket value of the context id
* @throws TargetingException Unable to create hash of target context
*/
public static double isTargetedPercentage(String contextId) {
byte[] hash = null;

try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
hash = digest.digest(contextId.getBytes());

} catch (NoSuchAlgorithmException e) {
throw new TargetingException("Unable to find SHA-256 for targeting.", e);
}

if (hash == null) {
throw new TargetingException("Unable to create Targeting Hash for " + contextId);
}

BigInteger bi = fromLittleEndianByteArray(hash);

return (bi.longValue() / (Math.pow(2, 32) - 1)) * 100;
}

public static BigInteger fromLittleEndianByteArray(byte[] bytes) {
byte[] reversedBytes = new byte[4];
Comment thread
mrm9084 marked this conversation as resolved.
Outdated
for (int i = 0; i < 4; i++) {
reversedBytes[i] = bytes[3 - i];
}

return new BigInteger(1, reversedBytes);
}

}
Loading