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 }