001 /* 002 * Copyright 2002-2005 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 005 * use this file except in compliance with the License. You may obtain a copy of 006 * the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 013 * License for the specific language governing permissions and limitations under 014 * the License. 015 */ 016 package org.springframework.binding.validation.support; 017 018 import java.beans.PropertyDescriptor; 019 import java.beans.PropertyEditor; 020 import java.util.ArrayList; 021 import java.util.Collection; 022 import java.util.HashMap; 023 import java.util.HashSet; 024 import java.util.Iterator; 025 import java.util.List; 026 import java.util.Map; 027 import java.util.Set; 028 029 import org.apache.commons.collections.functors.AndPredicate; 030 import org.apache.commons.collections.functors.NotPredicate; 031 import org.apache.commons.collections.functors.OrPredicate; 032 import org.springframework.beans.BeanWrapper; 033 import org.springframework.beans.BeansException; 034 import org.springframework.beans.PropertyValue; 035 import org.springframework.beans.PropertyValues; 036 import org.springframework.beans.TypeMismatchException; 037 import org.springframework.binding.form.FormModel; 038 import org.springframework.binding.validation.RichValidator; 039 import org.springframework.binding.validation.ValidationMessage; 040 import org.springframework.binding.validation.ValidationResults; 041 import org.springframework.context.support.MessageSourceAccessor; 042 import org.springframework.core.MethodParameter; 043 import org.springframework.core.ReflectiveVisitorHelper; 044 import org.springframework.richclient.application.ApplicationServicesLocator; 045 import org.springframework.richclient.core.Severity; 046 import org.springframework.util.Assert; 047 import org.springframework.util.CachingMapDecorator; 048 import org.springframework.util.StringUtils; 049 import org.springmodules.validation.valang.ValangValidator; 050 import org.springmodules.validation.valang.functions.AbstractFunction; 051 import org.springmodules.validation.valang.functions.AbstractMathFunction; 052 import org.springmodules.validation.valang.functions.BeanPropertyFunction; 053 import org.springmodules.validation.valang.functions.Function; 054 import org.springmodules.validation.valang.functions.MapEntryFunction; 055 import org.springmodules.validation.valang.predicates.BasicValidationRule; 056 import org.springmodules.validation.valang.predicates.GenericTestPredicate; 057 058 /** 059 * Implementation of <code>RichValidator</code> that delegates to a 060 * <code>ValangValidator</code> for validation. 061 * 062 * @author Oliver Hutchison 063 * @see ValangValidator 064 */ 065 public class ValangRichValidator implements RichValidator { 066 067 // map to lists of rules effecting a given property 068 private final Map propertyRules = new CachingMapDecorator(false) { 069 protected Object create(Object key) { 070 return new ArrayList(); 071 } 072 }; 073 074 private final DefaultValidationResults results = new DefaultValidationResults(); 075 076 private final Map validationErrors = new HashMap(); 077 078 private final FormModel formModel; 079 080 private final Collection allRules; 081 082 private MessageSourceAccessor messageSourceAccessor; 083 084 public ValangRichValidator(FormModel formModel, ValangValidator validator) { 085 this.formModel = formModel; 086 this.allRules = validator.getRules(); 087 initPropertyRules(); 088 } 089 090 private void initPropertyRules() { 091 for (Iterator i = allRules.iterator(); i.hasNext();) { 092 BasicValidationRule rule = (BasicValidationRule)i.next(); 093 Set propertiesUsedByRule = getPropertiesUsedByRule(rule); 094 for (Iterator j = propertiesUsedByRule.iterator(); j.hasNext();) { 095 String propertyName = (String)j.next(); 096 ((List)propertyRules.get(propertyName)).add(rule); 097 } 098 } 099 } 100 101 public MessageSourceAccessor getMessageSourceAccessor() { 102 if (messageSourceAccessor == null) { 103 messageSourceAccessor = (MessageSourceAccessor)ApplicationServicesLocator.services().getService(MessageSourceAccessor.class); 104 } 105 return messageSourceAccessor; 106 } 107 108 public void setMessageSourceAccessor(MessageSourceAccessor messageSourceAccessor) { 109 this.messageSourceAccessor = messageSourceAccessor; 110 } 111 112 private Set getPropertiesUsedByRule(BasicValidationRule rule) { 113 PropertiesUsedByRuleCollector collector = new PropertiesUsedByRuleCollector(rule); 114 return collector.getPropertiesUsedByRule(); 115 } 116 117 public ValidationResults validate(Object object) { 118 return validate(object, null); 119 } 120 121 public ValidationResults validate(Object object, String propertyName) { 122 Collection rulesToCheck = getRulesEffectedByProperty(propertyName); 123 for (Iterator i = rulesToCheck.iterator(); i.hasNext();) { 124 checkRule((BasicValidationRule)i.next()); 125 } 126 return null; 127 } 128 129 protected Collection getRulesEffectedByProperty(String propertyName) { 130 return (propertyName == null) ? allRules : (Collection)propertyRules.get(propertyName); 131 } 132 133 private void checkRule(BasicValidationRule rule) { 134 if (rule.getPredicate().evaluate(getSourceObject())) { 135 ruleSatisfied(rule); 136 } 137 else { 138 ruleViolated(rule); 139 } 140 } 141 142 protected void ruleSatisfied(BasicValidationRule rule) { 143 ValidationMessage message = (ValidationMessage)validationErrors.remove(rule); 144 if (message != null) { 145 results.removeMessage(message); 146 } 147 } 148 149 protected void ruleViolated(BasicValidationRule rule) { 150 ValidationMessage message = getValidationMessage(rule); 151 ValidationMessage oldMessage = (ValidationMessage)validationErrors.get(rule); 152 if (!message.equals(oldMessage)) { 153 results.removeMessage(oldMessage); 154 validationErrors.put(rule, message); 155 results.addMessage(message); 156 } 157 } 158 159 protected ValidationMessage getValidationMessage(BasicValidationRule rule) { 160 String translatedMessage; 161 String field = rule.getField(); 162 String errorMessage = rule.getErrorMessage(); 163 String errorKey = rule.getErrorKey(); 164 if (StringUtils.hasLength(errorKey)) { 165 Collection errorArgs = rule.getErrorArgs(); 166 if (errorArgs != null && !errorArgs.isEmpty()) { 167 Collection arguments = new ArrayList(); 168 for (Iterator iter = errorArgs.iterator(); iter.hasNext();) { 169 arguments.add(((Function)iter.next()).getResult(getSourceObject())); 170 } 171 translatedMessage = getMessageSourceAccessor().getMessage(errorKey, arguments.toArray(), errorMessage); 172 } 173 else { 174 translatedMessage = getMessageSourceAccessor().getMessage(errorKey, errorMessage); 175 } 176 } 177 else { 178 translatedMessage = getMessageSourceAccessor().getMessage(field, errorMessage); 179 } 180 return new DefaultValidationMessage(field, Severity.ERROR, translatedMessage); 181 } 182 183 protected Object getSourceObject() { 184 return new FormModel2BeanWrapperAdapter(); 185 } 186 187 /** 188 * Visitor that collects the names of all properties that are used by a single Valang 189 * validation rule. 190 */ 191 private static class PropertiesUsedByRuleCollector { 192 193 private static final ReflectiveVisitorHelper reflectiveVisitorHelper = new ReflectiveVisitorHelper(); 194 195 private final BasicValidationRule rule; 196 197 private Set propertiesUsedByRule; 198 199 public PropertiesUsedByRuleCollector(BasicValidationRule rule) { 200 this.rule = rule; 201 } 202 203 public Set getPropertiesUsedByRule() { 204 if (propertiesUsedByRule == null) { 205 propertiesUsedByRule = new HashSet(); 206 doVisit(rule.getPredicate()); 207 Collection errorArgs = rule.getErrorArgs(); 208 if (errorArgs != null && !errorArgs.isEmpty()) { 209 for (Iterator iter = errorArgs.iterator(); iter.hasNext();) { 210 doVisit(iter.next()); 211 } 212 } 213 } 214 return propertiesUsedByRule; 215 } 216 217 protected void doVisit(Object value) { 218 reflectiveVisitorHelper.invokeVisit(this, value); 219 } 220 221 void visit(BeanPropertyFunction f) { 222 propertiesUsedByRule.add(f.getField()); 223 } 224 225 void visitNull() { 226 } 227 228 void visit(Function f) { 229 } 230 231 void visit(AbstractFunction f) { 232 Function[] arguments = f.getArguments(); 233 for (int i = 0; i < arguments.length; i++) { 234 doVisit(arguments[i]); 235 } 236 } 237 238 void visit(NotPredicate p) { 239 Assert.isTrue(p.getPredicates().length == 1); 240 doVisit(p.getPredicates()[0]); 241 } 242 243 void visit(AndPredicate p) { 244 for (int i = 0; i < p.getPredicates().length; i++) { 245 doVisit(p.getPredicates()[i]); 246 } 247 } 248 249 void visit(OrPredicate p) { 250 for (int i = 0; i < p.getPredicates().length; i++) { 251 doVisit(p.getPredicates()[i]); 252 } 253 } 254 255 void visit(GenericTestPredicate p) { 256 doVisit(p.getLeftFunction()); 257 doVisit(p.getRightFunction()); 258 } 259 260 void visit(MapEntryFunction f) { 261 doVisit(f.getMapFunction()); 262 doVisit(f.getKeyFunction()); 263 } 264 265 void visit(AbstractMathFunction f) { 266 doVisit(f.getLeftFunction()); 267 doVisit(f.getRightFunction()); 268 } 269 } 270 271 /** 272 * Adapts the FormModel interface to the BeanWrapper interface 273 * so that the Valang rules evaluator can access the form models 274 * properties. 275 */ 276 private class FormModel2BeanWrapperAdapter implements BeanWrapper { 277 278 public Object getPropertyValue(String propertyName) throws BeansException { 279 return formModel.getValueModel(propertyName).getValue(); 280 } 281 282 public void setWrappedInstance(Object obj) { 283 throw new UnsupportedOperationException("Not implemented"); 284 } 285 286 public Object getWrappedInstance() { 287 throw new UnsupportedOperationException("Not implemented"); 288 } 289 290 public Class getWrappedClass() { 291 throw new UnsupportedOperationException("Not implemented"); 292 } 293 294 public void registerCustomEditor(Class requiredType, PropertyEditor propertyEditor) { 295 throw new UnsupportedOperationException("Not implemented"); 296 } 297 298 public void registerCustomEditor(Class requiredType, String propertyPath, PropertyEditor propertyEditor) { 299 throw new UnsupportedOperationException("Not implemented"); 300 } 301 302 public PropertyEditor findCustomEditor(Class requiredType, String propertyPath) { 303 throw new UnsupportedOperationException("Not implemented"); 304 } 305 306 public PropertyDescriptor[] getPropertyDescriptors() throws BeansException { 307 throw new UnsupportedOperationException("Not implemented"); 308 } 309 310 public PropertyDescriptor getPropertyDescriptor(String propertyName) throws BeansException { 311 throw new UnsupportedOperationException("Not implemented"); 312 } 313 314 public Class getPropertyType(String propertyName) throws BeansException { 315 throw new UnsupportedOperationException("Not implemented"); 316 } 317 318 public boolean isReadableProperty(String propertyName) throws BeansException { 319 throw new UnsupportedOperationException("Not implemented"); 320 } 321 322 public boolean isWritableProperty(String propertyName) throws BeansException { 323 throw new UnsupportedOperationException("Not implemented"); 324 } 325 326 public void setPropertyValue(String propertyName, Object value) throws BeansException { 327 throw new UnsupportedOperationException("Not implemented"); 328 } 329 330 public void setPropertyValue(PropertyValue pv) throws BeansException { 331 throw new UnsupportedOperationException("Not implemented"); 332 } 333 334 public void setPropertyValues(Map map) throws BeansException { 335 throw new UnsupportedOperationException("Not implemented"); 336 } 337 338 public void setPropertyValues(PropertyValues pvs) throws BeansException { 339 throw new UnsupportedOperationException("Not implemented"); 340 } 341 342 public void setPropertyValues(PropertyValues propertyValues, boolean ignoreUnknown) throws BeansException { 343 throw new UnsupportedOperationException("Not implemented"); 344 } 345 346 public void setPropertyValues(PropertyValues propertyValues, boolean ignoreUnknown, boolean ignoreInvalid) 347 throws BeansException { 348 throw new UnsupportedOperationException("Not implemented"); 349 } 350 351 public void setExtractOldValueForEditor(boolean extractOldValueForEditor){ 352 throw new UnsupportedOperationException("Not implemented"); 353 } 354 355 public boolean isExtractOldValueForEditor() { 356 throw new UnsupportedOperationException("Not implemented"); 357 } 358 359 public Object convertIfNecessary(Object object, Class aClass) throws TypeMismatchException { 360 throw new UnsupportedOperationException("Not implemented"); 361 } 362 363 public Object convertIfNecessary(Object object, Class aClass, MethodParameter methodParameter) 364 throws TypeMismatchException { 365 throw new UnsupportedOperationException("Not implemented"); 366 } 367 } 368 }