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 )
{
}
}
}