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    }