Add template rendering support
This commit introduces template rendering support in the web.reactive package, through a Response.render method and a Rendering interface.
This commit is contained in:
@@ -34,6 +34,7 @@ import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||
import org.springframework.http.codec.xml.Jaxb2XmlDecoder;
|
||||
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
|
||||
/**
|
||||
* A default implementation of configuration.
|
||||
@@ -54,6 +55,8 @@ class DefaultConfiguration implements Router.Configuration {
|
||||
|
||||
private final List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
|
||||
|
||||
private final List<ViewResolver> viewResolvers = new ArrayList<>();
|
||||
|
||||
public DefaultConfiguration() {
|
||||
this.messageReaders.add(new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
|
||||
this.messageReaders.add(new DecoderHttpMessageReader<>(new StringDecoder()));
|
||||
@@ -78,4 +81,9 @@ class DefaultConfiguration implements Router.Configuration {
|
||||
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
|
||||
return this.messageWriters::stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<Stream<ViewResolver>> viewResolvers() {
|
||||
return this.viewResolvers::stream;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,15 @@ import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.core.Conventions;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.CacheControl;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
@@ -169,4 +174,27 @@ class DefaultResponseBuilder implements Response.BodyBuilder {
|
||||
return ServerSentEventResponse
|
||||
.fromPublisher(this.statusCode, this.headers, eventsPublisher, eventClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response<Rendering> render(String name, Object... modelAttributes) {
|
||||
Map<String, Object> modelMap = Arrays.stream(modelAttributes)
|
||||
.filter(o -> !isEmptyCollection(o))
|
||||
.collect(Collectors.toMap(Conventions::getVariableName, o -> o));
|
||||
return new RenderingResponse(this.statusCode, this.headers, name, modelMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response<Rendering> render(String name, Map<String, ?> model) {
|
||||
Assert.hasLength(name, "'name' must not be empty");
|
||||
Map<String, Object> modelMap = new LinkedHashMap<>();
|
||||
if (model != null) {
|
||||
modelMap.putAll(model);
|
||||
}
|
||||
return new RenderingResponse(this.statusCode, this.headers, name, modelMap);
|
||||
}
|
||||
|
||||
private static boolean isEmptyCollection(Object o) {
|
||||
return o instanceof Collection && ((Collection<?>) o).isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.reactive.function;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents a template rendering, based on a {@code String} name and a model {@code Map}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 5.0
|
||||
*/
|
||||
public interface Rendering {
|
||||
|
||||
/**
|
||||
* Return the name of the template to be rendered.
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* Return the unmodifiable model map.
|
||||
*/
|
||||
Map<String, Object> model();
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.reactive.function;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
class RenderingResponse extends AbstractResponse<Rendering> {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final Map<String, Object> model;
|
||||
|
||||
private final Rendering rendering = new DefaultRendering();
|
||||
|
||||
public RenderingResponse(int statusCode, HttpHeaders headers, String name,
|
||||
Map<String, Object> model) {
|
||||
super(statusCode, headers);
|
||||
this.name = name;
|
||||
this.model = Collections.unmodifiableMap(model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rendering body() {
|
||||
return this.rendering;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> writeTo(ServerWebExchange exchange) {
|
||||
writeStatusAndHeaders(exchange);
|
||||
MediaType contentType = exchange.getResponse().getHeaders().getContentType();
|
||||
Locale locale = Locale.ENGLISH; // TODO
|
||||
return Flux.fromStream(viewResolverStream(exchange))
|
||||
.concatMap(viewResolver -> viewResolver.resolveViewName(this.name, locale))
|
||||
.next()
|
||||
.otherwiseIfEmpty(Mono.error(new IllegalArgumentException("Could not resolve view with name '" + this.name +"'")))
|
||||
.then(view -> view.render(this.model, contentType, exchange));
|
||||
}
|
||||
|
||||
private Stream<ViewResolver> viewResolverStream(ServerWebExchange exchange) {
|
||||
return exchange.<Supplier<Stream<ViewResolver>>>getAttribute(
|
||||
Router.VIEW_RESOLVERS_ATTRIBUTE)
|
||||
.orElseThrow(() -> new IllegalStateException(
|
||||
"Could not find ViewResolvers in ServerWebExchange"))
|
||||
.get();
|
||||
}
|
||||
|
||||
private class DefaultRendering implements Rendering {
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> model() {
|
||||
return model;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ package org.springframework.web.reactive.function;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
@@ -170,6 +172,7 @@ public interface Response<T> {
|
||||
*/
|
||||
Mono<Void> writeTo(ServerWebExchange exchange);
|
||||
|
||||
|
||||
/**
|
||||
* Defines a builder that adds headers to the response.
|
||||
*
|
||||
@@ -277,6 +280,7 @@ public interface Response<T> {
|
||||
<T extends Publisher<Void>> Response<T> build(T voidPublisher);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Defines a builder that adds a body to the response.
|
||||
*/
|
||||
@@ -348,6 +352,27 @@ public interface Response<T> {
|
||||
*/
|
||||
<T, S extends Publisher<T>> Response<S> sse(S eventsPublisher, Class<T> eventClass);
|
||||
|
||||
/**
|
||||
* Render the template with the given {@code name} using the given {@code modelAttributes}.
|
||||
* The model attributes are mapped under a
|
||||
* {@linkplain org.springframework.core.Conventions#getVariableName generated name}.
|
||||
* <p><emphasis>Note: Empty {@link Collection Collections} are not added to
|
||||
* the model when using this method because we cannot correctly determine
|
||||
* the true convention name.</emphasis>
|
||||
* @param name the name of the template to be rendered
|
||||
* @param modelAttributes the modelAttributes used to render the template
|
||||
* @return the built response
|
||||
*/
|
||||
Response<Rendering> render(String name, Object... modelAttributes);
|
||||
|
||||
/**
|
||||
* Render the template with the given {@code name} using the given {@code model}.
|
||||
* @param name the name of the template to be rendered
|
||||
* @param model the model used to render the template
|
||||
* @return the built response
|
||||
*/
|
||||
Response<Rendering> render(String name, Map<String, ?> model);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.adapter.HttpWebHandlerAdapter;
|
||||
|
||||
@@ -82,6 +83,13 @@ public abstract class Router {
|
||||
*/
|
||||
public static final String URI_TEMPLATE_VARIABLES_ATTRIBUTE = Router.class.getName() + ".uriTemplateVariables";
|
||||
|
||||
/**
|
||||
* Name of the {@link ServerWebExchange} attribute that contains the {@link Supplier} to the
|
||||
* {@linkplain Stream stream} of {@link ViewResolver}s obtained
|
||||
* from the {@linkplain Configuration#viewResolvers() configuration}.
|
||||
*/
|
||||
public static final String VIEW_RESOLVERS_ATTRIBUTE = Router.class.getName() + ".viewResolvers";
|
||||
|
||||
|
||||
/**
|
||||
* Route to the given handler function if the given request predicate applies.
|
||||
@@ -215,6 +223,7 @@ public abstract class Router {
|
||||
attributes.put(REQUEST_ATTRIBUTE, request);
|
||||
attributes.put(HTTP_MESSAGE_READERS_ATTRIBUTE, configuration.messageReaders());
|
||||
attributes.put(HTTP_MESSAGE_WRITERS_ATTRIBUTE, configuration.messageWriters());
|
||||
attributes.put(VIEW_RESOLVERS_ATTRIBUTE, configuration.viewResolvers());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -251,6 +260,11 @@ public abstract class Router {
|
||||
return () -> applicationContext.getBeansOfType(HttpMessageWriter.class).values().stream()
|
||||
.map(CastingUtils::cast);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<Stream<ViewResolver>> viewResolvers() {
|
||||
return () -> applicationContext.getBeansOfType(ViewResolver.class).values().stream();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -273,6 +287,13 @@ public abstract class Router {
|
||||
* @return the stream of message writers
|
||||
*/
|
||||
Supplier<Stream<HttpMessageWriter<?>>> messageWriters();
|
||||
|
||||
/**
|
||||
* Supply a {@linkplain Stream stream} of {@link ViewResolver}s to be used for view name
|
||||
* resolution.
|
||||
* @return the stream of view resolvers
|
||||
*/
|
||||
Supplier<Stream<ViewResolver>> viewResolvers();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.reactive.result.view;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.core.Conventions;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Immutable holder for both model and view in the web MVC framework. Note that these are entirely
|
||||
* distinct. This class merely holds both to make it possible for a handler to return both model
|
||||
* and view in a single return value.
|
||||
*
|
||||
* <p>Represents a model and view returned by a handler. The view can take the form of a String
|
||||
* view name which will need to be resolved by a {@link ViewResolver}; alternatively a
|
||||
* {@link View} can be specified directly. The model
|
||||
* is a {@link Map}, allowing the use of multiple objects keyed by name.
|
||||
* @param <T> the type of view the model contains (either a {@link View} or a {@code String}
|
||||
* denoting the view name).
|
||||
* @author Arjen Poutsma
|
||||
* @see ViewResolver
|
||||
* @since 5.0
|
||||
*/
|
||||
public class ModelAndView<T> {
|
||||
|
||||
/**
|
||||
* View instance or view name String
|
||||
*/
|
||||
private final T view;
|
||||
|
||||
/**
|
||||
* Model Map
|
||||
*/
|
||||
private final Map<String, Object> model;
|
||||
|
||||
|
||||
private ModelAndView(T view, Map<String, Object> model) {
|
||||
Assert.notNull(view, "'view' must not be null");
|
||||
Assert.notNull(model, "'model' must not be null");
|
||||
this.view = view;
|
||||
this.model = Collections.unmodifiableMap(model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a builder for a {@code ModelAndView} with a view name.
|
||||
* @return the builder
|
||||
*/
|
||||
public static Builder<String> viewName(String viewName) {
|
||||
Assert.hasLength(viewName, "'viewName' must not be empty");
|
||||
return new BuilderImpl<>(viewName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a builder for a {@code ModelAndView} with a {@link View} instance.
|
||||
* @return the builder
|
||||
*/
|
||||
public static Builder<View> view(View view) {
|
||||
Assert.notNull(view, "'view' must not be null");
|
||||
return new BuilderImpl<>(view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the optional view name to be resolved by the DispatcherHandler
|
||||
* via a ViewResolver.
|
||||
*/
|
||||
public Optional<String> viewName() {
|
||||
return this.view instanceof String ? Optional.of((String) this.view) : Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the optional View object.
|
||||
*/
|
||||
public Optional<View> view() {
|
||||
return this.view instanceof View ? Optional.of((View) this.view) : Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether we use a view reference, i.e. {@code true}
|
||||
* if the view has been specified via a name to be resolved by a ViewResolver.
|
||||
*/
|
||||
public boolean isReference() {
|
||||
return (this.view instanceof String);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the unmodifiable model map. Never returns {@code null}.
|
||||
*/
|
||||
public Map<String, Object> model() {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return diagnostic information about this model and view.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder("ModelAndView: ");
|
||||
if (isReference()) {
|
||||
sb.append("reference to view with name '").append(this.view).append("'");
|
||||
}
|
||||
else {
|
||||
sb.append("materialized View is [").append(this.view).append(']');
|
||||
}
|
||||
sb.append("; model is ").append(this.model);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A mutable builder for a {@link ModelAndView}.
|
||||
* @param <T> the type of view the model contains (either {@link View} or a view name).
|
||||
*/
|
||||
public interface Builder<T> {
|
||||
|
||||
/**
|
||||
* Add the supplied model attribute under the supplied name.
|
||||
* @param attributeName the name of the model attribute (never {@code null})
|
||||
* @param attributeValue the model attribute value (can be {@code null})
|
||||
*/
|
||||
Builder<T> modelAttribute(String attributeName, Object attributeValue);
|
||||
|
||||
/**
|
||||
* Add a model attribute using parameter name generation.
|
||||
* @param attributeValue the object to add to the model (never {@code null})
|
||||
*/
|
||||
Builder<T> modelAttribute(Object attributeValue);
|
||||
|
||||
/**
|
||||
* Copy all attributes in the supplied {@code Map} into the model.
|
||||
* @see #modelAttribute(String, Object)
|
||||
*/
|
||||
Builder<T> modelAttributes(Map<String, ?> attributes);
|
||||
|
||||
/**
|
||||
* Builds the {@link ModelAndView}.
|
||||
* @return the built model and view
|
||||
*/
|
||||
ModelAndView<T> build();
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class BuilderImpl<T> implements Builder<T> {
|
||||
|
||||
private final T view;
|
||||
|
||||
private final Map<String, Object> model = new LinkedHashMap<>();
|
||||
|
||||
public BuilderImpl(T view) {
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<T> modelAttribute(String attributeName, Object attributeValue) {
|
||||
Assert.notNull(attributeName, "Model attribute name must not be null");
|
||||
this.model.put(attributeName, attributeValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<T> modelAttribute(Object attributeValue) {
|
||||
Assert.notNull(attributeValue, "Model object must not be null");
|
||||
if (attributeValue instanceof Collection && ((Collection<?>) attributeValue).isEmpty()) {
|
||||
return this;
|
||||
}
|
||||
return modelAttribute(Conventions.getVariableName(attributeValue), attributeValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<T> modelAttributes(Map<String, ?> attributes) {
|
||||
if (attributes != null) {
|
||||
this.model.putAll(attributes);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelAndView<T> build() {
|
||||
return new ModelAndView<T>(this.view, this.model);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package org.springframework.web.reactive.function;
|
||||
import java.net.URI;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -32,7 +33,7 @@ import static org.junit.Assert.assertEquals;
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public class ResponseTests {
|
||||
public class DefaultResponseBuilderTests {
|
||||
|
||||
@Test
|
||||
public void from() throws Exception {
|
||||
@@ -121,6 +122,7 @@ public class ResponseTests {
|
||||
Response<Void> result = Response.ok().eTag("foo").build();
|
||||
assertEquals("\"foo\"", result.headers().getETag());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lastModified() throws Exception {
|
||||
ZonedDateTime now = ZonedDateTime.now();
|
||||
@@ -141,7 +143,13 @@ public class ResponseTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeTo() throws Exception {
|
||||
public void renderObjectArray() throws Exception {
|
||||
Response<Rendering> result =
|
||||
Response.ok().render("name", this, Collections.emptyList(), "foo");
|
||||
Map<String, Object> model = result.body().model();
|
||||
assertEquals(2, model.size());
|
||||
assertEquals(this, model.get("defaultResponseBuilderTests"));
|
||||
assertEquals("foo", model.get("string"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.web.reactive.function;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
@@ -45,6 +46,7 @@ import org.springframework.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.reactive.config.WebReactiveConfiguration;
|
||||
import org.springframework.web.reactive.function.support.HandlerFunctionAdapter;
|
||||
import org.springframework.web.reactive.function.support.ResponseResultHandler;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -128,6 +130,11 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr
|
||||
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
|
||||
return () -> getMessageWriters().stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<Stream<ViewResolver>> viewResolvers() {
|
||||
return () -> Collections.<ViewResolver>emptySet().stream();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.reactive.function;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||
import org.springframework.web.reactive.result.view.View;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
import org.springframework.web.server.session.MockWebSessionManager;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public class RenderingResponseTests {
|
||||
|
||||
private final Map<String, Object> model = Collections.singletonMap("foo", "bar");
|
||||
|
||||
private final RenderingResponse renderingResponse = new RenderingResponse(200, new HttpHeaders(), "view",
|
||||
model);
|
||||
|
||||
@Test
|
||||
public void body() throws Exception {
|
||||
assertEquals("view", renderingResponse.body().name());
|
||||
assertEquals(model, renderingResponse.body().model());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeTo() throws Exception {
|
||||
MockServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, URI.create("http://localhost"));
|
||||
MockServerHttpResponse response = new MockServerHttpResponse();
|
||||
ServerWebExchange exchange = new DefaultServerWebExchange(request, response, new MockWebSessionManager());
|
||||
ViewResolver viewResolver = mock(ViewResolver.class);
|
||||
View view = mock(View.class);
|
||||
when(viewResolver.resolveViewName("view", Locale.ENGLISH)).thenReturn(Mono.just(view));
|
||||
when(view.render(model, null, exchange)).thenReturn(Mono.empty());
|
||||
exchange.getAttributes().put(Router.VIEW_RESOLVERS_ATTRIBUTE,
|
||||
(Supplier<Stream<ViewResolver>>) () -> Collections
|
||||
.singleton(viewResolver).stream());
|
||||
|
||||
|
||||
renderingResponse.writeTo(exchange).block();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -37,15 +37,11 @@ import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
@@ -137,6 +133,8 @@ public class RouterTests {
|
||||
() -> Collections.<HttpMessageReader<?>>emptyList().stream());
|
||||
when(configuration.messageWriters()).thenReturn(
|
||||
() -> Collections.<HttpMessageWriter<?>>emptyList().stream());
|
||||
when(configuration.viewResolvers()).thenReturn(
|
||||
() -> Collections.<ViewResolver>emptyList().stream());
|
||||
|
||||
HttpHandler result = Router.toHttpHandler(routingFunction, configuration);
|
||||
assertNotNull(result);
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.reactive.result.view;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public class ModelAndViewTests {
|
||||
|
||||
@Test
|
||||
public void view() {
|
||||
View view = mock(View.class);
|
||||
ModelAndView<View> mav = ModelAndView.view(view).build();
|
||||
assertFalse(mav.isReference());
|
||||
assertTrue(mav.view().isPresent());
|
||||
assertFalse(mav.viewName().isPresent());
|
||||
assertEquals(view, mav.view().get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void viewName() {
|
||||
String viewName = "foo";
|
||||
ModelAndView<String> mav = ModelAndView.viewName(viewName).build();
|
||||
assertTrue(mav.isReference());
|
||||
assertTrue(mav.viewName().isPresent());
|
||||
assertFalse(mav.view().isPresent());
|
||||
assertEquals(viewName, mav.viewName().get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modelAttribute() {
|
||||
ModelAndView<String> mav = ModelAndView.viewName("foo")
|
||||
.modelAttribute("foo", "bar").build();
|
||||
assertEquals(1, mav.model().size());
|
||||
assertTrue(mav.model().containsKey("foo"));
|
||||
assertEquals("bar", mav.model().get("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modelAttributeNoName() {
|
||||
ModelAndView<String> mav = ModelAndView.viewName("foo")
|
||||
.modelAttribute(this).build();
|
||||
assertEquals(1, mav.model().size());
|
||||
assertTrue(mav.model().containsKey("modelAndViewTests"));
|
||||
assertEquals(this, mav.model().get("modelAndViewTests"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modelAttributes() {
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
model.put("foo", "bar");
|
||||
model.put("baz", "qux");
|
||||
ModelAndView<String> mav = ModelAndView.viewName("foo")
|
||||
.modelAttributes(model).build();
|
||||
assertEquals(2, mav.model().size());
|
||||
assertTrue(mav.model().containsKey("foo"));
|
||||
assertEquals("bar", mav.model().get("foo"));
|
||||
assertTrue(mav.model().containsKey("baz"));
|
||||
assertEquals("qux", mav.model().get("baz"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user