package org.jboss.seam.core; import static org.jboss.seam.annotations.Install.BUILT_IN; import java.beans.FeatureDescriptor; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.el.ELContext; import javax.el.ELException; import javax.el.ELResolver; import javax.el.PropertyNotFoundException; import javax.el.PropertyNotWritableException; import javax.el.ValueExpression; import org.hibernate.validator.ClassValidator; import org.hibernate.validator.InvalidValue; import org.jboss.seam.Component; import org.jboss.seam.Instance; import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.Install; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.intercept.BypassInterceptors; import org.jboss.seam.contexts.Contexts; import org.jboss.seam.el.EL; /** * Caches instances of Hibernate Validator ClassValidator * * @author Gavin King * */ @Name("org.jboss.seam.core.validators") @BypassInterceptors @Scope(ScopeType.APPLICATION) @Install(precedence = BUILT_IN, classDependencies = "org.hibernate.validator.ClassValidator") public class Validators { // TODO: should use weak references here... private Map<Key, ClassValidator> classValidators = new ConcurrentHashMap<Key, ClassValidator>(); class Key { private Class validatableClass; private java.util.Locale locale; public Key(Class validatableClass, java.util.Locale locale) { this.validatableClass = validatableClass; this.locale = locale; } @Override public boolean equals(Object other) { if (other == null || !(other instanceof Key)) { return false; } Key key = (Key) other; return key.validatableClass.equals(validatableClass) && key.locale.equals(locale); } @Override public int hashCode() { return validatableClass.hashCode() + locale.hashCode(); } } /** * Get the cached ClassValidator instance. If the argument is an instance of * a session bean Seam component instance, the returned validator will be * aware of constraints defined on the bean class. Therefore this method is * preferred to getValidator(Class) if the argument might be a session bean. * * @param model the object to be validated */ public <T> ClassValidator<T> getValidator(T model) { Class modelClass = model instanceof Instance ? ((Instance) model).getComponent().getBeanClass() : model.getClass(); return getValidator((Class<T>) modelClass); } /** * Get the cached ClassValidator instance. * * @param modelClass the class to be validated */ @SuppressWarnings("unchecked") public <T> ClassValidator<T> getValidator(Class<T> modelClass) { java.util.ResourceBundle bundle = SeamResourceBundle.getBundle(); Locale none = bundle == null ? new Locale("NONE") : bundle.getLocale(); Key key = new Key(modelClass, none); ClassValidator result = classValidators.get(key); if (result == null) { result = createValidator(modelClass); classValidators.put(key, result); } return result; } /** * Create a new ClassValidator for the given class, using the current Seam * ResourceBundle. * * @param modelClass the class to be validated */ @SuppressWarnings("unchecked") protected <T> ClassValidator<T> createValidator(Class<T> modelClass) { java.util.ResourceBundle bundle = SeamResourceBundle.getBundle(); return bundle == null ? new ClassValidator(modelClass) : new ClassValidator(modelClass, bundle); } /** * Validate that the given value can be assigned to the property given by the * value expression. * * @param valueExpression a value expression, referring to a property * @param elContext the ELContext in which to evaluate the expression * @param value a value to be assigned to the property * @return a set of potential InvalidValues, from Hibernate Validator */ public InvalidValue[] validate(ValueExpression valueExpression, ELContext elContext, Object value) { ValidatingResolver validatingResolver = new ValidatingResolver(elContext.getELResolver()); ELContext decoratedContext = EL.createELContext(elContext, validatingResolver); valueExpression.setValue(decoratedContext, value); return validatingResolver.getInvalidValues(); } class ValidatingResolver extends ELResolver { private ELResolver delegate; private InvalidValue[] invalidValues; public ValidatingResolver(ELResolver delegate) { this.delegate = delegate; } public InvalidValue[] getInvalidValues() { return invalidValues; } @Override public Class<?> getCommonPropertyType(ELContext context, Object value) { return delegate.getCommonPropertyType(context, value); } @Override public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object value) { return delegate.getFeatureDescriptors(context, value); } @Override public Class<?> getType(ELContext context, Object x, Object y) throws NullPointerException, PropertyNotFoundException, ELException { return delegate.getType(context, x, y); } @Override public Object getValue(ELContext context, Object base, Object property) throws NullPointerException, PropertyNotFoundException, ELException { return delegate.getValue(context, base, property); } @Override public boolean isReadOnly(ELContext context, Object base, Object property) throws NullPointerException, PropertyNotFoundException, ELException { return delegate.isReadOnly(context, base, property); } @Override public void setValue(ELContext context, Object base, Object property, Object value) throws NullPointerException, PropertyNotFoundException, PropertyNotWritableException, ELException { if (base != null && property != null) { context.setPropertyResolved(true); invalidValues = getValidator(base).getPotentialInvalidValues(property.toString(), value); } } } public static Validators instance() { if (!Contexts.isApplicationContextActive()) { throw new IllegalStateException("No active application scope"); } return (Validators) Component.getInstance(Validators.class, ScopeType.APPLICATION); } }