Skip to content

Commit

Permalink
Servlet
Browse files Browse the repository at this point in the history
  • Loading branch information
poutsma committed Jul 10, 2023
1 parent 76f94e9 commit 123743f
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
Expand Down Expand Up @@ -36,6 +36,7 @@
import java.util.Optional;
import java.util.OptionalLong;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import jakarta.servlet.ServletException;
Expand All @@ -47,6 +48,7 @@
import jakarta.servlet.http.Part;

import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRange;
Expand All @@ -56,12 +58,17 @@
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
Expand Down Expand Up @@ -218,6 +225,35 @@ private List<MediaType> getSupportedMediaTypes(Class<?> bodyClass) {
return result;
}

@Override
@SuppressWarnings("unchecked")
public <T> T bind(Class<T> bindType, Consumer<WebDataBinder> dataBinderCustomizer) throws BindException {
Assert.notNull(bindType, "BindType must not be null");
Assert.notNull(dataBinderCustomizer, "DataBinderCustomizer must not be null");

ServletRequestDataBinder dataBinder = new ServletRequestDataBinder(null);
dataBinder.setTargetType(ResolvableType.forClass(bindType));
dataBinderCustomizer.accept(dataBinder);

HttpServletRequest servletRequest = servletRequest();
dataBinder.construct(servletRequest);
dataBinder.bind(servletRequest);

BindingResult bindingResult = dataBinder.getBindingResult();
if (bindingResult.hasErrors()) {
throw new BindException(bindingResult);
}
else {
T result = (T) bindingResult.getTarget();
if (result != null) {
return result;
}
else {
throw new IllegalStateException("Binding result has neither target nor errors");
}
}
}

@Override
public Optional<Object> attribute(String name) {
return Optional.ofNullable(servletRequest().getAttribute(name));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import jakarta.servlet.http.Part;

import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpMethod;
Expand All @@ -52,7 +53,11 @@
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.util.UriBuilder;
import org.springframework.web.util.UriComponentsBuilder;

Expand Down Expand Up @@ -323,6 +328,35 @@ private <T> T bodyInternal(Type bodyType, Class<?> bodyClass) throws ServletExce
throw new HttpMediaTypeNotSupportedException(contentType, Collections.emptyList(), method());
}

@Override
@SuppressWarnings("unchecked")
public <T> T bind(Class<T> bindType, Consumer<WebDataBinder> dataBinderCustomizer) throws BindException {
Assert.notNull(bindType, "BindType must not be null");
Assert.notNull(dataBinderCustomizer, "DataBinderCustomizer must not be null");

ServletRequestDataBinder dataBinder = new ServletRequestDataBinder(null);
dataBinder.setTargetType(ResolvableType.forClass(bindType));
dataBinderCustomizer.accept(dataBinder);

HttpServletRequest servletRequest = servletRequest();
dataBinder.construct(servletRequest);
dataBinder.bind(servletRequest);

BindingResult bindingResult = dataBinder.getBindingResult();
if (bindingResult.hasErrors()) {
throw new BindException(bindingResult);
}
else {
T result = (T) bindingResult.getTarget();
if (result != null) {
return result;
}
else {
throw new IllegalStateException("Binding result has neither target nor errors");
}
}
}

@Override
public Map<String, Object> attributes() {
return this.attributes;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
Expand Down Expand Up @@ -31,6 +31,7 @@
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;

Expand All @@ -54,6 +55,8 @@
import org.springframework.util.Assert;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.validation.BindException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.util.UriBuilder;
import org.springframework.web.util.UriUtils;
Expand Down Expand Up @@ -1018,6 +1021,16 @@ public <T> T body(ParameterizedTypeReference<T> bodyType)
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);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 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.
Expand Down Expand Up @@ -48,6 +48,8 @@
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.validation.BindException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.UriBuilder;

