Showing posts with label JSR 303. Show all posts
Showing posts with label JSR 303. Show all posts

Saturday, February 6, 2010

JSR 303 Bean Validation: Error "java.lang.NoSuchMethodError: javax.persistence.Persistence.getPersistenceUtil()Ljavax/persistence/PersistenceUtil;"

When we adopted the JSR 303 Bean Validation into our ADF/EJB project, we encountered the above error, so I posted on hibernate forums and was advised to implement my own custom class that implements TraversableResolver (Check the discussion here).
Here I will post a simple working implementation that works.
TODO:
1) Create a class that implements TraversableResolver like below:
package blogspot.soadev.validator;

import java.lang.annotation.ElementType;
import javax.validation.Path;
import javax.validation.TraversableResolver;

public class CustomTraversableResolver implements TraversableResolver {
   
    public boolean isReachable(Object traversableObject, Path.Node traversableProperty, 
                               Class rootBeanType, Path pathToTraversableObject, 
                               ElementType elementType) {
        return true;
    }

    public boolean isCascadable(Object object, Path.Node node, Class c,
                                Path path, ElementType elementType) {
        return true;
    }
}

2) Modify your code that gets the validator instance to as follows:
import blogspot.soadev.validator.CustomTraversableResolver;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
...

ValidatorFactory factory = Validation.byDefaultProvider().configure()
                      .traversableResolver( new CustomTraversableResolver() )
                      .buildValidatorFactory();
Validator validator = factory.getValidator();

Kudos to Java Powers who first attended and tested this!

Related Posts

Cheers!

Pino

Friday, January 29, 2010

JSR 303 Bean Validation: @ValidateDateRange - A reusable constraint annotation to validate date ranges

In a previous blog-post, I have described my experience on JSR 303 Bean Validation, and in the process, this @ValidateDateRange constraint annotation came upon into my mind. I thought that it would be useful so it inspired me to create its implementation and eventually made this blog-post.

The @ValidateDateRange is a class-level constraint annotation that will validate a date range represented by two java.util.Date properties of an object. This can be reused in any class.
@ValidateDateRange(start="startDate", end="endDate")
public class MyEntity {
   private Date startDate;
   private Date endDate;
   ...
}

The annotation definition are as follows:
package blogspot.soadev.validator;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import java.util.Date;

import javax.validation.Constraint;
import javax.validation.ConstraintPayload;

