Polish view resolution

This commit is contained in:
Rossen Stoyanchev 2016-05-31 22:14:17 -04:00
parent 8cc72b320b
commit a40a8b06bf
2 changed files with 64 additions and 54 deletions

View File

@ -127,6 +127,9 @@ public class ViewResolutionResultHandler extends ContentNegotiatingResultHandler
} }
} }
/**
* Return the configured default {@code View}'s.
*/
public List<View> getDefaultViews() { public List<View> getDefaultViews() {
return this.defaultViews; return this.defaultViews;
} }
@ -187,46 +190,48 @@ public class ViewResolutionResultHandler extends ContentNegotiatingResultHandler
} }
Mono<Object> viewMono; Mono<Object> viewMono;
if (isViewReturnType(result, elementType)) { if (isViewNameOrReference(elementType, result)) {
viewMono = valueMono.otherwiseIfEmpty(selectDefaultViewName(exchange, result)); Mono<Object> viewName = getDefaultViewNameMono(exchange, result);
viewMono = valueMono.otherwiseIfEmpty(viewName);
} }
else { else {
viewMono = valueMono.map(value -> updateModel(result, value)) viewMono = valueMono.map(value -> updateModel(value, result))
.defaultIfEmpty(result.getModel()) .defaultIfEmpty(result.getModel())
.then(model -> selectDefaultViewName(exchange, result)); .then(model -> getDefaultViewNameMono(exchange, result));
} }
return viewMono.then(returnValue -> { return viewMono.then(view -> {
if (returnValue instanceof View) { if (view instanceof View) {
return ((View) returnValue).render(result, null, exchange); return ((View) view).render(result, null, exchange);
} }
else if (returnValue instanceof CharSequence) { else if (view instanceof CharSequence) {
String viewName = returnValue.toString(); String viewName = view.toString();
Locale locale = Locale.getDefault(); // TODO Locale locale = Locale.getDefault(); // TODO
return resolveViewAndRender(viewName, locale, result, exchange); return resolveAndRender(viewName, locale, result, exchange);
} }
else { else {
// Should not happen // Should not happen
return Mono.error(new IllegalStateException("Unexpected return value")); return Mono.error(new IllegalStateException("Unexpected view type"));
} }
}); });
} }
private boolean isViewReturnType(HandlerResult result, ResolvableType elementType) { private boolean isViewNameOrReference(ResolvableType elementType, HandlerResult result) {
Class<?> clazz = elementType.getRawClass(); Class<?> clazz = elementType.getRawClass();
return (View.class.isAssignableFrom(clazz) || return (View.class.isAssignableFrom(clazz) ||
(CharSequence.class.isAssignableFrom(clazz) && !hasModelAttributeAnnotation(result))); (CharSequence.class.isAssignableFrom(clazz) && !hasModelAttributeAnnotation(result)));
} }
private Mono<Object> selectDefaultViewName(ServerWebExchange exchange, HandlerResult result) { private Mono<Object> getDefaultViewNameMono(ServerWebExchange exchange, HandlerResult result) {
String defaultViewName = getDefaultViewName(exchange, result); String defaultViewName = getDefaultViewName(result, exchange);
if (defaultViewName != null) { if (defaultViewName != null) {
return Mono.just(defaultViewName); return Mono.just(defaultViewName);
} }
else { else {
return Mono.error(new IllegalStateException("Handler [" + result.getHandler() + "] " + return Mono.error(new IllegalStateException(
"neither returned a view name nor a View object")); "Handler [" + result.getHandler() + "] " +
"neither returned a view name nor a View object"));
} }
} }
@ -239,7 +244,7 @@ public class ViewResolutionResultHandler extends ContentNegotiatingResultHandler
* processing will result in an IllegalStateException. * processing will result in an IllegalStateException.
*/ */
@SuppressWarnings("UnusedParameters") @SuppressWarnings("UnusedParameters")
protected String getDefaultViewName(ServerWebExchange exchange, HandlerResult result) { protected String getDefaultViewName(HandlerResult result, ServerWebExchange exchange) {
String path = this.pathHelper.getLookupPathForRequest(exchange); String path = this.pathHelper.getLookupPathForRequest(exchange);
if (path.startsWith("/")) { if (path.startsWith("/")) {
path = path.substring(1); path = path.substring(1);
@ -250,7 +255,7 @@ public class ViewResolutionResultHandler extends ContentNegotiatingResultHandler
return StringUtils.stripFilenameExtension(path); return StringUtils.stripFilenameExtension(path);
} }
private Object updateModel(HandlerResult result, Object value) { private Object updateModel(Object value, HandlerResult result) {
if (value instanceof Model) { if (value instanceof Model) {
result.getModel().addAllAttributes(((Model) value).asMap()); result.getModel().addAllAttributes(((Model) value).asMap());
} }
@ -293,7 +298,7 @@ public class ViewResolutionResultHandler extends ContentNegotiatingResultHandler
} }
} }
private Mono<? extends Void> resolveViewAndRender(String viewName, Locale locale, private Mono<? extends Void> resolveAndRender(String viewName, Locale locale,
HandlerResult result, ServerWebExchange exchange) { HandlerResult result, ServerWebExchange exchange) {
return Flux.fromIterable(getViewResolvers()) return Flux.fromIterable(getViewResolvers())

View File

@ -43,6 +43,7 @@ import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.ReactiveStreamsToRxJava1Converter; import org.springframework.core.convert.support.ReactiveStreamsToRxJava1Converter;
import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.core.io.buffer.support.DataBufferTestUtils;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.MockServerHttpRequest; import org.springframework.http.server.reactive.MockServerHttpRequest;
@ -88,7 +89,7 @@ public class ViewResolutionResultHandlerTests {
@Test @Test
public void supportsWithNullReturnValue() throws Exception { public void supports() throws Exception {
testSupports("handleString", true); testSupports("handleString", true);
testSupports("handleView", true); testSupports("handleView", true);
testSupports("handleMonoString", true); testSupports("handleMonoString", true);
@ -120,7 +121,8 @@ public class ViewResolutionResultHandlerTests {
handle("/path", value, "handleView"); handle("/path", value, "handleView");
new TestSubscriber<DataBuffer>().bindTo(this.response.getBody()) new TestSubscriber<DataBuffer>().bindTo(this.response.getBody())
.assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); .assertValuesWith(buf -> assertEquals("account: {id=123}",
DataBufferTestUtils.dumpString(buf, Charset.forName("UTF-8"))));
} }
@Test @Test
@ -129,7 +131,8 @@ public class ViewResolutionResultHandlerTests {
handle("/path", value, "handleMonoView"); handle("/path", value, "handleMonoView");
new TestSubscriber<DataBuffer>().bindTo(this.response.getBody()) new TestSubscriber<DataBuffer>().bindTo(this.response.getBody())
.assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); .assertValuesWith(buf -> assertEquals("account: {id=123}",
DataBufferTestUtils.dumpString(buf, Charset.forName("UTF-8"))));
} }
@Test @Test
@ -139,16 +142,18 @@ public class ViewResolutionResultHandlerTests {
TestSubscriber<DataBuffer> subscriber = new TestSubscriber<>(); TestSubscriber<DataBuffer> subscriber = new TestSubscriber<>();
subscriber.bindTo(this.response.getBody()) subscriber.bindTo(this.response.getBody())
.assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); .assertValuesWith(buf -> assertEquals("account: {id=123}",
DataBufferTestUtils.dumpString(buf, Charset.forName("UTF-8"))));
} }
@Test @Test
public void viewNameInMono() throws Exception { public void viewNameMono() throws Exception {
Object value = Mono.just("account"); Object value = Mono.just("account");
handle("/path", value, "handleMonoString", new TestViewResolver("account")); handle("/path", value, "handleMonoString", new TestViewResolver("account"));
new TestSubscriber<DataBuffer>().bindTo(this.response.getBody()) new TestSubscriber<DataBuffer>().bindTo(this.response.getBody())
.assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); .assertValuesWith(buf -> assertEquals("account: {id=123}",
DataBufferTestUtils.dumpString(buf, Charset.forName("UTF-8"))));
} }
@Test @Test
@ -158,7 +163,8 @@ public class ViewResolutionResultHandlerTests {
new TestViewResolver("account"), new TestViewResolver("profile")); new TestViewResolver("account"), new TestViewResolver("profile"));
new TestSubscriber<DataBuffer>().bindTo(this.response.getBody()) new TestSubscriber<DataBuffer>().bindTo(this.response.getBody())
.assertValuesWith(buf -> assertEquals("profile: {id=123}", asString(buf))); .assertValuesWith(buf -> assertEquals("profile: {id=123}",
DataBufferTestUtils.dumpString(buf, Charset.forName("UTF-8"))));
} }
@Test @Test
@ -173,15 +179,18 @@ public class ViewResolutionResultHandlerTests {
handle("/account", null, "handleString", resolver); handle("/account", null, "handleString", resolver);
new TestSubscriber<DataBuffer>().bindTo(this.response.getBody()) new TestSubscriber<DataBuffer>().bindTo(this.response.getBody())
.assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); .assertValuesWith(buf -> assertEquals("account: {id=123}",
DataBufferTestUtils.dumpString(buf, Charset.forName("UTF-8"))));
handle("/account/", null, "handleString", resolver); handle("/account/", null, "handleString", resolver);
new TestSubscriber<DataBuffer>().bindTo(this.response.getBody()) new TestSubscriber<DataBuffer>().bindTo(this.response.getBody())
.assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); .assertValuesWith(buf -> assertEquals("account: {id=123}",
DataBufferTestUtils.dumpString(buf, Charset.forName("UTF-8"))));
handle("/account.123", null, "handleString", resolver); handle("/account.123", null, "handleString", resolver);
new TestSubscriber<DataBuffer>().bindTo(this.response.getBody()) new TestSubscriber<DataBuffer>().bindTo(this.response.getBody())
.assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); .assertValuesWith(buf -> assertEquals("account: {id=123}",
DataBufferTestUtils.dumpString(buf, Charset.forName("UTF-8"))));
} }
@Test @Test
@ -190,47 +199,52 @@ public class ViewResolutionResultHandlerTests {
handle("/account", value, "handleMonoString", new TestViewResolver("account")); handle("/account", value, "handleMonoString", new TestViewResolver("account"));
new TestSubscriber<DataBuffer>().bindTo(this.response.getBody()) new TestSubscriber<DataBuffer>().bindTo(this.response.getBody())
.assertValuesWith(buf -> assertEquals("account: {id=123}", asString(buf))); .assertValuesWith(buf -> assertEquals("account: {id=123}",
DataBufferTestUtils.dumpString(buf, Charset.forName("UTF-8"))));
} }
@Test @Test
public void model() throws Exception { public void modelReturnValue() throws Exception {
Model value = new ExtendedModelMap().addAttribute("name", "Joe"); Model value = new ExtendedModelMap().addAttribute("name", "Joe");
handle("/account", value, "handleModel", new TestViewResolver("account")); handle("/account", value, "handleModel", new TestViewResolver("account"));
new TestSubscriber<DataBuffer>().bindTo(this.response.getBody()) new TestSubscriber<DataBuffer>().bindTo(this.response.getBody())
.assertValuesWith(buf -> assertEquals("account: {id=123, name=Joe}", asString(buf))); .assertValuesWith(buf -> assertEquals("account: {id=123, name=Joe}",
DataBufferTestUtils.dumpString(buf, Charset.forName("UTF-8"))));
} }
@Test @Test
public void map() throws Exception { public void mapReturnValue() throws Exception {
Map<String, String> value = Collections.singletonMap("name", "Joe"); Map<String, String> value = Collections.singletonMap("name", "Joe");
handle("/account", value, "handleMap", new TestViewResolver("account")); handle("/account", value, "handleMap", new TestViewResolver("account"));
new TestSubscriber<DataBuffer>().bindTo(this.response.getBody()) new TestSubscriber<DataBuffer>().bindTo(this.response.getBody())
.assertValuesWith(buf -> assertEquals("account: {id=123, name=Joe}", asString(buf))); .assertValuesWith(buf -> assertEquals("account: {id=123, name=Joe}",
DataBufferTestUtils.dumpString(buf, Charset.forName("UTF-8"))));
} }
@Test @Test
public void modelAttributeAnnotation() throws Exception { public void modelAttributeAnnotationReturnValue() throws Exception {
String value = "Joe"; String value = "Joe";
handle("/account", value, "handleModelAttributeAnnotation", new TestViewResolver("account")); handle("/account", value, "handleModelAttributeAnnotation", new TestViewResolver("account"));
new TestSubscriber<DataBuffer>().bindTo(this.response.getBody()) new TestSubscriber<DataBuffer>().bindTo(this.response.getBody())
.assertValuesWith(buf -> assertEquals("account: {id=123, name=Joe}", asString(buf))); .assertValuesWith(buf -> assertEquals("account: {id=123, name=Joe}",
DataBufferTestUtils.dumpString(buf, Charset.forName("UTF-8"))));
} }
@Test @Test
public void testBean() throws Exception { public void objectReturnValue() throws Exception {
Object value = new TestBean("Joe"); Object value = new TestBean("Joe");
handle("/account", value, "handleTestBean", new TestViewResolver("account")); handle("/account", value, "handleTestBean", new TestViewResolver("account"));
new TestSubscriber<DataBuffer>().bindTo(this.response.getBody()) new TestSubscriber<DataBuffer>().bindTo(this.response.getBody())
.assertValuesWith(buf -> assertEquals("account: {id=123, testBean=TestBean[name=Joe]}", asString(buf))); .assertValuesWith(buf -> assertEquals("account: {id=123, testBean=TestBean[name=Joe]}",
DataBufferTestUtils.dumpString(buf, Charset.forName("UTF-8"))));
} }
@Test @Test
public void selectBestMediaType() throws Exception { public void contentNegotiation() throws Exception {
TestView htmlView = new TestView("account"); TestView htmlView = new TestView("account");
htmlView.setMediaTypes(Collections.singletonList(MediaType.TEXT_HTML)); htmlView.setMediaTypes(Collections.singletonList(MediaType.TEXT_HTML));
@ -245,11 +259,12 @@ public class ViewResolutionResultHandlerTests {
assertEquals(MediaType.APPLICATION_JSON, this.response.getHeaders().getContentType()); assertEquals(MediaType.APPLICATION_JSON, this.response.getHeaders().getContentType());
new TestSubscriber<DataBuffer>().bindTo(this.response.getBody()) new TestSubscriber<DataBuffer>().bindTo(this.response.getBody())
.assertValuesWith(buf -> assertEquals("defaultView: {id=123}", asString(buf))); .assertValuesWith(buf -> assertEquals("defaultView: {id=123}",
DataBufferTestUtils.dumpString(buf, Charset.forName("UTF-8"))));
} }
@Test @Test
public void selectBestMediaTypeNotAcceptable() throws Exception { public void contentNegotiationNotAcceptable() throws Exception {
TestView htmlView = new TestView("account"); TestView htmlView = new TestView("account");
htmlView.setMediaTypes(Collections.singletonList(MediaType.TEXT_HTML)); htmlView.setMediaTypes(Collections.singletonList(MediaType.TEXT_HTML));
@ -304,18 +319,6 @@ public class ViewResolutionResultHandlerTests {
return subscriber.bindTo(mono).await(Duration.ofSeconds(1)); return subscriber.bindTo(mono).await(Duration.ofSeconds(1));
} }
private static DataBuffer asDataBuffer(String value) {
ByteBuffer byteBuffer = ByteBuffer.wrap(value.getBytes(Charset.forName("UTF-8")));
return new DefaultDataBufferFactory().wrap(byteBuffer);
}
private static String asString(DataBuffer dataBuffer) {
ByteBuffer byteBuffer = dataBuffer.asByteBuffer();
final byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
return new String(bytes, Charset.forName("UTF-8"));
}
private static class TestViewResolver implements ViewResolver, Ordered { private static class TestViewResolver implements ViewResolver, Ordered {
@ -381,7 +384,9 @@ public class ViewResolutionResultHandlerTests {
if (mediaType != null) { if (mediaType != null) {
response.getHeaders().setContentType(mediaType); response.getHeaders().setContentType(mediaType);
} }
return response.writeWith(Flux.just(asDataBuffer(value))); ByteBuffer byteBuffer = ByteBuffer.wrap(value.getBytes(Charset.forName("UTF-8")));
DataBuffer dataBuffer = new DefaultDataBufferFactory().wrap(byteBuffer);
return response.writeWith(Flux.just(dataBuffer));
} }
} }