-
Notifications
You must be signed in to change notification settings - Fork 37.7k
/
DataBinder.java
1323 lines (1190 loc) · 47.5 KB
/
DataBinder.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.validation;
import java.beans.PropertyEditor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.ConfigurablePropertyAccessor;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyAccessException;
import org.springframework.beans.PropertyAccessorUtils;
import org.springframework.beans.PropertyBatchUpdateException;
import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.TypeMismatchException;
import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.format.Formatter;
import org.springframework.format.support.FormatterPropertyEditorAdapter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.ValidationAnnotationUtils;
/**
* Binder that allows applying property values to a target object via constructor
* and setter injection, and also supports validation and binding result analysis.
*
* <p>The binding process can be customized by specifying allowed field patterns,
* required fields, custom editors, etc.
*
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
* parts of the object graph that are not meant to be accessed or modified by
* external clients. Therefore, the design and use of data binding should be considered
* carefully with regard to security. For more details, please refer to the dedicated
* sections on data binding for
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
* in the reference manual.
*
* <p>The binding results can be examined via the {@link BindingResult} interface,
* extending the {@link Errors} interface: see the {@link #getBindingResult()} method.
* Missing fields and property access exceptions will be converted to {@link FieldError FieldErrors},
* collected in the Errors instance, using the following error codes:
*
* <ul>
* <li>Missing field error: "required"
* <li>Type mismatch error: "typeMismatch"
* <li>Method invocation error: "methodInvocation"
* </ul>
*
* <p>By default, binding errors get resolved through the {@link BindingErrorProcessor}
* strategy, processing for missing fields and property access exceptions: see the
* {@link #setBindingErrorProcessor} method. You can override the default strategy
* if needed, for example to generate different error codes.
*
* <p>Custom validation errors can be added afterwards. You will typically want to resolve
* such error codes into proper user-visible error messages; this can be achieved through
* resolving each error via a {@link org.springframework.context.MessageSource}, which is
* able to resolve an {@link ObjectError}/{@link FieldError} through its
* {@link org.springframework.context.MessageSource#getMessage(org.springframework.context.MessageSourceResolvable, java.util.Locale)}
* method. The list of message codes can be customized through the {@link MessageCodesResolver}
* strategy: see the {@link #setMessageCodesResolver} method. {@link DefaultMessageCodesResolver}'s
* javadoc states details on the default resolution rules.
*
* <p>This generic data binder can be used in any kind of environment.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Rob Harrop
* @author Stephane Nicoll
* @author Kazuki Shimizu
* @author Sam Brannen
* @see #setAllowedFields
* @see #setRequiredFields
* @see #registerCustomEditor
* @see #setMessageCodesResolver
* @see #setBindingErrorProcessor
* @see #construct
* @see #bind
* @see #getBindingResult
* @see DefaultMessageCodesResolver
* @see DefaultBindingErrorProcessor
* @see org.springframework.context.MessageSource
*/
public class DataBinder implements PropertyEditorRegistry, TypeConverter {
/** Default object name used for binding: "target". */
public static final String DEFAULT_OBJECT_NAME = "target";
/** Default limit for array and collection growing: 256. */
public static final int DEFAULT_AUTO_GROW_COLLECTION_LIMIT = 256;
/**
* We'll create a lot of DataBinder instances: Let's use a static logger.
*/
protected static final Log logger = LogFactory.getLog(DataBinder.class);
@Nullable
private Object target;
@Nullable
ResolvableType targetType;
private final String objectName;
@Nullable
private AbstractPropertyBindingResult bindingResult;
private boolean directFieldAccess = false;
@Nullable
private ExtendedTypeConverter typeConverter;
private boolean declarativeBinding = false;
private boolean ignoreUnknownFields = true;
private boolean ignoreInvalidFields = false;
private boolean autoGrowNestedPaths = true;
private int autoGrowCollectionLimit = DEFAULT_AUTO_GROW_COLLECTION_LIMIT;
@Nullable
private String[] allowedFields;
@Nullable
private String[] disallowedFields;
@Nullable
private String[] requiredFields;
@Nullable
private NameResolver nameResolver;
@Nullable
private ConversionService conversionService;
@Nullable
private MessageCodesResolver messageCodesResolver;
private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor();
private final List<Validator> validators = new ArrayList<>();
@Nullable
private Predicate<Validator> excludedValidators;
/**
* Create a new DataBinder instance, with default object name.
* @param target the target object to bind onto (or {@code null}
* if the binder is just used to convert a plain parameter value)
* @see #DEFAULT_OBJECT_NAME
*/
public DataBinder(@Nullable Object target) {
this(target, DEFAULT_OBJECT_NAME);
}
/**
* Create a new DataBinder instance.
* @param target the target object to bind onto (or {@code null}
* if the binder is just used to convert a plain parameter value)
* @param objectName the name of the target object
*/
public DataBinder(@Nullable Object target, String objectName) {
this.target = ObjectUtils.unwrapOptional(target);
this.objectName = objectName;
}
/**
* Return the wrapped target object.
* <p>If the target object is {@code null} and {@link #getTargetType()} is set,
* then {@link #construct(ValueResolver)} may be called to create the target.
*/
@Nullable
public Object getTarget() {
return this.target;
}
/**
* Return the name of the bound object.
*/
public String getObjectName() {
return this.objectName;
}
/**
* Set the type for the target object. When the target is {@code null},
* setting the targetType allows using {@link #construct} to create the target.
* @param targetType the type of the target object
* @since 6.1
* @see #construct
*/
public void setTargetType(ResolvableType targetType) {
Assert.state(this.target == null, "targetType is used to for target creation but target is already set");
this.targetType = targetType;
}
/**
* Return the {@link #setTargetType configured} type for the target object.
* @since 6.1
*/
@Nullable
public ResolvableType getTargetType() {
return this.targetType;
}
/**
* Set whether this binder should attempt to "auto-grow" a nested path that contains a null value.
* <p>If "true", a null path location will be populated with a default object value and traversed
* instead of resulting in an exception. This flag also enables auto-growth of collection elements
* when accessing an out-of-bounds index.
* <p>Default is "true" on a standard DataBinder. Note that since Spring 4.1 this feature is supported
* for bean property access (DataBinder's default mode) and field access.
* <p>Used for setter/field injection via {@link #bind(PropertyValues)}, and not
* applicable to constructor binding via {@link #construct}.
* @see #initBeanPropertyAccess()
* @see org.springframework.beans.BeanWrapper#setAutoGrowNestedPaths
*/
public void setAutoGrowNestedPaths(boolean autoGrowNestedPaths) {
Assert.state(this.bindingResult == null,
"DataBinder is already initialized - call setAutoGrowNestedPaths before other configuration methods");
this.autoGrowNestedPaths = autoGrowNestedPaths;
}
/**
* Return whether "auto-growing" of nested paths has been activated.
*/
public boolean isAutoGrowNestedPaths() {
return this.autoGrowNestedPaths;
}
/**
* Specify the limit for array and collection auto-growing.
* <p>Default is 256, preventing OutOfMemoryErrors in case of large indexes.
* Raise this limit if your auto-growing needs are unusually high.
* <p>Used for setter/field injection via {@link #bind(PropertyValues)}, and not
* applicable to constructor binding via {@link #construct}.
* @see #initBeanPropertyAccess()
* @see org.springframework.beans.BeanWrapper#setAutoGrowCollectionLimit
*/
public void setAutoGrowCollectionLimit(int autoGrowCollectionLimit) {
Assert.state(this.bindingResult == null,
"DataBinder is already initialized - call setAutoGrowCollectionLimit before other configuration methods");
this.autoGrowCollectionLimit = autoGrowCollectionLimit;
}
/**
* Return the current limit for array and collection auto-growing.
*/
public int getAutoGrowCollectionLimit() {
return this.autoGrowCollectionLimit;
}
/**
* Initialize standard JavaBean property access for this DataBinder.
* <p>This is the default; an explicit call just leads to eager initialization.
* @see #initDirectFieldAccess()
* @see #createBeanPropertyBindingResult()
*/
public void initBeanPropertyAccess() {
Assert.state(this.bindingResult == null,
"DataBinder is already initialized - call initBeanPropertyAccess before other configuration methods");
this.directFieldAccess = false;
}
/**
* Create the {@link AbstractPropertyBindingResult} instance using standard
* JavaBean property access.
* @since 4.2.1
*/
protected AbstractPropertyBindingResult createBeanPropertyBindingResult() {
BeanPropertyBindingResult result = new BeanPropertyBindingResult(getTarget(),
getObjectName(), isAutoGrowNestedPaths(), getAutoGrowCollectionLimit());
if (this.conversionService != null) {
result.initConversion(this.conversionService);
}
if (this.messageCodesResolver != null) {
result.setMessageCodesResolver(this.messageCodesResolver);
}
return result;
}
/**
* Initialize direct field access for this DataBinder,
* as alternative to the default bean property access.
* @see #initBeanPropertyAccess()
* @see #createDirectFieldBindingResult()
*/
public void initDirectFieldAccess() {
Assert.state(this.bindingResult == null,
"DataBinder is already initialized - call initDirectFieldAccess before other configuration methods");
this.directFieldAccess = true;
}
/**
* Create the {@link AbstractPropertyBindingResult} instance using direct
* field access.
* @since 4.2.1
*/
protected AbstractPropertyBindingResult createDirectFieldBindingResult() {
DirectFieldBindingResult result = new DirectFieldBindingResult(getTarget(),
getObjectName(), isAutoGrowNestedPaths());
if (this.conversionService != null) {
result.initConversion(this.conversionService);
}
if (this.messageCodesResolver != null) {
result.setMessageCodesResolver(this.messageCodesResolver);
}
return result;
}
/**
* Return the internal BindingResult held by this DataBinder,
* as an AbstractPropertyBindingResult.
*/
protected AbstractPropertyBindingResult getInternalBindingResult() {
if (this.bindingResult == null) {
this.bindingResult = (this.directFieldAccess ?
createDirectFieldBindingResult(): createBeanPropertyBindingResult());
}
return this.bindingResult;
}
/**
* Return the underlying PropertyAccessor of this binder's BindingResult.
*/
protected ConfigurablePropertyAccessor getPropertyAccessor() {
return getInternalBindingResult().getPropertyAccessor();
}
/**
* Return this binder's underlying SimpleTypeConverter.
*/
protected SimpleTypeConverter getSimpleTypeConverter() {
if (this.typeConverter == null) {
this.typeConverter = new ExtendedTypeConverter();
if (this.conversionService != null) {
this.typeConverter.setConversionService(this.conversionService);
}
}
return this.typeConverter;
}
/**
* Return the underlying TypeConverter of this binder's BindingResult.
*/
protected PropertyEditorRegistry getPropertyEditorRegistry() {
if (getTarget() != null) {
return getInternalBindingResult().getPropertyAccessor();
}
else {
return getSimpleTypeConverter();
}
}
/**
* Return the underlying TypeConverter of this binder's BindingResult.
*/
protected TypeConverter getTypeConverter() {
if (getTarget() != null) {
return getInternalBindingResult().getPropertyAccessor();
}
else {
return getSimpleTypeConverter();
}
}
/**
* Return the BindingResult instance created by this DataBinder.
* This allows for convenient access to the binding results after
* a bind operation.
* @return the BindingResult instance, to be treated as BindingResult
* or as Errors instance (Errors is a super-interface of BindingResult)
* @see Errors
* @see #bind
*/
public BindingResult getBindingResult() {
return getInternalBindingResult();
}
/**
* Set whether to bind only fields explicitly intended for binding including:
* <ul>
* <li>Constructor binding via {@link #construct}.
* <li>Property binding with configured
* {@link #setAllowedFields(String...) allowedFields}.
* </ul>
* <p>Default is "false". Turn this on to limit binding to constructor
* parameters and allowed fields.
* @since 6.1
*/
public void setDeclarativeBinding(boolean declarativeBinding) {
this.declarativeBinding = declarativeBinding;
}
/**
* Return whether to bind only fields intended for binding.
* @since 6.1
*/
public boolean isDeclarativeBinding() {
return this.declarativeBinding;
}
/**
* Set whether to ignore unknown fields, that is, whether to ignore bind
* parameters that do not have corresponding fields in the target object.
* <p>Default is "true". Turn this off to enforce that all bind parameters
* must have a matching field in the target object.
* <p>Note that this setting only applies to <i>binding</i> operations
* on this DataBinder, not to <i>retrieving</i> values via its
* {@link #getBindingResult() BindingResult}.
* <p>Used for binding to fields with {@link #bind(PropertyValues)},
* and not applicable to constructor binding via {@link #construct}
* which uses only the values it needs.
* @see #bind
*/
public void setIgnoreUnknownFields(boolean ignoreUnknownFields) {
this.ignoreUnknownFields = ignoreUnknownFields;
}
/**
* Return whether to ignore unknown fields when binding.
*/
public boolean isIgnoreUnknownFields() {
return this.ignoreUnknownFields;
}
/**
* Set whether to ignore invalid fields, that is, whether to ignore bind
* parameters that have corresponding fields in the target object which are
* not accessible (for example because of null values in the nested path).
* <p>Default is "false". Turn this on to ignore bind parameters for
* nested objects in non-existing parts of the target object graph.
* <p>Note that this setting only applies to <i>binding</i> operations
* on this DataBinder, not to <i>retrieving</i> values via its
* {@link #getBindingResult() BindingResult}.
* <p>Used for binding to fields with {@link #bind(PropertyValues)}, and not
* applicable to constructor binding via {@link #construct},
* which uses only the values it needs.
* @see #bind
*/
public void setIgnoreInvalidFields(boolean ignoreInvalidFields) {
this.ignoreInvalidFields = ignoreInvalidFields;
}
/**
* Return whether to ignore invalid fields when binding.
*/
public boolean isIgnoreInvalidFields() {
return this.ignoreInvalidFields;
}
/**
* Register field patterns that should be allowed for binding.
* <p>Default is all fields.
* <p>Restrict this for example to avoid unwanted modifications by malicious
* users when binding HTTP request parameters.
* <p>Supports {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
* well as direct equality.
* <p>The default implementation of this method stores allowed field patterns
* in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String) canonical}
* form. Subclasses which override this method must therefore take this into
* account.
* <p>More sophisticated matching can be implemented by overriding the
* {@link #isAllowed} method.
* <p>Alternatively, specify a list of <i>disallowed</i> field patterns.
* <p>Used for binding to fields with {@link #bind(PropertyValues)}, and not
* applicable to constructor binding via {@link #construct},
* which uses only the values it needs.
* @param allowedFields array of allowed field patterns
* @see #setDisallowedFields
* @see #isAllowed(String)
*/
public void setAllowedFields(@Nullable String... allowedFields) {
this.allowedFields = PropertyAccessorUtils.canonicalPropertyNames(allowedFields);
}
/**
* Return the field patterns that should be allowed for binding.
* @return array of allowed field patterns
* @see #setAllowedFields(String...)
*/
@Nullable
public String[] getAllowedFields() {
return this.allowedFields;
}
/**
* Register field patterns that should <i>not</i> be allowed for binding.
* <p>Default is none.
* <p>Mark fields as disallowed, for example to avoid unwanted
* modifications by malicious users when binding HTTP request parameters.
* <p>Supports {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
* well as direct equality.
* <p>The default implementation of this method stores disallowed field patterns
* in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String) canonical}
* form. As of Spring Framework 5.2.21, the default implementation also transforms
* disallowed field patterns to {@linkplain String#toLowerCase() lowercase} to
* support case-insensitive pattern matching in {@link #isAllowed}. Subclasses
* which override this method must therefore take both of these transformations
* into account.
* <p>More sophisticated matching can be implemented by overriding the
* {@link #isAllowed} method.
* <p>Alternatively, specify a list of <i>allowed</i> field patterns.
* <p>Used for binding to fields with {@link #bind(PropertyValues)}, and not
* applicable to constructor binding via {@link #construct},
* which uses only the values it needs.
* @param disallowedFields array of disallowed field patterns
* @see #setAllowedFields
* @see #isAllowed(String)
*/
public void setDisallowedFields(@Nullable String... disallowedFields) {
if (disallowedFields == null) {
this.disallowedFields = null;
}
else {
String[] fieldPatterns = new String[disallowedFields.length];
for (int i = 0; i < fieldPatterns.length; i++) {
fieldPatterns[i] = PropertyAccessorUtils.canonicalPropertyName(disallowedFields[i]).toLowerCase();
}
this.disallowedFields = fieldPatterns;
}
}
/**
* Return the field patterns that should <i>not</i> be allowed for binding.
* @return array of disallowed field patterns
* @see #setDisallowedFields(String...)
*/
@Nullable
public String[] getDisallowedFields() {
return this.disallowedFields;
}
/**
* Register fields that are required for each binding process.
* <p>If one of the specified fields is not contained in the list of
* incoming property values, a corresponding "missing field" error
* will be created, with error code "required" (by the default
* binding error processor).
* <p>Used for binding to fields with {@link #bind(PropertyValues)}, and not
* applicable to constructor binding via {@link #construct},
* which uses only the values it needs.
* @param requiredFields array of field names
* @see #setBindingErrorProcessor
* @see DefaultBindingErrorProcessor#MISSING_FIELD_ERROR_CODE
*/
public void setRequiredFields(@Nullable String... requiredFields) {
this.requiredFields = PropertyAccessorUtils.canonicalPropertyNames(requiredFields);
if (logger.isDebugEnabled()) {
logger.debug("DataBinder requires binding of required fields [" +
StringUtils.arrayToCommaDelimitedString(requiredFields) + "]");
}
}
/**
* Return the fields that are required for each binding process.
* @return array of field names
*/
@Nullable
public String[] getRequiredFields() {
return this.requiredFields;
}
/**
* Configure a resolver to determine the name of the value to bind to a
* constructor parameter in {@link #construct}.
* <p>If not configured, or if the name cannot be resolved, by default
* {@link org.springframework.core.DefaultParameterNameDiscoverer} is used.
* @param nameResolver the resolver to use
* @since 6.1
*/
public void setNameResolver(NameResolver nameResolver) {
this.nameResolver = nameResolver;
}
/**
* Return the {@link #setNameResolver configured} name resolver for
* constructor parameters.
* @since 6.1
*/
@Nullable
public NameResolver getNameResolver() {
return this.nameResolver;
}
/**
* Set the strategy to use for resolving errors into message codes.
* Applies the given strategy to the underlying errors holder.
* <p>Default is a DefaultMessageCodesResolver.
* @see BeanPropertyBindingResult#setMessageCodesResolver
* @see DefaultMessageCodesResolver
*/
public void setMessageCodesResolver(@Nullable MessageCodesResolver messageCodesResolver) {
Assert.state(this.messageCodesResolver == null, "DataBinder is already initialized with MessageCodesResolver");
this.messageCodesResolver = messageCodesResolver;
if (this.bindingResult != null && messageCodesResolver != null) {
this.bindingResult.setMessageCodesResolver(messageCodesResolver);
}
}
/**
* Set the strategy to use for processing binding errors, that is,
* required field errors and {@code PropertyAccessException}s.
* <p>Default is a DefaultBindingErrorProcessor.
* @see DefaultBindingErrorProcessor
*/
public void setBindingErrorProcessor(BindingErrorProcessor bindingErrorProcessor) {
Assert.notNull(bindingErrorProcessor, "BindingErrorProcessor must not be null");
this.bindingErrorProcessor = bindingErrorProcessor;
}
/**
* Return the strategy for processing binding errors.
*/
public BindingErrorProcessor getBindingErrorProcessor() {
return this.bindingErrorProcessor;
}
/**
* Set the Validator to apply after each binding step.
* @see #addValidators(Validator...)
* @see #replaceValidators(Validator...)
*/
public void setValidator(@Nullable Validator validator) {
assertValidators(validator);
this.validators.clear();
if (validator != null) {
this.validators.add(validator);
}
}
private void assertValidators(Validator... validators) {
Object target = getTarget();
for (Validator validator : validators) {
if (validator != null && (target != null && !validator.supports(target.getClass()))) {
throw new IllegalStateException("Invalid target for Validator [" + validator + "]: " + target);
}
}
}
/**
* Configure a predicate to exclude validators.
* @since 6.1
*/
public void setExcludedValidators(Predicate<Validator> predicate) {
this.excludedValidators = predicate;
}
/**
* Add Validators to apply after each binding step.
* @see #setValidator(Validator)
* @see #replaceValidators(Validator...)
*/
public void addValidators(Validator... validators) {
assertValidators(validators);
this.validators.addAll(Arrays.asList(validators));
}
/**
* Replace the Validators to apply after each binding step.
* @see #setValidator(Validator)
* @see #addValidators(Validator...)
*/
public void replaceValidators(Validator... validators) {
assertValidators(validators);
this.validators.clear();
this.validators.addAll(Arrays.asList(validators));
}
/**
* Return the primary Validator to apply after each binding step, if any.
*/
@Nullable
public Validator getValidator() {
return (!this.validators.isEmpty() ? this.validators.get(0) : null);
}
/**
* Return the Validators to apply after data binding.
*/
public List<Validator> getValidators() {
return Collections.unmodifiableList(this.validators);
}
/**
* Return the Validators to apply after data binding. This includes the
* configured {@link #getValidators() validators} filtered by the
* {@link #setExcludedValidators(Predicate) exclude predicate}.
* @since 6.1
*/
public List<Validator> getValidatorsToApply() {
return (this.excludedValidators != null ?
this.validators.stream().filter(validator -> !this.excludedValidators.test(validator)).toList() :
Collections.unmodifiableList(this.validators));
}
//---------------------------------------------------------------------
// Implementation of PropertyEditorRegistry/TypeConverter interface
//---------------------------------------------------------------------
/**
* Specify a {@link ConversionService} to use for converting
* property values, as an alternative to JavaBeans PropertyEditors.
*/
public void setConversionService(@Nullable ConversionService conversionService) {
Assert.state(this.conversionService == null, "DataBinder is already initialized with ConversionService");
this.conversionService = conversionService;
if (this.bindingResult != null && conversionService != null) {
this.bindingResult.initConversion(conversionService);
}
}
/**
* Return the associated ConversionService, if any.
*/
@Nullable
public ConversionService getConversionService() {
return this.conversionService;
}
/**
* Add a custom formatter, applying it to all fields matching the
* {@link Formatter}-declared type.
* <p>Registers a corresponding {@link PropertyEditor} adapter underneath the covers.
* @param formatter the formatter to add, generically declared for a specific type
* @since 4.2
* @see #registerCustomEditor(Class, PropertyEditor)
*/
public void addCustomFormatter(Formatter<?> formatter) {
FormatterPropertyEditorAdapter adapter = new FormatterPropertyEditorAdapter(formatter);
getPropertyEditorRegistry().registerCustomEditor(adapter.getFieldType(), adapter);
}
/**
* Add a custom formatter for the field type specified in {@link Formatter} class,
* applying it to the specified fields only, if any, or otherwise to all fields.
* <p>Registers a corresponding {@link PropertyEditor} adapter underneath the covers.
* @param formatter the formatter to add, generically declared for a specific type
* @param fields the fields to apply the formatter to, or none if to be applied to all
* @since 4.2
* @see #registerCustomEditor(Class, String, PropertyEditor)
*/
public void addCustomFormatter(Formatter<?> formatter, String... fields) {
FormatterPropertyEditorAdapter adapter = new FormatterPropertyEditorAdapter(formatter);
Class<?> fieldType = adapter.getFieldType();
if (ObjectUtils.isEmpty(fields)) {
getPropertyEditorRegistry().registerCustomEditor(fieldType, adapter);
}
else {
for (String field : fields) {
getPropertyEditorRegistry().registerCustomEditor(fieldType, field, adapter);
}
}
}
/**
* Add a custom formatter, applying it to the specified field types only, if any,
* or otherwise to all fields matching the {@link Formatter}-declared type.
* <p>Registers a corresponding {@link PropertyEditor} adapter underneath the covers.
* @param formatter the formatter to add (does not need to generically declare a
* field type if field types are explicitly specified as parameters)
* @param fieldTypes the field types to apply the formatter to, or none if to be
* derived from the given {@link Formatter} implementation class
* @since 4.2
* @see #registerCustomEditor(Class, PropertyEditor)
*/
public void addCustomFormatter(Formatter<?> formatter, Class<?>... fieldTypes) {
FormatterPropertyEditorAdapter adapter = new FormatterPropertyEditorAdapter(formatter);
if (ObjectUtils.isEmpty(fieldTypes)) {
getPropertyEditorRegistry().registerCustomEditor(adapter.getFieldType(), adapter);
}
else {
for (Class<?> fieldType : fieldTypes) {
getPropertyEditorRegistry().registerCustomEditor(fieldType, adapter);
}
}
}
@Override
public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
getPropertyEditorRegistry().registerCustomEditor(requiredType, propertyEditor);
}
@Override
public void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String field, PropertyEditor propertyEditor) {
getPropertyEditorRegistry().registerCustomEditor(requiredType, field, propertyEditor);
}
@Override
@Nullable
public PropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath) {
return getPropertyEditorRegistry().findCustomEditor(requiredType, propertyPath);
}
@Override
@Nullable
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType) throws TypeMismatchException {
return getTypeConverter().convertIfNecessary(value, requiredType);
}
@Override
@Nullable
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
@Nullable MethodParameter methodParam) throws TypeMismatchException {
return getTypeConverter().convertIfNecessary(value, requiredType, methodParam);
}
@Override
@Nullable
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable Field field)
throws TypeMismatchException {
return getTypeConverter().convertIfNecessary(value, requiredType, field);
}
@Nullable
@Override
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
@Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {
return getTypeConverter().convertIfNecessary(value, requiredType, typeDescriptor);
}
/**
* Create the target with constructor injection of values. It is expected that
* {@link #setTargetType(ResolvableType)} was previously called and that
* {@link #getTarget()} is {@code null}.
* <p>Uses a public, no-arg constructor if available in the target object type,
* also supporting a "primary constructor" approach for data classes as follows:
* It understands the JavaBeans {@code ConstructorProperties} annotation as
* well as runtime-retained parameter names in the bytecode, associating
* input values with constructor arguments by name. If no such constructor is
* found, the default constructor will be used (even if not public), assuming
* subsequent bean property bindings through setter methods.
* <p>After the call, use {@link #getBindingResult()} to check for failures
* to bind to, and/or validate constructor arguments. If there are no errors,
* the target is set, and {@link #doBind(MutablePropertyValues)} can be used
* for further initialization via setters.
* @param valueResolver to resolve constructor argument values with
* @throws BeanInstantiationException in case of constructor failure
* @since 6.1
*/
public void construct(ValueResolver valueResolver) {
Assert.state(this.target == null, "Target instance already available");
Assert.state(this.targetType != null, "Target type not set");
this.target = createObject(this.targetType, "", valueResolver);
if (!getBindingResult().hasErrors()) {
this.bindingResult = null;
if (this.typeConverter != null) {
this.typeConverter.registerCustomEditors(getPropertyAccessor());
}
}
}
@Nullable
private Object createObject(ResolvableType objectType, String nestedPath, ValueResolver valueResolver) {
Class<?> clazz = objectType.resolve();
boolean isOptional = (clazz == Optional.class);
clazz = (isOptional ? objectType.resolveGeneric(0) : clazz);
if (clazz == null) {
throw new IllegalStateException(
"Insufficient type information to create instance of " + objectType);
}
Object result = null;
Constructor<?> ctor = BeanUtils.getResolvableConstructor(clazz);
if (ctor.getParameterCount() == 0) {
// A single default constructor -> clearly a standard JavaBeans arrangement.
result = BeanUtils.instantiateClass(ctor);
}
else {
// A single data class constructor -> resolve constructor arguments from request parameters.
String[] paramNames = BeanUtils.getParameterNames(ctor);
Class<?>[] paramTypes = ctor.getParameterTypes();
Object[] args = new Object[paramTypes.length];
Set<String> failedParamNames = new HashSet<>(4);
for (int i = 0; i < paramNames.length; i++) {
MethodParameter param = MethodParameter.forFieldAwareConstructor(ctor, i, paramNames[i]);
String lookupName = null;
if (this.nameResolver != null) {
lookupName = this.nameResolver.resolveName(param);
}
if (lookupName == null) {
lookupName = paramNames[i];
}
String paramPath = nestedPath + lookupName;
Class<?> paramType = paramTypes[i];
Object value = valueResolver.resolveValue(paramPath, paramType);
if (value == null && shouldConstructArgument(param)) {
ResolvableType type = ResolvableType.forMethodParameter(param);
args[i] = createObject(type, paramPath + ".", valueResolver);
}
else {
try {
if (value == null && (param.isOptional() || getBindingResult().hasErrors())) {
args[i] = (param.getParameterType() == Optional.class ? Optional.empty() : null);
}
else {
args[i] = convertIfNecessary(value, paramType, param);
}
}
catch (TypeMismatchException ex) {
ex.initPropertyName(paramPath);
args[i] = null;
failedParamNames.add(paramPath);
getBindingResult().recordFieldValue(paramPath, paramType, value);
getBindingErrorProcessor().processPropertyAccessException(ex, getBindingResult());
}
}
}
if (getBindingResult().hasErrors()) {
for (int i = 0; i < paramNames.length; i++) {
String paramPath = nestedPath + paramNames[i];
if (!failedParamNames.contains(paramPath)) {
Object value = args[i];
getBindingResult().recordFieldValue(paramPath, paramTypes[i], value);
validateConstructorArgument(ctor.getDeclaringClass(), nestedPath, paramNames[i], value);
}
}
if (!(objectType.getSource() instanceof MethodParameter param && param.isOptional())) {
try {
result = BeanUtils.instantiateClass(ctor, args);
}
catch (BeanInstantiationException ex) {
// swallow and proceed without target instance
}
}
}
else {
try {
result = BeanUtils.instantiateClass(ctor, args);
}
catch (BeanInstantiationException ex) {
if (KotlinDetector.isKotlinType(clazz) && ex.getCause() instanceof NullPointerException cause) {
ObjectError error = new ObjectError(ctor.getName(), cause.getMessage());
getBindingResult().addError(error);
}