Enterprise Java Development@TOPIC@
EJB Bean Class
Business method(s)
Normalization and validation concerns
Decouple data manipulation/validation from business/DAO logic
Figure 120.1. EJB Bean with Business Method
@Stateless
@Validation
public class ContactsEJB implements ContactsRemote {
@Override
public Contact createContact(Contact contact) throws InvalidParam {
logger.debug("createContact({})", contact);
em.persist(contact);
return contact;
}
Must correct prior to processing
Callers can provide names using different normalization
Name could be missing entirely
Declarative API to define validation rules
JPA validates Default.class
group by default on @PrePersist and @PreUpdate
Can define other custom lifecycle groups
Figure 120.3. Validation API
@Entity
public class Contact implements Serializable {
@Id @GeneratedValue
@Column(name="CONTACT_ID")
private int id;
@NotNull(groups={PreNormalizedCheck.class, PrePersistCheck.class})
@Size(min=1, max=32, groups={PostNormalizedCheck.class, PrePersistCheck.class})
@Pattern.List({
@Pattern(regexp="^[A-Za-z0-9-\\ ]+$", groups=PreNormalizedCheck.class),
@Pattern(regexp="^([A-Z][a-z0-9-]+\\ *)+$", groups=PostNormalizedCheck.class)
})
@Column(name="CONTACT_NAME", length=32, nullable=false)
private String name;
@Valid
@OneToMany(fetch=FetchType.LAZY, mappedBy="contact", orphanRemoval=true,
cascade={CascadeType.PERSIST, CascadeType.REMOVE})
private List<ContactInfo> contactInfo;
Figure 120.4. Custom Validation Groups
public interface PreNormalizedCheck {}
public interface PrePersistCheck {}
public interface PostNormalizedCheck {}
JPA will validate Default
group for pre-persist and pre-update by default when activated
Can override/specify pre-persist, pre-update, and pre-remove validation
Figure 120.5. Activate JPA Validation with Custom Group
@NotNull(groups={PrePersistCheck.class})
@Size(min=1, max=32, groups={PrePersistCheck.class})
@Column(name="CONTACT_NAME", length=32, nullable=false)
private String name;
<?xml version="1.0" encoding="UTF-8"?>
<persistence ...
<persistence-unit name="ejbinterceptor-contacts">
<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
<validation-mode>CALLBACK</validation-mode>
<properties>
<property name="javax.persistence.validation.group.pre-persist"
value="info.ejava.examples.ejb.interceptor.bo.PrePersistCheck"/>
<property name="javax.persistence.validation.group.pre-update"
value="info.ejava.examples.ejb.interceptor.bo.PrePersistCheck"/>
</properties>
</persistence-unit>
</persistence>
Activating validation within JPA lifecycle
Overriding/specifying pre-persist and pre-update to use custom PrePersistCheck
Custom group to defining validation after normalization and prior to JPA
Figure 120.6. PostNormalizedCheck
@Size(min=1, max=32, groups={PostNormalizedCheck.class, PrePersistCheck.class})
@Pattern(regexp="^([A-Z][a-z0-9-]+\\ *)+$", groups=PostNormalizedCheck.class)
private String name;
We apply detailed validation checks during PostNormalizationCheck
Custom group to defining validation prior to normalization
Figure 120.7. PreNormalizedCheck
@NotNull(groups={PreNormalizedCheck.class, PrePersistCheck.class})
@Size(min=1, max=32, groups={PostNormalizedCheck.class, PrePersistCheck.class})
@Pattern.List({
@Pattern(regexp="^[A-Za-z0-9-\\ ]+$", groups=PreNormalizedCheck.class),
@Pattern(regexp="^([A-Z][a-z0-9-]+\\ *)+$", groups=PostNormalizedCheck.class)
})
@Column(name="CONTACT_NAME", length=32, nullable=false)
private String name;
We apply brief sanity check validation prior to normalization
Handle validating Validation API-marked POJOs as Interceptor
Figure 120.8. Validator Base Class
public class ValidatorInterceptor {
@Inject
private Validator validator;
private Class<?>[] groups;
protected ValidatorInterceptor() {}
public ValidatorInterceptor(Class<?>[] groups) { this.groups = groups; }
@AroundInvoke
public Object invoke(InvocationContext ctx) throws Exception {
logger.debug("validating method: {}, groups: {}", ctx.getMethod(), Arrays.toString(groups));
//validate each parameter
for (Object param: ctx.getParameters()) {
logger.debug("validating param: {}, groups: {}", param, Arrays.toString(groups));
Set<ConstraintViolation<Object>> violations = validator.validate(param, groups);
if (!violations.isEmpty()) {
Exception ex = new InvalidParam(param.toString(), getErrors(violations));
logger.debug("aborting call, found error: {}", ex.getMessage());
throw ex;
}
}
return ctx.proceed();
}
private List<String> getErrors(Set<ConstraintViolation<Object>> violations) {
List<String> errors = new ArrayList<String>(violations.size());
for (ConstraintViolation<Object> v: violations) {
errors.add(v.toString());
}
return errors;
}
}
Grabs parameters from InvocationContext
Validates each of them against assigned validation group
Figure 120.9. Pre/PostNormalized Concrete Interceptors
@Validation
@Interceptor
public class PreNormizedInterceptor extends ValidatorInterceptor {
public PreNormizedInterceptor() {super(new Class<?>[]{PreNormalizedCheck.class});}
}
@Validation
@Interceptor
public class PostNormizedInterceptor extends ValidatorInterceptor {
public PostNormizedInterceptor() { super(new Class<?>[]{PostNormalizedCheck.class}); }
}
Define @InterceptorBinding (@Validation) and @Interceptor role for CDI
Define validation group(s) for base class
Intercepts parameters going into business methods
Applies normalization rules to parameter values
Figure 120.10. ContactsNormalizerInterceptor
@Validation
@Interceptor
public class ContactsNormalizerInterceptor {
private ContactNormalizer contactNormalizer=new ContactNormalizer();
@AroundInvoke
public Object invoke(InvocationContext ctx) throws Exception {
logger.debug("intercepting: {}", ctx.getMethod());
for (Object param: ctx.getParameters()) {
if (param instanceof Contact) {
logger.debug("normalizing: {}", param);
contactNormalizer.normalize((Contact) param);
logger.debug("normalized: {}", param);
} else if (param instanceof PostalInfo) {
logger.debug("normalizing: {}", param);
contactNormalizer.normalize((PostalInfo) param);
logger.debug("normalized: {}", param);
} else if (param instanceof PhoneInfo) {
logger.debug("normalizing: {}", param);
contactNormalizer.normalize((PhoneInfo) param);
logger.debug("normalized: {}", param);
}
}
return ctx.proceed();
}
Defines @InterceptorBinding (@Validator) and @Interceptor role
Defines @AroundInvoke for business method
Subjects each parameter to normalization rules
Details of normalization omitted (initial caps for each name/word)
Figure 120.11. Bean Class
@Stateless
@Validation
public class ContactsEJB implements ContactsRemote {
@Override
public Contact createContact(Contact contact) throws InvalidParam {
logger.debug("createContact({})", contact);
em.persist(contact);
return contact;
}
Applies @interceptorBinding annotation (@Validation)
Supplies business method
One of several techniques possible
Figure 120.12. CDI Activation (bean.xml)
<?xml version="1.0" encoding="UTF-8"?>
<beans ...
<interceptors>
<class>info.ejava.examples.ejb.interceptor.interceptors.PreNormizedInterceptor</class>
<class>info.ejava.examples.ejb.interceptor.interceptors.ContactsNormalizerInterceptor</class>
<class>info.ejava.examples.ejb.interceptor.interceptors.PostNormizedInterceptor</class>
</interceptors>
</beans>
Control ordering of interceptors
Figure 120.13. Example Output
ValidatorInterceptor] validating param: Contact [id=0, name=john Doe], groups: [interface info.ejava.examples.ejb.interceptor.bo.PreNormalizedCheck] ContactsNormalizerInterceptor] normalizing: Contact [id=0, name=john Doe] ContactsNormalizerInterceptor] normalized: Contact [id=0, name=John Doe] ValidatorInterceptor] validating param: Contact [id=0, name=John Doe], groups: [interface info.ejava.examples.ejb.interceptor.bo.PostNormalizedCheck] ContactsEJB] createContact(Contact [id=0, name=John Doe]) call next value for hibernate_sequence insert into EJBINTERCEPTOR_CONTACT (CONTACT_NAME, NORMALIZED_NAME, CONTACT_ID) values (?, ?, ?)
Input hand a correctable, but invalid name
Name passed initial pre-normalization validation
Name corrected by normalization
Name passed post-normalization validation
Name passed pre-persist validation
Contac persisted