diff options
17 files changed, 507 insertions, 443 deletions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/BatchPluginRepository.java b/sonar-batch/src/main/java/org/sonar/batch/BatchPluginRepository.java index 105686834e4..5544762ad7a 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/BatchPluginRepository.java +++ b/sonar-batch/src/main/java/org/sonar/batch/BatchPluginRepository.java @@ -20,12 +20,16 @@ package org.sonar.batch; import com.google.common.collect.HashMultimap; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.lang.ArrayUtils; import org.picocontainer.MutablePicoContainer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.BatchExtension; import org.sonar.api.Plugin; -import org.sonar.api.platform.PluginRepository; +import org.sonar.api.batch.AbstractCoverageExtension; +import org.sonar.api.utils.SonarException; +import org.sonar.core.plugin.AbstractPluginRepository; import org.sonar.core.plugin.JpaPlugin; import org.sonar.core.plugin.JpaPluginDao; import org.sonar.core.plugin.JpaPluginFile; @@ -36,15 +40,26 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; -public class BatchPluginRepository extends PluginRepository { +public class BatchPluginRepository extends AbstractPluginRepository { + + private static final Logger LOG = LoggerFactory.getLogger(BatchPluginRepository.class); private Map<String, ClassLoader> classloaders; private String baseUrl; private JpaPluginDao dao; + private Configuration configuration; - public BatchPluginRepository(JpaPluginDao dao, ServerMetadata server) { - this.dao= dao; + public BatchPluginRepository(JpaPluginDao dao, ServerMetadata server, Configuration configuration) { + this.dao = dao; this.baseUrl = server.getUrl() + "/deploy/plugins/"; + this.configuration = configuration; + } + + /** + * only for unit tests + */ + BatchPluginRepository(Configuration configuration) { + this.configuration = configuration; } public void start() { @@ -56,7 +71,7 @@ public class BatchPluginRepository extends PluginRepository { urlsByKey.put(key, url); } catch (MalformedURLException e) { - throw new RuntimeException("Can not build the classloader of the plugin " + pluginFile.getPluginKey(), e); + throw new SonarException("Can not build the classloader of the plugin " + pluginFile.getPluginKey(), e); } } @@ -64,11 +79,10 @@ public class BatchPluginRepository extends PluginRepository { for (String key : urlsByKey.keySet()) { Set<URL> urls = urlsByKey.get(key); - Logger logger = LoggerFactory.getLogger(getClass()); - if (logger.isDebugEnabled()) { - logger.debug("Classloader of plugin " + key + ":"); + if (LOG.isDebugEnabled()) { + LOG.debug("Classloader of plugin " + key + ":"); for (URL url : urls) { - logger.debug(" -> " + url); + LOG.debug(" -> " + url); } } classloaders.put(key, new RemoteClassLoader(urls, Thread.currentThread().getContextClassLoader()).getClassLoader()); @@ -80,16 +94,36 @@ public class BatchPluginRepository extends PluginRepository { } public void registerPlugins(MutablePicoContainer pico) { - try { - for (JpaPlugin pluginMetadata : dao.getPlugins()) { + for (JpaPlugin pluginMetadata : dao.getPlugins()) { + try { String classloaderKey = getClassloaderKey(pluginMetadata.getKey()); Class claz = classloaders.get(classloaderKey).loadClass(pluginMetadata.getPluginClass()); Plugin plugin = (Plugin) claz.newInstance(); - registerPlugin(pico, plugin, BatchExtension.class); + registerPlugin(pico, plugin, pluginMetadata.getKey()); + + } catch (Exception e) { + throw new SonarException("Fail to load extensions from plugin " + pluginMetadata.getKey(), e); } + } + } - } catch (Exception e) { - throw new RuntimeException(e); + boolean shouldRegisterCoverageExtension(String pluginKey) { + String[] selectedPluginKeys = configuration.getStringArray(AbstractCoverageExtension.PARAM_PLUGIN); + if (ArrayUtils.isEmpty(selectedPluginKeys)) { + selectedPluginKeys = new String[]{AbstractCoverageExtension.DEFAULT_PLUGIN}; + } + return ArrayUtils.contains(selectedPluginKeys, pluginKey); + } + + @Override + protected boolean shouldRegisterExtension(String pluginKey, Object extension) { + boolean ok = isType(extension, BatchExtension.class); + if (ok && isType(extension, AbstractCoverageExtension.class)) { + ok = shouldRegisterCoverageExtension(pluginKey); + if (!ok) { + LOG.debug("The following extension is ignored: " + extension + ". See the parameter " + AbstractCoverageExtension.PARAM_PLUGIN); + } } + return ok; } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/BatchPluginRepositoryTest.java b/sonar-batch/src/test/java/org/sonar/batch/BatchPluginRepositoryTest.java new file mode 100644 index 00000000000..1f7696ea5d8 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/BatchPluginRepositoryTest.java @@ -0,0 +1,76 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * 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.batch; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.junit.Test; +import org.sonar.api.BatchExtension; +import org.sonar.api.ServerExtension; +import org.sonar.api.batch.AbstractCoverageExtension; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +public class BatchPluginRepositoryTest { + + @Test + public void shouldRegisterBatchExtension() { + BatchPluginRepository repository = new BatchPluginRepository(new PropertiesConfiguration()); + + // check classes + assertThat(repository.shouldRegisterExtension("foo", FakeBatchExtension.class), is(true)); + assertThat(repository.shouldRegisterExtension("foo", FakeServerExtension.class), is(false)); + assertThat(repository.shouldRegisterExtension("foo", String.class), is(false)); + + // check objects + assertThat(repository.shouldRegisterExtension("foo", new FakeBatchExtension()), is(true)); + assertThat(repository.shouldRegisterExtension("foo", new FakeServerExtension()), is(false)); + assertThat(repository.shouldRegisterExtension("foo", "bar"), is(false)); + } + + @Test + public void shouldRegisterOnlyCoberturaExtensionByDefault() { + Configuration conf = new PropertiesConfiguration(); + BatchPluginRepository repository = new BatchPluginRepository(conf); + assertThat(repository.shouldRegisterCoverageExtension("cobertura"), is(true)); + assertThat(repository.shouldRegisterCoverageExtension("clover"), is(false)); + } + + @Test + public void shouldRegisterCustomCoverageExtension() { + Configuration conf = new PropertiesConfiguration(); + conf.setProperty(AbstractCoverageExtension.PARAM_PLUGIN, "clover,phpunit"); + BatchPluginRepository repository = new BatchPluginRepository(conf); + assertThat(repository.shouldRegisterCoverageExtension("cobertura"), is(false)); + assertThat(repository.shouldRegisterCoverageExtension("clover"), is(true)); + assertThat(repository.shouldRegisterCoverageExtension("phpunit"), is(true)); + assertThat(repository.shouldRegisterCoverageExtension("other"), is(false)); + } + + + public static class FakeBatchExtension implements BatchExtension { + + } + + public static class FakeServerExtension implements ServerExtension { + + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/plugin/AbstractPluginRepository.java b/sonar-core/src/main/java/org/sonar/core/plugin/AbstractPluginRepository.java new file mode 100644 index 00000000000..24a40d9fd68 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/plugin/AbstractPluginRepository.java @@ -0,0 +1,120 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * 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.core.plugin; + +import com.google.common.collect.Maps; +import org.picocontainer.Characteristics; +import org.picocontainer.MutablePicoContainer; +import org.picocontainer.injectors.ProviderAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.*; +import org.sonar.api.platform.PluginRepository; + +import java.util.Collection; +import java.util.Map; + +/** + * @since 2.2 + */ +public abstract class AbstractPluginRepository implements PluginRepository { + + private static final Logger LOG = LoggerFactory.getLogger(AbstractPluginRepository.class); + + private Map<String, Plugin> pluginByKey = Maps.newHashMap(); + private Map<Object, Plugin> pluginByExtension = Maps.newIdentityHashMap(); + + protected void registerPlugin(MutablePicoContainer container, Plugin plugin, String pluginKey) { + LOG.debug("Register the plugin {}", pluginKey); + pluginByKey.put(pluginKey, plugin); + for (Object extension : plugin.getExtensions()) { + registerExtension(container, plugin, pluginKey, extension); + } + } + + private void registerExtension(MutablePicoContainer container, Plugin plugin, String pluginKey, Object extension) { + if (shouldRegisterExtension(pluginKey, extension)) { + LOG.debug("Register the extension: {}", extension); + container.as(Characteristics.CACHE).addComponent(getExtensionKey(extension), extension); + pluginByExtension.put(extension, plugin); + } + if (isExtensionProvider(extension)) { + LOG.debug("Register the extension provider: {}", extension); + container.as(Characteristics.CACHE).addAdapter(new ExtensionProviderAdapter(extension)); + } + } + + protected abstract boolean shouldRegisterExtension(String pluginKey, Object extension); + + public Collection<Plugin> getPlugins() { + return pluginByKey.values(); + } + + public Plugin getPlugin(String key) { + return pluginByKey.get(key); + } + + /** + * Returns the list of properties of a plugin + */ + public Property[] getProperties(Plugin plugin) { + if (plugin != null) { + Class<? extends Plugin> classInstance = plugin.getClass(); + if (classInstance.isAnnotationPresent(Properties.class)) { + return classInstance.getAnnotation(Properties.class).value(); + } + } + return new Property[0]; + } + + public Property[] getProperties(String pluginKey) { + return getProperties(pluginByKey.get(pluginKey)); + } + + public Plugin getPluginForExtension(Object extension) { + Plugin plugin = pluginByExtension.get(extension); + if (plugin == null && !(extension instanceof Class)) { + plugin = pluginByExtension.get(extension.getClass()); + } + return plugin; + } + + protected static boolean isType(Object extension, Class<? extends Extension> extensionClass) { + Class clazz = (extension instanceof Class ? (Class) extension : extension.getClass()); + return extensionClass.isAssignableFrom(clazz); + } + + protected static boolean isExtensionProvider(Object extension) { + return extension instanceof ExtensionProvider; + } + + protected static Object getExtensionKey(Object component) { + if (component instanceof Class) { + return component; + } + return component.getClass().getCanonicalName() + "-" + component.toString(); + } + + public static class ExtensionProviderAdapter extends ProviderAdapter { + public ExtensionProviderAdapter(Object provider) { + super(provider); + } + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/plugin/AbstractPluginRepositoryTest.java b/sonar-core/src/test/java/org/sonar/core/plugin/AbstractPluginRepositoryTest.java new file mode 100644 index 00000000000..319150c5c95 --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/plugin/AbstractPluginRepositoryTest.java @@ -0,0 +1,152 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * 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.core.plugin; + +import org.junit.Test; +import org.picocontainer.MutablePicoContainer; +import org.sonar.api.BatchExtension; +import org.sonar.api.ExtensionProvider; +import org.sonar.api.Plugin; +import org.sonar.api.ServerExtension; +import org.sonar.api.utils.IocContainer; + +import java.util.Arrays; + +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class AbstractPluginRepositoryTest { + + @Test + public void testIsType() { + assertThat(AbstractPluginRepository.isType(FakeServerExtension.class, ServerExtension.class), is(true)); + assertThat(AbstractPluginRepository.isType(new FakeServerExtension(), ServerExtension.class), is(true)); + + assertThat(AbstractPluginRepository.isType(FakeBatchExtension.class, ServerExtension.class), is(false)); + assertThat(AbstractPluginRepository.isType(new FakeBatchExtension(), ServerExtension.class), is(false)); + assertThat(AbstractPluginRepository.isType(String.class, ServerExtension.class), is(false)); + assertThat(AbstractPluginRepository.isType("foo", ServerExtension.class), is(false)); + } + + @Test + public void extensionKeyshouldBeClassNameIfClass() { + assertEquals(AbstractPluginRepository.getExtensionKey(FakeServerExtension.class), FakeServerExtension.class); + } + + @Test + public void extensionKeyshouldBeUniqueIfObject() { + assertThat((String) AbstractPluginRepository.getExtensionKey(new FakeServerExtension()), endsWith("FakeServerExtension-instance")); + } + + @Test + public void extensionProviderIsAnObjectButNotAClass() { + assertThat(AbstractPluginRepository.isExtensionProvider(BProvider.class), is(false)); + assertThat(AbstractPluginRepository.isExtensionProvider(new BProvider()), is(true)); + } + + @Test + public void shouldRegisterExtensionProviders() { + MutablePicoContainer pico = IocContainer.buildPicoContainer(); + AbstractPluginRepository repository = new AbstractPluginRepository() { + @Override + protected boolean shouldRegisterExtension(String pluginKey, Object extension) { + return isType(extension, ServerExtension.class); + } + }; + + Plugin plugin = mock(Plugin.class); + BProvider bProvider = new BProvider(); + when(plugin.getExtensions()).thenReturn(Arrays.asList(A.class, bProvider, C.class, D.class)); + repository.registerPlugin(pico, plugin, "foo"); + pico.start(); + + assertThat(pico.getComponent(A.class), is(A.class)); + assertThat(pico.getComponent(C.class), is(C.class)); + assertThat(pico.getComponent(D.class), is(D.class)); + assertThat(pico.getComponent(C.class).getBees().length, is(2)); + assertThat(pico.getComponent(D.class).getBees().length, is(2)); + assertThat(bProvider.calls, is(1)); // do not create B instances two times (C and D dependencies) + + // Picocontainer question: why components created by providers are not registered in the container ? + //assertThat(pico.getComponents(B.class).size(), is(2)); // created by the ExtensionProvider + } + + public static class FakeServerExtension implements ServerExtension { + @Override + public String toString() { + return "instance"; + } + } + + public static class FakeBatchExtension implements BatchExtension { + + } + + public static class A implements ServerExtension { + } + + public static class B implements ServerExtension { + private A a; + + public B(A a) { + this.a = a; + } + } + + public static class C implements ServerExtension { + private B[] bees; + + public C(B[] bees) { + this.bees = bees; + } + + public B[] getBees() { + return bees; + } + } + + public static class D implements ServerExtension { + private B[] bees; + + public D(B[] bees) { + this.bees = bees; + } + + public B[] getBees() { + return bees; + } + } + + public static class BProvider extends ExtensionProvider { + + private int calls = 0; + public B[] provide(A a) { + calls++; + return new B[]{new B(a), new B(a)}; + } + } + + +} diff --git a/sonar-deprecated/src/main/java/org/sonar/api/rules/DefaultRulesManager.java b/sonar-deprecated/src/main/java/org/sonar/api/rules/DefaultRulesManager.java index 26b917b9329..37345cead6a 100644 --- a/sonar-deprecated/src/main/java/org/sonar/api/rules/DefaultRulesManager.java +++ b/sonar-deprecated/src/main/java/org/sonar/api/rules/DefaultRulesManager.java @@ -19,143 +19,31 @@ */ package org.sonar.api.rules; -import org.apache.commons.collections.CollectionUtils; -import org.sonar.api.Plugin; -import org.sonar.api.Plugins; +import com.google.common.collect.Maps; import org.sonar.jpa.dao.RulesDao; -import org.sonar.api.resources.Language; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * A class to manage and access rules defined in Sonar. * - * @deprecated UGLY CLASS - WILL BE COMPLETELY REFACTORED IN SONAR 2.3 + * @deprecated UGLY CLASS */ @Deprecated public class DefaultRulesManager extends RulesManager { - private final Set<Language> languages; - private final RulesRepository<?>[] repositories; - private final Map<Language, List<RulesRepository<?>>> rulesByLanguage; - private final Map<Language, List<Plugin>> pluginsByLanguage; - private final Map<String, Map<String, Rule>> rulesByPluginAndKey = new HashMap<String, Map<String, Rule>>(); + private final Map<String, Map<String, Rule>> rulesByPluginAndKey = Maps.newHashMap(); private final RulesDao rulesDao; - private final Plugins plugins; - /** - * Creates a RuleManager - * @param plugins the plugins dictionnary - * @param repositories the repositories of rules - * @param dao the dao object - */ - public DefaultRulesManager(Plugins plugins, RulesRepository[] repositories, RulesDao dao) { - this.plugins = plugins; + public DefaultRulesManager(RulesDao dao) { this.rulesDao = dao; - - languages = new HashSet<Language>(); - rulesByLanguage = new HashMap<Language, List<RulesRepository<?>>>(); - pluginsByLanguage = new HashMap<Language, List<Plugin>>(); - this.repositories = repositories; - - for (RulesRepository<?> repository : repositories) { - languages.add(repository.getLanguage()); - - List<RulesRepository<?>> list = rulesByLanguage.get(repository.getLanguage()); - if (list == null) { - list = new ArrayList<RulesRepository<?>>(); - rulesByLanguage.put(repository.getLanguage(), list); - } - list.add(repository); - - List<Plugin> languagePlugins = pluginsByLanguage.get(repository.getLanguage()); - if (languagePlugins == null) { - languagePlugins = new ArrayList<Plugin>(); - pluginsByLanguage.put(repository.getLanguage(), languagePlugins); - } - languagePlugins.add(plugins.getPluginByExtension(repository)); - } - } - - /** - * Constructor for tests only - * - * @param dao the dao - */ - protected DefaultRulesManager(RulesDao dao, Plugins plugins) { - this.rulesDao = dao; - this.plugins = plugins; - languages = new HashSet<Language>(); - rulesByLanguage = new HashMap<Language, List<RulesRepository<?>>>(); - pluginsByLanguage = new HashMap<Language, List<Plugin>>(); - repositories = null; - - } - - /** - * Returns the list of languages for which there is a rule repository - * - * @return a Set of languages - */ - public Set<Language> getLanguages() { - return languages; - } - - /** - * Gets the list of Rules Repositories available for a language - * - * @param language the language - * @return the list of rules repositories - */ - public List<RulesRepository<?>> getRulesRepositories(Language language) { - List<RulesRepository<?>> rulesRepositories = rulesByLanguage.get(language); - if (CollectionUtils.isNotEmpty(rulesRepositories)) { - return rulesRepositories; - } - return Collections.emptyList(); - } - - /** - * Gets the complete list of Rules Repositories in the Sonar instance - * - * @return the list of rules repositories - */ - public List<RulesRepository<?>> getRulesRepositories() { - return Arrays.asList(repositories); - } - - /** - * Gets the list of rules plugins for a given language - * @param language the language - * @return the list of plugins - */ - public List<Plugin> getPlugins(Language language) { - List<Plugin> result = pluginsByLanguage.get(language); - if (!CollectionUtils.isEmpty(result)) { - return result; - } - return Collections.emptyList(); - } - - - /** - * Get the list of rules plugin that implement a mechanism of import for a given language - * - * @param language the language - * @return the list of plugins - */ - public List<Plugin> getImportablePlugins(Language language) { - List<Plugin> targets = new ArrayList<Plugin>(); - for (RulesRepository<?> repository : getRulesRepositories(language)) { - if (repository instanceof ConfigurationImportable) { - targets.add(plugins.getPluginByExtension(repository)); - } - } - return targets; } /** * Gets a list of rules indexed by their key for a given plugin + * * @param pluginKey the plugin key * @return a Map with the rule key and the rule */ @@ -175,21 +63,10 @@ public class DefaultRulesManager extends RulesManager { } /** - * Gets a collection of rules belonging to a plugin - * - * @param pluginKey the plugin key - * @return the collection of rules - */ - public Collection<Rule> getPluginRules(String pluginKey) { - Map<String, Rule> rulesByKey = getPluginRulesIndexedByKey(pluginKey); - return rulesByKey.values(); - } - - /** * Gets a rule belonging to a defined plugin based on its key * * @param pluginKey the plugin key - * @param ruleKey the rule key + * @param ruleKey the rule key * @return the rule */ public Rule getPluginRule(String pluginKey, String ruleKey) { diff --git a/sonar-deprecated/src/main/java/org/sonar/api/rules/RulesManager.java b/sonar-deprecated/src/main/java/org/sonar/api/rules/RulesManager.java index 374f2241cf3..2cf6a872307 100644 --- a/sonar-deprecated/src/main/java/org/sonar/api/rules/RulesManager.java +++ b/sonar-deprecated/src/main/java/org/sonar/api/rules/RulesManager.java @@ -34,58 +34,6 @@ import java.util.Set; public abstract class RulesManager { /** - * Returns the list of languages for which there is a rule repository - * - * @return a Set of languages - */ - public abstract Set<Language> getLanguages(); - - /** - * Gets the list of Rules Repositories available for a language - * - * @param language the language - * @return the list of rules repositories - */ - public abstract List<RulesRepository<?>> getRulesRepositories(Language language); - - /** - * Gets the complete list of Rules Repositories in the Sonar instance - * - * @return the list of rules repositories - */ - public abstract List<RulesRepository<?>> getRulesRepositories(); - - /** - * Gets the list of rules plugins for a given language - * @param language the language - * @return the list of plugins - */ - public abstract List<Plugin> getPlugins(Language language); - - /** - * Get the list of rules plugin that implement a mechanism of import for a given language - * - * @param language the language - * @return the list of plugins - */ - public abstract List<Plugin> getImportablePlugins(Language language); - - /** - * Gets a list of rules indexed by their key for a given plugin - * @param pluginKey the plugin key - * @return a Map with the rule key and the rule - */ - public abstract Map<String, Rule> getPluginRulesIndexedByKey(String pluginKey); - - /** - * Gets a collection of rules belonging to a plugin - * - * @param pluginKey the plugin key - * @return the collection of rules - */ - public abstract Collection<Rule> getPluginRules(String pluginKey); - - /** * Gets a rule belonging to a defined plugin based on its key * * @param pluginKey the plugin key diff --git a/sonar-deprecated/src/test/java/org/sonar/api/rules/DefaultRulesManagerTest.java b/sonar-deprecated/src/test/java/org/sonar/api/rules/DefaultRulesManagerTest.java deleted file mode 100644 index 7aec32d575e..00000000000 --- a/sonar-deprecated/src/test/java/org/sonar/api/rules/DefaultRulesManagerTest.java +++ /dev/null @@ -1,72 +0,0 @@ -/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2009 SonarSource SA
- * 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.api.rules;
-
-import org.junit.Test;
-import org.sonar.api.Plugin;
-import org.sonar.api.Plugins;
-import org.sonar.jpa.dao.RulesDao;
-import org.sonar.api.resources.Language;
-import org.sonar.jpa.dao.DaoFacade;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class DefaultRulesManagerTest {
-
- @Test
- public void shouldReturnsZeroImportablePluginsWhenLanguageHasNoRulesPlugin() {
- DefaultRulesManager rulesManager = createRulesManagerForAlanguageWithNoPlugins();
-
- Language language = mock(Language.class);
- List<Plugin> result = rulesManager.getImportablePlugins(language);
- assertThat(result.size(), is(0));
- }
-
- @Test
- public void shouldReturnZeroPluginWhenLanguageHasNoRulesPlugin() {
- DefaultRulesManager rulesManager = createRulesManagerForAlanguageWithNoPlugins();
- Language language = mock(Language.class);
- List<Plugin> result = rulesManager.getPlugins(language);
- assertThat(result.size(), is(0));
- }
-
- @Test
- public void shouldReturnZeroRulesRepositoryWhenLanguageHasNoRulesRepository() {
- DefaultRulesManager rulesManager = createRulesManagerForAlanguageWithNoPlugins();
- Language language = mock(Language.class);
- List<RulesRepository<?>> result = rulesManager.getRulesRepositories(language);
- assertThat(result.size(), is(0));
- }
-
- private DefaultRulesManager createRulesManagerForAlanguageWithNoPlugins() {
- DaoFacade dao = mock(DaoFacade.class);
- RulesDao rulesDao = mock(RulesDao.class);
- when(rulesDao.getCategories()).thenReturn(Collections.<RulesCategory>emptyList());
- DefaultRulesManager rulesManager = new DefaultRulesManager(rulesDao, new Plugins(null));
- return rulesManager;
- }
-}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/ExtensionProvider.java b/sonar-plugin-api/src/main/java/org/sonar/api/ExtensionProvider.java index bc5b61544a6..615c57a6631 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/ExtensionProvider.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/ExtensionProvider.java @@ -19,13 +19,11 @@ */ package org.sonar.api; -import java.util.Collection; +import org.picocontainer.injectors.Provider; /** * @since 2.3 */ -public interface ExtensionProvider { - - Collection provide(); +public abstract class ExtensionProvider implements Extension, Provider { } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/Plugins.java b/sonar-plugin-api/src/main/java/org/sonar/api/Plugins.java index eca185e4920..82fa9606c36 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/Plugins.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/Plugins.java @@ -54,21 +54,6 @@ public class Plugins { return pluginProvider.getPlugin(key); } - - /** - * Returns a plugin based on its extension - */ - public Plugin getPluginByExtension(Class<? extends Extension> clazz) { - return pluginProvider.getPluginForExtension(clazz); - } - - /** - * Returns a plugin key based on its extension - */ - public String getPluginKeyByExtension(Class<? extends Extension> clazz) { - return pluginProvider.getPluginKeyForExtension(clazz); - } - /** * Returns a plugin based on its extension */ diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/AbstractCoverageExtension.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/AbstractCoverageExtension.java index 330e1d4e79c..02869c36a5b 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/AbstractCoverageExtension.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/AbstractCoverageExtension.java @@ -19,7 +19,6 @@ */ package org.sonar.api.batch; -import org.apache.commons.lang.ArrayUtils; import org.sonar.api.BatchExtension; import org.sonar.api.Plugins; import org.sonar.api.resources.Project; @@ -42,29 +41,22 @@ public abstract class AbstractCoverageExtension implements BatchExtension { */ public static final String DEFAULT_PLUGIN = "cobertura"; - private final Plugins plugins; - /** * Default constructor + * * @param plugins the list of plugins available + * @deprecated since 2.3. Use the default constructor */ public AbstractCoverageExtension(Plugins plugins) { - this.plugins = plugins; + } + + public AbstractCoverageExtension() { } /** * Whether to implement the extension on the project */ public boolean shouldExecuteOnProject(Project project) { - return project.getAnalysisType().isDynamic(true) && isSelectedPlugin(project); - } - - protected boolean isSelectedPlugin(Project project) { - String[] selectedPluginKeys = project.getConfiguration().getStringArray(PARAM_PLUGIN); - if (selectedPluginKeys.length == 0) { - selectedPluginKeys = new String[]{DEFAULT_PLUGIN}; - } - String pluginKey = plugins.getPluginKeyByExtension(getClass()); - return ArrayUtils.contains(selectedPluginKeys, pluginKey); + return project.getAnalysisType().isDynamic(true); } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/platform/PluginRepository.java b/sonar-plugin-api/src/main/java/org/sonar/api/platform/PluginRepository.java index 611c3eeca28..8529642a9f1 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/platform/PluginRepository.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/platform/PluginRepository.java @@ -19,95 +19,20 @@ */ package org.sonar.api.platform; -import org.picocontainer.Characteristics; -import org.picocontainer.MutablePicoContainer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.*; +import org.sonar.api.BatchComponent; +import org.sonar.api.Plugin; +import org.sonar.api.Property; +import org.sonar.api.ServerComponent; import java.util.Collection; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.Map; -/** - * @since 2.2 - */ -public abstract class PluginRepository implements BatchComponent, ServerComponent { - - private static final Logger LOG = LoggerFactory.getLogger(PluginRepository.class); - - private Map<String, Plugin> pluginByKey = new HashMap<String, Plugin>(); - private Map<Object, Plugin> pluginByExtension = new IdentityHashMap<Object, Plugin>(); - - public void registerPlugin(MutablePicoContainer container, Plugin plugin, Class<? extends Extension> extensionClass) { - LOG.debug("Registering the plugin {}", plugin.getKey()); - pluginByKey.put(plugin.getKey(), plugin); - for (Object extension : plugin.getExtensions()) { - if (isExtension(extension, extensionClass)) { - LOG.debug("Registering the extension: {}", extension); - container.as(Characteristics.CACHE).addComponent(getExtensionKey(extension), extension); - pluginByExtension.put(extension, plugin); - } - } - } - - public Collection<Plugin> getPlugins() { - return pluginByKey.values(); - } - - public Plugin getPlugin(String key) { - return pluginByKey.get(key); - } - - /** - * Returns the list of properties of a plugin - */ - public Property[] getProperties(Plugin plugin) { - if (plugin != null) { - Class<? extends Plugin> classInstance = plugin.getClass(); - if (classInstance.isAnnotationPresent(Properties.class)) { - return classInstance.getAnnotation(Properties.class).value(); - } - } - return new Property[0]; - } - - public Property[] getProperties(String pluginKey) { - return getProperties(pluginByKey.get(pluginKey)); - } - - public Plugin getPluginForExtension(Object extension) { - Plugin plugin = pluginByExtension.get(extension); - if (plugin == null && !(extension instanceof Class)) { - plugin = pluginByExtension.get(extension.getClass()); - } - return plugin; - } - - public String getPluginKeyForExtension(Object extension) { - Plugin plugin = getPluginForExtension(extension); - if (plugin != null) { - return plugin.getKey(); - } - return null; - } - - private boolean isExtension(Object extension, Class<? extends Extension> extensionClass) { - Class clazz = (extension instanceof Class ? (Class) extension : extension.getClass()); - return extensionClass.isAssignableFrom(clazz); - } +public interface PluginRepository extends BatchComponent, ServerComponent { + Collection<Plugin> getPlugins(); - public void registerExtension(MutablePicoContainer container, Plugin plugin, Object extension) { - container.as(Characteristics.CACHE).addComponent(getExtensionKey(extension), extension); - pluginByExtension.put(extension, plugin); - } + Plugin getPlugin(String key); - protected Object getExtensionKey(Object component) { - if (component instanceof Class) { - return component; - } - return component.getClass().getCanonicalName() + "-" + component.toString(); - } + @Deprecated + Plugin getPluginForExtension(Object extension); + Property[] getProperties(Plugin plugin); } diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/batch/AbstractCoverageExtensionTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/batch/AbstractCoverageExtensionTest.java index bfa556fabd0..036b8647878 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/batch/AbstractCoverageExtensionTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/batch/AbstractCoverageExtensionTest.java @@ -19,53 +19,26 @@ */ package org.sonar.api.batch; -import org.apache.commons.configuration.PropertiesConfiguration; +import org.junit.Test; +import org.sonar.api.resources.Project; + import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; -import org.junit.Test; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import org.sonar.api.Plugins; -import org.sonar.api.resources.Project; public class AbstractCoverageExtensionTest { @Test - public void defaultPluginIsCobertura() { - Plugins plugins = mock(Plugins.class); - when(plugins.getPluginKeyByExtension(FakeCoverageSensor.class)).thenReturn("cobertura"); - - Project project = mock(Project.class); - when(project.getConfiguration()).thenReturn(new PropertiesConfiguration()); - - assertThat(new FakeCoverageSensor(plugins).isSelectedPlugin(project), is(true)); - } - - @Test - public void doNotExecuteIfNotSelectedPlugin() { - Plugins plugins = mock(Plugins.class); - when(plugins.getPluginKeyByExtension(FakeCoverageSensor.class)).thenReturn("fake"); - - Project project = mock(Project.class); - PropertiesConfiguration config = new PropertiesConfiguration(); - when(project.getConfiguration()).thenReturn(config); - config.setProperty(AbstractCoverageExtension.PARAM_PLUGIN, "cobertura"); - - assertThat(new FakeCoverageSensor(plugins).isSelectedPlugin(project), is(false)); - } - - @Test public void doNotExecuteIfStaticAnalysis() { Project project = mock(Project.class); when(project.getAnalysisType()).thenReturn(Project.AnalysisType.STATIC); - FakeCoverageSensor sensor = new FakeCoverageSensor(null); + FakeCoverageSensor sensor = new FakeCoverageSensor(); assertThat(sensor.shouldExecuteOnProject(project), is(false)); } protected static class FakeCoverageSensor extends AbstractCoverageExtension { - public FakeCoverageSensor(Plugins plugins) { - super(plugins); - } + } } diff --git a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java index bf8b923b6b9..145ba28e3f4 100644 --- a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java +++ b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java @@ -133,7 +133,7 @@ public final class Platform { coreContainer.as(Characteristics.CACHE).addComponent(UpdateFinderFactory.class); coreContainer.as(Characteristics.CACHE).addComponent(PluginDownloader.class); coreContainer.as(Characteristics.NO_CACHE).addComponent(FilterExecutor.class); - coreContainer.addAdapter(new DatabaseSessionProvider()); + coreContainer.as(Characteristics.NO_CACHE).addAdapter(new DatabaseSessionProvider()); coreContainer.start(); DatabaseConfiguration dbConfiguration = new DatabaseConfiguration(coreContainer.getComponent(DatabaseSessionFactory.class)); diff --git a/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java b/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java index 980898bc0eb..cfb1191fc11 100644 --- a/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java +++ b/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java @@ -23,15 +23,15 @@ import org.picocontainer.Characteristics; import org.picocontainer.MutablePicoContainer; import org.sonar.api.Plugin; import org.sonar.api.ServerExtension; -import org.sonar.api.platform.PluginRepository; import org.sonar.api.utils.SonarException; +import org.sonar.core.plugin.AbstractPluginRepository; import org.sonar.core.plugin.JpaPlugin; import org.sonar.core.plugin.JpaPluginDao; /** * @since 2.2 */ -public class ServerPluginRepository extends PluginRepository { +public class ServerPluginRepository extends AbstractPluginRepository { private JpaPluginDao dao; private PluginClassLoaders classloaders; @@ -41,18 +41,29 @@ public class ServerPluginRepository extends PluginRepository { this.classloaders = classloaders; } + /** + * Only for unit tests + */ + ServerPluginRepository() { + } + public void registerPlugins(MutablePicoContainer pico) { for (JpaPlugin jpaPlugin : dao.getPlugins()) { try { Class pluginClass = classloaders.getClassLoader(jpaPlugin.getKey()).loadClass(jpaPlugin.getPluginClass()); pico.as(Characteristics.CACHE).addComponent(pluginClass); Plugin plugin = (Plugin) pico.getComponent(pluginClass); - registerPlugin(pico, plugin, ServerExtension.class); - + registerPlugin(pico, plugin, jpaPlugin.getKey()); + + } catch (ClassNotFoundException e) { throw new SonarException("Please check the plugin manifest. The main plugin class does not exist: " + jpaPlugin.getPluginClass(), e); } } } + @Override + protected boolean shouldRegisterExtension(String pluginKey, Object extension) { + return isType(extension, ServerExtension.class); + } } diff --git a/sonar-server/src/main/java/org/sonar/server/startup/RegisterMetrics.java b/sonar-server/src/main/java/org/sonar/server/startup/RegisterMetrics.java index 4b66047e7d2..4e9ea3d96ca 100644 --- a/sonar-server/src/main/java/org/sonar/server/startup/RegisterMetrics.java +++ b/sonar-server/src/main/java/org/sonar/server/startup/RegisterMetrics.java @@ -36,35 +36,30 @@ import com.google.common.collect.Maps; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import javax.persistence.Query;
public class RegisterMetrics {
- private PluginRepository pluginRepository;
private MeasuresDao measuresDao;
private Metrics[] metricsRepositories = null;
private DatabaseSession session;
- public RegisterMetrics(DatabaseSession session, MeasuresDao measuresDao, PluginRepository pluginRepository, Metrics[] metricsRepositories) {
+ public RegisterMetrics(DatabaseSession session, MeasuresDao measuresDao, Metrics[] metricsRepositories) {
this.session = session;
this.measuresDao = measuresDao;
- this.pluginRepository = pluginRepository;
this.metricsRepositories = metricsRepositories;
}
- public RegisterMetrics(MeasuresDao measuresDao) {
- this.measuresDao = measuresDao;
- }
-
public void start() {
TimeProfiler profiler = new TimeProfiler().start("Load metrics");
measuresDao.disableAutomaticMetrics();
- ArrayList<Metric> metricsToRegister = Lists.newArrayList();
+ List<Metric> metricsToRegister = Lists.newArrayList();
metricsToRegister.addAll(CoreMetrics.getMetrics());
- HashMap<String, Metrics> metricsByRepository = Maps.newHashMap();
+ Map<String, Metrics> metricsByRepository = Maps.newHashMap();
if (metricsRepositories != null) {
for (Metrics metrics : metricsRepositories) {
checkMetrics(metricsByRepository, metrics);
@@ -76,17 +71,15 @@ public class RegisterMetrics { profiler.stop();
}
- private void checkMetrics(HashMap<String, Metrics> metricsByRepository, Metrics metrics) {
+ private void checkMetrics(Map<String, Metrics> metricsByRepository, Metrics metrics) {
for (Metric metric : metrics.getMetrics()) {
String metricKey = metric.getKey();
if (CoreMetrics.metrics.contains(metric)) {
- throw new ServerStartException("Found plugin, which contains metric '" + metricKey + "' from core: " + pluginRepository.getPluginKeyForExtension(metrics));
+ throw new ServerStartException("The following metric is already defined in sonar: " + metricKey);
}
Metrics anotherRepository = metricsByRepository.get(metricKey);
if (anotherRepository != null) {
- String firstPlugin = pluginRepository.getPluginKeyForExtension(anotherRepository);
- String secondPlugin = pluginRepository.getPluginKeyForExtension(metrics);
- throw new ServerStartException("Found two plugins with the same metric '" + metricKey + "': " + firstPlugin + " and " + secondPlugin);
+ throw new ServerStartException("The metric '" + metricKey + "' is already defined in the extension: " + anotherRepository);
}
metricsByRepository.put(metricKey, metrics);
}
diff --git a/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java b/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java new file mode 100644 index 00000000000..d6d3115e806 --- /dev/null +++ b/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java @@ -0,0 +1,52 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * 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.server.plugins; + +import org.junit.Test; +import org.sonar.api.BatchExtension; +import org.sonar.api.ServerExtension; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +public class ServerPluginRepositoryTest { + @Test + public void shouldRegisterServerExtensions() { + ServerPluginRepository repository = new ServerPluginRepository(); + + // check classes + assertThat(repository.shouldRegisterExtension("foo", FakeBatchExtension.class), is(false)); + assertThat(repository.shouldRegisterExtension("foo", FakeServerExtension.class), is(true)); + assertThat(repository.shouldRegisterExtension("foo", String.class), is(false)); + + // check objects + assertThat(repository.shouldRegisterExtension("foo", new FakeBatchExtension()), is(false)); + assertThat(repository.shouldRegisterExtension("foo", new FakeServerExtension()), is(true)); + assertThat(repository.shouldRegisterExtension("foo", "foo"), is(false)); + } + + public static class FakeBatchExtension implements BatchExtension { + + } + + public static class FakeServerExtension implements ServerExtension { + + } +} diff --git a/sonar-server/src/test/java/org/sonar/server/startup/RegisterMetricsTest.java b/sonar-server/src/test/java/org/sonar/server/startup/RegisterMetricsTest.java index 2f2244e938a..49670f559ca 100644 --- a/sonar-server/src/test/java/org/sonar/server/startup/RegisterMetricsTest.java +++ b/sonar-server/src/test/java/org/sonar/server/startup/RegisterMetricsTest.java @@ -41,7 +41,7 @@ public class RegisterMetricsTest extends AbstractDbUnitTestCase { Metric metric1 = new Metric("new1", "short1", "desc1", Metric.ValueType.FLOAT, 1, true, "domain1", false);
Metric metric2 = new Metric("new2", "short2", "desc2", Metric.ValueType.FLOAT, 1, true, "domain2", false);
- RegisterMetrics synchronizer = new RegisterMetrics(getSession(), new MeasuresDao(getSession()), null, null);
+ RegisterMetrics synchronizer = new RegisterMetrics(getSession(), new MeasuresDao(getSession()), null);
synchronizer.register(Arrays.asList(metric1, metric2));
checkTables("shouldSaveIfNew", "metrics");
}
@@ -52,7 +52,7 @@ public class RegisterMetricsTest extends AbstractDbUnitTestCase { final List<Metric> metrics = new ArrayList<Metric>();
metrics.add(new Metric("key", "new short name", "new description", Metric.ValueType.FLOAT, -1, true, "new domain", false));
- RegisterMetrics synchronizer = new RegisterMetrics(getSession(), new MeasuresDao(getSession()), null, null);
+ RegisterMetrics synchronizer = new RegisterMetrics(getSession(), new MeasuresDao(getSession()), null);
synchronizer.register(metrics);
checkTables("shouldUpdateIfAlreadyExists", "metrics");
@@ -62,7 +62,7 @@ public class RegisterMetricsTest extends AbstractDbUnitTestCase { public void enableOnlyLoadedMetrics() throws SQLException {
setupData("enableOnlyLoadedMetrics");
- RegisterMetrics loader = new RegisterMetrics(getSession(), new MeasuresDao(getSession()), null, null);
+ RegisterMetrics loader = new RegisterMetrics(getSession(), new MeasuresDao(getSession()), null);
loader.start();
assertFalse(getDao().getMeasuresDao().getMetric("deprecated").getEnabled());
@@ -73,7 +73,7 @@ public class RegisterMetricsTest extends AbstractDbUnitTestCase { public void cleanAlerts() throws SQLException {
setupData("cleanAlerts");
- RegisterMetrics loader = new RegisterMetrics(getSession(), new MeasuresDao(getSession()), null, null);
+ RegisterMetrics loader = new RegisterMetrics(getSession(), new MeasuresDao(getSession()), null);
loader.cleanAlerts();
checkTables("cleanAlerts", "metrics", "alerts");
|