Expand Down Expand Up @@ -148,6 +150,30 @@ default RequestPath requestPath() {
*/
<T> T body(ParameterizedTypeReference<T> bodyType) throws ServletException, IOException;

/**
* Binds this request to an instance of the given type.
* @param bindType the type of class to bind this request to
* @return a constructed and bound instance of {@code bindType}
* @param <T> the type to bind to
* @throws BindException in case of binding errors
* @since 6.1
*/
default <T> T bind(Class<T> bindType) throws BindException {
return bind(bindType, dataBinder -> {});
}

/**
* Binds this request to an instance of the given type.
* @param bindType the type of class to bind this request to
* @param dataBinderCustomizer used to customize the data binder, e.g. set
* (dis)allowed fields
* @return a constructed and bound instance of {@code bindType}
* @param <T> the type to bind to
* @throws BindException in case of binding errors
* @since 6.1
*/
<T> T bind(Class<T> bindType, Consumer<WebDataBinder> dataBinderCustomizer) throws BindException;

/**
* Get the request attribute value if present.
* @param name the attribute name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.validation.BindException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.servlet.handler.PathPatternsTestUtils;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
Expand Down Expand Up @@ -304,6 +305,59 @@ void principal() {
assertThat(request.principal()).contains(principal);
}

@Test
void bindToConstructor() throws BindException {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
servletRequest.addParameter("foo", "FOO");
servletRequest.addParameter("bar", "BAR");

DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);

ConstructorInjection result = request.bind(ConstructorInjection.class);
assertThat(result.getFoo()).isEqualTo("FOO");
assertThat(result.getBar()).isEqualTo("BAR");
}

@Test
void bindToProperties() throws BindException {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
servletRequest.addParameter("foo", "FOO");
servletRequest.addParameter("bar", "BAR");

DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);

PropertyInjection result = request.bind(PropertyInjection.class);
assertThat(result.getFoo()).isEqualTo("FOO");
assertThat(result.getBar()).isEqualTo("BAR");
}

@Test
void bindToMixed() throws BindException {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
servletRequest.addParameter("foo", "FOO");
servletRequest.addParameter("bar", "BAR");

DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);

MixedInjection result = request.bind(MixedInjection.class);
assertThat(result.getFoo()).isEqualTo("FOO");
assertThat(result.getBar()).isEqualTo("BAR");
}

@Test
void bindCustomizer() throws BindException {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest("GET", "/", true);
servletRequest.addParameter("foo", "FOO");
servletRequest.addParameter("bar", "BAR");

DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);

PropertyInjection result = request.bind(PropertyInjection.class, dataBinder -> dataBinder.setAllowedFields("foo"));
assertThat(result.getFoo()).isEqualTo("FOO");
assertThat(result.getBar()).isNull();
}


@ParameterizedHttpMethodTest
void checkNotModifiedTimestamp(String method) throws Exception {
MockHttpServletRequest servletRequest = PathPatternsTestUtils.initRequest(method, "/", true);
Expand Down Expand Up @@ -487,4 +541,69 @@ void checkModifiedETagAndNotModifiedTimestamp(String method) {
@interface ParameterizedHttpMethodTest {
}

private static final class ConstructorInjection {
private final String foo;

private final String bar;

public ConstructorInjection(String foo, String bar) {
this.foo = foo;
this.bar = bar;
}

public String getFoo() {
return this.foo;
}

public String getBar() {
return this.bar;
}
}

private static final class PropertyInjection {
private String foo;

private String bar;

public String getFoo() {
return this.foo;
}

public void setFoo(String foo) {
this.foo = foo;
}

public String getBar() {
return this.bar;
}

public void setBar(String bar) {
this.bar = bar;
}
}

private static final class MixedInjection {
private final String foo;

private String bar;

public MixedInjection(String foo) {
this.foo = foo;
}

public String getFoo() {
return this.foo;
}

public String getBar() {
return this.bar;
}

public void setBar(String bar) {
this.bar = bar;
}
}



}

0 comments on commit 123743f

Please sign in to comment.