/* BeanValidator.java Purpose: Description: History: 2011/12/22 Created by Dennis Chen Copyright (C) 2011 Potix Corporation. All Rights Reserved. */ package org.zkoss.bind.validator; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; import javax.validation.ConstraintViolation; import javax.validation.Validator; import org.zkoss.bind.Property; import org.zkoss.bind.ValidationContext; /** * A <a href="http://jcp.org/en/jsr/detail?id=303"/>JSR 303</a> compatible validator for a property-binding.<p/> * Notice : Before use this validator, you have to configure your environment (depends on the implementation you chosen). * Here is a article <a href="http://books.zkoss.org/wiki/Small_Talks/2011/May/Integrate_ZK_with_JSR_303:_Bean_Validation#How_to_use_Bean_Validation_in_your_ZK_application">Integrate ZK with JSR 303: Bean Validation</a> * talks about how to set up JSR 303 in ZK with Hibernate implementation. * <p/> * It validates a single property of a bean and sets the invalid message by * {@link AbstractValidator#addInvalidMessage(ValidationContext, String)}. <p/> * * To use this class, you have to add <code>@validator('beanValidator')</code> or <code>@validator('org.zkoss.bind.validator.BeanValidator')</code> to the property-binding * <p/> * <b>Example</b><p/> * <pre>{@code * <grid width="600px"> * <textbox id="tb" value="@bind(vm.person.firstName) @validator('beanValidator')"/> * <label value="@load(vmsgs[tb])"/> *</grid> * }</pre> * * <b>Note</b><p/> * It doesn't supports to validate a property of a form which properties are load from a bean, * if you want to validate the form property of the bean, you could use <code>org.zkoss.zkmax.bind.BeanValodator</code> * @author dennis * @since 6.0.0 */ public class BeanValidator extends AbstractValidator { protected Validator getValidator() { return BeanValidations.getValidator(); } /** * Validate the value * @param clz the class of bean * @param property the property of bean * @param value the value to be validated. * @param groups the validation groups (since 8.0.0) * @return the ConstraintViolation set. * @since 6.0.1 */ @SuppressWarnings({ "unchecked", "rawtypes" }) protected Set<ConstraintViolation<?>> validate(Class clz, String property, Object value, Class<?>[] groups) { if (groups != null) return getValidator().validateValue(clz, property, value, groups); return getValidator().validateValue(clz, property, value); } public void validate(ValidationContext ctx) { final Property p = ctx.getProperty(); final Object base = p.getBase(); String property = p.getProperty(); final Object value = p.getValue(); Object[] info = getValidationInfo(ctx, base, property); Class<?> clz = (Class<?>) info[0]; property = (String) info[1]; final Class<?>[] groups = (Class<?>[]) ctx.getValidatorArg("groups"); Set<ConstraintViolation<?>> violations = validate(clz, property, value, groups); handleConstraintViolation(ctx, violations); } /** * Sort the violations, make multiple violation order more predictable. * By default, sort it by the constraint name. * @param viloations */ protected void sort(List<ConstraintViolation<?>> viloations) { Collections.sort(viloations, new Comparator<ConstraintViolation<?>>() { public int compare(ConstraintViolation<?> o1, ConstraintViolation<?> o2) { String s1 = o1.getConstraintDescriptor().getAnnotation().toString(); String s2 = o2.getConstraintDescriptor().getAnnotation().toString(); return s1.compareTo(s2); } }); } /** * Get the bean class of the base object and property to validate. <br/> * By default, it returns the class of base object directly. * @param ctx the validation context * @param base the base object * @param property the property to validate * @return a object array, the first item is the bean class of base object, 2nd item is the tuned property regards to the bean class * @since 6.0.1 */ protected Object[] getValidationInfo(ValidationContext ctx, Object base, String property) { Class<?> clz = base.getClass(); return new Object[] { clz, property }; } /** * Handle hibernate ConstraintViolation. by default, it add to invalid messages. * @param ctx * @param violations * @since 6.0.1 */ protected void handleConstraintViolation(ValidationContext ctx, Set<ConstraintViolation<?>> violations) { final int s = violations.size(); if (s == 1) { addInvalidMessage(ctx, violations.iterator().next().getMessage()); } else if (s > 0) { String[] msgs = new String[violations.size()]; // it is a Set, I tested in hibernate 4 , it doesn't guarantee the // order, so I sort it by annottion.toString List<ConstraintViolation<?>> l = new ArrayList<ConstraintViolation<?>>(violations); sort(l); for (int i = 0; i < msgs.length; i++) { msgs[i] = l.get(i).getMessage(); } addInvalidMessages(ctx, msgs); } } }