提交 8ad95b09 编写于 作者: R Rossen Stoyanchev

ReactiveContext and BindStatus in spring-web-reactive

Issue: SPR-14533
上级 136b33bc
......@@ -49,6 +49,8 @@ public abstract class AbstractView implements View, ApplicationContextAware {
private Charset defaultCharset = StandardCharsets.UTF_8;
private String requestContextAttribute;
private ApplicationContext applicationContext;
......@@ -95,6 +97,21 @@ public abstract class AbstractView implements View, ApplicationContextAware {
return this.defaultCharset;
}
/**
* Set the name of the RequestContext attribute for this view.
* Default is none.
*/
public void setRequestContextAttribute(String requestContextAttribute) {
this.requestContextAttribute = requestContextAttribute;
}
/**
* Return the name of the RequestContext attribute, if any.
*/
public String getRequestContextAttribute() {
return this.requestContextAttribute;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
......@@ -126,6 +143,12 @@ public abstract class AbstractView implements View, ApplicationContextAware {
}
Map<String, Object> mergedModel = getModelAttributes(model, exchange);
// Expose RequestContext?
if (this.requestContextAttribute != null) {
mergedModel.put(this.requestContextAttribute, createRequestContext(exchange, mergedModel));
}
return renderInternal(mergedModel, contentType, exchange);
}
......@@ -145,6 +168,20 @@ public abstract class AbstractView implements View, ApplicationContextAware {
return attributes;
}
/**
* Create a RequestContext to expose under the specified attribute name.
* <p>The default implementation creates a standard RequestContext instance for the
* given request and model. Can be overridden in subclasses for custom instances.
* @param exchange current exchange
* @param model combined output Map (never {@code null}),
* with dynamic values taking precedence over static attributes
* @return the RequestContext instance
* @see #setRequestContextAttribute
*/
protected RequestContext createRequestContext(ServerWebExchange exchange, Map<String, Object> model) {
return new RequestContext(exchange, model, this.applicationContext);
}
/**
* Subclasses must implement this method to actually render the view.
* @param renderAttributes combined output Map (never {@code null}),
......
/*
* 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.beans.PropertyEditor;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.context.NoSuchMessageException;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.ObjectError;
import org.springframework.web.util.HtmlUtils;
/**
* Simple adapter to expose the bind status of a field or object.
* Set as a variable by FreeMarker macros and other tag libraries.
*
* <p>Obviously, object status representations (i.e. errors at the object level
* rather than the field level) do not have an expression and a value but only
* error codes and messages. For simplicity's sake and to be able to use the same
* tags and macros, the same status class is used for both scenarios.
*
* @author Rossen Stoyanchev
* @since 5.0
* @see RequestContext#getBindStatus
*/
public class BindStatus {
private final RequestContext requestContext;
private final String path;
private final boolean htmlEscape;
private final String expression;
private final Errors errors;
private BindingResult bindingResult;
private Object value;
private Class<?> valueType;
private Object actualValue;
private PropertyEditor editor;
private List<? extends ObjectError> objectErrors;
private String[] errorCodes;
private String[] errorMessages;
/**
* Create a new BindStatus instance, representing a field or object status.
* @param requestContext the current RequestContext
* @param path the bean and property path for which values and errors
* will be resolved (e.g. "customer.address.street")
* @param htmlEscape whether to HTML-escape error messages and string values
* @throws IllegalStateException if no corresponding Errors object found
*/
public BindStatus(RequestContext requestContext, String path, boolean htmlEscape)
throws IllegalStateException {
this.requestContext = requestContext;
this.path = path;
this.htmlEscape = htmlEscape;
// determine name of the object and property
String beanName;
int dotPos = path.indexOf('.');
if (dotPos == -1) {
// property not set, only the object itself
beanName = path;
this.expression = null;
}
else {
beanName = path.substring(0, dotPos);
this.expression = path.substring(dotPos + 1);
}
this.errors = requestContext.getErrors(beanName, false).orElse(null);
if (this.errors != null) {
// Usual case: A BindingResult is available as request attribute.
// Can determine error codes and messages for the given expression.
// Can use a custom PropertyEditor, as registered by a form controller.
if (this.expression != null) {
if ("*".equals(this.expression)) {
this.objectErrors = this.errors.getAllErrors();
}
else if (this.expression.endsWith("*")) {
this.objectErrors = this.errors.getFieldErrors(this.expression);
}
else {
this.objectErrors = this.errors.getFieldErrors(this.expression);
this.value = this.errors.getFieldValue(this.expression);
this.valueType = this.errors.getFieldType(this.expression);
if (this.errors instanceof BindingResult) {
this.bindingResult = (BindingResult) this.errors;
this.actualValue = this.bindingResult.getRawFieldValue(this.expression);
this.editor = this.bindingResult.findEditor(this.expression, null);
}
else {
this.actualValue = this.value;
}
}
}
else {
this.objectErrors = this.errors.getGlobalErrors();
}
this.errorCodes = initErrorCodes(this.objectErrors);
}
else {
// No BindingResult available as request attribute:
// Probably forwarded directly to a form view.
// Let's do the best we can: extract a plain target if appropriate.
Object target = requestContext.getModelObject(beanName);
if (target == null) {
throw new IllegalStateException(
"Neither BindingResult nor plain target object for bean name '" +
beanName + "' available as request attribute");
}
if (this.expression != null && !"*".equals(this.expression) && !this.expression.endsWith("*")) {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(target);
this.value = bw.getPropertyValue(this.expression);
this.valueType = bw.getPropertyType(this.expression);
this.actualValue = this.value;
}
this.errorCodes = new String[0];
this.errorMessages = new String[0];
}
if (htmlEscape && this.value instanceof String) {
this.value = HtmlUtils.htmlEscape((String) this.value);
}
}
/**
* Extract the error codes from the ObjectError list.
*/
private static String[] initErrorCodes(List<? extends ObjectError> objectErrors) {
String[] errorCodes = new String[objectErrors.size()];
for (int i = 0; i < objectErrors.size(); i++) {
ObjectError error = objectErrors.get(i);
errorCodes[i] = error.getCode();
}
return errorCodes;
}
/**
* Return the bean and property path for which values and errors
* will be resolved (e.g. "customer.address.street").
*/
public String getPath() {
return this.path;
}
/**
* Return a bind expression that can be used in HTML forms as input name
* for the respective field, or {@code null} if not field-specific.
* <p>Returns a bind path appropriate for resubmission, e.g. "address.street".
* Note that the complete bind path as required by the bind tag is
* "customer.address.street", if bound to a "customer" bean.
*/
public String getExpression() {
return this.expression;
}
/**
* Return the current value of the field, i.e. either the property value
* or a rejected update, or {@code null} if not field-specific.
* <p>This value will be an HTML-escaped String if the original value
* already was a String.
*/
public Object getValue() {
return this.value;
}
/**
* Get the '{@code Class}' type of the field. Favor this instead of
* '{@code getValue().getClass()}' since '{@code getValue()}' may
* return '{@code null}'.
*/
public Class<?> getValueType() {
return this.valueType;
}
/**
* Return the actual value of the field, i.e. the raw property value,
* or {@code null} if not available.
*/
public Object getActualValue() {
return this.actualValue;
}
/**
* Return a suitable display value for the field, i.e. the stringified
* value if not null, and an empty string in case of a null value.
* <p>This value will be an HTML-escaped String if the original value
* was non-null: the {@code toString} result of the original value
* will get HTML-escaped.
*/
public String getDisplayValue() {
if (this.value instanceof String) {
return (String) this.value;
}
if (this.value != null) {
return (this.htmlEscape ?
HtmlUtils.htmlEscape(this.value.toString()) : this.value.toString());
}
return "";
}
/**
* Return if this status represents a field or object error.
*/
public boolean isError() {
return (this.errorCodes != null && this.errorCodes.length > 0);
}
/**
* Return the error codes for the field or object, if any.
* Returns an empty array instead of null if none.
*/
public String[] getErrorCodes() {
return this.errorCodes;
}
/**
* Return the first error codes for the field or object, if any.
*/
public String getErrorCode() {
return (this.errorCodes.length > 0 ? this.errorCodes[0] : "");
}
/**
* Return the resolved error messages for the field or object,
* if any. Returns an empty array instead of null if none.
*/
public String[] getErrorMessages() {
initErrorMessages();
return this.errorMessages;
}
/**
* Return the first error message for the field or object, if any.
*/
public String getErrorMessage() {
initErrorMessages();
return (this.errorMessages.length > 0 ? this.errorMessages[0] : "");
}
/**
* Return an error message string, concatenating all messages
* separated by the given delimiter.
* @param delimiter separator string, e.g. ", " or "<br>"
* @return the error message string
*/
public String getErrorMessagesAsString(String delimiter) {
initErrorMessages();
return StringUtils.arrayToDelimitedString(this.errorMessages, delimiter);
}
/**
* Extract the error messages from the ObjectError list.
*/
private void initErrorMessages() throws NoSuchMessageException {
if (this.errorMessages == null) {
this.errorMessages = new String[this.objectErrors.size()];
for (int i = 0; i < this.objectErrors.size(); i++) {
ObjectError error = this.objectErrors.get(i);
this.errorMessages[i] = this.requestContext.getMessage(error, this.htmlEscape);
}
}
}
/**
* Return the Errors instance (typically a BindingResult) that this
* bind status is currently associated with.
* @return the current Errors instance, or {@code null} if none
* @see org.springframework.validation.BindingResult
*/
public Errors getErrors() {
return this.errors;
}
/**
* Return the PropertyEditor for the property that this bind status
* is currently bound to.
* @return the current PropertyEditor, or {@code null} if none
*/
public PropertyEditor getEditor() {
return this.editor;
}
/**
* Find a PropertyEditor for the given value class, associated with
* the property that this bound status is currently bound to.
* @param valueClass the value class that an editor is needed for
* @return the associated PropertyEditor, or {@code null} if none
*/
public PropertyEditor findEditor(Class<?> valueClass) {
return (this.bindingResult != null ?
this.bindingResult.findEditor(this.expression, valueClass) : null);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("BindStatus: ");
sb.append("expression=[").append(this.expression).append("]; ");
sb.append("value=[").append(this.value).append("]");
if (isError()) {
sb.append("; errorCodes=").append(Arrays.asList(this.errorCodes));
}
return sb.toString();
}
}
/*
* 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.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.TimeZone;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.NoSuchMessageException;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.Assert;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.web.bind.EscapedErrors;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.HtmlUtils;
import org.springframework.web.util.UriTemplate;
/**
* Context holder for request-specific state, like the {@link MessageSource} to
* use, current locale, binding errors, etc. Provides easy access to localized
* messages and Errors instances.
*
* <p>Suitable for exposition to views, and usage within FreeMarker templates,
* and tag libraries.
*
* <p>Can be instantiated manually, or automatically exposed to views as model
* attribute via AbstractView's "requestContextAttribute" property.
*
* @author Rossen Stoyanchev
* @since 5.0
*/
public class RequestContext {
private final ServerWebExchange exchange;
private final Map<String, Object> model;
private final MessageSource messageSource;
private Boolean defaultHtmlEscape;
private Map<String, Errors> errorsMap;
private Locale locale;
private TimeZone timeZone;
public RequestContext(ServerWebExchange exchange, Map<String, Object> model,
MessageSource messageSource) {
Assert.notNull(exchange, "'exchange' is required");
Assert.notNull(model, "'model' is required");
Assert.notNull(messageSource, "'messageSource' is required");
this.exchange = exchange;
this.model = model;
this.messageSource = messageSource;
this.defaultHtmlEscape = null; // TODO
this.locale = Locale.getDefault(); // TODO
this.timeZone = TimeZone.getDefault(); // TODO
}
protected final ServerWebExchange getExchange() {
return this.exchange;
}
/**
* Return the MessageSource in use with this request.
*/
public MessageSource getMessageSource() {
return this.messageSource;
}
/**
* Return the model Map that this RequestContext encapsulates, if any.
* @return the populated model Map, or {@code null} if none available
*/
public Map<String, Object> getModel() {
return this.model;
}
/**
* Return the current Locale.
* TODO: currently this is Locale.getDefault()
*/
public final Locale getLocale() {
return this.locale;
}
/**
* Return the current TimeZone.
* TODO: currently this is the Timezone.getDefault()
*/
public TimeZone getTimeZone() {
return this.timeZone;
}
/**
* Change the current locale to the specified one.
* TODO: currently simply change the internal field
*/
public void changeLocale(Locale locale) {
this.locale = locale;
}
/**
* Change the current locale to the specified locale and time zone context.
* TODO: currently simply change the internal fields
*/
public void changeLocale(Locale locale, TimeZone timeZone) {
this.locale = locale;
this.timeZone = timeZone;
}
/**
* (De)activate default HTML escaping for messages and errors, for the scope
* of this RequestContext.
* <p>TODO: currently no application-wide setting ...
*/
public void setDefaultHtmlEscape(boolean defaultHtmlEscape) {
this.defaultHtmlEscape = defaultHtmlEscape;
}
/**
* Is default HTML escaping active? Falls back to {@code false} in case of
* no explicit default given.
*/
public boolean isDefaultHtmlEscape() {
return (this.defaultHtmlEscape != null && this.defaultHtmlEscape.booleanValue());
}
/**
* Return the default HTML escape setting, differentiating between no default
* specified and an explicit value.
* @return whether default HTML escaping is enabled (null = no explicit default)
*/
public Boolean getDefaultHtmlEscape() {
return this.defaultHtmlEscape;
}
/**
* Return the context path of the the current web application. This is
* useful for building links to other resources within the application.
* <p>Delegates to {@link ServerHttpRequest#getContextPath()}.
*/
public String getContextPath() {
return this.exchange.getRequest().getContextPath();
}
/**
* Return a context-aware URl for the given relative URL.
* @param relativeUrl the relative URL part
* @return a URL that points back to the current web application with an
* absolute path also URL-encoded accordingly
*/
public String getContextUrl(String relativeUrl) {
String url = getContextPath() + relativeUrl;
// TODO: this.response.encodeURL(url)
return url;
}
/**
* Return a context-aware URl for the given relative URL with placeholders --
* named keys with braces {@code {}}. For example, send in a relative URL
* {@code foo/{bar}?spam={spam}} and a parameter map {@code {bar=baz,spam=nuts}}
* and the result will be {@code [contextpath]/foo/baz?spam=nuts}.
* @param relativeUrl the relative URL part
* @param params a map of parameters to insert as placeholders in the url
* @return a URL that points back to the current web application with an
* absolute path also URL-encoded accordingly
*/
public String getContextUrl(String relativeUrl, Map<String, ?> params) {
String url = getContextPath() + relativeUrl;
UriTemplate template = new UriTemplate(url);
url = template.expand(params).toASCIIString();
// TODO: this.response.encodeURL(url)
return url;
}
/**
* Return the request path of the request. This is useful as HTML form
* action target, also in combination with the original query string.
*/
public String getRequestPath() {
return this.exchange.getRequest().getURI().getPath();
}
/**
* Return the query string of the current request. This is useful for
* building an HTML form action target in combination with the original
* request path.
*/
public String getQueryString() {
return this.exchange.getRequest().getURI().getQuery();
}
/**
* Retrieve the message for the given code, using the "defaultHtmlEscape" setting.
* @param code code of the message
* @param defaultMessage String to return if the lookup fails
* @return the message
*/
public String getMessage(String code, String defaultMessage) {
return getMessage(code, null, defaultMessage, isDefaultHtmlEscape());
}
/**
* Retrieve the message for the given code, using the "defaultHtmlEscape" setting.
* @param code code of the message
* @param args arguments for the message, or {@code null} if none
* @param defaultMessage String to return if the lookup fails
* @return the message
*/
public String getMessage(String code, Object[] args, String defaultMessage) {
return getMessage(code, args, defaultMessage, isDefaultHtmlEscape());
}
/**
* Retrieve the message for the given code, using the "defaultHtmlEscape" setting.
* @param code code of the message
* @param args arguments for the message as a List, or {@code null} if none
* @param defaultMessage String to return if the lookup fails
* @return the message
*/
public String getMessage(String code, List<?> args, String defaultMessage) {
return getMessage(code, (args != null ? args.toArray() : null), defaultMessage, isDefaultHtmlEscape());
}
/**
* Retrieve the message for the given code.
* @param code code of the message
* @param args arguments for the message, or {@code null} if none
* @param defaultMessage String to return if the lookup fails
* @param htmlEscape HTML escape the message?
* @return the message
*/
public String getMessage(String code, Object[] args, String defaultMessage, boolean htmlEscape) {
String msg = this.messageSource.getMessage(code, args, defaultMessage, this.locale);
return (htmlEscape ? HtmlUtils.htmlEscape(msg) : msg);
}
/**
* Retrieve the message for the given code, using the "defaultHtmlEscape" setting.
* @param code code of the message
* @return the message
* @throws org.springframework.context.NoSuchMessageException if not found
*/
public String getMessage(String code) throws NoSuchMessageException {
return getMessage(code, null, isDefaultHtmlEscape());
}
/**
* Retrieve the message for the given code, using the "defaultHtmlEscape" setting.
* @param code code of the message
* @param args arguments for the message, or {@code null} if none
* @return the message
* @throws org.springframework.context.NoSuchMessageException if not found
*/
public String getMessage(String code, Object[] args) throws NoSuchMessageException {
return getMessage(code, args, isDefaultHtmlEscape());
}
/**
* Retrieve the message for the given code, using the "defaultHtmlEscape" setting.
* @param code code of the message
* @param args arguments for the message as a List, or {@code null} if none
* @return the message
* @throws org.springframework.context.NoSuchMessageException if not found
*/
public String getMessage(String code, List<?> args) throws NoSuchMessageException {
return getMessage(code, (args != null ? args.toArray() : null), isDefaultHtmlEscape());
}
/**
* Retrieve the message for the given code.
* @param code code of the message
* @param args arguments for the message, or {@code null} if none
* @param htmlEscape HTML escape the message?
* @return the message
* @throws org.springframework.context.NoSuchMessageException if not found
*/
public String getMessage(String code, Object[] args, boolean htmlEscape) throws NoSuchMessageException {
String msg = this.messageSource.getMessage(code, args, this.locale);
return (htmlEscape ? HtmlUtils.htmlEscape(msg) : msg);
}
/**
* Retrieve the given MessageSourceResolvable (e.g. an ObjectError instance), using the "defaultHtmlEscape" setting.
* @param resolvable the MessageSourceResolvable
* @return the message
* @throws org.springframework.context.NoSuchMessageException if not found
*/
public String getMessage(MessageSourceResolvable resolvable) throws NoSuchMessageException {
return getMessage(resolvable, isDefaultHtmlEscape());
}
/**
* Retrieve the given MessageSourceResolvable (e.g. an ObjectError instance).
* @param resolvable the MessageSourceResolvable
* @param htmlEscape HTML escape the message?
* @return the message
* @throws org.springframework.context.NoSuchMessageException if not found
*/
public String getMessage(MessageSourceResolvable resolvable, boolean htmlEscape) throws NoSuchMessageException {
String msg = this.messageSource.getMessage(resolvable, this.locale);
return (htmlEscape ? HtmlUtils.htmlEscape(msg) : msg);
}
/**
* Retrieve the Errors instance for the given bind object, using the
* "defaultHtmlEscape" setting.
* @param name name of the bind object
* @return the Errors instance, or {@code null} if not found
*/
public Optional<Errors> getErrors(String name) {
return getErrors(name, isDefaultHtmlEscape());
}
/**
* Retrieve the Errors instance for the given bind object.
* @param name name of the bind object
* @param htmlEscape create an Errors instance with automatic HTML escaping?
* @return the Errors instance, or {@code null} if not found
*/
public Optional<Errors> getErrors(String name, boolean htmlEscape) {
if (this.errorsMap == null) {
this.errorsMap = new HashMap<>();
}
// Since there is no Optional orElse + flatMap...
Optional<Errors> optional = Optional.ofNullable(this.errorsMap.get(name));
optional = optional.isPresent() ? optional : getModelObject(BindingResult.MODEL_KEY_PREFIX + name);
return optional
.map(errors -> {
if (errors instanceof BindException) {
return ((BindException) errors).getBindingResult();
}
else {
return errors;
}
})
.map(errors -> {
if (htmlEscape && !(errors instanceof EscapedErrors)) {
errors = new EscapedErrors(errors);
}
else if (!htmlEscape && errors instanceof EscapedErrors) {
errors = ((EscapedErrors) errors).getSource();
}
this.errorsMap.put(name, errors);
return errors;
});
}
/**
* Retrieve the model object for the given model name, either from the model
* or from the request attributes.
* @param modelName the name of the model object
* @return the model object
*/
@SuppressWarnings("unchecked")
protected <T> Optional<T> getModelObject(String modelName) {
return Optional.ofNullable(this.model)
.map(model -> Optional.ofNullable((T) model.get(modelName)))
.orElse(this.exchange.getAttribute(modelName));
}
/**
* Create a BindStatus for the given bind object using the
* "defaultHtmlEscape" setting.
* @param path the bean and property path for which values and errors will
* be resolved (e.g. "person.age")
* @return the new BindStatus instance
* @throws IllegalStateException if no corresponding Errors object found
*/
public BindStatus getBindStatus(String path) throws IllegalStateException {
return new BindStatus(this, path, isDefaultHtmlEscape());
}
/**
* Create a BindStatus for the given bind object, using the
* "defaultHtmlEscape" setting.
* @param path the bean and property path for which values and errors will
* be resolved (e.g. "person.age")
* @param htmlEscape create a BindStatus with automatic HTML escaping?
* @return the new BindStatus instance
* @throws IllegalStateException if no corresponding Errors object found
*/
public BindStatus getBindStatus(String path, boolean htmlEscape) throws IllegalStateException {
return new BindStatus(this, path, htmlEscape);
}
}
......@@ -77,6 +77,8 @@ public class UrlBasedViewResolver extends ViewResolverSupport implements ViewRes
private Function<String, RedirectView> redirectViewProvider = url -> new RedirectView(url);
private String requestContextAttribute;
/**
* Set the view class to instantiate through {@link #createUrlBasedView(String)}.
......@@ -162,6 +164,23 @@ public class UrlBasedViewResolver extends ViewResolverSupport implements ViewRes
this.redirectViewProvider = redirectViewProvider;
}
/**
* Set the name of the RequestContext attribute for all views.
* @param requestContextAttribute name of the RequestContext attribute
* @see AbstractView#setRequestContextAttribute
*/
public void setRequestContextAttribute(String requestContextAttribute) {
this.requestContextAttribute = requestContextAttribute;
}
/**
* Return the name of the RequestContext attribute for all views, if any.
*/
protected String getRequestContextAttribute() {
return this.requestContextAttribute;
}
@Override
public void afterPropertiesSet() throws Exception {
if (getViewClass() == null) {
......@@ -169,6 +188,7 @@ public class UrlBasedViewResolver extends ViewResolverSupport implements ViewRes
}
}
@Override
public Mono<View> resolveViewName(String viewName, Locale locale) {
if (!canHandle(viewName, locale)) {
......@@ -221,6 +241,7 @@ public class UrlBasedViewResolver extends ViewResolverSupport implements ViewRes
protected AbstractUrlBasedView createUrlBasedView(String viewName) {
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
view.setSupportedMediaTypes(getSupportedMediaTypes());
view.setRequestContextAttribute(getRequestContextAttribute());
view.setDefaultCharset(getDefaultCharset());
view.setUrl(getPrefix() + viewName + getSuffix());
return view;
......
/*
* 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.Before;
import org.junit.Test;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.DefaultWebSessionManager;
import static org.junit.Assert.assertEquals;
/**
* Unit tests for {@link RequestContext}.
* @author Rossen Stoyanchev
*/
public class RequestContextTests {
private ServerWebExchange exchange;
private MockServerHttpRequest request;
private GenericApplicationContext applicationContext;
private Map<String, Object> model = new HashMap<>();
@Before
public void init() {
this.request = new MockServerHttpRequest();
MockServerHttpResponse response = new MockServerHttpResponse();
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
this.exchange = new DefaultServerWebExchange(this.request, response, sessionManager);
this.applicationContext = new GenericApplicationContext();
this.applicationContext.refresh();
}
@Test
public void testGetContextUrl() throws Exception {
this.request.setContextPath("foo/");
RequestContext context = new RequestContext(this.exchange, this.model, this.applicationContext);
assertEquals("foo/bar", context.getContextUrl("bar"));
}
@Test
public void testGetContextUrlWithMap() throws Exception {
this.request.setContextPath("foo/");
RequestContext context = new RequestContext(this.exchange, this.model, this.applicationContext);
Map<String, Object> map = new HashMap<>();
map.put("foo", "bar");
map.put("spam", "bucket");
assertEquals("foo/bar?spam=bucket", context.getContextUrl("{foo}?spam={spam}", map));
}
@Test
public void testGetContextUrlWithMapEscaping() throws Exception {
this.request.setContextPath("foo/");
RequestContext context = new RequestContext(this.exchange, this.model, this.applicationContext);
Map<String, Object> map = new HashMap<>();
map.put("foo", "bar baz");
map.put("spam", "&bucket=");
assertEquals("foo/bar%20baz?spam=%26bucket%3D", context.getContextUrl("{foo}?spam={spam}", map));
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册