Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -2,21 +2,17 @@
// Licensed under the MIT License.
package com.azure.spring.cloud.feature.management.filters;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import com.azure.spring.cloud.feature.management.implementation.FeatureFilterUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import com.azure.spring.cloud.feature.management.implementation.FeatureFilterUtils;
import com.azure.spring.cloud.feature.management.implementation.targeting.Audience;
import com.azure.spring.cloud.feature.management.implementation.targeting.Exclusion;
import com.azure.spring.cloud.feature.management.implementation.targeting.GroupRollout;
Expand Down Expand Up @@ -133,8 +129,10 @@ public boolean evaluate(FeatureFilterEvaluationContext context) {

Audience audience;
String exclusionValue = FeatureFilterUtils.getKeyCase(parameters, EXCLUSION_CAMEL);
String exclusionUserValue = FeatureFilterUtils.getKeyCase((Map<String, Object>) parameters.get(exclusionValue), "Users");
String exclusionGroupsValue = FeatureFilterUtils.getKeyCase((Map<String, Object>) parameters.get(exclusionValue), "Groups");
String exclusionUserValue = FeatureFilterUtils.getKeyCase((Map<String, Object>) parameters.get(exclusionValue),
"Users");
String exclusionGroupsValue = FeatureFilterUtils
.getKeyCase((Map<String, Object>) parameters.get(exclusionValue), "Groups");

if (((Map<String, Object>) parameters.getOrDefault(exclusionValue, new HashMap<>()))
.get(exclusionUserValue) instanceof List) {
Expand Down Expand Up @@ -227,35 +225,8 @@ private boolean validateTargetingContext(TargetingFilterContext targetingContext
return (!hasUserDefined && !(hasGroupsDefined && hasAtLeastOneGroup));
}

/**
* 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
*/
protected double isTargetedPercentage(String contextId) {
byte[] hash = null;

try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
hash = digest.digest(contextId.getBytes(Charset.defaultCharset()));
} 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);
}

ByteBuffer wrapped = ByteBuffer.wrap(hash);
int contextMarker = Math.abs(wrapped.getInt());

return (contextMarker / (double) Integer.MAX_VALUE) * 100;
}

private boolean isTargeted(String contextId, double percentage) {
return isTargetedPercentage(contextId) < percentage;
return FeatureFilterUtils.isTargetedPercentage(contextId) < percentage;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@

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

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
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 {

/**
Expand All @@ -34,4 +39,43 @@ 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;
if (contextId == null) {
contextId = "\n";
}

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 = bigEndianToLittleEndian(hash);

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

public static BigInteger bigEndianToLittleEndian(byte[] bigEndian) {
byte[] reversedBytes = new byte[4];
for (int i = 0; i < 4; i++) {
reversedBytes[i] = bigEndian[3 - i];
}

return new BigInteger(1, reversedBytes);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ public void targetedGroupFiftyPass() {

TargetingFilter filter = new TargetingFilter(new TargetingFilterTestContextAccessor("Jane", targetedGroups));

assertTrue(filter.evaluate(context));
assertFalse(filter.evaluate(context));
}

@Test
Expand Down Expand Up @@ -246,7 +246,7 @@ public void targetedGroupFiftyFalse() {

TargetingFilter filter = new TargetingFilter(new TargetingFilterTestContextAccessor("Doe", targetedGroups));

assertFalse(filter.evaluate(context));
assertTrue(filter.evaluate(context));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.azure.spring.cloud.feature.management.implemenation;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;

import com.azure.spring.cloud.feature.management.implementation.FeatureFilterUtils;

public class TargetingFilterUtilsTest {

@Test
public void isTargetedPercentageTest() {
assertEquals(FeatureFilterUtils.isTargetedPercentage(null), 9.875071074318855);
assertEquals(FeatureFilterUtils.isTargetedPercentage(""), 26.0813765987012);
assertEquals(FeatureFilterUtils.isTargetedPercentage("Alice"), 38.306839656621875);
assertEquals(FeatureFilterUtils.isTargetedPercentage("Quinn\nDeb"), 38.306839656621875);
assertEquals(FeatureFilterUtils.isTargetedPercentage("\nProd"), 79.98622464481421);
}

}