Skip to content

Commit 6ee8786

Browse files
committed
Updates to WebFlux fragment rendering API
See gh-33162
1 parent 54e76c8 commit 6ee8786

File tree

7 files changed

+257
-143
lines changed

7 files changed

+257
-143
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/result/view/DefaultFragmentRenderingBuilder.java

-108
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.reactive.result.view;
18+
19+
import java.util.ArrayList;
20+
import java.util.Arrays;
21+
import java.util.Collection;
22+
import java.util.Map;
23+
import java.util.function.Consumer;
24+
25+
import org.reactivestreams.Publisher;
26+
import reactor.core.publisher.Flux;
27+
28+
import org.springframework.http.HttpHeaders;
29+
import org.springframework.http.HttpStatusCode;
30+
import org.springframework.lang.Nullable;
31+
32+
/**
33+
* Default implementation of {@link FragmentsRendering.Builder}.
34+
*
35+
* @author Rossen Stoyanchev
36+
* @since 6.2
37+
*/
38+
class DefaultFragmentsRenderingBuilder implements FragmentsRendering.Builder {
39+
40+
@Nullable
41+
private Collection<Fragment> fragmentsCollection;
42+
43+
@Nullable
44+
private final Flux<Fragment> fragmentsFlux;
45+
46+
@Nullable
47+
private HttpStatusCode status;
48+
49+
@Nullable
50+
private HttpHeaders headers;
51+
52+
DefaultFragmentsRenderingBuilder(Collection<Fragment> fragments) {
53+
this.fragmentsCollection = new ArrayList<>(fragments);
54+
this.fragmentsFlux = null;
55+
}
56+
57+
DefaultFragmentsRenderingBuilder(Publisher<Fragment> fragments) {
58+
this.fragmentsFlux = Flux.from(fragments);
59+
}
60+
61+
62+
@Override
63+
public FragmentsRendering.Builder status(HttpStatusCode status) {
64+
this.status = status;
65+
return this;
66+
}
67+
68+
@Override
69+
public FragmentsRendering.Builder header(String headerName, String... headerValues) {
70+
initHeaders().put(headerName, Arrays.asList(headerValues));
71+
return this;
72+
}
73+
74+
@Override
75+
public FragmentsRendering.Builder headers(Consumer<HttpHeaders> headersConsumer) {
76+
headersConsumer.accept(initHeaders());
77+
return this;
78+
}
79+
80+
private HttpHeaders initHeaders() {
81+
if (this.headers == null) {
82+
this.headers = new HttpHeaders();
83+
}
84+
return this.headers;
85+
}
86+
87+
@Override
88+
public FragmentsRendering.Builder fragment(String viewName, Map<String, Object> model) {
89+
return fragment(Fragment.create(viewName, model));
90+
}
91+
92+
@Override
93+
public FragmentsRendering.Builder fragment(String viewName) {
94+
return fragment(Fragment.create(viewName));
95+
}
96+
97+
@Override
98+
public FragmentsRendering.Builder fragment(Fragment fragment) {
99+
initFragmentsCollection().add(fragment);
100+
return this;
101+
}
102+
103+
private Collection<Fragment> initFragmentsCollection() {
104+
if (this.fragmentsCollection == null) {
105+
this.fragmentsCollection = new ArrayList<>();
106+
}
107+
return this.fragmentsCollection;
108+
}
109+
110+
@Override
111+
public FragmentsRendering build() {
112+
return new DefaultFragmentsRendering(
113+
this.status, (this.headers != null ? this.headers : HttpHeaders.EMPTY), getFragmentsFlux());
114+
}
115+
116+
private Flux<Fragment> getFragmentsFlux() {
117+
if (this.fragmentsFlux != null && this.fragmentsCollection != null) {
118+
return this.fragmentsFlux.concatWith(Flux.fromIterable(this.fragmentsCollection));
119+
}
120+
else if (this.fragmentsFlux != null) {
121+
return this.fragmentsFlux;
122+
}
123+
else if (this.fragmentsCollection != null) {
124+
return Flux.fromIterable(this.fragmentsCollection);
125+
}
126+
else {
127+
return Flux.empty();
128+
}
129+
}
130+
131+
132+
/**
133+
* Default implementation of {@link FragmentsRendering}.
134+
*/
135+
private record DefaultFragmentsRendering(@Nullable HttpStatusCode status, HttpHeaders headers, Flux<Fragment> fragments)
136+
implements FragmentsRendering {
137+
}
138+
139+
}

