org.springframework.web.reactive.result.view.RequestContext Maven / Gradle / Ivy
/*
* Copyright 2002-2018 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.TimeZone;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.NoSuchMessageException;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
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.
*
* Suitable for exposition to views, and usage within FreeMarker templates,
* and tag libraries.
*
*
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 model;
private final MessageSource messageSource;
private Locale locale;
private TimeZone timeZone;
@Nullable
private Boolean defaultHtmlEscape;
@Nullable
private Map errorsMap;
@Nullable
private RequestDataValueProcessor dataValueProcessor;
public RequestContext(ServerWebExchange exchange, Map model, MessageSource messageSource) {
this(exchange, model, messageSource, null);
}
public RequestContext(ServerWebExchange exchange, Map model, MessageSource messageSource,
@Nullable RequestDataValueProcessor dataValueProcessor) {
Assert.notNull(exchange, "ServerWebExchange is required");
Assert.notNull(model, "Model is required");
Assert.notNull(messageSource, "MessageSource is required");
this.exchange = exchange;
this.model = model;
this.messageSource = messageSource;
LocaleContext localeContext = exchange.getLocaleContext();
Locale locale = localeContext.getLocale();
this.locale = (locale != null ? locale : Locale.getDefault());
TimeZone timeZone = (localeContext instanceof TimeZoneAwareLocaleContext ?
((TimeZoneAwareLocaleContext) localeContext).getTimeZone() : null);
this.timeZone = (timeZone != null ? timeZone : TimeZone.getDefault());
this.defaultHtmlEscape = null; // TODO
this.dataValueProcessor = dataValueProcessor;
}
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
*/
@Nullable
public Map getModel() {
return this.model;
}
/**
* Return the current Locale.
*/
public final Locale getLocale() {
return this.locale;
}
/**
* Return the current TimeZone.
*/
public TimeZone getTimeZone() {
return this.timeZone;
}
/**
* Change the current locale to the specified one.
*/
public void changeLocale(Locale locale) {
this.locale = locale;
}
/**
* Change the current locale to the specified locale and time zone context.
*/
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.
* 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)
*/
@Nullable
public Boolean getDefaultHtmlEscape() {
return this.defaultHtmlEscape;
}
/**
* Return the {@link RequestDataValueProcessor} instance to apply to in form
* tag libraries and to redirect URLs.
*/
@Nullable
public RequestDataValueProcessor getRequestDataValueProcessor() {
return this.dataValueProcessor;
}
/**
* Return the context path of the current web application. This is
* useful for building links to other resources within the application.
*
Delegates to {@link ServerHttpRequest#getPath()}.
*/
public String getContextPath() {
return this.exchange.getRequest().getPath().contextPath().value();
}
/**
* 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 = StringUtils.applyRelativePath(getContextPath() + "/", relativeUrl);
return getExchange().transformUrl(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 params) {
String url = StringUtils.applyRelativePath(getContextPath() + "/", relativeUrl);
UriTemplate template = new UriTemplate(url);
url = template.expand(params).toASCIIString();
return getExchange().transformUrl(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 the 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 the String to return if the lookup fails
* @return the message
*/
public String getMessage(String code, @Nullable 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 the String to return if the lookup fails
* @return the message
*/
public String getMessage(String code, @Nullable 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 the String to return if the lookup fails
* @param htmlEscape if the message should be HTML-escaped
* @return the message
*/
public String getMessage(String code, @Nullable Object[] args, String defaultMessage, boolean htmlEscape) {
String msg = this.messageSource.getMessage(code, args, defaultMessage, this.locale);
if (msg == null) {
return "";
}
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, @Nullable 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, @Nullable 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 if the message should be HTML-escaped
* @return the message
* @throws org.springframework.context.NoSuchMessageException if not found
*/
public String getMessage(String code, @Nullable 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 if the message should be HTML-escaped
* @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
*/
@Nullable
public 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
*/
@Nullable
public Errors getErrors(String name, boolean htmlEscape) {
if (this.errorsMap == null) {
this.errorsMap = new HashMap<>();
}
Errors errors = this.errorsMap.get(name);
if (errors == null) {
errors = getModelObject(BindingResult.MODEL_KEY_PREFIX + name);
if (errors == null) {
return null;
}
}
if (errors instanceof BindException) {
errors = ((BindException) errors).getBindingResult();
}
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")
@Nullable
protected T getModelObject(String modelName) {
T modelObject = (T) this.model.get(modelName);
if (modelObject == null) {
modelObject = this.exchange.getAttribute(modelName);
}
return modelObject;
}
/**
* 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);
}
}