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}