One feature that’s been on my to-do list for quite a while is composite constraints. As of Deadbolt 2.5, these will be available. The Java version is implemented, and I’m working on the Scala version at the moment. Here, I’ll be taking a look at how to do this in Java.

So, what are composite constraints? At the moment, the various constraints available can’t be combined meaningfully with other constraints. For example, this wouldn’t work in the way you would expect. What’s the logical relationship between the constraints – AND, OR…NOT?

@SubjectNotPresent
@Restrict(@Group("hurdy", "gurdy"))
public CompletionStage foo() {
    // ...
}

Let’s say, for the sake of argument, this is an OR relationship. Access to the resource is allowed if there is no subject present OR there is a subject and that subject has the hurdy and gurdy roles.

The inflexible nature of Java annotations does not make it easy to define the required combination within a generic mechanism, and I’m not sure this would be a good idea anyway. Annotations, if allowed, can become monsters – this is summarized perfectly over at the jOOQ blog. A far more natural (and testable, and reusable) approach is to define the composite constraints in code, and reference them from an annotation.

The available constraint types are

  • subject present
  • subject not present
  • restrict
  • pattern
  • dynamic
  • constraint tree

The first five have existed in Deadbolt for quite some time, as both controller and template constraints; a constraint tree contains a list of constraints along with the AND/OR operator that should be applied to them. Because ConstraintTree is also a Constraint, sub-trees can be used to form arbitrarily complex constraints.

To create and register a composite constraint, the easiest way is to use an eager singleton, and inject it with instances of CompositeCache and ConstraintBuilders. The ConstraintBuilders instance contains everything you need to create constraints. Once you have a constraint, you need to register it with the composite cache.

import be.objectify.deadbolt.java.cache.CompositeCache;
import be.objectify.deadbolt.java.composite.ConstraintBuilders;
import be.objectify.deadbolt.java.composite.ConstraintTree;
import be.objectify.deadbolt.java.composite.Operator;
import be.objectify.deadbolt.java.models.PatternType;
import static be.objectify.deadbolt.java.composite.ConstraintBuilders.allOf;

import javax.inject.Inject;
import javax.inject.Singleton;

@Singleton
public class CompositeConstraints
{
    @Inject
    public CompositeConstraints(final CompositeCache compositeCache,
                                final ConstraintBuilders builders)
    {
        compositeCache.register("hurdyGurdyOrSubjectNotPresent",
                                new ConstraintTree(Operator.OR,
                                                   builders.subjectNotPresent(),
                                                   builders.restrict(allOf("hurdy", "gurdy"))));
    }
}

In your controllers, you can now use the Composite annotation to apply the constraint to either the controller class or a specific method, in the same way you would any other Deadbolt annotation.

@Composite("hurdyGurdyOrSubjectNotPresent")
public CompletionStage foo() {
    // ...
}

Finally, register a binding for CompositeConstraints. You can do this in the same place as you register your HandlerCache, as described in Integrating Deadbolt in the documentation.

public class CustomDeadboltHook extends Module
{
    @Override
    public Seq> bindings(final Environment environment,
                                    final Configuration configuration)
    {
        return seq(bind(HandlerCache.class).toInstance(new MyHandlerCache(new MyDeadboltHandler())),
                   bind(CompositeConstraints.class).toSelf().eagerly());
    }
}

Earlier, I mentioned re-usability. Constraints contain only configurational state, and so can be used in multiple constraint trees.

final Constraint c1 = new ConstraintTree(// some constraints);
final Constraint c2 = new ConstraintTree(// some constraints);
final Constraint c3 = new ConstraintTree(// some constraints);
compositeCache.register("fooConstraint",
                        new ConstraintTree(Operator.OR,
                                           builders.subjectNotPresent(),
                                           c1,
                                           c3));
compositeCache.register("barConstraint",
                        new ConstraintTree(Operator.AND,
                                           c2,
                                           c3));

Leave a Reply

Your email address will not be published. Required fields are marked *