Constraints are defined in Constraint.
If you want to reproduce what’s explained in this tutorial, remember to depend on the Core Bootstrap artifact:
Table 9. Artifact
Group ID | Artifact ID | Version |
---|---|---|
org.apache.polygene.core | org.apache.polygene.core.bootstrap | 3.0.0 |
At runtime you will need the Core Runtime artifact too. See the Depend on Polygene™ tutorial for details.
Method Constraints are declared with annotations on the method argument. The annotation itself is custom, and it is possible to make your own.
public interface Dialer { void callPhoneNumber(@PhoneNumber String phoneNo); }
In the code above we say that we want the argument to the callPhoneNumber() method to be a valid phone number. This annotation is not built-in, so we need to declare it.
@ConstraintDeclaration @Retention( RetentionPolicy.RUNTIME ) @Target( { ElementType.PARAMETER, ElementType.ANNOTATION_TYPE, ElementType.METHOD } ) public @interface PhoneNumber { }
We then need to provide the Constraint implementation.
public class PhoneNumberConstraint implements Constraint<PhoneNumber, String> { public boolean isValid( PhoneNumber annotation, String number ) { boolean validPhoneNumber = true; // check phone number format... return validPhoneNumber; // return true if valid phone number. } }
We also need to include the Constraint on the Composites we want to have them present.
@Constraints( PhoneNumberConstraint.class ) public interface DialerComposite extends Dialer { }
If a Constraint is violated, then a ConstraintViolationException is thrown. The Exception contains ALL violations found in the method invocation. Concerns can be used to catch and report these violations.
public class ParameterViolationConcern extends ConcernOf<InvocationHandler> implements InvocationHandler { public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable { try { return next.invoke( proxy, method, args ); } catch( ConstraintViolationException e ) { for( ValueConstraintViolation violation : e.constraintViolations() ) { String name = violation.name(); Object value = violation.value(); Annotation constraint = violation.constraint(); report( name, value, constraint ); } throw new IllegalArgumentException("Invalid argument(s)", e); } } [...snip...] private void report( String name, Object value, Annotation constraint ) { } }
Property Constraints are declared on the Property method.
public interface HasPhoneNumber { @PhoneNumber Property<String> phoneNumber(); }
In this case, the Constraint associated with the phoneNumber() method, will be called before the set() method on that Property is called. If there is a constraint violation, the Exception thrown will be part of the caller, and not the composite containing the Property, so a reporting constraint on the containing Composite will not see it. If you want the containing Composite to handle the Constraint Violation, then you need to add a Concern on the Property itself, which can be done like this;
public abstract class PhoneNumberParameterViolationConcern extends ConcernOf<HasPhoneNumber> implements HasPhoneNumber { @Concerns( CheckViolation.class ) public abstract Property<String> phoneNumber(); private abstract class CheckViolation extends ConcernOf<Property<String>> implements Property<String> { public void set( String number ) { try { next.set( number ); } catch( ConstraintViolationException e ) { Collection<ValueConstraintViolation> violations = e.constraintViolations(); report( violations ); } } [...snip...] private void report( Collection<ValueConstraintViolation> violations ) { } } }