001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.context.support;
018
019import java.text.MessageFormat;
020import java.util.ArrayList;
021import java.util.List;
022import java.util.Locale;
023import java.util.Properties;
024
025import org.springframework.context.HierarchicalMessageSource;
026import org.springframework.context.MessageSource;
027import org.springframework.context.MessageSourceResolvable;
028import org.springframework.context.NoSuchMessageException;
029import org.springframework.util.ObjectUtils;
030
031/**
032 * Abstract implementation of the {@link HierarchicalMessageSource} interface,
033 * implementing common handling of message variants, making it easy
034 * to implement a specific strategy for a concrete MessageSource.
035 *
036 * <p>Subclasses must implement the abstract {@link #resolveCode}
037 * method. For efficient resolution of messages without arguments, the
038 * {@link #resolveCodeWithoutArguments} method should be overridden
039 * as well, resolving messages without a MessageFormat being involved.
040 *
041 * <p><b>Note:</b> By default, message texts are only parsed through
042 * MessageFormat if arguments have been passed in for the message. In case
043 * of no arguments, message texts will be returned as-is. As a consequence,
044 * you should only use MessageFormat escaping for messages with actual
045 * arguments, and keep all other messages unescaped. If you prefer to
046 * escape all messages, set the "alwaysUseMessageFormat" flag to "true".
047 *
048 * <p>Supports not only MessageSourceResolvables as primary messages
049 * but also resolution of message arguments that are in turn
050 * MessageSourceResolvables themselves.
051 *
052 * <p>This class does not implement caching of messages per code, thus
053 * subclasses can dynamically change messages over time. Subclasses are
054 * encouraged to cache their messages in a modification-aware fashion,
055 * allowing for hot deployment of updated messages.
056 *
057 * @author Juergen Hoeller
058 * @author Rod Johnson
059 * @see #resolveCode(String, java.util.Locale)
060 * @see #resolveCodeWithoutArguments(String, java.util.Locale)
061 * @see #setAlwaysUseMessageFormat
062 * @see java.text.MessageFormat
063 */
064public abstract class AbstractMessageSource extends MessageSourceSupport implements HierarchicalMessageSource {
065
066        private MessageSource parentMessageSource;
067
068        private Properties commonMessages;
069
070        private boolean useCodeAsDefaultMessage = false;
071
072
073        @Override
074        public void setParentMessageSource(MessageSource parent) {
075                this.parentMessageSource = parent;
076        }
077
078        @Override
079        public MessageSource getParentMessageSource() {
080                return this.parentMessageSource;
081        }
082
083        /**
084         * Specify locale-independent common messages, with the message code as key
085         * and the full message String (may contain argument placeholders) as value.
086         * <p>May also link to an externally defined Properties object, e.g. defined
087         * through a {@link org.springframework.beans.factory.config.PropertiesFactoryBean}.
088         */
089        public void setCommonMessages(Properties commonMessages) {
090                this.commonMessages = commonMessages;
091        }
092
093        /**
094         * Return a Properties object defining locale-independent common messages, if any.
095         */
096        protected Properties getCommonMessages() {
097                return this.commonMessages;
098        }
099
100        /**
101         * Set whether to use the message code as default message instead of
102         * throwing a NoSuchMessageException. Useful for development and debugging.
103         * Default is "false".
104         * <p>Note: In case of a MessageSourceResolvable with multiple codes
105         * (like a FieldError) and a MessageSource that has a parent MessageSource,
106         * do <i>not</i> activate "useCodeAsDefaultMessage" in the <i>parent</i>:
107         * Else, you'll get the first code returned as message by the parent,
108         * without attempts to check further codes.
109         * <p>To be able to work with "useCodeAsDefaultMessage" turned on in the parent,
110         * AbstractMessageSource and AbstractApplicationContext contain special checks
111         * to delegate to the internal {@link #getMessageInternal} method if available.
112         * In general, it is recommended to just use "useCodeAsDefaultMessage" during
113         * development and not rely on it in production in the first place, though.
114         * @see #getMessage(String, Object[], Locale)
115         * @see org.springframework.validation.FieldError
116         */
117        public void setUseCodeAsDefaultMessage(boolean useCodeAsDefaultMessage) {
118                this.useCodeAsDefaultMessage = useCodeAsDefaultMessage;
119        }
120
121        /**
122         * Return whether to use the message code as default message instead of
123         * throwing a NoSuchMessageException. Useful for development and debugging.
124         * Default is "false".
125         * <p>Alternatively, consider overriding the {@link #getDefaultMessage}
126         * method to return a custom fallback message for an unresolvable code.
127         * @see #getDefaultMessage(String)
128         */
129        protected boolean isUseCodeAsDefaultMessage() {
130                return this.useCodeAsDefaultMessage;
131        }
132
133
134        @Override
135        public final String getMessage(String code, Object[] args, String defaultMessage, Locale locale) {
136                String msg = getMessageInternal(code, args, locale);
137                if (msg != null) {
138                        return msg;
139                }
140                if (defaultMessage == null) {
141                        String fallback = getDefaultMessage(code);
142                        if (fallback != null) {
143                                return fallback;
144                        }
145                }
146                return renderDefaultMessage(defaultMessage, args, locale);
147        }
148
149        @Override
150        public final String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException {
151                String msg = getMessageInternal(code, args, locale);
152                if (msg != null) {
153                        return msg;
154                }
155                String fallback = getDefaultMessage(code);
156                if (fallback != null) {
157                        return fallback;
158                }
159                throw new NoSuchMessageException(code, locale);
160        }
161
162        @Override
163        public final String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException {
164                String[] codes = resolvable.getCodes();
165                if (codes != null) {
166                        for (String code : codes) {
167                                String message = getMessageInternal(code, resolvable.getArguments(), locale);
168                                if (message != null) {
169                                        return message;
170                                }
171                        }
172                }
173                String defaultMessage = getDefaultMessage(resolvable, locale);
174                if (defaultMessage != null) {
175                        return defaultMessage;
176                }
177                throw new NoSuchMessageException(!ObjectUtils.isEmpty(codes) ? codes[codes.length - 1] : null, locale);
178        }
179
180
181        /**
182         * Resolve the given code and arguments as message in the given Locale,
183         * returning {@code null} if not found. Does <i>not</i> fall back to
184         * the code as default message. Invoked by {@code getMessage} methods.
185         * @param code the code to lookup up, such as 'calculator.noRateSet'
186         * @param args array of arguments that will be filled in for params
187         * within the message
188         * @param locale the locale in which to do the lookup
189         * @return the resolved message, or {@code null} if not found
190         * @see #getMessage(String, Object[], String, Locale)
191         * @see #getMessage(String, Object[], Locale)
192         * @see #getMessage(MessageSourceResolvable, Locale)
193         * @see #setUseCodeAsDefaultMessage
194         */
195        protected String getMessageInternal(String code, Object[] args, Locale locale) {
196                if (code == null) {
197                        return null;
198                }
199                if (locale == null) {
200                        locale = Locale.getDefault();
201                }
202                Object[] argsToUse = args;
203
204                if (!isAlwaysUseMessageFormat() && ObjectUtils.isEmpty(args)) {
205                        // Optimized resolution: no arguments to apply,
206                        // therefore no MessageFormat needs to be involved.
207                        // Note that the default implementation still uses MessageFormat;
208                        // this can be overridden in specific subclasses.
209                        String message = resolveCodeWithoutArguments(code, locale);
210                        if (message != null) {
211                                return message;
212                        }
213                }
214
215                else {
216                        // Resolve arguments eagerly, for the case where the message
217                        // is defined in a parent MessageSource but resolvable arguments
218                        // are defined in the child MessageSource.
219                        argsToUse = resolveArguments(args, locale);
220
221                        MessageFormat messageFormat = resolveCode(code, locale);
222                        if (messageFormat != null) {
223                                synchronized (messageFormat) {
224                                        return messageFormat.format(argsToUse);
225                                }
226                        }
227                }
228
229                // Check locale-independent common messages for the given message code.
230                Properties commonMessages = getCommonMessages();
231                if (commonMessages != null) {
232                        String commonMessage = commonMessages.getProperty(code);
233                        if (commonMessage != null) {
234                                return formatMessage(commonMessage, args, locale);
235                        }
236                }
237
238                // Not found -> check parent, if any.
239                return getMessageFromParent(code, argsToUse, locale);
240        }
241
242        /**
243         * Try to retrieve the given message from the parent {@code MessageSource}, if any.
244         * @param code the code to lookup up, such as 'calculator.noRateSet'
245         * @param args array of arguments that will be filled in for params
246         * within the message
247         * @param locale the locale in which to do the lookup
248         * @return the resolved message, or {@code null} if not found
249         * @see #getParentMessageSource()
250         */
251        protected String getMessageFromParent(String code, Object[] args, Locale locale) {
252                MessageSource parent = getParentMessageSource();
253                if (parent != null) {
254                        if (parent instanceof AbstractMessageSource) {
255                                // Call internal method to avoid getting the default code back
256                                // in case of "useCodeAsDefaultMessage" being activated.
257                                return ((AbstractMessageSource) parent).getMessageInternal(code, args, locale);
258                        }
259                        else {
260                                // Check parent MessageSource, returning null if not found there.
261                                return parent.getMessage(code, args, null, locale);
262                        }
263                }
264                // Not found in parent either.
265                return null;
266        }
267
268        /**
269         * Get a default message for the given {@code MessageSourceResolvable}.
270         * <p>This implementation fully renders the default message if available,
271         * or just returns the plain default message {@code String} if the primary
272         * message code is being used as a default message.
273         * @param resolvable the value object to resolve a default message for
274         * @param locale the current locale
275         * @return the default message, or {@code null} if none
276         * @since 4.3.6
277         * @see #renderDefaultMessage(String, Object[], Locale)
278         * @see #getDefaultMessage(String)
279         */
280        protected String getDefaultMessage(MessageSourceResolvable resolvable, Locale locale) {
281                String defaultMessage = resolvable.getDefaultMessage();
282                String[] codes = resolvable.getCodes();
283                if (defaultMessage != null) {
284                        if (!ObjectUtils.isEmpty(codes) && defaultMessage.equals(codes[0])) {
285                                // Never format a code-as-default-message, even with alwaysUseMessageFormat=true
286                                return defaultMessage;
287                        }
288                        return renderDefaultMessage(defaultMessage, resolvable.getArguments(), locale);
289                }
290                return (!ObjectUtils.isEmpty(codes) ? getDefaultMessage(codes[0]) : null);
291        }
292
293        /**
294         * Return a fallback default message for the given code, if any.
295         * <p>Default is to return the code itself if "useCodeAsDefaultMessage" is activated,
296         * or return no fallback else. In case of no fallback, the caller will usually
297         * receive a {@code NoSuchMessageException} from {@code getMessage}.
298         * @param code the message code that we couldn't resolve
299         * and that we didn't receive an explicit default message for
300         * @return the default message to use, or {@code null} if none
301         * @see #setUseCodeAsDefaultMessage
302         */
303        protected String getDefaultMessage(String code) {
304                if (isUseCodeAsDefaultMessage()) {
305                        return code;
306                }
307                return null;
308        }
309
310
311        /**
312         * Searches through the given array of objects, finds any MessageSourceResolvable
313         * objects and resolves them.
314         * <p>Allows for messages to have MessageSourceResolvables as arguments.
315         * @param args array of arguments for a message
316         * @param locale the locale to resolve through
317         * @return an array of arguments with any MessageSourceResolvables resolved
318         */
319        @Override
320        protected Object[] resolveArguments(Object[] args, Locale locale) {
321                if (args == null) {
322                        return new Object[0];
323                }
324                List<Object> resolvedArgs = new ArrayList<Object>(args.length);
325                for (Object arg : args) {
326                        if (arg instanceof MessageSourceResolvable) {
327                                resolvedArgs.add(getMessage((MessageSourceResolvable) arg, locale));
328                        }
329                        else {
330                                resolvedArgs.add(arg);
331                        }
332                }
333                return resolvedArgs.toArray();
334        }
335
336        /**
337         * Subclasses can override this method to resolve a message without arguments
338         * in an optimized fashion, i.e. to resolve without involving a MessageFormat.
339         * <p>The default implementation <i>does</i> use MessageFormat, through
340         * delegating to the {@link #resolveCode} method. Subclasses are encouraged
341         * to replace this with optimized resolution.
342         * <p>Unfortunately, {@code java.text.MessageFormat} is not implemented
343         * in an efficient fashion. In particular, it does not detect that a message
344         * pattern doesn't contain argument placeholders in the first place. Therefore,
345         * it is advisable to circumvent MessageFormat for messages without arguments.
346         * @param code the code of the message to resolve
347         * @param locale the locale to resolve the code for
348         * (subclasses are encouraged to support internationalization)
349         * @return the message String, or {@code null} if not found
350         * @see #resolveCode
351         * @see java.text.MessageFormat
352         */
353        protected String resolveCodeWithoutArguments(String code, Locale locale) {
354                MessageFormat messageFormat = resolveCode(code, locale);
355                if (messageFormat != null) {
356                        synchronized (messageFormat) {
357                                return messageFormat.format(new Object[0]);
358                        }
359                }
360                return null;
361        }
362
363        /**
364         * Subclasses must implement this method to resolve a message.
365         * <p>Returns a MessageFormat instance rather than a message String,
366         * to allow for appropriate caching of MessageFormats in subclasses.
367         * <p><b>Subclasses are encouraged to provide optimized resolution
368         * for messages without arguments, not involving MessageFormat.</b>
369         * See the {@link #resolveCodeWithoutArguments} javadoc for details.
370         * @param code the code of the message to resolve
371         * @param locale the locale to resolve the code for
372         * (subclasses are encouraged to support internationalization)
373         * @return the MessageFormat for the message, or {@code null} if not found
374         * @see #resolveCodeWithoutArguments(String, java.util.Locale)
375         */
376        protected abstract MessageFormat resolveCode(String code, Locale locale);
377
378}