Skip to content

Commit 65296c6

Browse files
committed
Add status/headers to WebMVC FragmentsRendering
See gh-33162
1 parent 14c1faa commit 65296c6

File tree

6 files changed

+120
-10
lines changed

6 files changed

+120
-10
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java

+11
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@
1818

1919
import java.util.Collection;
2020

21+
import jakarta.servlet.http.HttpServletResponse;
22+
2123
import org.springframework.core.MethodParameter;
24+
import org.springframework.http.HttpHeaders;
2225
import org.springframework.lang.Nullable;
26+
import org.springframework.util.Assert;
2327
import org.springframework.util.PatternMatchUtils;
2428
import org.springframework.web.context.request.NativeWebRequest;
2529
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
@@ -96,6 +100,13 @@ public void handleReturnValue(@Nullable Object returnValue, MethodParameter retu
96100
}
97101

98102
if (returnValue instanceof FragmentsRendering rendering) {
103+
mavContainer.setStatus(rendering.status());
104+
HttpHeaders headers = rendering.headers();
105+
if (!headers.isEmpty()) {
106+
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
107+
Assert.state(response != null, "No HttpServletResponse");
108+
headers.forEach((name, values) -> values.forEach(value -> response.addHeader(name, value)));
109+
}
99110
mavContainer.setView(rendering);
100111
return;
101112
}

spring-webmvc/src/main/java/org/springframework/web/servlet/view/DefaultFragmentsRendering.java

+24-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import jakarta.servlet.http.HttpServletResponse;
3030
import jakarta.servlet.http.HttpServletResponseWrapper;
3131