spring-webflux/src/main/java/org/springframework/web/reactive/result/view/Fragment.java

+37-9
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,23 @@
1616

1717
package org.springframework.web.reactive.result.view;
1818

19+
import java.util.Collections;
20+
import java.util.LinkedHashMap;
1921
import java.util.Map;
2022

2123
import org.springframework.lang.Nullable;
24+
import org.springframework.ui.Model;
2225
import org.springframework.util.Assert;
26+
import org.springframework.util.CollectionUtils;
2327

2428
/**
25-
* Container for a model and a view for use with {@link FragmentRendering} and
29+
* Container for a model and a view for use with {@link FragmentsRendering} and
2630
* multi-view rendering. For full page rendering with a single model and view,
2731
* use {@link Rendering}.
2832
*
2933
* @author Rossen Stoyanchev
3034
* @since 6.2
31-
* @see FragmentRendering
35+
* @see FragmentsRendering
3236
*/
3337
public final class Fragment {
3438

@@ -38,10 +42,11 @@ public final class Fragment {
3842
@Nullable
3943
private final View view;
4044

41-
private final Map<String, Object> model;
45+
@Nullable
46+
private Map<String, Object> model;
4247

4348

44-
private Fragment(@Nullable String viewName, @Nullable View view, Map<String, Object> model) {
49+
private Fragment(@Nullable String viewName, @Nullable View view, @Nullable Map<String, Object> model) {
4550
this.viewName = viewName;
4651
this.view = view;
4752
this.model = model;
@@ -73,15 +78,29 @@ public View view() {
7378
}
7479

7580
/**
76-
* Return the model for this Fragment.
81+
* Return the model for this Fragment, or an empty map.
7782
*/
7883
public Map<String, Object> model() {
79-
return this.model;
84+
return (this.model != null ? this.model : Collections.emptyMap());
85+
}
86+
87+
/**
88+
* Merge attributes from the request model if not already present.
89+
*/
90+
public void mergeAttributes(Model model) {
91+
if (CollectionUtils.isEmpty(model.asMap())) {
92+
return;
93+
}
94+
if (this.model == null) {
95+
this.model = new LinkedHashMap<>();
96+
}
97+
model.asMap().forEach((key, value) -> this.model.putIfAbsent(key, value));
8098
}
8199

100+
82101
@Override
83102
public String toString() {
84-
return "Fragment [view=" + formatView() + "; model=" + this.model + "]";
103+
return "Fragment [view=" + formatView() + "; model=" + model() + "]";
85104
}
86105

87106
private String formatView() {
@@ -90,14 +109,23 @@ private String formatView() {
90109

91110

92111
/**
93-
* Create a Fragment with a view name and a model.
112+
* Create a Fragment with a view name and a model, also inheriting model
113+
* attributes from the top-level model for the request.
94114
*/
95115
public static Fragment create(String viewName, Map<String, Object> model) {
96116
return new Fragment(viewName, null, model);
97117
}
98118

99119
/**
100-
* Create a Fragment with a resolved {@link View} instance and a model.
120+
* Create a Fragment with a view name only, inheriting model attributes from
121+
* the top-level model for the request.
122+
*/
123+
public static Fragment create(String viewName) {
124+
return new Fragment(viewName, null, null);
125+
}
126+
127+
/**
128+
* Variant of {@link #create(String, Map)} with a resolved {@link View}.
101129
*/
102130
public static Fragment create(View view, Map<String, Object> model) {
103131
return new Fragment(null, view, model);

0 commit comments

Comments
 (0)