Skip to content

Commit 4a1cc9c

Browse files
committed
Constructor-provided field values get recorded for failed binding result
Also, TypeMismatchExceptions get registered via BindingErrorProcessor. Issue: SPR-16449
1 parent 9c069f6 commit 4a1cc9c

File tree

8 files changed

+184
-78
lines changed

8 files changed

+184
-78
lines changed

spring-beans/src/main/java/org/springframework/beans/TypeMismatchException.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import java.beans.PropertyChangeEvent;
2020

2121
import org.springframework.lang.Nullable;
22+
import org.springframework.util.Assert;
2223
import org.springframework.util.ClassUtils;
2324

2425
/**
@@ -36,6 +37,9 @@ public class TypeMismatchException extends PropertyAccessException {
3637
public static final String ERROR_CODE = "typeMismatch";
3738

3839

40+
@Nullable
41+
private String propertyName;
42+
3943
@Nullable
4044
private transient Object value;
4145

@@ -69,6 +73,7 @@ public TypeMismatchException(PropertyChangeEvent propertyChangeEvent, @Nullable
6973
(propertyChangeEvent.getPropertyName() != null ?
7074
" for property '" + propertyChangeEvent.getPropertyName() + "'" : ""),
7175
cause);
76+
this.propertyName = propertyChangeEvent.getPropertyName();
7277
this.value = propertyChangeEvent.getNewValue();
7378
this.requiredType = requiredType;
7479
}
@@ -77,6 +82,7 @@ public TypeMismatchException(PropertyChangeEvent propertyChangeEvent, @Nullable
7782
* Create a new TypeMismatchException without PropertyChangeEvent.
7883
* @param value the offending value that couldn't be converted (may be {@code null})
7984
* @param requiredType the required target type (or {@code null} if not known)
85+
* @see #initPropertyName
8086
*/
8187
public TypeMismatchException(@Nullable Object value, @Nullable Class<?> requiredType) {
8288
this(value, requiredType, null);
@@ -87,6 +93,7 @@ public TypeMismatchException(@Nullable Object value, @Nullable Class<?> required
8793
* @param value the offending value that couldn't be converted (may be {@code null})
8894
* @param requiredType the required target type (or {@code null} if not known)
8995
* @param cause the root cause (may be {@code null})
96+
* @see #initPropertyName
9097
*/
9198
public TypeMismatchException(@Nullable Object value, @Nullable Class<?> requiredType, @Nullable Throwable cause) {
9299
super("Failed to convert value of type '" + ClassUtils.getDescriptiveType(value) + "'" +
@@ -97,6 +104,27 @@ public TypeMismatchException(@Nullable Object value, @Nullable Class<?> required
97104
}
98105

99106

107+
/**
108+
* Initialize this exception's property name for exposure through {@link #getPropertyName()},
109+
* as an alternative to having it initialized via a {@link PropertyChangeEvent}.
110+
* @param propertyName the property name to expose
111+
* @since 5.0.4
112+
* @see #TypeMismatchException(Object, Class)
113+
* @see #TypeMismatchException(Object, Class, Throwable)
114+
*/
115+
public void initPropertyName(String propertyName) {
116+
Assert.state(this.propertyName == null, "Property name already initialized");
117+
this.propertyName = propertyName;
118+
}
119+
120+
/**
121+
* Return the name of the affected property, if available.
122+
*/
123+
@Nullable
124+
public String getPropertyName() {
125+
return this.propertyName;
126+
}
127+
100128
/**
101129
* Return the offending value (may be {@code null}).
102130
*/

spring-context/src/main/java/org/springframework/validation/AbstractBindingResult.java

Lines changed: 50 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import java.beans.PropertyEditor;
2020
import java.io.Serializable;
2121
import java.util.Collections;
22+
import java.util.HashMap;
2223
import java.util.HashSet;
2324
import java.util.LinkedHashMap;
2425
import java.util.LinkedList;
@@ -51,6 +52,10 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi
5152

5253
private final List<ObjectError> errors = new LinkedList<>();
5354

55+
private final Map<String, Class<?>> fieldTypes = new HashMap<>(0);
56+
57+
private final Map<String, Object> fieldValues = new HashMap<>(0);
58+
5459
private final Set<String> suppressedFields = new HashSet<>();
5560

5661

@@ -63,6 +68,7 @@ protected AbstractBindingResult(String objectName) {
6368
this.objectName = objectName;
6469
}
6570

71+
6672
/**
6773
* Set the strategy to use for resolving errors into message codes.
6874
* Default is DefaultMessageCodesResolver.
@@ -90,7 +96,6 @@ public String getObjectName() {
9096
return this.objectName;
9197
}
9298

93-
9499
@Override
95100
public void reject(String errorCode, @Nullable Object[] errorArgs, @Nullable String defaultMessage) {
96101
addError(new ObjectError(getObjectName(), resolveMessageCodes(errorCode), errorArgs, defaultMessage));
@@ -115,11 +120,6 @@ public void rejectValue(@Nullable String field, String errorCode, @Nullable Obje
115120
addError(fe);
116121
}
117122

118-
@Override
119-
public void addError(ObjectError error) {
120-
this.errors.add(error);
121-
}
122-
123123
@Override
124124
public void addAllErrors(Errors errors) {
125125
if (!errors.getObjectName().equals(getObjectName())) {
@@ -128,19 +128,6 @@ public void addAllErrors(Errors errors) {
128128
this.errors.addAll(errors.getAllErrors());
129129
}
130130

131-
@Override
132-
public String[] resolveMessageCodes(String errorCode) {
133-
return getMessageCodesResolver().resolveMessageCodes(errorCode, getObjectName());
134-
}
135-
136-
@Override
137-
public String[] resolveMessageCodes(String errorCode, @Nullable String field) {
138-
Class<?> fieldType = getFieldType(field);
139-
return getMessageCodesResolver().resolveMessageCodes(
140-
errorCode, getObjectName(), fixedField(field), fieldType);
141-
}
142-
143-
144131
@Override
145132
public boolean hasErrors() {
146133
return !this.errors.isEmpty();
@@ -231,14 +218,19 @@ public FieldError getFieldError(String field) {
231218
@Nullable
232219
public Object getFieldValue(String field) {
233220
FieldError fieldError = getFieldError(field);
234-
// Use rejected value in case of error, current bean property value else.
235-
Object value = (fieldError != null ? fieldError.getRejectedValue() :
236-
getActualFieldValue(fixedField(field)));
237-
// Apply formatting, but not on binding failures like type mismatches.
238-
if (fieldError == null || !fieldError.isBindingFailure()) {
239-
value = formatFieldValue(field, value);
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() ? 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);
240233
}
241-
return value;
242234
}
243235

244236
/**
@@ -250,11 +242,13 @@ public Object getFieldValue(String field) {
250242
@Override
251243
@Nullable
252244
public Class<?> getFieldType(@Nullable String field) {
253-
Object value = getActualFieldValue(fixedField(field));
254-
if (value != null) {
255-
return value.getClass();
245+
if (getTarget() != null) {
246+
Object value = getActualFieldValue(fixedField(field));
247+
if (value != null) {
248+
return value.getClass();
249+
}
256250
}
257-
return null;
251+
return this.fieldTypes.get(field);
258252
}
259253

260254

@@ -287,7 +281,7 @@ public Map<String, Object> getModel() {
287281
@Override
288282
@Nullable
289283
public Object getRawFieldValue(String field) {
290-
return getActualFieldValue(fixedField(field));
284+
return (getTarget() != null ? getActualFieldValue(fixedField(field)) : null);
291285
}
292286

293287
/**
@@ -320,6 +314,29 @@ public PropertyEditorRegistry getPropertyEditorRegistry() {
320314
return null;
321315
}
322316

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+
Class<?> fieldType = (getTarget() != null ? getFieldType(field) : null);
325+
return getMessageCodesResolver().resolveMessageCodes(
326+
errorCode, getObjectName(), fixedField(field), fieldType);
327+
}
328+
329+
@Override
330+
public void addError(ObjectError error) {
331+
this.errors.add(error);
332+
}
333+
334+
@Override
335+
public void recordFieldValue(String field, Class<?> type, Object value) {
336+
this.fieldTypes.put(field, type);
337+
this.fieldValues.put(field, value);
338+
}
339+
323340
/**
324341
* Mark the specified disallowed field as suppressed.
325342
* <p>The data binder invokes this for each field value that was
@@ -333,7 +350,7 @@ public void recordSuppressedField(String field) {
333350

334351
/**
335352
* Return the list of fields that were suppressed during the bind process.
336-
* <p>Can be used to determine whether any field values were targetting
353+
* <p>Can be used to determine whether any field values were targeting
337354
* disallowed fields.
338355
* @see DataBinder#setAllowedFields
339356
*/

spring-context/src/main/java/org/springframework/validation/AbstractPropertyBindingResult.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -71,7 +71,7 @@ public void initConversion(ConversionService conversionService) {
7171
*/
7272
@Override
7373
public PropertyEditorRegistry getPropertyEditorRegistry() {
74-
return getPropertyAccessor();
74+
return (getTarget() != null ? getPropertyAccessor() : null);
7575
}
7676

7777
/**
@@ -90,7 +90,8 @@ protected String canonicalFieldName(String field) {
9090
@Override
9191
@Nullable
9292
public Class<?> getFieldType(@Nullable String field) {
93-
return getPropertyAccessor().getPropertyType(fixedField(field));
93+
return (getTarget() != null ? getPropertyAccessor().getPropertyType(fixedField(field)) :
94+
super.getFieldType(field));
9495
}
9596

9697
/**
@@ -161,7 +162,7 @@ public PropertyEditor findEditor(@Nullable String field, @Nullable Class<?> valu
161162
PropertyEditor editor = super.findEditor(field, valueTypeForLookup);
162163
if (editor == null && this.conversionService != null) {
163164
TypeDescriptor td = null;
164-
if (field != null) {
165+
if (field != null && getTarget() != null) {
165166
TypeDescriptor ptd = getPropertyAccessor().getPropertyTypeDescriptor(fixedField(field));
166167
if (ptd != null && (valueType == null || valueType.isAssignableFrom(ptd.getType()))) {
167168
td = ptd;

spring-context/src/main/java/org/springframework/validation/BindException.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -259,11 +259,6 @@ public PropertyEditorRegistry getPropertyEditorRegistry() {
259259
return this.bindingResult.getPropertyEditorRegistry();
260260
}
261261

262-
@Override
263-
public void addError(ObjectError error) {
264-
this.bindingResult.addError(error);
265-
}
266-
267262
@Override
268263
public String[] resolveMessageCodes(String errorCode) {
269264
return this.bindingResult.resolveMessageCodes(errorCode);
@@ -274,6 +269,16 @@ public String[] resolveMessageCodes(String errorCode, String field) {
274269
return this.bindingResult.resolveMessageCodes(errorCode, field);
275270
}
276271

272+
@Override
273+
public void addError(ObjectError error) {
274+
this.bindingResult.addError(error);
275+
}
276+
277+
@Override
278+
public void recordFieldValue(String field, Class<?> type, Object value) {
279+
this.bindingResult.recordFieldValue(field, type, value);
280+
}
281+
277282
@Override
278283
public void recordSuppressedField(String field) {
279284
this.bindingResult.recordSuppressedField(field);

spring-context/src/main/java/org/springframework/validation/BindingResult.java

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -82,8 +82,7 @@ public interface BindingResult extends Errors {
8282
* Extract the raw field value for the given field.
8383
* Typically used for comparison purposes.
8484
* @param field the field to check
85-
* @return the current value of the field in its raw form,
86-
* or {@code null} if not known
85+
* @return the current value of the field in its raw form, or {@code null} if not known
8786
*/
8887
@Nullable
8988
Object getRawFieldValue(String field);
@@ -107,15 +106,6 @@ public interface BindingResult extends Errors {
107106
@Nullable
108107
PropertyEditorRegistry getPropertyEditorRegistry();
109108

110-
/**
111-
* Add a custom {@link ObjectError} or {@link FieldError} to the errors list.
112-
* <p>Intended to be used by cooperating strategies such as {@link BindingErrorProcessor}.
113-
* @see ObjectError
114-
* @see FieldError
115-
* @see BindingErrorProcessor
116-
*/
117-
void addError(ObjectError error);
118-
119109
/**
120110
* Resolve the given error code into message codes.
121111
* <p>Calls the configured {@link MessageCodesResolver} with appropriate parameters.
@@ -133,20 +123,46 @@ public interface BindingResult extends Errors {
133123
*/
134124
String[] resolveMessageCodes(String errorCode, String field);
135125

126+
/**
127+
* Add a custom {@link ObjectError} or {@link FieldError} to the errors list.
128+
* <p>Intended to be used by cooperating strategies such as {@link BindingErrorProcessor}.
129+
* @see ObjectError
130+
* @see FieldError
131+
* @see BindingErrorProcessor
132+
*/
133+
void addError(ObjectError error);
134+
135+
/**
136+
* Record the given value for the specified field.
137+
* <p>To be used when a target object cannot be constructed, making
138+
* the original field values available through {@link #getFieldValue}.
139+
* In case of a registered error, the rejected value will be exposed
140+
* for each affected field.
141+
* @param field the field to record the value for
142+
* @param type the type of the field
143+
* @param value the original value
144+
* @since 5.0.4
145+
*/
146+
default void recordFieldValue(String field, Class<?> type, Object value) {
147+
}
148+
136149
/**
137150
* Mark the specified disallowed field as suppressed.
138151
* <p>The data binder invokes this for each field value that was
139152
* detected to target a disallowed field.
140153
* @see DataBinder#setAllowedFields
141154
*/
142-
void recordSuppressedField(String field);
155+
default void recordSuppressedField(String field) {
156+
}
143157

144158
/**
145159
* Return the list of fields that were suppressed during the bind process.
146160
* <p>Can be used to determine whether any field values were targeting
147161
* disallowed fields.
148162
* @see DataBinder#setAllowedFields
149163
*/
150-
String[] getSuppressedFields();
164+
default String[] getSuppressedFields() {
165+
return new String[0];
166+
}
151167

152168
}

0 commit comments

Comments
 (0)