001/* 002 * Copyright 2002-2020 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.util.LinkedHashSet; 020import java.util.Locale; 021import java.util.Set; 022 023import org.springframework.lang.Nullable; 024import org.springframework.util.Assert; 025import org.springframework.util.ObjectUtils; 026 027/** 028 * Abstract base class for {@code MessageSource} implementations based on 029 * resource bundle conventions, such as {@link ResourceBundleMessageSource} 030 * and {@link ReloadableResourceBundleMessageSource}. Provides common 031 * configuration methods and corresponding semantic definitions. 032 * 033 * @author Juergen Hoeller 034 * @since 4.3 035 * @see ResourceBundleMessageSource 036 * @see ReloadableResourceBundleMessageSource 037 */ 038public abstract class AbstractResourceBasedMessageSource extends AbstractMessageSource { 039 040 private final Set<String> basenameSet = new LinkedHashSet<>(4); 041 042 @Nullable 043 private String defaultEncoding; 044 045 private boolean fallbackToSystemLocale = true; 046 047 @Nullable 048 private Locale defaultLocale; 049 050 private long cacheMillis = -1; 051 052 053 /** 054 * Set a single basename, following the basic ResourceBundle convention 055 * of not specifying file extension or language codes. The resource location 056 * format is up to the specific {@code MessageSource} implementation. 057 * <p>Regular and XMl properties files are supported: e.g. "messages" will find 058 * a "messages.properties", "messages_en.properties" etc arrangement as well 059 * as "messages.xml", "messages_en.xml" etc. 060 * @param basename the single basename 061 * @see #setBasenames 062 * @see org.springframework.core.io.ResourceEditor 063 * @see java.util.ResourceBundle 064 */ 065 public void setBasename(String basename) { 066 setBasenames(basename); 067 } 068 069 /** 070 * Set an array of basenames, each following the basic ResourceBundle convention 071 * of not specifying file extension or language codes. The resource location 072 * format is up to the specific {@code MessageSource} implementation. 073 * <p>Regular and XMl properties files are supported: e.g. "messages" will find 074 * a "messages.properties", "messages_en.properties" etc arrangement as well 075 * as "messages.xml", "messages_en.xml" etc. 076 * <p>The associated resource bundles will be checked sequentially when resolving 077 * a message code. Note that message definitions in a <i>previous</i> resource 078 * bundle will override ones in a later bundle, due to the sequential lookup. 079 * <p>Note: In contrast to {@link #addBasenames}, this replaces existing entries 080 * with the given names and can therefore also be used to reset the configuration. 081 * @param basenames an array of basenames 082 * @see #setBasename 083 * @see java.util.ResourceBundle 084 */ 085 public void setBasenames(String... basenames) { 086 this.basenameSet.clear(); 087 addBasenames(basenames); 088 } 089 090 /** 091 * Add the specified basenames to the existing basename configuration. 092 * <p>Note: If a given basename already exists, the position of its entry 093 * will remain as in the original set. New entries will be added at the 094 * end of the list, to be searched after existing basenames. 095 * @since 4.3 096 * @see #setBasenames 097 * @see java.util.ResourceBundle 098 */ 099 public void addBasenames(String... basenames) { 100 if (!ObjectUtils.isEmpty(basenames)) { 101 for (String basename : basenames) { 102 Assert.hasText(basename, "Basename must not be empty"); 103 this.basenameSet.add(basename.trim()); 104 } 105 } 106 } 107 108 /** 109 * Return this {@code MessageSource}'s basename set, containing entries 110 * in the order of registration. 111 * <p>Calling code may introspect this set as well as add or remove entries. 112 * @since 4.3 113 * @see #addBasenames 114 */ 115 public Set<String> getBasenameSet() { 116 return this.basenameSet; 117 } 118 119 /** 120 * Set the default charset to use for parsing properties files. 121 * Used if no file-specific charset is specified for a file. 122 * <p>The effective default is the {@code java.util.Properties} 123 * default encoding: ISO-8859-1. A {@code null} value indicates 124 * the platform default encoding. 125 * <p>Only applies to classic properties files, not to XML files. 126 * @param defaultEncoding the default charset 127 */ 128 public void setDefaultEncoding(@Nullable String defaultEncoding) { 129 this.defaultEncoding = defaultEncoding; 130 } 131 132 /** 133 * Return the default charset to use for parsing properties files, if any. 134 * @since 4.3 135 */ 136 @Nullable 137 protected String getDefaultEncoding() { 138 return this.defaultEncoding; 139 } 140 141 /** 142 * Set whether to fall back to the system Locale if no files for a specific 143 * Locale have been found. Default is "true"; if this is turned off, the only 144 * fallback will be the default file (e.g. "messages.properties" for 145 * basename "messages"). 146 * <p>Falling back to the system Locale is the default behavior of 147 * {@code java.util.ResourceBundle}. However, this is often not desirable 148 * in an application server environment, where the system Locale is not relevant 149 * to the application at all: set this flag to "false" in such a scenario. 150 * @see #setDefaultLocale 151 */ 152 public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) { 153 this.fallbackToSystemLocale = fallbackToSystemLocale; 154 } 155 156 /** 157 * Return whether to fall back to the system Locale if no files for a specific 158 * Locale have been found. 159 * @since 4.3 160 * @deprecated as of 5.2.2, in favor of {@link #getDefaultLocale()} 161 */ 162 @Deprecated 163 protected boolean isFallbackToSystemLocale() { 164 return this.fallbackToSystemLocale; 165 } 166 167 /** 168 * Specify a default Locale to fall back to, as an alternative to falling back 169 * to the system Locale. 170 * <p>Default is to fall back to the system Locale. You may override this with 171 * a locally specified default Locale here, or enforce no fallback locale at all 172 * through disabling {@link #setFallbackToSystemLocale "fallbackToSystemLocale"}. 173 * @since 5.2.2 174 * @see #setFallbackToSystemLocale 175 * @see #getDefaultLocale() 176 */ 177 public void setDefaultLocale(@Nullable Locale defaultLocale) { 178 this.defaultLocale = defaultLocale; 179 } 180 181 /** 182 * Determine a default Locale to fall back to: either a locally specified default 183 * Locale or the system Locale, or {@code null} for no fallback locale at all. 184 * @since 5.2.2 185 * @see #setDefaultLocale 186 * @see #setFallbackToSystemLocale 187 * @see Locale#getDefault() 188 */ 189 @Nullable 190 protected Locale getDefaultLocale() { 191 if (this.defaultLocale != null) { 192 return this.defaultLocale; 193 } 194 if (this.fallbackToSystemLocale) { 195 return Locale.getDefault(); 196 } 197 return null; 198 } 199 200 /** 201 * Set the number of seconds to cache loaded properties files. 202 * <ul> 203 * <li>Default is "-1", indicating to cache forever (matching the default behavior 204 * of {@code java.util.ResourceBundle}). Note that this constant follows Spring 205 * conventions, not {@link java.util.ResourceBundle.Control#getTimeToLive}. 206 * <li>A positive number will cache loaded properties files for the given 207 * number of seconds. This is essentially the interval between refresh checks. 208 * Note that a refresh attempt will first check the last-modified timestamp 209 * of the file before actually reloading it; so if files don't change, this 210 * interval can be set rather low, as refresh attempts will not actually reload. 211 * <li>A value of "0" will check the last-modified timestamp of the file on 212 * every message access. <b>Do not use this in a production environment!</b> 213 * </ul> 214 * <p><b>Note that depending on your ClassLoader, expiration might not work reliably 215 * since the ClassLoader may hold on to a cached version of the bundle file.</b> 216 * Prefer {@link ReloadableResourceBundleMessageSource} over 217 * {@link ResourceBundleMessageSource} in such a scenario, in combination with 218 * a non-classpath location. 219 */ 220 public void setCacheSeconds(int cacheSeconds) { 221 this.cacheMillis = cacheSeconds * 1000L; 222 } 223 224 /** 225 * Set the number of milliseconds to cache loaded properties files. 226 * Note that it is common to set seconds instead: {@link #setCacheSeconds}. 227 * <ul> 228 * <li>Default is "-1", indicating to cache forever (matching the default behavior 229 * of {@code java.util.ResourceBundle}). Note that this constant follows Spring 230 * conventions, not {@link java.util.ResourceBundle.Control#getTimeToLive}. 231 * <li>A positive number will cache loaded properties files for the given 232 * number of milliseconds. This is essentially the interval between refresh checks. 233 * Note that a refresh attempt will first check the last-modified timestamp 234 * of the file before actually reloading it; so if files don't change, this 235 * interval can be set rather low, as refresh attempts will not actually reload. 236 * <li>A value of "0" will check the last-modified timestamp of the file on 237 * every message access. <b>Do not use this in a production environment!</b> 238 * </ul> 239 * @since 4.3 240 * @see #setCacheSeconds 241 */ 242 public void setCacheMillis(long cacheMillis) { 243 this.cacheMillis = cacheMillis; 244 } 245 246 /** 247 * Return the number of milliseconds to cache loaded properties files. 248 * @since 4.3 249 */ 250 protected long getCacheMillis() { 251 return this.cacheMillis; 252 } 253 254}