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 */ 020package org.sonar.core.i18n; 021 022import com.google.common.collect.Maps; 023import org.apache.commons.io.IOUtils; 024import org.apache.commons.lang.StringUtils; 025import org.slf4j.Logger; 026import org.slf4j.LoggerFactory; 027import org.sonar.api.ServerExtension; 028import org.sonar.api.i18n.I18n; 029import org.sonar.api.platform.PluginMetadata; 030import org.sonar.api.platform.PluginRepository; 031import org.sonar.api.utils.SonarException; 032 033import java.io.IOException; 034import java.io.InputStream; 035import java.text.MessageFormat; 036import java.util.*; 037 038public 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}