001 /*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * Sonar is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * Sonar is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with Sonar; if not, write to the Free Software
018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
019 */
020 package org.sonar.core.i18n;
021
022 import com.google.common.collect.Maps;
023 import org.apache.commons.io.IOUtils;
024 import org.apache.commons.lang.StringUtils;
025 import org.slf4j.Logger;
026 import org.slf4j.LoggerFactory;
027 import org.sonar.api.ServerExtension;
028 import org.sonar.api.i18n.I18n;
029 import org.sonar.api.platform.PluginMetadata;
030 import org.sonar.api.platform.PluginRepository;
031 import org.sonar.api.utils.SonarException;
032
033 import java.io.IOException;
034 import java.io.InputStream;
035 import java.text.MessageFormat;
036 import java.util.*;
037
038 public class I18nManager implements I18n, ServerExtension {
039 private static final Logger LOG = LoggerFactory.getLogger(I18nManager.class);
040
041 public static final String ENGLISH_PACK_PLUGIN_KEY = "l10nen";
042 public static final String BUNDLE_PACKAGE = "org.sonar.l10n.";
043
044 private PluginRepository pluginRepository;
045 private Map<String, ClassLoader> bundleToClassloaders;
046 private Map<String, String> propertyToBundles;
047 private ClassLoader languagePackClassLoader;
048 private Map<String, Map<Locale, String>> fileContentCache = Maps.newHashMap();
049
050 public I18nManager(PluginRepository pluginRepository) {
051 this.pluginRepository = pluginRepository;
052 }
053
054 I18nManager(Map<String, ClassLoader> bundleToClassloaders) {
055 this.bundleToClassloaders = bundleToClassloaders;
056 }
057
058 public void start() {
059 initClassloaders();
060 initProperties();
061 }
062
063 private void initClassloaders() {
064 if (bundleToClassloaders == null) {
065 languagePackClassLoader = pluginRepository.getPlugin(ENGLISH_PACK_PLUGIN_KEY).getClass().getClassLoader();
066 bundleToClassloaders = Maps.newHashMap();
067 for (PluginMetadata metadata : pluginRepository.getMetadata()) {
068 if (!metadata.isCore() && !ENGLISH_PACK_PLUGIN_KEY.equals(metadata.getBasePlugin())) {
069 // plugin but not a language pack
070 // => plugins embedd only their own bundles with all locales
071 ClassLoader classLoader = pluginRepository.getPlugin(metadata.getKey()).getClass().getClassLoader();
072 bundleToClassloaders.put(BUNDLE_PACKAGE + metadata.getKey(), classLoader);
073
074 } else if (metadata.isCore()) {
075 // bundles of core plugins are defined into language packs. All language packs are supposed
076 // to share the same classloader (english pack classloader)
077 bundleToClassloaders.put(BUNDLE_PACKAGE + metadata.getKey(), languagePackClassLoader);
078 }
079 }
080 }
081 bundleToClassloaders = Collections.unmodifiableMap(bundleToClassloaders);
082 }
083
084 private void initProperties() {
085 propertyToBundles = Maps.newHashMap();
086 for (Map.Entry<String, ClassLoader> entry : bundleToClassloaders.entrySet()) {
087 try {
088 String bundleKey = entry.getKey();
089 ResourceBundle bundle = ResourceBundle.getBundle(bundleKey, Locale.ENGLISH, entry.getValue());
090 Enumeration<String> keys = bundle.getKeys();
091 while (keys.hasMoreElements()) {
092 String key = keys.nextElement();
093 propertyToBundles.put(key, bundleKey);
094 }
095 } catch (MissingResourceException e) {
096 // ignore
097 }
098 }
099 propertyToBundles = Collections.unmodifiableMap(propertyToBundles);
100 LOG.debug(String.format("Loaded %d properties from English bundles", propertyToBundles.size()));
101 }
102
103 public String message(Locale locale, String key, String defaultValue, Object... parameters) {
104 String bundleKey = propertyToBundles.get(key);
105 ResourceBundle resourceBundle = getBundle(bundleKey, locale);
106 return message(resourceBundle, key, defaultValue, parameters);
107 }
108
109 /**
110 * Only the given locale is searched. Contrary to java.util.ResourceBundle, no strategy for locating the bundle is implemented in
111 * this method.
112 */
113 String messageFromFile(Locale locale, String filename, String relatedProperty, boolean keepInCache) {
114 Map<Locale, String> fileCache = fileContentCache.get(filename);
115 if (fileCache != null && fileCache.containsKey(locale)) {
116 return fileCache.get(locale);
117 }
118
119 ClassLoader classloader = getClassLoaderForProperty(relatedProperty);
120 String result = null;
121 if (classloader != null) {
122 String bundleBase = propertyToBundles.get(relatedProperty);
123 String filePath = bundleBase.replace('.', '/');
124 if (!"en".equals(locale.getLanguage())) {
125 filePath += "_" + locale.getLanguage();
126 }
127 filePath += "/" + filename;
128 InputStream input = classloader.getResourceAsStream(filePath);
129 if (input != null) {
130 try {
131 result = IOUtils.toString(input, "UTF-8");
132
133 } catch (IOException e) {
134 throw new SonarException("Fail to load file: " + filePath, e);
135 } finally {
136 IOUtils.closeQuietly(input);
137 }
138 }
139
140 if (keepInCache) {
141 if (fileCache == null) {
142 fileCache = Maps.newHashMap();
143 fileContentCache.put(filename, fileCache);
144 }
145 // put null value for negative caching.
146 fileCache.put(locale, result);
147 }
148 }
149 return result;
150 }
151
152 Set<String> getPropertyKeys() {
153 return propertyToBundles.keySet();
154 }
155
156 ResourceBundle getBundle(String bundleKey, Locale locale) {
157 try {
158 ClassLoader classloader = bundleToClassloaders.get(bundleKey);
159 if (classloader != null) {
160 return ResourceBundle.getBundle(bundleKey, locale, classloader);
161 }
162 } catch (MissingResourceException e) {
163 // ignore
164 }
165 return null;
166 }
167
168
169 ClassLoader getClassLoaderForProperty(String propertyKey) {
170 String bundleKey = propertyToBundles.get(propertyKey);
171 return (bundleKey != null ? bundleToClassloaders.get(bundleKey) : null);
172 }
173
174 String message(ResourceBundle resourceBundle, String key, String defaultValue, Object... parameters) {
175 String value = null;
176 if (resourceBundle != null) {
177 try {
178 value = resourceBundle.getString(key);
179 } catch (MissingResourceException e) {
180 // ignore
181 }
182 }
183 if (value == null) {
184 value = defaultValue;
185 }
186 if (value != null && parameters.length > 0) {
187 return MessageFormat.format(value, parameters);
188 }
189 return value;
190 }
191
192 String extractBundleFromKey(String key) {
193 String bundleKey = BUNDLE_PACKAGE + StringUtils.substringBefore(key, ".");
194 if (bundleToClassloaders.containsKey(bundleKey)) {
195 return bundleKey;
196 }
197 return BUNDLE_PACKAGE + "core";
198 }
199
200 ClassLoader getLanguagePackClassLoader() {
201 return languagePackClassLoader;
202 }
203
204 Map<String, Map<Locale, String>> getFileContentCache() {
205 return fileContentCache;
206 }
207 }