diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfo.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfo.java index 57c64954c4..736e46ce60 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfo.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -89,29 +89,27 @@ public final class RequestMappingInfo implements RequestCondition custom) { - this.name = (StringUtils.hasText(name) ? name : null); - this.patternsCondition = (patterns != null ? patterns : EMPTY_PATTERNS); - this.methodsCondition = (methods != null ? methods : EMPTY_REQUEST_METHODS); - this.paramsCondition = (params != null ? params : EMPTY_PARAMS); - this.headersCondition = (headers != null ? headers : EMPTY_HEADERS); - this.consumesCondition = (consumes != null ? consumes : EMPTY_CONSUMES); - this.producesCondition = (produces != null ? produces : EMPTY_PRODUCES); - this.customConditionHolder = (custom != null ? new RequestConditionHolder(custom) : EMPTY_CUSTOM); - - this.hashCode = calculateHashCode( - this.patternsCondition, this.methodsCondition, this.paramsCondition, this.headersCondition, - this.consumesCondition, this.producesCondition, this.customConditionHolder); + this(name, patterns, methods, params, headers, consumes, produces, custom, new BuilderConfiguration()); } /** - * Creates a new instance with the given request conditions. + * Create an instance with the given conditions. + * @deprecated as of 5.3.4 in favor using {@link RequestMappingInfo.Builder} via {@link #paths(String...)}. */ + @Deprecated public RequestMappingInfo(@Nullable PatternsRequestCondition patterns, @Nullable RequestMethodsRequestCondition methods, @Nullable ParamsRequestCondition params, @Nullable HeadersRequestCondition headers, @Nullable ConsumesRequestCondition consumes, @@ -122,12 +120,35 @@ public final class RequestMappingInfo implements RequestCondition customRequestCondition) { this(info.name, info.patternsCondition, info.methodsCondition, info.paramsCondition, info.headersCondition, info.consumesCondition, info.producesCondition, customRequestCondition); } + private RequestMappingInfo(@Nullable String name, @Nullable PatternsRequestCondition patterns, + @Nullable RequestMethodsRequestCondition methods, @Nullable ParamsRequestCondition params, + @Nullable HeadersRequestCondition headers, @Nullable ConsumesRequestCondition consumes, + @Nullable ProducesRequestCondition produces, @Nullable RequestCondition custom, + BuilderConfiguration options) { + + this.name = (StringUtils.hasText(name) ? name : null); + this.patternsCondition = (patterns != null ? patterns : EMPTY_PATTERNS); + this.methodsCondition = (methods != null ? methods : EMPTY_REQUEST_METHODS); + this.paramsCondition = (params != null ? params : EMPTY_PARAMS); + this.headersCondition = (headers != null ? headers : EMPTY_HEADERS); + this.consumesCondition = (consumes != null ? consumes : EMPTY_CONSUMES); + this.producesCondition = (produces != null ? produces : EMPTY_PRODUCES); + this.customConditionHolder = (custom != null ? new RequestConditionHolder(custom) : EMPTY_CUSTOM); + this.options = options; + + this.hashCode = calculateHashCode( + this.patternsCondition, this.methodsCondition, this.paramsCondition, this.headersCondition, + this.consumesCondition, this.producesCondition, this.customConditionHolder); + } + /** * Return the name for this mapping, or {@code null}. @@ -219,7 +240,7 @@ public final class RequestMappingInfo implements RequestCondition parse(String[] patterns, PathPatternParser parser) { + static List parse(String[] patterns, PathPatternParser parser) { if (isEmpty(patterns)) { return Collections.emptyList(); } @@ -581,7 +611,7 @@ public final class RequestMappingInfo implements RequestCondition condition) { + this.customConditionHolder = new RequestConditionHolder(condition); + return this; + } + + @Override + public Builder options(BuilderConfiguration options) { + this.options = options; + return this; + } + + @Override + public RequestMappingInfo build() { + return new RequestMappingInfo(this.name, this.patternsCondition, + this.methodsCondition, this.paramsCondition, this.headersCondition, + this.consumesCondition, this.producesCondition, + this.customConditionHolder, this.options); + } + } + + /** * Container for configuration options used for request mapping purposes. * Such configuration is required to create RequestMappingInfo instances but diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/RequestMappingInfoTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/RequestMappingInfoTests.java index badbb70024..79d34e2750 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/RequestMappingInfoTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/condition/RequestMappingInfoTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -38,6 +38,7 @@ import org.springframework.web.util.pattern.PatternParseException; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.reactive.result.method.RequestMappingInfo.paths; /** @@ -292,11 +293,10 @@ public class RequestMappingInfoTests { @Test @Disabled - public void preFlightRequest() throws Exception { + public void preFlightRequest() { MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.options("/foo") .header("Origin", "https://domain.com") - .header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "POST") - ); + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "POST")); RequestMappingInfo info = paths("/foo").methods(RequestMethod.POST).build(); RequestMappingInfo match = info.getMatchingCondition(exchange); @@ -307,4 +307,26 @@ public class RequestMappingInfoTests { assertThat(match).as("Pre-flight should match the ACCESS_CONTROL_REQUEST_METHOD").isNull(); } + @Test + void mutate() { + RequestMappingInfo.BuilderConfiguration options = new RequestMappingInfo.BuilderConfiguration(); + options.setPatternParser(new PathPatternParser()); + + RequestMappingInfo info1 = RequestMappingInfo.paths("/foo") + .methods(GET).headers("h1=hv1").params("q1=qv1") + .consumes("application/json").produces("application/json") + .mappingName("testMapping").options(options) + .build(); + + RequestMappingInfo info2 = info1.mutate().produces("application/hal+json").build(); + + assertThat(info2.getName()).isEqualTo(info1.getName()); + assertThat(info2.getPatternsCondition()).isEqualTo(info1.getPatternsCondition()); + assertThat(info2.getHeadersCondition()).isEqualTo(info1.getHeadersCondition()); + assertThat(info2.getParamsCondition()).isEqualTo(info1.getParamsCondition()); + assertThat(info2.getConsumesCondition()).isEqualTo(info1.getConsumesCondition()); + assertThat(info2.getProducesCondition().getProducibleMediaTypes()) + .containsOnly(MediaType.parseMediaType("application/hal+json")); + } + } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java index 62f517a4c7..9633b97d30 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -105,6 +105,8 @@ public final class RequestMappingInfo implements RequestCondition customCondition) { - return new RequestMappingInfo(this.name, this.pathPatternsCondition, this.patternsCondition, + return new RequestMappingInfo(this.name, + this.pathPatternsCondition, this.patternsCondition, this.methodsCondition, this.paramsCondition, this.headersCondition, - this.consumesCondition, this.producesCondition, new RequestConditionHolder(customCondition)); + this.consumesCondition, this.producesCondition, + new RequestConditionHolder(customCondition), this.options); } /** @@ -337,8 +344,8 @@ public final class RequestMappingInfo implements RequestCondition condition) { + this.customConditionHolder = new RequestConditionHolder(condition); + return this; + } + + @Override + public Builder options(BuilderConfiguration options) { + this.options = options; + return this; + } + + @Override + public RequestMappingInfo build() { + return new RequestMappingInfo(this.name, + this.pathPatternsCondition, this.patternsCondition, + this.methodsCondition, this.paramsCondition, this.headersCondition, + this.consumesCondition, this.producesCondition, + this.customConditionHolder, this.options); } } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoTests.java index 3a8bca0742..11a435d25d 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -26,11 +26,11 @@ import javax.servlet.http.HttpServletRequest; import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.handler.PathPatternsParameterizedTest; import org.springframework.web.servlet.handler.PathPatternsTestUtils; import org.springframework.web.testfixture.servlet.MockHttpServletRequest; -import org.springframework.web.util.UrlPathHelper; import org.springframework.web.util.pattern.PathPatternParser; import static java.util.Arrays.asList; @@ -108,7 +108,6 @@ class RequestMappingInfoTests { void matchParamsCondition() { MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/foo", false); request.setParameter("foo", "bar"); - UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request); RequestMappingInfo info = RequestMappingInfo.paths("/foo").params("foo=bar").build(); RequestMappingInfo match = info.getMatchingCondition(request); @@ -125,7 +124,6 @@ class RequestMappingInfoTests { void matchHeadersCondition() { MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/foo", false); request.addHeader("foo", "bar"); - UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request); RequestMappingInfo info = RequestMappingInfo.paths("/foo").headers("foo=bar").build(); RequestMappingInfo match = info.getMatchingCondition(request); @@ -142,7 +140,6 @@ class RequestMappingInfoTests { void matchConsumesCondition() { MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/foo", false); request.setContentType("text/plain"); - UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request); RequestMappingInfo info = RequestMappingInfo.paths("/foo").consumes("text/plain").build(); RequestMappingInfo match = info.getMatchingCondition(request); @@ -159,7 +156,6 @@ class RequestMappingInfoTests { void matchProducesCondition() { MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/foo", false); request.addHeader("Accept", "text/plain"); - UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request); RequestMappingInfo info = RequestMappingInfo.paths("/foo").produces("text/plain").build(); RequestMappingInfo match = info.getMatchingCondition(request); @@ -176,7 +172,6 @@ class RequestMappingInfoTests { void matchCustomCondition() { MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/foo", false); request.setParameter("foo", "bar"); - UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request); RequestMappingInfo info = RequestMappingInfo.paths("/foo").params("foo=bar").build(); RequestMappingInfo match = info.getMatchingCondition(request); @@ -196,7 +191,6 @@ class RequestMappingInfoTests { RequestMappingInfo oneMethodOneParam = RequestMappingInfo.paths().methods(GET).params("foo").build(); MockHttpServletRequest request = PathPatternsTestUtils.initRequest("GET", "/", false); - UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request); Comparator comparator = (info, otherInfo) -> info.compareTo(otherInfo, request); List list = asList(noMethods, oneMethod, oneMethodOneParam); @@ -317,4 +311,27 @@ class RequestMappingInfoTests { assertThat(match).as("Pre-flight should match the ACCESS_CONTROL_REQUEST_METHOD").isNull(); } + @Test + void mutate() { + RequestMappingInfo.BuilderConfiguration options = new RequestMappingInfo.BuilderConfiguration(); + options.setPatternParser(new PathPatternParser()); + + RequestMappingInfo info1 = RequestMappingInfo.paths("/foo") + .methods(GET).headers("h1=hv1").params("q1=qv1") + .consumes("application/json").produces("application/json") + .mappingName("testMapping").options(options) + .build(); + + RequestMappingInfo info2 = info1.mutate().produces("application/hal+json").build(); + + assertThat(info2.getName()).isEqualTo(info1.getName()); + assertThat(info2.getPatternsCondition()).isNull(); + assertThat(info2.getPathPatternsCondition()).isEqualTo(info1.getPathPatternsCondition()); + assertThat(info2.getHeadersCondition()).isEqualTo(info1.getHeadersCondition()); + assertThat(info2.getParamsCondition()).isEqualTo(info1.getParamsCondition()); + assertThat(info2.getConsumesCondition()).isEqualTo(info1.getConsumesCondition()); + assertThat(info2.getProducesCondition().getProducibleMediaTypes()) + .containsOnly(MediaType.parseMediaType("application/hal+json")); + } + }