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}