Specification pattern

The interfaces and classes listed in this chapter provide support for the Specification pattern, as described in Eric Evans' book Domain Driven Design, p224.

Apache Causeway will automatically apply such specifications as validation rules on properties (as per @Property#mustSatisfy()) and on action parameters (as per @Parameter#mustSatisfy()).

Specification

The heart of the support for this pattern is the Specification interface:

public interface Specification {
    String satisfies(Object obj);  (1)
}
1 if returns null, then the constraint is satisfies; otherwise returns the reason why the constraint has not been satisfied.

For example:

public class StartWithCapitalLetterSpecification implements Specification {
    public String satisfies(Object proposedObj) {
        String proposed = (String)proposedObj;               (1)
        return "".equals(proposed)
            ? "Empty string"
            : !Character.isUpperCase(proposed.charAt(0))
                ? "Does not start with a capital letter"
                : null;
    }
}
public class Customer {
    @Property(mustSatisfy=StartWithCapitalLetterSpecification.class)
    public String getFirstName() { /* ... */ }
    ...
}
1 this ugly cast can be avoided using some of the other classes available; see below.

Specification2

The Specification2 interface extends the Specification API to add support for i18n. This is done by defining an additional method that returns a translatable string:

public interface Specification2 extends Specification {
    public TranslatableString satisfiesTranslatable(Object obj);  (1)
}
1 if returns null, then the constraint is satisfies; otherwise returns the reason why the constraint has not been satisfied.

Note that if implementing Specification2 then there is no need to also provide an implementation of the inherited satisfies(Object) method; this will never be called by the framework for Specification2 instances.

Adapter classes

The AbstractSpecification and AbstractSpecification2 adapter classes provide a partial implementation of the respective interfaces, providing type-safety. (Their design is modelled on the TypesafeMatcher class within Hamcrest).

For example:

public class StartWithCapitalLetterSpecification extends AbstractSpecification<String> {
    public String satisfiesSafely(String proposed) {
        return "".equals(proposed)
            ? "Empty string"
            : !Character.isUpperCase(proposed.charAt(0))
                ? "Does not start with a capital letter"
                : null;
    }
}
public class Customer {
    @Property(mustSatisfy=StartWithCapitalLetterSpecification.class)
    public String getFirstName() { /* ... */ }
    ...
}

The AbstractSpecification2 class is almost identical; its type-safe method is satisfiesTranslatableSafely(T) instead.

Combining specifications

There are also adapter classes that can be inherited from to combine specifications:

  • SpecificationAnd - all provided specifications' constraints must be met

  • SpecificationOr - at least one provided specifications' constraints must be met

  • SpecificationNot - its constraints are met if-and-only-if the provided specification’s constraint was not met.

Note that these adapter classes inherit Specification but do not inherit Specification2; in other words they do not support i18n.