@@ -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, | |||
@@ -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); | |||
} | |||
} |
@@ -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 |
@@ -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 { | |||
} | |||
} |
@@ -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 | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -0,0 +1 @@ | |||
checkstyle.rule1.name=Rule one |
@@ -0,0 +1 @@ | |||
checkstyle.rule1.name=Rule un |
@@ -0,0 +1,2 @@ | |||
any=Tous | |||
empty= |
@@ -0,0 +1 @@ | |||
coreext.rule1.name=Rule one |
@@ -0,0 +1 @@ | |||
coreext.rule1.name=Rule un |
@@ -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')) { |
@@ -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); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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(); | |||
@@ -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); | |||
} | |||
} |
@@ -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) { |
@@ -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); | |||
} | |||
} |
@@ -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(); | |||
} |
@@ -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; | |||
@@ -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 { | |||
} | |||
} |
@@ -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 |
@@ -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"); |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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' | |||
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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"); | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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()); | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
@@ -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); |
@@ -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) { |
@@ -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"); | |||
} | |||
}; | |||
} | |||
} |
@@ -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 { | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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(); |
@@ -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); | |||
} | |||
} |
@@ -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; |
@@ -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 |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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 |
@@ -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 { | |||
} | |||
} |
@@ -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(); |