Rossen Stoyanchev
8 years ago
8 changed files with 553 additions and 10 deletions
@ -0,0 +1,80 @@
@@ -0,0 +1,80 @@
|
||||
/* |
||||
* Copyright 2002-2017 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.Collections; |
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.ui.Model; |
||||
|
||||
/** |
||||
* Default implementation of {@link Rendering}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 5.0 |
||||
*/ |
||||
class DefaultRendering implements Rendering { |
||||
|
||||
private static final HttpHeaders EMPTY_HEADERS = HttpHeaders.readOnlyHttpHeaders(new HttpHeaders()); |
||||
|
||||
|
||||
private final Object view; |
||||
|
||||
private final Map<String, Object> model; |
||||
|
||||
private final HttpStatus status; |
||||
|
||||
private final HttpHeaders headers; |
||||
|
||||
|
||||
DefaultRendering(Object view, Model model, HttpStatus status, HttpHeaders headers) { |
||||
this.view = view; |
||||
this.model = (model != null ? model.asMap() : Collections.emptyMap()); |
||||
this.status = status; |
||||
this.headers = headers != null ? headers : EMPTY_HEADERS; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Optional<Object> view() { |
||||
return Optional.ofNullable(this.view); |
||||
} |
||||
|
||||
@Override |
||||
public Map<String, Object> modelAttributes() { |
||||
return this.model; |
||||
} |
||||
|
||||
@Override |
||||
public Optional<HttpStatus> status() { |
||||
return Optional.ofNullable(this.status); |
||||
} |
||||
|
||||
@Override |
||||
public HttpHeaders headers() { |
||||
return this.headers; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "Rendering[view=" + this.view + ", modelAttributes=" + this.model + |
||||
", status=" + this.status + ", headers=" + this.headers + "]"; |
||||
} |
||||
} |
@ -0,0 +1,131 @@
@@ -0,0 +1,131 @@
|
||||
/* |
||||
* Copyright 2002-2017 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.Arrays; |
||||
import java.util.Map; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.ui.ExtendedModelMap; |
||||
import org.springframework.ui.Model; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Default implementation of {@link Rendering.RedirectBuilder}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 5.0 |
||||
*/ |
||||
class DefaultRenderingBuilder implements Rendering.RedirectBuilder { |
||||
|
||||
private final Object view; |
||||
|
||||
private Model model; |
||||
|
||||
private HttpStatus status; |
||||
|
||||
private HttpHeaders headers; |
||||
|
||||
|
||||
DefaultRenderingBuilder(Object view) { |
||||
this.view = view; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public DefaultRenderingBuilder modelAttribute(String name, Object value) { |
||||
initModel(); |
||||
this.model.addAttribute(name, value); |
||||
return this; |
||||
} |
||||
|
||||
private void initModel() { |
||||
if (this.model == null) { |
||||
this.model = new ExtendedModelMap(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public DefaultRenderingBuilder modelAttribute(Object value) { |
||||
initModel(); |
||||
this.model.addAttribute(value); |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public DefaultRenderingBuilder modelAttributes(Object... values) { |
||||
initModel(); |
||||
this.model.addAllAttributes(Arrays.asList(values)); |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public DefaultRenderingBuilder model(Map<String, ?> map) { |
||||
initModel(); |
||||
this.model.addAllAttributes(map); |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public DefaultRenderingBuilder status(HttpStatus status) { |
||||
this.status = status; |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public DefaultRenderingBuilder header(String headerName, String... headerValues) { |
||||
initHeaders(); |
||||
this.headers.put(headerName, Arrays.asList(headerValues)); |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public DefaultRenderingBuilder headers(HttpHeaders headers) { |
||||
initHeaders(); |
||||
this.headers.putAll(headers); |
||||
return this; |
||||
} |
||||
|
||||
private void initHeaders() { |
||||
if (this.headers == null) { |
||||
this.headers = new HttpHeaders(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public Rendering.RedirectBuilder contextRelative(boolean contextRelative) { |
||||
getRedirectView().setContextRelative(contextRelative); |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public Rendering.RedirectBuilder propagateQuery(boolean propagate) { |
||||
getRedirectView().setPropagateQuery(propagate); |
||||
return this; |
||||
} |
||||
|
||||
private RedirectView getRedirectView() { |
||||
Assert.isInstanceOf(RedirectView.class, this.view); |
||||
return (RedirectView) this.view; |
||||
} |
||||
|
||||
@Override |
||||
public Rendering build() { |
||||
return new DefaultRendering(this.view, this.model, this.status, this.headers); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,163 @@
@@ -0,0 +1,163 @@
|
||||
/* |
||||
* Copyright 2002-2017 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.Map; |
||||
import java.util.Optional; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.ui.Model; |
||||
|
||||
|
||||
/** |
||||
* Public API for HTML rendering. Supported as a return value in Spring WebFlux |
||||
* controllers. Comparable to the use of {@code ModelAndView} as a return value |
||||
* in Spring MVC controllers. |
||||
* |
||||
* <p>Controllers typically return a {@link String} view name and rely on the |
||||
* "implicit" model which can also be injected into the controller method. |
||||
* Or controllers may return model attribute(s) and rely on a default view name |
||||
* being selected based on the request path. |
||||
* |
||||
* <p>{@link Rendering} can be used to combine a view name with model attributes, |
||||
* set the HTTP status or headers, and for other more advanced options around |
||||
* redirect scenarios. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 5.0 |
||||
*/ |
||||
public interface Rendering { |
||||
|
||||
/** |
||||
* Return the selected {@link String} view name or {@link View} object. |
||||
*/ |
||||
Optional<Object> view(); |
||||
|
||||
/** |
||||
* Return attributes to add to the model. |
||||
*/ |
||||
Map<String, Object> modelAttributes(); |
||||
|
||||
/** |
||||
* Return the HTTP status to set the response to. |
||||
*/ |
||||
Optional<HttpStatus> status(); |
||||
|
||||
/** |
||||
* Return headers to add to the response. |
||||
*/ |
||||
HttpHeaders headers(); |
||||
|
||||
|
||||
/** |
||||
* Create a new builder for response rendering based on the given view name. |
||||
* @param name the view name to be resolved to a {@link View} |
||||
* @return the builder |
||||
*/ |
||||
static Builder<?> view(String name) { |
||||
return new DefaultRenderingBuilder(name); |
||||
} |
||||
|
||||
/** |
||||
* Create a new builder for a redirect through a {@link RedirectView}. |
||||
* @param url the redirect URL |
||||
* @return the builder |
||||
*/ |
||||
static RedirectBuilder redirectTo(String url) { |
||||
return new DefaultRenderingBuilder(new RedirectView(url)); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Defines a builder for {@link Rendering}. |
||||
*/ |
||||
interface Builder<B extends Builder<B>> { |
||||
|
||||
/** |
||||
* Add the given model attribute with the supplied name. |
||||
* @see Model#addAttribute(String, Object) |
||||
*/ |
||||
B modelAttribute(String name, Object value); |
||||
|
||||
/** |
||||
* Add an attribute to the model using a |
||||
* {@link org.springframework.core.Conventions#getVariableName generated name}. |
||||
* @see Model#addAttribute(Object) |
||||
*/ |
||||
B modelAttribute(Object value); |
||||
|
||||
/** |
||||
* Add all given attributes to the model using |
||||
* {@link org.springframework.core.Conventions#getVariableName generated names}. |
||||
* @see Model#addAllAttributes(Collection) |
||||
*/ |
||||
B modelAttributes(Object... values); |
||||
|
||||
/** |
||||
* Add the given attributes to the model. |
||||
* @see Model#addAllAttributes(Map) |
||||
*/ |
||||
B model(Map<String, ?> map); |
||||
|
||||
/** |
||||
* Specify the status to use for the response. |
||||
*/ |
||||
B status(HttpStatus status); |
||||
|
||||
/** |
||||
* Specify a header to add to the response. |
||||
*/ |
||||
B header(String headerName, String... headerValues); |
||||
|
||||
/** |
||||
* Specify headers to add to the response. |
||||
*/ |
||||
B headers(HttpHeaders headers); |
||||
|
||||
/** |
||||
* Builder the {@link Rendering} instance. |
||||
*/ |
||||
Rendering build(); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Extends {@link Builder} with extra options for redirect scenarios. |
||||
*/ |
||||
interface RedirectBuilder extends Builder<RedirectBuilder> { |
||||
|
||||
/** |
||||
* Whether to the provided redirect URL should be prepended with the |
||||
* application context path (if any). |
||||
* <p>By default this is set to {@code true}. |
||||
* |
||||
* @see RedirectView#setContextRelative(boolean) |
||||
*/ |
||||
RedirectBuilder contextRelative(boolean contextRelative); |
||||
|
||||
/** |
||||
* Whether to append the query string of the current URL to the target |
||||
* redirect URL or not. |
||||
* <p>By default this is set to {@code false}. |
||||
* |
||||
* @see RedirectView#setPropagateQuery(boolean) |
||||
*/ |
||||
RedirectBuilder propagateQuery(boolean propagate); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,140 @@
@@ -0,0 +1,140 @@
|
||||
/* |
||||
* Copyright 2002-2017 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.Arrays; |
||||
import java.util.Collections; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.Map; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.ui.ExtendedModelMap; |
||||
import org.springframework.ui.Model; |
||||
|
||||
import static org.junit.Assert.assertEquals; |
||||
import static org.junit.Assert.assertFalse; |
||||
import static org.junit.Assert.assertNull; |
||||
import static org.junit.Assert.assertTrue; |
||||
|
||||
|
||||
/** |
||||
* Unit tests for {@link DefaultRenderingBuilder}. |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public class DefaultRenderingBuilderTests { |
||||
|
||||
|
||||
@Test |
||||
public void defaultValues() { |
||||
Rendering rendering = Rendering.view("abc").build(); |
||||
|
||||
assertEquals("abc", rendering.view().orElse(null)); |
||||
assertEquals(Collections.emptyMap(), rendering.modelAttributes()); |
||||
assertNull(rendering.status().orElse(null)); |
||||
assertEquals(0, rendering.headers().size()); |
||||
} |
||||
|
||||
@Test |
||||
public void defaultValuesForRedirect() throws Exception { |
||||
Rendering rendering = Rendering.redirectTo("abc").build(); |
||||
|
||||
Object view = rendering.view().orElse(null); |
||||
assertEquals(RedirectView.class, view.getClass()); |
||||
assertEquals("abc", ((RedirectView) view).getUrl()); |
||||
assertTrue(((RedirectView) view).isContextRelative()); |
||||
assertFalse(((RedirectView) view).isPropagateQuery()); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void viewName() { |
||||
Rendering rendering = Rendering.view("foo").build(); |
||||
assertEquals("foo", rendering.view().orElse(null)); |
||||
} |
||||
|
||||
@Test |
||||
public void modelAttribute() throws Exception { |
||||
Foo foo = new Foo(); |
||||
Rendering rendering = Rendering.view("foo").modelAttribute(foo).build(); |
||||
|
||||
assertEquals(Collections.singletonMap("foo", foo), rendering.modelAttributes()); |
||||
} |
||||
|
||||
@Test |
||||
public void modelAttributes() throws Exception { |
||||
Foo foo = new Foo(); |
||||
Bar bar = new Bar(); |
||||
Rendering rendering = Rendering.view("foo").modelAttributes(foo, bar).build(); |
||||
|
||||
Map<String, Object> map = new LinkedHashMap<>(2); |
||||
map.put("foo", foo); |
||||
map.put("bar", bar); |
||||
assertEquals(map, rendering.modelAttributes()); |
||||
} |
||||
|
||||
@Test |
||||
public void model() throws Exception { |
||||
Map<String, Object> map = new LinkedHashMap<>(); |
||||
map.put("foo", new Foo()); |
||||
map.put("bar", new Bar()); |
||||
Rendering rendering = Rendering.view("foo").model(map).build(); |
||||
|
||||
assertEquals(map, rendering.modelAttributes()); |
||||
} |
||||
|
||||
@Test |
||||
public void header() throws Exception { |
||||
Rendering rendering = Rendering.view("foo").header("foo", "bar").build(); |
||||
|
||||
assertEquals(1, rendering.headers().size()); |
||||
assertEquals(Collections.singletonList("bar"), rendering.headers().get("foo")); |
||||
} |
||||
|
||||
@Test |
||||
public void httpHeaders() throws Exception { |
||||
HttpHeaders headers = new HttpHeaders(); |
||||
headers.add("foo", "bar"); |
||||
Rendering rendering = Rendering.view("foo").headers(headers).build(); |
||||
|
||||
assertEquals(headers, rendering.headers()); |
||||
} |
||||
|
||||
@Test |
||||
public void redirectWithAbsoluteUrl() throws Exception { |
||||
Rendering rendering = Rendering.redirectTo("foo").contextRelative(false).build(); |
||||
|
||||
Object view = rendering.view().orElse(null); |
||||
assertEquals(RedirectView.class, view.getClass()); |
||||
assertFalse(((RedirectView) view).isContextRelative()); |
||||
} |
||||
|
||||
@Test |
||||
public void redirectWithPropagateQuery() throws Exception { |
||||
Rendering rendering = Rendering.redirectTo("foo").propagateQuery(true).build(); |
||||
|
||||
Object view = rendering.view().orElse(null); |
||||
assertEquals(RedirectView.class, view.getClass()); |
||||
assertTrue(((RedirectView) view).isPropagateQuery()); |
||||
} |
||||
|
||||
|
||||
private static class Foo {} |
||||
|
||||
private static class Bar {} |
||||
|
||||
} |
Loading…
Reference in new issue