summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java14
-rw-r--r--server/sonar-ce/src/main/java/org/sonar/ce/platform/CECoreExtensionsInstaller.java32
-rw-r--r--server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java4
-rw-r--r--server/sonar-ce/src/test/java/org/sonar/ce/platform/CECoreExtensionsInstallerTest.java109
-rw-r--r--server/sonar-server-common/build.gradle34
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/l18n/ServerI18n.java58
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/l18n/ServerI18nTest.java93
-rw-r--r--server/sonar-server-common/src/test/resources/org/sonar/l10n/checkstyle.properties1
-rw-r--r--server/sonar-server-common/src/test/resources/org/sonar/l10n/checkstyle_fr.properties1
-rw-r--r--server/sonar-server-common/src/test/resources/org/sonar/l10n/core_fr.properties2
-rw-r--r--server/sonar-server-common/src/test/resources/org/sonar/l10n/coreext.properties1
-rw-r--r--server/sonar-server-common/src/test/resources/org/sonar/l10n/coreext_fr.properties1
-rw-r--r--server/sonar-server/build.gradle1
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/WebCoreExtensionsInstaller.java32
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java9
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java9
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java9
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/plugins/StaticResourcesServlet.java35
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/ui/PageRepository.java75
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/ui/page/CorePageDefinition.java37
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/ui/page/package-info.java24
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/WebCoreExtensionsInstallerTest.java109
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/plugins/StaticResourcesServletTest.java94
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/ui/PageRepositoryTest.java16
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/ui/ws/ComponentActionTest.java5
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/ui/ws/GlobalActionTest.java5
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/ui/ws/OrganizationActionTest.java5
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/ui/ws/SettingsActionTest.java5
-rw-r--r--settings.gradle1
-rw-r--r--sonar-core/src/main/java/org/sonar/core/extension/CoreExtension.java48
-rw-r--r--sonar-core/src/main/java/org/sonar/core/extension/CoreExtensionRepository.java57
-rw-r--r--sonar-core/src/main/java/org/sonar/core/extension/CoreExtensionRepositoryImpl.java70
-rw-r--r--sonar-core/src/main/java/org/sonar/core/extension/CoreExtensionsInstaller.java166
-rw-r--r--sonar-core/src/main/java/org/sonar/core/extension/CoreExtensionsLoader.java78
-rw-r--r--sonar-core/src/main/java/org/sonar/core/extension/ExtensionProviderSupport.java37
-rw-r--r--sonar-core/src/main/java/org/sonar/core/extension/package-info.java24
-rw-r--r--sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java14
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java20
-rw-r--r--sonar-core/src/test/java/org/sonar/core/extension/CoreExtensionRepositoryImplTest.java177
-rw-r--r--sonar-core/src/test/java/org/sonar/core/extension/CoreExtensionsInstallerTest.java424
-rw-r--r--sonar-core/src/test/java/org/sonar/core/extension/CoreExtensionsLoaderTest.java98
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/GlobalContainer.java11
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/extension/ScannerCoreExtensionsInstaller.java32
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/extension/package-info.java23
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java13
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java25
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/task/TaskContainer.java21
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/extension/ScannerCoreExtensionsInstallerTest.java109
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/ProjectScanContainerTest.java3
49 files changed, 2161 insertions, 110 deletions
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
index e8ef7188e6a..bfc640055d9 100644
--- a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
+++ b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
@@ -52,6 +52,7 @@ import org.sonar.ce.cleaning.CeCleaningModule;
import org.sonar.ce.db.ReadOnlyPropertiesDao;
import org.sonar.ce.log.CeProcessLogging;
import org.sonar.ce.notification.ReportAnalysisFailureNotificationModule;
+import org.sonar.ce.platform.CECoreExtensionsInstaller;
import org.sonar.ce.platform.ComputeEngineExtensionInstaller;
import org.sonar.ce.queue.CeQueueCleaner;
import org.sonar.ce.queue.PurgeCeActivities;
@@ -61,7 +62,6 @@ import org.sonar.ce.taskprocessor.CeTaskProcessorModule;
import org.sonar.ce.user.CeUserSession;
import org.sonar.core.component.DefaultResourceTypes;
import org.sonar.core.config.CorePropertyDefinitions;
-import org.sonar.core.i18n.DefaultI18n;
import org.sonar.core.i18n.RuleI18nManager;
import org.sonar.core.platform.ComponentContainer;
import org.sonar.core.platform.Module;
@@ -89,6 +89,9 @@ import org.sonar.server.debt.DebtRulesXMLImporter;
import org.sonar.server.es.EsModule;
import org.sonar.server.es.ProjectIndexersImpl;
import org.sonar.server.event.NewAlerts;
+import org.sonar.core.extension.CoreExtensionRepositoryImpl;
+import org.sonar.core.extension.CoreExtensionsInstaller;
+import org.sonar.core.extension.CoreExtensionsLoader;
import org.sonar.server.favorite.FavoriteUpdater;
import org.sonar.server.issue.IssueFieldsSetter;
import org.sonar.server.issue.index.IssueIndex;
@@ -104,6 +107,7 @@ import org.sonar.server.issue.notification.NewIssuesNotificationDispatcher;
import org.sonar.server.issue.notification.NewIssuesNotificationFactory;
import org.sonar.server.issue.workflow.FunctionExecutor;
import org.sonar.server.issue.workflow.IssueWorkflow;
+import org.sonar.server.l18n.ServerI18n;
import org.sonar.server.measure.index.ProjectMeasuresIndex;
import org.sonar.server.measure.index.ProjectMeasuresIndexer;
import org.sonar.server.metric.CoreCustomMetrics;
@@ -191,6 +195,7 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
ComponentContainer level2 = this.level1.createChild();
populateLevel2(level2);
configureFromModules(level2);
+ level2.getComponentByType(CoreExtensionsLoader.class).load();
level2.startComponents();
ComponentContainer level3 = level2.createChild();
@@ -204,6 +209,8 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
configureFromModules(this.level4);
ServerExtensionInstaller extensionInstaller = this.level4.getComponentByType(ServerExtensionInstaller.class);
extensionInstaller.installExtensions(this.level4);
+ CoreExtensionsInstaller coreExtensionsInstaller = this.level4.getComponentByType(CECoreExtensionsInstaller.class);
+ coreExtensionsInstaller.install(this.level4, t -> true);
this.level4.startComponents();
startupTasks();
@@ -305,9 +312,11 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
CePluginRepository.class,
InstalledPluginReferentialFactory.class,
ComputeEngineExtensionInstaller.class,
+ CoreExtensionRepositoryImpl.class,
+ CoreExtensionsLoader.class,
// depends on plugins
- DefaultI18n.class, // used by RuleI18nManager
+ ServerI18n.class, // used by RuleI18nManager
RuleI18nManager.class, // used by DebtRulesXMLImporter
Durations.class // used in Web Services and DebtCalculator
);
@@ -424,6 +433,7 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
ServerLogging.class,
// privileged plugins
+ CECoreExtensionsInstaller.class,
PrivilegedPluginsBootstraper.class,
PrivilegedPluginsStopper.class,
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/platform/CECoreExtensionsInstaller.java b/server/sonar-ce/src/main/java/org/sonar/ce/platform/CECoreExtensionsInstaller.java
new file mode 100644
index 00000000000..607726cf88f
--- /dev/null
+++ b/server/sonar-ce/src/main/java/org/sonar/ce/platform/CECoreExtensionsInstaller.java
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.ce.platform;
+
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.core.extension.CoreExtensionRepository;
+import org.sonar.core.extension.CoreExtensionsInstaller;
+
+@ComputeEngineSide
+public class CECoreExtensionsInstaller extends CoreExtensionsInstaller {
+ public CECoreExtensionsInstaller(SonarRuntime sonarRuntime, CoreExtensionRepository coreExtensionRepository) {
+ super(sonarRuntime, coreExtensionRepository, ComputeEngineSide.class);
+ }
+}
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
index e9c72239901..b079bae241e 100644
--- a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
+++ b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
@@ -94,7 +94,7 @@ public class ComputeEngineContainerImplTest {
assertThat(picoContainer.getComponentAdapters())
.hasSize(
CONTAINER_ITSELF
- + 83 // level 4
+ + 84 // level 4
+ 21 // content of QualityGateModule
+ 6 // content of CeConfigurationModule
+ 4 // content of CeQueueModule
@@ -114,7 +114,7 @@ public class ComputeEngineContainerImplTest {
assertThat(picoContainer.getParent().getParent().getComponentAdapters()).hasSize(
CONTAINER_ITSELF
+ 16 // MigrationConfigurationModule
- + 17 // level 2
+ + 19 // level 2
);
assertThat(picoContainer.getParent().getParent().getParent().getComponentAdapters()).hasSize(
COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/platform/CECoreExtensionsInstallerTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/platform/CECoreExtensionsInstallerTest.java
new file mode 100644
index 00000000000..df075842c66
--- /dev/null
+++ b/server/sonar-ce/src/test/java/org/sonar/ce/platform/CECoreExtensionsInstallerTest.java
@@ -0,0 +1,109 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.ce.platform;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.stream.Stream;
+import org.junit.Test;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.batch.ScannerSide;
+import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.server.ServerSide;
+import org.sonar.core.extension.CoreExtension;
+import org.sonar.core.extension.CoreExtensionRepository;
+import org.sonar.core.platform.ComponentContainer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class CECoreExtensionsInstallerTest {
+ private SonarRuntime sonarRuntime = mock(SonarRuntime.class);
+ private CoreExtensionRepository coreExtensionRepository = mock(CoreExtensionRepository.class);
+
+ private CECoreExtensionsInstaller underTest = new CECoreExtensionsInstaller(sonarRuntime, coreExtensionRepository);
+
+ @Test
+ public void install_only_adds_ComputeEngineSide_annotated_extension_to_container() {
+ when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(Stream.of(
+ new CoreExtension() {
+ @Override
+ public String getName() {
+ return "foo";
+ }
+
+ @Override
+ public void load(Context context) {
+ context.addExtensions(CeClass.class, ScannerClass.class, WebServerClass.class,
+ NoAnnotationClass.class, OtherAnnotationClass.class, MultipleAnnotationClass.class);
+ }
+ }
+ ));
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.install(container, t -> true);
+
+ assertThat(container.getPicoContainer().getComponentAdapters())
+ .hasSize(ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 2);
+ assertThat(container.getComponentByType(CeClass.class)).isNotNull();
+ assertThat(container.getComponentByType(MultipleAnnotationClass.class)).isNotNull();
+ }
+
+ @ComputeEngineSide
+ public static final class CeClass {
+
+ }
+
+ @ServerSide
+ public static final class WebServerClass {
+
+ }
+
+ @ScannerSide
+ public static final class ScannerClass {
+
+ }
+
+ @ServerSide
+ @ComputeEngineSide
+ @ScannerSide
+ public static final class MultipleAnnotationClass {
+
+ }
+
+ public static final class NoAnnotationClass {
+
+ }
+
+ @DarkSide
+ public static final class OtherAnnotationClass {
+
+ }
+
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface DarkSide {
+ }
+}
diff --git a/server/sonar-server-common/build.gradle b/server/sonar-server-common/build.gradle
new file mode 100644
index 00000000000..12184b6cdaa
--- /dev/null
+++ b/server/sonar-server-common/build.gradle
@@ -0,0 +1,34 @@
+sonarqube {
+ properties {
+ property 'sonar.projectName', "${projectTitle} :: Server :: Common"
+ }
+}
+
+dependencies {
+ // please keep the list grouped by configuration and ordered by name
+
+ compile 'com.google.guava:guava'
+ compile 'org.slf4j:slf4j-api'
+
+ compile project(':sonar-core')
+ compileOnly project(path: ':sonar-plugin-api')
+
+ compileOnly 'com.google.code.findbugs:jsr305'
+
+ testCompile 'com.google.code.findbugs:jsr305'
+ testCompile 'com.tngtech.java:junit-dataprovider'
+ testCompile 'junit:junit'
+ testCompile 'org.assertj:assertj-core'
+ testCompile 'org.mockito:mockito-core'
+}
+
+//artifactoryPublish.skip = false
+
+// Used by core plugins
+publishing {
+ publications {
+ mavenJava(MavenPublication) {
+ from components.java
+ }
+ }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/l18n/ServerI18n.java b/server/sonar-server-common/src/main/java/org/sonar/server/l18n/ServerI18n.java
new file mode 100644
index 00000000000..0f5c55663d6
--- /dev/null
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/l18n/ServerI18n.java
@@ -0,0 +1,58 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.l18n;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.System2;
+import org.sonar.core.i18n.DefaultI18n;
+import org.sonar.core.platform.PluginRepository;
+import org.sonar.core.extension.CoreExtension;
+import org.sonar.core.extension.CoreExtensionRepository;
+
+/**
+ * Subclass of {@link DefaultI18n} which supports Core Extensions.
+ */
+@ServerSide
+@ComputeEngineSide
+public class ServerI18n extends DefaultI18n {
+ private final CoreExtensionRepository coreExtensionRepository;
+
+ public ServerI18n(PluginRepository pluginRepository, System2 system2, CoreExtensionRepository coreExtensionRepository) {
+ super(pluginRepository, system2);
+ this.coreExtensionRepository = coreExtensionRepository;
+ }
+
+ @Override
+ protected void initialize() {
+ super.initialize();
+
+ coreExtensionRepository.loadedCoreExtensions()
+ .map(CoreExtension::getName)
+ .forEach(this::initPlugin);
+ }
+
+ @VisibleForTesting
+ @Override
+ protected void doStart(ClassLoader classloader) {
+ super.doStart(classloader);
+ }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/l18n/ServerI18nTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/l18n/ServerI18nTest.java
new file mode 100644
index 00000000000..608b2f3fde7
--- /dev/null
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/l18n/ServerI18nTest.java
@@ -0,0 +1,93 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.l18n;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Stream;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.utils.internal.TestSystem2;
+import org.sonar.core.extension.CoreExtension;
+import org.sonar.core.extension.CoreExtensionRepository;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.platform.PluginRepository;
+
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ServerI18nTest {
+
+ private TestSystem2 system2 = new TestSystem2();
+ private ServerI18n underTest;
+
+ @Before
+ public void before() {
+ PluginRepository pluginRepository = mock(PluginRepository.class);
+ List<PluginInfo> plugins = singletonList(newPlugin("checkstyle"));
+ when(pluginRepository.getPluginInfos()).thenReturn(plugins);
+
+ CoreExtensionRepository coreExtensionRepository = mock(CoreExtensionRepository.class);
+ Stream<CoreExtension> coreExtensions = Stream.of(newCoreExtension("coreext"), newCoreExtension("othercorext"));
+ when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(coreExtensions);
+
+ underTest = new ServerI18n(pluginRepository, system2, coreExtensionRepository);
+ underTest.doStart(getClass().getClassLoader());
+ }
+
+ @Test
+ public void get_english_labels() {
+ assertThat(underTest.message(Locale.ENGLISH, "any", null)).isEqualTo("Any");
+ assertThat(underTest.message(Locale.ENGLISH, "coreext.rule1.name", null)).isEqualTo("Rule one");
+ }
+
+ @Test
+ public void get_english_labels_when_default_locale_is_not_english() {
+ Locale defaultLocale = Locale.getDefault();
+ try {
+ Locale.setDefault(Locale.FRENCH);
+ assertThat(underTest.message(Locale.ENGLISH, "any", null)).isEqualTo("Any");
+ assertThat(underTest.message(Locale.ENGLISH, "coreext.rule1.name", null)).isEqualTo("Rule one");
+ } finally {
+ Locale.setDefault(defaultLocale);
+ }
+ }
+
+ @Test
+ public void get_labels_from_french_pack() {
+ assertThat(underTest.message(Locale.FRENCH, "coreext.rule1.name", null)).isEqualTo("Rule un");
+ assertThat(underTest.message(Locale.FRENCH, "any", null)).isEqualTo("Tous");
+ }
+
+ private static PluginInfo newPlugin(String key) {
+ PluginInfo plugin = mock(PluginInfo.class);
+ when(plugin.getKey()).thenReturn(key);
+ return plugin;
+ }
+
+ private static CoreExtension newCoreExtension(String name) {
+ CoreExtension res = mock(CoreExtension.class);
+ when(res.getName()).thenReturn(name);
+ return res;
+ }
+
+}
diff --git a/server/sonar-server-common/src/test/resources/org/sonar/l10n/checkstyle.properties b/server/sonar-server-common/src/test/resources/org/sonar/l10n/checkstyle.properties
new file mode 100644
index 00000000000..10fa9295c44
--- /dev/null
+++ b/server/sonar-server-common/src/test/resources/org/sonar/l10n/checkstyle.properties
@@ -0,0 +1 @@
+checkstyle.rule1.name=Rule one
diff --git a/server/sonar-server-common/src/test/resources/org/sonar/l10n/checkstyle_fr.properties b/server/sonar-server-common/src/test/resources/org/sonar/l10n/checkstyle_fr.properties
new file mode 100644
index 00000000000..b2fc8f9651f
--- /dev/null
+++ b/server/sonar-server-common/src/test/resources/org/sonar/l10n/checkstyle_fr.properties
@@ -0,0 +1 @@
+checkstyle.rule1.name=Rule un \ No newline at end of file
diff --git a/server/sonar-server-common/src/test/resources/org/sonar/l10n/core_fr.properties b/server/sonar-server-common/src/test/resources/org/sonar/l10n/core_fr.properties
new file mode 100644
index 00000000000..9b473d07f5c
--- /dev/null
+++ b/server/sonar-server-common/src/test/resources/org/sonar/l10n/core_fr.properties
@@ -0,0 +1,2 @@
+any=Tous
+empty=
diff --git a/server/sonar-server-common/src/test/resources/org/sonar/l10n/coreext.properties b/server/sonar-server-common/src/test/resources/org/sonar/l10n/coreext.properties
new file mode 100644
index 00000000000..e84fc7ce875
--- /dev/null
+++ b/server/sonar-server-common/src/test/resources/org/sonar/l10n/coreext.properties
@@ -0,0 +1 @@
+coreext.rule1.name=Rule one
diff --git a/server/sonar-server-common/src/test/resources/org/sonar/l10n/coreext_fr.properties b/server/sonar-server-common/src/test/resources/org/sonar/l10n/coreext_fr.properties
new file mode 100644
index 00000000000..0cd4598e2fb
--- /dev/null
+++ b/server/sonar-server-common/src/test/resources/org/sonar/l10n/coreext_fr.properties
@@ -0,0 +1 @@
+coreext.rule1.name=Rule un
diff --git a/server/sonar-server/build.gradle b/server/sonar-server/build.gradle
index 78bf82df662..6b4fd8a88a6 100644
--- a/server/sonar-server/build.gradle
+++ b/server/sonar-server/build.gradle
@@ -51,6 +51,7 @@ dependencies {
compile project(':server:sonar-db-migration')
compile project(':server:sonar-plugin-bridge')
compile project(':server:sonar-process')
+ compile project(':server:sonar-server-common')
compile project(':sonar-core')
compile project(':sonar-scanner-protocol')
compile(project(':sonar-markdown')) {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/WebCoreExtensionsInstaller.java b/server/sonar-server/src/main/java/org/sonar/server/platform/WebCoreExtensionsInstaller.java
new file mode 100644
index 00000000000..7ab01bc96f9
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/WebCoreExtensionsInstaller.java
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.platform;
+
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.server.ServerSide;
+import org.sonar.core.extension.CoreExtensionRepository;
+import org.sonar.core.extension.CoreExtensionsInstaller;
+
+@ServerSide
+public class WebCoreExtensionsInstaller extends CoreExtensionsInstaller {
+ public WebCoreExtensionsInstaller(SonarRuntime sonarRuntime, CoreExtensionRepository coreExtensionRepository) {
+ super(sonarRuntime, coreExtensionRepository, ServerSide.class);
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java
index ca1ef1020e5..b32ad3d14e9 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java
@@ -20,10 +20,12 @@
package org.sonar.server.platform.platformlevel;
import org.sonar.api.utils.Durations;
-import org.sonar.core.i18n.DefaultI18n;
import org.sonar.core.i18n.RuleI18nManager;
import org.sonar.core.platform.PluginClassloaderFactory;
import org.sonar.core.platform.PluginLoader;
+import org.sonar.core.extension.CoreExtensionRepositoryImpl;
+import org.sonar.core.extension.CoreExtensionsLoader;
+import org.sonar.server.l18n.ServerI18n;
import org.sonar.server.platform.DatabaseServerCompatibility;
import org.sonar.server.platform.DefaultServerUpgradeStatus;
import org.sonar.server.platform.StartupMetadataProvider;
@@ -70,9 +72,11 @@ public class PlatformLevel2 extends PlatformLevel {
PluginClassloaderFactory.class,
InstalledPluginReferentialFactory.class,
WebServerExtensionInstaller.class,
+ CoreExtensionRepositoryImpl.class,
+ CoreExtensionsLoader.class,
// depends on plugins
- DefaultI18n.class,
+ ServerI18n.class,
RuleI18nManager.class);
// Migration state must be kept at level2 to survive moving in and then out of safe mode
@@ -95,6 +99,7 @@ public class PlatformLevel2 extends PlatformLevel {
public PlatformLevel start() {
// ensuring the HistoryTable exists must be the first thing done when this level is started
getOptional(MigrationHistoryTable.class).ifPresent(MigrationHistoryTable::start);
+ get(CoreExtensionsLoader.class).load();
return super.start();
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
index e9988fbc7c9..928da0fd9fa 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
@@ -32,6 +32,7 @@ import org.sonar.ce.CeModule;
import org.sonar.ce.notification.ReportAnalysisFailureNotificationModule;
import org.sonar.ce.settings.ProjectConfigurationFactory;
import org.sonar.core.component.DefaultResourceTypes;
+import org.sonar.core.platform.ComponentContainer;
import org.sonar.core.timemachine.Periods;
import org.sonar.server.authentication.AuthenticationModule;
import org.sonar.server.authentication.LogOAuthWarning;
@@ -66,6 +67,7 @@ import org.sonar.server.es.metadata.EsDbCompatibilityImpl;
import org.sonar.server.es.metadata.MetadataIndex;
import org.sonar.server.es.metadata.MetadataIndexDefinition;
import org.sonar.server.event.NewAlerts;
+import org.sonar.core.extension.CoreExtensionsInstaller;
import org.sonar.server.favorite.FavoriteModule;
import org.sonar.server.health.NodeHealthModule;
import org.sonar.server.issue.AddTagsAction;
@@ -115,6 +117,7 @@ import org.sonar.server.platform.ClusterVerification;
import org.sonar.server.platform.PersistentSettings;
import org.sonar.server.platform.ServerLogging;
import org.sonar.server.platform.SettingsChangeNotifier;
+import org.sonar.server.platform.WebCoreExtensionsInstaller;
import org.sonar.server.platform.monitoring.WebSystemInfoModule;
import org.sonar.server.platform.web.WebPagesFilter;
import org.sonar.server.platform.web.requestid.HttpRequestIdModule;
@@ -544,6 +547,7 @@ public class PlatformLevel4 extends PlatformLevel {
ProjectBadgesWsModule.class,
// privileged plugins
+ WebCoreExtensionsInstaller.class,
PrivilegedPluginsBootstraper.class,
PrivilegedPluginsStopper.class,
@@ -590,7 +594,10 @@ public class PlatformLevel4 extends PlatformLevel {
@Override
public PlatformLevel start() {
ServerExtensionInstaller extensionInstaller = get(ServerExtensionInstaller.class);
- extensionInstaller.installExtensions(getContainer());
+ CoreExtensionsInstaller coreExtensionsInstaller = get(WebCoreExtensionsInstaller.class);
+ ComponentContainer container = getContainer();
+ extensionInstaller.installExtensions(container);
+ coreExtensionsInstaller.install(container, t -> true);
super.start();
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java
index ae94ead30eb..16bcba75ca9 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java
@@ -36,6 +36,7 @@ import org.sonar.core.platform.PluginInfo;
import org.sonar.core.platform.PluginRepository;
import static java.util.Objects.requireNonNull;
+import static org.sonar.core.extension.ExtensionProviderSupport.isExtensionProvider;
/**
* Loads the plugins server extensions and injects them to DI container
@@ -121,12 +122,4 @@ public abstract class ServerExtensionInstaller {
return null;
}
- static boolean isExtensionProvider(Object extension) {
- return isType(extension, ExtensionProvider.class) || extension instanceof ExtensionProvider;
- }
-
- static boolean isType(Object extension, Class extensionClass) {
- Class clazz = extension instanceof Class ? (Class) extension : extension.getClass();
- return extensionClass.isAssignableFrom(clazz);
- }
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/StaticResourcesServlet.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/StaticResourcesServlet.java
index 3e5ad22e85b..2017eb7c693 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/StaticResourcesServlet.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/StaticResourcesServlet.java
@@ -33,6 +33,7 @@ import org.apache.commons.lang.StringUtils;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.core.platform.PluginRepository;
+import org.sonar.core.extension.CoreExtensionRepository;
import org.sonar.server.platform.Platform;
import org.sonarqube.ws.MediaTypes;
@@ -63,21 +64,25 @@ public class StaticResourcesServlet extends HttpServlet {
InputStream in = null;
OutputStream out = null;
try {
+ CoreExtensionRepository coreExtensionRepository = system.getCoreExtensionRepository();
PluginRepository pluginRepository = system.getPluginRepository();
- if (!pluginRepository.hasPlugin(pluginKey)) {
+
+ boolean coreExtension = coreExtensionRepository.isInstalled(pluginKey);
+ if (!coreExtension && !pluginRepository.hasPlugin(pluginKey)) {
silentlySendError(response, SC_NOT_FOUND);
return;
}
- in = system.openResourceStream(pluginKey, resource, pluginRepository);
- if (in != null) {
- // mime type must be set before writing response body
- completeContentType(response, resource);
- out = response.getOutputStream();
- IOUtils.copy(in, out);
- } else {
+ in = coreExtension ? system.openCoreExtensionResourceStream(resource) : system.openPluginResourceStream(pluginKey, resource, pluginRepository);
+ if (in == null) {
silentlySendError(response, SC_NOT_FOUND);
+ return;
}
+
+ // mime type must be set before writing response body
+ completeContentType(response, resource);
+ out = response.getOutputStream();
+ IOUtils.copy(in, out);
} catch (ClientAbortException e) {
LOG.trace("Client canceled loading resource [{}] from plugin [{}]: {}", resource, pluginKey, e);
} catch (Exception e) {
@@ -128,9 +133,19 @@ public class StaticResourcesServlet extends HttpServlet {
return Platform.getInstance().getContainer().getComponentByType(PluginRepository.class);
}
+ CoreExtensionRepository getCoreExtensionRepository() {
+ return Platform.getInstance().getContainer().getComponentByType(CoreExtensionRepository.class);
+ }
+
+ @CheckForNull
+ InputStream openPluginResourceStream(String pluginKey, String resource, PluginRepository pluginRepository) throws Exception {
+ ClassLoader pluginClassLoader = pluginRepository.getPluginInstance(pluginKey).getClass().getClassLoader();
+ return pluginClassLoader.getResourceAsStream(resource);
+ }
+
@CheckForNull
- InputStream openResourceStream(String pluginKey, String resource, PluginRepository pluginRepository) throws Exception {
- return pluginRepository.getPluginInstance(pluginKey).getClass().getClassLoader().getResourceAsStream(resource);
+ InputStream openCoreExtensionResourceStream(String resource) throws Exception {
+ return getClass().getClassLoader().getResourceAsStream(resource);
}
boolean isCommitted(HttpServletResponse response) {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/ui/PageRepository.java b/server/sonar-server/src/main/java/org/sonar/server/ui/PageRepository.java
index 0929a36d438..f16dc84394a 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/ui/PageRepository.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/ui/PageRepository.java
@@ -20,10 +20,8 @@
package org.sonar.server.ui;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableList;
-import java.util.Collections;
import java.util.List;
-import java.util.function.Consumer;
+import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.sonar.api.Startable;
import org.sonar.api.server.ServerSide;
@@ -33,8 +31,11 @@ import org.sonar.api.web.page.Page.Qualifier;
import org.sonar.api.web.page.Page.Scope;
import org.sonar.api.web.page.PageDefinition;
import org.sonar.core.platform.PluginRepository;
+import org.sonar.core.extension.CoreExtensionRepository;
+import org.sonar.server.ui.page.CorePageDefinition;
import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.ImmutableList.copyOf;
import static java.util.Collections.emptyList;
import static java.util.Comparator.comparing;
import static java.util.Objects.requireNonNull;
@@ -46,26 +47,66 @@ import static org.sonar.core.util.stream.MoreCollectors.toList;
@ServerSide
public class PageRepository implements Startable {
private final PluginRepository pluginRepository;
+ private final CoreExtensionRepository coreExtensionRepository;
private final List<PageDefinition> definitions;
+ private final List<CorePageDefinition> corePageDefinitions;
private List<Page> pages;
- public PageRepository(PluginRepository pluginRepository) {
+ /**
+ * Used by Pico when there is no {@link PageDefinition}.
+ */
+ public PageRepository(PluginRepository pluginRepository, CoreExtensionRepository coreExtensionRepository) {
this.pluginRepository = pluginRepository;
+ this.coreExtensionRepository = coreExtensionRepository;
// in case there's no page definition
- this.definitions = Collections.emptyList();
+ this.definitions = emptyList();
+ this.corePageDefinitions = emptyList();
}
- public PageRepository(PluginRepository pluginRepository, PageDefinition[] pageDefinitions) {
+ /**
+ * Used by Pico when there is only {@link PageDefinition} provided both by Plugin(s).
+ */
+ public PageRepository(PluginRepository pluginRepository, CoreExtensionRepository coreExtensionRepository,
+ PageDefinition[] pageDefinitions) {
this.pluginRepository = pluginRepository;
- this.definitions = ImmutableList.copyOf(pageDefinitions);
+ this.coreExtensionRepository = coreExtensionRepository;
+ this.definitions = copyOf(pageDefinitions);
+ this.corePageDefinitions = emptyList();
+ }
+
+ /**
+ * Used by Pico when there is only {@link PageDefinition} provided both by Core Extension(s).
+ */
+ public PageRepository(PluginRepository pluginRepository, CoreExtensionRepository coreExtensionRepository,
+ CorePageDefinition[] corePageDefinitions) {
+ this.pluginRepository = pluginRepository;
+ this.coreExtensionRepository = coreExtensionRepository;
+ this.definitions = emptyList();
+ this.corePageDefinitions = copyOf(corePageDefinitions);
+ }
+
+ /**
+ * Used by Pico when there is {@link PageDefinition} provided both by Core Extension(s) and Plugin(s).
+ */
+ public PageRepository(PluginRepository pluginRepository, CoreExtensionRepository coreExtensionRepository,
+ PageDefinition[] pageDefinitions, CorePageDefinition[] corePageDefinitions) {
+ this.pluginRepository = pluginRepository;
+ this.coreExtensionRepository = coreExtensionRepository;
+ this.definitions = copyOf(pageDefinitions);
+ this.corePageDefinitions = copyOf(corePageDefinitions);
}
@Override
public void start() {
Context context = new Context();
definitions.forEach(definition -> definition.define(context));
- pages = context.getPages().stream()
- .peek(checkPluginExists())
+ Context coreContext = new Context();
+ corePageDefinitions.stream()
+ .map(CorePageDefinition::getPageDefinition)
+ .forEach(definition -> definition.define(coreContext));
+ pages = Stream.concat(
+ context.getPages().stream().peek(this::checkPluginExists),
+ coreContext.getPages().stream().peek(this::checkCoreExtensionExists))
.sorted(comparing(Page::getKey))
.collect(toList());
}
@@ -101,12 +142,16 @@ public class PageRepository implements Startable {
return requireNonNull(pages, "Pages haven't been initialized yet");
}
- private Consumer<Page> checkPluginExists() {
- return page -> {
- String pluginKey = page.getPluginKey();
- boolean pluginExists = pluginRepository.hasPlugin(pluginKey);
- checkState(pluginExists, "Page '%s' references plugin '%s' that does not exist", page.getName(), pluginKey);
- };
+ private void checkPluginExists(Page page) {
+ String pluginKey = page.getPluginKey();
+ checkState(pluginRepository.hasPlugin(pluginKey),
+ "Page '%s' references plugin '%s' that does not exist", page.getName(), pluginKey);
+ }
+
+ private void checkCoreExtensionExists(Page page) {
+ String coreExtensionName = page.getPluginKey();
+ checkState(coreExtensionRepository.isInstalled(coreExtensionName),
+ "Page '%s' references Core Extension '%s' which is not installed", page.getName(), coreExtensionName);
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/ui/page/CorePageDefinition.java b/server/sonar-server/src/main/java/org/sonar/server/ui/page/CorePageDefinition.java
new file mode 100644
index 00000000000..595cd0826e6
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/ui/page/CorePageDefinition.java
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.ui.page;
+
+import org.sonar.api.ExtensionPoint;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.web.page.PageDefinition;
+
+/**
+ * This class must be used by core extensions to declare {@link PageDefinition}(s).
+ * <p>
+ * This allows to distinguish {@link PageDefinition} provided by plugins from those provided by Core Extensions and
+ * apply the appropriate security and consistency checks.
+ */
+@ServerSide
+@ExtensionPoint
+public interface CorePageDefinition {
+
+ PageDefinition getPageDefinition();
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/ui/page/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/ui/page/package-info.java
new file mode 100644
index 00000000000..239574f4451
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/ui/page/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.ui.page;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/WebCoreExtensionsInstallerTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/WebCoreExtensionsInstallerTest.java
new file mode 100644
index 00000000000..60eee2cce4d
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/WebCoreExtensionsInstallerTest.java
@@ -0,0 +1,109 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.platform;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.stream.Stream;
+import org.junit.Test;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.batch.ScannerSide;
+import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.server.ServerSide;
+import org.sonar.core.extension.CoreExtension;
+import org.sonar.core.extension.CoreExtensionRepository;
+import org.sonar.core.platform.ComponentContainer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class WebCoreExtensionsInstallerTest {
+ private SonarRuntime sonarRuntime = mock(SonarRuntime.class);
+ private CoreExtensionRepository coreExtensionRepository = mock(CoreExtensionRepository.class);
+
+ private WebCoreExtensionsInstaller underTest = new WebCoreExtensionsInstaller(sonarRuntime, coreExtensionRepository);
+
+ @Test
+ public void install_only_adds_ServerSide_annotated_extension_to_container() {
+ when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(Stream.of(
+ new CoreExtension() {
+ @Override
+ public String getName() {
+ return "foo";
+ }
+
+ @Override
+ public void load(Context context) {
+ context.addExtensions(CeClass.class, ScannerClass.class, WebServerClass.class,
+ NoAnnotationClass.class, OtherAnnotationClass.class, MultipleAnnotationClass.class);
+ }
+ }));
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.install(container, t -> true);
+
+ assertThat(container.getPicoContainer().getComponentAdapters())
+ .hasSize(ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 2);
+ assertThat(container.getComponentByType(WebServerClass.class)).isNotNull();
+ assertThat(container.getComponentByType(MultipleAnnotationClass.class)).isNotNull();
+ }
+
+ @ComputeEngineSide
+ public static final class CeClass {
+
+ }
+
+ @ServerSide
+ public static final class WebServerClass {
+
+ }
+
+ @ScannerSide
+ public static final class ScannerClass {
+
+ }
+
+ @ServerSide
+ @ComputeEngineSide
+ @ScannerSide
+ public static final class MultipleAnnotationClass {
+
+ }
+
+ public static final class NoAnnotationClass {
+
+ }
+
+ @DarkSide
+ public static final class OtherAnnotationClass {
+
+ }
+
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface DarkSide {
+ }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/StaticResourcesServletTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/StaticResourcesServletTest.java
index fad36be8214..d3b79457c9f 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/plugins/StaticResourcesServletTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/StaticResourcesServletTest.java
@@ -40,6 +40,7 @@ import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;
import org.sonar.core.platform.PluginInfo;
import org.sonar.core.platform.PluginRepository;
+import org.sonar.core.extension.CoreExtensionRepository;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -52,7 +53,8 @@ public class StaticResourcesServletTest {
private Server jetty;
private PluginRepository pluginRepository = mock(PluginRepository.class);
- private TestSystem system = new TestSystem(pluginRepository);
+ private CoreExtensionRepository coreExtensionRepository = mock(CoreExtensionRepository.class);
+ private TestSystem system = new TestSystem(pluginRepository, coreExtensionRepository);
@Before
public void setUp() throws Exception {
@@ -82,31 +84,58 @@ public class StaticResourcesServletTest {
@Test
public void return_content_if_exists_in_installed_plugin() throws Exception {
- system.answer = IOUtils.toInputStream("bar");
+ system.pluginStream = IOUtils.toInputStream("bar");
when(pluginRepository.hasPlugin("myplugin")).thenReturn(true);
Response response = call("/static/myplugin/foo.txt");
assertThat(response.isSuccessful()).isTrue();
assertThat(response.body().string()).isEqualTo("bar");
- assertThat(system.calledResource).isEqualTo("static/foo.txt");
+ assertThat(system.pluginResource).isEqualTo("static/foo.txt");
}
@Test
public void return_content_of_folder_of_installed_plugin() throws Exception {
- system.answer = IOUtils.toInputStream("bar");
+ system.pluginStream = IOUtils.toInputStream("bar");
when(pluginRepository.hasPlugin("myplugin")).thenReturn(true);
Response response = call("/static/myplugin/foo/bar.txt");
assertThat(response.isSuccessful()).isTrue();
assertThat(response.body().string()).isEqualTo("bar");
- assertThat(system.calledResource).isEqualTo("static/foo/bar.txt");
+ assertThat(system.pluginResource).isEqualTo("static/foo/bar.txt");
+ }
+
+ @Test
+ public void return_content_of_folder_of_installed_core_extension() throws Exception {
+ system.coreExtensionStream = IOUtils.toInputStream("bar");
+ when(coreExtensionRepository.isInstalled("coreext")).thenReturn(true);
+
+ Response response = call("/static/coreext/foo/bar.txt");
+
+ assertThat(response.isSuccessful()).isTrue();
+ assertThat(response.body().string()).isEqualTo("bar");
+ assertThat(system.coreExtensionResource).isEqualTo("static/foo/bar.txt");
+ }
+
+ @Test
+ public void return_content_of_folder_of_installed_core_extension_over_installed_plugin_in_case_of_key_conflict() throws Exception {
+ system.coreExtensionStream = IOUtils.toInputStream("bar of plugin");
+ when(coreExtensionRepository.isInstalled("samekey")).thenReturn(true);
+ system.coreExtensionStream = IOUtils.toInputStream("bar of core extension");
+ when(coreExtensionRepository.isInstalled("samekey")).thenReturn(true);
+
+ Response response = call("/static/samekey/foo/bar.txt");
+
+ assertThat(response.isSuccessful()).isTrue();
+ assertThat(response.body().string()).isEqualTo("bar of core extension");
+ assertThat(system.pluginResource).isNull();
+ assertThat(system.coreExtensionResource).isEqualTo("static/foo/bar.txt");
}
@Test
public void mime_type_is_set_on_response() throws Exception {
- system.answer = IOUtils.toInputStream("bar");
+ system.pluginStream = IOUtils.toInputStream("bar");
when(pluginRepository.hasPlugin("myplugin")).thenReturn(true);
Response response = call("/static/myplugin/foo.css");
@@ -117,7 +146,7 @@ public class StaticResourcesServletTest {
@Test
public void return_404_if_resource_not_found_in_installed_plugin() throws Exception {
- system.answer = null;
+ system.pluginStream = null;
when(pluginRepository.hasPlugin("myplugin")).thenReturn(true);
Response response = call("/static/myplugin/foo.css");
@@ -129,7 +158,7 @@ public class StaticResourcesServletTest {
@Test
public void return_404_if_plugin_does_not_exist() throws Exception {
- system.answer = null;
+ system.pluginStream = null;
when(pluginRepository.hasPlugin("myplugin")).thenReturn(false);
Response response = call("/static/myplugin/foo.css");
@@ -141,7 +170,7 @@ public class StaticResourcesServletTest {
@Test
public void return_resource_if_exists_in_requested_plugin() throws Exception {
- system.answer = IOUtils.toInputStream("bar");
+ system.pluginStream = IOUtils.toInputStream("bar");
when(pluginRepository.hasPlugin("myplugin")).thenReturn(true);
when(pluginRepository.getPluginInfo("myplugin")).thenReturn(new PluginInfo("myplugin"));
@@ -155,7 +184,7 @@ public class StaticResourcesServletTest {
@Test
public void do_not_fail_nor_log_ERROR_when_response_is_already_committed_and_plugin_does_not_exist() throws Exception {
- system.answer = null;
+ system.pluginStream = null;
system.isCommitted = true;
when(pluginRepository.hasPlugin("myplugin")).thenReturn(false);
@@ -181,7 +210,7 @@ public class StaticResourcesServletTest {
@Test
public void do_not_fail_nor_log_ERROR_when_response_is_already_committed_and_resource_does_not_exist_in_installed_plugin() throws Exception {
system.isCommitted = true;
- system.answer = null;
+ system.pluginStream = null;
when(pluginRepository.hasPlugin("myplugin")).thenReturn(true);
Response response = call("/static/myplugin/foo.css");
@@ -193,7 +222,7 @@ public class StaticResourcesServletTest {
@Test
public void do_not_fail_nor_log_not_attempt_to_send_error_if_ClientAbortException_is_raised() throws Exception {
- system.answerException = new ClientAbortException("Simulating ClientAbortException");
+ system.pluginStreamException = new ClientAbortException("Simulating ClientAbortException");
when(pluginRepository.hasPlugin("myplugin")).thenReturn(true);
Response response = call("/static/myplugin/foo.css");
@@ -207,7 +236,7 @@ public class StaticResourcesServletTest {
@Test
public void do_not_fail_when_response_is_committed_after_other_error() throws Exception {
system.isCommitted = true;
- system.answerException = new RuntimeException("Simulating a error");
+ system.pluginStreamException = new RuntimeException("Simulating a error");
when(pluginRepository.hasPlugin("myplugin")).thenReturn(true);
Response response = call("/static/myplugin/foo.css");
@@ -218,16 +247,22 @@ public class StaticResourcesServletTest {
private static class TestSystem extends StaticResourcesServlet.System {
private final PluginRepository pluginRepository;
+ private final CoreExtensionRepository coreExtensionRepository;
+ @Nullable
+ private InputStream pluginStream;
+ private Exception pluginStreamException = null;
@Nullable
- private InputStream answer;
- private Exception answerException = null;
+ private String pluginResource;
@Nullable
- private String calledResource;
+ private InputStream coreExtensionStream;
+ private Exception coreExtensionStreamException = null;
+ private String coreExtensionResource;
private boolean isCommitted = false;
private IOException sendErrorException = null;
- TestSystem(PluginRepository pluginRepository) {
+ TestSystem(PluginRepository pluginRepository, CoreExtensionRepository coreExtensionRepository) {
this.pluginRepository = pluginRepository;
+ this.coreExtensionRepository = coreExtensionRepository;
}
@Override
@@ -235,14 +270,29 @@ public class StaticResourcesServletTest {
return pluginRepository;
}
+ @Override
+ CoreExtensionRepository getCoreExtensionRepository() {
+ return this.coreExtensionRepository;
+ }
+
+ @CheckForNull
+ @Override
+ InputStream openPluginResourceStream(String pluginKey, String resource, PluginRepository pluginRepository) throws Exception {
+ pluginResource = resource;
+ if (pluginStreamException != null) {
+ throw pluginStreamException;
+ }
+ return pluginStream;
+ }
+
@CheckForNull
@Override
- InputStream openResourceStream(String pluginKey, String resource, PluginRepository pluginRepository) throws Exception {
- calledResource = resource;
- if (answerException != null) {
- throw answerException;
+ InputStream openCoreExtensionResourceStream(String resource) throws Exception {
+ coreExtensionResource = resource;
+ if (coreExtensionStreamException != null) {
+ throw coreExtensionStreamException;
}
- return answer;
+ return coreExtensionStream;
}
@Override
diff --git a/server/sonar-server/src/test/java/org/sonar/server/ui/PageRepositoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/ui/PageRepositoryTest.java
index 89165d85990..ae5b61f49f8 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/ui/PageRepositoryTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/ui/PageRepositoryTest.java
@@ -31,6 +31,7 @@ import org.sonar.api.web.page.Page.Qualifier;
import org.sonar.api.web.page.PageDefinition;
import org.sonar.core.platform.PluginInfo;
import org.sonar.core.platform.PluginRepository;
+import org.sonar.core.extension.CoreExtensionRepository;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
@@ -49,8 +50,9 @@ public class PageRepositoryTest {
public LogTester logTester = new LogTester();
private PluginRepository pluginRepository = mock(PluginRepository.class);
+ private CoreExtensionRepository coreExtensionRepository = mock(CoreExtensionRepository.class);
- private PageRepository underTest = new PageRepository(pluginRepository);
+ private PageRepository underTest = new PageRepository(pluginRepository, coreExtensionRepository);
@Before
public void setUp() {
@@ -64,7 +66,7 @@ public class PageRepositoryTest {
.addPage(Page.builder("my_plugin/K1").setName("N1").build())
.addPage(Page.builder("my_plugin/K3").setName("N3").build());
PageDefinition secondPlugin = context -> context.addPage(Page.builder("my_plugin/K2").setName("N2").build());
- underTest = new PageRepository(pluginRepository, new PageDefinition[]{firstPlugin, secondPlugin});
+ underTest = new PageRepository(pluginRepository, coreExtensionRepository, new PageDefinition[]{firstPlugin, secondPlugin});
underTest.start();
List<Page> result = underTest.getAllPages();
@@ -87,7 +89,7 @@ public class PageRepositoryTest {
.addPage(Page.builder("my_plugin/K4").setName("K4").setScope(GLOBAL).build())
.addPage(Page.builder("my_plugin/K5").setName("K5").setScope(COMPONENT).setComponentQualifiers(Qualifier.VIEW).build())
.addPage(Page.builder("my_plugin/K6").setName("K6").setScope(COMPONENT).setComponentQualifiers(Qualifier.APP).build());
- underTest = new PageRepository(pluginRepository, new PageDefinition[]{plugin});
+ underTest = new PageRepository(pluginRepository, coreExtensionRepository, new PageDefinition[]{plugin});
underTest.start();
List<Page> result = underTest.getComponentPages(false, Qualifiers.PROJECT);
@@ -112,7 +114,7 @@ public class PageRepositoryTest {
.addPage(Page.builder("my_plugin/K1").setName("N1").build())
.addPage(Page.builder("my_plugin/K2").setName("N2").build())
.addPage(Page.builder("my_plugin/K3").setName("N3").build());
- underTest = new PageRepository(pluginRepository, new PageDefinition[]{plugin});
+ underTest = new PageRepository(pluginRepository, coreExtensionRepository, new PageDefinition[]{plugin});
underTest.start();
List<Page> result = underTest.getGlobalPages(false);
@@ -131,7 +133,7 @@ public class PageRepositoryTest {
.addPage(Page.builder("my_plugin/O2").setName("O2").setScope(ORGANIZATION).build())
.addPage(Page.builder("my_plugin/O3").setName("O3").setScope(ORGANIZATION).build())
.addPage(Page.builder("my_plugin/OA1").setName("OA1").setScope(ORGANIZATION).setAdmin(true).build());
- underTest = new PageRepository(pluginRepository, new PageDefinition[]{plugin});
+ underTest = new PageRepository(pluginRepository, coreExtensionRepository, new PageDefinition[]{plugin});
underTest.start();
List<Page> result = underTest.getOrganizationPages(false);
@@ -146,7 +148,7 @@ public class PageRepositoryTest {
PageDefinition plugin = context -> context
.addPage(Page.builder("my_plugin/O1").setName("O1").setScope(ORGANIZATION).build())
.addPage(Page.builder("my_plugin/O2").setName("O2").setScope(ORGANIZATION).setAdmin(true).build());
- underTest = new PageRepository(pluginRepository, new PageDefinition[]{plugin});
+ underTest = new PageRepository(pluginRepository, coreExtensionRepository, new PageDefinition[]{plugin});
underTest.start();
List<Page> result = underTest.getOrganizationPages(true);
@@ -170,7 +172,7 @@ public class PageRepositoryTest {
PageDefinition plugin42 = context -> context.addPage(Page.builder("plugin_42/my_key").setName("N2").build());
pluginRepository = mock(PluginRepository.class);
when(pluginRepository.hasPlugin("governance")).thenReturn(true);
- underTest = new PageRepository(pluginRepository, new PageDefinition[]{governance, plugin42});
+ underTest = new PageRepository(pluginRepository, coreExtensionRepository, new PageDefinition[]{governance, plugin42});
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Page 'N2' references plugin 'plugin_42' that does not exist");
diff --git a/server/sonar-server/src/test/java/org/sonar/server/ui/ws/ComponentActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/ui/ws/ComponentActionTest.java
index 7ddcd2b32c0..c214196f65c 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/ui/ws/ComponentActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/ui/ws/ComponentActionTest.java
@@ -54,6 +54,7 @@ import org.sonar.db.user.UserDto;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.core.extension.CoreExtensionRepository;
import org.sonar.server.organization.BillingValidations;
import org.sonar.server.organization.BillingValidationsProxy;
import org.sonar.server.qualitygate.QualityGateFinder;
@@ -595,7 +596,9 @@ public class ComponentActionTest {
PluginRepository pluginRepository = mock(PluginRepository.class);
when(pluginRepository.hasPlugin(any())).thenReturn(true);
when(pluginRepository.getPluginInfo(any())).thenReturn(new PluginInfo("unused").setVersion(Version.create("1.0")));
- PageRepository pageRepository = new PageRepository(pluginRepository, new PageDefinition[] {context -> {
+ CoreExtensionRepository coreExtensionRepository = mock(CoreExtensionRepository.class);
+ when(coreExtensionRepository.isInstalled(any())).thenReturn(false);
+ PageRepository pageRepository = new PageRepository(pluginRepository, coreExtensionRepository, new PageDefinition[] {context -> {
for (Page page : pages) {
context.addPage(page);
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/ui/ws/GlobalActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/ui/ws/GlobalActionTest.java
index 186264842f8..26415593278 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/ui/ws/GlobalActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/ui/ws/GlobalActionTest.java
@@ -34,6 +34,7 @@ import org.sonar.db.DbClient;
import org.sonar.db.dialect.H2;
import org.sonar.db.dialect.MySql;
import org.sonar.server.branch.BranchFeatureRule;
+import org.sonar.core.extension.CoreExtensionRepository;
import org.sonar.server.organization.DefaultOrganizationProvider;
import org.sonar.server.organization.TestDefaultOrganizationProvider;
import org.sonar.server.organization.TestOrganizationFlags;
@@ -283,7 +284,9 @@ public class GlobalActionTest {
PluginRepository pluginRepository = mock(PluginRepository.class);
when(pluginRepository.hasPlugin(any())).thenReturn(true);
when(pluginRepository.getPluginInfo(any())).thenReturn(new PluginInfo("unused").setVersion(Version.create("1.0")));
- PageRepository pageRepository = new PageRepository(pluginRepository, new PageDefinition[] {context -> {
+ CoreExtensionRepository coreExtensionRepository = mock(CoreExtensionRepository.class);
+ when(coreExtensionRepository.isInstalled(any())).thenReturn(false);
+ PageRepository pageRepository = new PageRepository(pluginRepository, coreExtensionRepository, new PageDefinition[] {context -> {
for (Page page : pages) {
context.addPage(page);
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/ui/ws/OrganizationActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/ui/ws/OrganizationActionTest.java
index 986475ca37b..e49187b7382 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/ui/ws/OrganizationActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/ui/ws/OrganizationActionTest.java
@@ -33,6 +33,7 @@ import org.sonar.core.platform.PluginRepository;
import org.sonar.db.DbClient;
import org.sonar.db.DbTester;
import org.sonar.db.organization.OrganizationDto;
+import org.sonar.core.extension.CoreExtensionRepository;
import org.sonar.server.organization.BillingValidations;
import org.sonar.server.organization.BillingValidationsProxy;
import org.sonar.server.organization.DefaultOrganizationProvider;
@@ -248,7 +249,9 @@ public class OrganizationActionTest {
PluginRepository pluginRepository = mock(PluginRepository.class);
when(pluginRepository.hasPlugin(any())).thenReturn(true);
when(pluginRepository.getPluginInfo(any())).thenReturn(new PluginInfo("unused").setVersion(Version.create("1.0")));
- PageRepository pageRepository = new PageRepository(pluginRepository, new PageDefinition[] {context -> {
+ CoreExtensionRepository coreExtensionRepository = mock(CoreExtensionRepository.class);
+ when(coreExtensionRepository.isInstalled(any())).thenReturn(false);
+ PageRepository pageRepository = new PageRepository(pluginRepository, coreExtensionRepository, new PageDefinition[] {context -> {
for (Page page : pages) {
context.addPage(page);
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/ui/ws/SettingsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/ui/ws/SettingsActionTest.java
index 2b4034c6b24..14085068fbf 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/ui/ws/SettingsActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/ui/ws/SettingsActionTest.java
@@ -27,6 +27,7 @@ import org.sonar.api.web.page.PageDefinition;
import org.sonar.core.platform.PluginInfo;
import org.sonar.core.platform.PluginRepository;
import org.sonar.process.ProcessProperties;
+import org.sonar.core.extension.CoreExtensionRepository;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ui.PageRepository;
import org.sonar.server.ws.WsActionTester;
@@ -83,7 +84,9 @@ public class SettingsActionTest {
PluginRepository pluginRepository = mock(PluginRepository.class);
when(pluginRepository.hasPlugin(any())).thenReturn(true);
when(pluginRepository.getPluginInfo(any())).thenReturn(new PluginInfo("unused"));
- PageRepository pageRepository = new PageRepository(pluginRepository, new PageDefinition[] {context -> {
+ CoreExtensionRepository coreExtensionRepository = mock(CoreExtensionRepository.class);
+ when(coreExtensionRepository.isInstalled(any())).thenReturn(false);
+ PageRepository pageRepository = new PageRepository(pluginRepository, coreExtensionRepository, new PageDefinition[] {context -> {
for (Page page : pages) {
context.addPage(page);
}
diff --git a/settings.gradle b/settings.gradle
index ff20a6df267..d15f0d6f31c 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -13,6 +13,7 @@ include 'server:sonar-plugin-bridge'
include 'server:sonar-process'
include 'server:sonar-qa-util'
include 'server:sonar-server'
+include 'server:sonar-server-common'
include 'server:sonar-vsts'
include 'server:sonar-web'
diff --git a/sonar-core/src/main/java/org/sonar/core/extension/CoreExtension.java b/sonar-core/src/main/java/org/sonar/core/extension/CoreExtension.java
new file mode 100644
index 00000000000..c8279d81893
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/extension/CoreExtension.java
@@ -0,0 +1,48 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.core.extension;
+
+import java.util.Collection;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.config.Configuration;
+
+public interface CoreExtension {
+
+ /**
+ * Name of the core extension.
+ * <p>
+ * Used in the same fashion as the key for a plugin.
+ */
+ String getName();
+
+ interface Context {
+ SonarRuntime getRuntime();
+
+ Configuration getBootConfiguration();
+
+ Context addExtension(Object component);
+
+ Context addExtensions(Object component, Object... otherComponents);
+
+ <T> Context addExtensions(Collection<T> o);
+ }
+
+ void load(Context context);
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/extension/CoreExtensionRepository.java b/sonar-core/src/main/java/org/sonar/core/extension/CoreExtensionRepository.java
new file mode 100644
index 00000000000..9369bf5ab56
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/extension/CoreExtensionRepository.java
@@ -0,0 +1,57 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.core.extension;
+
+import java.util.Set;
+import java.util.stream.Stream;
+
+public interface CoreExtensionRepository {
+ /**
+ * Register the loaded Core Extensions in the repository.
+ *
+ * @throws IllegalStateException if the setCoreExtensionNames has already been called
+ */
+ void setLoadedCoreExtensions(Set<CoreExtension> coreExtensions);
+
+ /**
+ * @return a {@link Stream} of the loaded Core Extensions (if any).
+ *
+ * @see CoreExtension#getName()
+ * @throws IllegalStateException if {@link #setLoadedCoreExtensions(Set)} has not been called yet
+ */
+ Stream<CoreExtension> loadedCoreExtensions();
+
+ /**
+ * Register that the specified Core Extension has been installed.
+ *
+ * @throws IllegalArgumentException if the specified {@link CoreExtension} has not been loaded prior to this call
+ * ({@link #setLoadedCoreExtensions(Set)}
+ * @throws IllegalStateException if {@link #setLoadedCoreExtensions(Set)} has not been called yet
+ */
+ void installed(CoreExtension coreExtension);
+
+ /**
+ * Tells whether the repository knows of Core Extension with this exact name.
+ *
+ * @see CoreExtension#getName()
+ * @throws IllegalStateException if {@link #setLoadedCoreExtensions(Set)} has not been called yet
+ */
+ boolean isInstalled(String coreExtensionName);
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/extension/CoreExtensionRepositoryImpl.java b/sonar-core/src/main/java/org/sonar/core/extension/CoreExtensionRepositoryImpl.java
new file mode 100644
index 00000000000..5a87e47bbbd
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/extension/CoreExtensionRepositoryImpl.java
@@ -0,0 +1,70 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.core.extension;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
+public class CoreExtensionRepositoryImpl implements CoreExtensionRepository {
+ private Set<CoreExtension> coreExtensions = null;
+ private Set<CoreExtension> installedCoreExtensions = null;
+
+ @Override
+ public void setLoadedCoreExtensions(Set<CoreExtension> coreExtensions) {
+ checkState(this.coreExtensions == null, "Repository has already been initialized");
+
+ this.coreExtensions = ImmutableSet.copyOf(coreExtensions);
+ this.installedCoreExtensions = new HashSet<>(coreExtensions.size());
+ }
+
+ @Override
+ public Stream<CoreExtension> loadedCoreExtensions() {
+ checkInitialized();
+
+ return coreExtensions.stream();
+ }
+
+ @Override
+ public void installed(CoreExtension coreExtension) {
+ checkInitialized();
+ requireNonNull(coreExtension, "coreExtension can't be null");
+ checkArgument(coreExtensions.contains(coreExtension), "Specified CoreExtension has not been loaded first");
+
+ this.installedCoreExtensions.add(coreExtension);
+ }
+
+ @Override
+ public boolean isInstalled(String coreExtensionName) {
+ checkInitialized();
+
+ return installedCoreExtensions.stream()
+ .anyMatch(t -> coreExtensionName.equals(t.getName()));
+ }
+
+ private void checkInitialized() {
+ checkState(coreExtensions != null, "Repository has not been initialized yet");
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/extension/CoreExtensionsInstaller.java b/sonar-core/src/main/java/org/sonar/core/extension/CoreExtensionsInstaller.java
new file mode 100644
index 00000000000..a9b175f353e
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/extension/CoreExtensionsInstaller.java
@@ -0,0 +1,166 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.core.extension;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Predicate;
+import org.sonar.api.ExtensionProvider;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.utils.AnnotationUtils;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.core.platform.ComponentContainer;
+
+import static java.util.Objects.requireNonNull;
+
+public abstract class CoreExtensionsInstaller {
+ private static final Logger LOG = Loggers.get(CoreExtensionsInstaller.class);
+
+ private final SonarRuntime sonarRuntime;
+ private final CoreExtensionRepository coreExtensionRepository;
+ private final Class<? extends Annotation> supportedAnnotationType;
+
+ protected CoreExtensionsInstaller(SonarRuntime sonarRuntime, CoreExtensionRepository coreExtensionRepository,
+ Class<? extends Annotation> supportedAnnotationType) {
+ this.sonarRuntime = sonarRuntime;
+ this.coreExtensionRepository = coreExtensionRepository;
+ this.supportedAnnotationType = supportedAnnotationType;
+ }
+
+ public void install(ComponentContainer container, Predicate<Object> extensionFilter) {
+ coreExtensionRepository.loadedCoreExtensions()
+ .forEach(coreExtension -> install(container, extensionFilter, coreExtension));
+ }
+
+ private void install(ComponentContainer container, Predicate<Object> extensionFilter, CoreExtension coreExtension) {
+ String coreExtensionName = coreExtension.getName();
+ try {
+ List<Object> providerKeys = addDeclaredExtensions(container, extensionFilter, coreExtension);
+ addProvidedExtensions(container, extensionFilter, coreExtensionName, providerKeys);
+
+ LOG.info("Installed core extension: " + coreExtensionName);
+ coreExtensionRepository.installed(coreExtension);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load core extension " + coreExtensionName, e);
+ }
+ }
+
+ private List<Object> addDeclaredExtensions(ComponentContainer container, Predicate<Object> extensionFilter,
+ CoreExtension coreExtension) {
+ ContextImpl context = new ContextImpl(container, extensionFilter, coreExtension.getName());
+ coreExtension.load(context);
+ return context.getProviders();
+ }
+
+ private void addProvidedExtensions(ComponentContainer container, Predicate<Object> extensionFilter,
+ String extensionCategory, List<Object> providerKeys) {
+ providerKeys.stream()
+ .map(providerKey -> (ExtensionProvider) container.getComponentByKey(providerKey))
+ .forEach(provider -> addFromProvider(container, extensionFilter, extensionCategory, provider));
+ }
+
+ private void addFromProvider(ComponentContainer container, Predicate<Object> extensionFilter,
+ String extensionCategory, ExtensionProvider provider) {
+ Object obj = provider.provide();
+ if (obj != null) {
+ if (obj instanceof Iterable) {
+ for (Object ext : (Iterable) obj) {
+ addSupportedExtension(container, extensionFilter, extensionCategory, ext);
+ }
+ } else {
+ addSupportedExtension(container, extensionFilter, extensionCategory, obj);
+ }
+ }
+ }
+
+ private <T> boolean addSupportedExtension(ComponentContainer container, Predicate<Object> extensionFilter,
+ String extensionCategory, T component) {
+ if (hasSupportedAnnotation(component) && extensionFilter.test(component)) {
+ container.addExtension(extensionCategory, component);
+ return true;
+ }
+ return false;
+ }
+
+ private <T> boolean hasSupportedAnnotation(T component) {
+ return AnnotationUtils.getAnnotation(component, supportedAnnotationType) != null;
+ }
+
+ private class ContextImpl implements CoreExtension.Context {
+ private final ComponentContainer container;
+ private final Predicate<Object> extensionFilter;
+ private final String extensionCategory;
+ private final List<Object> providers = new ArrayList<>();
+
+ public ContextImpl(ComponentContainer container, Predicate<Object> extensionFilter, String extensionCategory) {
+ this.container = container;
+ this.extensionFilter = extensionFilter;
+ this.extensionCategory = extensionCategory;
+ }
+
+ @Override
+ public SonarRuntime getRuntime() {
+ return sonarRuntime;
+ }
+
+ @Override
+ public Configuration getBootConfiguration() {
+ return Optional.ofNullable(container.getComponentByType(Configuration.class))
+ .orElseGet(() -> new MapSettings().asConfig());
+ }
+
+ @Override
+ public CoreExtension.Context addExtension(Object component) {
+ requireNonNull(component, "component can't be null");
+
+ if (!addSupportedExtension(container, extensionFilter, extensionCategory, component)) {
+ container.declareExtension(extensionCategory, component);
+ } else if (ExtensionProviderSupport.isExtensionProvider(component)) {
+ providers.add(component);
+ }
+ return this;
+ }
+
+ @Override
+ public final CoreExtension.Context addExtensions(Object component, Object... otherComponents) {
+ addExtension(component);
+ Arrays.stream(otherComponents).forEach(this::addExtension);
+ return this;
+ }
+
+ @Override
+ public <T> CoreExtension.Context addExtensions(Collection<T> components) {
+ requireNonNull(components, "components can't be null");
+ components.forEach(this::addExtension);
+ return this;
+ }
+
+ public List<Object> getProviders() {
+ return providers;
+ }
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/extension/CoreExtensionsLoader.java b/sonar-core/src/main/java/org/sonar/core/extension/CoreExtensionsLoader.java
new file mode 100644
index 00000000000..79d78cb257b
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/extension/CoreExtensionsLoader.java
@@ -0,0 +1,78 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.core.extension;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.core.util.stream.MoreCollectors;
+
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Load {@link CoreExtension} and register them into the {@link CoreExtensionRepository}.
+ */
+public class CoreExtensionsLoader {
+ private static final Logger LOG = Loggers.get(CoreExtensionsLoader.class);
+
+ private final CoreExtensionRepository coreExtensionRepository;
+ private final ServiceLoaderWrapper serviceLoaderWrapper;
+
+ public CoreExtensionsLoader(CoreExtensionRepository coreExtensionRepository) {
+ this(coreExtensionRepository, new ServiceLoaderWrapper());
+ }
+
+ CoreExtensionsLoader(CoreExtensionRepository coreExtensionRepository, ServiceLoaderWrapper serviceLoaderWrapper) {
+ this.coreExtensionRepository = coreExtensionRepository;
+ this.serviceLoaderWrapper = serviceLoaderWrapper;
+ }
+
+ public void load() {
+ Set<CoreExtension> coreExtensions = serviceLoaderWrapper.load(getClass().getClassLoader());
+ ensureNoDuplicateName(coreExtensions);
+
+ coreExtensionRepository.setLoadedCoreExtensions(coreExtensions);
+ LOG.info("Loaded core extensions: {}", coreExtensions.stream().map(CoreExtension::getName).collect(Collectors.joining(", ")));
+ }
+
+ private static void ensureNoDuplicateName(Set<CoreExtension> coreExtensions) {
+ Map<String, Long> nameCounts = coreExtensions.stream()
+ .map(CoreExtension::getName)
+ .collect(Collectors.groupingBy(t -> t, Collectors.counting()));
+ Set<String> duplicatedNames = nameCounts.entrySet().stream()
+ .filter(t -> t.getValue() > 1)
+ .map(Map.Entry::getKey)
+ .collect(MoreCollectors.toSet());
+ checkState(duplicatedNames.isEmpty(),
+ "Multiple core extensions declare the following names: %s",
+ duplicatedNames.stream().sorted().collect(Collectors.joining(", ")));
+ }
+
+ static class ServiceLoaderWrapper {
+ Set<CoreExtension> load(ClassLoader classLoader) {
+ ServiceLoader<CoreExtension> loader = ServiceLoader.load(CoreExtension.class, classLoader);
+ return ImmutableSet.copyOf(loader.iterator());
+ }
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/extension/ExtensionProviderSupport.java b/sonar-core/src/main/java/org/sonar/core/extension/ExtensionProviderSupport.java
new file mode 100644
index 00000000000..bc0fae86485
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/extension/ExtensionProviderSupport.java
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.core.extension;
+
+import org.sonar.api.ExtensionProvider;
+
+public final class ExtensionProviderSupport {
+ private ExtensionProviderSupport() {
+ // prevents implementation
+ }
+
+ public static boolean isExtensionProvider(Object extension) {
+ return isType(extension, ExtensionProvider.class) || extension instanceof ExtensionProvider;
+ }
+
+ private static boolean isType(Object extension, Class extensionClass) {
+ Class clazz = extension instanceof Class ? (Class) extension : extension.getClass();
+ return extensionClass.isAssignableFrom(clazz);
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/extension/package-info.java b/sonar-core/src/main/java/org/sonar/core/extension/package-info.java
new file mode 100644
index 00000000000..49ad26896c3
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/extension/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.core.extension;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java b/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java
index fcdff6c4f1a..c181cfbe7c0 100644
--- a/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java
+++ b/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java
@@ -43,8 +43,6 @@ import org.apache.commons.io.IOUtils;
import org.picocontainer.Startable;
import org.sonar.api.batch.ScannerSide;
import org.sonar.api.i18n.I18n;
-import org.sonar.api.ce.ComputeEngineSide;
-import org.sonar.api.server.ServerSide;
import org.sonar.api.utils.SonarException;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.log.Logger;
@@ -53,8 +51,6 @@ import org.sonar.core.platform.PluginInfo;
import org.sonar.core.platform.PluginRepository;
@ScannerSide
-@ServerSide
-@ComputeEngineSide
public class DefaultI18n implements I18n, Startable {
private static final Logger LOG = Loggers.get(DefaultI18n.class);
@@ -88,10 +84,15 @@ public class DefaultI18n implements I18n, Startable {
}
@VisibleForTesting
- void doStart(ClassLoader classloader) {
+ protected void doStart(ClassLoader classloader) {
this.classloader = classloader;
this.propertyToBundles = new HashMap<>();
+ initialize();
+ LOG.debug("Loaded {} properties from l10n bundles", propertyToBundles.size());
+ }
+
+ protected void initialize() {
// org.sonar.l10n.core bundle is provided by sonar-core module
initPlugin("core");
@@ -99,10 +100,9 @@ public class DefaultI18n implements I18n, Startable {
for (PluginInfo plugin : infos) {
initPlugin(plugin.getKey());
}
- LOG.debug("Loaded {} properties from l10n bundles", propertyToBundles.size());
}
- private void initPlugin(String pluginKey) {
+ protected void initPlugin(String pluginKey) {
try {
String bundleKey = BUNDLE_PACKAGE + pluginKey;
ResourceBundle bundle = ResourceBundle.getBundle(bundleKey, Locale.ENGLISH, this.classloader, control);
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java b/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java
index 0bcb93396b9..7454777c7a0 100644
--- a/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java
+++ b/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java
@@ -43,6 +43,7 @@ import org.sonar.api.server.ServerSide;
import static com.google.common.collect.ImmutableList.copyOf;
import static java.util.Objects.requireNonNull;
+import static java.util.Optional.ofNullable;
@ScannerSide
@ServerSide
@@ -228,7 +229,7 @@ public class ComponentContainer implements ContainerPopulator.Container {
} catch (Throwable t) {
throw new IllegalStateException("Unable to register component " + getName(component), t);
}
- declareExtension(null, component);
+ declareExtension("", component);
}
return this;
}
@@ -244,6 +245,17 @@ public class ComponentContainer implements ContainerPopulator.Container {
return this;
}
+ public ComponentContainer addExtension(@Nullable String defaultCategory, Object extension) {
+ Object key = componentKeys.of(extension);
+ try {
+ pico.as(Characteristics.CACHE).addComponent(key, extension);
+ } catch (Throwable t) {
+ throw new IllegalStateException("Unable to register extension " + getName(extension), t);
+ }
+ declareExtension(defaultCategory, extension);
+ return this;
+ }
+
private static String getName(Object extension) {
if (extension instanceof Class) {
return ((Class<?>) extension).getName();
@@ -252,7 +264,11 @@ public class ComponentContainer implements ContainerPopulator.Container {
}
public void declareExtension(@Nullable PluginInfo pluginInfo, Object extension) {
- propertyDefinitions.addComponent(extension, pluginInfo != null ? pluginInfo.getName() : "");
+ declareExtension(pluginInfo != null ? pluginInfo.getName() : "", extension);
+ }
+
+ public void declareExtension(@Nullable String defaultCategory, Object extension) {
+ propertyDefinitions.addComponent(extension, ofNullable(defaultCategory).orElse(""));
}
public ComponentContainer addPicoAdapter(ComponentAdapter<?> adapter) {
diff --git a/sonar-core/src/test/java/org/sonar/core/extension/CoreExtensionRepositoryImplTest.java b/sonar-core/src/test/java/org/sonar/core/extension/CoreExtensionRepositoryImplTest.java
new file mode 100644
index 00000000000..6764e347257
--- /dev/null
+++ b/sonar-core/src/test/java/org/sonar/core/extension/CoreExtensionRepositoryImplTest.java
@@ -0,0 +1,177 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.core.extension;
+
+import com.google.common.collect.ImmutableSet;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singleton;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(DataProviderRunner.class)
+public class CoreExtensionRepositoryImplTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private CoreExtensionRepositoryImpl underTest = new CoreExtensionRepositoryImpl();
+
+ @Test
+ public void loadedCoreExtensions_fails_with_ISE_if_called_before_setLoadedCoreExtensions() {
+ expectRepositoryNotInitializedISE();
+
+ underTest.loadedCoreExtensions();
+ }
+
+ @Test
+ @UseDataProvider("coreExtensionsSets")
+ public void loadedCoreExtensions_returns_CoreExtensions_from_setLoadedCoreExtensions(Set<CoreExtension> coreExtensions) {
+ underTest.setLoadedCoreExtensions(coreExtensions);
+
+ assertThat(underTest.loadedCoreExtensions().collect(Collectors.toSet()))
+ .isEqualTo(coreExtensions);
+ }
+
+ @Test
+ public void setLoadedCoreExtensions_fails_with_NPE_if_argument_is_null() {
+ expectedException.expect(NullPointerException.class);
+
+ underTest.setLoadedCoreExtensions(null);
+ }
+
+ @Test
+ @UseDataProvider("coreExtensionsSets")
+ public void setLoadedCoreExtensions_fails_with_ISE_if_called_twice(Set<CoreExtension> coreExtensions) {
+ underTest.setLoadedCoreExtensions(coreExtensions);
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Repository has already been initialized");
+
+ underTest.setLoadedCoreExtensions(coreExtensions);
+ }
+
+ @Test
+ public void installed_fails_with_ISE_if_called_before_setLoadedCoreExtensions() {
+ CoreExtension coreExtension = newCoreExtension();
+
+ expectRepositoryNotInitializedISE();
+
+ underTest.installed(coreExtension);
+ }
+
+ @Test
+ @UseDataProvider("coreExtensionsSets")
+ public void installed_fails_with_IAE_if_CoreExtension_is_not_loaded(Set<CoreExtension> coreExtensions) {
+ underTest.setLoadedCoreExtensions(coreExtensions);
+ CoreExtension coreExtension = newCoreExtension();
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Specified CoreExtension has not been loaded first");
+
+ underTest.installed(coreExtension);
+ }
+
+ @Test
+ @UseDataProvider("coreExtensionsSets")
+ public void installed_fails_with_NPE_if_CoreExtension_is_null(Set<CoreExtension> coreExtensions) {
+ underTest.setLoadedCoreExtensions(coreExtensions);
+
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("coreExtension can't be null");
+
+ underTest.installed(null);
+ }
+
+ @Test
+ public void isInstalled_fails_with_ISE_if_called_before_setLoadedCoreExtensions() {
+ expectRepositoryNotInitializedISE();
+
+ underTest.isInstalled("foo");
+ }
+
+ @Test
+ @UseDataProvider("coreExtensionsSets")
+ public void isInstalled_returns_false_for_not_loaded_CoreExtension(Set<CoreExtension> coreExtensions) {
+ underTest.setLoadedCoreExtensions(coreExtensions);
+
+ assertThat(underTest.isInstalled("not loaded")).isFalse();
+ }
+
+ @Test
+ public void isInstalled_returns_false_for_loaded_but_not_installed_CoreExtension() {
+ CoreExtension coreExtension = newCoreExtension();
+ underTest.setLoadedCoreExtensions(singleton(coreExtension));
+
+ assertThat(underTest.isInstalled(coreExtension.getName())).isFalse();
+ }
+
+ @Test
+ public void isInstalled_returns_true_for_loaded_and_installed_CoreExtension() {
+ CoreExtension coreExtension = newCoreExtension();
+ underTest.setLoadedCoreExtensions(singleton(coreExtension));
+ underTest.installed(coreExtension);
+
+ assertThat(underTest.isInstalled(coreExtension.getName())).isTrue();
+ }
+
+ private void expectRepositoryNotInitializedISE() {
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Repository has not been initialized yet");
+ }
+
+ @DataProvider
+ public static Object[][] coreExtensionsSets() {
+ return new Object[][] {
+ {emptySet()},
+ {singleton(newCoreExtension())},
+ {ImmutableSet.of(newCoreExtension(), newCoreExtension())},
+ };
+ }
+
+ private static int nameCounter = 0;
+
+ private static CoreExtension newCoreExtension() {
+ String name = "name_" + nameCounter;
+ nameCounter++;
+ return newCoreExtension(name);
+ }
+
+ private static CoreExtension newCoreExtension(String name) {
+ return new CoreExtension() {
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public void load(Context context) {
+ throw new UnsupportedOperationException("load should not be called");
+ }
+ };
+ }
+}
diff --git a/sonar-core/src/test/java/org/sonar/core/extension/CoreExtensionsInstallerTest.java b/sonar-core/src/test/java/org/sonar/core/extension/CoreExtensionsInstallerTest.java
new file mode 100644
index 00000000000..d9d03370863
--- /dev/null
+++ b/sonar-core/src/test/java/org/sonar/core/extension/CoreExtensionsInstallerTest.java
@@ -0,0 +1,424 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.core.extension;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+import org.picocontainer.ComponentAdapter;
+import org.sonar.api.ExtensionProvider;
+import org.sonar.api.Property;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.config.PropertyDefinition;
+import org.sonar.api.config.PropertyDefinitions;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.core.platform.ComponentContainer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER;
+
+@RunWith(DataProviderRunner.class)
+public class CoreExtensionsInstallerTest {
+ private SonarRuntime sonarRuntime = mock(SonarRuntime.class);
+ private CoreExtensionRepository coreExtensionRepository = mock(CoreExtensionRepository.class);
+ private CoreExtensionsInstaller underTest = new CoreExtensionsInstaller(sonarRuntime, coreExtensionRepository, WestSide.class) {
+
+ };
+
+ private ArgumentCaptor<CoreExtension.Context> contextCaptor = ArgumentCaptor.forClass(CoreExtension.Context.class);
+ private static int name_counter = 0;
+
+ @Test
+ public void install_has_no_effect_if_CoreExtensionRepository_has_no_loaded_CoreExtension() {
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.install(container, t -> true);
+
+ assertAddedExtensions(container, 0);
+ }
+
+ @Test
+ public void install_calls_load_method_on_all_loaded_CoreExtension() {
+ CoreExtension coreExtension1 = newCoreExtension();
+ CoreExtension coreExtension2 = newCoreExtension();
+ CoreExtension coreExtension3 = newCoreExtension();
+ CoreExtension coreExtension4 = newCoreExtension();
+ List<CoreExtension> coreExtensions = ImmutableList.of(coreExtension1, coreExtension2, coreExtension3, coreExtension4);
+ InOrder inOrder = Mockito.inOrder(coreExtension1, coreExtension2, coreExtension3, coreExtension4);
+ when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(coreExtensions.stream());
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.install(container, t -> true);
+
+ inOrder.verify(coreExtension1).load(contextCaptor.capture());
+ inOrder.verify(coreExtension2).load(contextCaptor.capture());
+ inOrder.verify(coreExtension3).load(contextCaptor.capture());
+ inOrder.verify(coreExtension4).load(contextCaptor.capture());
+ // verify each core extension gets its own Context
+ assertThat(contextCaptor.getAllValues())
+ .hasSameElementsAs(ImmutableSet.copyOf(contextCaptor.getAllValues()));
+ }
+
+ @Test
+ public void install_provides_runtime_from_constructor_in_context() {
+ CoreExtension coreExtension1 = newCoreExtension();
+ CoreExtension coreExtension2 = newCoreExtension();
+ when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(Stream.of(coreExtension1, coreExtension2));
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.install(container, t -> true);
+
+ verify(coreExtension1).load(contextCaptor.capture());
+ verify(coreExtension2).load(contextCaptor.capture());
+ assertThat(contextCaptor.getAllValues())
+ .extracting(CoreExtension.Context::getRuntime)
+ .containsOnly(sonarRuntime);
+ }
+
+ @Test
+ public void install_provides_new_Configuration_when_getBootConfiguration_is_called_and_there_is_none_in_container() {
+ CoreExtension coreExtension1 = newCoreExtension();
+ CoreExtension coreExtension2 = newCoreExtension();
+ when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(Stream.of(coreExtension1, coreExtension2));
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.install(container, t -> true);
+
+ verify(coreExtension1).load(contextCaptor.capture());
+ verify(coreExtension2).load(contextCaptor.capture());
+ // verify each core extension gets its own configuration
+ assertThat(contextCaptor.getAllValues())
+ .hasSameElementsAs(ImmutableSet.copyOf(contextCaptor.getAllValues()));
+ }
+
+ @Test
+ public void install_provides_Configuration_from_container_when_getBootConfiguration_is_called() {
+ CoreExtension coreExtension1 = newCoreExtension();
+ CoreExtension coreExtension2 = newCoreExtension();
+ when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(Stream.of(coreExtension1, coreExtension2));
+ Configuration configuration = new MapSettings().asConfig();
+ ComponentContainer container = new ComponentContainer();
+ container.add(configuration);
+
+ underTest.install(container, t -> true);
+
+ verify(coreExtension1).load(contextCaptor.capture());
+ verify(coreExtension2).load(contextCaptor.capture());
+ assertThat(contextCaptor.getAllValues())
+ .extracting(CoreExtension.Context::getBootConfiguration)
+ .containsOnly(configuration);
+ }
+
+ @Test
+ @UseDataProvider("allMethodsToAddExtension")
+ public void install_installs_extensions_annotated_with_expected_annotation(BiConsumer<CoreExtension.Context, Collection<Object>> extensionAdder) {
+ List<Object> extensions = ImmutableList.of(WestSideClass.class, EastSideClass.class, OtherSideClass.class, Latitude.class);
+ CoreExtension coreExtension = newCoreExtension(context -> extensionAdder.accept(context, extensions));
+ when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(Stream.of(coreExtension));
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.install(container, t -> true);
+
+ assertAddedExtensions(container, WestSideClass.class, Latitude.class);
+ assertPropertyDefinitions(container);
+ }
+
+ @Test
+ @UseDataProvider("allMethodsToAddExtension")
+ public void install_does_not_install_extensions_annotated_with_expected_annotation_but_filtered_out(BiConsumer<CoreExtension.Context, Collection<Object>> extensionAdder) {
+ List<Object> extensions = ImmutableList.of(WestSideClass.class, EastSideClass.class, OtherSideClass.class, Latitude.class);
+ CoreExtension coreExtension = newCoreExtension(context -> extensionAdder.accept(context, extensions));
+ when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(Stream.of(coreExtension));
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.install(container, t -> t != Latitude.class);
+
+ assertAddedExtensions(container, WestSideClass.class);
+ assertPropertyDefinitions(container);
+ }
+
+ @Test
+ @UseDataProvider("allMethodsToAddExtension")
+ public void install_adds_PropertyDefinition_from_annotation_no_matter_annotations(BiConsumer<CoreExtension.Context, Collection<Object>> extensionAdder) {
+ List<Object> extensions = ImmutableList.of(WestSidePropertyDefinition.class, EastSidePropertyDefinition.class,
+ OtherSidePropertyDefinition.class, LatitudePropertyDefinition.class, BlankPropertyDefinition.class);
+ CoreExtension coreExtension = newCoreExtension(context -> extensionAdder.accept(context, extensions));
+ when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(Stream.of(coreExtension));
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.install(container, t -> true);
+
+ assertAddedExtensions(container, WestSidePropertyDefinition.class, LatitudePropertyDefinition.class);
+ assertPropertyDefinitions(container, "westKey", "eastKey", "otherKey", "latitudeKey", "blankKey");
+ }
+
+ @Test
+ @UseDataProvider("allMethodsToAddExtension")
+ public void install_adds_PropertyDefinition_from_annotation_no_matter_annotations_even_if_filtered_out(BiConsumer<CoreExtension.Context, Collection<Object>> extensionAdder) {
+ List<Object> extensions = ImmutableList.of(WestSidePropertyDefinition.class, EastSidePropertyDefinition.class,
+ OtherSidePropertyDefinition.class, LatitudePropertyDefinition.class, BlankPropertyDefinition.class);
+ CoreExtension coreExtension = newCoreExtension(context -> extensionAdder.accept(context, extensions));
+ when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(Stream.of(coreExtension));
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.install(container, t -> false);
+
+ assertAddedExtensions(container, 0);
+ assertPropertyDefinitions(container, "westKey", "eastKey", "otherKey", "latitudeKey", "blankKey");
+ }
+
+ @Test
+ @UseDataProvider("allMethodsToAddExtension")
+ public void install_adds_PropertyDefinition_with_extension_name_as_default_category(BiConsumer<CoreExtension.Context, Collection<Object>> extensionAdder) {
+ PropertyDefinition propertyDefinitionNoCategory = PropertyDefinition.builder("fooKey").build();
+ PropertyDefinition propertyDefinitionWithCategory = PropertyDefinition.builder("barKey").category("donut").build();
+ List<Object> extensions = ImmutableList.of(propertyDefinitionNoCategory, propertyDefinitionWithCategory);
+ CoreExtension coreExtension = newCoreExtension(context -> extensionAdder.accept(context, extensions));
+ when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(Stream.of(coreExtension));
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.install(container, t -> true);
+
+ assertAddedExtensions(container, 0);
+ assertPropertyDefinitions(container, coreExtension, propertyDefinitionNoCategory, propertyDefinitionWithCategory);
+ }
+
+ @Test
+ @UseDataProvider("allMethodsToAddExtension")
+ public void install_adds_providers_to_container_and_install_extensions_they_provide_when_annotated_with_expected_annotation(BiConsumer<CoreExtension.Context, Collection<Object>> extensionAdder) {
+ List<Object> extensions = ImmutableList.of(WestSideProvider.class, PartiallyWestSideProvider.class, EastSideProvider.class);
+ CoreExtension coreExtension = newCoreExtension(context -> extensionAdder.accept(context, extensions));
+ when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(Stream.of(coreExtension));
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.install(container, t -> true);
+
+ assertAddedExtensions(container, WestSideProvider.class, WestSideProvided.class, PartiallyWestSideProvider.class);
+ assertPropertyDefinitions(container);
+ }
+
+ @DataProvider
+ public static Object[][] allMethodsToAddExtension() {
+ BiConsumer<CoreExtension.Context, Collection<Object>> addExtension = (context, objects) -> objects.forEach(context::addExtension);
+ BiConsumer<CoreExtension.Context, Collection<Object>> addExtensionsVarArg = (context, objects) -> {
+ if (objects.isEmpty()) {
+ return;
+ }
+ if (objects.size() == 1) {
+ context.addExtensions(objects.iterator().next());
+ }
+ context.addExtensions(objects.iterator().next(), objects.stream().skip(1).toArray(Object[]::new));
+ };
+ BiConsumer<CoreExtension.Context, Collection<Object>> addExtensions = CoreExtension.Context::addExtensions;
+ return new Object[][] {
+ {addExtension},
+ {addExtensions},
+ {addExtensionsVarArg}
+ };
+ }
+
+ private static void assertAddedExtensions(ComponentContainer container, int addedExtensions) {
+ Collection<ComponentAdapter<?>> adapters = container.getPicoContainer().getComponentAdapters();
+ assertThat(adapters)
+ .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + addedExtensions);
+ }
+
+ private static void assertAddedExtensions(ComponentContainer container, Class... classes) {
+ Collection<ComponentAdapter<?>> adapters = container.getPicoContainer().getComponentAdapters();
+ assertThat(adapters)
+ .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + classes.length);
+
+ Stream<Class> installedExtensions = adapters.stream()
+ .map(t -> (Class) t.getComponentImplementation())
+ .filter(t -> !PropertyDefinitions.class.isAssignableFrom(t) && t != ComponentContainer.class);
+ assertThat(installedExtensions)
+ .contains(classes)
+ .hasSize(classes.length);
+ }
+
+ private void assertPropertyDefinitions(ComponentContainer container, String... keys) {
+ PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class);
+ if (keys.length == 0) {
+ assertThat(propertyDefinitions.getAll()).isEmpty();
+ } else {
+ for (String key : keys) {
+ assertThat(propertyDefinitions.get(key)).isNotNull();
+ }
+ }
+ }
+
+ private void assertPropertyDefinitions(ComponentContainer container, CoreExtension coreExtension, PropertyDefinition... definitions) {
+ PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class);
+ if (definitions.length == 0) {
+ assertThat(propertyDefinitions.getAll()).isEmpty();
+ } else {
+ for (PropertyDefinition definition : definitions) {
+ PropertyDefinition actual = propertyDefinitions.get(definition.key());
+ assertThat(actual.category()).isEqualTo(definition.category() == null ? coreExtension.getName() : definition.category());
+ }
+ }
+ }
+
+ private static CoreExtension newCoreExtension() {
+ return newCoreExtension(t -> {
+ });
+ }
+
+ private static CoreExtension newCoreExtension(Consumer<CoreExtension.Context> loadImplementation) {
+ CoreExtension res = mock(CoreExtension.class);
+ when(res.getName()).thenReturn("name_" + name_counter);
+ name_counter++;
+ doAnswer(invocation -> {
+ CoreExtension.Context context = invocation.getArgument(0);
+ loadImplementation.accept(context);
+ return null;
+ }).when(res).load(any());
+ return res;
+ }
+
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface WestSide {
+ }
+
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface EastSide {
+ }
+
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface OtherSide {
+ }
+
+ @WestSide
+ public static class WestSideClass {
+
+ }
+
+ @EastSide
+ public static class EastSideClass {
+
+ }
+
+ @OtherSide
+ public static class OtherSideClass {
+
+ }
+
+ @WestSide
+ @EastSide
+ public static class Latitude {
+
+ }
+
+ @WestSide
+ public static class WestSideProvider extends ExtensionProvider {
+
+ @Override
+ public Object provide() {
+ return WestSideProvided.class;
+ }
+ }
+
+ @WestSide
+ public static class WestSideProvided {
+
+ }
+
+ @WestSide
+ public static class PartiallyWestSideProvider extends ExtensionProvider {
+
+ @Override
+ public Object provide() {
+ return NotWestSideProvided.class;
+ }
+ }
+
+ public static class NotWestSideProvided {
+
+ }
+
+ @EastSide
+ public static class EastSideProvider extends ExtensionProvider {
+
+ @Override
+ public Object provide() {
+ throw new IllegalStateException("EastSideProvider#provide should not be called");
+ }
+ }
+
+ @Property(key = "westKey", name = "westName")
+ @WestSide
+ public static class WestSidePropertyDefinition {
+
+ }
+
+ @Property(key = "eastKey", name = "eastName")
+ @EastSide
+ public static class EastSidePropertyDefinition {
+
+ }
+
+ @Property(key = "otherKey", name = "otherName")
+ @OtherSide
+ public static class OtherSidePropertyDefinition {
+
+ }
+
+ @Property(key = "latitudeKey", name = "latitudeName")
+ @WestSide
+ @EastSide
+ public static class LatitudePropertyDefinition {
+
+ }
+
+ @Property(key = "blankKey", name = "blankName")
+ public static class BlankPropertyDefinition {
+
+ }
+
+}
diff --git a/sonar-core/src/test/java/org/sonar/core/extension/CoreExtensionsLoaderTest.java b/sonar-core/src/test/java/org/sonar/core/extension/CoreExtensionsLoaderTest.java
new file mode 100644
index 00000000000..180592eff69
--- /dev/null
+++ b/sonar-core/src/test/java/org/sonar/core/extension/CoreExtensionsLoaderTest.java
@@ -0,0 +1,98 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.core.extension;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Collections;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+public class CoreExtensionsLoaderTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private CoreExtensionRepository coreExtensionRepository = mock(CoreExtensionRepository.class);
+ private CoreExtensionsLoader.ServiceLoaderWrapper serviceLoaderWrapper = mock(CoreExtensionsLoader.ServiceLoaderWrapper.class);
+ private CoreExtensionsLoader underTest = new CoreExtensionsLoader(coreExtensionRepository, serviceLoaderWrapper);
+
+ @Test
+ public void load_has_no_effect_if_there_is_no_ServiceLoader_for_CoreExtension_class() {
+ when(serviceLoaderWrapper.load(any())).thenReturn(Collections.emptySet());
+
+ underTest.load();
+
+ verify(serviceLoaderWrapper).load(CoreExtensionsLoader.class.getClassLoader());
+ verify(coreExtensionRepository).setLoadedCoreExtensions(Collections.emptySet());
+ verifyNoMoreInteractions(serviceLoaderWrapper, coreExtensionRepository);
+ }
+
+ @Test
+ public void load_sets_loaded_core_extensions_into_repository() {
+ Set<CoreExtension> coreExtensions = IntStream.range(0, 1 + new Random().nextInt(5))
+ .mapToObj(i -> newCoreExtension("core_ext_" + i))
+ .collect(Collectors.toSet());
+ when(serviceLoaderWrapper.load(any())).thenReturn(coreExtensions);
+
+ underTest.load();
+
+ verify(serviceLoaderWrapper).load(CoreExtensionsLoader.class.getClassLoader());
+ verify(coreExtensionRepository).setLoadedCoreExtensions(coreExtensions);
+ verifyNoMoreInteractions(serviceLoaderWrapper, coreExtensionRepository);
+ }
+
+ @Test
+ public void load_fails_with_ISE_if_multiple_core_extensions_declare_same_name() {
+ Set<CoreExtension> coreExtensions = ImmutableSet.of(newCoreExtension("a"), newCoreExtension("a"));
+ when(serviceLoaderWrapper.load(any())).thenReturn(coreExtensions);
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Multiple core extensions declare the following names: a");
+
+ underTest.load();
+ }
+
+ @Test
+ public void load_fails_with_ISE_if_multiple_core_extensions_declare_same_names() {
+ Set<CoreExtension> coreExtensions = ImmutableSet.of(newCoreExtension("a"), newCoreExtension("a"), newCoreExtension("b"), newCoreExtension("b"));
+ when(serviceLoaderWrapper.load(any())).thenReturn(coreExtensions);
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Multiple core extensions declare the following names: a, b");
+
+ underTest.load();
+ }
+
+ private static CoreExtension newCoreExtension(String name) {
+ CoreExtension res = mock(CoreExtension.class);
+ when(res.getName()).thenReturn(name);
+ return res;
+ }
+}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/GlobalContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/GlobalContainer.java
index 1aea964a478..bc49833ec6f 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/GlobalContainer.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/GlobalContainer.java
@@ -32,6 +32,9 @@ import org.sonar.api.utils.UriReader;
import org.sonar.api.utils.Version;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
+import org.sonar.core.extension.CoreExtensionRepositoryImpl;
+import org.sonar.core.extension.CoreExtensionsInstaller;
+import org.sonar.core.extension.CoreExtensionsLoader;
import org.sonar.core.platform.ComponentContainer;
import org.sonar.core.platform.PluginClassloaderFactory;
import org.sonar.core.platform.PluginInfo;
@@ -39,6 +42,7 @@ import org.sonar.core.platform.PluginLoader;
import org.sonar.core.platform.PluginRepository;
import org.sonar.core.util.DefaultHttpDownloader;
import org.sonar.core.util.UuidFactoryImpl;
+import org.sonar.scanner.extension.ScannerCoreExtensionsInstaller;
import org.sonar.scanner.platform.DefaultServer;
import org.sonar.scanner.repository.DefaultMetricsRepositoryLoader;
import org.sonar.scanner.repository.MetricsRepositoryLoader;
@@ -99,6 +103,7 @@ public class GlobalContainer extends ComponentContainer {
new MetricsRepositoryProvider(),
UuidFactoryImpl.INSTANCE);
addIfMissing(ScannerPluginInstaller.class, PluginInstaller.class);
+ add(CoreExtensionRepositoryImpl.class, CoreExtensionsLoader.class, ScannerCoreExtensionsInstaller.class);
addIfMissing(DefaultSettingsLoader.class, SettingsLoader.class);
addIfMissing(DefaultMetricsRepositoryLoader.class, MetricsRepositoryLoader.class);
}
@@ -106,6 +111,7 @@ public class GlobalContainer extends ComponentContainer {
@Override
protected void doAfterStart() {
installPlugins();
+ loadCoreExtensions();
}
private void installPlugins() {
@@ -116,6 +122,11 @@ public class GlobalContainer extends ComponentContainer {
}
}
+ private void loadCoreExtensions() {
+ CoreExtensionsLoader loader = getComponentByType(CoreExtensionsLoader.class);
+ loader.load();
+ }
+
public void executeTask(Map<String, String> taskProperties, Object... components) {
long startTime = System.currentTimeMillis();
new TaskContainer(this, taskProperties, components).execute();
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/extension/ScannerCoreExtensionsInstaller.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/extension/ScannerCoreExtensionsInstaller.java
new file mode 100644
index 00000000000..e1b2f49aa8a
--- /dev/null
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/extension/ScannerCoreExtensionsInstaller.java
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.extension;
+
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.batch.ScannerSide;
+import org.sonar.core.extension.CoreExtensionRepository;
+import org.sonar.core.extension.CoreExtensionsInstaller;
+
+@ScannerSide
+public class ScannerCoreExtensionsInstaller extends CoreExtensionsInstaller {
+ public ScannerCoreExtensionsInstaller(SonarRuntime sonarRuntime, CoreExtensionRepository coreExtensionRepository) {
+ super(sonarRuntime, coreExtensionRepository, ScannerSide.class);
+ }
+}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/extension/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/extension/package-info.java
new file mode 100644
index 00000000000..eb9aa1ab845
--- /dev/null
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/extension/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.scanner.extension;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java
index f00208dd652..af03f0ae932 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java
@@ -21,17 +21,16 @@ package org.sonar.scanner.scan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.sonar.api.batch.InstantiationStrategy;
import org.sonar.api.batch.fs.internal.DefaultInputModule;
import org.sonar.api.batch.fs.internal.FileMetadata;
import org.sonar.api.batch.rule.CheckFactory;
import org.sonar.api.issue.NoSonarFilter;
import org.sonar.api.resources.Project;
import org.sonar.api.scan.filesystem.FileExclusions;
+import org.sonar.core.extension.CoreExtensionsInstaller;
import org.sonar.core.platform.ComponentContainer;
import org.sonar.scanner.DefaultFileLinesContextFactory;
import org.sonar.scanner.bootstrap.ExtensionInstaller;
-import org.sonar.scanner.bootstrap.ExtensionUtils;
import org.sonar.scanner.bootstrap.GlobalAnalysisMode;
import org.sonar.scanner.bootstrap.ScannerExtensionDictionnary;
import org.sonar.scanner.deprecated.DeprecatedSensorContext;
@@ -73,6 +72,10 @@ import org.sonar.scanner.sensor.SensorOptimizer;
import org.sonar.scanner.source.HighlightableBuilder;
import org.sonar.scanner.source.SymbolizableBuilder;
+import static org.sonar.api.batch.InstantiationStrategy.PER_PROJECT;
+import static org.sonar.scanner.bootstrap.ExtensionUtils.isInstantiationStrategy;
+import static org.sonar.scanner.bootstrap.ExtensionUtils.isScannerSide;
+
public class ModuleScanContainer extends ComponentContainer {
private static final Logger LOG = LoggerFactory.getLogger(ModuleScanContainer.class);
private final DefaultInputModule module;
@@ -165,8 +168,10 @@ public class ModuleScanContainer extends ComponentContainer {
}
private void addExtensions() {
- ExtensionInstaller installer = getComponentByType(ExtensionInstaller.class);
- installer.install(this, e -> ExtensionUtils.isScannerSide(e) && ExtensionUtils.isInstantiationStrategy(e, InstantiationStrategy.PER_PROJECT));
+ ExtensionInstaller pluginInstaller = getComponentByType(ExtensionInstaller.class);
+ pluginInstaller.install(this, e -> isScannerSide(e) && isInstantiationStrategy(e, PER_PROJECT));
+ CoreExtensionsInstaller coreExtensionsInstaller = getComponentByType(CoreExtensionsInstaller.class);
+ coreExtensionsInstaller.install(this, t -> isInstantiationStrategy(t, PER_PROJECT));
}
@Override
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java
index af9ea941f51..c0ab266b34f 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java
@@ -22,7 +22,6 @@ package org.sonar.scanner.scan;
import com.google.common.annotations.VisibleForTesting;
import javax.annotation.Nullable;
import org.sonar.api.CoreProperties;
-import org.sonar.api.batch.InstantiationStrategy;
import org.sonar.api.batch.fs.internal.DefaultInputModule;
import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
import org.sonar.api.batch.fs.internal.SensorStrategy;
@@ -33,6 +32,7 @@ import org.sonar.api.scan.filesystem.PathResolver;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.core.config.ScannerProperties;
+import org.sonar.core.extension.CoreExtensionsInstaller;
import org.sonar.core.metric.ScannerMetrics;
import org.sonar.core.platform.ComponentContainer;
import org.sonar.scanner.ProjectAnalysisInfo;
@@ -41,7 +41,6 @@ import org.sonar.scanner.analysis.AnalysisTempFolderProvider;
import org.sonar.scanner.analysis.DefaultAnalysisMode;
import org.sonar.scanner.bootstrap.ExtensionInstaller;
import org.sonar.scanner.bootstrap.ExtensionMatcher;
-import org.sonar.scanner.bootstrap.ExtensionUtils;
import org.sonar.scanner.bootstrap.GlobalAnalysisMode;
import org.sonar.scanner.bootstrap.MetricProvider;
import org.sonar.scanner.cpd.CpdExecutor;
@@ -102,6 +101,10 @@ import org.sonar.scanner.scan.measure.MeasureCache;
import org.sonar.scanner.scm.ScmChangedFilesProvider;
import org.sonar.scanner.storage.Storages;
+import static org.sonar.api.batch.InstantiationStrategy.PER_BATCH;
+import static org.sonar.scanner.bootstrap.ExtensionUtils.isInstantiationStrategy;
+import static org.sonar.scanner.bootstrap.ExtensionUtils.isScannerSide;
+
public class ProjectScanContainer extends ComponentContainer {
private static final Logger LOG = Loggers.get(ProjectScanContainer.class);
@@ -238,7 +241,15 @@ public class ProjectScanContainer extends ComponentContainer {
}
private void addBatchExtensions() {
- getComponentByType(ExtensionInstaller.class).install(this, new BatchExtensionFilter());
+ getComponentByType(ExtensionInstaller.class)
+ .install(this, getBatchPluginExtensionsFilter());
+ getComponentByType(CoreExtensionsInstaller.class)
+ .install(this, extension -> isInstantiationStrategy(extension, PER_BATCH));
+ }
+
+ @VisibleForTesting
+ static ExtensionMatcher getBatchPluginExtensionsFilter() {
+ return extension -> isScannerSide(extension) && isInstantiationStrategy(extension, PER_BATCH);
}
@Override
@@ -301,12 +312,4 @@ public class ProjectScanContainer extends ComponentContainer {
new ModuleScanContainer(this, module, analysisMode).execute();
}
- static class BatchExtensionFilter implements ExtensionMatcher {
- @Override
- public boolean accept(Object extension) {
- return ExtensionUtils.isScannerSide(extension)
- && ExtensionUtils.isInstantiationStrategy(extension, InstantiationStrategy.PER_BATCH);
- }
- }
-
}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/task/TaskContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/task/TaskContainer.java
index e863148e9e8..4d81fa18127 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/task/TaskContainer.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/task/TaskContainer.java
@@ -22,16 +22,18 @@ package org.sonar.scanner.task;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.CoreProperties;
-import org.sonar.api.batch.InstantiationStrategy;
import org.sonar.api.task.Task;
import org.sonar.api.task.TaskDefinition;
import org.sonar.api.utils.MessageException;
+import org.sonar.core.extension.CoreExtensionsInstaller;
import org.sonar.core.platform.ComponentContainer;
import org.sonar.scanner.bootstrap.ExtensionInstaller;
-import org.sonar.scanner.bootstrap.ExtensionMatcher;
-import org.sonar.scanner.bootstrap.ExtensionUtils;
import org.sonar.scanner.bootstrap.GlobalProperties;
+import static org.sonar.api.batch.InstantiationStrategy.PER_TASK;
+import static org.sonar.scanner.bootstrap.ExtensionUtils.isInstantiationStrategy;
+import static org.sonar.scanner.bootstrap.ExtensionUtils.isScannerSide;
+
public class TaskContainer extends ComponentContainer {
private final Map<String, String> taskProperties;
@@ -57,15 +59,10 @@ public class TaskContainer extends ComponentContainer {
}
private void addTaskExtensions() {
- getComponentByType(ExtensionInstaller.class).install(this, new TaskExtensionFilter());
- }
-
- static class TaskExtensionFilter implements ExtensionMatcher {
- @Override
- public boolean accept(Object extension) {
- return ExtensionUtils.isScannerSide(extension)
- && ExtensionUtils.isInstantiationStrategy(extension, InstantiationStrategy.PER_TASK);
- }
+ getComponentByType(ExtensionInstaller.class)
+ .install(this, extension -> isScannerSide(extension) && isInstantiationStrategy(extension, PER_TASK));
+ getComponentByType(CoreExtensionsInstaller.class)
+ .install(this, t -> isInstantiationStrategy(t, PER_TASK));
}
@Override
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/extension/ScannerCoreExtensionsInstallerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/extension/ScannerCoreExtensionsInstallerTest.java
new file mode 100644
index 00000000000..a359ba30967
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/extension/ScannerCoreExtensionsInstallerTest.java
@@ -0,0 +1,109 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.extension;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.stream.Stream;
+import org.junit.Test;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.batch.ScannerSide;
+import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.server.ServerSide;
+import org.sonar.core.extension.CoreExtension;
+import org.sonar.core.extension.CoreExtensionRepository;
+import org.sonar.core.platform.ComponentContainer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ScannerCoreExtensionsInstallerTest {
+ private SonarRuntime sonarRuntime = mock(SonarRuntime.class);
+ private CoreExtensionRepository coreExtensionRepository = mock(CoreExtensionRepository.class);
+
+ private ScannerCoreExtensionsInstaller underTest = new ScannerCoreExtensionsInstaller(sonarRuntime, coreExtensionRepository);
+
+ @Test
+ public void install_only_adds_ScannerSide_annotated_extension_to_container() {
+ when(coreExtensionRepository.loadedCoreExtensions()).thenReturn(Stream.of(
+ new CoreExtension() {
+ @Override
+ public String getName() {
+ return "foo";
+ }
+
+ @Override
+ public void load(Context context) {
+ context.addExtensions(CeClass.class, ScannerClass.class, WebServerClass.class,
+ NoAnnotationClass.class, OtherAnnotationClass.class, MultipleAnnotationClass.class);
+ }
+ }));
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.install(container, t -> true);
+
+ assertThat(container.getPicoContainer().getComponentAdapters())
+ .hasSize(ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 2);
+ assertThat(container.getComponentByType(ScannerClass.class)).isNotNull();
+ assertThat(container.getComponentByType(MultipleAnnotationClass.class)).isNotNull();
+ }
+
+ @ComputeEngineSide
+ public static final class CeClass {
+
+ }
+
+ @ServerSide
+ public static final class WebServerClass {
+
+ }
+
+ @ScannerSide
+ public static final class ScannerClass {
+
+ }
+
+ @ServerSide
+ @ComputeEngineSide
+ @ScannerSide
+ public static final class MultipleAnnotationClass {
+
+ }
+
+ public static final class NoAnnotationClass {
+
+ }
+
+ @DarkSide
+ public static final class OtherAnnotationClass {
+
+ }
+
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface DarkSide {
+ }
+
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/ProjectScanContainerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/ProjectScanContainerTest.java
index dd1a77c3d26..8ca3ebf301f 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/ProjectScanContainerTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/ProjectScanContainerTest.java
@@ -24,6 +24,7 @@ import org.sonar.api.BatchExtension;
import org.sonar.api.ServerExtension;
import org.sonar.api.batch.InstantiationStrategy;
import org.sonar.api.task.TaskExtension;
+import org.sonar.scanner.bootstrap.ExtensionMatcher;
import static org.assertj.core.api.Assertions.assertThat;
@@ -31,7 +32,7 @@ public class ProjectScanContainerTest {
@Test
public void should_add_only_batch_extensions() {
- ProjectScanContainer.BatchExtensionFilter filter = new ProjectScanContainer.BatchExtensionFilter();
+ ExtensionMatcher filter = ProjectScanContainer.getBatchPluginExtensionsFilter();
assertThat(filter.accept(new MyBatchExtension())).isTrue();
assertThat(filter.accept(MyBatchExtension.class)).isTrue();