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