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}