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 }