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.util.HashMap;
019    import java.util.Iterator;
020    import java.util.Map;
021    
022    import org.apache.commons.logging.Log;
023    import org.apache.commons.logging.LogFactory;
024    import org.springframework.binding.form.FormModel;
025    import org.springframework.binding.form.support.FormModelPropertyAccessStrategy;
026    import org.springframework.binding.validation.RichValidator;
027    import org.springframework.binding.validation.ValidationMessage;
028    import org.springframework.binding.validation.ValidationResults;
029    import org.springframework.richclient.application.ApplicationServicesLocator;
030    import org.springframework.richclient.core.Severity;
031    import org.springframework.rules.PropertyConstraintProvider;
032    import org.springframework.rules.Rules;
033    import org.springframework.rules.RulesSource;
034    import org.springframework.rules.constraint.property.PropertyConstraint;
035    import org.springframework.rules.reporting.BeanValidationResultsCollector;
036    import org.springframework.rules.reporting.MessageTranslator;
037    import org.springframework.rules.reporting.MessageTranslatorFactory;
038    import org.springframework.rules.reporting.ObjectNameResolver;
039    import org.springframework.rules.reporting.PropertyResults;
040    
041    /**
042     * <p>
043     * Implementation of a {@link RichValidator} which will check the formObject
044     * against rules found in a {@link RulesSource}. This {@link RulesSource} can
045     * be specifically supplied, which allows multiple rulesSources, or can be
046     * globally defined in the Application Context. In the latter case the
047     * {@link RulesValidator} will look for the specific {@link RulesSource} type in
048     * the context.
049     * </p>
050     *
051     * <p>
052     * When validating an object, all results are cached. Any following validation
053     * of a specific property will validate that property, update the cached results
054     * accordingly and return <em>all</em> validation results of the object.
055     * </p>
056     *
057     * @author Keith Donald
058     * @author Jan Hoskens
059     */
060    public class RulesValidator implements RichValidator, ObjectNameResolver {
061    
062            private static final Log logger = LogFactory.getLog(RulesValidator.class);
063    
064            private final DefaultValidationResults results = new DefaultValidationResults();
065    
066            private final MessageTranslator messageTranslator;
067    
068            private final Map validationErrors = new HashMap();
069    
070            private final FormModel formModel;
071    
072            private BeanValidationResultsCollector validationResultsCollector;
073    
074            private RulesSource rulesSource;
075    
076            private String rulesContextId = null;
077    
078            private Class objectClass;
079    
080            /**
081             * Creates a RulesValidator for the given formModel. When no RulesSource is
082             * given, a default/global RulesSource is retrieved by the
083             * ApplicationServices class.
084             *
085             * @see org.springframework.richclient.application.ApplicationServices#getRulesSource()
086             */
087            public RulesValidator(FormModel formModel) {
088                    this(formModel, null);
089            }
090    
091            /**
092             * Create a RulesValidator which uses the supplied RulesSource on the
093             * FormModel.
094             */
095            public RulesValidator(FormModel formModel, RulesSource rulesSource) {
096                    this.formModel = formModel;
097                    this.rulesSource = rulesSource;
098                    validationResultsCollector = new BeanValidationResultsCollector(new FormModelPropertyAccessStrategy(formModel));
099                    MessageTranslatorFactory factory = (MessageTranslatorFactory) ApplicationServicesLocator.services().getService(
100                                    MessageTranslatorFactory.class);
101                    messageTranslator = factory.createTranslator(this);
102            }
103    
104            /**
105             * {@inheritDoc}
106             */
107            public ValidationResults validate(Object object) {
108                    return validate(object, null);
109            }
110    
111            /**
112             * {@inheritDoc}
113             */
114            public ValidationResults validate(Object object, String propertyName) {
115                    // Forms can have different types of objects, so when type of object
116                    // changes, messages that are already listed on the previous type must
117                    // be removed. If evaluating the whole object (propertyName == null)
118                    // also clear results.
119                    if ((propertyName == null) || ((objectClass != null) && objectClass != object.getClass())) {
120                            clearMessages();
121                    }
122                    objectClass = object.getClass();
123                    Rules rules = null;
124                    if (object instanceof PropertyConstraintProvider) {
125                            PropertyConstraintProvider propertyConstraintProvider = (PropertyConstraintProvider) object;
126                            if (propertyName != null) {
127                                    PropertyConstraint validationRule = propertyConstraintProvider.getPropertyConstraint(propertyName);
128                                    checkRule(validationRule);
129                            }
130                            else {
131                                    for (Iterator fieldNamesIter = formModel.getFieldNames().iterator(); fieldNamesIter.hasNext();) {
132                                            PropertyConstraint validationRule = propertyConstraintProvider
133                                                            .getPropertyConstraint((String) fieldNamesIter.next());
134                                            checkRule(validationRule);
135                                    }
136                            }
137                    }
138                    else {
139                            if (getRulesSource() != null) {
140                                    rules = getRulesSource().getRules(objectClass, getRulesContextId());
141                                    if (rules != null) {
142                                            for (Iterator i = rules.iterator(); i.hasNext();) {
143                                                    PropertyConstraint validationRule = (PropertyConstraint) i.next();
144                                                    if (propertyName == null) {
145                                                            if (formModel.hasValueModel(validationRule.getPropertyName())) {
146                                                                    checkRule(validationRule);
147                                                            }
148                                                    }
149                                                    else if (validationRule.isDependentOn(propertyName)) {
150                                                            checkRule(validationRule);
151                                                    }
152                                            }
153                                    }
154                            }
155                            else {
156                                    logger.debug("No rules source has been configured; "
157                                                    + "please set a valid reference to enable rules-based validation.");
158                            }
159                    }
160                    return results;
161            }
162    
163            private void checkRule(PropertyConstraint validationRule) {
164                    if (validationRule == null)
165                            return;
166                    BeanValidationResultsCollector resultsCollector = takeResultsCollector();
167                    PropertyResults results = resultsCollector.collectPropertyResults(validationRule);
168                    returnResultsCollector(resultsCollector);
169                    if (results == null) {
170                            constraintSatisfied(validationRule);
171                    }
172                    else {
173                            constraintViolated(validationRule, results);
174                    }
175            }
176    
177            protected void constraintSatisfied(PropertyConstraint exp) {
178                    ValidationMessage message = (ValidationMessage) validationErrors.remove(exp);
179                    if (message != null) {
180                            results.removeMessage(message);
181                    }
182            }
183    
184            protected void constraintViolated(PropertyConstraint exp, PropertyResults propertyResults) {
185                    ValidationMessage message = new DefaultValidationMessage(exp.getPropertyName(), Severity.ERROR,
186                                    messageTranslator.getMessage(propertyResults));
187    
188                    ValidationMessage oldMessage = (ValidationMessage) validationErrors.get(exp);
189                    if (!message.equals(oldMessage)) {
190                            results.removeMessage(oldMessage);
191                            validationErrors.put(exp, message);
192                            results.addMessage(message);
193                    }
194            }
195    
196            private RulesSource getRulesSource() {
197                    if (rulesSource == null) {
198                            rulesSource = (RulesSource) ApplicationServicesLocator.services().getService(RulesSource.class);
199                    }
200                    return rulesSource;
201            }
202    
203            private BeanValidationResultsCollector takeResultsCollector() {
204                    BeanValidationResultsCollector resultsCollector = validationResultsCollector;
205                    if (resultsCollector != null) {
206                            validationResultsCollector = null;
207                    }
208                    else {
209                            resultsCollector = new BeanValidationResultsCollector(new FormModelPropertyAccessStrategy(formModel));
210                    }
211                    return resultsCollector;
212            }
213    
214            private void returnResultsCollector(BeanValidationResultsCollector resultsCollector) {
215                    validationResultsCollector = resultsCollector;
216            }
217    
218            /**
219             * Returns the rules context id set on this validator.
220             */
221            public String getRulesContextId() {
222                    return rulesContextId;
223            }
224    
225            /**
226             * Set the rules context id. This is passed in the call to
227             * {@link RulesSource#getRules(Class, String)} to allow for context specific
228             * rules.
229             * @param rulesContextId
230             */
231            public void setRulesContextId(String rulesContextId) {
232                    this.rulesContextId = rulesContextId;
233            }
234    
235            /**
236             * {@inheritDoc}
237             */
238            public String resolveObjectName(String objectName) {
239                    return formModel.getFieldFace(objectName).getDisplayName();
240            }
241    
242            /**
243             * Clear the current validationMessages and the errors.
244             *
245             * @see #validate(Object, String)
246             */
247            public void clearMessages() {
248                    this.results.clearMessages();
249                    this.validationErrors.clear();
250            }
251    }