KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > validation > DataBinder


1 /*
2  * Copyright 2002-2007 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.validation;
18
19 import java.beans.PropertyEditor JavaDoc;
20 import java.util.HashMap JavaDoc;
21 import java.util.Map JavaDoc;
22
23 import org.apache.commons.logging.Log;
24 import org.apache.commons.logging.LogFactory;
25
26 import org.springframework.beans.ConfigurablePropertyAccessor;
27 import org.springframework.beans.MutablePropertyValues;
28 import org.springframework.beans.PropertyAccessException;
29 import org.springframework.beans.PropertyAccessorUtils;
30 import org.springframework.beans.PropertyBatchUpdateException;
31 import org.springframework.beans.PropertyEditorRegistry;
32 import org.springframework.beans.PropertyValue;
33 import org.springframework.beans.PropertyValues;
34 import org.springframework.util.Assert;
35 import org.springframework.util.ObjectUtils;
36 import org.springframework.util.PatternMatchUtils;
37 import org.springframework.util.StringUtils;
38
39 /**
40  * Binder that allows for setting property values onto a target object,
41  * including support for validation and binding result analysis.
42  * The binding process can be customized through specifying allowed fields,
43  * required fields, custom editors, etc.
44  *
45  * <p>Note that there are potential security implications in failing to set an array
46  * of allowed fields. In the case of HTTP form POST data for example, malicious clients
47  * can attempt to subvert an application by supplying values for fields or properties
48  * that do not exist on the form. In some cases this could lead to illegal data being
49  * set on command objects <i>or their nested objects</i>. For this reason, it is
50  * <b>highly recommended to specify the {@link #setAllowedFields allowedFields} property</b>
51  * on the DataBinder.
52  *
53  * <p>The binding results can be examined via the {@link BindingResult} interface,
54  * extending the {@link Errors} interface: see the {@link #getBindingResult()} method.
55  * Missing fields and property access exceptions will be converted to {@link FieldError FieldErrors},
56  * collected in the Errors instance, using the following error codes:
57  *
58  * <ul>
59  * <li>Missing field error: "required"
60  * <li>Type mismatch error: "typeMismatch"
61  * <li>Method invocation error: "methodInvocation"
62  * </ul>
63  *
64  * <p>By default, binding errors get resolved through the {@link BindingErrorProcessor}
65  * strategy, processing for missing fields and property access exceptions: see the
66  * {@link #setBindingErrorProcessor} method. You can override the default strategy
67  * if needed, for example to generate different error codes.
68  *
69  * <p>Custom validation errors can be added afterwards. You will typically want to resolve
70  * such error codes into proper user-visible error messages; this can be achieved through
71  * resolving each error via a {@link org.springframework.context.MessageSource}, which is
72  * able to resolve an {@link ObjectError}/{@link FieldError} through its
73  * {@link org.springframework.context.MessageSource#getMessage(org.springframework.context.MessageSourceResolvable, java.util.Locale)}
74  * method. The list of message codes can be customized through the {@link MessageCodesResolver}
75  * strategy: see the {@link #setMessageCodesResolver} method. {@link DefaultMessageCodesResolver}'s
76  * javadoc states details on the default resolution rules.
77  *
78  * <p>This generic data binder can be used in any kind of environment.
79  * It is typically used by Spring web MVC controllers, via the web-specific
80  * subclasses {@link org.springframework.web.bind.ServletRequestDataBinder}
81  * and {@link org.springframework.web.portlet.bind.PortletRequestDataBinder}.
82  *
83  * @author Rod Johnson
84  * @author Juergen Hoeller
85  * @author Rob Harrop
86  * @see #setAllowedFields
87  * @see #setRequiredFields
88  * @see #registerCustomEditor
89  * @see #setMessageCodesResolver
90  * @see #setBindingErrorProcessor
91  * @see #bind
92  * @see #getBindingResult
93  * @see DefaultMessageCodesResolver
94  * @see DefaultBindingErrorProcessor
95  * @see org.springframework.context.MessageSource
96  * @see org.springframework.web.bind.ServletRequestDataBinder
97  */

