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    }