@Target( { TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = {DateRangeValidator.class} )
@Documented

public @interface ValidateDateRange {
    String message() default "{end} should be later than {start}";
    String start();
    String end();
    Class[] groups() default {};
    Class[] payload() default {};       
}
And the implementing DateRangeValidator are as follows:
package blogspot.soadev.validator;

import java.lang.reflect.Field;
import java.util.Date;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class DateRangeValidator implements ConstraintValidator{
    private String start;
    private String end;
    
    public void initialize(ValidateDateRange validateDateRange) {
        start = validateDateRange.start();
        end =  validateDateRange.end();
    }

    public boolean isValid(Object object,
                           ConstraintValidatorContext constraintValidatorContext) {
        try {
            Class clazz = object.getClass();
            Date startDate = null;
            Method startGetter = clazz.getMethod(getAccessorMethodName(start), new Class[0]);
            Object startGetterResult = startGetter.invoke(object, null);
            if (startGetterResult != null && startGetterResult instanceof Date){
                startDate = (Date) startGetterResult;
            }else{
                return false;
            }
            Date endDate = null;
            Method endGetter = clazz.getMethod(getAccessorMethodName(end), new Class[0]);
            Object endGetterResult = endGetter.invoke(object, null);
            if (endGetterResult == null){
                return true;
            }
            if (endGetterResult instanceof Date){
                endDate = (Date) endGetterResult;
            }
            return (startDate.before(endDate));           
        } catch (Throwable e) {
            System.err.println(e);
        }

        return false;
    }
    private String getAccessorMethodName(String property){
        StringBuilder builder = new StringBuilder("get");
        builder.append(Character.toUpperCase(property.charAt(0))); 
        builder.append(property.substring(1));
        return builder.toString();
    }
}

Related Posts


Cheers!

Tuesday, January 26, 2010

JSR 303 Bean Validation: @AssertMethodAsTrue - A reusable constraint annotation that spans multiple properties.

I have made my own hands-on on this Bean Validation based on the [hibernate-validator-4.0.0.Beta2] and the experience was great. One thing that I have observed though is that there is no built-in annotation to check for a constraint spanning multiple properties, or to check if a certain property is valid based on other properties. One obvious example to this is validating date ranges where you would usually have two properties like startDate and endDate where it doesn't make sense to have a startDate property that is later than the endDate. You could say - why not create a new class-level constraint and
apply it like below:
@ValidateDateRange(start="startDate", end="endDate")
public class MyEntity {
   private Date startDate;
   private Date endDate;
   ...
}
Well, this is good because it is likely that I can reuse this constraint in other entities needing validation on date ranges (I just realized in writing this that it also interesting to post a blog on an implementation of the @ValidateDateRange annotation above). The following are some of the annotations which are reusable to other entities.
  • @NotNull
  • @NotEmpty
  • @Size
  • @Min
  • @Max
  • @DecimalMin
  • @DecimalMax
  • @Email
  • @Pattern
But how about other validation spanning multiple properties that is not likely to be reused in other entities (just like the sample class-level constraint @ValidPassengerCount in the Bean Validator documentation)? I believe, that to create two additional classes to support such simple validation requirement of an specific entity is too much work especially that it is most likely that in addition to checking if the number of passengers is valid , you will also have two or more other validation requirements on the same entity. For example, if I have three business logic for an entity to be validated, it means creating more or less six additional classes to support the three constraint validation. The sad thing is, I cannot reuse such custom constraint validations to other entities.

The Bean Validation reference implementation has @AssertTrue annotation which can be applied to a property or getter method, but an exception is raised if you will try to access other properties value inside a getter method.

So how then could we easily validate a business logic constraint that spans multiple properties without too much work? Our answer is the reusable @AssertMethodAsTrue class-level constraint annotation that accepts a method name as a parameter. The method name that you set as parameter should return a boolean value. An example would be:
@AssertMethodAsTrue(value="isPassengerCountValid", message="Invalid passenger count!")
public class Car {
    @Min(2)
    private int seatCount;
    @NotNull
    List passengers;

    public boolean isPassengerCountValid(){
        if(this.seatCount >= passengers.size()){
        return true;
        }
        return false;
    }
}
The definition of the @AssertMethodAsTrue annotation are as follows:
package blogspot.soadev.validator;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.ConstraintPayload;

@Target( { TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = {AssertMethodAsTrueValidator.class} )
@Documented

public @interface AssertMethodAsTrue {
    String message() default "{value} returned false";
    String value() default "isValid";
    Class[] groups() default {};
    Class[] payload() default {};       
}

And the corresponding implementing validator class that use reflection:
package blogspot.soadev.validator;

import java.lang.reflect.Method;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class AssertMethodAsTrueValidator implements ConstraintValidator{    
    private String methodName;
    
    public void initialize(AssertMethodAsTrue assertMethodAsTrue) {
        methodName =  assertMethodAsTrue.value();
    }
    public boolean isValid(Object object,
                           ConstraintValidatorContext constraintValidatorContext) {
        
        try {
            Class clazz = object.getClass();
            Method validate = clazz.getMethod(methodName, new Class[0]);
            return (Boolean) validate.invoke(object);
        } catch (Throwable e) {
            System.err.println(e);
        }
        return false;
    }
}
Since it is also most likely that you will have more than one method to validate, then an @AssertMethodAsTrueList would also be needed. The code are as follows:
package blogspot.soadev.validator;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Target(value={TYPE, ANNOTATION_TYPE})
@Retention(value=RUNTIME)
@Documented

public @interface AssertMethodAsTrueList {
    AssertMethodAsTrue[] value() default {};
}
With the @AssertMethodAsTrueList above you can have multiple @AssertMethodAsTrue on a single entity:
@AssertMethodAsTrueList({
    @AssertMethodAsTrue(value="isPassengerCountValid", message="Invalid passenger count!"),
    @AssertMethodAsTrue(value="isTirePressureIdeal")
})
public class Car {
    ...
    public void isPassengerCountValid(){...}
    public void isTirePressureIdeal(){...}
}

Related Posts

Cheers to the JSR 303 Bean Validation!

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