001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2011 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    }