Save This Page
Home » Spring-Framework-090522 » org.springframework » context » support » [javadoc | source]
    1   /*
    2    * Copyright 2002-2008 the original author or authors.
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *      http://www.apache.org/licenses/LICENSE-2.0
    9    *
   10    * Unless required by applicable law or agreed to in writing, software
   11    * distributed under the License is distributed on an "AS IS" BASIS,
   12    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   13    * See the License for the specific language governing permissions and
   14    * limitations under the License.
   15    */
   16   
   17   package org.springframework.context.support;
   18   
   19   import java.io.IOException;
   20   import java.io.InputStream;
   21   import java.io.InputStreamReader;
   22   import java.text.MessageFormat;
   23   import java.util.ArrayList;
   24   import java.util.HashMap;
   25   import java.util.Iterator;
   26   import java.util.List;
   27   import java.util.Locale;
   28   import java.util.Map;
   29   import java.util.Properties;
   30   
   31   import org.springframework.context.ResourceLoaderAware;
   32   import org.springframework.core.io.DefaultResourceLoader;
   33   import org.springframework.core.io.Resource;
   34   import org.springframework.core.io.ResourceLoader;
   35   import org.springframework.util.Assert;
   36   import org.springframework.util.DefaultPropertiesPersister;
   37   import org.springframework.util.PropertiesPersister;
   38   import org.springframework.util.StringUtils;
   39   
   40   /**
   41    * {@link org.springframework.context.MessageSource} implementation that
   42    * accesses resource bundles using specified basenames. This class uses
   43    * {@link java.util.Properties} instances as its custom data structure for
   44    * messages, loading them via a {@link org.springframework.util.PropertiesPersister}
   45    * strategy: The default strategy is capable of loading properties files
   46    * with a specific character encoding, if desired.
   47    *
   48    * <p>In contrast to {@link ResourceBundleMessageSource}, this class supports
   49    * reloading of properties files through the {@link #setCacheSeconds "cacheSeconds"}
   50    * setting, and also through programmatically clearing the properties cache.
   51    * Since application servers typically cache all files loaded from the classpath,
   52    * it is necessary to store resources somewhere else (for example, in the
   53    * "WEB-INF" directory of a web app). Otherwise changes of files in the
   54    * classpath will <i>not</i> be reflected in the application.
   55    *
   56    * <p>Note that the base names set as {@link #setBasenames "basenames"} property
   57    * are treated in a slightly different fashion than the "basenames" property of
   58    * {@link ResourceBundleMessageSource}. It follows the basic ResourceBundle rule of not
   59    * specifying file extension or language codes, but can refer to any Spring resource
   60    * location (instead of being restricted to classpath resources). With a "classpath:"
   61    * prefix, resources can still be loaded from the classpath, but "cacheSeconds" values
   62    * other than "-1" (caching forever) will not work in this case.
   63    *
   64    * <p>This MessageSource implementation is usually slightly faster than
   65    * {@link ResourceBundleMessageSource}, which builds on {@link java.util.ResourceBundle}
   66    * - in the default mode, i.e. when caching forever. With "cacheSeconds" set to 1,
   67    * message lookup takes about twice as long - with the benefit that changes in
   68    * individual properties files are detected with a maximum delay of 1 second.
   69    * Higher "cacheSeconds" values usually <i>do not</i> make a significant difference.
   70    *
   71    * <p>This MessageSource can easily be used outside of an
   72    * {@link org.springframework.context.ApplicationContext}: It will use a
   73    * {@link org.springframework.core.io.DefaultResourceLoader} as default,
   74    * simply getting overridden with the ApplicationContext's resource loader
   75    * if running in a context. It does not have any other specific dependencies.
   76    *
   77    * <p>Thanks to Thomas Achleitner for providing the initial implementation of
   78    * this message source!
   79    *
   80    * @author Juergen Hoeller
   81    * @see #setCacheSeconds
   82    * @see #setBasenames
   83    * @see #setDefaultEncoding
   84    * @see #setFileEncodings
   85    * @see #setPropertiesPersister
   86    * @see #setResourceLoader
   87    * @see org.springframework.util.DefaultPropertiesPersister
   88    * @see org.springframework.core.io.DefaultResourceLoader
   89    * @see ResourceBundleMessageSource
   90    * @see java.util.ResourceBundle
   91    */
   92   public class ReloadableResourceBundleMessageSource extends AbstractMessageSource
   93       implements ResourceLoaderAware {
   94   
   95   	private static final String PROPERTIES_SUFFIX = ".properties";
   96   
   97   	private static final String XML_SUFFIX = ".xml";
   98   
   99   
  100   	private String[] basenames = new String[0];
  101   
  102   	private String defaultEncoding;
  103   
  104   	private Properties fileEncodings;
  105   
  106   	private boolean fallbackToSystemLocale = true;
  107   
  108   	private long cacheMillis = -1;
  109   
  110   	private PropertiesPersister propertiesPersister = new DefaultPropertiesPersister();
  111   
  112   	private ResourceLoader resourceLoader = new DefaultResourceLoader();
  113   
  114   	/** Cache to hold filename lists per Locale */
  115   	private final Map cachedFilenames = new HashMap();
  116   
  117   	/** Cache to hold already loaded properties per filename */
  118   	private final Map cachedProperties = new HashMap();
  119   
  120   	/** Cache to hold merged loaded properties per basename */
  121   	private final Map cachedMergedProperties = new HashMap();
  122   
  123   
  124   	/**
  125   	 * Set a single basename, following the basic ResourceBundle convention of
  126   	 * not specifying file extension or language codes, but in contrast to
  127   	 * {@link ResourceBundleMessageSource} referring to a Spring resource location:
  128   	 * e.g. "WEB-INF/messages" for "WEB-INF/messages.properties",
  129   	 * "WEB-INF/messages_en.properties", etc.
  130   	 * <p>As of Spring 1.2.2, XML properties files are also supported:
  131   	 * e.g. "WEB-INF/messages" will find and load "WEB-INF/messages.xml",
  132   	 * "WEB-INF/messages_en.xml", etc as well. Note that this will only
  133   	 * work on JDK 1.5+.
  134   	 * @param basename the single basename
  135   	 * @see #setBasenames
  136   	 * @see org.springframework.core.io.ResourceEditor
  137   	 * @see java.util.ResourceBundle
  138   	 */
  139   	public void setBasename(String basename) {
  140   		setBasenames(new String[] {basename});
  141   	}
  142   
  143   	/**
  144   	 * Set an array of basenames, each following the basic ResourceBundle convention
  145   	 * of not specifying file extension or language codes, but in contrast to
  146   	 * {@link ResourceBundleMessageSource} referring to a Spring resource location:
  147   	 * e.g. "WEB-INF/messages" for "WEB-INF/messages.properties",
  148   	 * "WEB-INF/messages_en.properties", etc.
  149   	 * <p>As of Spring 1.2.2, XML properties files are also supported:
  150   	 * e.g. "WEB-INF/messages" will find and load "WEB-INF/messages.xml",
  151   	 * "WEB-INF/messages_en.xml", etc as well. Note that this will only
  152   	 * work on JDK 1.5+.
  153   	 * <p>The associated resource bundles will be checked sequentially
  154   	 * when resolving a message code. Note that message definitions in a
  155   	 * <i>previous</i> resource bundle will override ones in a later bundle,
  156   	 * due to the sequential lookup.
  157   	 * @param basenames an array of basenames
  158   	 * @see #setBasename
  159   	 * @see java.util.ResourceBundle
  160   	 */
  161   	public void setBasenames(String[] basenames) {
  162   		if (basenames != null) {
  163   			this.basenames = new String[basenames.length];
  164   			for (int i = 0; i < basenames.length; i++) {
  165   				String basename = basenames[i];
  166   				Assert.hasText(basename, "Basename must not be empty");
  167   				this.basenames[i] = basename.trim();
  168   			}
  169   		}
  170   		else {
  171   			this.basenames = new String[0];
  172   		}
  173   	}
  174   
  175   	/**
  176   	 * Set the default charset to use for parsing properties files.
  177   	 * Used if no file-specific charset is specified for a file.
  178   	 * <p>Default is none, using the <code>java.util.Properties</code>
  179   	 * default encoding.
  180   	 * <p>Only applies to classic properties files, not to XML files.
  181   	 * @param defaultEncoding the default charset
  182   	 * @see #setFileEncodings
  183   	 * @see org.springframework.util.PropertiesPersister#load
  184   	 */
  185   	public void setDefaultEncoding(String defaultEncoding) {
  186   		this.defaultEncoding = defaultEncoding;
  187   	}
  188   
  189   	/**
  190   	 * Set per-file charsets to use for parsing properties files.
  191   	 * <p>Only applies to classic properties files, not to XML files.
  192   	 * @param fileEncodings Properties with filenames as keys and charset
  193   	 * names as values. Filenames have to match the basename syntax,
  194   	 * with optional locale-specific appendices: e.g. "WEB-INF/messages"
  195   	 * or "WEB-INF/messages_en".
  196   	 * @see #setBasenames
  197   	 * @see org.springframework.util.PropertiesPersister#load
  198   	 */
  199   	public void setFileEncodings(Properties fileEncodings) {
  200   		this.fileEncodings = fileEncodings;
  201   	}
  202   
  203   	/**
  204   	 * Set whether to fall back to the system Locale if no files for a specific
  205   	 * Locale have been found. Default is "true"; if this is turned off, the only
  206   	 * fallback will be the default file (e.g. "messages.properties" for
  207   	 * basename "messages").
  208   	 * <p>Falling back to the system Locale is the default behavior of
  209   	 * <code>java.util.ResourceBundle</code>. However, this is often not
  210   	 * desirable in an application server environment, where the system Locale
  211   	 * is not relevant to the application at all: Set this flag to "false"
  212   	 * in such a scenario.
  213   	 */
  214   	public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) {
  215   		this.fallbackToSystemLocale = fallbackToSystemLocale;
  216   	}
  217   
  218   	/**
  219   	 * Set the number of seconds to cache loaded properties files.
  220   	 * <ul>
  221   	 * <li>Default is "-1", indicating to cache forever (just like
  222   	 * <code>java.util.ResourceBundle</code>).
  223   	 * <li>A positive number will cache loaded properties files for the given
  224   	 * number of seconds. This is essentially the interval between refresh checks.
  225   	 * Note that a refresh attempt will first check the last-modified timestamp
  226   	 * of the file before actually reloading it; so if files don't change, this
  227   	 * interval can be set rather low, as refresh attempts will not actually reload.
  228   	 * <li>A value of "0" will check the last-modified timestamp of the file on
  229   	 * every message access. <b>Do not use this in a production environment!</b>
  230   	 * </ul>
  231   	 */
  232   	public void setCacheSeconds(int cacheSeconds) {
  233   		this.cacheMillis = (cacheSeconds * 1000);
  234   	}
  235   
  236   	/**
  237   	 * Set the PropertiesPersister to use for parsing properties files.
  238   	 * <p>The default is a DefaultPropertiesPersister.
  239   	 * @see org.springframework.util.DefaultPropertiesPersister
  240   	 */
  241   	public void setPropertiesPersister(PropertiesPersister propertiesPersister) {
  242   		this.propertiesPersister =
  243   				(propertiesPersister != null ? propertiesPersister : new DefaultPropertiesPersister());
  244   	}
  245   
  246   	/**
  247   	 * Set the ResourceLoader to use for loading bundle properties files.
  248   	 * <p>The default is a DefaultResourceLoader. Will get overridden by the
  249   	 * ApplicationContext if running in a context, as it implements the
  250   	 * ResourceLoaderAware interface. Can be manually overridden when
  251   	 * running outside of an ApplicationContext.
  252   	 * @see org.springframework.core.io.DefaultResourceLoader
  253   	 * @see org.springframework.context.ResourceLoaderAware
  254   	 */
  255   	public void setResourceLoader(ResourceLoader resourceLoader) {
  256   		this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader());
  257   	}
  258   
  259   
  260   	/**
  261   	 * Resolves the given message code as key in the retrieved bundle files,
  262   	 * returning the value found in the bundle as-is (without MessageFormat parsing).
  263   	 */
  264   	protected String resolveCodeWithoutArguments(String code, Locale locale) {
  265   		if (this.cacheMillis < 0) {
  266   			PropertiesHolder propHolder = getMergedProperties(locale);
  267   			String result = propHolder.getProperty(code);
  268   			if (result != null) {
  269   				return result;
  270   			}
  271   		}
  272   		else {
  273   			for (int i = 0; i < this.basenames.length; i++) {
  274   				List filenames = calculateAllFilenames(this.basenames[i], locale);
  275   				for (int j = 0; j < filenames.size(); j++) {
  276   					String filename = (String) filenames.get(j);
  277   					PropertiesHolder propHolder = getProperties(filename);
  278   					String result = propHolder.getProperty(code);
  279   					if (result != null) {
  280   						return result;
  281   					}
  282   				}
  283   			}
  284   		}
  285   		return null;
  286   	}
  287   
  288   	/**
  289   	 * Resolves the given message code as key in the retrieved bundle files,
  290   	 * using a cached MessageFormat instance per message code.
  291   	 */
  292   	protected MessageFormat resolveCode(String code, Locale locale) {
  293   		if (this.cacheMillis < 0) {
  294   			PropertiesHolder propHolder = getMergedProperties(locale);
  295   			MessageFormat result = propHolder.getMessageFormat(code, locale);
  296   			if (result != null) {
  297   				return result;
  298   			}
  299   		}
  300   		else {
  301   			for (int i = 0; i < this.basenames.length; i++) {
  302   				List filenames = calculateAllFilenames(this.basenames[i], locale);
  303   				for (int j = 0; j < filenames.size(); j++) {
  304   					String filename = (String) filenames.get(j);
  305   					PropertiesHolder propHolder = getProperties(filename);
  306   					MessageFormat result = propHolder.getMessageFormat(code, locale);
  307   					if (result != null) {
  308   						return result;
  309   					}
  310   				}
  311   			}
  312   		}
  313   		return null;
  314   	}
  315   
  316   
  317   	/**
  318   	 * Get a PropertiesHolder that contains the actually visible properties
  319   	 * for a Locale, after merging all specified resource bundles.
  320   	 * Either fetches the holder from the cache or freshly loads it.
  321   	 * <p>Only used when caching resource bundle contents forever, i.e.
  322   	 * with cacheSeconds < 0. Therefore, merged properties are always
  323   	 * cached forever.
  324   	 */
  325   	protected PropertiesHolder getMergedProperties(Locale locale) {
  326   		synchronized (this.cachedMergedProperties) {
  327   			PropertiesHolder mergedHolder = (PropertiesHolder) this.cachedMergedProperties.get(locale);
  328   			if (mergedHolder != null) {
  329   				return mergedHolder;
  330   			}
  331   			Properties mergedProps = new Properties();
  332   			mergedHolder = new PropertiesHolder(mergedProps, -1);
  333   			for (int i = this.basenames.length - 1; i >= 0; i--) {
  334   				List filenames = calculateAllFilenames(this.basenames[i], locale);
  335   				for (int j = filenames.size() - 1; j >= 0; j--) {
  336   					String filename = (String) filenames.get(j);
  337   					PropertiesHolder propHolder = getProperties(filename);
  338   					if (propHolder.getProperties() != null) {
  339   						mergedProps.putAll(propHolder.getProperties());
  340   					}
  341   				}
  342   			}
  343   			this.cachedMergedProperties.put(locale, mergedHolder);
  344   			return mergedHolder;
  345   		}
  346   	}
  347   
  348   	/**
  349   	 * Calculate all filenames for the given bundle basename and Locale.
  350   	 * Will calculate filenames for the given Locale, the system Locale
  351   	 * (if applicable), and the default file.
  352   	 * @param basename the basename of the bundle
  353   	 * @param locale the locale
  354   	 * @return the List of filenames to check
  355   	 * @see #setFallbackToSystemLocale
  356   	 * @see #calculateFilenamesForLocale
  357   	 */
  358   	protected List calculateAllFilenames(String basename, Locale locale) {
  359   		synchronized (this.cachedFilenames) {
  360   			Map localeMap = (Map) this.cachedFilenames.get(basename);
  361   			if (localeMap != null) {
  362   				List filenames = (List) localeMap.get(locale);
  363   				if (filenames != null) {
  364   					return filenames;
  365   				}
  366   			}
  367   			List filenames = new ArrayList(7);
  368   			filenames.addAll(calculateFilenamesForLocale(basename, locale));
  369   			if (this.fallbackToSystemLocale && !locale.equals(Locale.getDefault())) {
  370   				List fallbackFilenames = calculateFilenamesForLocale(basename, Locale.getDefault());
  371   				for (Iterator it = fallbackFilenames.iterator(); it.hasNext();) {
  372   					String fallbackFilename = (String) it.next();
  373   					if (!filenames.contains(fallbackFilename)) {
  374   						// Entry for fallback locale that isn't already in filenames list.
  375   						filenames.add(fallbackFilename);
  376   					}
  377   				}
  378   			}
  379   			filenames.add(basename);
  380   			if (localeMap != null) {
  381   				localeMap.put(locale, filenames);
  382   			}
  383   			else {
  384   				localeMap = new HashMap();
  385   				localeMap.put(locale, filenames);
  386   				this.cachedFilenames.put(basename, localeMap);
  387   			}
  388   			return filenames;
  389   		}
  390   	}
  391   
  392   	/**
  393   	 * Calculate the filenames for the given bundle basename and Locale,
  394   	 * appending language code, country code, and variant code.
  395   	 * E.g.: basename "messages", Locale "de_AT_oo" -> "messages_de_AT_OO",
  396   	 * "messages_de_AT", "messages_de".
  397   	 * @param basename the basename of the bundle
  398   	 * @param locale the locale
  399   	 * @return the List of filenames to check
  400   	 */
  401   	protected List calculateFilenamesForLocale(String basename, Locale locale) {
  402   		List result = new ArrayList(3);
  403   		String language = locale.getLanguage();
  404   		String country = locale.getCountry();
  405   		String variant = locale.getVariant();
  406   		StringBuffer temp = new StringBuffer(basename);
  407   
  408   		if (language.length() > 0) {
  409   			temp.append('_').append(language);
  410   			result.add(0, temp.toString());
  411   		}
  412   
  413   		if (country.length() > 0) {
  414   			temp.append('_').append(country);
  415   			result.add(0, temp.toString());
  416   		}
  417   
  418   		if (variant.length() > 0) {
  419   			temp.append('_').append(variant);
  420   			result.add(0, temp.toString());
  421   		}
  422   
  423   		return result;
  424   	}
  425   
  426   
  427   	/**
  428   	 * Get a PropertiesHolder for the given filename, either from the
  429   	 * cache or freshly loaded.
  430   	 * @param filename the bundle filename (basename + Locale)
  431   	 * @return the current PropertiesHolder for the bundle
  432   	 */
  433   	protected PropertiesHolder getProperties(String filename) {
  434   		synchronized (this.cachedProperties) {
  435   			PropertiesHolder propHolder = (PropertiesHolder) this.cachedProperties.get(filename);
  436   			if (propHolder != null &&
  437   					(propHolder.getRefreshTimestamp() < 0 ||
  438   					 propHolder.getRefreshTimestamp() > System.currentTimeMillis() - this.cacheMillis)) {
  439   				// up to date
  440   				return propHolder;
  441   			}
  442   			return refreshProperties(filename, propHolder);
  443   		}
  444   	}
  445   
  446   	/**
  447   	 * Refresh the PropertiesHolder for the given bundle filename.
  448   	 * The holder can be <code>null</code> if not cached before, or a timed-out cache entry
  449   	 * (potentially getting re-validated against the current last-modified timestamp).
  450   	 * @param filename the bundle filename (basename + Locale)
  451   	 * @param propHolder the current PropertiesHolder for the bundle
  452   	 */
  453   	protected PropertiesHolder refreshProperties(String filename, PropertiesHolder propHolder) {
  454   		long refreshTimestamp = (this.cacheMillis < 0) ? -1 : System.currentTimeMillis();
  455   
  456   		Resource resource = this.resourceLoader.getResource(filename + PROPERTIES_SUFFIX);
  457   		if (!resource.exists()) {
  458   			resource = this.resourceLoader.getResource(filename + XML_SUFFIX);
  459   		}
  460   
  461   		if (resource.exists()) {
  462   			long fileTimestamp = -1;
  463   			if (this.cacheMillis >= 0) {
  464   				// Last-modified timestamp of file will just be read if caching with timeout.
  465   				try {
  466   					fileTimestamp = resource.lastModified();
  467   					if (propHolder != null && propHolder.getFileTimestamp() == fileTimestamp) {
  468   						if (logger.isDebugEnabled()) {
  469   							logger.debug("Re-caching properties for filename [" + filename + "] - file hasn't been modified");
  470   						}
  471   						propHolder.setRefreshTimestamp(refreshTimestamp);
  472   						return propHolder;
  473   					}
  474   				}
  475   				catch (IOException ex) {
  476   					// Probably a class path resource: cache it forever.
  477   					if (logger.isDebugEnabled()) {
  478   						logger.debug(
  479   								resource + " could not be resolved in the file system - assuming that is hasn't changed", ex);
  480   					}
  481   					fileTimestamp = -1;
  482   				}
  483   			}
  484   			try {
  485   				Properties props = loadProperties(resource, filename);
  486   				propHolder = new PropertiesHolder(props, fileTimestamp);
  487   			}
  488   			catch (IOException ex) {
  489   				if (logger.isWarnEnabled()) {
  490   					logger.warn("Could not parse properties file [" + resource.getFilename() + "]", ex);
  491   				}
  492   				// Empty holder representing "not valid".
  493   				propHolder = new PropertiesHolder();
  494   			}
  495   		}
  496   
  497   		else {
  498   			// Resource does not exist.
  499   			if (logger.isDebugEnabled()) {
  500   				logger.debug("No properties file found for [" + filename + "] - neither plain properties nor XML");
  501   			}
  502   			// Empty holder representing "not found".
  503   			propHolder = new PropertiesHolder();
  504   		}
  505   
  506   		propHolder.setRefreshTimestamp(refreshTimestamp);
  507   		this.cachedProperties.put(filename, propHolder);
  508   		return propHolder;
  509   	}
  510   
  511   	/**
  512   	 * Load the properties from the given resource.
  513   	 * @param resource the resource to load from
  514   	 * @param filename the original bundle filename (basename + Locale)
  515   	 * @return the populated Properties instance
  516   	 * @throws IOException if properties loading failed
  517   	 */
  518   	protected Properties loadProperties(Resource resource, String filename) throws IOException {
  519   		InputStream is = resource.getInputStream();
  520   		Properties props = new Properties();
  521   		try {
  522   			if (resource.getFilename().endsWith(XML_SUFFIX)) {
  523   				if (logger.isDebugEnabled()) {
  524   					logger.debug("Loading properties [" + resource.getFilename() + "]");
  525   				}
  526   				this.propertiesPersister.loadFromXml(props, is);
  527   			}
  528   			else {
  529   				String encoding = null;
  530   				if (this.fileEncodings != null) {
  531   					encoding = this.fileEncodings.getProperty(filename);
  532   				}
  533   				if (encoding == null) {
  534   					encoding = this.defaultEncoding;
  535   				}
  536   				if (encoding != null) {
  537   					if (logger.isDebugEnabled()) {
  538   						logger.debug("Loading properties [" + resource.getFilename() + "] with encoding '" + encoding + "'");
  539   					}
  540   					this.propertiesPersister.load(props, new InputStreamReader(is, encoding));
  541   				}
  542   				else {
  543   					if (logger.isDebugEnabled()) {
  544   						logger.debug("Loading properties [" + resource.getFilename() + "]");
  545   					}
  546   					this.propertiesPersister.load(props, is);
  547   				}
  548   			}
  549   			return props;
  550   		}
  551   		finally {
  552   			is.close();
  553   		}
  554   	}
  555   
  556   
  557   	/**
  558   	 * Clear the resource bundle cache.
  559   	 * Subsequent resolve calls will lead to reloading of the properties files.
  560   	 */
  561   	public void clearCache() {
  562   		logger.debug("Clearing entire resource bundle cache");
  563   		synchronized (this.cachedProperties) {
  564   			this.cachedProperties.clear();
  565   		}
  566   		synchronized (this.cachedMergedProperties) {
  567   			this.cachedMergedProperties.clear();
  568   		}
  569   	}
  570   
  571   	/**
  572   	 * Clear the resource bundle caches of this MessageSource and all its ancestors.
  573   	 * @see #clearCache
  574   	 */
  575   	public void clearCacheIncludingAncestors() {
  576   		clearCache();
  577   		if (getParentMessageSource() instanceof ReloadableResourceBundleMessageSource) {
  578   			((ReloadableResourceBundleMessageSource) getParentMessageSource()).clearCacheIncludingAncestors();
  579   		}
  580   	}
  581   
  582   
  583   	public String toString() {
  584   		return getClass().getName() + ": basenames=[" + StringUtils.arrayToCommaDelimitedString(this.basenames) + "]";
  585   	}
  586   
  587   
  588   	/**
  589   	 * PropertiesHolder for caching.
  590   	 * Stores the last-modified timestamp of the source file for efficient
  591   	 * change detection, and the timestamp of the last refresh attempt
  592   	 * (updated every time the cache entry gets re-validated).
  593   	 */
  594   	protected class PropertiesHolder {
  595   
  596   		private Properties properties;
  597   
  598   		private long fileTimestamp = -1;
  599   
  600   		private long refreshTimestamp = -1;
  601   
  602   		/** Cache to hold already generated MessageFormats per message code */
  603   		private final Map cachedMessageFormats = new HashMap();
  604   
  605   		public PropertiesHolder(Properties properties, long fileTimestamp) {
  606   			this.properties = properties;
  607   			this.fileTimestamp = fileTimestamp;
  608   		}
  609   
  610   		public PropertiesHolder() {
  611   		}
  612   
  613   		public Properties getProperties() {
  614   			return properties;
  615   		}
  616   
  617   		public long getFileTimestamp() {
  618   			return fileTimestamp;
  619   		}
  620   
  621   		public void setRefreshTimestamp(long refreshTimestamp) {
  622   			this.refreshTimestamp = refreshTimestamp;
  623   		}
  624   
  625   		public long getRefreshTimestamp() {
  626   			return refreshTimestamp;
  627   		}
  628   
  629   		public String getProperty(String code) {
  630   			if (this.properties == null) {
  631   				return null;
  632   			}
  633   			return this.properties.getProperty(code);
  634   		}
  635   
  636   		public MessageFormat getMessageFormat(String code, Locale locale) {
  637   			if (this.properties == null) {
  638   				return null;
  639   			}
  640   			synchronized (this.cachedMessageFormats) {
  641   				Map localeMap = (Map) this.cachedMessageFormats.get(code);
  642   				if (localeMap != null) {
  643   					MessageFormat result = (MessageFormat) localeMap.get(locale);
  644   					if (result != null) {
  645   						return result;
  646   					}
  647   				}
  648   				String msg = this.properties.getProperty(code);
  649   				if (msg != null) {
  650   					if (localeMap == null) {
  651   						localeMap = new HashMap();
  652   						this.cachedMessageFormats.put(code, localeMap);
  653   					}
  654   					MessageFormat result = createMessageFormat(msg, locale);
  655   					localeMap.put(locale, result);
  656   					return result;
  657   				}
  658   				return null;
  659   			}
  660   		}
  661   	}
  662   
  663   }

Save This Page
Home » Spring-Framework-090522 » org.springframework » context » support » [javadoc | source]