Composite constraints

It is possible to create composite constraints by creating a tree of the existing constraint types. Because of the inflexibility of Java annotations, combined with a preference for re-usability, composite constraints are objects built and registered in code.

The available constraint types are

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

The first five are exactly as covered elsewhere in this documentation; 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 javax.inject.Inject;
import javax.inject.Singleton;

@Singleton
public class CompositeConstraints
{
    @Inject
    public CompositeConstraints(final CompositeCache compositeCache,
                                final ConstraintBuilders builders)
    {
        compositeCache.register("curatorOrSubjectNotPresent",
                                new ConstraintTree(Operator.OR,
                                                   builders.subjectNotPresent().build(),
                                                   builders.pattern("curator.museum.*",
                                                                    PatternType.REGEX).build()));
    }
}

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("curatorOrSubjectNotPresent")
public class Foo extends Controller
{
    ...
}

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

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

Constraint re-use

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().build(),
                                           c1,
                                           c3));
compositeCache.register("barConstraint",
                        new ConstraintTree(Operator.AND,
                                           c2,
                                           c3));

Building constraints

You can see in the examples above that constraints have .build() hanging off the end of each call to builders. This is because further customization of the constraints is possible, allowing you to override the reasonable (i.e. harmless) defaults that are pre-set.

Each constraint has various options; one that is present for every constraint is content(Optional<String> content), which allows you to provide a hint for the onAuthFailure method of your Deadbolt handler implementations if authorization should fail. Other options include providing additional information for dynamic and pattern constraints, via meta(Optional<String> meta) and inverting the meaning of pattern constraints via invert(boolean invert).

All of these options have the same meaning as those found in controller constraint annotations and template parameters.