98 public class DataBinder implements PropertyEditorRegistry {
99
100     /** Default object name used for binding: "target" */
101     public static final String JavaDoc DEFAULT_OBJECT_NAME = "target";
102
103
104     /**
105      * We'll create a lot of DataBinder instances: Let's use a static logger.
106      */

107     protected static final Log logger = LogFactory.getLog(DataBinder.class);
108
109     private final Object JavaDoc target;
110
111     private final String JavaDoc objectName;
112
113     private AbstractPropertyBindingResult bindingResult;
114
115     private BindException bindException;
116
117     private boolean ignoreUnknownFields = true;
118
119     private boolean ignoreInvalidFields = false;
120
121     private String JavaDoc[] allowedFields;
122
123     private String JavaDoc[] disallowedFields;
124
125     private String JavaDoc[] requiredFields;
126
127     private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor();
128
129
130     /**
131      * Create a new DataBinder instance, with default object name.
132      * @param target target object to bind onto
133      * @see #DEFAULT_OBJECT_NAME
134      */

135     public DataBinder(Object JavaDoc target) {
136         this(target, DEFAULT_OBJECT_NAME);
137     }
138
139     /**
140      * Create a new DataBinder instance.
141      * @param target target object to bind onto
142      * @param objectName name of the target object
143      */

144     public DataBinder(Object JavaDoc target, String JavaDoc objectName) {
145         Assert.notNull(target, "Target must not be null");
146         this.target = target;
147         this.objectName = objectName;
148     }
149
150
151     /**
152      * Return the wrapped target object.
153      */

154     public Object JavaDoc getTarget() {
155         return this.target;
156     }
157
158     /**
159      * Return the name of the bound object.
160      */

161     public String JavaDoc getObjectName() {
162         return this.objectName;
163     }
164
165     /**
166      * Initialize standard JavaBean property access for this DataBinder.
167      * <p>This is the default; an explicit call just leads to eager initialization.
168      * @see #initDirectFieldAccess()
169      */

170     public void initBeanPropertyAccess() {
171         Assert.isNull(this.bindingResult,
172                 "DataBinder is already initialized - call initBeanPropertyAccess before any other configuration methods");
173         this.bindingResult = new BeanPropertyBindingResult(getTarget(), getObjectName());
174     }
175
176     /**
177      * Initialize direct field access for this DataBinder,
178      * as alternative to the default bean property access.
179      * @see #initBeanPropertyAccess()
180      */

181     public void initDirectFieldAccess() {
182         Assert.isNull(this.bindingResult,
183                 "DataBinder is already initialized - call initDirectFieldAccess before any other configuration methods");
184         this.bindingResult = new DirectFieldBindingResult(getTarget(), getObjectName());
185     }
186
187     /**
188      * Return the internal BindingResult held by this DataBinder,
189      * as AbstractPropertyBindingResult.
190      */

191     protected AbstractPropertyBindingResult getInternalBindingResult() {
192         if (this.bindingResult == null) {
193             initBeanPropertyAccess();
194         }
195         return this.bindingResult;
196     }
197
198     /**
199      * Return the underlying PropertyAccessor of this binder's BindingResult.
200      * To be used by binder subclasses that need property checks.
201      */

202     protected ConfigurablePropertyAccessor getPropertyAccessor() {
203         return getInternalBindingResult().getPropertyAccessor();
204     }
205
206     /**
207      * Return the BindingResult instance created by this DataBinder.
208      * This allows for convenient access to the binding results after
209      * a bind operation.
210      * @return the BindingResult instance, to be treated as BindingResult
211      * or as Errors instance (Errors is a super-interface of BindingResult)
212      * @see Errors
213      * @see #bind
214      */

215     public BindingResult getBindingResult() {
216         return getInternalBindingResult();
217     }
218
219     /**
220      * Return the Errors instance for this data binder.
221      * @return the Errors instance, to be treated as Errors or as BindException
222      * @deprecated in favor of {@link #getBindingResult()}.
223      * Use the {@link BindException#BindException(BindingResult)} constructor
224      * to create a BindException instance if still needed.
225      * @see #getBindingResult()
226      */

227     public BindException getErrors() {
228         if (this.bindException == null) {
229             this.bindException = new BindException(getBindingResult());
230         }
231         return this.bindException;
232     }
233
234
235     /**
236      * Set whether to ignore unknown fields, that is, whether to ignore bind
237      * parameters that do not have corresponding fields in the target object.
238      * <p>Default is "true". Turn this off to enforce that all bind parameters
239      * must have a matching field in the target object.
240      * <p>Note that this setting only applies to <i>binding</i> operations
241      * on this DataBinder, not to <i>retrieving</i> values via its
242      * {@link #getBindingResult() BindingResult}.
243      * @see #bind
244      */

245     public void setIgnoreUnknownFields(boolean ignoreUnknownFields) {
246         this.ignoreUnknownFields = ignoreUnknownFields;
247     }
248
249     /**
250      * Return whether to ignore unknown fields when binding.
251      */

252     public boolean isIgnoreUnknownFields() {
253         return this.ignoreUnknownFields;
254     }
255
256     /**
257      * Set whether to ignore invalid fields, that is, whether to ignore bind
258      * parameters that have corresponding fields in the target object which are
259      * not accessible (for example because of null values in the nested path).
260      * <p>Default is "false". Turn this on to ignore bind parameters for
261      * nested objects in non-existing parts of the target object graph.
262      * <p>Note that this setting only applies to <i>binding</i> operations
263      * on this DataBinder, not to <i>retrieving</i> values via its
264      * {@link #getBindingResult() BindingResult}.
265      * @see #bind
266      */

267     public void setIgnoreInvalidFields(boolean ignoreInvalidFields) {
268         this.ignoreInvalidFields = ignoreInvalidFields;
269     }
270
271     /**
272      * Return whether to ignore invalid fields when binding.
273      */

274     public boolean isIgnoreInvalidFields() {
275         return this.ignoreInvalidFields;
276     }
277
278     /**
279      * Register fields that should be allowed for binding. Default is all
280      * fields. Restrict this for example to avoid unwanted modifications
281      * by malicious users when binding HTTP request parameters.
282      * <p>Supports "xxx*", "*xxx" and "*xxx*" patterns. More sophisticated matching
283      * can be implemented by overriding the <code>isAllowed</code> method.
284      * <p>Alternatively, specify a list of <i>disallowed</i> fields.
285      * @param allowedFields array of field names
286      * @see #setDisallowedFields
287      * @see #isAllowed(String)
288      * @see org.springframework.web.bind.ServletRequestDataBinder
289      */

290     public void setAllowedFields(String JavaDoc[] allowedFields) {
291         this.allowedFields = PropertyAccessorUtils.canonicalPropertyNames(allowedFields);
292     }
293
294     /**
295      * Return the fields that should be allowed for binding.
296      * @return array of field names
297      */

298     public String JavaDoc[] getAllowedFields() {
299         return this.allowedFields;
300     }
301
302     /**
303      * Register fields that should <i>not</i> be allowed for binding. Default is none.
304      * Mark fields as disallowed for example to avoid unwanted modifications
305      * by malicious users when binding HTTP request parameters.
306      * <p>Supports "xxx*", "*xxx" and "*xxx*" patterns. More sophisticated matching
307      * can be implemented by overriding the <code>isAllowed</code> method.
308      * <p>Alternatively, specify a list of <i>allowed</i> fields.
309      * @param disallowedFields array of field names
310      * @see #setAllowedFields
311      * @see #isAllowed(String)
312      * @see org.springframework.web.bind.ServletRequestDataBinder
313      */

314     public void setDisallowedFields(String JavaDoc[] disallowedFields) {
315         this.disallowedFields = PropertyAccessorUtils.canonicalPropertyNames(disallowedFields);
316     }
317
318     /**
319      * Return the fields that should <i>not</i> be allowed for binding.
320      * @return array of field names
321      */

322     public String JavaDoc[] getDisallowedFields() {
323         return this.disallowedFields;
324     }
325
326     /**
327      * Register fields that are required for each binding process.
328      * <p>If one of the specified fields is not contained in the list of
329      * incoming property values, a corresponding "missing field" error
330      * will be created, with error code "required" (by the default
331      * binding error processor).
332      * @param requiredFields array of field names
333      * @see #setBindingErrorProcessor
334      * @see DefaultBindingErrorProcessor#MISSING_FIELD_ERROR_CODE
335      */

336     public void setRequiredFields(String JavaDoc[] requiredFields) {
337         this.requiredFields = PropertyAccessorUtils.canonicalPropertyNames(requiredFields);
338         if (logger.isDebugEnabled()) {
339             logger.debug("DataBinder requires binding of required fields [" +
340                     StringUtils.arrayToCommaDelimitedString(requiredFields) + "]");
341         }
342     }
343
344     /**
345      * Return the fields that are required for each binding process.
346      * @return array of field names
347      */

348     public String JavaDoc[] getRequiredFields() {
349         return this.requiredFields;
350     }
351
352     /**
353      * Set whether to extract the old field value when applying a
354      * property editor to a new value for a field.
355      * <p>Default is "true", exposing previous field values to custom editors.
356      * Turn this to "false" to avoid side effects caused by getters.
357      */

358     public void setExtractOldValueForEditor(boolean extractOldValueForEditor) {
359         getPropertyAccessor().setExtractOldValueForEditor(extractOldValueForEditor);
360     }
361
362     public void registerCustomEditor(Class JavaDoc requiredType, PropertyEditor JavaDoc propertyEditor) {
363         getPropertyAccessor().registerCustomEditor(requiredType, propertyEditor);
364     }
365
366     public void registerCustomEditor(Class JavaDoc requiredType, String JavaDoc field, PropertyEditor JavaDoc propertyEditor) {
367         getPropertyAccessor().registerCustomEditor(requiredType, field, propertyEditor);
368     }
369
370     public PropertyEditor JavaDoc findCustomEditor(Class JavaDoc requiredType, String JavaDoc propertyPath) {
371         return getPropertyAccessor().findCustomEditor(requiredType, propertyPath);
372     }
373
374     /**
375      * Set the strategy to use for resolving errors into message codes.
376      * Applies the given strategy to the underlying errors holder.
377      * <p>Default is a DefaultMessageCodesResolver.
378      * @see BeanPropertyBindingResult#setMessageCodesResolver
379      * @see DefaultMessageCodesResolver
380      */

381     public void setMessageCodesResolver(MessageCodesResolver messageCodesResolver) {
382         getInternalBindingResult().setMessageCodesResolver(messageCodesResolver);
383     }
384
385     /**
386      * Set the strategy to use for processing binding errors, that is,
387      * required field errors and <code>PropertyAccessException</code>s.
388      * <p>Default is a DefaultBindingErrorProcessor.
389      * @see DefaultBindingErrorProcessor
390      */

391     public void setBindingErrorProcessor(BindingErrorProcessor bindingErrorProcessor) {
392         this.bindingErrorProcessor = bindingErrorProcessor;
393     }
394
395     /**
396      * Return the strategy for processing binding errors.
397      */

398     public BindingErrorProcessor getBindingErrorProcessor() {
399         return bindingErrorProcessor;
400     }
401
402
403     /**
404      * Bind the given property values to this binder's target.
405      * <p>This call can create field errors, representing basic binding
406      * errors like a required field (code "required"), or type mismatch
407      * between value and bean property (code "typeMismatch").
408      * <p>Note that the given PropertyValues should be a throwaway instance:
409      * For efficiency, it will be modified to just contain allowed fields if it
410      * implements the MutablePropertyValues interface; else, an internal mutable
411      * copy will be created for this purpose. Pass in a copy of the PropertyValues
412      * if you want your original instance to stay unmodified in any case.
413      * @param pvs property values to bind
414      * @see #doBind(org.springframework.beans.MutablePropertyValues)
415      */

416     public void bind(PropertyValues pvs) {
417         MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues) ?
418                 (MutablePropertyValues) pvs : new MutablePropertyValues(pvs);
419         doBind(mpvs);
420     }
421
422     /**
423      * Actual implementation of the binding process, working with the
424      * passed-in MutablePropertyValues instance.
425      * @param mpvs the property values to bind,
426      * as MutablePropertyValues instance
427      * @see #checkAllowedFields
428      * @see #checkRequiredFields
429      * @see #applyPropertyValues
430      */

