001/*
002 * Copyright 2002-2018 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 *      https://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
017package org.springframework.web.bind;
018
019import java.lang.reflect.Array;
020import java.util.Collection;
021import java.util.List;
022import java.util.Map;
023
024import org.springframework.beans.MutablePropertyValues;
025import org.springframework.beans.PropertyValue;
026import org.springframework.core.CollectionFactory;
027import org.springframework.lang.Nullable;
028import org.springframework.validation.DataBinder;
029import org.springframework.web.multipart.MultipartFile;
030
031/**
032 * Special {@link DataBinder} for data binding from web request parameters
033 * to JavaBean objects. Designed for web environments, but not dependent on
034 * the Servlet API; serves as base class for more specific DataBinder variants,
035 * such as {@link org.springframework.web.bind.ServletRequestDataBinder}.
036 *
037 * <p>Includes support for field markers which address a common problem with
038 * HTML checkboxes and select options: detecting that a field was part of
039 * the form, but did not generate a request parameter because it was empty.
040 * A field marker allows to detect that state and reset the corresponding
041 * bean property accordingly. Default values, for parameters that are otherwise
042 * not present, can specify a value for the field other then empty.
043 *
044 * @author Juergen Hoeller
045 * @author Scott Andrews
046 * @author Brian Clozel
047 * @since 1.2
048 * @see #registerCustomEditor
049 * @see #setAllowedFields
050 * @see #setRequiredFields
051 * @see #setFieldMarkerPrefix
052 * @see #setFieldDefaultPrefix
053 * @see ServletRequestDataBinder
054 */
055public class WebDataBinder extends DataBinder {
056
057        /**
058         * Default prefix that field marker parameters start with, followed by the field
059         * name: e.g. "_subscribeToNewsletter" for a field "subscribeToNewsletter".
060         * <p>Such a marker parameter indicates that the field was visible, that is,
061         * existed in the form that caused the submission. If no corresponding field
062         * value parameter was found, the field will be reset. The value of the field
063         * marker parameter does not matter in this case; an arbitrary value can be used.
064         * This is particularly useful for HTML checkboxes and select options.
065         * @see #setFieldMarkerPrefix
066         */
067        public static final String DEFAULT_FIELD_MARKER_PREFIX = "_";
068
069        /**
070         * Default prefix that field default parameters start with, followed by the field
071         * name: e.g. "!subscribeToNewsletter" for a field "subscribeToNewsletter".
072         * <p>Default parameters differ from field markers in that they provide a default
073         * value instead of an empty value.
074         * @see #setFieldDefaultPrefix
075         */
076        public static final String DEFAULT_FIELD_DEFAULT_PREFIX = "!";
077
078        @Nullable
079        private String fieldMarkerPrefix = DEFAULT_FIELD_MARKER_PREFIX;
080
081        @Nullable
082        private String fieldDefaultPrefix = DEFAULT_FIELD_DEFAULT_PREFIX;
083
084        private boolean bindEmptyMultipartFiles = true;
085
086
087        /**
088         * Create a new WebDataBinder instance, with default object name.
089         * @param target the target object to bind onto (or {@code null}
090         * if the binder is just used to convert a plain parameter value)
091         * @see #DEFAULT_OBJECT_NAME
092         */
093        public WebDataBinder(@Nullable Object target) {
094                super(target);
095        }
096
097        /**
098         * Create a new WebDataBinder instance.
099         * @param target the target object to bind onto (or {@code null}
100         * if the binder is just used to convert a plain parameter value)
101         * @param objectName the name of the target object
102         */
103        public WebDataBinder(@Nullable Object target, String objectName) {
104                super(target, objectName);
105        }
106
107
108        /**
109         * Specify a prefix that can be used for parameters that mark potentially
110         * empty fields, having "prefix + field" as name. Such a marker parameter is
111         * checked by existence: You can send any value for it, for example "visible".
112         * This is particularly useful for HTML checkboxes and select options.
113         * <p>Default is "_", for "_FIELD" parameters (e.g. "_subscribeToNewsletter").
114         * Set this to null if you want to turn off the empty field check completely.
115         * <p>HTML checkboxes only send a value when they're checked, so it is not
116         * possible to detect that a formerly checked box has just been unchecked,
117         * at least not with standard HTML means.
118         * <p>One way to address this is to look for a checkbox parameter value if
119         * you know that the checkbox has been visible in the form, resetting the
120         * checkbox if no value found. In Spring web MVC, this typically happens
121         * in a custom {@code onBind} implementation.
122         * <p>This auto-reset mechanism addresses this deficiency, provided
123         * that a marker parameter is sent for each checkbox field, like
124         * "_subscribeToNewsletter" for a "subscribeToNewsletter" field.
125         * As the marker parameter is sent in any case, the data binder can
126         * detect an empty field and automatically reset its value.
127         * @see #DEFAULT_FIELD_MARKER_PREFIX
128         */
129        public void setFieldMarkerPrefix(@Nullable String fieldMarkerPrefix) {
130                this.fieldMarkerPrefix = fieldMarkerPrefix;
131        }
132
133        /**
134         * Return the prefix for parameters that mark potentially empty fields.
135         */
136        @Nullable
137        public String getFieldMarkerPrefix() {
138                return this.fieldMarkerPrefix;
139        }
140
141        /**
142         * Specify a prefix that can be used for parameters that indicate default
143         * value fields, having "prefix + field" as name. The value of the default
144         * field is used when the field is not provided.
145         * <p>Default is "!", for "!FIELD" parameters (e.g. "!subscribeToNewsletter").
146         * Set this to null if you want to turn off the field defaults completely.
147         * <p>HTML checkboxes only send a value when they're checked, so it is not
148         * possible to detect that a formerly checked box has just been unchecked,
149         * at least not with standard HTML means.  A default field is especially
150         * useful when a checkbox represents a non-boolean value.
151         * <p>The presence of a default parameter preempts the behavior of a field
152         * marker for the given field.
153         * @see #DEFAULT_FIELD_DEFAULT_PREFIX
154         */
155        public void setFieldDefaultPrefix(@Nullable String fieldDefaultPrefix) {
156                this.fieldDefaultPrefix = fieldDefaultPrefix;
157        }
158
159        /**
160         * Return the prefix for parameters that mark default fields.
161         */
162        @Nullable
163        public String getFieldDefaultPrefix() {
164                return this.fieldDefaultPrefix;
165        }
166
167        /**
168         * Set whether to bind empty MultipartFile parameters. Default is "true".
169         * <p>Turn this off if you want to keep an already bound MultipartFile
170         * when the user resubmits the form without choosing a different file.
171         * Else, the already bound MultipartFile will be replaced by an empty
172         * MultipartFile holder.
173         * @see org.springframework.web.multipart.MultipartFile
174         */
175        public void setBindEmptyMultipartFiles(boolean bindEmptyMultipartFiles) {
176                this.bindEmptyMultipartFiles = bindEmptyMultipartFiles;
177        }
178
179        /**
180         * Return whether to bind empty MultipartFile parameters.
181         */
182        public boolean isBindEmptyMultipartFiles() {
183                return this.bindEmptyMultipartFiles;
184        }
185
186
187        /**
188         * This implementation performs a field default and marker check
189         * before delegating to the superclass binding process.
190         * @see #checkFieldDefaults
191         * @see #checkFieldMarkers
192         */
193        @Override
194        protected void doBind(MutablePropertyValues mpvs) {
195                checkFieldDefaults(mpvs);
196                checkFieldMarkers(mpvs);
197                super.doBind(mpvs);
198        }
199
200        /**
201         * Check the given property values for field defaults,
202         * i.e. for fields that start with the field default prefix.
203         * <p>The existence of a field defaults indicates that the specified
204         * value should be used if the field is otherwise not present.
205         * @param mpvs the property values to be bound (can be modified)
206         * @see #getFieldDefaultPrefix
207         */
208        protected void checkFieldDefaults(MutablePropertyValues mpvs) {
209                String fieldDefaultPrefix = getFieldDefaultPrefix();
210                if (fieldDefaultPrefix != null) {
211                        PropertyValue[] pvArray = mpvs.getPropertyValues();
212                        for (PropertyValue pv : pvArray) {
213                                if (pv.getName().startsWith(fieldDefaultPrefix)) {
214                                        String field = pv.getName().substring(fieldDefaultPrefix.length());
215                                        if (getPropertyAccessor().isWritableProperty(field) && !mpvs.contains(field)) {
216                                                mpvs.add(field, pv.getValue());
217                                        }
218                                        mpvs.removePropertyValue(pv);
219                                }
220                        }
221                }
222        }
223
224        /**
225         * Check the given property values for field markers,
226         * i.e. for fields that start with the field marker prefix.
227         * <p>The existence of a field marker indicates that the specified
228         * field existed in the form. If the property values do not contain
229         * a corresponding field value, the field will be considered as empty
230         * and will be reset appropriately.
231         * @param mpvs the property values to be bound (can be modified)
232         * @see #getFieldMarkerPrefix
233         * @see #getEmptyValue(String, Class)
234         */
235        protected void checkFieldMarkers(MutablePropertyValues mpvs) {
236                String fieldMarkerPrefix = getFieldMarkerPrefix();
237                if (fieldMarkerPrefix != null) {
238                        PropertyValue[] pvArray = mpvs.getPropertyValues();
239                        for (PropertyValue pv : pvArray) {
240                                if (pv.getName().startsWith(fieldMarkerPrefix)) {
241                                        String field = pv.getName().substring(fieldMarkerPrefix.length());
242                                        if (getPropertyAccessor().isWritableProperty(field) && !mpvs.contains(field)) {
243                                                Class<?> fieldType = getPropertyAccessor().getPropertyType(field);
244                                                mpvs.add(field, getEmptyValue(field, fieldType));
245                                        }
246                                        mpvs.removePropertyValue(pv);
247                                }
248                        }
249                }
250        }
251
252        /**
253         * Determine an empty value for the specified field.
254         * <p>The default implementation delegates to {@link #getEmptyValue(Class)}
255         * if the field type is known, otherwise falls back to {@code null}.
256         * @param field the name of the field
257         * @param fieldType the type of the field
258         * @return the empty value (for most fields: {@code null})
259         */
260        @Nullable
261        protected Object getEmptyValue(String field, @Nullable Class<?> fieldType) {
262                return (fieldType != null ? getEmptyValue(fieldType) : null);
263        }
264
265        /**
266         * Determine an empty value for the specified field.
267         * <p>The default implementation returns:
268         * <ul>
269         * <li>{@code Boolean.FALSE} for boolean fields
270         * <li>an empty array for array types
271         * <li>Collection implementations for Collection types
272         * <li>Map implementations for Map types
273         * <li>else, {@code null} is used as default
274         * </ul>
275         * @param fieldType the type of the field
276         * @return the empty value (for most fields: {@code null})
277         * @since 5.0
278         */
279        @Nullable
280        public Object getEmptyValue(Class<?> fieldType) {
281                try {
282                        if (boolean.class == fieldType || Boolean.class == fieldType) {
283                                // Special handling of boolean property.
284                                return Boolean.FALSE;
285                        }
286                        else if (fieldType.isArray()) {
287                                // Special handling of array property.
288                                return Array.newInstance(fieldType.getComponentType(), 0);
289                        }
290                        else if (Collection.class.isAssignableFrom(fieldType)) {
291                                return CollectionFactory.createCollection(fieldType, 0);
292                        }
293                        else if (Map.class.isAssignableFrom(fieldType)) {
294                                return CollectionFactory.createMap(fieldType, 0);
295                        }
296                }
297                catch (IllegalArgumentException ex) {
298                        if (logger.isDebugEnabled()) {
299                                logger.debug("Failed to create default value - falling back to null: " + ex.getMessage());
300                        }
301                }
302                // Default value: null.
303                return null;
304        }
305
306
307        /**
308         * Bind all multipart files contained in the given request, if any
309         * (in case of a multipart request). To be called by subclasses.
310         * <p>Multipart files will only be added to the property values if they
311         * are not empty or if we're configured to bind empty multipart files too.
312         * @param multipartFiles a Map of field name String to MultipartFile object
313         * @param mpvs the property values to be bound (can be modified)
314         * @see org.springframework.web.multipart.MultipartFile
315         * @see #setBindEmptyMultipartFiles
316         */
317        protected void bindMultipart(Map<String, List<MultipartFile>> multipartFiles, MutablePropertyValues mpvs) {
318                multipartFiles.forEach((key, values) -> {
319                        if (values.size() == 1) {
320                                MultipartFile value = values.get(0);
321                                if (isBindEmptyMultipartFiles() || !value.isEmpty()) {
322                                        mpvs.add(key, value);
323                                }
324                        }
325                        else {
326                                mpvs.add(key, values);
327                        }
328                });
329        }
330
331}