001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of 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,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.web.bind;
018:
019: import java.lang.reflect.Array;
020: import java.util.Iterator;
021: import java.util.Map;
022:
023: import org.springframework.beans.MutablePropertyValues;
024: import org.springframework.beans.PropertyValue;
025: import org.springframework.validation.DataBinder;
026: import org.springframework.web.multipart.MultipartFile;
027:
028: /**
029: * Special {@link DataBinder} for data binding from web request parameters
030: * to JavaBean objects. Designed for web environments, but not dependent on
031: * the Servlet API; serves as base class for more specific DataBinder variants,
032: * such as {@link org.springframework.web.bind.ServletRequestDataBinder}.
033: *
034: * <p>Includes support for field markers which address a common problem with
035: * HTML checkboxes and select options: detecting that a field was part of
036: * the form, but did not generate a request parameter because it was empty.
037: * A field marker allows to detect that state and reset the corresponding
038: * bean property accordingly.
039: *
040: * @author Juergen Hoeller
041: * @since 1.2
042: * @see #registerCustomEditor
043: * @see #setAllowedFields
044: * @see #setRequiredFields
045: * @see #setFieldMarkerPrefix
046: * @see ServletRequestDataBinder
047: */
048: public class WebDataBinder extends DataBinder {
049:
050: /**
051: * Default prefix that field marker parameters start with, followed by the field
052: * name: e.g. "_subscribeToNewsletter" for a field "subscribeToNewsletter".
053: * <p>Such a marker parameter indicates that the field was visible, that is,
054: * existed in the form that caused the submission. If no corresponding field
055: * value parameter was found, the field will be reset. The value of the field
056: * marker parameter does not matter in this case; an arbitrary value can be used.
057: * This is particularly useful for HTML checkboxes and select options.
058: * @see #setFieldMarkerPrefix
059: */
060: public static final String DEFAULT_FIELD_MARKER_PREFIX = "_";
061:
062: private String fieldMarkerPrefix = DEFAULT_FIELD_MARKER_PREFIX;
063:
064: private boolean bindEmptyMultipartFiles = true;
065:
066: /**
067: * Create a new WebDataBinder instance, with default object name.
068: * @param target target object to bind onto
069: * @see #DEFAULT_OBJECT_NAME
070: */
071: public WebDataBinder(Object target) {
072: super (target);
073: }
074:
075: /**
076: * Create a new WebDataBinder instance.
077: * @param target target object to bind onto
078: * @param objectName objectName of the target object
079: */
080: public WebDataBinder(Object target, String objectName) {
081: super (target, objectName);
082: }
083:
084: /**
085: * Specify a prefix that can be used for parameters that mark potentially
086: * empty fields, having "prefix + field" as name. Such a marker parameter is
087: * checked by existence: You can send any value for it, for example "visible".
088: * This is particularly useful for HTML checkboxes and select options.
089: * <p>Default is "_", for "_FIELD" parameters (e.g. "_subscribeToNewsletter").
090: * Set this to null if you want to turn off the empty field check completely.
091: * <p>HTML checkboxes only send a value when they're checked, so it is not
092: * possible to detect that a formerly checked box has just been unchecked,
093: * at least not with standard HTML means.
094: * <p>One way to address this is to look for a checkbox parameter value if
095: * you know that the checkbox has been visible in the form, resetting the
096: * checkbox if no value found. In Spring web MVC, this typically happens
097: * in a custom <code>onBind</code> implementation.
098: * <p>This auto-reset mechanism addresses this deficiency, provided
099: * that a marker parameter is sent for each checkbox field, like
100: * "_subscribeToNewsletter" for a "subscribeToNewsletter" field.
101: * As the marker parameter is sent in any case, the data binder can
102: * detect an empty field and automatically reset its value.
103: * @see #DEFAULT_FIELD_MARKER_PREFIX
104: * @see org.springframework.web.servlet.mvc.BaseCommandController#onBind
105: */
106: public void setFieldMarkerPrefix(String fieldMarkerPrefix) {
107: this .fieldMarkerPrefix = fieldMarkerPrefix;
108: }
109:
110: /**
111: * Return the prefix for parameters that mark potentially empty fields.
112: */
113: public String getFieldMarkerPrefix() {
114: return this .fieldMarkerPrefix;
115: }
116:
117: /**
118: * Set whether to bind empty MultipartFile parameters. Default is "true".
119: * <p>Turn this off if you want to keep an already bound MultipartFile
120: * when the user resubmits the form without choosing a different file.
121: * Else, the already bound MultipartFile will be replaced by an empty
122: * MultipartFile holder.
123: * @see org.springframework.web.multipart.MultipartFile
124: */
125: public void setBindEmptyMultipartFiles(
126: boolean bindEmptyMultipartFiles) {
127: this .bindEmptyMultipartFiles = bindEmptyMultipartFiles;
128: }
129:
130: /**
131: * Return whether to bind empty MultipartFile parameters.
132: */
133: public boolean isBindEmptyMultipartFiles() {
134: return this .bindEmptyMultipartFiles;
135: }
136:
137: /**
138: * This implementation performs a field marker check
139: * before delegating to the superclass binding process.
140: * @see #checkFieldMarkers
141: */
142: protected void doBind(MutablePropertyValues mpvs) {
143: checkFieldMarkers(mpvs);
144: super .doBind(mpvs);
145: }
146:
147: /**
148: * Check the given property values for field markers,
149: * i.e. for fields that start with the field marker prefix.
150: * <p>The existence of a field marker indicates that the specified
151: * field existed in the form. If the property values do not contain
152: * a corresponding field value, the field will be considered as empty
153: * and will be reset appropriately.
154: * @param mpvs the property values to be bound (can be modified)
155: * @see #getFieldMarkerPrefix
156: * @see #getEmptyValue(String, Class)
157: */
158: protected void checkFieldMarkers(MutablePropertyValues mpvs) {
159: if (getFieldMarkerPrefix() != null) {
160: String fieldMarkerPrefix = getFieldMarkerPrefix();
161: PropertyValue[] pvArray = mpvs.getPropertyValues();
162: for (int i = 0; i < pvArray.length; i++) {
163: PropertyValue pv = pvArray[i];
164: if (pv.getName().startsWith(fieldMarkerPrefix)) {
165: String field = pv.getName().substring(
166: fieldMarkerPrefix.length());
167: if (getPropertyAccessor().isWritableProperty(field)
168: && !mpvs.contains(field)) {
169: Class fieldType = getPropertyAccessor()
170: .getPropertyType(field);
171: mpvs.addPropertyValue(field, getEmptyValue(
172: field, fieldType));
173: }
174: mpvs.removePropertyValue(pv);
175: }
176: }
177: }
178: }
179:
180: /**
181: * Determine an empty value for the specified field.
182: * <p>Default implementation returns <code>Boolean.FALSE</code>
183: * for boolean fields and an empty array of array types.
184: * Else, <code>null</code> is used as default.
185: * @param field the name of the field
186: * @param fieldType the type of the field
187: * @return the empty value (for most fields: null)
188: */
189: protected Object getEmptyValue(String field, Class fieldType) {
190: if (fieldType != null && boolean.class.equals(fieldType)
191: || Boolean.class.equals(fieldType)) {
192: // Special handling of boolean property.
193: return Boolean.FALSE;
194: } else if (fieldType != null && fieldType.isArray()) {
195: // Special handling of array property.
196: return Array.newInstance(fieldType.getComponentType(), 0);
197: } else {
198: // Default value: try null.
199: return null;
200: }
201: }
202:
203: /**
204: * Bind the multipart files contained in the given request, if any
205: * (in case of a multipart request).
206: * <p>Multipart files will only be added to the property values if they
207: * are not empty or if we're configured to bind empty multipart files too.
208: * @param multipartFiles Map of field name String to MultipartFile object
209: * @param mpvs the property values to be bound (can be modified)
210: * @see org.springframework.web.multipart.MultipartFile
211: * @see #setBindEmptyMultipartFiles
212: */
213: protected void bindMultipartFiles(Map multipartFiles,
214: MutablePropertyValues mpvs) {
215: for (Iterator it = multipartFiles.entrySet().iterator(); it
216: .hasNext();) {
217: Map.Entry entry = (Map.Entry) it.next();
218: String key = (String) entry.getKey();
219: MultipartFile value = (MultipartFile) entry.getValue();
220: if (isBindEmptyMultipartFiles() || !value.isEmpty()) {
221: mpvs.addPropertyValue(key, value);
222: }
223: }
224: }
225:
226: }
|