32+
import org.springframework.http.HttpHeaders;
33+
import org.springframework.http.HttpStatusCode;
3234
import org.springframework.lang.Nullable;
3335
import org.springframework.util.Assert;
3436
import org.springframework.web.servlet.ModelAndView;
@@ -44,14 +46,34 @@
4446
*/
4547
final class DefaultFragmentsRendering implements FragmentsRendering {
4648

49+
@Nullable
50+
private final HttpStatusCode status;
51+
52+
private final HttpHeaders headers;
53+
4754
private final Collection<ModelAndView> modelAndViews;
4855

4956

50-
DefaultFragmentsRendering(Collection<ModelAndView> modelAndViews) {
51-
this.modelAndViews = new ArrayList<>(modelAndViews);
57+
DefaultFragmentsRendering(
58+
@Nullable HttpStatusCode status, HttpHeaders headers, Collection<ModelAndView> fragments) {
59+
60+
this.status = status;
61+
this.headers = headers;
62+
this.modelAndViews = new ArrayList<>(fragments);
5263
}
5364

5465

66+
@Nullable
67+
@Override
68+
public HttpStatusCode status() {
69+
return this.status;
70+
}
71+
72+
@Override
73+
public HttpHeaders headers() {
74+
return this.headers;
75+
}
76+
5577
@Override
5678
public boolean isRedirectView() {
5779
return false;

spring-webmvc/src/main/java/org/springframework/web/servlet/view/DefaultFragmentsRenderingBuilder.java

+38-1
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,14 @@
1717
package org.springframework.web.servlet.view;
1818

1919
import java.util.ArrayList;
20+
import java.util.Arrays;
2021
import java.util.Collection;
2122
import java.util.Map;
23+
import java.util.function.Consumer;
2224

25+
import org.springframework.http.HttpHeaders;
26+
import org.springframework.http.HttpStatusCode;
27+
import org.springframework.lang.Nullable;
2328
import org.springframework.web.servlet.ModelAndView;
2429

2530
/**
@@ -31,9 +36,40 @@
3136
*/
3237
final class DefaultFragmentsRenderingBuilder implements FragmentsRendering.Builder {
3338

39+
@Nullable
40+
private HttpStatusCode status;
41+
42+
@Nullable
43+
private HttpHeaders headers;
44+
3445
private final Collection<ModelAndView> fragments = new ArrayList<>();
3546

3647

48+
@Override
49+
public FragmentsRendering.Builder status(HttpStatusCode status) {
50+
this.status = status;
51+
return this;
52+
}
53+
54+
@Override
55+
public FragmentsRendering.Builder header(String headerName, String... headerValues) {
56+
initHeaders().put(headerName, Arrays.asList(headerValues));
57+
return this;
58+
}
59+
60+
@Override
61+
public FragmentsRendering.Builder headers(Consumer<HttpHeaders> headersConsumer) {
62+
headersConsumer.accept(initHeaders());
63+
return this;
64+
}
65+
66+
private HttpHeaders initHeaders() {
67+
if (this.headers == null) {
68+
this.headers = new HttpHeaders();
69+
}
70+
return this.headers;
71+
}
72+
3773
@Override
3874
public DefaultFragmentsRenderingBuilder fragment(String viewName, Map<String, Object> model) {
3975
return fragment(new ModelAndView(viewName, model));
@@ -58,7 +94,8 @@ public DefaultFragmentsRenderingBuilder fragments(Collection<ModelAndView> fragm
5894

5995
@Override
6096
public FragmentsRendering build() {
61-
return new DefaultFragmentsRendering(this.fragments);
97+
return new DefaultFragmentsRendering(
98+
this.status, (this.headers != null ? this.headers : HttpHeaders.EMPTY), this.fragments);
6299
}
63100

64101
}

spring-webmvc/src/main/java/org/springframework/web/servlet/view/FragmentsRendering.java

+38
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@
1818

1919
import java.util.Collection;
2020
import java.util.Map;
21+
import java.util.function.Consumer;
2122

23+
import org.springframework.http.HttpHeaders;
24+
import org.springframework.http.HttpStatusCode;
25+
import org.springframework.lang.Nullable;
2226
import org.springframework.web.servlet.ModelAndView;
2327
import org.springframework.web.servlet.SmartView;
2428

@@ -34,6 +38,17 @@
3438
*/
3539
public interface FragmentsRendering extends SmartView {
3640

41+
/**
42+
* Return the HTTP status to set the response to.
43+
*/
44+
@Nullable
45+
HttpStatusCode status();
46+
47+
/**
48+
* Return headers to add to the response.
49+
*/
50+
HttpHeaders headers();
51+
3752

3853
/**
3954
* Create a builder for {@link FragmentsRendering}, adding a fragment with
@@ -73,6 +88,29 @@ static Builder with(Collection<ModelAndView> fragments) {
7388
*/
7489
interface Builder {
7590

91+
/**
92+
* Specify the status to use for the response.
93+
* @param status the status to set
94+
* @return this builder
95+
*/
96+
Builder status(HttpStatusCode status);
97+
98+
/**
99+
* Add the given, single header value under the given name.
100+
* @param headerName the header name
101+
* @param headerValues the header value(s)
102+
* @return this builder
103+
*/
104+
Builder header(String headerName, String... headerValues);
105+
106+
/**
107+
* Provides access to every header declared so far with the possibility
108+
* to add, replace, or remove values.
109+
* @param headersConsumer the consumer to provide access to
110+
* @return this builder
111+
*/
112+
Builder headers(Consumer<HttpHeaders> headersConsumer);
113+
76114
/**
77115
* Add a fragment with a view name and a model.
78116
* @param viewName the name of the view for the fragment

spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandlerTests.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.springframework.web.servlet.view.FragmentsRendering;
3434
import org.springframework.web.servlet.view.RedirectView;
3535
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
36+
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
3637

3738
import static org.assertj.core.api.Assertions.assertThat;
3839

@@ -56,7 +57,7 @@ class ModelAndViewMethodReturnValueHandlerTests {
5657
void setup() throws Exception {
5758
this.handler = new ModelAndViewMethodReturnValueHandler();
5859
this.mavContainer = new ModelAndViewContainer();
59-
this.webRequest = new ServletWebRequest(new MockHttpServletRequest());
60+
this.webRequest = new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse());
6061
this.returnParamModelAndView = getReturnValueParam("modelAndView");
6162
}
6263

@@ -90,10 +91,13 @@ void handleViewInstance() throws Exception {
9091

9192
@Test
9293
void handleFragmentsRendering() throws Exception {
93-
FragmentsRendering rendering = FragmentsRendering.with("viewName").build();
94+
FragmentsRendering rendering = FragmentsRendering.with("viewName")
95+
.header("headerName", "headerValue")
96+
.build();
9497

9598
handler.handleReturnValue(rendering, returnParamModelAndView, mavContainer, webRequest);
9699
assertThat(mavContainer.getView()).isInstanceOf(SmartView.class);
100+
assertThat(this.webRequest.getResponse().getHeader("headerName")).isEqualTo("headerValue");
97101
}
98102

99103
@Test

spring-webmvc/src/test/java/org/springframework/web/servlet/view/DefaultFragmentsRenderingTests.java

+3-5
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.web.servlet.view;
1818

1919
import java.util.Collections;
20-
import java.util.List;
2120
import java.util.Locale;
2221
import java.util.Map;
2322

@@ -28,7 +27,6 @@
2827
import org.springframework.context.annotation.Bean;
2928
import org.springframework.context.annotation.Configuration;
3029
import org.springframework.context.support.ResourceBundleMessageSource;
31-
import org.springframework.web.servlet.ModelAndView;
3230
import org.springframework.web.servlet.view.script.ScriptTemplateConfigurer;
3331
import org.springframework.web.servlet.view.script.ScriptTemplateViewResolver;
3432
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
@@ -59,9 +57,9 @@ void render() throws Exception {
5957
MockHttpServletRequest request = new MockHttpServletRequest();
6058
MockHttpServletResponse response = new MockHttpServletResponse();
6159

62-
DefaultFragmentsRendering view = new DefaultFragmentsRendering(List.of(
63-
new ModelAndView("fragment1", Map.of("foo", "Foo")),
64-
new ModelAndView("fragment2", Map.of("bar", "Bar"))));
60+
FragmentsRendering view = FragmentsRendering.with("fragment1", Map.of("foo", "Foo"))
61+
.fragment("fragment2", Map.of("bar", "Bar"))
62+
.build();
6563

6664
view.resolveNestedViews(viewResolver, Locale.ENGLISH);
6765
view.render(Collections.emptyMap(), request, response);

0 commit comments

Comments
 (0)