001 /*
002 * Databinder: a simple bridge from Wicket to Hibernate
003 * Copyright (C) 2008 Nathan Hamblen nathan@technically.us
004
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either
008 * version 2.1 of the License, or (at your option) any later version.
009 *
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013 * Lesser General Public License for more details.
014 *
015 * You should have received a copy of the GNU Lesser General Public
016 * License along with this library; if not, write to the Free Software
017 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
018 */
019 package net.databinder.valid.hib;
020
021 import org.apache.wicket.Component;
022 import org.apache.wicket.markup.html.form.FormComponent;
023 import org.apache.wicket.model.IModel;
024 import org.apache.wicket.model.IWrapModel;
025 import org.apache.wicket.model.PropertyModel;
026 import org.apache.wicket.validation.IValidatable;
027 import org.apache.wicket.validation.IValidatorAddListener;
028 import org.apache.wicket.validation.ValidationError;
029 import org.apache.wicket.validation.validator.AbstractValidator;
030 import org.hibernate.Hibernate;
031 import org.hibernate.validator.ClassValidator;
032 import org.hibernate.validator.InvalidValue;
033
034 /**
035 * Checks a base model and property name against Hibernate Validator.
036 * @author Nathan Hamblen
037 */
038 public class DatabinderValidator extends AbstractValidator implements IValidatorAddListener {
039 /** base model, may be null until first call to onValidate. */
040 private IModel base;
041 /** property of base to validate, may be null until first call to onValidate. */
042 private String property;
043 /** component added to */
044 private Component component;
045
046 /**
047 * Validator for a property of an entity.
048 * @param base entity to validate
049 * @param property property of base to validate
050 */
051 public DatabinderValidator(IModel base, String property) {
052 this.base = base;
053 this.property = property;
054 }
055
056 /**
057 * Construct instance that attempts to determine the base object and property
058 * to validate form the component it is added to. This is only possible for
059 * components that depend on a parent CompoundPropertyModel or their own
060 * PropertyModels. The attempt is not made until the first validation check
061 * in {@link #onValidate(IValidatable)} (to allow the full component
062 * hierarchy to be constructed). Do not use an instance for more than
063 * one component.
064 */
065 public DatabinderValidator() { }
066
067 /**
068 * Checks the component against Hibernate Validator. If the base model
069 * and property were not supplied in the constructor, they will be determined
070 * from the component this validator was added to.
071 */
072 @SuppressWarnings("unchecked")
073 @Override
074 protected void onValidate(IValidatable comp) {
075 if (base == null || property == null) {
076 ModelProp mp = getModelProp(component);
077 base = mp.model;
078 property = mp.prop;
079 }
080 Object o = base.getObject();
081 Class c = Hibernate.getClass(o);
082 ClassValidator validator = new ClassValidator(c);
083 for (InvalidValue iv : validator.getPotentialInvalidValues(property, comp.getValue()))
084 comp.error(new ValidationError().setMessage(iv.getPropertyName() + " " + iv.getMessage()));
085 }
086
087 /** Retains component for possible use in onValidate. */
088 public void onAdded(Component component) {
089 this.component = component;
090 }
091
092 /** @return always true */
093 @Override
094 public boolean validateOnNullValue() {
095 return true;
096 }
097
098 private static class ModelProp { IModel model; String prop; }
099
100 /** @return base object and property derived from this component */
101 private static ModelProp getModelProp(Component formComponent) {
102 IModel model = formComponent.getModel();
103 ModelProp mp = new ModelProp();
104 if (model instanceof PropertyModel) {
105 PropertyModel propModel = (PropertyModel) model;
106 mp.model = propModel.getChainedModel();
107 mp.prop = propModel.getPropertyExpression();
108 } else if (model instanceof IWrapModel) {
109 mp.model = ((IWrapModel)model).getWrappedModel();
110 mp.prop = formComponent.getId();
111 } else throw new UnrecognizedModelException(formComponent, model);
112 return mp;
113 }
114
115 /**
116 * Add immediately to a form component. Note that the component's model
117 * object must be available for inspection at this point or an exception will
118 * be thrown. (For a CompoundPropertyModel, this means the hierarchy must
119 * be established.) This is only possible for components that depend on a
120 * parent CompoundPropertyModel or their own PropertyModels.
121 * @param formComponent component to add validator to
122 * @throws UnrecognizedModelException if no usable model is present
123 */
124 public static void addTo(FormComponent formComponent) {
125 ModelProp mp = getModelProp(formComponent);
126 formComponent.add(new DatabinderValidator(mp.model, mp.prop));
127 }
128
129 public static class UnrecognizedModelException extends RuntimeException {
130 public UnrecognizedModelException(Component formComponent, IModel model) {
131 super("DatabinderValidator doesn't recognize the model "
132 + model + " of component " + formComponent.toString());
133 }
134 }
135 }