Skip to content

Commit 53a9697

Browse files
committed
Consistent conversion of Optional array/list arrangements
Issue: SPR-15918 Issue: SPR-15919 Issue: SPR-15676 (cherry picked from commit 15c82af)
1 parent d11bd64 commit 53a9697

File tree

3 files changed

+103
-10
lines changed

3 files changed

+103
-10
lines changed

spring-core/src/main/java/org/springframework/core/convert/support/ObjectToOptionalConverter.java

Lines changed: 15 additions & 5 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-2017 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.
@@ -16,7 +16,9 @@
1616

1717
package org.springframework.core.convert.support;
1818

19-
import java.util.Collections;
19+
import java.lang.reflect.Array;
20+
import java.util.Collection;
21+
import java.util.LinkedHashSet;
2022
import java.util.Optional;
2123
import java.util.Set;
2224

@@ -47,7 +49,11 @@ public ObjectToOptionalConverter(ConversionService conversionService) {
4749

4850
@Override
4951
public Set<ConvertiblePair> getConvertibleTypes() {
50-
return Collections.singleton(new ConvertiblePair(Object.class, Optional.class));
52+
Set<ConvertiblePair> convertibleTypes = new LinkedHashSet<ConvertiblePair>(4);
53+
convertibleTypes.add(new ConvertiblePair(Collection.class, Optional.class));
54+
convertibleTypes.add(new ConvertiblePair(Object[].class, Optional.class));
55+
convertibleTypes.add(new ConvertiblePair(Object.class, Optional.class));
56+
return convertibleTypes;
5157
}
5258

5359
@Override
@@ -70,7 +76,11 @@ else if (source instanceof Optional) {
7076
}
7177
else if (targetType.getResolvableType() != null) {
7278
Object target = this.conversionService.convert(source, sourceType, new GenericTypeDescriptor(targetType));
73-
return Optional.ofNullable(target);
79+
if (target == null || (target.getClass().isArray() && Array.getLength(target) == 0) ||
80+
(target instanceof Collection && ((Collection) target).isEmpty())) {
81+
return Optional.empty();
82+
}
83+
return Optional.of(target);
7484
}
7585
else {
7686
return Optional.of(source);
@@ -82,7 +92,7 @@ else if (targetType.getResolvableType() != null) {
8292
private static class GenericTypeDescriptor extends TypeDescriptor {
8393

8494
public GenericTypeDescriptor(TypeDescriptor typeDescriptor) {
85-
super(typeDescriptor.getResolvableType().getGeneric(0), null, typeDescriptor.getAnnotations());
95+
super(typeDescriptor.getResolvableType().getGeneric(), null, typeDescriptor.getAnnotations());
8696
}
8797
}
8898

spring-core/src/main/java/org/springframework/util/ObjectUtils.java

Lines changed: 4 additions & 4 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-2017 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.
@@ -131,12 +131,12 @@ public static boolean isEmpty(Object obj) {
131131
return true;
132132
}
133133

134-
if (obj.getClass().isArray()) {
135-
return Array.getLength(obj) == 0;
136-
}
137134
if (obj instanceof CharSequence) {
138135
return ((CharSequence) obj).length() == 0;
139136
}
137+
if (obj.getClass().isArray()) {
138+
return Array.getLength(obj) == 0;
139+
}
140140
if (obj instanceof Collection) {
141141
return ((Collection) obj).isEmpty();
142142
}

spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ public class RequestParamMethodArgumentResolverTests {
8484
private MethodParameter paramRequired;
8585
private MethodParameter paramNotRequired;
8686
private MethodParameter paramOptional;
87+
private MethodParameter paramOptionalArray;
88+
private MethodParameter paramOptionalList;
8789
private MethodParameter multipartFileOptional;
8890

8991
private NativeWebRequest webRequest;
@@ -119,7 +121,9 @@ public void setUp() throws Exception {
119121
paramRequired = new SynthesizingMethodParameter(method, 15);
120122
paramNotRequired = new SynthesizingMethodParameter(method, 16);
121123
paramOptional = new SynthesizingMethodParameter(method, 17);
122-
multipartFileOptional = new SynthesizingMethodParameter(method, 18);
124+
paramOptionalArray = new SynthesizingMethodParameter(method, 18);
125+
paramOptionalList = new SynthesizingMethodParameter(method, 19);
126+
multipartFileOptional = new SynthesizingMethodParameter(method, 20);
123127

124128
request = new MockHttpServletRequest();
125129
webRequest = new ServletWebRequest(request, new MockHttpServletResponse());
@@ -437,6 +441,83 @@ public void resolveOptionalParamValue() throws Exception {
437441
assertEquals(123, ((Optional) result).get());
438442
}
439443

444+
@Test
445+
@SuppressWarnings("rawtypes")
446+
public void missingOptionalParamValue() throws Exception {
447+
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
448+
initializer.setConversionService(new DefaultConversionService());
449+
WebDataBinderFactory binderFactory = new DefaultDataBinderFactory(initializer);
450+
451+
Object result = resolver.resolveArgument(paramOptional, null, webRequest, binderFactory);
452+
assertEquals(Optional.empty(), result);
453+
454+
result = resolver.resolveArgument(paramOptional, null, webRequest, binderFactory);
455+
assertEquals(Optional.class, result.getClass());
456+
assertFalse(((Optional) result).isPresent());
457+
}
458+
459+
@Test
460+
@SuppressWarnings("rawtypes")
461+
public void resolveOptionalParamArray() throws Exception {
462+
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
463+
initializer.setConversionService(new DefaultConversionService());
464+
WebDataBinderFactory binderFactory = new DefaultDataBinderFactory(initializer);
465+
466+
Object result = resolver.resolveArgument(paramOptionalArray, null, webRequest, binderFactory);
467+
assertEquals(Optional.empty(), result);
468+
469+
this.request.addParameter("name", "123", "456");
470+
result = resolver.resolveArgument(paramOptionalArray, null, webRequest, binderFactory);
471+
assertEquals(Optional.class, result.getClass());
472+
assertArrayEquals(new Integer[] {123, 456}, (Integer[]) ((Optional) result).get());
473+
}
474+
475+
@Test
476+
@SuppressWarnings("rawtypes")
477+
public void missingOptionalParamArray() throws Exception {
478+
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
479+
initializer.setConversionService(new DefaultConversionService());
480+
WebDataBinderFactory binderFactory = new DefaultDataBinderFactory(initializer);
481+
482+
Object result = resolver.resolveArgument(paramOptionalArray, null, webRequest, binderFactory);
483+
assertEquals(Optional.empty(), result);
484+
485+
result = resolver.resolveArgument(paramOptionalArray, null, webRequest, binderFactory);
486+
assertEquals(Optional.class, result.getClass());
487+
assertFalse(((Optional) result).isPresent());
488+
}
489+
490+
@Test
491+
@SuppressWarnings("rawtypes")
492+
public void resolveOptionalParamList() throws Exception {
493+
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
494+
initializer.setConversionService(new DefaultConversionService());
495+
WebDataBinderFactory binderFactory = new DefaultDataBinderFactory(initializer);
496+
497+
Object result = resolver.resolveArgument(paramOptionalList, null, webRequest, binderFactory);
498+
assertEquals(Optional.empty(), result);
499+
500+
this.request.addParameter("name", "123", "456");
501+
result = resolver.resolveArgument(paramOptionalList, null, webRequest, binderFactory);
502+
assertEquals(Optional.class, result.getClass());
503+
assertEquals(Arrays.asList("123", "456"), ((Optional) result).get());
504+
}
505+
506+
@Test
507+
@SuppressWarnings("rawtypes")
508+
public void missingOptionalParamList() throws Exception {
509+
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
510+
initializer.setConversionService(new DefaultConversionService());
511+
WebDataBinderFactory binderFactory = new DefaultDataBinderFactory(initializer);
512+
513+
Object result = resolver.resolveArgument(paramOptionalList, null, webRequest, binderFactory);
514+
assertEquals(Optional.empty(), result);
515+
516+
result = resolver.resolveArgument(paramOptionalList, null, webRequest, binderFactory);
517+
assertEquals(Optional.class, result.getClass());
518+
assertFalse(((Optional) result).isPresent());
519+
}
520+
440521
@Test
441522
public void resolveOptionalMultipartFile() throws Exception {
442523
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
@@ -493,6 +574,8 @@ public void handle(
493574
@RequestParam("name") String paramRequired,
494575
@RequestParam(name = "name", required = false) String paramNotRequired,
495576
@RequestParam("name") Optional<Integer> paramOptional,
577+
@RequestParam("name") Optional<Integer[]> paramOptionalArray,
578+
@RequestParam("name") Optional<List> paramOptionalList,
496579
@RequestParam("mfile") Optional<MultipartFile> multipartFileOptional) {
497580
}
498581

0 commit comments

Comments
 (0)