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 }