Partially revert RequestPredicates attribute handling

This commit partially reverts 39786e4790
and c5c843696b, as the approach taken did
not take into account request predicates that query request attributes,
including path variables.

Closes gh-31732
This commit is contained in:
Arjen Poutsma 2023-12-04 10:06:28 +01:00
parent aa347e5fe6
commit 52d4b83dba
5 changed files with 499 additions and 366 deletions

View File

@ -27,7 +27,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@ -315,7 +314,28 @@ public abstract class RequestPredicates {
else {
return newPattern;
}
}
private static <K, V> Map<K, V> mergeMaps(Map<K, V> left, Map<K, V> right) {
if (left.isEmpty()) {
if (right.isEmpty()) {
return Collections.emptyMap();
}
else {
return right;
}
}
else {
if (right.isEmpty()) {
return left;
}
else {
Map<K, V> result = new LinkedHashMap<>(left.size() + right.size());
result.putAll(left);
result.putAll(right);
return result;
}
}
}
@ -452,7 +472,7 @@ public abstract class RequestPredicates {
Result result = testInternal(request);
boolean value = result.value();
if (value) {
result.modify(request);
result.modifyAttributes(request.attributes());
}
return value;
}
@ -470,12 +490,12 @@ public abstract class RequestPredicates {
private final boolean value;
@Nullable
private final Consumer<ServerRequest> modify;
private final Consumer<Map<String, Object>> modifyAttributes;
private Result(boolean value, @Nullable Consumer<ServerRequest> modify) {
private Result(boolean value, @Nullable Consumer<Map<String, Object>> modifyAttributes) {
this.value = value;
this.modify = modify;
this.modifyAttributes = modifyAttributes;
}
@ -483,12 +503,12 @@ public abstract class RequestPredicates {
return of(value, null);
}
public static Result of(boolean value, @Nullable Consumer<ServerRequest> commit) {
if (commit == null) {
public static Result of(boolean value, @Nullable Consumer<Map<String, Object>> modifyAttributes) {
if (modifyAttributes == null) {
return value ? TRUE : FALSE;
}
else {
return new Result(value, commit);
return new Result(value, modifyAttributes);
}
}
@ -497,11 +517,15 @@ public abstract class RequestPredicates {
return this.value;
}
public void modify(ServerRequest request) {
if (this.modify != null) {
this.modify.accept(request);
public void modifyAttributes(Map<String, Object> attributes) {
if (this.modifyAttributes != null) {
this.modifyAttributes.accept(attributes);
}
}
public boolean modifiesAttributes() {
return this.modifyAttributes != null;
}
}
}
@ -575,29 +599,32 @@ public abstract class RequestPredicates {
PathPattern.PathMatchInfo info = this.pattern.matchAndExtract(pathContainer);
traceMatch("Pattern", this.pattern.getPatternString(), request.path(), info != null);
if (info != null) {
return Result.of(true, serverRequest -> mergeAttributes(serverRequest, info.getUriVariables()));
return Result.of(true, attributes -> modifyAttributes(attributes, request, info.getUriVariables()));
}
else {
return Result.of(false);
}
}
private void mergeAttributes(ServerRequest request, Map<String, String> variables) {
Map<String, Object> attributes = request.attributes();
Map<String, String> pathVariables = mergePathVariables(request.pathVariables(), variables);
private void modifyAttributes(Map<String, Object> attributes, ServerRequest request,
Map<String, String> variables) {
Map<String, String> pathVariables = mergeMaps(request.pathVariables(), variables);
attributes.put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
Collections.unmodifiableMap(pathVariables));
PathPattern pattern = mergePatterns(
(PathPattern) attributes.get(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE),
this.pattern);
attributes.put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, pattern);
}
@Override
public Optional<ServerRequest> nest(ServerRequest request) {
return Optional.ofNullable(this.pattern.matchStartOfPath(request.requestPath().pathWithinApplication()))
.map(info -> new SubPathServerRequestWrapper(request, info, this.pattern));
.map(info -> new NestedPathPatternServerRequestWrapper(request, info, this.pattern));
}
@Override
@ -861,13 +888,23 @@ public abstract class RequestPredicates {
if (!leftResult.value()) {
return leftResult;
}
Result rightResult = this.rightModifying.testInternal(request);
// ensure that attributes (and uri variables) set in left and available in right
ServerRequest rightRequest;
if (leftResult.modifiesAttributes()) {
Map<String, Object> leftAttributes = new LinkedHashMap<>(2);
leftResult.modifyAttributes(leftAttributes);
rightRequest = new ExtendedAttributesServerRequestWrapper(request, leftAttributes);
}
else {
rightRequest = request;
}
Result rightResult = this.rightModifying.testInternal(rightRequest);
if (!rightResult.value()) {
return rightResult;
}
return Result.of(true, serverRequest -> {
leftResult.modify(serverRequest);
rightResult.modify(serverRequest);
return Result.of(true, attributes -> {
leftResult.modifyAttributes(attributes);
rightResult.modifyAttributes(attributes);
});
}
@ -923,7 +960,7 @@ public abstract class RequestPredicates {
@Override
protected Result testInternal(ServerRequest request) {
Result result = this.delegateModifying.testInternal(request);
return Result.of(!result.value(), result::modify);
return Result.of(!result.value(), result::modifyAttributes);
}
@Override
@ -1020,19 +1057,194 @@ public abstract class RequestPredicates {
}
private static class SubPathServerRequestWrapper implements ServerRequest {
private final ServerRequest request;
private abstract static class DelegatingServerRequest implements ServerRequest {
private final RequestPath requestPath;
private final ServerRequest delegate;
protected DelegatingServerRequest(ServerRequest delegate) {
Assert.notNull(delegate, "Delegate must not be null");
this.delegate = delegate;
}
@Override
public HttpMethod method() {
return this.delegate.method();
}
@Override
@Deprecated
public String methodName() {
return this.delegate.methodName();
}
@Override
public URI uri() {
return this.delegate.uri();
}
@Override
public UriBuilder uriBuilder() {
return this.delegate.uriBuilder();
}
@Override
public Headers headers() {
return this.delegate.headers();
}
@Override
public MultiValueMap<String, HttpCookie> cookies() {
return this.delegate.cookies();
}
@Override
public Optional<InetSocketAddress> remoteAddress() {
return this.delegate.remoteAddress();
}
@Override
public Optional<InetSocketAddress> localAddress() {
return this.delegate.localAddress();
}
@Override
public List<HttpMessageReader<?>> messageReaders() {
return this.delegate.messageReaders();
}
@Override
public <T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor) {
return this.delegate.body(extractor);
}
@Override
public <T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor, Map<String, Object> hints) {
return this.delegate.body(extractor, hints);
}
@Override
public <T> Mono<T> bodyToMono(Class<? extends T> elementClass) {
return this.delegate.bodyToMono(elementClass);
}
@Override
public <T> Mono<T> bodyToMono(ParameterizedTypeReference<T> typeReference) {
return this.delegate.bodyToMono(typeReference);
}
@Override
public <T> Flux<T> bodyToFlux(Class<? extends T> elementClass) {
return this.delegate.bodyToFlux(elementClass);
}
@Override
public <T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> typeReference) {
return this.delegate.bodyToFlux(typeReference);
}
@Override
public <T> Mono<T> bind(Class<T> bindType, Consumer<WebDataBinder> dataBinderCustomizer) {
return this.delegate.bind(bindType, dataBinderCustomizer);
}
@Override
public Map<String, Object> attributes() {
return this.delegate.attributes();
}
@Override
public MultiValueMap<String, String> queryParams() {
return this.delegate.queryParams();
}
@Override
public Map<String, String> pathVariables() {
return this.delegate.pathVariables();
}
@Override
public Mono<WebSession> session() {
return this.delegate.session();
}
@Override
public Mono<? extends Principal> principal() {
return this.delegate.principal();
}
@Override
public Mono<MultiValueMap<String, String>> formData() {
return this.delegate.formData();
}
@Override
public Mono<MultiValueMap<String, Part>> multipartData() {
return this.delegate.multipartData();
}
@Override
public ServerWebExchange exchange() {
return this.delegate.exchange();
}
@Override
public String toString() {
return this.delegate.toString();
}
}
private static class ExtendedAttributesServerRequestWrapper extends DelegatingServerRequest {
private final Map<String, Object> attributes;
public SubPathServerRequestWrapper(ServerRequest request,
public ExtendedAttributesServerRequestWrapper(ServerRequest delegate, Map<String, Object> newAttributes) {
super(delegate);
Assert.notNull(newAttributes, "NewAttributes must not be null");
this.attributes = mergeMaps(delegate.attributes(), newAttributes);
}
@Override
public Map<String, Object> attributes() {
return this.attributes;
}
@Override
@SuppressWarnings("unchecked")
public Map<String, String> pathVariables() {
return (Map<String, String>) this.attributes.getOrDefault(
RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Collections.emptyMap());
}
}
private static class NestedPathPatternServerRequestWrapper extends ExtendedAttributesServerRequestWrapper {
private final RequestPath requestPath;
public NestedPathPatternServerRequestWrapper(ServerRequest request,
PathPattern.PathRemainingMatchInfo info, PathPattern pattern) {
this.request = request;
super(request, mergeAttributes(request, info.getUriVariables(), pattern));
this.requestPath = requestPath(request.requestPath(), info);
this.attributes = mergeAttributes(request, info.getUriVariables(), pattern);
}
private static Map<String, Object> mergeAttributes(ServerRequest request, Map<String, String> newPathVariables,
PathPattern newPathPattern) {
Map<String, String> oldPathVariables = request.pathVariables();
PathPattern oldPathPattern = (PathPattern) request.attribute(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE)
.orElse(null);
Map<String, Object> result = new LinkedHashMap<>(2);
result.put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, mergeMaps(oldPathVariables, newPathVariables));
result.put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, mergePatterns(oldPathPattern, newPathPattern));
return result;
}
private static RequestPath requestPath(RequestPath original, PathPattern.PathRemainingMatchInfo info) {
@ -1045,163 +1257,10 @@ public abstract class RequestPredicates {
return original.modifyContextPath(contextPath.toString());
}
private static Map<String, Object> mergeAttributes(ServerRequest request,
Map<String, String> pathVariables, PathPattern pattern) {
Map<String, Object> result = new ConcurrentHashMap<>(request.attributes());
result.put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
mergePathVariables(request.pathVariables(), pathVariables));
pattern = mergePatterns(
(PathPattern) request.attributes().get(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE),
pattern);
result.put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, pattern);
return result;
}
@Override
public HttpMethod method() {
return this.request.method();
}
@Override
@Deprecated
public String methodName() {
return this.request.methodName();
}
@Override
public URI uri() {
return this.request.uri();
}
@Override
public UriBuilder uriBuilder() {
return this.request.uriBuilder();
}
@Override
public RequestPath requestPath() {
return this.requestPath;
}
@Override
public Headers headers() {
return this.request.headers();
}
@Override
public MultiValueMap<String, HttpCookie> cookies() {
return this.request.cookies();
}
@Override
public Optional<InetSocketAddress> remoteAddress() {
return this.request.remoteAddress();
}
@Override
public Optional<InetSocketAddress> localAddress() {
return this.request.localAddress();
}
@Override
public List<HttpMessageReader<?>> messageReaders() {
return this.request.messageReaders();
}
@Override
public <T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor) {
return this.request.body(extractor);
}
@Override
public <T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor, Map<String, Object> hints) {
return this.request.body(extractor, hints);
}
@Override
public <T> Mono<T> bodyToMono(Class<? extends T> elementClass) {
return this.request.bodyToMono(elementClass);
}
@Override
public <T> Mono<T> bodyToMono(ParameterizedTypeReference<T> typeReference) {
return this.request.bodyToMono(typeReference);
}
@Override
public <T> Flux<T> bodyToFlux(Class<? extends T> elementClass) {
return this.request.bodyToFlux(elementClass);
}
@Override
public <T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> typeReference) {
return this.request.bodyToFlux(typeReference);
}
@Override
public <T> Mono<T> bind(Class<T> bindType) {
return this.request.bind(bindType);
}
@Override
public <T> Mono<T> bind(Class<T> bindType, Consumer<WebDataBinder> dataBinderCustomizer) {
return this.request.bind(bindType, dataBinderCustomizer);
}
@Override
public Map<String, Object> attributes() {
return this.attributes;
}
@Override
public Optional<String> queryParam(String name) {
return this.request.queryParam(name);
}
@Override
public MultiValueMap<String, String> queryParams() {
return this.request.queryParams();
}
@Override
@SuppressWarnings("unchecked")
public Map<String, String> pathVariables() {
return (Map<String, String>) this.attributes.getOrDefault(
RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Collections.emptyMap());
}
@Override
public Mono<WebSession> session() {
return this.request.session();
}
@Override
public Mono<? extends Principal> principal() {
return this.request.principal();
}
@Override
public Mono<MultiValueMap<String, String>> formData() {
return this.request.formData();
}
@Override
public Mono<MultiValueMap<String, Part>> multipartData() {
return this.request.multipartData();
}
@Override
public ServerWebExchange exchange() {
return this.request.exchange();
}
@Override
public String toString() {
return method() + " " + path();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* 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.
@ -90,7 +90,11 @@ class NestedRouteIntegrationTests extends AbstractRouterFunctionIntegrationTests
restTemplate.getForEntity("http://localhost:" + port + "/1/2/3", String.class);
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(result.getBody()).isEqualTo("/{foo}/{bar}/{baz}\n{foo=1, bar=2, baz=3}");
String body = result.getBody();
assertThat(body).startsWith("/{foo}/{bar}/{baz}");
assertThat(body).contains("foo=1");
assertThat(body).contains("bar=2");
assertThat(body).contains("baz=3");
}
// SPR-16868

