Since version 2.0.0 Togglz offers Activation Strategies to go with a feature. For instance, you can connect a list of server IP addresses that shall have the feature enabled. However, how are these strategies actually attached to a feature? All I saw was that I can change the strategy in the Togglz console or even edit the data by hand in the database.
What I was looking for is some default mechanism rather similar to @EnabledByDefault
. I could implement a custom state repository, it could even look for annotations, but I suspected that this solution existed out of the box.
Just to share my own solution.
I defined annotations that should be used the way @EnabledByDefault
is. Here is one for the server-ip strategy:
/**
* Allows to specify that the annotated feature should use
* {@link ServerIPStrategy} if the repository doesn't have any
* state saved.
*
* @author Michael Piefel
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface UsingServerIPStrategy {
/**
* A comma-separated list of server IPs for which
* the feature should be active.
*/
String value();
}
In the feature definition, use the annotion like this:
…
@EnabledByDefault
@UsingServerIPStrategy(value = "192.168.1.211")
@Label("Run regular jobs to send status e-mails to participants")
MAIL_CRON_JOBS;
…
I want to take the feature state from a repository if it already has been saved. If not, the annotations must be evaluated. For this, a delegation repository is needed:
/**
* A Togglz {@link StateRepository} that looks for default strategies
* on the defined features.
*
* @author Michael Piefel
*/
@AllArgsConstructor
public class DefaultingStateRepository implements StateRepository {
private StateRepository delegate;
@Override
public FeatureState getFeatureState(Feature feature) {
FeatureState featureState = delegate.getFeatureState(feature);
if (featureState == null) {
// Look for a default strategy.
// If none is defined, a null return value is good enough.
UsingServerIPStrategy serverIPStrategy = FeatureAnnotations
.getAnnotation(feature, UsingServerIPStrategy.class);
if (serverIPStrategy != null) {
featureState = new FeatureState(feature,
FeatureAnnotations.isEnabledByDefault(feature));
featureState.setStrategyId(ServerIpActivationStrategy.ID);
featureState.setParameter(ServerIpActivationStrategy.PARAM_IPS,
serverIPStrategy.value());
}
}
return featureState;
}
@Override
public void setFeatureState(FeatureState featureState) {
// write through
delegate.setFeatureState(featureState);
}
}
Finally, to use the repository, I wired it in our TogglzConfig
component, deferring to JDBC, but letting it be cached as well:
…
@Override
public StateRepository getStateRepository() {
JDBCStateRepository jdbcStateRepository = new JDBCStateRepository(dataSource);
DefaultingStateRepository defaultingStateRepository = new
DefaultingStateRepository(jdbcStateRepository);
return new CachingStateRepository(defaultingStateRepository, 60_000);
}
…