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 }