431     protected void doBind(MutablePropertyValues mpvs) {
432         checkAllowedFields(mpvs);
433         checkRequiredFields(mpvs);
434         applyPropertyValues(mpvs);
435     }
436
437     /**
438      * Check the given property values against the allowed fields,
439      * removing values for fields that are not allowed.
440      * @param mpvs the property values to be bound (can be modified)
441      * @see #getAllowedFields
442      * @see #isAllowed(String)
443      */

444     protected void checkAllowedFields(MutablePropertyValues mpvs) {
445         PropertyValue[] pvs = mpvs.getPropertyValues();
446         for (int i = 0; i < pvs.length; i++) {
447             PropertyValue pv = pvs[i];
448             String JavaDoc field = PropertyAccessorUtils.canonicalPropertyName(pv.getName());
449             if (!isAllowed(field)) {
450                 mpvs.removePropertyValue(pv);
451                 getBindingResult().recordSuppressedField(field);
452                 if (logger.isDebugEnabled()) {
453                     logger.debug("Field [" + field + "] has been removed from PropertyValues " +
454                             "and will not be bound, because it has not been found in the list of allowed fields");
455                 }
456             }
457         }
458     }
459
460     /**
461      * Return if the given field is allowed for binding.
462      * Invoked for each passed-in property value.
463      * <p>The default implementation checks for "xxx*", "*xxx" and "*xxx*" matches,
464      * as well as direct equality, in the specified lists of allowed fields and
465      * disallowed fields. A field matching a disallowed pattern will not be accepted
466      * even if it also happens to match a pattern in the allowed list.
467      * <p>Can be overridden in subclasses.
468      * @param field the field to check
469      * @return if the field is allowed
470      * @see #setAllowedFields
471      * @see #setDisallowedFields
472      * @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String)
473      */

