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 }