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.form.support; 017 018 import java.beans.PropertyChangeEvent; 019 import java.beans.PropertyChangeListener; 020 import java.util.HashMap; 021 import java.util.Map; 022 023 import org.springframework.beans.PropertyAccessException; 024 import org.springframework.binding.MutablePropertyAccessStrategy; 025 import org.springframework.binding.convert.ConversionException; 026 import org.springframework.binding.form.BindingErrorMessageProvider; 027 import org.springframework.binding.form.HierarchicalFormModel; 028 import org.springframework.binding.form.ValidatingFormModel; 029 import org.springframework.binding.validation.RichValidator; 030 import org.springframework.binding.validation.ValidationMessage; 031 import org.springframework.binding.validation.ValidationResultsModel; 032 import org.springframework.binding.validation.Validator; 033 import org.springframework.binding.validation.support.DefaultValidationResults; 034 import org.springframework.binding.validation.support.DefaultValidationResultsModel; 035 import org.springframework.binding.validation.support.RulesValidator; 036 import org.springframework.binding.value.ValueModel; 037 import org.springframework.binding.value.support.AbstractValueModelWrapper; 038 import org.springframework.core.style.ToStringCreator; 039 import org.springframework.richclient.util.Assert; 040 import org.springframework.rules.support.DefaultRulesSource; 041 042 /** 043 * Default form model implementation. Is configurable, hierarchical and 044 * validating. 045 * <p> 046 * If you need this form model to use validation rules that are specific to a 047 * given context (such as a specific form), then you will need to call 048 * {@link #setValidator(Validator)} with a validator configured with the 049 * required context id. Like this: <code> 050 * RulesValidator validator = myValidatingFormModel.getValidator(); 051 * validator.setRulesContextId( "mySpecialFormId" ); 052 * </code> 053 * Along with this you will need to register your rules using the context id. 054 * See {@link DefaultRulesSource#addRules(String, org.springframework.rules.Rules)}. 055 * 056 * @author Keith Donald 057 * @author Oliver Hutchison 058 */ 059 public class DefaultFormModel extends AbstractFormModel implements ValidatingFormModel { 060 061 private final DefaultValidationResultsModel validationResultsModel = new DefaultValidationResultsModel(); 062 063 private final DefaultValidationResults additionalValidationResults = new DefaultValidationResults(); 064 065 private final Map bindingErrorMessages = new HashMap(); 066 067 private boolean validating = true; 068 069 private boolean oldValidating = true; 070 071 private boolean oldHasErrors = false; 072 073 private Validator validator; 074 075 private BindingErrorMessageProvider bindingErrorMessageProvider = new DefaultBindingErrorMessageProvider(); 076 077 public DefaultFormModel() { 078 init(); 079 } 080 081 public DefaultFormModel(Object domainObject) { 082 super(domainObject); 083 init(); 084 } 085 086 public DefaultFormModel(Object domainObject, boolean buffered) { 087 super(domainObject, buffered); 088 init(); 089 } 090 091 public DefaultFormModel(ValueModel domainObjectHolder) { 092 super(domainObjectHolder, true); 093 init(); 094 } 095 096 public DefaultFormModel(ValueModel domainObjectHolder, boolean buffered) { 097 super(domainObjectHolder, buffered); 098 init(); 099 } 100 101 public DefaultFormModel(MutablePropertyAccessStrategy domainObjectAccessStrategy) { 102 super(domainObjectAccessStrategy, true); 103 init(); 104 } 105 106 public DefaultFormModel(MutablePropertyAccessStrategy domainObjectAccessStrategy, boolean bufferChanges) { 107 super(domainObjectAccessStrategy, bufferChanges); 108 init(); 109 } 110 111 /** 112 * Initialization of DefaultFormModel. Adds a listener on the Enabled 113 * property in order to switch validating state on or off. When disabling a 114 * formModel, no validation will happen. 115 */ 116 protected void init() { 117 addPropertyChangeListener(ENABLED_PROPERTY, new PropertyChangeListener() { 118 119 public void propertyChange(PropertyChangeEvent evt) { 120 validatingUpdated(); 121 } 122 123 }); 124 validationResultsModel.addPropertyChangeListener(ValidationResultsModel.HAS_ERRORS_PROPERTY, 125 childStateChangeHandler); 126 } 127 128 /** 129 * {@inheritDoc} 130 */ 131 public boolean isValidating() { 132 if (validating && isEnabled()) { 133 if (getParent() instanceof ValidatingFormModel) 134 { 135 return ((ValidatingFormModel)getParent()).isValidating(); 136 } 137 return true; 138 } 139 return false; 140 } 141 142 public void setValidating(boolean validating) { 143 this.validating = validating; 144 validatingUpdated(); 145 } 146 147 protected void validatingUpdated() { 148 boolean validating = isValidating(); 149 if (hasChanged(oldValidating, validating)) { 150 if (validating) { 151 validate(); 152 } 153 else { 154 validationResultsModel.clearAllValidationResults(); 155 } 156 oldValidating = validating; 157 firePropertyChange(VALIDATING_PROPERTY, !validating, validating); 158 } 159 } 160 161 public void addChild(HierarchicalFormModel child) { 162 if (child.getParent() == this) 163 return; 164 165 super.addChild(child); 166 if (child instanceof ValidatingFormModel) { 167 getValidationResults().add(((ValidatingFormModel) child).getValidationResults()); 168 child.addPropertyChangeListener(ValidationResultsModel.HAS_ERRORS_PROPERTY, childStateChangeHandler); 169 } 170 } 171 172 public void removeChild(HierarchicalFormModel child) { 173 if (child instanceof ValidatingFormModel) { 174 getValidationResults().remove(((ValidatingFormModel) child).getValidationResults()); 175 child.removePropertyChangeListener(ValidationResultsModel.HAS_ERRORS_PROPERTY, childStateChangeHandler); 176 } 177 super.removeChild(child); 178 } 179 180 /** 181 * {@inheritDoc} 182 * 183 * Additionally the {@link DefaultFormModel} adds the event: 184 * 185 * <ul> 186 * <li><em>Has errors event:</em> if the validation results model 187 * contains errors, the form model error state should be revised as well as 188 * the committable state.</li> 189 * </ul> 190 * 191 * Note that we see the {@link ValidationResultsModel} as a child model of 192 * the {@link DefaultFormModel} as the result model is bundled together with 193 * the value models. 194 */ 195 protected void childStateChanged(PropertyChangeEvent evt) { 196 super.childStateChanged(evt); 197 if (ValidationResultsModel.HAS_ERRORS_PROPERTY.equals(evt.getPropertyName())) { 198 hasErrorsUpdated(); 199 } 200 } 201 202 public void setParent(HierarchicalFormModel parent) { 203 super.setParent(parent); 204 if (parent instanceof ValidatingFormModel) { 205 parent.addPropertyChangeListener(ValidatingFormModel.VALIDATING_PROPERTY, parentStateChangeHandler); 206 } 207 } 208 209 /** 210 * {@inheritDoc} 211 * 212 * Additionally the {@link DefaultFormModel} adds the event: 213 * 214 * <ul> 215 * <li><em>Validating state:</em> if validating is disabled on parent, 216 * child should not validate as well. If parent is set to validating the 217 * child's former validating state should apply.</li> 218 * </ul> 219 */ 220 protected void parentStateChanged(PropertyChangeEvent evt) { 221 super.parentStateChanged(evt); 222 if (ValidatingFormModel.VALIDATING_PROPERTY.equals(evt.getPropertyName())) { 223 validatingUpdated(); 224 } 225 } 226 227 public void removeParent() { 228 if (getParent() instanceof ValidatingFormModel) { 229 getParent().removePropertyChangeListener(ValidatingFormModel.VALIDATING_PROPERTY, parentStateChangeHandler); 230 } 231 super.removeParent(); 232 } 233 234 public ValidationResultsModel getValidationResults() { 235 return validationResultsModel; 236 } 237 238 public boolean getHasErrors() { 239 return validationResultsModel.getHasErrors(); 240 } 241 242 protected void hasErrorsUpdated() { 243 boolean hasErrors = getHasErrors(); 244 if (hasChanged(oldHasErrors, hasErrors)) { 245 oldHasErrors = hasErrors; 246 firePropertyChange(ValidationResultsModel.HAS_ERRORS_PROPERTY, !hasErrors, hasErrors); 247 committableUpdated(); 248 } 249 } 250 251 public void validate() { 252 if (isValidating()) { 253 validateAfterPropertyChanged(null); 254 } 255 } 256 257 public Validator getValidator() { 258 if (validator == null) { 259 setValidator(new RulesValidator(this)); 260 } 261 return validator; 262 } 263 264 /** 265 * {@inheritDoc} 266 * 267 * <p> 268 * Setting a validator will trigger a validate of the current object. 269 * </p> 270 */ 271 public void setValidator(Validator validator) { 272 Assert.required(validator, "validator"); 273 this.validator = validator; 274 validate(); 275 } 276 277 public boolean isCommittable() { 278 final boolean superIsCommittable = super.isCommittable(); 279 final boolean hasNoErrors = !getValidationResults().getHasErrors(); 280 return superIsCommittable && hasNoErrors; 281 } 282 283 protected ValueModel preProcessNewValueModel(String formProperty, ValueModel formValueModel) { 284 if (!(formValueModel instanceof ValidatingFormValueModel)) { 285 return new ValidatingFormValueModel(formProperty, formValueModel, true); 286 } 287 return formValueModel; 288 } 289 290 protected void postProcessNewValueModel(String formProperty, ValueModel valueModel) { 291 validateAfterPropertyChanged(formProperty); 292 } 293 294 protected ValueModel preProcessNewConvertingValueModel(String formProperty, Class targetClass, 295 ValueModel formValueModel) { 296 return new ValidatingFormValueModel(formProperty, formValueModel, false); 297 } 298 299 protected void postProcessNewConvertingValueModel(String formProperty, Class targetClass, ValueModel valueModel) { 300 } 301 302 protected void formPropertyValueChanged(String formProperty) { 303 validateAfterPropertyChanged(formProperty); 304 } 305 306 /** 307 * 308 * @param formProperty the name of the only property that has changed since 309 * the last call to validateAfterPropertyChange or <code>null</code> if 310 * this is not known/available. 311 */ 312 protected void validateAfterPropertyChanged(String formProperty) { 313 if (isValidating()) { 314 Validator validator = getValidator(); 315 if (validator != null) { 316 DefaultValidationResults validationResults = new DefaultValidationResults(bindingErrorMessages.values()); 317 if (formProperty != null && validator instanceof RichValidator) { 318 validationResults.addAllMessages(((RichValidator) validator) 319 .validate(getFormObject(), formProperty)); 320 } 321 else { 322 validationResults.addAllMessages(validator.validate(getFormObject())); 323 } 324 validationResults.addAllMessages(additionalValidationResults); 325 validationResultsModel.updateValidationResults(validationResults); 326 } 327 } 328 } 329 330 protected void raiseBindingError(ValidatingFormValueModel valueModel, Object valueBeingSet, Exception e) { 331 ValidationMessage oldValidationMessage = (ValidationMessage) bindingErrorMessages.get(valueModel); 332 ValidationMessage newValidationMessage = getBindingErrorMessage(valueModel.getFormProperty(), valueBeingSet, e); 333 bindingErrorMessages.put(valueModel, newValidationMessage); 334 if (isValidating()) { 335 validationResultsModel.replaceMessage(oldValidationMessage, newValidationMessage); 336 } 337 } 338 339 protected void clearBindingError(ValidatingFormValueModel valueModel) { 340 ValidationMessage validationMessage = (ValidationMessage) bindingErrorMessages.remove(valueModel); 341 if (validationMessage != null) { 342 validationResultsModel.removeMessage(validationMessage); 343 } 344 } 345 346 public void raiseValidationMessage(ValidationMessage validationMessage) { 347 additionalValidationResults.addMessage(validationMessage); 348 if (isValidating()) { 349 validationResultsModel.addMessage(validationMessage); 350 } 351 } 352 353 public void clearValidationMessage(ValidationMessage validationMessage) { 354 additionalValidationResults.removeMessage(validationMessage); 355 if (isValidating()) { 356 validationResultsModel.removeMessage(validationMessage); 357 } 358 } 359 360 protected ValidationMessage getBindingErrorMessage(String propertyName, Object valueBeingSet, Exception e) { 361 return bindingErrorMessageProvider.getErrorMessage(this, propertyName, valueBeingSet, e); 362 } 363 364 public void setBindingErrorMessageProvider(BindingErrorMessageProvider bindingErrorMessageProvider) { 365 Assert.required(bindingErrorMessageProvider, "bindingErrorMessageProvider"); 366 this.bindingErrorMessageProvider = bindingErrorMessageProvider; 367 } 368 369 public String toString() { 370 return new ToStringCreator(this).append("id", getId()).append("buffered", isBuffered()).append("enabled", 371 isEnabled()).append("dirty", isDirty()).append("validating", isValidating()).append( 372 "validationResults", getValidationResults()).toString(); 373 } 374 375 protected class ValidatingFormValueModel extends AbstractValueModelWrapper { 376 private final String formProperty; 377 378 private final ValueChangeHandler valueChangeHander; 379 380 public ValidatingFormValueModel(String formProperty, ValueModel model, boolean validateOnChange) { 381 super(model); 382 this.formProperty = formProperty; 383 if (validateOnChange) { 384 this.valueChangeHander = new ValueChangeHandler(); 385 addValueChangeListener(valueChangeHander); 386 } 387 else { 388 this.valueChangeHander = null; 389 } 390 } 391 392 public String getFormProperty() { 393 return formProperty; 394 } 395 396 public void setValueSilently(Object value, PropertyChangeListener listenerToSkip) { 397 try { 398 if (logger.isDebugEnabled()) { 399 Class valueClass = (value != null ? value.getClass() : null); 400 logger.debug("Setting '" + formProperty + "' value to convert/validate '" 401 + (UserMetadata.isFieldProtected(DefaultFormModel.this, formProperty) ? "***" : value) 402 + "', class=" + valueClass); 403 } 404 super.setValueSilently(value, listenerToSkip); 405 clearBindingError(this); 406 } 407 catch (ConversionException ce) { 408 logger.warn("Conversion exception occurred setting value", ce); 409 raiseBindingError(this, value, ce); 410 } 411 catch (PropertyAccessException pae) { 412 logger.warn("Type Mismatch Exception occurred setting value", pae); 413 raiseBindingError(this, value, pae); 414 } 415 } 416 417 public class ValueChangeHandler implements PropertyChangeListener { 418 public void propertyChange(PropertyChangeEvent evt) { 419 formPropertyValueChanged(formProperty); 420 } 421 } 422 } 423 }