474     protected boolean isAllowed(String JavaDoc field) {
475         String JavaDoc[] allowed = getAllowedFields();
476         String JavaDoc[] disallowed = getDisallowedFields();
477         return ((ObjectUtils.isEmpty(allowed) || PatternMatchUtils.simpleMatch(allowed, field)) &&
478                 (ObjectUtils.isEmpty(disallowed) || !PatternMatchUtils.simpleMatch(disallowed, field)));
479     }
480
481     /**
482      * Check the given property values against the required fields,
483      * generating missing field errors where appropriate.
484      * @param mpvs the property values to be bound (can be modified)
485      * @see #getRequiredFields
486      * @see #getBindingErrorProcessor
487      * @see BindingErrorProcessor#processMissingFieldError
488      */

489     protected void checkRequiredFields(MutablePropertyValues mpvs) {
490         String JavaDoc[] requiredFields = getRequiredFields();
491         if (!ObjectUtils.isEmpty(requiredFields)) {
492             Map JavaDoc propertyValues = new HashMap JavaDoc();
493             PropertyValue[] pvs = mpvs.getPropertyValues();
494             for (int i = 0; i < pvs.length; i++) {
495                 PropertyValue pv = pvs[i];
496                 String JavaDoc canonicalName = PropertyAccessorUtils.canonicalPropertyName(pv.getName());
497                 propertyValues.put(canonicalName, pv);
498             }
499             for (int i = 0; i < requiredFields.length; i++) {
500                 String JavaDoc field = requiredFields[i];
501                 PropertyValue pv = (PropertyValue) propertyValues.get(field);
502                 if (pv == null || pv.getValue() == null ||
503                         (pv.getValue() instanceof String JavaDoc && !StringUtils.hasText((String JavaDoc) pv.getValue()))) {
504                     // Use bind error processor to create FieldError.
505
getBindingErrorProcessor().processMissingFieldError(field, getInternalBindingResult());
506                     // Remove property from property values to bind:
507
// It has already caused a field error with a rejected value.
508
if (pv != null) {
509                         mpvs.removePropertyValue(pv);
510                         propertyValues.remove(field);
511                     }
512                 }
513             }
514         }
515     }
516
517     /**
518      * Apply given property values to the target object.
519      * <p>Default implementation applies all of the supplied property
520      * values as bean property values. By default, unknown fields will
521      * be ignored.
522      * @param mpvs the property values to be bound (can be modified)
523      * @see #getTarget
524      * @see #getPropertyAccessor
525      * @see #isIgnoreUnknownFields
526      * @see #getBindingErrorProcessor
527      * @see BindingErrorProcessor#processPropertyAccessException
528      */

529     protected void applyPropertyValues(MutablePropertyValues mpvs) {
530         try {
531             // Bind request parameters onto target object.
532
getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
533         }
534         catch (PropertyBatchUpdateException ex) {
535             // Use bind error processor to create FieldErrors.
536
PropertyAccessException[] exs = ex.getPropertyAccessExceptions();
537             for (int i = 0; i < exs.length; i++) {
538                 getBindingErrorProcessor().processPropertyAccessException(exs[i], getInternalBindingResult());
539             }
540         }
541     }
542
543
544     /**
545      * Close this DataBinder, which may result in throwing
546      * a BindException if it encountered any errors.
547      * @return the model Map, containing target object and Errors instance
548      * @throws BindException if there were any errors in the bind operation
549      * @see BindingResult#getModel()
550      */

551     public Map JavaDoc close() throws BindException {
552         if (getBindingResult().hasErrors()) {
553             throw new BindException(getBindingResult());
554         }
555         return getBindingResult().getModel();
556     }
557
558 }
559
Popular Tags