001 package org.springframework.binding.validation.support;
002
003 import java.beans.IntrospectionException;
004 import java.beans.Introspector;
005 import java.beans.PropertyDescriptor;
006 import java.util.ArrayList;
007 import java.util.HashSet;
008 import java.util.List;
009 import java.util.Set;
010
011 import org.hibernate.validator.AssertFalse;
012 import org.hibernate.validator.AssertTrue;
013 import org.hibernate.validator.ClassValidator;
014 import org.hibernate.validator.InvalidValue;
015 import org.springframework.binding.form.ValidatingFormModel;
016 import org.springframework.binding.validation.RichValidator;
017 import org.springframework.binding.validation.ValidationMessage;
018 import org.springframework.binding.validation.ValidationResults;
019 import org.springframework.binding.validation.Validator;
020 import org.springframework.richclient.core.Severity;
021 import org.springframework.richclient.util.Assert;
022 import org.springframework.rules.reporting.ObjectNameResolver;
023
024 /**
025 * <p>
026 * Validator which uses the {@link ClassValidator} of Hibernate to discover
027 * {@link InvalidValue}. These are then translated to {@link ValidationMessage}s
028 * and added to the {@link ValidationResults} as usual.
029 * </p>
030 *
031 * <p>
032 * Usage in a {@link ValidatingFormModel} where <code>SomeClass</code> has
033 * annotations for Hibernate Validator:
034 * </p>
035 *
036 * <pre>
037 * formModel.setValidator(new HibernateRulesValidator(formModel, SomeClass.class));
038 * </pre>
039 *
040 * <p>
041 * This can be used in combination with other {@link Validator}s as well by
042 * creating a {@link CompositeRichValidator}:
043 * </p>
044 *
045 * <pre>
046 * HibernateRulesValidator hibernateRulesValidator = new HibernateRulesValidator(getFormModel(), SomeClass.class);
047 * hibernateRulesValidator.addIgnoredHibernateProperty("ignoredProperty");
048 * RulesValidator rulesValidator = new RulesValidator(getFormModel(), myRulesSource);
049 * getFormModel().setValidator(new CompositeRichValidator(rulesValidator, hibernateRulesValidator));
050 * </pre>
051 *
052 * <p>
053 * Note that we're adding one property to the {@link HibernateRulesValidator}
054 * that will be ignored. This property will only be checked at your back-end by
055 * Hibernate. The {@link RulesValidator} adds additional rules that are only
056 * used at the front-end or/and may contain the equivalent of the
057 * {@link AssertTrue} or {@link AssertFalse} methods on <code>SomeClass</code>.
058 * </p>
059 *
060 * @author Andy DuPue
061 * @author Lieven Doclo
062 * @author Jan Hoskens
063 */
064 @SuppressWarnings("unchecked")
065 public class HibernateRulesValidator implements RichValidator, ObjectNameResolver {
066
067 private final ClassValidator hibernateValidator;
068
069 private ValidatingFormModel formModel;
070
071 private DefaultValidationResults results = new DefaultValidationResults();
072
073 private Set<String> ignoredHibernateProperties;
074
075 /**
076 * Creates a new HibernateRulesValidator without ignoring any properties.
077 *
078 * @param formModel The {@link ValidatingFormModel} on which validation
079 * needs to occur
080 * @param clazz The class of the object this validator needs to check
081 */
082 public HibernateRulesValidator(ValidatingFormModel formModel, Class clazz) {
083 this(formModel, clazz, new HashSet<String>());
084 }
085
086 /**
087 * Creates a new HibernateRulesValidator with additionally a set of
088 * properties that should not be validated.
089 *
090 * @param formModel The {@link ValidatingFormModel} on which validation
091 * needs to occur
092 * @param clazz The class of the object this validator needs to check
093 * @param ignoredHibernateProperties properties that should not be checked
094 * though are
095 */
096 public HibernateRulesValidator(ValidatingFormModel formModel, Class clazz, Set<String> ignoredHibernateProperties) {
097 this.formModel = formModel;
098 this.hibernateValidator = new ClassValidator(clazz, new HibernateRulesMessageInterpolator());
099 this.ignoredHibernateProperties = ignoredHibernateProperties;
100 }
101
102 /**
103 * {@inheritDoc}
104 */
105 public ValidationResults validate(Object object) {
106 return validate(object, null);
107 }
108
109 /**
110 * {@inheritDoc}
111 */
112 public ValidationResults validate(Object object, String propertyName) {
113 // hibernate will return InvalidValues per propertyName, remove any
114 // previous validationMessages.
115 if (propertyName == null) {
116 results.clearMessages();
117 }
118 else {
119 results.clearMessages(propertyName);
120 }
121
122 addInvalidValues(doHibernateValidate(object, propertyName));
123 return results;
124 }
125
126 /**
127 * Add all {@link InvalidValue}s to the {@link ValidationResults}.
128 */
129 protected void addInvalidValues(InvalidValue[] invalidValues) {
130 if (invalidValues != null) {
131 for (InvalidValue invalidValue : invalidValues) {
132 results.addMessage(translateMessage(invalidValue));
133 }
134 }
135 }
136
137 /**
138 * Translate a single {@link InvalidValue} to a {@link ValidationMessage}.
139 */
140 protected ValidationMessage translateMessage(InvalidValue invalidValue) {
141 return new DefaultValidationMessage(invalidValue.getPropertyName(), Severity.ERROR,
142 resolveObjectName(invalidValue.getPropertyName()) + " " + invalidValue.getMessage());
143 }
144
145 /**
146 * Validates the object through Hibernate Validator
147 *
148 * @param object The object that needs to be validated
149 * @param property The properties that needs to be validated
150 * @return An array of {@link InvalidValue}, containing all validation
151 * errors
152 */
153 protected InvalidValue[] doHibernateValidate(final Object object, final String property) {
154 if (property == null) {
155 final List<InvalidValue> ret = new ArrayList<InvalidValue>();
156 PropertyDescriptor[] propertyDescriptors;
157 try {
158 propertyDescriptors = Introspector.getBeanInfo(object.getClass()).getPropertyDescriptors();
159 }
160 catch (IntrospectionException e) {
161 throw new IllegalStateException("Could not retrieve property information");
162 }
163 for (final PropertyDescriptor prop : propertyDescriptors) {
164 String propertyName = prop.getName();
165 if (formModel.hasValueModel(propertyName) && !ignoredHibernateProperties.contains(propertyName)) {
166 final InvalidValue[] result = hibernateValidator.getPotentialInvalidValues(propertyName, formModel
167 .getValueModel(propertyName).getValue());
168 if (result != null) {
169 for (final InvalidValue r : result) {
170 ret.add(r);
171 }
172 }
173 }
174 }
175 return ret.toArray(new InvalidValue[ret.size()]);
176 }
177 else if (!ignoredHibernateProperties.contains(property) && formModel.hasValueModel(property)) {
178 return hibernateValidator.getPotentialInvalidValues(property, formModel.getValueModel(property).getValue());
179 }
180 else {
181 return null;
182 }
183 }
184
185 /**
186 * Clear the current validationMessages and the errors.
187 *
188 * @see #validate(Object, String)
189 */
190 public void clearMessages() {
191 this.results.clearMessages();
192 }
193
194 /**
195 * Add a property for the Hibernate validator to ignore.
196 *
197 * @param propertyName Name of the property to ignore. Cannot be null.
198 */
199 public void addIgnoredHibernateProperty(String propertyName) {
200 Assert.notNull(propertyName);
201 ignoredHibernateProperties.add(propertyName);
202 }
203
204 /**
205 * Remove a property for the Hibernate validator to ignore.
206 *
207 * @param propertyName Name of the property to be removed. Cannot be null.
208 */
209 public void removeIgnoredHibernateProperty(String propertyName) {
210 Assert.notNull(propertyName);
211 ignoredHibernateProperties.remove(propertyName);
212 }
213
214 /**
215 * {@inheritDoc}
216 */
217 public String resolveObjectName(String objectName) {
218 return formModel.getFieldFace(objectName).getDisplayName();
219 }
220 }