001/* 002 * Copyright 2002-2020 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.validation; 018 019import java.beans.PropertyEditor; 020import java.io.Serializable; 021import java.util.ArrayList; 022import java.util.Collections; 023import java.util.HashMap; 024import java.util.HashSet; 025import java.util.LinkedHashMap; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029 030import org.springframework.beans.PropertyEditorRegistry; 031import org.springframework.lang.Nullable; 032import org.springframework.util.Assert; 033import org.springframework.util.ObjectUtils; 034import org.springframework.util.StringUtils; 035 036/** 037 * Abstract implementation of the {@link BindingResult} interface and 038 * its super-interface {@link Errors}. Encapsulates common management of 039 * {@link ObjectError ObjectErrors} and {@link FieldError FieldErrors}. 040 * 041 * @author Juergen Hoeller 042 * @author Rob Harrop 043 * @since 2.0 044 * @see Errors 045 */ 046@SuppressWarnings("serial") 047public abstract class AbstractBindingResult extends AbstractErrors implements BindingResult, Serializable { 048 049 private final String objectName; 050 051 private MessageCodesResolver messageCodesResolver = new DefaultMessageCodesResolver(); 052 053 private final List<ObjectError> errors = new ArrayList<>(); 054 055 private final Map<String, Class<?>> fieldTypes = new HashMap<>(); 056 057 private final Map<String, Object> fieldValues = new HashMap<>(); 058 059 private final Set<String> suppressedFields = new HashSet<>(); 060 061 062 /** 063 * Create a new AbstractBindingResult instance. 064 * @param objectName the name of the target object 065 * @see DefaultMessageCodesResolver 066 */ 067 protected AbstractBindingResult(String objectName) { 068 this.objectName = objectName; 069 } 070 071 072 /** 073 * Set the strategy to use for resolving errors into message codes. 074 * Default is DefaultMessageCodesResolver. 075 * @see DefaultMessageCodesResolver 076 */ 077 public void setMessageCodesResolver(MessageCodesResolver messageCodesResolver) { 078 Assert.notNull(messageCodesResolver, "MessageCodesResolver must not be null"); 079 this.messageCodesResolver = messageCodesResolver; 080 } 081 082 /** 083 * Return the strategy to use for resolving errors into message codes. 084 */ 085 public MessageCodesResolver getMessageCodesResolver() { 086 return this.messageCodesResolver; 087 } 088 089 090 //--------------------------------------------------------------------- 091 // Implementation of the Errors interface 092 //--------------------------------------------------------------------- 093 094 @Override 095 public String getObjectName() { 096 return this.objectName; 097 } 098 099 @Override 100 public void reject(String errorCode, @Nullable Object[] errorArgs, @Nullable String defaultMessage) { 101 addError(new ObjectError(getObjectName(), resolveMessageCodes(errorCode), errorArgs, defaultMessage)); 102 } 103 104 @Override 105 public void rejectValue(@Nullable String field, String errorCode, @Nullable Object[] errorArgs, 106 @Nullable String defaultMessage) { 107 108 if (!StringUtils.hasLength(getNestedPath()) && !StringUtils.hasLength(field)) { 109 // We're at the top of the nested object hierarchy, 110 // so the present level is not a field but rather the top object. 111 // The best we can do is register a global error here... 112 reject(errorCode, errorArgs, defaultMessage); 113 return; 114 } 115 116 String fixedField = fixedField(field); 117 Object newVal = getActualFieldValue(fixedField); 118 FieldError fe = new FieldError(getObjectName(), fixedField, newVal, false, 119 resolveMessageCodes(errorCode, field), errorArgs, defaultMessage); 120 addError(fe); 121 } 122 123 @Override 124 public void addAllErrors(Errors errors) { 125 if (!errors.getObjectName().equals(getObjectName())) { 126 throw new IllegalArgumentException("Errors object needs to have same object name"); 127 } 128 this.errors.addAll(errors.getAllErrors()); 129 } 130 131 @Override 132 public boolean hasErrors() { 133 return !this.errors.isEmpty(); 134 } 135 136 @Override 137 public int getErrorCount() { 138 return this.errors.size(); 139 } 140 141 @Override 142 public List<ObjectError> getAllErrors() { 143 return Collections.unmodifiableList(this.errors); 144 } 145 146 @Override 147 public List<ObjectError> getGlobalErrors() { 148 List<ObjectError> result = new ArrayList<>(); 149 for (ObjectError objectError : this.errors) { 150 if (!(objectError instanceof FieldError)) { 151 result.add(objectError); 152 } 153 } 154 return Collections.unmodifiableList(result); 155 } 156 157 @Override 158 @Nullable 159 public ObjectError getGlobalError() { 160 for (ObjectError objectError : this.errors) { 161 if (!(objectError instanceof FieldError)) { 162 return objectError; 163 } 164 } 165 return null; 166 } 167 168 @Override 169 public List<FieldError> getFieldErrors() { 170 List<FieldError> result = new ArrayList<>(); 171 for (ObjectError objectError : this.errors) { 172 if (objectError instanceof FieldError) { 173 result.add((FieldError) objectError); 174 } 175 } 176 return Collections.unmodifiableList(result); 177 } 178 179 @Override 180 @Nullable 181 public FieldError getFieldError() { 182 for (ObjectError objectError : this.errors) { 183 if (objectError instanceof FieldError) { 184 return (FieldError) objectError; 185 } 186 } 187 return null; 188 } 189 190 @Override 191 public List<FieldError> getFieldErrors(String field) { 192 List<FieldError> result = new ArrayList<>(); 193 String fixedField = fixedField(field); 194 for (ObjectError objectError : this.errors) { 195 if (objectError instanceof FieldError && isMatchingFieldError(fixedField, (FieldError) objectError)) { 196 result.add((FieldError) objectError); 197 } 198 } 199 return Collections.unmodifiableList(result); 200 } 201 202 @Override 203 @Nullable 204 public FieldError getFieldError(String field) { 205 String fixedField = fixedField(field); 206 for (ObjectError objectError : this.errors) { 207 if (objectError instanceof FieldError) { 208 FieldError fieldError = (FieldError) objectError; 209 if (isMatchingFieldError(fixedField, fieldError)) { 210 return fieldError; 211 } 212 } 213 } 214 return null; 215 } 216 217 @Override 218 @Nullable 219 public Object getFieldValue(String field) { 220 FieldError fieldError = getFieldError(field); 221 // Use rejected value in case of error, current field value otherwise. 222 if (fieldError != null) { 223 Object value = fieldError.getRejectedValue(); 224 // Do not apply formatting on binding failures like type mismatches. 225 return (fieldError.isBindingFailure() || getTarget() == null ? value : formatFieldValue(field, value)); 226 } 227 else if (getTarget() != null) { 228 Object value = getActualFieldValue(fixedField(field)); 229 return formatFieldValue(field, value); 230 } 231 else { 232 return this.fieldValues.get(field); 233 } 234 } 235 236 /** 237 * This default implementation determines the type based on the actual 238 * field value, if any. Subclasses should override this to determine 239 * the type from a descriptor, even for {@code null} values. 240 * @see #getActualFieldValue 241 */ 242 @Override 243 @Nullable 244 public Class<?> getFieldType(@Nullable String field) { 245 if (getTarget() != null) { 246 Object value = getActualFieldValue(fixedField(field)); 247 if (value != null) { 248 return value.getClass(); 249 } 250 } 251 return this.fieldTypes.get(field); 252 } 253 254 255 //--------------------------------------------------------------------- 256 // Implementation of BindingResult interface 257 //--------------------------------------------------------------------- 258 259 /** 260 * Return a model Map for the obtained state, exposing an Errors 261 * instance as '{@link #MODEL_KEY_PREFIX MODEL_KEY_PREFIX} + objectName' 262 * and the object itself. 263 * <p>Note that the Map is constructed every time you're calling this method. 264 * Adding things to the map and then re-calling this method will not work. 265 * <p>The attributes in the model Map returned by this method are usually 266 * included in the ModelAndView for a form view that uses Spring's bind tag, 267 * which needs access to the Errors instance. 268 * @see #getObjectName 269 * @see #MODEL_KEY_PREFIX 270 */ 271 @Override 272 public Map<String, Object> getModel() { 273 Map<String, Object> model = new LinkedHashMap<>(2); 274 // Mapping from name to target object. 275 model.put(getObjectName(), getTarget()); 276 // Errors instance, even if no errors. 277 model.put(MODEL_KEY_PREFIX + getObjectName(), this); 278 return model; 279 } 280 281 @Override 282 @Nullable 283 public Object getRawFieldValue(String field) { 284 return (getTarget() != null ? getActualFieldValue(fixedField(field)) : null); 285 } 286 287 /** 288 * This implementation delegates to the 289 * {@link #getPropertyEditorRegistry() PropertyEditorRegistry}'s 290 * editor lookup facility, if available. 291 */ 292 @Override 293 @Nullable 294 public PropertyEditor findEditor(@Nullable String field, @Nullable Class<?> valueType) { 295 PropertyEditorRegistry editorRegistry = getPropertyEditorRegistry(); 296 if (editorRegistry != null) { 297 Class<?> valueTypeToUse = valueType; 298 if (valueTypeToUse == null) { 299 valueTypeToUse = getFieldType(field); 300 } 301 return editorRegistry.findCustomEditor(valueTypeToUse, fixedField(field)); 302 } 303 else { 304 return null; 305 } 306 } 307 308 /** 309 * This implementation returns {@code null}. 310 */ 311 @Override 312 @Nullable 313 public PropertyEditorRegistry getPropertyEditorRegistry() { 314 return null; 315 } 316 317 @Override 318 public String[] resolveMessageCodes(String errorCode) { 319 return getMessageCodesResolver().resolveMessageCodes(errorCode, getObjectName()); 320 } 321 322 @Override 323 public String[] resolveMessageCodes(String errorCode, @Nullable String field) { 324 return getMessageCodesResolver().resolveMessageCodes( 325 errorCode, getObjectName(), fixedField(field), getFieldType(field)); 326 } 327 328 @Override 329 public void addError(ObjectError error) { 330 this.errors.add(error); 331 } 332 333 @Override 334 public void recordFieldValue(String field, Class<?> type, @Nullable Object value) { 335 this.fieldTypes.put(field, type); 336 this.fieldValues.put(field, value); 337 } 338 339 /** 340 * Mark the specified disallowed field as suppressed. 341 * <p>The data binder invokes this for each field value that was 342 * detected to target a disallowed field. 343 * @see DataBinder#setAllowedFields 344 */ 345 @Override 346 public void recordSuppressedField(String field) { 347 this.suppressedFields.add(field); 348 } 349 350 /** 351 * Return the list of fields that were suppressed during the bind process. 352 * <p>Can be used to determine whether any field values were targeting 353 * disallowed fields. 354 * @see DataBinder#setAllowedFields 355 */ 356 @Override 357 public String[] getSuppressedFields() { 358 return StringUtils.toStringArray(this.suppressedFields); 359 } 360 361 362 @Override 363 public boolean equals(@Nullable Object other) { 364 if (this == other) { 365 return true; 366 } 367 if (!(other instanceof BindingResult)) { 368 return false; 369 } 370 BindingResult otherResult = (BindingResult) other; 371 return (getObjectName().equals(otherResult.getObjectName()) && 372 ObjectUtils.nullSafeEquals(getTarget(), otherResult.getTarget()) && 373 getAllErrors().equals(otherResult.getAllErrors())); 374 } 375 376 @Override 377 public int hashCode() { 378 return getObjectName().hashCode(); 379 } 380 381 382 //--------------------------------------------------------------------- 383 // Template methods to be implemented/overridden by subclasses 384 //--------------------------------------------------------------------- 385 386 /** 387 * Return the wrapped target object. 388 */ 389 @Override 390 @Nullable 391 public abstract Object getTarget(); 392 393 /** 394 * Extract the actual field value for the given field. 395 * @param field the field to check 396 * @return the current value of the field 397 */ 398 @Nullable 399 protected abstract Object getActualFieldValue(String field); 400 401 /** 402 * Format the given value for the specified field. 403 * <p>The default implementation simply returns the field value as-is. 404 * @param field the field to check 405 * @param value the value of the field (either a rejected value 406 * other than from a binding error, or an actual field value) 407 * @return the formatted value 408 */ 409 @Nullable 410 protected Object formatFieldValue(String field, @Nullable Object value) { 411 return value; 412 } 413 414}