/* * Databinder: a simple bridge from Wicket to Hibernate * Copyright (C) 2008 Nathan Hamblen nathan@technically.us * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package net.databinder.valid.hib; import org.apache.wicket.Component; import org.apache.wicket.markup.html.form.FormComponent; import org.apache.wicket.model.IModel; import org.apache.wicket.model.IWrapModel; import org.apache.wicket.model.PropertyModel; import org.apache.wicket.validation.IValidatable; import org.apache.wicket.validation.IValidatorAddListener; import org.apache.wicket.validation.ValidationError; import org.apache.wicket.validation.validator.AbstractValidator; import org.hibernate.Hibernate; import org.hibernate.validator.ClassValidator; import org.hibernate.validator.InvalidValue; /** * Checks a base model and property name against Hibernate Validator. * @author Nathan Hamblen * @author Rodolfo Hansen * * @param <T> Type parameter for the validator. */ public class DatabinderValidator<T> extends AbstractValidator<T> implements IValidatorAddListener { private static final long serialVersionUID = 1L; /** Hibernate ClassValidator to use. */ private ClassValidator<?> validator; /** base model, may be null until first call to onValidate. */ private IModel<T> base; /** property of base to validate, may be null until first call to onValidate. */ private String property; /** component added to */ private FormComponent<T> component; /** * Validator for a property of an entity. * @param base entity to validate * @param property property of base to validate * @param validator validator to validate with */ public DatabinderValidator(final IModel<T> base, final String property, final ClassValidator<?> validator) { this.base = base; this.property = property; this.validator = validator; } /** * Validator for a property of an entity. * @param base entity to validate * @param property property of base to validate */ public DatabinderValidator(final IModel<T> base, final String property) { this.base = base; this.property = property; } /** * Construct instance that attempts to determine the base object and property * to validate form the component it is added to. This is only possible for * components that depend on a parent CompoundPropertyModel or their own * PropertyModels. The attempt is not made until the first validation check * in {@link #onValidate(IValidatable)} (to allow the full component * hierarchy to be constructed). Do not use an instance for more than * one component. */ public DatabinderValidator() { } /** Gets the <tt>validator</tt>. * @return the validator */ public ClassValidator<?> getValidator() { return validator; } /** Sets the <tt>validator</tt>. * @param validator the validator to set * @return the DatabinderValidator object (builder ideology). */ public DatabinderValidator<?> setValidator(final ClassValidator<T> validator) { this.validator = validator; return this; } /** * Checks the component against Hibernate Validator. If the base model * and property were not supplied in the constructor, they will be determined * from the component this validator was added to. */ @SuppressWarnings("unchecked") @Override protected void onValidate(final IValidatable comp) { if (base == null || property == null) { final ModelProp mp = getModelProp(component); base = mp.model; property = mp.prop; } final Object o = base.getObject(); if (validator == null) { final Class c = Hibernate.getClass(o); validator = new ClassValidator(c); } for (final InvalidValue iv : validator.getPotentialInvalidValues(property, comp.getValue())) { comp.error(new ValidationError().setMessage(iv.getPropertyName() + " " + iv.getMessage())); } } /** Retains component for possible use in onValidate. * @param component component assigned to this validator. */ @SuppressWarnings("unchecked") public void onAdded(final Component component) { this.component = (FormComponent<T>) component; } /** @return always true */ @Override public boolean validateOnNullValue() { return true; } private static class ModelProp<T> { IModel<T> model; String prop; } /** @return base object and property derived from this component */ @SuppressWarnings("unchecked") private static <T> ModelProp<T> getModelProp(final FormComponent<T> formComponent) { final IModel<T> model = formComponent.getModel(); final ModelProp<T> mp = new ModelProp<T>(); if (model instanceof PropertyModel) { final PropertyModel<T> propModel = (PropertyModel<T>) model; mp.model = (IModel<T>) propModel.getChainedModel(); mp.prop = propModel.getPropertyExpression(); } else if (model instanceof IWrapModel) { mp.model = ((IWrapModel)model).getWrappedModel(); mp.prop = formComponent.getId(); } else { throw new UnrecognizedModelException(formComponent, model); } return mp; } /** * Add immediately to a form component. Note that the component's model * object must be available for inspection at this point or an exception will * be thrown. (For a CompoundPropertyModel, this means the hierarchy must * be established.) This is only possible for components that depend on a * parent CompoundPropertyModel or their own PropertyModels. * @param <T> Type Safe implementation for any given FormComponent. * @param formComponent component to add validator to * @throws UnrecognizedModelException if no usable model is present */ public static <T> void addTo(final FormComponent<T> formComponent) { final ModelProp<T> mp = getModelProp(formComponent); formComponent.add(new DatabinderValidator<T>(mp.model, mp.prop)); } /** * Add immediately to a form component. Note that the component's model * object must be available for inspection at this point or an exception will * be thrown. (For a CompoundPropertyModel, this means the hierarchy must * be established.) This is only possible for components that depend on a * parent CompoundPropertyModel or their own PropertyModels. * @param <T> Type Safe implementation for any given FormComponent. * @param formComponent component to add validator to * @param validator ClassValidator for the newly asigned instance * @throws UnrecognizedModelException if no usable model is present */ public static <T> void addTo(final FormComponent<T> formComponent, final ClassValidator<?> validator) { final ModelProp<T> mp = getModelProp(formComponent); formComponent.add(new DatabinderValidator<T>(mp.model, mp.prop, validator)); } public static class UnrecognizedModelException extends RuntimeException { private static final long serialVersionUID = 1L; public UnrecognizedModelException(final Component formComponent, final IModel<?> model) { super("DatabinderValidator doesn't recognize the model " + model + " of component " + formComponent.toString()); } } }