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}