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