/** * Activation strategy that enables features for a given percentage of users. This strategy is * typically used to implement gradual rollouts. The implementation is based on a hashcode created * from the name of the acting user which is calculated by {@link #calculateHashCode(FeatureUser)}. * * @author Christian Kaltepoth */ public class GradualActivationStrategy implements ActivationStrategy { private final Log log = LogFactory.getLog(GradualActivationStrategy.class); public static final String ID = "gradual"; public static final String PARAM_PERCENTAGE = "percentage"; @Override public String getId() { return ID; } @Override public String getName() { return "Gradual rollout"; } @Override public boolean isActive(FeatureState state, FeatureUser user) { if (user != null && Strings.isNotBlank(user.getName())) { String percentageAsString = state.getParameter(PARAM_PERCENTAGE); try { int percentage = Integer.valueOf(percentageAsString); if (percentage > 0) { int hashCode = Math.abs(calculateHashCode(user, state.getFeature())); return (hashCode % 100) < percentage; } } catch (NumberFormatException e) { log.error( "Invalid gradual rollout percentage for feature " + state.getFeature().name() + ": " + percentageAsString); } } return false; } /** @deprecated Use {@link #calculateHashCode(FeatureUser, Feature)} instead */ @Deprecated protected int calculateHashCode(FeatureUser user) { return calculateHashCode(user, null); } protected int calculateHashCode(FeatureUser user, Feature feature) { Validate.notNull(user, "user is required"); return new StringBuilder() .append(user.getName().toLowerCase(Locale.ENGLISH).trim()) .append(":") .append(feature != null ? feature.name() : "") .toString() .hashCode(); } @Override public Parameter[] getParameters() { return new Parameter[] { ParameterBuilder.create(PARAM_PERCENTAGE) .label("Percentage") .matching("\\d{1,3}") .description( "Percentage of users for which the feature should be active (i.e. '25' for every fourth user).") }; } }
public class ScriptEngineActivationStrategy implements ActivationStrategy { private final Log log = LogFactory.getLog(ScriptEngineActivationStrategy.class); public static final String ID = "script"; public static final String PARAM_SCRIPT = "script"; public static final String PARAM_LANG = "lang"; private final ScriptEngineManager engineManager; public ScriptEngineActivationStrategy() { engineManager = new ScriptEngineManager(); } @Override public String getId() { return ID; } @Override public String getName() { return "Java Scripting API"; } @Override public boolean isActive(FeatureState featureState, FeatureUser user) { String lang = featureState.getParameter(PARAM_LANG); String script = featureState.getParameter(PARAM_SCRIPT); ScriptEngine engine = engineManager.getEngineByName(lang); if (engine == null) { log.error("Could not find script engine for: " + lang); return false; } engine.put("user", user); engine.put("date", new Date()); try { Object result = engine.eval(script); if (result instanceof Boolean) { return ((Boolean) result).booleanValue(); } } catch (ScriptException e) { log.error( "Could not evaluate script for feature " + featureState.getFeature().name() + ": " + e.getMessage()); } return false; } @Override public Parameter[] getParameters() { return new Parameter[] { new ScriptLanguageParameter(engineManager), ParameterBuilder.create(PARAM_SCRIPT) .label("Script") .largeText() .description( "The script to check if the feature is active. " + "The script context provides access to some default objects. " + "The variable 'user' refers to the current acting FeatureUser " + "and 'date' to the current time represented as a java.util.Date.") }; } private static class ScriptLanguageParameter implements Parameter { private List<String> languages = new ArrayList<String>(); public ScriptLanguageParameter(ScriptEngineManager engineManager) { for (ScriptEngineFactory factory : engineManager.getEngineFactories()) { languages.add(factory.getLanguageName()); } } @Override public String getName() { return PARAM_LANG; } @Override public String getLabel() { return "Language"; } @Override public String getDescription() { return "The script language to use. Your system seems to support the following languages: " + Strings.join(languages, ", "); } @Override public boolean isOptional() { return false; } @Override public boolean isLargeText() { return false; } @Override public boolean isValid(String value) { return Strings.isNotBlank(value) && languages.contains(value); } } }