Merge branch '6.2.x'
This commit is contained in:
commit
bae12e739d
|
@ -142,6 +142,10 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||||
*/
|
*/
|
||||||
protected static final Log logger = LogFactory.getLog(DataBinder.class);
|
protected static final Log logger = LogFactory.getLog(DataBinder.class);
|
||||||
|
|
||||||
|
/** Internal constant for constructor binding via "[]". */
|
||||||
|
private static final int NO_INDEX = -1;
|
||||||
|
|
||||||
|
|
||||||
private @Nullable Object target;
|
private @Nullable Object target;
|
||||||
|
|
||||||
@Nullable ResolvableType targetType;
|
@Nullable ResolvableType targetType;
|
||||||
|
@ -1028,15 +1032,17 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
int size = (indexes.last() < this.autoGrowCollectionLimit ? indexes.last() + 1 : 0);
|
int lastIndex = Math.max(indexes.last(), 0);
|
||||||
|
int size = (lastIndex < this.autoGrowCollectionLimit ? lastIndex + 1 : 0);
|
||||||
List<?> list = (List<?>) CollectionFactory.createCollection(paramType, size);
|
List<?> list = (List<?>) CollectionFactory.createCollection(paramType, size);
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
list.add(null);
|
list.add(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int index : indexes) {
|
for (int index : indexes) {
|
||||||
String indexedPath = paramPath + "[" + index + "]";
|
String indexedPath = paramPath + "[" + (index != NO_INDEX ? index : "") + "]";
|
||||||
list.set(index, createIndexedValue(paramPath, paramType, elementType, indexedPath, valueResolver));
|
list.set(Math.max(index, 0),
|
||||||
|
createIndexedValue(paramPath, paramType, elementType, indexedPath, valueResolver));
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
|
@ -1078,12 +1084,14 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
int size = (indexes.last() < this.autoGrowCollectionLimit ? indexes.last() + 1: 0);
|
int lastIndex = Math.max(indexes.last(), 0);
|
||||||
|
int size = (lastIndex < this.autoGrowCollectionLimit ? lastIndex + 1: 0);
|
||||||
@Nullable V[] array = (V[]) Array.newInstance(elementType.resolve(), size);
|
@Nullable V[] array = (V[]) Array.newInstance(elementType.resolve(), size);
|
||||||
|
|
||||||
for (int index : indexes) {
|
for (int index : indexes) {
|
||||||
String indexedPath = paramPath + "[" + index + "]";
|
String indexedPath = paramPath + "[" + (index != NO_INDEX ? index : "") + "]";
|
||||||
array[index] = createIndexedValue(paramPath, paramType, elementType, indexedPath, valueResolver);
|
array[Math.max(index, 0)] =
|
||||||
|
createIndexedValue(paramPath, paramType, elementType, indexedPath, valueResolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
return array;
|
return array;
|
||||||
|
@ -1093,13 +1101,20 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||||
SortedSet<Integer> indexes = null;
|
SortedSet<Integer> indexes = null;
|
||||||
for (String name : valueResolver.getNames()) {
|
for (String name : valueResolver.getNames()) {
|
||||||
if (name.startsWith(paramPath + "[")) {
|
if (name.startsWith(paramPath + "[")) {
|
||||||
int endIndex = name.indexOf(']', paramPath.length() + 2);
|
int index;
|
||||||
String rawIndex = name.substring(paramPath.length() + 1, endIndex);
|
if (paramPath.length() + 2 == name.length()) {
|
||||||
if (StringUtils.hasLength(rawIndex)) {
|
if (!name.endsWith("[]")) {
|
||||||
int index = Integer.parseInt(rawIndex);
|
continue;
|
||||||
indexes = (indexes != null ? indexes : new TreeSet<>());
|
}
|
||||||
indexes.add(index);
|
index = NO_INDEX;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
int endIndex = name.indexOf(']', paramPath.length() + 2);
|
||||||
|
String indexValue = name.substring(paramPath.length() + 1, endIndex);
|
||||||
|
index = Integer.parseInt(indexValue);
|
||||||
|
}
|
||||||
|
indexes = (indexes != null ? indexes : new TreeSet<>());
|
||||||
|
indexes.add(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return indexes;
|
return indexes;
|
||||||
|
@ -1107,23 +1122,36 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private <V> @Nullable V createIndexedValue(
|
private <V> @Nullable V createIndexedValue(
|
||||||
String paramPath, Class<?> paramType, ResolvableType elementType,
|
String paramPath, Class<?> containerType, ResolvableType elementType,
|
||||||
String indexedPath, ValueResolver valueResolver) {
|
String indexedPath, ValueResolver valueResolver) {
|
||||||
|
|
||||||
Object value = null;
|
Object value = null;
|
||||||
Class<?> elementClass = elementType.resolve(Object.class);
|
Class<?> elementClass = elementType.resolve(Object.class);
|
||||||
Object rawValue = valueResolver.resolveValue(indexedPath, elementClass);
|
|
||||||
if (rawValue != null) {
|
if (List.class.isAssignableFrom(elementClass)) {
|
||||||
try {
|
value = createList(indexedPath, elementClass, elementType, valueResolver);
|
||||||
value = convertIfNecessary(rawValue, elementClass);
|
}
|
||||||
}
|
else if (Map.class.isAssignableFrom(elementClass)) {
|
||||||
catch (TypeMismatchException ex) {
|
value = createMap(indexedPath, elementClass, elementType, valueResolver);
|
||||||
handleTypeMismatchException(ex, paramPath, paramType, rawValue);
|
}
|
||||||
}
|
else if (elementClass.isArray()) {
|
||||||
|
value = createArray(indexedPath, elementClass, elementType, valueResolver);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
value = createObject(elementType, indexedPath + ".", valueResolver);
|
Object rawValue = valueResolver.resolveValue(indexedPath, elementClass);
|
||||||
|
if (rawValue != null) {
|
||||||
|
try {
|
||||||
|
value = convertIfNecessary(rawValue, elementClass);
|
||||||
|
}
|
||||||
|
catch (TypeMismatchException ex) {
|
||||||
|
handleTypeMismatchException(ex, paramPath, containerType, rawValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
value = createObject(elementType, indexedPath + ".", valueResolver);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (V) value;
|
return (V) value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -188,6 +188,17 @@ class DataBinderConstructTests {
|
||||||
assertThat(target.integerList()).containsExactly(1, 2);
|
assertThat(target.integerList()).containsExactly(1, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void simpleListBindingEmptyBrackets() {
|
||||||
|
MapValueResolver valueResolver = new MapValueResolver(Map.of("integerList[]", "1"));
|
||||||
|
|
||||||
|
DataBinder binder = initDataBinder(IntegerListRecord.class);
|
||||||
|
binder.construct(valueResolver);
|
||||||
|
|
||||||
|
IntegerListRecord target = getTarget(binder);
|
||||||
|
assertThat(target.integerList()).containsExactly(1);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void simpleMapBinding() {
|
void simpleMapBinding() {
|
||||||
MapValueResolver valueResolver = new MapValueResolver(Map.of("integerMap[a]", "1", "integerMap[b]", "2"));
|
MapValueResolver valueResolver = new MapValueResolver(Map.of("integerMap[a]", "1", "integerMap[b]", "2"));
|
||||||
|
@ -210,6 +221,34 @@ class DataBinderConstructTests {
|
||||||
assertThat(target.integerArray()).containsExactly(1, 2);
|
assertThat(target.integerArray()).containsExactly(1, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void nestedListWithinMap() {
|
||||||
|
MapValueResolver valueResolver = new MapValueResolver(Map.of(
|
||||||
|
"integerListMap[a][0]", "1", "integerListMap[a][1]", "2",
|
||||||
|
"integerListMap[b][0]", "3", "integerListMap[b][1]", "4"));
|
||||||
|
|
||||||
|
DataBinder binder = initDataBinder(IntegerListMapRecord.class);
|
||||||
|
binder.construct(valueResolver);
|
||||||
|
|
||||||
|
IntegerListMapRecord target = getTarget(binder);
|
||||||
|
assertThat(target.integerListMap().get("a")).containsExactly(1, 2);
|
||||||
|
assertThat(target.integerListMap().get("b")).containsExactly(3, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void nestedMapWithinList() {
|
||||||
|
MapValueResolver valueResolver = new MapValueResolver(Map.of(
|
||||||
|
"integerMapList[0][a]", "1", "integerMapList[0][b]", "2",
|
||||||
|
"integerMapList[1][a]", "3", "integerMapList[1][b]", "4"));
|
||||||
|
|
||||||
|
DataBinder binder = initDataBinder(IntegerMapListRecord.class);
|
||||||
|
binder.construct(valueResolver);
|
||||||
|
|
||||||
|
IntegerMapListRecord target = getTarget(binder);
|
||||||
|
assertThat(target.integerMapList().get(0)).containsOnly(Map.entry("a", 1), Map.entry("b", 2));
|
||||||
|
assertThat(target.integerMapList().get(1)).containsOnly(Map.entry("a", 3), Map.entry("b", 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("SameParameterValue")
|
@SuppressWarnings("SameParameterValue")
|
||||||
private static DataBinder initDataBinder(Class<?> targetType) {
|
private static DataBinder initDataBinder(Class<?> targetType) {
|
||||||
|
@ -304,6 +343,14 @@ class DataBinderConstructTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private record IntegerMapListRecord(List<Map<String, Integer>> integerMapList) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private record IntegerListMapRecord(Map<String, List<Integer>> integerListMap) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private record MapValueResolver(Map<String, Object> map) implements DataBinder.ValueResolver {
|
private record MapValueResolver(Map<String, Object> map) implements DataBinder.ValueResolver {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -465,7 +465,7 @@ public class HttpRequestValues {
|
||||||
|
|
||||||
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(uriTemplate);
|
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(uriTemplate);
|
||||||
for (Map.Entry<String, List<String>> entry : requestParams.entrySet()) {
|
for (Map.Entry<String, List<String>> entry : requestParams.entrySet()) {
|
||||||
String nameVar = entry.getKey();
|
String nameVar = entry.getKey().replace(":", "%3A"); // suppress treatment as regex
|
||||||
uriVars.put(nameVar, entry.getKey());
|
uriVars.put(nameVar, entry.getKey());
|
||||||
for (int j = 0; j < entry.getValue().size(); j++) {
|
for (int j = 0; j < entry.getValue().size(); j++) {
|
||||||
String valueVar = nameVar + "[" + j + "]";
|
String valueVar = nameVar + "[" + j + "]";
|
||||||
|
|
|
@ -99,6 +99,24 @@ class HttpRequestValuesTests {
|
||||||
.isEqualTo("/path?param1=1st%20value¶m2=2nd%20value%20A¶m2=2nd%20value%20B");
|
.isEqualTo("/path?param1=1st%20value¶m2=2nd%20value%20A¶m2=2nd%20value%20B");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // gh-34364
|
||||||
|
void queryParamWithSemicolon() {
|
||||||
|
HttpRequestValues requestValues = HttpRequestValues.builder().setHttpMethod(HttpMethod.POST)
|
||||||
|
.setUriTemplate("/path")
|
||||||
|
.addRequestParameter("userId:eq", "test value")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String uriTemplate = requestValues.getUriTemplate();
|
||||||
|
assertThat(uriTemplate).isEqualTo("/path?{userId%3Aeq}={userId%3Aeq[0]}");
|
||||||
|
|
||||||
|
URI uri = UriComponentsBuilder.fromUriString(uriTemplate)
|
||||||
|
.encode()
|
||||||
|
.build(requestValues.getUriVariables());
|
||||||
|
|
||||||
|
assertThat(uri.toString())
|
||||||
|
.isEqualTo("/path?userId%3Aeq=test%20value");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void queryParamsWithPreparedUri() {
|
void queryParamsWithPreparedUri() {
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue