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