Friday, December 25, 2009

Integrating JSR 303 Bean Validation with Oracle ADF 11g

In this post, I will try to show you how we were able to successfully integrate the JSR 303 Bean Validation into our ADF 11g Application built with EJB/JPA. The result is a centralized area to define validation constraints using annotations which can be invoked from any layer of the application.

Below is the screen-shot of a sample edit page created with the JSR 303 Bean Validation:
To apply JSR 303 Bean Validation a project, we need to do the following steps:
  1. Download the JSR 303 reference implementation from Hibernate.
  2. Add the 5 jar files into your Model project's Libraries and Classpath (Project Properties).
  3. Annotate your entities with validation annotations like the sample code snippet below:
    ...
    import javax.validation.constraints.DecimalMax;
    import javax.validation.constraints.Max;
    import javax.validation.constraints.Size;
    ...
    public class Employee implements Serializable {
        @Id
        @Column(name="EMPLOYEE_ID", nullable = false)
        private Long employeeId;
        @Column(name="FIRST_NAME", length = 20)
        @Size(min = 3, max = 20)
        private String firstName;
        @Column(name="LAST_NAME", nullable = false, length = 25)
        @Size(min = 3, max = 20)
        private String lastName;
        @Max(1000000)
        private Double salary;
        @Column(name="COMMISSION_PCT")
        @DecimalMax("1.00")
        private Double commissionPct;
    ...
    
  4. Create a utility class that will handle extraction of validation messages like the one below:
    package com.blogspot.soadev.util;
    
    import java.util.Set;
    import javax.validation.ConstraintViolation;
    import javax.validation.Validation;
    import javax.validation.ValidatorFactory;
    
    public class MyUtils {
        public static javax.validation.Validator getValidator() {
    
            ValidatorFactory factory =
                Validation.buildDefaultValidatorFactory();
            return factory.getValidator();
        }
    
        public static String extractConstraintViolationMessages(Set violations) {
            StringBuilder builder = new StringBuilder();
            for (Object obj : violations) {
                if (obj instanceof ConstraintViolation) {
                    builder.append(((ConstraintViolation)obj).getMessage());
                    builder.append("\n");
                }
            }
            return builder.toString();
        }
        //This method returns a set of validation constraint errors
    
        public static Set validateEntity(Object object) {
            javax.validation.Validator validator = getValidator();
            return validator.validate(object);
        }
        // returns null if no violation
    
        public static String getEntityValidationErrorMsg(Object object) {
            Set violations = validateEntity(object);
            if (violations.size() > 0) {
                return extractConstraintViolationMessages(violations);
            }
            return null;
        }
    }
    
  5. In the ViewController project, create the following class that implements javax.faces.validator.Validator.
    package com.blogspot.soadev.view.validator;
    import com.blogspot.soadev.util.MyUtils;
    import com.blogspot.soadev.view.util.JSFUtils;
    import java.util.Set;
    import javax.el.ELException;
    import javax.faces.FacesException;
    import javax.faces.application.FacesMessage;
    import javax.faces.component.EditableValueHolder;
    import javax.faces.component.UIComponent;
    import javax.faces.context.FacesContext;
    import javax.faces.validator.Validator;
    import javax.faces.validator.ValidatorException;
    import javax.validation.ConstraintViolation;
    import javax.validation.Validation;
    
    public class BeanValidator implements Validator {
        private static final String DOT_REGEX = "[.]";
        private static final String BINDINGS = "bindings";
        private static final String INPUT_VALUE = "inputValue";
        private static final String NAME = "name";
        private static final String EMPTY_STRING = "";
        private static final String END_CURLY_BRACKET = "}";
        private static final String DATA_PROVIDER = "currentRow.dataProvider";
        private static final String DOT = ".";
    
        public BeanValidator() {
            super();
        }
    
        public void validate(FacesContext facesContext, UIComponent component,
                             Object object) throws ValidatorException {
            if (component instanceof EditableValueHolder) {
                // Validate input component
                EditableValueHolder input = (EditableValueHolder)component;
                
                try {
                    String expression =
                        component.getValueExpression("value").getExpressionString();
                    if (null != expression) {
                        Set<ConstraintViolation<Object>> constraintViolations =
                            validateValue(object, expression);
    
                        if (constraintViolations.size() > 0) {
                            input.setValid(false);
                            // send all validation messages.
                            String msg =
                                MyUtils.extractConstraintViolationMessages(constraintViolations);
                            FacesMessage fm =
                                new FacesMessage(FacesMessage.SEVERITY_ERROR, msg,
                                                 null);
                            throw new ValidatorException(fm);
                        }
                    }
                } catch (ELException e) {
                    throw new FacesException(e);
                }
            }
    
        }
    
        private Set<ConstraintViolation<Object>> validateValue(Object object,
                                                                                 String expression) {
            Object targetObject = getTargetObject(expression);
            String property = getProperty(expression);
            javax.validation.Validator validator =
                Validation.buildDefaultValidatorFactory().getValidator();
            Set constraintViolations =
                validator.validateValue(targetObject.getClass(), property, object);
            return constraintViolations;
        }
        public String getProperty(String expression){
            if (expression.contains(BINDINGS) && expression.contains(INPUT_VALUE)){
               expression =
                    expression.replaceFirst(INPUT_VALUE, NAME);       
                return JSFUtils.resolveExpression(expression).toString();
            }
            String [] tokens = expression.split(DOT_REGEX);
            String result  = tokens[tokens.length-1];
            result = result.replaceAll(END_CURLY_BRACKET, EMPTY_STRING);
            return result;
        }
        
        public Object getTargetObject(String expression){
            if (expression.contains(BINDINGS) && expression.contains(INPUT_VALUE)){
                expression =
                    expression.replaceFirst(INPUT_VALUE, DATA_PROVIDER);
                return JSFUtils.resolveExpression(expression);
            }
                String [] tokens = expression.split(DOT_REGEX);
                StringBuilder builder = new StringBuilder(tokens[0]);
                for (int i = 1; i < tokens.length - 1; i++){
                    builder.append(DOT);
                    builder.append(tokens[i]);
                }
                builder.append(END_CURLY_BRACKET);
                return JSFUtils.resolveExpression(builder.toString());        
        }
    }
    
  6. Add the JSR303BeanValidator class above to the list of validators in faces-config.xml
  7. Apply the JSR303BeanValidator to your input components in ADF Faces.
    <af:inputText value="#{bindings.firstName.inputValue}"
                                        label="#{bindings.firstName.hints.label}"
                                        required="#{bindings.firstName.hints.mandatory}"
                                        columns="#{bindings.firstName.hints.displayWidth}"
                                        maximumLength="#{bindings.firstName.hints.precision}"
                                        shortDesc="#{bindings.firstName.hints.tooltip}"
                                        id="it2" autoSubmit="true">
                            <f:validator validatorId="JSR303BeanValidator"/>
                          </af:inputText>
                          <af:inputText value="#{bindings.lastName.inputValue}"
                                        label="#{bindings.lastName.hints.label}"
                                        required="#{bindings.lastName.hints.mandatory}"
                                        columns="#{bindings.lastName.hints.displayWidth}"
                                        maximumLength="#{bindings.lastName.hints.precision}"
                                        shortDesc="#{bindings.lastName.hints.tooltip}"
                                        id="it4" autoSubmit="true">
                            <f:validator validatorId="JSR303BeanValidator"/>
                          </af:inputText>
                          <af:inputText value="#{bindings.salary.inputValue}"
                                        label="#{bindings.salary.hints.label}"
                                        required="#{bindings.salary.hints.mandatory}"
                                        columns="#{bindings.salary.hints.displayWidth}"
                                        maximumLength="#{bindings.salary.hints.precision}"
                                        shortDesc="#{bindings.salary.hints.tooltip}"
                                        id="it3" autoSubmit="true">
                            <f:validator validatorId="JSR303BeanValidator"/>
                            <af:convertNumber groupingUsed="false"
                                              pattern="#{bindings.salary.format}"/>
                          </af:inputText>
    
  8. Run your project and try to input invalid values. To display the validation error messages after you tab out of a component, ensure to set the "AutoSubmit" attribute of the component to true.


To know more about JSR 303, click here and here.

Related Posts

No comments:

Post a Comment