001/* 002 * Copyright 2002-2015 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.ui.context.support; 018 019import java.util.Map; 020import java.util.concurrent.ConcurrentHashMap; 021 022import org.apache.commons.logging.Log; 023import org.apache.commons.logging.LogFactory; 024 025import org.springframework.beans.factory.BeanClassLoaderAware; 026import org.springframework.context.HierarchicalMessageSource; 027import org.springframework.context.MessageSource; 028import org.springframework.context.support.ResourceBundleMessageSource; 029import org.springframework.ui.context.HierarchicalThemeSource; 030import org.springframework.ui.context.Theme; 031import org.springframework.ui.context.ThemeSource; 032 033/** 034 * {@link ThemeSource} implementation that looks up an individual 035 * {@link java.util.ResourceBundle} per theme. The theme name gets 036 * interpreted as ResourceBundle basename, supporting a common 037 * basename prefix for all themes. 038 * 039 * @author Jean-Pierre Pawlak 040 * @author Juergen Hoeller 041 * @see #setBasenamePrefix 042 * @see java.util.ResourceBundle 043 * @see org.springframework.context.support.ResourceBundleMessageSource 044 */ 045public class ResourceBundleThemeSource implements HierarchicalThemeSource, BeanClassLoaderAware { 046 047 protected final Log logger = LogFactory.getLog(getClass()); 048 049 private ThemeSource parentThemeSource; 050 051 private String basenamePrefix = ""; 052 053 private String defaultEncoding; 054 055 private Boolean fallbackToSystemLocale; 056 057 private ClassLoader beanClassLoader; 058 059 /** Map from theme name to Theme instance */ 060 private final Map<String, Theme> themeCache = new ConcurrentHashMap<String, Theme>(); 061 062 063 @Override 064 public void setParentThemeSource(ThemeSource parent) { 065 this.parentThemeSource = parent; 066 067 // Update existing Theme objects. 068 // Usually there shouldn't be any at the time of this call. 069 synchronized (this.themeCache) { 070 for (Theme theme : this.themeCache.values()) { 071 initParent(theme); 072 } 073 } 074 } 075 076 @Override 077 public ThemeSource getParentThemeSource() { 078 return this.parentThemeSource; 079 } 080 081 /** 082 * Set the prefix that gets applied to the ResourceBundle basenames, 083 * i.e. the theme names. 084 * E.g.: basenamePrefix="test.", themeName="theme" -> basename="test.theme". 085 * <p>Note that ResourceBundle names are effectively classpath locations: As a 086 * consequence, the JDK's standard ResourceBundle treats dots as package separators. 087 * This means that "test.theme" is effectively equivalent to "test/theme", 088 * just like it is for programmatic {@code java.util.ResourceBundle} usage. 089 * @see java.util.ResourceBundle#getBundle(String) 090 */ 091 public void setBasenamePrefix(String basenamePrefix) { 092 this.basenamePrefix = (basenamePrefix != null ? basenamePrefix : ""); 093 } 094 095 /** 096 * Set the default charset to use for parsing resource bundle files. 097 * <p>{@link ResourceBundleMessageSource}'s default is the 098 * {@code java.util.ResourceBundle} default encoding: ISO-8859-1. 099 * @since 4.2 100 * @see ResourceBundleMessageSource#setDefaultEncoding 101 */ 102 public void setDefaultEncoding(String defaultEncoding) { 103 this.defaultEncoding = defaultEncoding; 104 } 105 106 /** 107 * Set whether to fall back to the system Locale if no files for a 108 * specific Locale have been found. 109 * <p>{@link ResourceBundleMessageSource}'s default is "true". 110 * @since 4.2 111 * @see ResourceBundleMessageSource#setFallbackToSystemLocale 112 */ 113 public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) { 114 this.fallbackToSystemLocale = fallbackToSystemLocale; 115 } 116 117 @Override 118 public void setBeanClassLoader(ClassLoader beanClassLoader) { 119 this.beanClassLoader = beanClassLoader; 120 } 121 122 123 /** 124 * This implementation returns a SimpleTheme instance, holding a 125 * ResourceBundle-based MessageSource whose basename corresponds to 126 * the given theme name (prefixed by the configured "basenamePrefix"). 127 * <p>SimpleTheme instances are cached per theme name. Use a reloadable 128 * MessageSource if themes should reflect changes to the underlying files. 129 * @see #setBasenamePrefix 130 * @see #createMessageSource 131 */ 132 @Override 133 public Theme getTheme(String themeName) { 134 if (themeName == null) { 135 return null; 136 } 137 Theme theme = this.themeCache.get(themeName); 138 if (theme == null) { 139 synchronized (this.themeCache) { 140 theme = this.themeCache.get(themeName); 141 if (theme == null) { 142 String basename = this.basenamePrefix + themeName; 143 MessageSource messageSource = createMessageSource(basename); 144 theme = new SimpleTheme(themeName, messageSource); 145 initParent(theme); 146 this.themeCache.put(themeName, theme); 147 if (logger.isDebugEnabled()) { 148 logger.debug("Theme created: name '" + themeName + "', basename [" + basename + "]"); 149 } 150 } 151 } 152 } 153 return theme; 154 } 155 156 /** 157 * Create a MessageSource for the given basename, 158 * to be used as MessageSource for the corresponding theme. 159 * <p>Default implementation creates a ResourceBundleMessageSource. 160 * for the given basename. A subclass could create a specifically 161 * configured ReloadableResourceBundleMessageSource, for example. 162 * @param basename the basename to create a MessageSource for 163 * @return the MessageSource 164 * @see org.springframework.context.support.ResourceBundleMessageSource 165 * @see org.springframework.context.support.ReloadableResourceBundleMessageSource 166 */ 167 protected MessageSource createMessageSource(String basename) { 168 ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); 169 messageSource.setBasename(basename); 170 if (this.defaultEncoding != null) { 171 messageSource.setDefaultEncoding(this.defaultEncoding); 172 } 173 if (this.fallbackToSystemLocale != null) { 174 messageSource.setFallbackToSystemLocale(this.fallbackToSystemLocale); 175 } 176 if (this.beanClassLoader != null) { 177 messageSource.setBeanClassLoader(this.beanClassLoader); 178 } 179 return messageSource; 180 } 181 182 /** 183 * Initialize the MessageSource of the given theme with the 184 * one from the corresponding parent of this ThemeSource. 185 * @param theme the Theme to (re-)initialize 186 */ 187 protected void initParent(Theme theme) { 188 if (theme.getMessageSource() instanceof HierarchicalMessageSource) { 189 HierarchicalMessageSource messageSource = (HierarchicalMessageSource) theme.getMessageSource(); 190 if (getParentThemeSource() != null && messageSource.getParentMessageSource() == null) { 191 Theme parentTheme = getParentThemeSource().getTheme(theme.getName()); 192 if (parentTheme != null) { 193 messageSource.setParentMessageSource(parentTheme.getMessageSource()); 194 } 195 } 196 } 197 } 198 199}