org.springframework.context.support.AbstractMessageSource Maven / Gradle / Ivy
Show all versions of spring-context Show documentation
/*
* Copyright 2002-2013 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.context.support;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import org.springframework.context.HierarchicalMessageSource;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.NoSuchMessageException;
import org.springframework.util.ObjectUtils;
/**
* Abstract implementation of the {@link HierarchicalMessageSource} interface,
* implementing common handling of message variants, making it easy
* to implement a specific strategy for a concrete MessageSource.
*
* Subclasses must implement the abstract {@link #resolveCode}
* method. For efficient resolution of messages without arguments, the
* {@link #resolveCodeWithoutArguments} method should be overridden
* as well, resolving messages without a MessageFormat being involved.
*
*
Note: By default, message texts are only parsed through
* MessageFormat if arguments have been passed in for the message. In case
* of no arguments, message texts will be returned as-is. As a consequence,
* you should only use MessageFormat escaping for messages with actual
* arguments, and keep all other messages unescaped. If you prefer to
* escape all messages, set the "alwaysUseMessageFormat" flag to "true".
*
*
Supports not only MessageSourceResolvables as primary messages
* but also resolution of message arguments that are in turn
* MessageSourceResolvables themselves.
*
*
This class does not implement caching of messages per code, thus
* subclasses can dynamically change messages over time. Subclasses are
* encouraged to cache their messages in a modification-aware fashion,
* allowing for hot deployment of updated messages.
*
* @author Juergen Hoeller
* @author Rod Johnson
* @see #resolveCode(String, java.util.Locale)
* @see #resolveCodeWithoutArguments(String, java.util.Locale)
* @see #setAlwaysUseMessageFormat
* @see java.text.MessageFormat
*/
public abstract class AbstractMessageSource extends MessageSourceSupport implements HierarchicalMessageSource {
private MessageSource parentMessageSource;
private Properties commonMessages;
private boolean useCodeAsDefaultMessage = false;
@Override
public void setParentMessageSource(MessageSource parent) {
this.parentMessageSource = parent;
}
@Override
public MessageSource getParentMessageSource() {
return this.parentMessageSource;
}
/**
* Specify locale-independent common messages, with the message code as key
* and the full message String (may contain argument placeholders) as value.
*
May also link to an externally defined Properties object, e.g. defined
* through a {@link org.springframework.beans.factory.config.PropertiesFactoryBean}.
*/
public void setCommonMessages(Properties commonMessages) {
this.commonMessages = commonMessages;
}
/**
* Return a Properties object defining locale-independent common messages, if any.
*/
protected Properties getCommonMessages() {
return this.commonMessages;
}
/**
* Set whether to use the message code as default message instead of
* throwing a NoSuchMessageException. Useful for development and debugging.
* Default is "false".
*
Note: In case of a MessageSourceResolvable with multiple codes
* (like a FieldError) and a MessageSource that has a parent MessageSource,
* do not activate "useCodeAsDefaultMessage" in the parent:
* Else, you'll get the first code returned as message by the parent,
* without attempts to check further codes.
*
To be able to work with "useCodeAsDefaultMessage" turned on in the parent,
* AbstractMessageSource and AbstractApplicationContext contain special checks
* to delegate to the internal {@link #getMessageInternal} method if available.
* In general, it is recommended to just use "useCodeAsDefaultMessage" during
* development and not rely on it in production in the first place, though.
* @see #getMessage(String, Object[], Locale)
* @see org.springframework.validation.FieldError
*/
public void setUseCodeAsDefaultMessage(boolean useCodeAsDefaultMessage) {
this.useCodeAsDefaultMessage = useCodeAsDefaultMessage;
}
/**
* Return whether to use the message code as default message instead of
* throwing a NoSuchMessageException. Useful for development and debugging.
* Default is "false".
*
Alternatively, consider overriding the {@link #getDefaultMessage}
* method to return a custom fallback message for an unresolvable code.
* @see #getDefaultMessage(String)
*/
protected boolean isUseCodeAsDefaultMessage() {
return this.useCodeAsDefaultMessage;
}
@Override
public final String getMessage(String code, Object[] args, String defaultMessage, Locale locale) {
String msg = getMessageInternal(code, args, locale);
if (msg != null) {
return msg;
}
if (defaultMessage == null) {
String fallback = getDefaultMessage(code);
if (fallback != null) {
return fallback;
}
}
return renderDefaultMessage(defaultMessage, args, locale);
}
@Override
public final String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException {
String msg = getMessageInternal(code, args, locale);
if (msg != null) {
return msg;
}
String fallback = getDefaultMessage(code);
if (fallback != null) {
return fallback;
}
throw new NoSuchMessageException(code, locale);
}
@Override
public final String getMessage(MessageSourceResolvable resolvable, Locale locale)
throws NoSuchMessageException {
String[] codes = resolvable.getCodes();
if (codes == null) {
codes = new String[0];
}
for (String code : codes) {
String msg = getMessageInternal(code, resolvable.getArguments(), locale);
if (msg != null) {
return msg;
}
}
String defaultMessage = resolvable.getDefaultMessage();
if (defaultMessage != null) {
return renderDefaultMessage(defaultMessage, resolvable.getArguments(), locale);
}
if (codes.length > 0) {
String fallback = getDefaultMessage(codes[0]);
if (fallback != null) {
return fallback;
}
}
throw new NoSuchMessageException(codes.length > 0 ? codes[codes.length - 1] : null, locale);
}
/**
* Resolve the given code and arguments as message in the given Locale,
* returning {@code null} if not found. Does not fall back to
* the code as default message. Invoked by {@code getMessage} methods.
* @param code the code to lookup up, such as 'calculator.noRateSet'
* @param args array of arguments that will be filled in for params
* within the message
* @param locale the Locale in which to do the lookup
* @return the resolved message, or {@code null} if not found
* @see #getMessage(String, Object[], String, Locale)
* @see #getMessage(String, Object[], Locale)
* @see #getMessage(MessageSourceResolvable, Locale)
* @see #setUseCodeAsDefaultMessage
*/
protected String getMessageInternal(String code, Object[] args, Locale locale) {
if (code == null) {
return null;
}
if (locale == null) {
locale = Locale.getDefault();
}
Object[] argsToUse = args;
if (!isAlwaysUseMessageFormat() && ObjectUtils.isEmpty(args)) {
// Optimized resolution: no arguments to apply,
// therefore no MessageFormat needs to be involved.
// Note that the default implementation still uses MessageFormat;
// this can be overridden in specific subclasses.
String message = resolveCodeWithoutArguments(code, locale);
if (message != null) {
return message;
}
}
else {
// Resolve arguments eagerly, for the case where the message
// is defined in a parent MessageSource but resolvable arguments
// are defined in the child MessageSource.
argsToUse = resolveArguments(args, locale);
MessageFormat messageFormat = resolveCode(code, locale);
if (messageFormat != null) {
synchronized (messageFormat) {
return messageFormat.format(argsToUse);
}
}
}
// Check locale-independent common messages for the given message code.
Properties commonMessages = getCommonMessages();
if (commonMessages != null) {
String commonMessage = commonMessages.getProperty(code);
if (commonMessage != null) {
return formatMessage(commonMessage, args, locale);
}
}
// Not found -> check parent, if any.
return getMessageFromParent(code, argsToUse, locale);
}
/**
* Try to retrieve the given message from the parent MessageSource, if any.
* @param code the code to lookup up, such as 'calculator.noRateSet'
* @param args array of arguments that will be filled in for params
* within the message
* @param locale the Locale in which to do the lookup
* @return the resolved message, or {@code null} if not found
* @see #getParentMessageSource()
*/
protected String getMessageFromParent(String code, Object[] args, Locale locale) {
MessageSource parent = getParentMessageSource();
if (parent != null) {
if (parent instanceof AbstractMessageSource) {
// Call internal method to avoid getting the default code back
// in case of "useCodeAsDefaultMessage" being activated.
return ((AbstractMessageSource) parent).getMessageInternal(code, args, locale);
}
else {
// Check parent MessageSource, returning null if not found there.
return parent.getMessage(code, args, null, locale);
}
}
// Not found in parent either.
return null;
}
/**
* Return a fallback default message for the given code, if any.
*
Default is to return the code itself if "useCodeAsDefaultMessage" is activated,
* or return no fallback else. In case of no fallback, the caller will usually
* receive a NoSuchMessageException from {@code getMessage}.
* @param code the message code that we couldn't resolve
* and that we didn't receive an explicit default message for
* @return the default message to use, or {@code null} if none
* @see #setUseCodeAsDefaultMessage
*/
protected String getDefaultMessage(String code) {
if (isUseCodeAsDefaultMessage()) {
return code;
}
return null;
}
/**
* Searches through the given array of objects, finds any MessageSourceResolvable
* objects and resolves them.
*
Allows for messages to have MessageSourceResolvables as arguments.
* @param args array of arguments for a message
* @param locale the locale to resolve through
* @return an array of arguments with any MessageSourceResolvables resolved
*/
@Override
protected Object[] resolveArguments(Object[] args, Locale locale) {
if (args == null) {
return new Object[0];
}
List