From e57b942b4daeb5133ffc36c948ca6198d910d6d6 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Tue, 24 Oct 2023 10:33:29 +0100 Subject: [PATCH] MockMvcBuilder supports filter name in addition to initParams Closes gh-31474 --- .../servlet/setup/AbstractMockMvcBuilder.java | 4 +- .../setup/ConfigurableMockMvcBuilder.java | 8 ++- .../servlet/setup/MockMvcFilterDecorator.java | 72 ++++++++++++------- .../setup/MockMvcFilterDecoratorTests.java | 8 +-- .../setup/StandaloneMockMvcBuilderTests.java | 2 +- ...tractNamedValueMethodArgumentResolver.java | 33 +++++---- 6 files changed, 79 insertions(+), 48 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java index 856d4d6199..1180d5a4a4 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java @@ -100,10 +100,10 @@ public abstract class AbstractMockMvcBuilder @Override public T addFilter( - Filter filter, Map initParams, + Filter filter, @Nullable String filterName, Map initParams, EnumSet dispatcherTypes, String... urlPatterns) { - filter = new MockMvcFilterDecorator(filter, initParams, dispatcherTypes, urlPatterns); + filter = new MockMvcFilterDecorator(filter, filterName, initParams, dispatcherTypes, urlPatterns); this.filters.add(filter); return self(); } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/ConfigurableMockMvcBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/ConfigurableMockMvcBuilder.java index 11ad705dc7..56dc6ce5da 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/ConfigurableMockMvcBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/ConfigurableMockMvcBuilder.java @@ -24,6 +24,7 @@ import jakarta.servlet.DispatcherType; import jakarta.servlet.Filter; import jakarta.servlet.FilterConfig; +import org.springframework.lang.Nullable; import org.springframework.test.web.servlet.DispatcherServletCustomizer; import org.springframework.test.web.servlet.MockMvcBuilder; import org.springframework.test.web.servlet.RequestBuilder; @@ -51,7 +52,7 @@ public interface ConfigurableMockMvcBuilderNote: if you need the filter to be initialized with {@link Filter#init(FilterConfig)}, - * please use {@link #addFilter(Filter, Map, EnumSet, String...)} instead. + * please use {@link #addFilter(Filter, String, Map, EnumSet, String...)} instead. * @param filter the filter to add * @param urlPatterns the URL patterns to map to; if empty, matches all requests */ @@ -62,6 +63,9 @@ public interface ConfigurableMockMvcBuilder T addFilter( - Filter filter, Map initParams, + Filter filter, @Nullable String filterName, Map initParams, EnumSet dispatcherTypes, String... urlPatterns); /** diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/MockMvcFilterDecorator.java b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/MockMvcFilterDecorator.java index 46c583bdd5..4574618628 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/MockMvcFilterDecorator.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/MockMvcFilterDecorator.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import java.util.Map; +import java.util.function.Function; import jakarta.servlet.DispatcherType; import jakarta.servlet.Filter; @@ -56,7 +57,7 @@ final class MockMvcFilterDecorator implements Filter { private final Filter delegate; @Nullable - private final Map initParams; + private final Function filterConfigInitializer; @Nullable private final EnumSet dispatcherTypes; @@ -78,7 +79,12 @@ final class MockMvcFilterDecorator implements Filter { *

Note: when this constructor is used, the Filter is not initialized. */ public MockMvcFilterDecorator(Filter delegate, String[] urlPatterns) { - this(delegate, null, null, urlPatterns); + Assert.notNull(delegate, "filter cannot be null"); + Assert.notNull(urlPatterns, "urlPatterns cannot be null"); + this.delegate = delegate; + this.filterConfigInitializer = null; + this.dispatcherTypes = null; + this.hasPatterns = initPatterns(urlPatterns); } /** @@ -86,38 +92,51 @@ final class MockMvcFilterDecorator implements Filter { * as well as dispatcher types and URL patterns to match. */ public MockMvcFilterDecorator( - Filter delegate, @Nullable Map initParams, + Filter delegate, @Nullable String filterName, @Nullable Map initParams, @Nullable EnumSet dispatcherTypes, String... urlPatterns) { Assert.notNull(delegate, "filter cannot be null"); Assert.notNull(urlPatterns, "urlPatterns cannot be null"); this.delegate = delegate; - this.initParams = initParams; + this.filterConfigInitializer = getFilterConfigInitializer(filterName, initParams); this.dispatcherTypes = dispatcherTypes; - this.hasPatterns = (urlPatterns.length != 0); - for (String urlPattern : urlPatterns) { - addUrlPattern(urlPattern); - } + this.hasPatterns = initPatterns(urlPatterns); } - private void addUrlPattern(String urlPattern) { - Assert.notNull(urlPattern, "Found null URL Pattern"); - if (urlPattern.startsWith(EXTENSION_MAPPING_PATTERN)) { - this.endsWithMatches.add(urlPattern.substring(1)); - } - else if (urlPattern.equals(PATH_MAPPING_PATTERN) || urlPattern.equals(ALL_MAPPING_PATTERN)) { - this.startsWithMatches.add(""); - } - else if (urlPattern.endsWith(PATH_MAPPING_PATTERN)) { - this.startsWithMatches.add(urlPattern.substring(0, urlPattern.length() - 1)); - this.exactMatches.add(urlPattern.substring(0, urlPattern.length() - 2)); - } - else { - if (urlPattern.isEmpty()) { - urlPattern = "/"; + private static Function getFilterConfigInitializer( + @Nullable String filterName, @Nullable Map initParams) { + + return servletContext -> { + MockFilterConfig filterConfig = (filterName != null ? + new MockFilterConfig(servletContext, filterName) : new MockFilterConfig(servletContext)); + if (initParams != null) { + initParams.forEach(filterConfig::addInitParameter); + } + return filterConfig; + }; + } + + private boolean initPatterns(String... urlPatterns) { + for (String urlPattern : urlPatterns) { + Assert.notNull(urlPattern, "Found null URL Pattern"); + if (urlPattern.startsWith(EXTENSION_MAPPING_PATTERN)) { + this.endsWithMatches.add(urlPattern.substring(1)); + } + else if (urlPattern.equals(PATH_MAPPING_PATTERN) || urlPattern.equals(ALL_MAPPING_PATTERN)) { + this.startsWithMatches.add(""); + } + else if (urlPattern.endsWith(PATH_MAPPING_PATTERN)) { + this.startsWithMatches.add(urlPattern.substring(0, urlPattern.length() - 1)); + this.exactMatches.add(urlPattern.substring(0, urlPattern.length() - 2)); + } + else { + if (urlPattern.isEmpty()) { + urlPattern = "/"; + } + this.exactMatches.add(urlPattern); } - this.exactMatches.add(urlPattern); } + return (urlPatterns.length != 0); } @@ -177,9 +196,8 @@ final class MockMvcFilterDecorator implements Filter { } public void initIfRequired(@Nullable ServletContext servletContext) throws ServletException { - if (this.initParams != null) { - MockFilterConfig filterConfig = new MockFilterConfig(servletContext); - this.initParams.forEach(filterConfig::addInitParameter); + if (this.filterConfigInitializer != null) { + FilterConfig filterConfig = this.filterConfigInitializer.apply(servletContext); this.delegate.init(filterConfig); } } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/setup/MockMvcFilterDecoratorTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/setup/MockMvcFilterDecoratorTests.java index 4b53b76880..f6d16ca1cc 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/setup/MockMvcFilterDecoratorTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/setup/MockMvcFilterDecoratorTests.java @@ -66,14 +66,14 @@ public class MockMvcFilterDecoratorTests { @Test public void init() throws Exception { FilterConfig config = new MockFilterConfig(); - filter = new MockMvcFilterDecorator(delegate, null, null, "/"); + filter = new MockMvcFilterDecorator(delegate, new String[] {"/"}); filter.init(config); assertThat(delegate.filterConfig).isEqualTo(config); } @Test public void destroy() { - filter = new MockMvcFilterDecorator(delegate, null, null, "/"); + filter = new MockMvcFilterDecorator(delegate, new String[] {"/"}); filter.destroy(); assertThat(delegate.destroy).isTrue(); } @@ -251,7 +251,7 @@ public class MockMvcFilterDecoratorTests { request.setDispatcherType(requestDispatcherType); request.setRequestURI(request.getContextPath() + requestUri); - filter = new MockMvcFilterDecorator(delegate, null, EnumSet.of(filterDispatcherType), pattern); + filter = new MockMvcFilterDecorator(delegate, null, null, EnumSet.of(filterDispatcherType), pattern); filter.doFilter(request, response, filterChain); assertThat(delegate.request).isNull(); @@ -265,7 +265,7 @@ public class MockMvcFilterDecoratorTests { private void assertFilterInvoked(String requestUri, String pattern) throws Exception { request.setRequestURI(request.getContextPath() + requestUri); - filter = new MockMvcFilterDecorator(delegate, null, null, pattern); + filter = new MockMvcFilterDecorator(delegate, new String[] {pattern}); filter.doFilter(request, response, filterChain); assertThat(delegate.request).isEqualTo(request); diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilderTests.java index c350cdd297..854ca7ad4b 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilderTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilderTests.java @@ -134,7 +134,7 @@ class StandaloneMockMvcBuilderTests { ArgumentCaptor captor = ArgumentCaptor.forClass(FilterConfig.class); MockMvcBuilders.standaloneSetup(new PersonController()) - .addFilter(filter, Map.of("p", "v"), EnumSet.of(DispatcherType.REQUEST), "/") + .addFilter(filter, null, Map.of("p", "v"), EnumSet.of(DispatcherType.REQUEST), "/") .build(); verify(filter, times(1)).init(captor.capture()); diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java index 281093fee5..7c71ea6b81 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java @@ -132,22 +132,12 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle } if (binderFactory != null && (arg != null || !hasDefaultValue)) { - WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name); - try { - arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter); - } - catch (ConversionNotSupportedException ex) { - throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(), - namedValueInfo.name, parameter, ex.getCause()); - } - catch (TypeMismatchException ex) { - throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(), - namedValueInfo.name, parameter, ex.getCause()); - } + arg = convertIfNecessary(parameter, webRequest, binderFactory, namedValueInfo, arg); // Check for null value after conversion of incoming argument value if (arg == null) { if (namedValueInfo.defaultValue != null) { arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue); + arg = convertIfNecessary(parameter, webRequest, binderFactory, namedValueInfo, arg); } else if (namedValueInfo.required && !nestedParameter.isOptional()) { handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest); @@ -284,6 +274,25 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle return value; } + private static Object convertIfNecessary( + MethodParameter parameter, NativeWebRequest webRequest, WebDataBinderFactory binderFactory, + NamedValueInfo namedValueInfo, Object arg) throws Exception { + + WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name); + try { + arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter); + } + catch (ConversionNotSupportedException ex) { + throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(), + namedValueInfo.name, parameter, ex.getCause()); + } + catch (TypeMismatchException ex) { + throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(), + namedValueInfo.name, parameter, ex.getCause()); + } + return arg; + } + /** * Invoked after a value is resolved. * @param arg the resolved argument value