View File

@ -200,7 +200,7 @@ public class RequestPredicateAttributesTests {
@Override
protected Result testInternal(ServerRequest request) {
return Result.of(this.result, serverRequest -> serverRequest.attributes().put(this.key, this.value));
return Result.of(this.result, attributes -> attributes.put(this.key, this.value));
}
}

View File

@ -29,7 +29,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@ -294,19 +293,6 @@ public abstract class RequestPredicates {
}
}
private static Map<String, String> mergePathVariables(Map<String, String> oldVariables,
Map<String, String> newVariables) {
if (!newVariables.isEmpty()) {
Map<String, String> mergedVariables = new LinkedHashMap<>(oldVariables);
mergedVariables.putAll(newVariables);
return mergedVariables;
}
else {
return oldVariables;
}
}
private static PathPattern mergePatterns(@Nullable PathPattern oldPattern, PathPattern newPattern) {
if (oldPattern != null) {
return oldPattern.combine(newPattern);
@ -314,7 +300,28 @@ public abstract class RequestPredicates {
else {
return newPattern;
}
}
private static <K, V> Map<K, V> mergeMaps(Map<K, V> left, Map<K, V> right) {
if (left.isEmpty()) {
if (right.isEmpty()) {
return Collections.emptyMap();
}
else {
return right;
}
}
else {
if (right.isEmpty()) {
return left;
}
else {
Map<K, V> result = new LinkedHashMap<>(left.size() + right.size());
result.putAll(left);
result.putAll(right);
return result;
}
}
}
@ -451,7 +458,7 @@ public abstract class RequestPredicates {
Result result = testInternal(request);
boolean value = result.value();
if (value) {
result.modify(request);
result.modifyAttributes(request.attributes());
}
return value;
}
@ -469,12 +476,12 @@ public abstract class RequestPredicates {
private final boolean value;
@Nullable
private final Consumer<ServerRequest> modify;
private final Consumer<Map<String, Object>> modifyAttributes;
private Result(boolean value, @Nullable Consumer<ServerRequest> modify) {
private Result(boolean value, @Nullable Consumer<Map<String, Object>> modifyAttributes) {
this.value = value;
this.modify = modify;
this.modifyAttributes = modifyAttributes;
}
@ -482,12 +489,12 @@ public abstract class RequestPredicates {
return of(value, null);
}
public static Result of(boolean value, @Nullable Consumer<ServerRequest> commit) {
if (commit == null) {
public static Result of(boolean value, @Nullable Consumer<Map<String, Object>> modifyAttributes) {
if (modifyAttributes == null) {
return value ? TRUE : FALSE;
}
else {
return new Result(value, commit);
return new Result(value, modifyAttributes);
}
}
@ -496,11 +503,15 @@ public abstract class RequestPredicates {
return this.value;
}
public void modify(ServerRequest request) {
if (this.modify != null) {
this.modify.accept(request);
public void modifyAttributes(Map<String, Object> attributes) {
if (this.modifyAttributes != null) {
this.modifyAttributes.accept(attributes);
}
}
public boolean modifiesAttributes() {
return this.modifyAttributes != null;
}
}
}
@ -574,29 +585,32 @@ public abstract class RequestPredicates {
PathPattern.PathMatchInfo info = this.pattern.matchAndExtract(pathContainer);
traceMatch("Pattern", this.pattern.getPatternString(), request.path(), info != null);
if (info != null) {
return Result.of(true, serverRequest -> mergeAttributes(serverRequest, info.getUriVariables()));
return Result.of(true, attributes -> modifyAttributes(attributes, request, info.getUriVariables()));
}
else {
return Result.of(false);
}
}
private void mergeAttributes(ServerRequest request, Map<String, String> variables) {
Map<String, Object> attributes = request.attributes();
Map<String, String> pathVariables = mergePathVariables(request.pathVariables(), variables);
private void modifyAttributes(Map<String, Object> attributes, ServerRequest request,
Map<String, String> variables) {
Map<String, String> pathVariables = mergeMaps(request.pathVariables(), variables);
attributes.put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
Collections.unmodifiableMap(pathVariables));
PathPattern pattern = mergePatterns(
(PathPattern) attributes.get(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE),
this.pattern);
attributes.put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, pattern);
}
@Override
public Optional<ServerRequest> nest(ServerRequest request) {
return Optional.ofNullable(this.pattern.matchStartOfPath(request.requestPath().pathWithinApplication()))
.map(info -> new SubPathServerRequestWrapper(request, info, this.pattern));
.map(info -> new NestedPathPatternServerRequestWrapper(request, info, this.pattern));
}
@Override
@ -860,13 +874,23 @@ public abstract class RequestPredicates {
if (!leftResult.value()) {
return leftResult;
}
Result rightResult = this.rightModifying.testInternal(request);
// ensure that attributes (and uri variables) set in left and available in right
ServerRequest rightRequest;
if (leftResult.modifiesAttributes()) {
Map<String, Object> leftAttributes = new LinkedHashMap<>(2);
leftResult.modifyAttributes(leftAttributes);
rightRequest = new ExtendedAttributesServerRequestWrapper(request, leftAttributes);
}
else {
rightRequest = request;
}
Result rightResult = this.rightModifying.testInternal(rightRequest);
if (!rightResult.value()) {
return rightResult;
}
return Result.of(true, serverRequest -> {
leftResult.modify(serverRequest);
rightResult.modify(serverRequest);
return Result.of(true, attributes -> {
leftResult.modifyAttributes(attributes);
rightResult.modifyAttributes(attributes);
});
}
@ -922,7 +946,7 @@ public abstract class RequestPredicates {
@Override
protected Result testInternal(ServerRequest request) {
Result result = this.delegateModifying.testInternal(request);
return Result.of(!result.value(), result::modify);
return Result.of(!result.value(), result::modifyAttributes);
}
@Override
@ -1019,19 +1043,194 @@ public abstract class RequestPredicates {
}
private static class SubPathServerRequestWrapper implements ServerRequest {
private final ServerRequest request;
private abstract static class DelegatingServerRequest implements ServerRequest {
private final RequestPath requestPath;
private final ServerRequest delegate;
protected DelegatingServerRequest(ServerRequest delegate) {
Assert.notNull(delegate, "Delegate must not be null");
this.delegate = delegate;
}
@Override
public HttpMethod method() {
return this.delegate.method();
}
@Override
@Deprecated
public String methodName() {
return this.delegate.methodName();
}
@Override
public URI uri() {
return this.delegate.uri();
}
@Override
public UriBuilder uriBuilder() {
return this.delegate.uriBuilder();
}
@Override
public String path() {
return this.delegate.path();
}
@Override
public RequestPath requestPath() {
return this.delegate.requestPath();
}
@Override
public Headers headers() {
return this.delegate.headers();
}
@Override
public MultiValueMap<String, Cookie> cookies() {
return this.delegate.cookies();
}
@Override
public Optional<InetSocketAddress> remoteAddress() {
return this.delegate.remoteAddress();
}
@Override
public List<HttpMessageConverter<?>> messageConverters() {
return this.delegate.messageConverters();
}
@Override
public <T> T body(Class<T> bodyType) throws ServletException, IOException {
return this.delegate.body(bodyType);
}
@Override
public <T> T body(ParameterizedTypeReference<T> bodyType) throws ServletException, IOException {
return this.delegate.body(bodyType);
}
@Override
public <T> T bind(Class<T> bindType) throws BindException {
return this.delegate.bind(bindType);
}
@Override
public <T> T bind(Class<T> bindType, Consumer<WebDataBinder> dataBinderCustomizer) throws BindException {
return this.delegate.bind(bindType, dataBinderCustomizer);
}
@Override
public Map<String, Object> attributes() {
return this.delegate.attributes();
}
@Override
public MultiValueMap<String, String> params() {
return this.delegate.params();
}
@Override
public MultiValueMap<String, Part> multipartData() throws IOException, ServletException {
return this.delegate.multipartData();
}
@Override
public Map<String, String> pathVariables() {
return this.delegate.pathVariables();
}
@Override
public HttpSession session() {
return this.delegate.session();
}
@Override
public Optional<Principal> principal() {
return this.delegate.principal();
}
@Override
public HttpServletRequest servletRequest() {
return this.delegate.servletRequest();
}
@Override
public Optional<ServerResponse> checkNotModified(Instant lastModified) {
return this.delegate.checkNotModified(lastModified);
}
@Override
public Optional<ServerResponse> checkNotModified(String etag) {
return this.delegate.checkNotModified(etag);
}
@Override
public Optional<ServerResponse> checkNotModified(Instant lastModified, String etag) {
return this.delegate.checkNotModified(lastModified, etag);
}
@Override
public String toString() {
return this.delegate.toString();
}
}
private static class ExtendedAttributesServerRequestWrapper extends DelegatingServerRequest {
private final Map<String, Object> attributes;
public SubPathServerRequestWrapper(ServerRequest request,
public ExtendedAttributesServerRequestWrapper(ServerRequest delegate, Map<String, Object> newAttributes) {
super(delegate);
Assert.notNull(newAttributes, "NewAttributes must not be null");
this.attributes = mergeMaps(delegate.attributes(), newAttributes);
}
@Override
public Map<String, Object> attributes() {
return this.attributes;
}
@Override
@SuppressWarnings("unchecked")
public Map<String, String> pathVariables() {
return (Map<String, String>) this.attributes.getOrDefault(
RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Collections.emptyMap());
}
}
private static class NestedPathPatternServerRequestWrapper extends ExtendedAttributesServerRequestWrapper {
private final RequestPath requestPath;
public NestedPathPatternServerRequestWrapper(ServerRequest request,
PathPattern.PathRemainingMatchInfo info, PathPattern pattern) {
this.request = request;
super(request, mergeAttributes(request, info.getUriVariables(), pattern));
this.requestPath = requestPath(request.requestPath(), info);
this.attributes = mergeAttributes(request, info.getUriVariables(), pattern);
}
private static Map<String, Object> mergeAttributes(ServerRequest request, Map<String, String> newPathVariables,
PathPattern newPathPattern) {
Map<String, String> oldPathVariables = request.pathVariables();
PathPattern oldPathPattern = (PathPattern) request.attribute(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE)
.orElse(null);
Map<String, Object> result = new LinkedHashMap<>(2);
result.put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, mergeMaps(oldPathVariables, newPathVariables));
result.put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, mergePatterns(oldPathPattern, newPathPattern));
return result;
}
private static RequestPath requestPath(RequestPath original, PathPattern.PathRemainingMatchInfo info) {
@ -1044,153 +1243,10 @@ public abstract class RequestPredicates {
return original.modifyContextPath(contextPath.toString());
}
private static Map<String, Object> mergeAttributes(ServerRequest request,
Map<String, String> pathVariables, PathPattern pattern) {
Map<String, Object> result = new ConcurrentHashMap<>(request.attributes());
result.put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
mergePathVariables(request.pathVariables(), pathVariables));
pattern = mergePatterns(
(PathPattern) request.attributes().get(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE),
pattern);
result.put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, pattern);
return result;
}
@Override
public HttpMethod method() {
return this.request.method();
}
@Override
@Deprecated
public String methodName() {
return this.request.methodName();
}
@Override
public URI uri() {
return this.request.uri();
}
@Override
public UriBuilder uriBuilder() {
return this.request.uriBuilder();
}
@Override
public RequestPath requestPath() {
return this.requestPath;
}
@Override
public Headers headers() {
return this.request.headers();
}
@Override
public MultiValueMap<String, Cookie> cookies() {
return this.request.cookies();
}
@Override
public Optional<InetSocketAddress> remoteAddress() {
return this.request.remoteAddress();
}
@Override
public List<HttpMessageConverter<?>> messageConverters() {
return this.request.messageConverters();
}
@Override
public <T> T body(Class<T> bodyType) throws ServletException, IOException {
return this.request.body(bodyType);
}
@Override
public <T> T body(ParameterizedTypeReference<T> bodyType)
throws ServletException, IOException {
return this.request.body(bodyType);
}
@Override
public <T> T bind(Class<T> bindType) throws BindException {
return this.request.bind(bindType);
}
@Override
public <T> T bind(Class<T> bindType, Consumer<WebDataBinder> dataBinderCustomizer) throws BindException {
return this.request.bind(bindType, dataBinderCustomizer);
}
@Override
public Optional<Object> attribute(String name) {
return this.request.attribute(name);
}
@Override
public Map<String, Object> attributes() {
return this.attributes;
}
@Override
public Optional<String> param(String name) {
return this.request.param(name);
}
@Override
public MultiValueMap<String, String> params() {
return this.request.params();
}
@Override
public MultiValueMap<String, Part> multipartData() throws IOException, ServletException {
return this.request.multipartData();
}
@Override
@SuppressWarnings("unchecked")
public Map<String, String> pathVariables() {
return (Map<String, String>) this.attributes.getOrDefault(
RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Collections.emptyMap());
}
@Override
public HttpSession session() {
return this.request.session();
}
@Override
public Optional<Principal> principal() {
return this.request.principal();
}
@Override
public HttpServletRequest servletRequest() {
return this.request.servletRequest();
}
@Override
public Optional<ServerResponse> checkNotModified(Instant lastModified) {
return this.request.checkNotModified(lastModified);
}
@Override
public Optional<ServerResponse> checkNotModified(String etag) {
return this.request.checkNotModified(etag);
}
@Override
public Optional<ServerResponse> checkNotModified(Instant lastModified, String etag) {
return this.request.checkNotModified(lastModified, etag);
}
@Override
public String toString() {
return method() + " " + path();
}
}
}

View File

@ -114,4 +114,18 @@ public class RouterFunctionsTests {
assertThat(resultHandlerFunction).contains(handlerFunction);
}
@Test
public void composedPathVariable() {
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().build();
RequestPredicate requestPredicate = RequestPredicates.path("/{foo}").and(
request -> request.pathVariable("foo").equals("bar"));
RouterFunction<ServerResponse> routerFunction = RouterFunctions.route(requestPredicate, handlerFunction);
MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/bar");
ServerRequest request = new DefaultServerRequest(servletRequest, Collections.emptyList());
Optional<HandlerFunction<ServerResponse>> resultHandlerFunction = routerFunction.route(request);
assertThat(resultHandlerFunction).isPresent();
assertThat(resultHandlerFunction).contains(handlerFunction);
}
}