It is standard industry practice for good architects to design enterprise applications in sets of tiers with many modules containing public interfaces around opaque implementations. These interfaces are often critical to servicing requests from a client endpoint such as dependent EJB call, web service, or web request. Bean Validation 1.1 allows us to write constraints around these endpoints with a new feature called Method-level constraints.
Method-level validation can only be applied to non-static methods and also constructors. The provider will ignore method-level constraints on static methods.
Method-level constraints require interception technology through the integration with CDI, Spring Framework, or Guice. It works for constructors as well as methods and the validation is implemented as a crosscutting concern.
Bean Validation 1.1 is available for both Java EE 7 Full and Web profile application servers. For earlier Java EE editions, in particular the Web profiles Java EE 5 and 6, the administrator must configure the validator to a CDI container in a proprietary configuration. Note that GlassFish 4 contains the reference implementation Hibernate Validator 5.
Let us start with a stateless session EJB that will stand in a business service that we want to constrain. It is a payment service that takes an account and returns a ticket acknowledging payment and a new account value.
Here is the definition of the PaymentServiceImpl
:
package je7hb.beanvalidation.payments; import javax.ejb.Stateless; import javax.validation.Valid; import javax.validation.constraints.*; import java.math.BigDecimal; import java.util.Date; @Stateless public class PaymentServiceImpl { @NotNull @SecureReceipt @Valid public Receipt payEntity( @NotNull @Valid Account account, @NotNull @Size(min = 5, max = 32) String counterparty, @NotNull @DecimalMin("0.0") BigDecimal amount) { String msg; if ( counterparty.contains("Bridgetown")) msg = "SEC123"; else msg = "Boo! Hoo!"; Account acct2 = new Account( account.getAccount(), account.getAmount().subtract(amount)); return new Receipt(msg,counterparty,acct2, new Date()); } }
The method payEntity()
has constraints applied on the arguments for the account, the counterparty, and the monetary value. It also has a custom constraint @SecureReceipt
applied on the return value and also a @NotNull
constraint. The Bean Validation provider through the interception technology is able to check the input arguments and also the return value. The
@Valid
constraint informs the Bean Validation provider to recursively apply the constraints to the dependent object. So in this example, we check the input Account
value object and also the returned Receipt
object instance.
The code snippet for
SecureReceipt
annotation looks like this:
@Documented @Constraint(validatedBy = SecureReceiptValidator.class) @Target({ METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface SecureReceipt { String message() default "{je7hb.beanvalidation.payment.SecureReceipt.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
The class-level constraint implementation
SecureReceiptValidator
object looks like this:
public class SecureReceiptValidator implements ConstraintValidator<SecureReceipt,Receipt> { @Override public void initialize(SecureReceipt annotation) { } @Override public boolean isValid(Receipt value, ConstraintValidatorContext context) { if ( value == null) return true; return value.getMessage().trim().startsWith("SEC"); } L}
The isValid()
method in SecureReceiptValidator
verifies the return object SecureType
and has a message that starts with a string SEC
. This is a perfect example of how an application can ensure that a business layer has not been compromised and actually provides the correct result to a web client—a RESTful client endpoint or an EJB endpoint.
See the book's source code for the Arquillian unit test class and the value objects Account
and Receipt
.
These rules are imposed by the Bean Validation specification in order to preserve the Barbara Liskov Substitution Principle, which governs the behavior of subtyping. An object is open for extension but closed for modification, and as such if there is a value type of A that extends S then I can replace it with another type B that extends S.
3.128.226.121