From 9decc5bd48352c1fb9fceca9de8b7eca654fd0bf Mon Sep 17 00:00:00 2001 From: Julien HENRY Date: Tue, 10 Jun 2014 00:41:17 +0200 Subject: [PATCH] SONAR-5389 Initial version of the new sensor mode --- .../org/sonar/plugins/core/CorePlugin.java | 7 - .../bootstrap/BatchPluginRepository.java | 18 +- .../batch/bootstrap/BootstrapContainer.java | 21 +- ...er.java => DefaultPluginsReferential.java} | 21 +- .../batch/bootstrap/PluginsReferential.java | 43 ++++ .../sonar/batch/bootstrap/TaskContainer.java | 4 +- .../sonar/batch/phases/Phase2Executor.java | 116 +++++++++ .../batch/scan/ProjectReactorBuilder.java | 2 +- .../batch/scan2/ModuleScanContainer.java | 211 ++++++++++++++++ .../batch/scan2/ProjectScanContainer.java | 229 ++++++++++++++++++ .../bootstrap/BatchPluginRepositoryTest.java | 16 +- .../batch/bootstrap/PluginDownloaderTest.java | 15 +- .../sonar/batch/medium/Scan2MediumTest.java | 168 +++++++++++++ .../java/org/sonar/api/CoreProperties.java | 5 + .../java/org/sonar/api/rules/RuleFinder.java | 8 +- 15 files changed, 836 insertions(+), 48 deletions(-) rename sonar-batch/src/main/java/org/sonar/batch/bootstrap/{PluginDownloader.java => DefaultPluginsReferential.java} (80%) create mode 100644 sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginsReferential.java create mode 100644 sonar-batch/src/main/java/org/sonar/batch/phases/Phase2Executor.java create mode 100644 sonar-batch/src/main/java/org/sonar/batch/scan2/ModuleScanContainer.java create mode 100644 sonar-batch/src/main/java/org/sonar/batch/scan2/ProjectScanContainer.java create mode 100644 sonar-batch/src/test/java/org/sonar/batch/medium/Scan2MediumTest.java diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java index eb8468ba5b6..0b22c904e59 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java @@ -119,13 +119,6 @@ import org.sonar.plugins.core.widgets.measures.MeasureFilterTreemapWidget; import java.util.List; @Properties({ - @Property( - key = CoreProperties.TASK, - name = "Task to be executed", - defaultValue = CoreProperties.SCAN_TASK, - module = false, - project = false, - global = false), @Property( key = CoreProperties.SERVER_BASE_URL, defaultValue = CoreProperties.SERVER_BASE_URL_DEFAULT_VALUE, diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java index 2e2b09da23c..aea375f3c0d 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java @@ -36,7 +36,11 @@ import org.sonar.core.plugins.RemotePlugin; import java.io.File; import java.text.MessageFormat; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Sets.newHashSet; @@ -46,7 +50,7 @@ public class BatchPluginRepository implements PluginRepository { private static final Logger LOG = LoggerFactory.getLogger(BatchPluginRepository.class); private static final String CORE_PLUGIN = "core"; - private PluginDownloader pluginDownloader; + private PluginsReferential pluginsReferential; private Map pluginsByKey; private Map metadataByKey; private Settings settings; @@ -54,9 +58,9 @@ public class BatchPluginRepository implements PluginRepository { private final AnalysisMode analysisMode; private final BatchPluginJarInstaller pluginInstaller; - public BatchPluginRepository(PluginDownloader pluginDownloader, Settings settings, AnalysisMode analysisMode, - BatchPluginJarInstaller pluginInstaller) { - this.pluginDownloader = pluginDownloader; + public BatchPluginRepository(PluginsReferential pluginsReferential, Settings settings, AnalysisMode analysisMode, + BatchPluginJarInstaller pluginInstaller) { + this.pluginsReferential = pluginsReferential; this.settings = settings; this.analysisMode = analysisMode; this.pluginInstaller = pluginInstaller; @@ -64,7 +68,7 @@ public class BatchPluginRepository implements PluginRepository { public void start() { LOG.info("Install plugins"); - doStart(pluginDownloader.downloadPluginIndex()); + doStart(pluginsReferential.pluginList()); } void doStart(List remotePlugins) { @@ -72,7 +76,7 @@ public class BatchPluginRepository implements PluginRepository { metadataByKey = Maps.newHashMap(); for (RemotePlugin remote : remotePlugins) { if (filter.accepts(remote.getKey())) { - File pluginFile = pluginDownloader.downloadPlugin(remote); + File pluginFile = pluginsReferential.pluginFile(remote); PluginMetadata metadata = pluginInstaller.installToCache(pluginFile, remote.isCore()); if (StringUtils.isBlank(metadata.getBasePlugin()) || filter.accepts(metadata.getBasePlugin())) { metadataByKey.put(metadata.getKey(), metadata); diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapContainer.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapContainer.java index feffc2f6ede..992ccf77819 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapContainer.java @@ -20,10 +20,13 @@ package org.sonar.batch.bootstrap; import org.apache.commons.configuration.PropertiesConfiguration; +import org.sonar.api.CoreProperties; import org.sonar.api.Plugin; import org.sonar.api.config.EmailSettings; +import org.sonar.api.measures.MetricFinder; import org.sonar.api.platform.ComponentContainer; import org.sonar.api.platform.PluginMetadata; +import org.sonar.api.rules.RuleFinder; import org.sonar.api.utils.Durations; import org.sonar.api.utils.HttpDownloader; import org.sonar.api.utils.System2; @@ -63,9 +66,11 @@ import java.util.Map; public class BootstrapContainer extends ComponentContainer { private final Map bootstrapProperties; + private final boolean sensorMode; private BootstrapContainer(Map bootstrapProperties) { super(); + this.sensorMode = CoreProperties.ANALYSIS_MODE_SENSOR.equals(bootstrapProperties.get(CoreProperties.ANALYSIS_MODE)); this.bootstrapProperties = bootstrapProperties; } @@ -78,7 +83,9 @@ public class BootstrapContainer extends ComponentContainer { @Override protected void doBeforeStart() { addBootstrapComponents(); - addDatabaseComponents(); + if (!sensorMode) { + addDatabaseComponents(); + } addCoreComponents(); } @@ -87,7 +94,6 @@ public class BootstrapContainer extends ComponentContainer { new PropertiesConfiguration(), new BootstrapProperties(bootstrapProperties), AnalysisMode.class, - PluginDownloader.class, BatchPluginRepository.class, BatchPluginJarInstaller.class, BatchSettings.class, @@ -105,6 +111,15 @@ public class BootstrapContainer extends ComponentContainer { if (getComponentByType(SettingsReferential.class) == null) { add(DefaultSettingsReferential.class); } + if (getComponentByType(PluginsReferential.class) == null) { + add(DefaultPluginsReferential.class); + } + if (getComponentByType(RuleFinder.class) == null) { + add(CacheRuleFinder.class); + } + if (getComponentByType(MetricFinder.class) == null) { + add(CacheMetricFinder.class); + } } private void addDatabaseComponents() { @@ -135,8 +150,6 @@ public class BootstrapContainer extends ComponentContainer { MeasuresDao.class, RulesDao.class, ProfilesDao.class, - CacheRuleFinder.class, - CacheMetricFinder.class, HibernateUserFinder.class, SemaphoreUpdater.class, SemaphoresImpl.class, diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginDownloader.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/DefaultPluginsReferential.java similarity index 80% rename from sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginDownloader.java rename to sonar-batch/src/main/java/org/sonar/batch/bootstrap/DefaultPluginsReferential.java index 0af5f2a4e96..93ae4e97c34 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginDownloader.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/DefaultPluginsReferential.java @@ -24,8 +24,6 @@ import org.apache.commons.lang.CharUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.sonar.api.BatchComponent; -import org.sonar.api.utils.SonarException; import org.sonar.core.plugins.RemotePlugin; import org.sonar.core.plugins.RemotePluginFile; import org.sonar.home.cache.FileCache; @@ -34,19 +32,23 @@ import java.io.File; import java.io.IOException; import java.util.List; -public class PluginDownloader implements BatchComponent { +/** + * A {@link PluginsReferential} implementation that put downloaded plugins in a FS cache. + */ +public class DefaultPluginsReferential implements PluginsReferential { - private static final Logger LOG = LoggerFactory.getLogger(PluginDownloader.class); + private static final Logger LOG = LoggerFactory.getLogger(DefaultPluginsReferential.class); private ServerClient server; private FileCache fileCache; - public PluginDownloader(FileCache fileCache, ServerClient server) { + public DefaultPluginsReferential(FileCache fileCache, ServerClient server) { this.server = server; this.fileCache = fileCache; } - public File downloadPlugin(final RemotePlugin remote) { + @Override + public File pluginFile(final RemotePlugin remote) { try { final RemotePluginFile file = remote.file(); File cachedFile = fileCache.get(file.getFilename(), file.getHash(), new FileCache.Downloader() { @@ -63,11 +65,12 @@ public class PluginDownloader implements BatchComponent { return cachedFile; } catch (Exception e) { - throw new SonarException("Fail to download plugin: " + remote.getKey(), e); + throw new IllegalStateException("Fail to download plugin: " + remote.getKey(), e); } } - public List downloadPluginIndex() { + @Override + public List pluginList() { String url = "/deploy/plugins/index.txt"; try { LOG.debug("Download index of plugins"); @@ -80,7 +83,7 @@ public class PluginDownloader implements BatchComponent { return remoteLocations; } catch (Exception e) { - throw new SonarException("Fail to download plugins index: " + url, e); + throw new IllegalStateException("Fail to download plugins index: " + url, e); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginsReferential.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginsReferential.java new file mode 100644 index 00000000000..daf38ae7abf --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginsReferential.java @@ -0,0 +1,43 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import org.sonar.core.plugins.RemotePlugin; + +import java.io.File; +import java.util.List; + +/** + * Plugin referential. + * @since 4.4 + */ +public interface PluginsReferential { + + /** + * Return list of plugins to be installed + */ + List pluginList(); + + /** + * Return location of a given plugin on the local FS. + */ + File pluginFile(RemotePlugin remote); + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java index 4bdda269f46..86122e7dec4 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java @@ -19,8 +19,8 @@ */ package org.sonar.batch.bootstrap; +import org.apache.commons.lang.StringUtils; import org.sonar.api.CoreProperties; -import org.sonar.api.config.Settings; import org.sonar.api.platform.ComponentContainer; import org.sonar.api.resources.ResourceTypes; import org.sonar.api.task.Task; @@ -94,7 +94,7 @@ public class TaskContainer extends ComponentContainer { @Override public void doAfterStart() { // default value is declared in CorePlugin - String taskKey = getComponentByType(Settings.class).getString(CoreProperties.TASK); + String taskKey = StringUtils.defaultIfEmpty(taskProperties.get(CoreProperties.TASK), CoreProperties.SCAN_TASK); TaskDefinition def = getComponentByType(Tasks.class).definition(taskKey); if (def == null) { diff --git a/sonar-batch/src/main/java/org/sonar/batch/phases/Phase2Executor.java b/sonar-batch/src/main/java/org/sonar/batch/phases/Phase2Executor.java new file mode 100644 index 00000000000..87d1348ae3b --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/phases/Phase2Executor.java @@ -0,0 +1,116 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.phases; + +import com.google.common.collect.Lists; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.SensorContext; +import org.sonar.api.resources.Project; +import org.sonar.batch.events.EventBus; +import org.sonar.batch.issue.ignore.scanner.IssueExclusionsLoader; +import org.sonar.batch.rule.QProfileVerifier; +import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem; +import org.sonar.batch.scan.filesystem.FileSystemLogger; +import org.sonar.batch.scan.maven.MavenPluginsConfigurator; + +import java.util.Collection; + +public final class Phase2Executor { + + public static final Logger LOGGER = LoggerFactory.getLogger(Phase2Executor.class); + + private final EventBus eventBus; + private final Phases phases; + private final MavenPluginsConfigurator mavenPluginsConfigurator; + private final InitializersExecutor initializersExecutor; + private final SensorsExecutor sensorsExecutor; + private final SensorContext sensorContext; + private final ProjectInitializer pi; + private final FileSystemLogger fsLogger; + private final DefaultModuleFileSystem fs; + private final QProfileVerifier profileVerifier; + private final IssueExclusionsLoader issueExclusionsLoader; + + public Phase2Executor(Phases phases, + MavenPluginsConfigurator mavenPluginsConfigurator, InitializersExecutor initializersExecutor, + SensorsExecutor sensorsExecutor, + SensorContext sensorContext, + EventBus eventBus, ProjectInitializer pi, + FileSystemLogger fsLogger, DefaultModuleFileSystem fs, QProfileVerifier profileVerifier, + IssueExclusionsLoader issueExclusionsLoader) { + this.phases = phases; + this.mavenPluginsConfigurator = mavenPluginsConfigurator; + this.initializersExecutor = initializersExecutor; + this.sensorsExecutor = sensorsExecutor; + this.sensorContext = sensorContext; + this.eventBus = eventBus; + this.pi = pi; + this.fsLogger = fsLogger; + this.fs = fs; + this.profileVerifier = profileVerifier; + this.issueExclusionsLoader = issueExclusionsLoader; + } + + public static Collection getPhaseClasses() { + return Lists.newArrayList(SensorsExecutor.class, InitializersExecutor.class, ProjectInitializer.class); + } + + /** + * Executed on each module + */ + public void execute(Project module) { + pi.execute(module); + + eventBus.fireEvent(new ProjectAnalysisEvent(module, true)); + + executeMavenPhase(module); + + executeInitializersPhase(); + + // Index and lock the filesystem + fs.index(); + + // Log detected languages and their profiles after FS is indexed and languages detected + profileVerifier.execute(); + + // Initialize issue exclusions + issueExclusionsLoader.execute(); + + sensorsExecutor.execute(sensorContext); + + eventBus.fireEvent(new ProjectAnalysisEvent(module, false)); + } + + private void executeInitializersPhase() { + if (phases.isEnabled(Phases.Phase.INIT)) { + initializersExecutor.execute(); + fsLogger.log(); + } + } + + private void executeMavenPhase(Project module) { + if (phases.isEnabled(Phases.Phase.MAVEN)) { + eventBus.fireEvent(new MavenPhaseEvent(true)); + mavenPluginsConfigurator.execute(module); + eventBus.fireEvent(new MavenPhaseEvent(false)); + } + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectReactorBuilder.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectReactorBuilder.java index f1700a38fcb..027abd427eb 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectReactorBuilder.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectReactorBuilder.java @@ -119,12 +119,12 @@ public class ProjectReactorBuilder { } protected ProjectDefinition defineProject(Properties properties, @Nullable ProjectDefinition parent) { - File baseDir = new File(properties.getProperty(PROPERTY_PROJECT_BASEDIR)); if (properties.containsKey(PROPERTY_MODULES)) { checkMandatoryProperties(properties, MANDATORY_PROPERTIES_FOR_MULTIMODULE_PROJECT); } else { checkMandatoryProperties(properties, MANDATORY_PROPERTIES_FOR_SIMPLE_PROJECT); } + File baseDir = new File(properties.getProperty(PROPERTY_PROJECT_BASEDIR)); final String projectKey = properties.getProperty(CoreProperties.PROJECT_KEY_PROPERTY); File workDir; if (parent == null) { diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/ModuleScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/ModuleScanContainer.java new file mode 100644 index 00000000000..44bcadc309c --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/ModuleScanContainer.java @@ -0,0 +1,211 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan2; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.BatchComponent; +import org.sonar.api.batch.InstantiationStrategy; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.rule.CheckFactory; +import org.sonar.api.platform.ComponentContainer; +import org.sonar.api.resources.Project; +import org.sonar.api.scan.filesystem.FileExclusions; +import org.sonar.batch.DefaultProjectClasspath; +import org.sonar.batch.DefaultSensorContext; +import org.sonar.batch.DefaultTimeMachine; +import org.sonar.batch.ProjectTree; +import org.sonar.batch.ResourceFilters; +import org.sonar.batch.ViolationFilters; +import org.sonar.batch.bootstrap.BatchExtensionDictionnary; +import org.sonar.batch.bootstrap.ExtensionInstaller; +import org.sonar.batch.bootstrap.ExtensionMatcher; +import org.sonar.batch.bootstrap.ExtensionUtils; +import org.sonar.batch.components.TimeMachineConfiguration; +import org.sonar.batch.events.EventBus; +import org.sonar.batch.index.DefaultIndex; +import org.sonar.batch.index.ResourcePersister; +import org.sonar.batch.issue.IssuableFactory; +import org.sonar.batch.issue.IssueFilters; +import org.sonar.batch.issue.ModuleIssues; +import org.sonar.batch.issue.ignore.EnforceIssuesFilter; +import org.sonar.batch.issue.ignore.IgnoreIssuesFilter; +import org.sonar.batch.issue.ignore.pattern.IssueExclusionPatternInitializer; +import org.sonar.batch.issue.ignore.pattern.IssueInclusionPatternInitializer; +import org.sonar.batch.issue.ignore.scanner.IssueExclusionsLoader; +import org.sonar.batch.issue.ignore.scanner.IssueExclusionsRegexpScanner; +import org.sonar.batch.language.LanguageDistributionDecorator; +import org.sonar.batch.phases.Phase2Executor; +import org.sonar.batch.phases.PhaseExecutor; +import org.sonar.batch.phases.PhasesTimeProfiler; +import org.sonar.batch.qualitygate.GenerateQualityGateEvents; +import org.sonar.batch.qualitygate.QualityGateProvider; +import org.sonar.batch.qualitygate.QualityGateVerifier; +import org.sonar.batch.rule.ActiveRulesProvider; +import org.sonar.batch.rule.ModuleQProfiles; +import org.sonar.batch.rule.QProfileDecorator; +import org.sonar.batch.rule.QProfileEventsDecorator; +import org.sonar.batch.rule.QProfileSensor; +import org.sonar.batch.rule.QProfileVerifier; +import org.sonar.batch.rule.RulesProfileProvider; +import org.sonar.batch.scan.LanguageVerifier; +import org.sonar.batch.scan.ModuleSettings; +import org.sonar.batch.scan.filesystem.ComponentIndexer; +import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem; +import org.sonar.batch.scan.filesystem.DeprecatedFileFilters; +import org.sonar.batch.scan.filesystem.ExclusionFilters; +import org.sonar.batch.scan.filesystem.FileIndexer; +import org.sonar.batch.scan.filesystem.FileSystemLogger; +import org.sonar.batch.scan.filesystem.InputFileBuilderFactory; +import org.sonar.batch.scan.filesystem.LanguageDetectionFactory; +import org.sonar.batch.scan.filesystem.ModuleFileSystemInitializer; +import org.sonar.batch.scan.filesystem.ModuleInputFileCache; +import org.sonar.batch.scan.filesystem.PreviousFileHashLoader; +import org.sonar.batch.scan.filesystem.ProjectFileSystemAdapter; +import org.sonar.batch.scan.filesystem.StatusDetectionFactory; +import org.sonar.batch.scan.report.JsonReport; +import org.sonar.core.component.ScanPerspectives; +import org.sonar.core.measure.MeasurementFilters; + +public class ModuleScanContainer extends ComponentContainer { + private static final Logger LOG = LoggerFactory.getLogger(ModuleScanContainer.class); + private final Project module; + + public ModuleScanContainer(ProjectScanContainer parent, Project module) { + super(parent); + this.module = module; + } + + @Override + protected void doBeforeStart() { + LOG.info("------------- Scan {}", module.getName()); + addCoreComponents(); + addExtensions(); + } + + private void addCoreComponents() { + ProjectDefinition moduleDefinition = getComponentByType(ProjectTree.class).getProjectDefinition(module); + add( + moduleDefinition, + module.getConfiguration(), + module, + ModuleSettings.class); + + // hack to initialize commons-configuration before ExtensionProviders + getComponentByType(ModuleSettings.class); + + add( + EventBus.class, + Phase2Executor.class, + PhasesTimeProfiler.class, + Phase2Executor.getPhaseClasses(), + moduleDefinition.getContainerExtensions(), + + // file system + ModuleInputFileCache.class, + FileExclusions.class, + ExclusionFilters.class, + DeprecatedFileFilters.class, + InputFileBuilderFactory.class, + StatusDetectionFactory.class, + LanguageDetectionFactory.class, + PreviousFileHashLoader.class, + FileIndexer.class, + ComponentIndexer.class, + LanguageVerifier.class, + FileSystemLogger.class, + DefaultProjectClasspath.class, + DefaultModuleFileSystem.class, + ModuleFileSystemInitializer.class, + ProjectFileSystemAdapter.class, + QProfileVerifier.class, + + // the Snapshot component will be removed when asynchronous measures are improved (required for AsynchronousMeasureSensor) + getComponentByType(ResourcePersister.class).getSnapshot(module), + + TimeMachineConfiguration.class, + DefaultSensorContext.class, + BatchExtensionDictionnary.class, + DefaultTimeMachine.class, + ViolationFilters.class, + IssueFilters.class, + MeasurementFilters.class, + ResourceFilters.class, + + // quality gates + new QualityGateProvider(), + QualityGateVerifier.class, + GenerateQualityGateEvents.class, + + // rules + ModuleQProfiles.class, + new ActiveRulesProvider(), + new RulesProfileProvider(), + QProfileSensor.class, + QProfileDecorator.class, + QProfileEventsDecorator.class, + CheckFactory.class, + + // report + JsonReport.class, + + // issues + IssuableFactory.class, + ModuleIssues.class, + + // issue exclusions + IssueInclusionPatternInitializer.class, + IssueExclusionPatternInitializer.class, + IssueExclusionsRegexpScanner.class, + IssueExclusionsLoader.class, + EnforceIssuesFilter.class, + IgnoreIssuesFilter.class, + + // language + LanguageDistributionDecorator.class, + + ScanPerspectives.class); + } + + private void addExtensions() { + ExtensionInstaller installer = getComponentByType(ExtensionInstaller.class); + installer.install(this, new ExtensionMatcher() { + public boolean accept(Object extension) { + if (ExtensionUtils.isType(extension, BatchComponent.class) && ExtensionUtils.isInstantiationStrategy(extension, InstantiationStrategy.PER_PROJECT)) { + // Special use-case: the extension point ProjectBuilder is used in a Maven environment to define some + // new sub-projects without pom. + // Example : C# plugin adds sub-projects at runtime, even if they are not defined in root pom. + return !ExtensionUtils.isMavenExtensionOnly(extension) || module.getPom() != null; + } + return false; + } + }); + } + + @Override + protected void doAfterStart() { + DefaultIndex index = getComponentByType(DefaultIndex.class); + index.setCurrentProject(module, + getComponentByType(ModuleIssues.class)); + + getComponentByType(PhaseExecutor.class).execute(module); + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/ProjectScanContainer.java new file mode 100644 index 00000000000..3ca92f4bc5b --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/ProjectScanContainer.java @@ -0,0 +1,229 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.scan2; + +import com.google.common.annotations.VisibleForTesting; +import org.sonar.api.BatchComponent; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.InstantiationStrategy; +import org.sonar.api.batch.bootstrap.ProjectBootstrapper; +import org.sonar.api.batch.bootstrap.ProjectReactor; +import org.sonar.api.config.Settings; +import org.sonar.api.platform.ComponentContainer; +import org.sonar.api.resources.Languages; +import org.sonar.api.resources.Project; +import org.sonar.api.scan.filesystem.PathResolver; +import org.sonar.api.utils.SonarException; +import org.sonar.batch.DefaultFileLinesContextFactory; +import org.sonar.batch.ProjectConfigurator; +import org.sonar.batch.ProjectTree; +import org.sonar.batch.bootstrap.ExtensionInstaller; +import org.sonar.batch.bootstrap.ExtensionMatcher; +import org.sonar.batch.bootstrap.ExtensionUtils; +import org.sonar.batch.bootstrap.MetricProvider; +import org.sonar.batch.components.PeriodsDefinition; +import org.sonar.batch.debt.DebtModelProvider; +import org.sonar.batch.debt.IssueChangelogDebtCalculator; +import org.sonar.batch.index.Caches; +import org.sonar.batch.index.ComponentDataCache; +import org.sonar.batch.index.ComponentDataPersister; +import org.sonar.batch.index.DefaultIndex; +import org.sonar.batch.index.DefaultPersistenceManager; +import org.sonar.batch.index.DefaultResourcePersister; +import org.sonar.batch.index.DependencyPersister; +import org.sonar.batch.index.EventPersister; +import org.sonar.batch.index.LinkPersister; +import org.sonar.batch.index.MeasurePersister; +import org.sonar.batch.index.ResourceCache; +import org.sonar.batch.index.ResourceKeyMigration; +import org.sonar.batch.index.SnapshotCache; +import org.sonar.batch.index.SourcePersister; +import org.sonar.batch.issue.DefaultProjectIssues; +import org.sonar.batch.issue.DeprecatedViolations; +import org.sonar.batch.issue.IssueCache; +import org.sonar.batch.issue.IssuePersister; +import org.sonar.batch.issue.ScanIssueStorage; +import org.sonar.batch.phases.GraphPersister; +import org.sonar.batch.profiling.PhasesSumUpTimeProfiler; +import org.sonar.batch.rule.RulesProvider; +import org.sonar.batch.scan.ProjectReactorBuilder; +import org.sonar.batch.scan.ProjectSettingsReady; +import org.sonar.batch.scan.filesystem.InputFileCache; +import org.sonar.batch.scan.maven.FakeMavenPluginExecutor; +import org.sonar.batch.scan.maven.MavenPluginExecutor; +import org.sonar.batch.scan.measure.MeasureCache; +import org.sonar.batch.source.HighlightableBuilder; +import org.sonar.batch.source.SymbolizableBuilder; +import org.sonar.core.component.ScanGraph; +import org.sonar.core.issue.IssueNotifications; +import org.sonar.core.issue.IssueUpdater; +import org.sonar.core.issue.workflow.FunctionExecutor; +import org.sonar.core.issue.workflow.IssueWorkflow; +import org.sonar.core.notification.DefaultNotificationManager; +import org.sonar.core.technicaldebt.DefaultTechnicalDebtModel; +import org.sonar.core.test.TestPlanBuilder; +import org.sonar.core.test.TestPlanPerspectiveLoader; +import org.sonar.core.test.TestableBuilder; +import org.sonar.core.test.TestablePerspectiveLoader; +import org.sonar.core.user.DefaultUserFinder; + +public class ProjectScanContainer extends ComponentContainer { + public ProjectScanContainer(ComponentContainer taskContainer) { + super(taskContainer); + } + + @Override + protected void doBeforeStart() { + projectBootstrap(); + addBatchComponents(); + fixMavenExecutor(); + addBatchExtensions(); + Settings settings = getComponentByType(Settings.class); + if (settings != null && settings.getBoolean(CoreProperties.PROFILING_LOG_PROPERTY)) { + add(PhasesSumUpTimeProfiler.class); + } + } + + private void projectBootstrap() { + // Old versions of bootstrappers used to pass project reactor as an extension + // so check if it is already present in parent container + ProjectReactor reactor = getComponentByType(ProjectReactor.class); + if (reactor == null) { + // OK, not present, so look for a custom ProjectBootstrapper + ProjectBootstrapper bootstrapper = getComponentByType(ProjectBootstrapper.class); + Settings settings = getComponentByType(Settings.class); + if (bootstrapper == null + // Starting from Maven plugin 2.3 then only DefaultProjectBootstrapper should be used. + || "true".equals(settings.getString("sonar.mojoUseRunner"))) { + // Use default SonarRunner project bootstrapper + ProjectReactorBuilder builder = getComponentByType(ProjectReactorBuilder.class); + reactor = builder.execute(); + } else { + reactor = bootstrapper.bootstrap(); + } + if (reactor == null) { + throw new SonarException(bootstrapper + " has returned null as ProjectReactor"); + } + add(reactor); + } + } + + private void addBatchComponents() { + add( + DefaultPersistenceManager.class, + DependencyPersister.class, + EventPersister.class, + LinkPersister.class, + MeasurePersister.class, + DefaultResourcePersister.class, + SourcePersister.class, + DefaultNotificationManager.class, + MetricProvider.class, + ProjectConfigurator.class, + DefaultIndex.class, + ResourceKeyMigration.class, + DefaultFileLinesContextFactory.class, + Caches.class, + SnapshotCache.class, + ResourceCache.class, + ComponentDataCache.class, + ComponentDataPersister.class, + DefaultUserFinder.class, + + // file system + InputFileCache.class, + PathResolver.class, + + // issues + IssueUpdater.class, + FunctionExecutor.class, + IssueWorkflow.class, + DeprecatedViolations.class, + IssueCache.class, + ScanIssueStorage.class, + IssuePersister.class, + IssueNotifications.class, + DefaultProjectIssues.class, + IssueChangelogDebtCalculator.class, + + // tests + TestPlanPerspectiveLoader.class, + TestablePerspectiveLoader.class, + TestPlanBuilder.class, + TestableBuilder.class, + ScanGraph.create(), + GraphPersister.class, + + // lang + Languages.class, + HighlightableBuilder.class, + SymbolizableBuilder.class, + + // technical debt + DefaultTechnicalDebtModel.class, + new DebtModelProvider(), + + // rules + new RulesProvider(), + + // Differential periods + PeriodsDefinition.class, + + // Measures + MeasureCache.class, + + ProjectSettingsReady.class); + } + + private void fixMavenExecutor() { + if (getComponentByType(MavenPluginExecutor.class) == null) { + add(FakeMavenPluginExecutor.class); + } + } + + private void addBatchExtensions() { + getComponentByType(ExtensionInstaller.class).install(this, new BatchExtensionFilter()); + } + + @Override + protected void doAfterStart() { + ProjectTree tree = getComponentByType(ProjectTree.class); + scanRecursively(tree.getRootProject()); + } + + private void scanRecursively(Project module) { + for (Project subModules : module.getModules()) { + scanRecursively(subModules); + } + scan(module); + } + + @VisibleForTesting + void scan(Project module) { + new ModuleScanContainer(this, module).execute(); + } + + static class BatchExtensionFilter implements ExtensionMatcher { + public boolean accept(Object extension) { + return ExtensionUtils.isType(extension, BatchComponent.class) + && ExtensionUtils.isInstantiationStrategy(extension, InstantiationStrategy.PER_BATCH); + } + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java index 2e29bf1f8e2..9153769ff23 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java @@ -68,8 +68,8 @@ public class BatchPluginRepositoryTest { public void shouldLoadPlugin() throws Exception { RemotePlugin checkstyle = new RemotePlugin("checkstyle", true); - PluginDownloader downloader = mock(PluginDownloader.class); - when(downloader.downloadPlugin(checkstyle)).thenReturn(fileFromCache("sonar-checkstyle-plugin-2.8.jar")); + DefaultPluginsReferential downloader = mock(DefaultPluginsReferential.class); + when(downloader.pluginFile(checkstyle)).thenReturn(fileFromCache("sonar-checkstyle-plugin-2.8.jar")); repository = new BatchPluginRepository(downloader, new Settings(), mode, new BatchPluginJarInstaller(cache)); @@ -86,9 +86,9 @@ public class BatchPluginRepositoryTest { RemotePlugin checkstyle = new RemotePlugin("checkstyle", true); RemotePlugin checkstyleExt = new RemotePlugin("checkstyleextensions", false); - PluginDownloader downloader = mock(PluginDownloader.class); - when(downloader.downloadPlugin(checkstyle)).thenReturn(fileFromCache("sonar-checkstyle-plugin-2.8.jar")); - when(downloader.downloadPlugin(checkstyleExt)).thenReturn(fileFromCache("sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar")); + DefaultPluginsReferential downloader = mock(DefaultPluginsReferential.class); + when(downloader.pluginFile(checkstyle)).thenReturn(fileFromCache("sonar-checkstyle-plugin-2.8.jar")); + when(downloader.pluginFile(checkstyleExt)).thenReturn(fileFromCache("sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar")); repository = new BatchPluginRepository(downloader, new Settings(), mode, new BatchPluginJarInstaller(cache)); @@ -106,9 +106,9 @@ public class BatchPluginRepositoryTest { RemotePlugin checkstyle = new RemotePlugin("checkstyle", true); RemotePlugin checkstyleExt = new RemotePlugin("checkstyleextensions", false); - PluginDownloader downloader = mock(PluginDownloader.class); - when(downloader.downloadPlugin(checkstyle)).thenReturn(fileFromCache("sonar-checkstyle-plugin-2.8.jar")); - when(downloader.downloadPlugin(checkstyleExt)).thenReturn(fileFromCache("sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar")); + DefaultPluginsReferential downloader = mock(DefaultPluginsReferential.class); + when(downloader.pluginFile(checkstyle)).thenReturn(fileFromCache("sonar-checkstyle-plugin-2.8.jar")); + when(downloader.pluginFile(checkstyleExt)).thenReturn(fileFromCache("sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar")); Settings settings = new Settings(); settings.setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle"); diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/PluginDownloaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/PluginDownloaderTest.java index 4c8120b903e..acb2a29c46c 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/PluginDownloaderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/PluginDownloaderTest.java @@ -23,7 +23,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; -import org.sonar.api.utils.SonarException; import org.sonar.core.plugins.RemotePlugin; import org.sonar.home.cache.FileCache; @@ -50,9 +49,9 @@ public class PluginDownloaderTest { FileCache cache = mock(FileCache.class); ServerClient server = mock(ServerClient.class); when(server.request("/deploy/plugins/index.txt")).thenReturn("checkstyle,true\nsqale,false"); - PluginDownloader downloader = new PluginDownloader(cache, server); + DefaultPluginsReferential downloader = new DefaultPluginsReferential(cache, server); - List plugins = downloader.downloadPluginIndex(); + List plugins = downloader.pluginList(); assertThat(plugins).hasSize(2); assertThat(plugins.get(0).getKey()).isEqualTo("checkstyle"); assertThat(plugins.get(0).isCore()).isTrue(); @@ -68,22 +67,22 @@ public class PluginDownloaderTest { when(cache.get(eq("checkstyle-plugin.jar"), eq("fakemd5_1"), any(FileCache.Downloader.class))).thenReturn(pluginJar); ServerClient server = mock(ServerClient.class); - PluginDownloader downloader = new PluginDownloader(cache, server); + DefaultPluginsReferential downloader = new DefaultPluginsReferential(cache, server); RemotePlugin plugin = new RemotePlugin("checkstyle", true) .setFile("checkstyle-plugin.jar", "fakemd5_1"); - File file = downloader.downloadPlugin(plugin); + File file = downloader.pluginFile(plugin); assertThat(file).isEqualTo(pluginJar); } @Test public void should_fail_to_get_plugin_index() throws Exception { - thrown.expect(SonarException.class); + thrown.expect(IllegalStateException.class); ServerClient server = mock(ServerClient.class); - doThrow(new SonarException()).when(server).request("/deploy/plugins/index.txt"); + doThrow(new IllegalStateException()).when(server).request("/deploy/plugins/index.txt"); - new PluginDownloader(mock(FileCache.class), server).downloadPluginIndex(); + new DefaultPluginsReferential(mock(FileCache.class), server).pluginList(); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/medium/Scan2MediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/medium/Scan2MediumTest.java new file mode 100644 index 00000000000..ae4a9ec02bb --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/medium/Scan2MediumTest.java @@ -0,0 +1,168 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.medium; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.junit.Test; +import org.sonar.api.measures.Metric; +import org.sonar.api.measures.MetricFinder; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rules.Rule; +import org.sonar.api.rules.RuleFinder; +import org.sonar.api.rules.RuleQuery; +import org.sonar.batch.bootstrap.PluginsReferential; +import org.sonar.batch.bootstrapper.Batch; +import org.sonar.batch.bootstrapper.EnvironmentInformation; +import org.sonar.batch.settings.SettingsReferential; +import org.sonar.core.plugins.RemotePlugin; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Scan2MediumTest { + + @Test + public void mediumTest() { + Batch batch = Batch.builder() + .setEnableLoggingConfiguration(true) + .addComponent(new EnvironmentInformation("mediumTest", "1.0")) + .addComponent(new MockSettingsReferential()) + .addComponent(new MockPluginsReferential()) + .addComponent(new MockMetricFinder()) + .addComponent(new MockRuleFinder()) + .setBootstrapProperties(ImmutableMap.of("sonar.analysis.mode", "sensor")) + .build(); + + batch.start(); + + // batch.executeTask(ImmutableMap.builder().put("sonar.task", "scan").build()); + + batch.stop(); + } + + private static class MockSettingsReferential implements SettingsReferential { + + private Map globalSettings = new HashMap(); + private Map> projectSettings = new HashMap>(); + + @Override + public Map globalSettings() { + return globalSettings; + } + + @Override + public Map projectSettings(String projectKey) { + return projectSettings.containsKey(projectKey) ? projectSettings.get(projectKey) : Collections.emptyMap(); + } + + } + + private static class MockPluginsReferential implements PluginsReferential { + + private List pluginList = new ArrayList(); + private Map pluginFiles = new HashMap(); + + @Override + public List pluginList() { + return pluginList; + } + + @Override + public File pluginFile(RemotePlugin remote) { + return pluginFiles.get(remote); + } + + } + + private static class MockMetricFinder implements MetricFinder { + + private Map metricsByKey = Maps.newLinkedHashMap(); + private Map metricsById = Maps.newLinkedHashMap(); + + @Override + public Metric findById(int metricId) { + return metricsById.get(metricId); + } + + @Override + public Metric findByKey(String key) { + return metricsByKey.get(key); + } + + @Override + public Collection findAll(List metricKeys) { + List result = Lists.newLinkedList(); + for (String metricKey : metricKeys) { + Metric metric = findByKey(metricKey); + if (metric != null) { + result.add(metric); + } + } + return result; + } + + @Override + public Collection findAll() { + return metricsByKey.values(); + } + + } + + private static class MockRuleFinder implements RuleFinder { + private BiMap rulesById = HashBiMap.create(); + private Map> rulesByRepoKeyAndRuleKey = Maps.newHashMap(); + + @Override + public Rule findById(int ruleId) { + return rulesById.get(ruleId); + } + + @Override + public Rule findByKey(String repositoryKey, String ruleKey) { + Map repository = rulesByRepoKeyAndRuleKey.get(repositoryKey); + return repository != null ? repository.get(ruleKey) : null; + } + + @Override + public Rule findByKey(RuleKey key) { + return findByKey(key.repository(), key.rule()); + } + + @Override + public Rule find(RuleQuery query) { + throw new UnsupportedOperationException(); + } + + @Override + public Collection findAll(RuleQuery query) { + throw new UnsupportedOperationException(); + } + } + +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java b/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java index 2b105f454fe..ee0d032f215 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java @@ -509,6 +509,11 @@ public interface CoreProperties { */ String ANALYSIS_MODE_INCREMENTAL = "incremental"; + /** + * @since 4.4 + */ + String ANALYSIS_MODE_SENSOR = "sensor"; + /** * @since 4.0 */ diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/rules/RuleFinder.java b/sonar-plugin-api/src/main/java/org/sonar/api/rules/RuleFinder.java index 60a1f2f83c3..6e93bf5b7fe 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/rules/RuleFinder.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/rules/RuleFinder.java @@ -19,12 +19,12 @@ */ package org.sonar.api.rules; +import org.sonar.api.ServerComponent; import org.sonar.api.rule.RuleKey; import org.sonar.api.task.TaskComponent; -import org.sonar.api.ServerComponent; - import javax.annotation.CheckForNull; + import java.util.Collection; /** @@ -44,6 +44,10 @@ public interface RuleFinder extends TaskComponent, ServerComponent { @CheckForNull Rule findByKey(RuleKey key); + /** + * @throw NonUniqueResultException if more than one result + */ + @CheckForNull Rule find(RuleQuery query); Collection findAll(RuleQuery query); -- 2.39.5