package org.togglz.core.activation;
import org.togglz.core.Feature;
import org.togglz.core.repository.FeatureState;
import org.togglz.core.spi.ActivationStrategy;
import org.togglz.core.user.FeatureUser;
import org.togglz.core.util.Strings;
/**
* <p>
* An abstract activation strategy that is designed to support cases where the activation of a feature is driven based
* on the value of environmental/contextual properties.
* </p>
* <p>
* {@code AbstractPropertyDrivenActivationStrategy} allows the name of the property can be passed via the
* "{@value #PARAM_NAME}" parameter and gracefully falls back on a property name that is derived from the
* {@link Feature} itself (e.g. "{@value #DEFAULT_PROPERTY_PREFIX}FEATURE_NAME"). It will take care of the majority of
* the work and only really requires implementations to provide the means to lookup the value of the property:
* </p>
* <pre>
* @Override
* protected abstract String getPropertyValue(FeatureState featureState, FeatureUser user, String name) {
* return doSomeStuffToGetPropertyValue(name);
* }
* </pre>
* <p>
* By default, the value of the property will be converted into a boolean but implementations are free to override this,
* where needed, by overriding the {@link #isActive(FeatureState, FeatureUser, String, String)} method. However, it
* would be ideal if all implementations tried to support the same value formats to allow for a unified experience. The
* default conversion will fail fast if the property value does not match any of the predefined boolean representations
* by throwing an {@code IllegalArgumentException}.
* </p>
* <p>
* All implementations should honor the rule that the feature should not be activated if no matching property is found.
* </p>
*
* @author Alasdair Mercer
* @see #getPropertyValue(FeatureState, FeatureUser, String)
*/
public abstract class AbstractPropertyDrivenActivationStrategy implements ActivationStrategy {
public static final String DEFAULT_PROPERTY_PREFIX = "togglz.";
public static final String PARAM_NAME = "name";
/**
* <p>
* Returns the name of the property on which to base the activation of the feature.
* </p>
* <p>
* This method will first attempt to use the value of the parameter with the name provided. If that does not return
* a valid property name (i.e. non-blank), then a property name will be constructed using a common prefix
* ("{@value #DEFAULT_PROPERTY_PREFIX}") and the name of the feature.
* </p>
*
* @param featureState
* the {@link FeatureState} which represents the current configuration of the feature
* @param parameterName
* the name of the parameter that potentially contains the property name
* @return The name of the property.
*/
protected String getPropertyName(FeatureState featureState, String parameterName) {
String propertyName = featureState.getParameter(parameterName);
if (Strings.isNotBlank(propertyName)) {
return propertyName;
}
return DEFAULT_PROPERTY_PREFIX + featureState.getFeature().name();
}
/**
* <p>
* Returns the value of the property with the specified {@code name} on which to base the activation of the feature.
* </p>
*
* @param featureState
* the {@link FeatureState} which represents the current configuration of the feature
* @param user
* the {@link FeatureUser user} for which to decide whether the feature is active (may be {@literal null})
* @param name
* the name of the property whose value is to be returned
* @return The (raw) value of the property with the given {@code name} or {@literal null} if none could be found.
*/
protected abstract String getPropertyValue(FeatureState featureState, FeatureUser user, String name);
@Override
public final boolean isActive(FeatureState featureState, FeatureUser user) {
String propertyName = getPropertyName(featureState, PARAM_NAME);
String propertyValue = getPropertyValue(featureState, user, propertyName);
return isActive(featureState, user, propertyName, propertyValue);
}
/**
* <p>
* This method is called by {@link #isActive(FeatureState, FeatureUser)} with the property name and value to make
* the decision as to whether the feature is active.
* </p>
* <p>
* By default, this method will convert {@code propertyValue} into a boolean using
* {@link Strings#toBoolean(String)} but implementations are free to override this, where needed. However, it would
* be ideal if all implementations tried to support the same value formats to allow for a unified experience. The
* default implementation throw an {@code IllegalArgumentException} if {@code propertyValue} does not match any of
* the predefined boolean representations.
* </p>
* <p>
* This method should never return {@literal true} if {@code propertyValue} is {@literal null}.
* </p>
*
* @param featureState
* the {@link FeatureState} which represents the current configuration of the feature
* @param user
* the {@link FeatureUser user} for which to decide whether the feature is active (may be {@literal null})
* @param propertyName
* the name of the property on which to base the activation of the feature
* @param propertyValue
* the (raw) value of the property on which to base the activation of the feature (may be {@literal null} if
* none was found)
* @return {@literal true} if the feature should be active; otherwise {@literal false}.
* @throws IllegalArgumentException
* If {@code propertyValue} is non-{@literal null} <b>and</b> does not match any of the predefined boolean
* representations.
*/
protected boolean isActive(FeatureState featureState, FeatureUser user, String propertyName, String propertyValue) {
return Boolean.TRUE.equals(Strings.toBoolean(propertyValue));
}
@Override
public Parameter[] getParameters() {
return new Parameter[] {
ParameterBuilder.create(PARAM_NAME)
.optional()
.label("Property Name")
.description("The name of the property to be used to determine whether the feature is enabled.")
};
}
}