Skip to content

Commit

Permalink
Support headers in DataBinding via constructor args
Browse files Browse the repository at this point in the history
Closes gh-34073
  • Loading branch information
rstoyanchev committed Dec 11, 2024
1 parent 7b4e19c commit 70c326e
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.support.WebExchangeDataBinder;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.server.ServerWebExchange;
Expand Down Expand Up @@ -57,7 +58,11 @@ public Mono<Map<String, Object>> getValuesToBind(ServerWebExchange exchange) {
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
List<String> values = entry.getValue();
if (!CollectionUtils.isEmpty(values)) {
String name = entry.getKey().replace("-", "");
// For constructor args with @BindParam mapped to the actual header name
String name = entry.getKey();
addValueIfNotPresent(map, "Header", name, (values.size() == 1 ? values.get(0) : values));
// Also adapt to Java conventions for setters
name = StringUtils.uncapitalize(entry.getKey().replace("-", ""));
addValueIfNotPresent(map, "Header", name, (values.size() == 1 ? values.get(0) : values));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.http.MediaType;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.BindParam;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
Expand Down Expand Up @@ -129,7 +131,7 @@ void createBinderTypeConversion() throws Exception {
}

@Test
void bindUriVariablesAndHeaders() throws Exception {
void bindUriVariablesAndHeadersViaSetters() throws Exception {

MockServerHttpRequest request = MockServerHttpRequest.get("/path")
.header("Some-Int-Array", "1")
Expand All @@ -153,6 +155,31 @@ void bindUriVariablesAndHeaders() throws Exception {
assertThat(target.getSomeIntArray()).containsExactly(1, 2);
}

@Test
void bindUriVariablesAndHeadersViaConstructor() throws Exception {

MockServerHttpRequest request = MockServerHttpRequest.get("/path")
.header("Some-Int-Array", "1")
.header("Some-Int-Array", "2")
.build();

MockServerWebExchange exchange = MockServerWebExchange.from(request);
exchange.getAttributes().put(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
Map.of("name", "John", "age", "25"));

BindingContext context = createBindingContext("initBinderWithAttributeName", WebDataBinder.class);
WebExchangeDataBinder binder = context.createDataBinder(exchange, null, "dataBean", null);
binder.setTargetType(ResolvableType.forClass(DataBean.class));
binder.construct(exchange).block();

DataBean bean = (DataBean) binder.getTarget();

assertThat(bean.name()).isEqualTo("John");
assertThat(bean.age()).isEqualTo(25);
assertThat(bean.someIntArray()).containsExactly(1, 2);
}

@Test
void bindUriVarsAndHeadersAddedConditionally() throws Exception {

Expand Down Expand Up @@ -212,4 +239,8 @@ public void initBinderTypeConversion(WebDataBinder dataBinder, @RequestParam int
}
}


private record DataBean(String name, int age, @BindParam("Some-Int-Array") Integer[] someIntArray) {
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ protected Object getRequestParameter(String name, Class<?> type) {
if (uriVars != null) {
value = uriVars.get(name);
}
if (value == null && getRequest() instanceof HttpServletRequest httpServletRequest) {
value = getHeaderValue(httpServletRequest, name);
}
}
return value;
}
Expand All @@ -167,6 +170,13 @@ protected Set<String> initParameterNames(ServletRequest request) {
if (uriVars != null) {
set.addAll(uriVars.keySet());
}
if (request instanceof HttpServletRequest httpServletRequest) {
Enumeration<String> enumeration = httpServletRequest.getHeaderNames();
while (enumeration.hasMoreElements()) {
String headerName = enumeration.nextElement();
set.add(headerName.replaceAll("-", ""));
}
}
return set;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
import org.junit.jupiter.api.Test;

import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.core.ResolvableType;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.annotation.BindParam;
import org.springframework.web.bind.support.BindParamNameResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;

Expand All @@ -45,7 +48,7 @@ void setup() {


@Test
void createBinder() {
void createBinderViaSetters() {
request.setAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
Map.of("name", "John", "age", "25"));
Expand All @@ -62,6 +65,27 @@ void createBinder() {
assertThat(target.getSomeIntArray()).containsExactly(1, 2);
}

@Test
void createBinderViaConstructor() {
request.setAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
Map.of("name", "John", "age", "25"));

request.addHeader("Some-Int-Array", "1");
request.addHeader("Some-Int-Array", "2");

ServletRequestDataBinder binder = new ExtendedServletRequestDataBinder(null);
binder.setTargetType(ResolvableType.forClass(DataBean.class));
binder.setNameResolver(new BindParamNameResolver());
binder.construct(request);

DataBean bean = (DataBean) binder.getTarget();

assertThat(bean.name()).isEqualTo("John");
assertThat(bean.age()).isEqualTo(25);
assertThat(bean.someIntArray()).containsExactly(1, 2);
}

@Test
void uriVarsAndHeadersAddedConditionally() {
request.addParameter("name", "John");
Expand All @@ -88,4 +112,8 @@ void noUriTemplateVars() {
assertThat(target.getAge()).isEqualTo(0);
}


private record DataBean(String name, int age, @BindParam("Some-Int-Array") Integer[] someIntArray) {
}

}

0 comments on commit 70c326e

Please sign in to comment.