5 changed files with 900 additions and 0 deletions
@ -0,0 +1,342 @@
@@ -0,0 +1,342 @@
|
||||
/* |
||||
* 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(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,414 @@
@@ -0,0 +1,414 @@
|
||||
/* |
||||
* 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); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,86 @@
@@ -0,0 +1,86 @@
|
||||
/* |
||||
* 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)); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue