From 23160a43dd5d7a34e51b4b6918c9eb6a3831ecdf Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Tue, 4 Jun 2024 15:40:38 +0100 Subject: [PATCH] Conditional use of URI vars for data binding in WebFlux This commit aligns Spring WebFlux with WebMvc in adding URI variables conditionally if not already in the map. The idea is that data binding is primarily through form data and query params, with URI variables, and request headers (to be implemented next) also made available but without shadowing existing values. See gh-32676 --- .../web/reactive/BindingContext.java | 29 +++++++++++++++---- .../web/reactive/BindingContextTests.java | 25 ++++++++++++++++ 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/BindingContext.java b/spring-webflux/src/main/java/org/springframework/web/reactive/BindingContext.java index 767ea0a7b2..fa0a4aefe2 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/BindingContext.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/BindingContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -18,7 +18,6 @@ package org.springframework.web.reactive; import java.lang.annotation.Annotation; import java.util.Collection; -import java.util.Collections; import java.util.Map; import reactor.core.publisher.Mono; @@ -29,6 +28,7 @@ import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.ResolvableType; import org.springframework.lang.Nullable; import org.springframework.ui.Model; +import org.springframework.util.CollectionUtils; import org.springframework.validation.BindingResult; import org.springframework.validation.DataBinder; import org.springframework.validation.SmartValidator; @@ -209,10 +209,29 @@ public class BindingContext { @Override public Mono> getValuesToBind(ServerWebExchange exchange) { - return super.getValuesToBind(exchange).doOnNext(map -> - map.putAll(exchange.>getAttributeOrDefault( - HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Collections.emptyMap()))); + return super.getValuesToBind(exchange).doOnNext(map -> { + Map vars = exchange.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + if (!CollectionUtils.isEmpty(vars)) { + vars.forEach((key, value) -> addValueIfNotPresent(map, "URI variable", key, value)); + } + }); } + + private static void addValueIfNotPresent( + Map map, String label, String name, @Nullable Object value) { + + if (value != null) { + if (map.containsKey(name)) { + if (logger.isDebugEnabled()) { + logger.debug(label + " '" + name + "' overridden by request bind value."); + } + } + else { + map.put(name, value); + } + } + } + } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/BindingContextTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/BindingContextTests.java index b058c03df7..7ffc03234f 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/BindingContextTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/BindingContextTests.java @@ -17,16 +17,20 @@ package org.springframework.web.reactive; import java.lang.reflect.Method; +import java.util.Map; import jakarta.validation.Valid; import org.junit.jupiter.api.Test; +import org.springframework.beans.testfixture.beans.TestBean; import org.springframework.core.ResolvableType; +import org.springframework.http.MediaType; import org.springframework.validation.Errors; import org.springframework.validation.SmartValidator; import org.springframework.validation.Validator; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.support.WebExchangeDataBinder; import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest; import org.springframework.web.testfixture.server.MockServerWebExchange; @@ -64,6 +68,27 @@ class BindingContextTests { assertThat(binder.getValidatorsToApply()).containsExactly(springValidator); } + @Test + void uriVariablesAddedConditionally() { + + MockServerHttpRequest request = MockServerHttpRequest.post("/path") + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .body("name=John&age=25"); + + MockServerWebExchange exchange = MockServerWebExchange.from(request); + exchange.getAttributes().put(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Map.of("age", "26")); + + TestBean testBean = new TestBean(); + + BindingContext bindingContext = new BindingContext(null); + WebExchangeDataBinder binder = bindingContext.createDataBinder(exchange, testBean, "testBean", null); + + binder.bind(exchange).block(); + + assertThat(testBean.getName()).isEqualTo("John"); + assertThat(testBean.getAge()).isEqualTo(25); + } + @SuppressWarnings("unused") private void handleValidObject(@Valid Foo foo) {