import org.sonar.plugins.core.colorizers.JavaColorizerFormat;
import org.sonar.plugins.core.duplicationsviewer.DuplicationsViewerDefinition;
import org.sonar.plugins.core.hotspots.Hotspots;
+import org.sonar.plugins.core.i18n.I18nManager;
import org.sonar.plugins.core.metrics.UserManagedMetrics;
import org.sonar.plugins.core.security.ApplyProjectRolesDecorator;
import org.sonar.plugins.core.sensors.*;
extensions.add(NewCoverageFileAnalyzer.class);
extensions.add(NewCoverageAggregator.class);
+ // i18n
+ extensions.add(I18nManager.class);
+
return extensions;
}
}
--- /dev/null
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.core.i18n;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.commons.collections.EnumerationUtils;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.BatchExtension;
+import org.sonar.api.ServerExtension;
+import org.sonar.api.i18n.I18n;
+import org.sonar.api.i18n.LanguagePack;
+import org.sonar.api.platform.PluginRepository;
+import org.sonar.api.utils.Logs;
+import org.sonar.api.utils.SonarException;
+
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.text.MessageFormat;
+import java.util.*;
+
+public final class I18nManager implements I18n, ServerExtension, BatchExtension {
+
+ private static final Logger LOG = LoggerFactory.getLogger(I18nManager.class);
+ public static final String packagePathToSearchIn = "org/sonar/i18n";
+
+ private PluginRepository pluginRepository;
+ private LanguagePack[] languagePacks;
+ private Map<String, String> keys = Maps.newHashMap();
+ private Properties unknownKeys = new Properties();
+ private BundleClassLoader bundleClassLoader = new BundleClassLoader();
+
+ public I18nManager(PluginRepository pluginRepository, LanguagePack[] languagePacks) {
+ this.pluginRepository = pluginRepository;
+ this.languagePacks = languagePacks;
+ }
+
+ public I18nManager(PluginRepository pluginRepository) {
+ this(pluginRepository, new LanguagePack[0]);
+ }
+
+
+ public void start() {
+ doStart(InstalledPlugin.create(pluginRepository));
+ }
+
+ void doStart(List<InstalledPlugin> installedPlugins) {
+ Logs.INFO.info("Loading i18n bundles");
+ Set<URI> alreadyLoadedResources = Sets.newHashSet();
+ for (InstalledPlugin plugin : installedPlugins) {
+ searchAndStoreBundleNames(plugin.key, plugin.classloader, alreadyLoadedResources);
+ }
+ for (LanguagePack pack : languagePacks) {
+ addLanguagePack(pack);
+ }
+ }
+
+ private void addLanguagePack(LanguagePack languagePack) {
+ LOG.debug("Search for bundles in language pack : {}", languagePack);
+ for (String pluginKey : languagePack.getPluginKeys()) {
+ String bundleBaseName = buildBundleBaseName(pluginKey);
+ for (Locale locale : languagePack.getLocales()) {
+ String bundlePropertiesFile = new StringBuilder(bundleBaseName).append('_').append(locale.toString()).append(".properties").toString();
+ ClassLoader classloader = languagePack.getClass().getClassLoader();
+ LOG.info("Adding locale {} for bundleName : {} from classloader : {}", new Object[]{locale, bundleBaseName, classloader});
+ bundleClassLoader.addResource(bundlePropertiesFile, classloader);
+ }
+ }
+ }
+
+ private String buildBundleBaseName(String pluginKey) {
+ return packagePathToSearchIn + "/" + pluginKey;
+ }
+
+ private void searchAndStoreBundleNames(String pluginKey, ClassLoader classloader, Set<URI> alreadyLoadedResources) {
+ String bundleBaseName = buildBundleBaseName(pluginKey);
+ String bundleDefaultPropertiesFile = bundleBaseName + ".properties";
+ try {
+ LOG.debug("Search for ResourceBundle base file '" + bundleDefaultPropertiesFile + "' in the classloader : " + classloader);
+ List<URL> resources = EnumerationUtils.toList(classloader.getResources(bundleDefaultPropertiesFile));
+ if (resources.size() > 0) {
+ if (resources.size() > 1) {
+ LOG.warn("File '{}' found several times in the classloader : {}. Only the first one will be taken in account.",
+ bundleDefaultPropertiesFile, classloader);
+ }
+
+ URL propertiesUrl = resources.get(0);
+ if (!alreadyLoadedResources.contains(propertiesUrl.toURI())) {
+ LOG.debug("Found the ResourceBundle base file : {} from classloader : {}", propertiesUrl, classloader);
+ LOG.debug("Add bundleName : {} from classloader : {}", bundleBaseName, classloader);
+ bundleClassLoader.addResource(bundleDefaultPropertiesFile, classloader);
+ alreadyLoadedResources.add(propertiesUrl.toURI());
+
+ Properties bundleContent = new Properties();
+ InputStream input = null;
+ try {
+ input = propertiesUrl.openStream();
+ bundleContent.load(input);
+ Enumeration<String> keysToAdd = (Enumeration<String>) bundleContent.propertyNames();
+ while (keysToAdd.hasMoreElements()) {
+ String key = keysToAdd.nextElement();
+ if (keys.containsKey(key)) {
+ LOG.warn("DUPLICATE KEY : Key '{}' defined in bundle '{}' is already defined in bundle '{}'. It is ignored.", new Object[]{
+ key, bundleBaseName, keys.get(key)});
+ } else {
+ keys.put(key, bundleBaseName);
+ }
+ }
+ } finally {
+ IOUtils.closeQuietly(input);
+ }
+ }
+ }
+ } catch (Exception e) {
+ LOG.error("Fail to load '" + bundleDefaultPropertiesFile + "' in classloader : " + classloader, e);
+ throw new SonarException("Fail to load '" + bundleDefaultPropertiesFile + "' in classloader : " + classloader, e);
+ }
+ }
+
+ public String message(final Locale locale, final String key, final String defaultText, final Object... objects) {
+ String result = defaultText;
+ try {
+ String bundleBaseName = keys.get(key);
+ if (bundleBaseName == null) {
+ LOG.warn("UNKNOWN KEY : Key '{}' not found in any bundle. Default value '{}' is returned.", key, defaultText);
+ unknownKeys.put(key, defaultText);
+ } else {
+ try {
+ ResourceBundle bundle = ResourceBundle.getBundle(bundleBaseName, locale, bundleClassLoader);
+
+ String value = bundle.getString(key);
+ if ("".equals(value)) {
+ LOG.warn("VOID KEY : Key '{}' (from bundle '{}') returns a void value. Default value '{}' is returned.", new Object[]{key,
+ bundleBaseName, defaultText});
+ } else {
+ result = value;
+ }
+ } catch (MissingResourceException e) {
+ LOG.warn("BUNDLE NOT LOADED : Failed loading bundle {} from classloader {}. Default value '{}' is returned.", new Object[]{
+ bundleBaseName, bundleClassLoader, defaultText});
+ }
+ }
+ } catch (Exception e) {
+ LOG.error("Exception when retrieving I18n string.", e);
+ }
+
+ if (objects.length > 0) {
+ LOG.debug("Translation : {}, {}, {}, {}", new String[]{locale.toString(), key, defaultText, Arrays.deepToString(objects)});
+ return MessageFormat.format(result, objects);
+ } else {
+ return result;
+ }
+ }
+
+ /**
+ * @return the unknownKeys
+ */
+ public Properties getUnknownKeys() {
+ return unknownKeys;
+ }
+
+
+ private static class BundleClassLoader extends URLClassLoader {
+ private Map<String, ClassLoader> resources = Maps.newHashMap();
+
+ public BundleClassLoader() {
+ super(new URL[]{}, null);
+ }
+
+ public void addResource(String resourceName, ClassLoader classloader) {
+ resources.put(resourceName, classloader);
+ }
+
+ @Override
+ public URL findResource(String name) {
+ if (resources.containsKey(name)) {
+ return resources.get(name).getResource(name);
+ }
+ return null;
+ }
+ }
+}
--- /dev/null
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.core.i18n;
+
+import com.google.common.collect.Lists;
+import org.sonar.api.Plugin;
+import org.sonar.api.platform.PluginMetadata;
+import org.sonar.api.platform.PluginRepository;
+
+import java.util.List;
+
+class InstalledPlugin {
+ String key;
+ ClassLoader classloader;
+
+ InstalledPlugin(String key, ClassLoader classloader) {
+ this.key = key;
+ this.classloader = classloader;
+ }
+
+ static List<InstalledPlugin> create(PluginRepository pluginRepository) {
+ List<InstalledPlugin> result = Lists.newArrayList();
+ for (PluginMetadata metadata : pluginRepository.getMetadata()) {
+ Plugin entryPoint = pluginRepository.getPlugin(metadata.getKey());
+ result.add(new InstalledPlugin(metadata.getKey(), entryPoint.getClass().getClassLoader()));
+ }
+ return result;
+ }
+}
--- /dev/null
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.core.i18n;
+
+import org.sonar.api.i18n.LanguagePack;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+public class FrenchLanguagePack extends LanguagePack {
+
+ @Override
+ public List<String> getPluginKeys() {
+ return Arrays.asList("test");
+ }
+
+ @Override
+ public List<Locale> getLocales() {
+ return Arrays.asList(Locale.FRENCH);
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.core.i18n;
+
+import com.google.common.collect.Lists;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.i18n.LanguagePack;
+import org.sonar.api.platform.PluginRepository;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+public class I18nManagerTest {
+
+ public static String TEST_PLUGIN_CLASS_NAME = "org.sonar.plugins.core.i18n.StandardPlugin";
+ public static String FRENCH_PACK_CLASS_NAME = "org.sonar.plugins.core.i18n.FrenchLanguagePack";
+ public static String QUEBEC_PACK_CLASS_NAME = "org.sonar.plugins.core.i18n.QuebecLanguagePack";
+
+ private static URL classSource = I18nManagerTest.class.getProtectionDomain().getCodeSource().getLocation();
+ private I18nManager manager;
+
+ @Before
+ public void createManager() throws Exception {
+ List<InstalledPlugin> plugins = Lists.newArrayList(
+ new InstalledPlugin("test", new TestClassLoader(getClass().getClassLoader().getResource("StandardPlugin.jar"))),
+ new InstalledPlugin("fake1", getClass().getClassLoader()),
+ new InstalledPlugin("fake2", getClass().getClassLoader())
+ );
+
+ TestClassLoader frenchPackClassLoader = new TestClassLoader(getClass().getClassLoader().getResource("FrenchPlugin.jar"));
+ LanguagePack frenchPack = (LanguagePack) frenchPackClassLoader.loadClass(FRENCH_PACK_CLASS_NAME).newInstance();
+
+ TestClassLoader quebecPackClassLoader = new TestClassLoader(getClass().getClassLoader().getResource("QuebecPlugin.jar"));
+ LanguagePack quebecPack = (LanguagePack) quebecPackClassLoader.loadClass(QUEBEC_PACK_CLASS_NAME).newInstance();
+
+ manager = new I18nManager(mock(PluginRepository.class), new LanguagePack[]{frenchPack, quebecPack});
+ manager.doStart(plugins);
+ }
+
+ @Test
+ public void shouldTranslateWithoutRegionalVariant() {
+ List<String> sentence = Arrays.asList("it", "is", "cold");
+ String result = "";
+ for (String token : sentence) {
+ result += manager.message(Locale.FRENCH, token, token) + " ";
+ }
+ assertEquals("Il fait froid ", result);
+ }
+
+ @Test
+ public void shouldTranslateWithRegionalVariant() {
+ // it & is are taken from the french language pack
+ // and cold is taken from the quebec language pack
+ List<String> sentence = Arrays.asList("it", "is", "cold");
+ String result = "";
+ for (String token : sentence) {
+ result += manager.message(Locale.CANADA_FRENCH, token, token) + " ";
+ }
+ assertEquals("Il fait frette ", result);
+ }
+
+ @Test
+ public void shouldTranslateReturnsDefaultBundleValue() {
+ String result = manager.message(Locale.FRENCH, "only.english", "Default");
+ assertEquals("Ketchup", result);
+ }
+
+ @Test
+ public void shouldTranslateUnknownValue() {
+ String result = manager.message(Locale.FRENCH, "unknown", "Default value for Unknown");
+ assertEquals("Default value for Unknown", result);
+ Assert.assertEquals(1, manager.getUnknownKeys().size());
+ Assert.assertEquals("Default value for Unknown", manager.getUnknownKeys().getProperty("unknown"));
+ }
+
+ public static class TestClassLoader extends URLClassLoader {
+ public TestClassLoader(URL url) {
+ super(new URL[]{url, classSource}, Thread.currentThread().getContextClassLoader());
+ }
+
+ protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ Class c = findLoadedClass(name);
+ if (c == null) {
+ if (name.equals(TEST_PLUGIN_CLASS_NAME) || name.equals(QUEBEC_PACK_CLASS_NAME) || name.equals(FRENCH_PACK_CLASS_NAME)) {
+ c = findClass(name);
+ } else {
+ return super.loadClass(name, resolve);
+ }
+ }
+ if (resolve) {
+ resolveClass(c);
+ }
+ return c;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.core.i18n;
+
+import org.sonar.api.i18n.LanguagePack;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+public class QuebecLanguagePack extends LanguagePack {
+
+ @Override
+ public List<String> getPluginKeys() {
+ return Arrays.asList("test");
+ }
+
+ @Override
+ public List<Locale> getLocales() {
+ return Arrays.asList(Locale.CANADA_FRENCH);
+ }
+}
\ No newline at end of file
--- /dev/null
+Manifest-Version: 1.0\r
+Created-By: 1.6.0_17 (Apple Inc.)\r
+\r
--- /dev/null
+it=Il
+is=fait
+cold=froid
+
--- /dev/null
+Manifest-Version: 1.0\r
+Created-By: 1.6.0_17 (Apple Inc.)\r
+\r
--- /dev/null
+cold=frette
+
--- /dev/null
+Manifest-Version: 1.0\r
+Created-By: 1.6.0_17 (Apple Inc.)\r
+\r
--- /dev/null
+it=It
+is=is
+cold=cold
+only.english=Ketchup
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<configuration>
+ <appender name="STDOUT"
+ class="ch.qos.logback.core.ConsoleAppender">
+ <layout class="ch.qos.logback.classic.PatternLayout">
+ <pattern>
+ %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n
+ </pattern>
+ </layout>
+ </appender>
+
+ <root>
+ <level value="INFO"/>
+ <appender-ref ref="STDOUT"/>
+ </root>
+</configuration>
\ No newline at end of file
+++ /dev/null
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2011 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * Sonar is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- */
-package org.sonar.plugins.i18n;
-
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import org.apache.commons.collections.EnumerationUtils;
-import org.apache.commons.io.IOUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.BatchExtension;
-import org.sonar.api.ServerExtension;
-import org.sonar.api.i18n.I18n;
-import org.sonar.api.i18n.LanguagePack;
-import org.sonar.api.platform.PluginRepository;
-import org.sonar.api.utils.Logs;
-import org.sonar.api.utils.SonarException;
-
-import java.io.InputStream;
-import java.net.URI;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.text.MessageFormat;
-import java.util.*;
-
-public final class I18nManager implements I18n, ServerExtension, BatchExtension {
-
- private static final Logger LOG = LoggerFactory.getLogger(I18nManager.class);
- public static final String packagePathToSearchIn = "org/sonar/i18n";
-
- private PluginRepository pluginRepository;
- private LanguagePack[] languagePacks;
- private Map<String, String> keys = Maps.newHashMap();
- private Properties unknownKeys = new Properties();
- private BundleClassLoader bundleClassLoader = new BundleClassLoader();
-
- public I18nManager(PluginRepository pluginRepository, LanguagePack[] languagePacks) {
- this.pluginRepository = pluginRepository;
- this.languagePacks = languagePacks;
- }
-
- public I18nManager(PluginRepository pluginRepository) {
- this(pluginRepository, new LanguagePack[0]);
- }
-
-
- public void start() {
- doStart(InstalledPlugin.create(pluginRepository));
- }
-
- void doStart(List<InstalledPlugin> installedPlugins) {
- Logs.INFO.info("Loading i18n bundles");
- Set<URI> alreadyLoadedResources = Sets.newHashSet();
- for (InstalledPlugin plugin : installedPlugins) {
- searchAndStoreBundleNames(plugin.key, plugin.classloader, alreadyLoadedResources);
- }
- for (LanguagePack pack : languagePacks) {
- addLanguagePack(pack);
- }
- }
-
- private void addLanguagePack(LanguagePack languagePack) {
- LOG.debug("Search for bundles in language pack : {}", languagePack);
- for (String pluginKey : languagePack.getPluginKeys()) {
- String bundleBaseName = buildBundleBaseName(pluginKey);
- for (Locale locale : languagePack.getLocales()) {
- String bundlePropertiesFile = new StringBuilder(bundleBaseName).append('_').append(locale.toString()).append(".properties").toString();
- ClassLoader classloader = languagePack.getClass().getClassLoader();
- LOG.info("Adding locale {} for bundleName : {} from classloader : {}", new Object[]{locale, bundleBaseName, classloader});
- bundleClassLoader.addResource(bundlePropertiesFile, classloader);
- }
- }
- }
-
- private String buildBundleBaseName(String pluginKey) {
- return packagePathToSearchIn + "/" + pluginKey;
- }
-
- private void searchAndStoreBundleNames(String pluginKey, ClassLoader classloader, Set<URI> alreadyLoadedResources) {
- String bundleBaseName = buildBundleBaseName(pluginKey);
- String bundleDefaultPropertiesFile = bundleBaseName + ".properties";
- try {
- LOG.debug("Search for ResourceBundle base file '" + bundleDefaultPropertiesFile + "' in the classloader : " + classloader);
- List<URL> resources = EnumerationUtils.toList(classloader.getResources(bundleDefaultPropertiesFile));
- if (resources.size() > 0) {
- if (resources.size() > 1) {
- LOG.warn("File '{}' found several times in the classloader : {}. Only the first one will be taken in account.",
- bundleDefaultPropertiesFile, classloader);
- }
-
- URL propertiesUrl = resources.get(0);
- if (!alreadyLoadedResources.contains(propertiesUrl.toURI())) {
- LOG.debug("Found the ResourceBundle base file : {} from classloader : {}", propertiesUrl, classloader);
- LOG.info("Add bundleName : {} from classloader : {}", bundleBaseName, classloader);
- bundleClassLoader.addResource(bundleDefaultPropertiesFile, classloader);
- alreadyLoadedResources.add(propertiesUrl.toURI());
-
- Properties bundleContent = new Properties();
- InputStream input = null;
- try {
- input = propertiesUrl.openStream();
- bundleContent.load(input);
- Enumeration<String> keysToAdd = (Enumeration<String>) bundleContent.propertyNames();
- while (keysToAdd.hasMoreElements()) {
- String key = keysToAdd.nextElement();
- if (keys.containsKey(key)) {
- LOG.warn("DUPLICATE KEY : Key '{}' defined in bundle '{}' is already defined in bundle '{}'. It is ignored.", new Object[]{
- key, bundleBaseName, keys.get(key)});
- } else {
- keys.put(key, bundleBaseName);
- }
- }
- } finally {
- IOUtils.closeQuietly(input);
- }
- }
- }
- } catch (Exception e) {
- LOG.error("Fail to load '" + bundleDefaultPropertiesFile + "' in classloader : " + classloader, e);
- throw new SonarException("Fail to load '" + bundleDefaultPropertiesFile + "' in classloader : " + classloader, e);
- }
- }
-
- public String message(final Locale locale, final String key, final String defaultText, final Object... objects) {
- String result = defaultText;
- try {
- String bundleBaseName = keys.get(key);
- if (bundleBaseName == null) {
- LOG.warn("UNKNOWN KEY : Key '{}' not found in any bundle. Default value '{}' is returned.", key, defaultText);
- unknownKeys.put(key, defaultText);
- } else {
- try {
- ResourceBundle bundle = ResourceBundle.getBundle(bundleBaseName, locale, bundleClassLoader);
-
- String value = bundle.getString(key);
- if ("".equals(value)) {
- LOG.warn("VOID KEY : Key '{}' (from bundle '{}') returns a void value. Default value '{}' is returned.", new Object[]{key,
- bundleBaseName, defaultText});
- } else {
- result = value;
- }
- } catch (MissingResourceException e) {
- LOG.warn("BUNDLE NOT LOADED : Failed loading bundle {} from classloader {}. Default value '{}' is returned.", new Object[]{
- bundleBaseName, bundleClassLoader, defaultText});
- }
- }
- } catch (Exception e) {
- LOG.error("Exception when retrieving I18n string.", e);
- }
-
- if (objects.length > 0) {
- LOG.debug("Translation : {}, {}, {}, {}", new String[]{locale.toString(), key, defaultText, Arrays.deepToString(objects)});
- return MessageFormat.format(result, objects);
- } else {
- return result;
- }
- }
-
- /**
- * @return the unknownKeys
- */
- public Properties getUnknownKeys() {
- return unknownKeys;
- }
-
-
- private static class BundleClassLoader extends URLClassLoader {
- private Map<String, ClassLoader> resources = Maps.newHashMap();
-
- public BundleClassLoader() {
- super(new URL[]{}, null);
- }
-
- public void addResource(String resourceName, ClassLoader classloader) {
- resources.put(resourceName, classloader);
- }
-
- @Override
- public URL findResource(String name) {
- if (resources.containsKey(name)) {
- return resources.get(name).getResource(name);
- }
- return null;
- }
- }
-}
+++ /dev/null
-/*\r
- * Sonar, open source software quality management tool.\r
- * Copyright (C) 2008-2011 SonarSource\r
- * mailto:contact AT sonarsource DOT com\r
- *\r
- * Sonar is free software; you can redistribute it and/or\r
- * modify it under the terms of the GNU Lesser General Public\r
- * License as published by the Free Software Foundation; either\r
- * version 3 of the License, or (at your option) any later version.\r
- *\r
- * Sonar is distributed in the hope that it will be useful,\r
- * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
- * Lesser General Public License for more details.\r
- *\r
- * You should have received a copy of the GNU Lesser General Public\r
- * License along with Sonar; if not, write to the Free Software\r
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02\r
- */\r
-package org.sonar.plugins.i18n;\r
-\r
-import org.sonar.api.SonarPlugin;\r
-\r
-import java.util.Arrays;\r
-import java.util.List;\r
-\r
-/**\r
- * @since 2.9\r
- */\r
-public final class I18nPlugin extends SonarPlugin {\r
-\r
- public List getExtensions() {\r
- return Arrays.asList(I18nManager.class, I18nWebService.class);\r
- }\r
-\r
-}\r
+++ /dev/null
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2011 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * Sonar is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- */
-package org.sonar.plugins.i18n;
-
-import com.google.common.collect.Lists;
-import org.sonar.api.Plugin;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
-
-import java.util.List;
-
-class InstalledPlugin {
- String key;
- ClassLoader classloader;
-
- InstalledPlugin(String key, ClassLoader classloader) {
- this.key = key;
- this.classloader = classloader;
- }
-
- static List<InstalledPlugin> create(PluginRepository pluginRepository) {
- List<InstalledPlugin> result = Lists.newArrayList();
- for (PluginMetadata metadata : pluginRepository.getMetadata()) {
- Plugin entryPoint = pluginRepository.getPlugin(metadata.getKey());
- result.add(new InstalledPlugin(metadata.getKey(), entryPoint.getClass().getClassLoader()));
- }
- return result;
- }
-}
+++ /dev/null
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2011 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * Sonar is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- */
-package org.sonar.plugins.i18n;
-
-import com.google.common.collect.Lists;
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.i18n.LanguagePack;
-import org.sonar.api.platform.PluginRepository;
-
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
-
-public class I18nManagerTest {
-
- public static String TEST_PLUGIN_CLASS_NAME = "org.sonar.plugins.i18n.utils.StandardPlugin";
- public static String FRENCH_PACK_CLASS_NAME = "org.sonar.plugins.i18n.utils.FrenchLanguagePack";
- public static String QUEBEC_PACK_CLASS_NAME = "org.sonar.plugins.i18n.utils.QuebecLanguagePack";
-
- private static URL classSource = I18nManagerTest.class.getProtectionDomain().getCodeSource().getLocation();
- private I18nManager manager;
-
- @Before
- public void createManager() throws Exception {
- List<InstalledPlugin> plugins = Lists.newArrayList(
- new InstalledPlugin("test", new TestClassLoader(getClass().getClassLoader().getResource("StandardPlugin.jar"))),
- new InstalledPlugin("fake1", getClass().getClassLoader()),
- new InstalledPlugin("fake2", getClass().getClassLoader())
- );
-
- TestClassLoader frenchPackClassLoader = new TestClassLoader(getClass().getClassLoader().getResource("FrenchPlugin.jar"));
- LanguagePack frenchPack = (LanguagePack) frenchPackClassLoader.loadClass(FRENCH_PACK_CLASS_NAME).newInstance();
-
- TestClassLoader quebecPackClassLoader = new TestClassLoader(getClass().getClassLoader().getResource("QuebecPlugin.jar"));
- LanguagePack quebecPack = (LanguagePack) quebecPackClassLoader.loadClass(QUEBEC_PACK_CLASS_NAME).newInstance();
-
- manager = new I18nManager(mock(PluginRepository.class), new LanguagePack[]{frenchPack, quebecPack});
- manager.doStart(plugins);
- }
-
- @Test
- public void shouldTranslateWithoutRegionalVariant() {
- List<String> sentence = Arrays.asList("it", "is", "cold");
- String result = "";
- for (String token : sentence) {
- result += manager.message(Locale.FRENCH, token, token) + " ";
- }
- assertEquals("Il fait froid ", result);
- }
-
- @Test
- public void shouldTranslateWithRegionalVariant() {
- // it & is are taken from the french language pack
- // and cold is taken from the quebec language pack
- List<String> sentence = Arrays.asList("it", "is", "cold");
- String result = "";
- for (String token : sentence) {
- result += manager.message(Locale.CANADA_FRENCH, token, token) + " ";
- }
- assertEquals("Il fait frette ", result);
- }
-
- @Test
- public void shouldTranslateReturnsDefaultBundleValue() {
- String result = manager.message(Locale.FRENCH, "only.english", "Default");
- assertEquals("Ketchup", result);
- }
-
- @Test
- public void shouldTranslateUnknownValue() {
- String result = manager.message(Locale.FRENCH, "unknown", "Default value for Unknown");
- assertEquals("Default value for Unknown", result);
- assertEquals(1, manager.getUnknownKeys().size());
- assertEquals("Default value for Unknown", manager.getUnknownKeys().getProperty("unknown"));
- }
-
- public static class TestClassLoader extends URLClassLoader {
- public TestClassLoader(URL url) {
- super(new URL[]{url, classSource}, Thread.currentThread().getContextClassLoader());
- }
-
- protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
- Class c = findLoadedClass(name);
- if (c == null) {
- if (name.equals(TEST_PLUGIN_CLASS_NAME) || name.equals(QUEBEC_PACK_CLASS_NAME) || name.equals(FRENCH_PACK_CLASS_NAME)) {
- c = findClass(name);
- } else {
- return super.loadClass(name, resolve);
- }
- }
- if (resolve) {
- resolveClass(c);
- }
- return c;
- }
- }
-
-}
+++ /dev/null
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2011 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * Sonar is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- */
-package org.sonar.plugins.i18n;
-
-import org.junit.Test;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.number.OrderingComparisons.greaterThanOrEqualTo;
-
-public class I18nPluginTest {
-
- @Test
- public void shouldGetExtensions() {
- assertThat(new I18nPlugin().getExtensions().size(), greaterThanOrEqualTo(2));
- }
-}
+++ /dev/null
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2011 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * Sonar is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- */
-package org.sonar.plugins.i18n.utils;
-
-import org.sonar.api.i18n.LanguagePack;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-
-public class FrenchLanguagePack extends LanguagePack {
-
- @Override
- public List<String> getPluginKeys() {
- return Arrays.asList("test");
- }
-
- @Override
- public List<Locale> getLocales() {
- return Arrays.asList(Locale.FRENCH);
- }
-}
\ No newline at end of file
+++ /dev/null
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2011 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * Sonar is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- */
-package org.sonar.plugins.i18n.utils;
-
-import org.sonar.api.i18n.LanguagePack;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-
-public class QuebecLanguagePack extends LanguagePack {
-
- @Override
- public List<String> getPluginKeys() {
- return Arrays.asList("test");
- }
-
- @Override
- public List<Locale> getLocales() {
- return Arrays.asList(Locale.CANADA_FRENCH);
- }
-}
\ No newline at end of file
+++ /dev/null
-Manifest-Version: 1.0\r
-Created-By: 1.6.0_17 (Apple Inc.)\r
-\r
+++ /dev/null
-it=Il
-is=fait
-cold=froid
-
+++ /dev/null
-Manifest-Version: 1.0\r
-Created-By: 1.6.0_17 (Apple Inc.)\r
-\r
+++ /dev/null
-cold=frette
-
+++ /dev/null
-Manifest-Version: 1.0\r
-Created-By: 1.6.0_17 (Apple Inc.)\r
-\r
+++ /dev/null
-it=It
-is=is
-cold=cold
-only.english=Ketchup
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" ?>
-
-<configuration>
- <appender name="STDOUT"
- class="ch.qos.logback.core.ConsoleAppender">
- <layout class="ch.qos.logback.classic.PatternLayout">
- <pattern>
- %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n
- </pattern>
- </layout>
- </appender>
-
- <root>
- <level value="INFO"/>
- <appender-ref ref="STDOUT"/>
- </root>
-</configuration>
\ No newline at end of file
<module>plugins/sonar-cpd-plugin</module>
<module>plugins/sonar-squid-java-plugin</module>
<module>plugins/sonar-design-plugin</module>
- <module>plugins/sonar-i18n-plugin</module>
</modules>
<organization>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
- <dependency>
- <groupId>org.codehaus.sonar.plugins</groupId>
- <artifactId>sonar-i18n-plugin</artifactId>
- <version>${project.version}</version>
- <scope>runtime</scope>
- </dependency>
<dependency>
<groupId>org.sonatype.jsw-binaries</groupId>
<artifactId>jsw-binaries</artifactId>
<level value="INFO"/>
</logger>
- <logger name="org.sonar.plugins.i18n.I18nManager">
+ <logger name="org.sonar.plugins.core.i18n.I18nManager">
<level value="INFO"/>
</logger>
public class BatchPluginRepository implements PluginRepository {
private static final Logger LOG = LoggerFactory.getLogger(BatchPluginRepository.class);
+ private static final String CORE_PLUGIN = "core";
private ArtifactDownloader artifactDownloader;
private Map<String, Plugin> pluginsByKey;
public BatchPluginRepository(ArtifactDownloader artifactDownloader, Configuration configuration) {
this.artifactDownloader = artifactDownloader;
- if (configuration.getString(CoreProperties.INCLUDE_PLUGINS) != null) {
- whiteList = Sets.newTreeSet(Arrays.asList(configuration.getStringArray(CoreProperties.INCLUDE_PLUGINS)));
+ if (configuration.getString(CoreProperties.BATCH_INCLUDE_PLUGINS) != null) {
+ whiteList = Sets.newTreeSet(Arrays.asList(configuration.getStringArray(CoreProperties.BATCH_INCLUDE_PLUGINS)));
LOG.info("Include plugins: " + Joiner.on(", ").join(whiteList));
}
- if (configuration.getString(CoreProperties.EXCLUDE_PLUGINS) != null) {
- blackList = Sets.newTreeSet(Arrays.asList(configuration.getStringArray(CoreProperties.EXCLUDE_PLUGINS)));
+ if (configuration.getString(CoreProperties.BATCH_EXCLUDE_PLUGINS) != null) {
+ blackList = Sets.newTreeSet(Arrays.asList(configuration.getStringArray(CoreProperties.BATCH_EXCLUDE_PLUGINS)));
LOG.info("Exclude plugins: " + Joiner.on(", ").join(blackList));
}
// TODO reactivate somewhere else: LOG.info("Execution environment: {} {}", environment.getKey(), environment.getVersion());
}
boolean isAccepted(String pluginKey) {
+ if (CORE_PLUGIN.equals(pluginKey)) {
+ return true;
+ }
if (whiteList != null) {
return whiteList.contains(pluginKey);
}
when(downloader.downloadPlugin(checkstyleExt)).thenReturn(copyFiles("sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar"));
PropertiesConfiguration conf = new PropertiesConfiguration();
- conf.setProperty(CoreProperties.EXCLUDE_PLUGINS, "checkstyle");
+ conf.setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle");
repository = new BatchPluginRepository(downloader, conf);
repository.doStart(Arrays.asList(checkstyle, checkstyleExt));
@Test
public void whiteListShouldTakePrecedenceOverBlackList() {
PropertiesConfiguration conf = new PropertiesConfiguration();
- conf.setProperty(CoreProperties.INCLUDE_PLUGINS, "checkstyle,pmd,findbugs");
- conf.setProperty(CoreProperties.EXCLUDE_PLUGINS, "cobertura,pmd");
+ conf.setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs");
+ conf.setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "cobertura,pmd");
repository = new BatchPluginRepository(mock(ArtifactDownloader.class), conf);
assertThat(repository.isAccepted("pmd"), Matchers.is(true));
}
+ @Test
+ public void corePluginShouldAlwaysBeInWhiteList() {
+ PropertiesConfiguration conf = new PropertiesConfiguration();
+ conf.setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs");
+ repository = new BatchPluginRepository(mock(ArtifactDownloader.class), conf);
+ assertThat(repository.isAccepted("core"), Matchers.is(true));
+ }
+
+ @Test
+ public void corePluginShouldNeverBeInBlackList() {
+ PropertiesConfiguration conf = new PropertiesConfiguration();
+ conf.setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "core,findbugs");
+ repository = new BatchPluginRepository(mock(ArtifactDownloader.class), conf);
+ assertThat(repository.isAccepted("core"), Matchers.is(true));
+ }
+
@Test
public void shouldCheckWhitelist() {
PropertiesConfiguration conf = new PropertiesConfiguration();
- conf.setProperty(CoreProperties.INCLUDE_PLUGINS, "checkstyle,pmd,findbugs");
+ conf.setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs");
repository = new BatchPluginRepository(mock(ArtifactDownloader.class), conf);
assertThat(repository.isAccepted("checkstyle"), Matchers.is(true));
@Test
public void shouldCheckBlackListIfNoWhiteList() {
PropertiesConfiguration conf = new PropertiesConfiguration();
- conf.setProperty(CoreProperties.EXCLUDE_PLUGINS, "checkstyle,pmd,findbugs");
+ conf.setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle,pmd,findbugs");
repository = new BatchPluginRepository(mock(ArtifactDownloader.class), conf);
assertThat(repository.isAccepted("checkstyle"), Matchers.is(false));
String SERVER_STARTTIME = "sonar.core.startTime"; // format is yyyy-MM-dd'T'HH:mm:ssZ
String SKIP_TENDENCIES_PROPERTY = "sonar.skipTendencies";
boolean SKIP_TENDENCIES_DEFAULT_VALUE = false;
- String INCLUDE_PLUGINS = "sonar.includePlugins";
- String EXCLUDE_PLUGINS = "sonar.excludePlugins";
+ String BATCH_INCLUDE_PLUGINS = "sonar.includePlugins";
+ String BATCH_EXCLUDE_PLUGINS = "sonar.excludePlugins";
/* CPD */
String CPD_PLUGIN = "cpd";
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
- <dependency>
- <groupId>org.codehaus.sonar.plugins</groupId>
- <artifactId>sonar-i18n-plugin</artifactId>
- <version>${project.version}</version>
- <scope>provided</scope>
- </dependency>
</dependencies>
</profile>
--- /dev/null
+#
+# Sonar, entreprise quality control tool.
+# Copyright (C) 2008-2011 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# Sonar is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 3 of the License, or (at your option) any later version.
+#
+# Sonar is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Sonar; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+#
+
+require "json"
+
+class I18nController < Api::ApiController
+
+ def index
+ render :text => "Use one of the following:<br><ul>" +
+ "<li>/i18n/unknown_keys?format=text|json</li>" +
+ "</ul>"
+ end
+
+ #
+ # GET /i18n/unknown_keys
+ #
+ def unknown_keys
+ begin
+ output = ""
+ properties = i18n_manager.unknown_keys
+
+ properties.keys.sort.each {|key| output += "#{key}=#{properties[key]}\n" }
+
+ output = "# No unknown keys" if output.empty?
+
+ respond_to do |format|
+ format.json { render :json => JSON(properties) }
+ format.xml { render :xml => xml_not_supported }
+ format.text { render :text => output }
+ end
+
+ rescue ApiException => e
+ render_error(e.msg, e.code)
+
+ rescue Exception => e
+ logger.error("Fails to execute #{request.url} : #{e.message}")
+ render_error(e.message)
+ end
+ end
+
+ private
+
+ def i18n_manager
+ java_facade.getComponentByClassname('i18n', 'org.sonar.plugins.core.i18n.I18nManager')
+ end
+
+end
\ No newline at end of file