From: Sébastien Lesaint Date: Fri, 22 Jun 2018 14:08:21 +0000 (+0200) Subject: move some classes in preparation of creation of sonar-ce-common X-Git-Tag: 7.5~926 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=525ed917b32bfd43f2517c03d500d7a557240ce0;p=sonarqube.git move some classes in preparation of creation of sonar-ce-common --- diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/taskprocessor/ReportTaskProcessor.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/taskprocessor/ReportTaskProcessor.java index 512a5d17e00..9bc1c36570e 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/taskprocessor/ReportTaskProcessor.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/taskprocessor/ReportTaskProcessor.java @@ -27,7 +27,7 @@ import org.sonar.ce.task.CeTaskResult; import org.sonar.ce.task.container.TaskContainer; import org.sonar.ce.task.projectanalysis.container.ContainerFactory; import org.sonar.ce.task.step.ComputationStepExecutor; -import org.sonar.ce.taskprocessor.CeTaskProcessor; +import org.sonar.ce.task.taskprocessor.CeTaskProcessor; import org.sonar.ce.taskprocessor.TaskResultHolder; import org.sonar.core.platform.ComponentContainer; import org.sonar.db.ce.CeTaskTypes; diff --git a/server/sonar-ce-task/src/main/java/org/sonar/ce/task/taskprocessor/CeTaskProcessor.java b/server/sonar-ce-task/src/main/java/org/sonar/ce/task/taskprocessor/CeTaskProcessor.java new file mode 100644 index 00000000000..88140137d37 --- /dev/null +++ b/server/sonar-ce-task/src/main/java/org/sonar/ce/task/taskprocessor/CeTaskProcessor.java @@ -0,0 +1,63 @@ +/* + * 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.task.taskprocessor; + +import java.util.Set; +import javax.annotation.CheckForNull; +import org.sonar.api.ce.ComputeEngineSide; +import org.sonar.api.server.ServerSide; +import org.sonar.ce.task.CeTask; +import org.sonar.ce.task.CeTaskResult; + +/** + * This interface is used to provide the processing code for {@link CeTask}s of one or more type to be called by the + * Compute Engine. + */ +@ComputeEngineSide +@ServerSide +public interface CeTaskProcessor { + + /** + * The {@link CeTask#getType()} for which this {@link CeTaskProcessor} provides the processing code. + *

+ * The match of type is done using {@link String#equals(Object)} and if more than one {@link CeTaskProcessor} declares + * itself had handler for the same {@link CeTask#getType()}, an error will be raised at startup and startup will + * fail. + *

+ *

+ * If an empty {@link Set} is returned, the {@link CeTaskProcessor} will be ignored. + *

+ */ + Set getHandledCeTaskTypes(); + + /** + * Calls the processing code for a specific {@link CeTask} which will optionally return a {@link CeTaskResult} + * holding information to be persisted in the processing history of the Compute Engine (currently the {@code CE_ACTIVITY} table). + *

+ * The specified is guaranteed to be non {@code null} and its {@link CeTask#getType()} to be one of the values + * of {@link #getHandledCeTaskTypes()}. + *

+ * + * @throws RuntimeException when thrown, it will be caught and logged by the Compute Engine and the processing of the + * specified {@link CeTask} will be flagged as failed. + */ + @CheckForNull + CeTaskResult process(CeTask task); +} diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/app/CeServer.java b/server/sonar-ce/src/main/java/org/sonar/ce/app/CeServer.java index 41f9c883f09..63b0b4b2b07 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/app/CeServer.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/app/CeServer.java @@ -28,7 +28,7 @@ import org.sonar.api.utils.log.Loggers; import org.sonar.ce.ComputeEngine; import org.sonar.ce.ComputeEngineImpl; import org.sonar.ce.container.ComputeEngineContainerImpl; -import org.sonar.ce.log.CeProcessLogging; +import org.sonar.ce.logging.CeProcessLogging; import org.sonar.process.MinimumViableSystem; import org.sonar.process.Monitored; import org.sonar.process.ProcessEntryPoint; diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java index 1812686e410..275d844fd68 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java @@ -51,7 +51,7 @@ import org.sonar.ce.StandaloneCeDistributedInformation; import org.sonar.ce.async.SynchronousAsyncExecution; import org.sonar.ce.cleaning.CeCleaningModule; import org.sonar.ce.db.ReadOnlyPropertiesDao; -import org.sonar.ce.log.CeProcessLogging; +import org.sonar.ce.logging.CeProcessLogging; import org.sonar.ce.notification.ReportAnalysisFailureNotificationModule; import org.sonar.ce.platform.CECoreExtensionsInstaller; import org.sonar.ce.platform.ComputeEngineExtensionInstaller; diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/logging/CeProcessLogging.java b/server/sonar-ce/src/main/java/org/sonar/ce/logging/CeProcessLogging.java new file mode 100644 index 00000000000..0429c4b168e --- /dev/null +++ b/server/sonar-ce/src/main/java/org/sonar/ce/logging/CeProcessLogging.java @@ -0,0 +1,51 @@ +/* + * 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.logging; + +import ch.qos.logback.classic.Level; +import org.sonar.process.ProcessId; +import org.sonar.process.logging.LogDomain; +import org.sonar.process.logging.LogLevelConfig; +import org.sonar.server.log.ServerProcessLogging; + +import static org.sonar.ce.task.log.CeTaskLogging.MDC_CE_TASK_UUID; + +/** + * Configure logback for the Compute Engine process. Logs are written to file "ce.log" in SQ's log directory. + */ +public class CeProcessLogging extends ServerProcessLogging { + + public CeProcessLogging() { + super(ProcessId.COMPUTE_ENGINE, "%X{" + MDC_CE_TASK_UUID + "}"); + } + + @Override + protected void extendLogLevelConfiguration(LogLevelConfig.Builder logLevelConfigBuilder) { + logLevelConfigBuilder.levelByDomain("sql", ProcessId.COMPUTE_ENGINE, LogDomain.SQL); + logLevelConfigBuilder.levelByDomain("es", ProcessId.COMPUTE_ENGINE, LogDomain.ES); + JMX_RMI_LOGGER_NAMES.forEach(loggerName -> logLevelConfigBuilder.levelByDomain(loggerName, ProcessId.COMPUTE_ENGINE, LogDomain.JMX)); + LOGGER_NAMES_TO_TURN_OFF.forEach(loggerName -> logLevelConfigBuilder.immutableLevel(loggerName, Level.OFF)); + } + + @Override + protected void extendConfigure() { + // nothing to do + } +} diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/notification/package-info.java b/server/sonar-ce/src/main/java/org/sonar/ce/notification/package-info.java index 0e90a860936..a3ca1a7fd1e 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/notification/package-info.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/notification/package-info.java @@ -18,6 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ @ParametersAreNonnullByDefault -package org.sonar.ce.notification; +package org.sonar.ce.task.projectanalysis.notification; import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepository.java b/server/sonar-ce/src/main/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepository.java index 9f9f25112db..2a4e9b5acc8 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepository.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepository.java @@ -21,6 +21,7 @@ package org.sonar.ce.taskprocessor; import java.util.Optional; import org.sonar.ce.task.CeTask; +import org.sonar.ce.task.taskprocessor.CeTaskProcessor; public interface CeTaskProcessorRepository { diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepositoryImpl.java b/server/sonar-ce/src/main/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepositoryImpl.java index ba0c97d34e9..1d5b936bf35 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepositoryImpl.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepositoryImpl.java @@ -30,6 +30,7 @@ import java.util.Map; import java.util.Optional; import javax.annotation.Nonnull; import org.sonar.ce.task.CeTask; +import org.sonar.ce.task.taskprocessor.CeTaskProcessor; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.FluentIterable.from; diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/taskprocessor/CeWorkerImpl.java b/server/sonar-ce/src/main/java/org/sonar/ce/taskprocessor/CeWorkerImpl.java index 06cc90b2546..831d0406913 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/taskprocessor/CeWorkerImpl.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/taskprocessor/CeWorkerImpl.java @@ -31,6 +31,7 @@ import org.sonar.api.utils.log.Loggers; import org.sonar.ce.queue.InternalCeQueue; import org.sonar.ce.task.CeTask; import org.sonar.ce.task.CeTaskResult; +import org.sonar.ce.task.taskprocessor.CeTaskProcessor; import org.sonar.core.util.logs.Profiler; import org.sonar.db.ce.CeActivityDto; diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/logging/CeProcessLoggingTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/logging/CeProcessLoggingTest.java new file mode 100644 index 00000000000..d151152ca30 --- /dev/null +++ b/server/sonar-ce/src/test/java/org/sonar/ce/logging/CeProcessLoggingTest.java @@ -0,0 +1,479 @@ +/* + * 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.logging; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.encoder.PatternLayoutEncoder; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.ConsoleAppender; +import ch.qos.logback.core.FileAppender; +import ch.qos.logback.core.joran.spi.JoranException; +import java.io.File; +import java.io.IOException; +import java.util.Properties; +import java.util.stream.Stream; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.process.Props; +import org.sonar.process.logging.LogbackHelper; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.slf4j.Logger.ROOT_LOGGER_NAME; +import static org.sonar.process.ProcessProperties.Property.PATH_LOGS; + +public class CeProcessLoggingTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private File logDir; + private Props props = new Props(new Properties()); + private CeProcessLogging underTest = new CeProcessLogging(); + + @Before + public void setUp() throws IOException { + logDir = temp.newFolder(); + props.set(PATH_LOGS.getKey(), logDir.getAbsolutePath()); + } + + @AfterClass + public static void resetLogback() throws JoranException { + new LogbackHelper().resetFromXml("/logback-test.xml"); + } + + @Test + public void do_not_log_to_console() { + LoggerContext ctx = underTest.configure(props); + + Logger root = ctx.getLogger(Logger.ROOT_LOGGER_NAME); + Appender appender = root.getAppender("CONSOLE"); + assertThat(appender).isNull(); + } + + @Test + public void startup_logger_prints_to_only_to_system_out() { + LoggerContext ctx = underTest.configure(props); + + Logger startup = ctx.getLogger("startup"); + assertThat(startup.isAdditive()).isFalse(); + Appender appender = startup.getAppender("CONSOLE"); + assertThat(appender).isInstanceOf(ConsoleAppender.class); + ConsoleAppender consoleAppender = (ConsoleAppender) appender; + assertThat(consoleAppender.getTarget()).isEqualTo("System.out"); + assertThat(consoleAppender.getEncoder()).isInstanceOf(PatternLayoutEncoder.class); + PatternLayoutEncoder patternEncoder = (PatternLayoutEncoder) consoleAppender.getEncoder(); + assertThat(patternEncoder.getPattern()).isEqualTo("%d{yyyy.MM.dd HH:mm:ss} %-5level app[][%logger{20}] %msg%n"); + } + + @Test + public void log_to_ce_file() { + LoggerContext ctx = underTest.configure(props); + + Logger root = ctx.getLogger(Logger.ROOT_LOGGER_NAME); + Appender appender = root.getAppender("file_ce"); + assertThat(appender).isInstanceOf(FileAppender.class); + FileAppender fileAppender = (FileAppender) appender; + assertThat(fileAppender.getFile()).isEqualTo(new File(logDir, "ce.log").getAbsolutePath()); + assertThat(fileAppender.getEncoder()).isInstanceOf(PatternLayoutEncoder.class); + PatternLayoutEncoder encoder = (PatternLayoutEncoder) fileAppender.getEncoder(); + assertThat(encoder.getPattern()).isEqualTo("%d{yyyy.MM.dd HH:mm:ss} %-5level ce[%X{ceTaskUuid}][%logger{20}] %msg%n"); + } + + @Test + public void default_level_for_root_logger_is_INFO() { + LoggerContext ctx = underTest.configure(props); + + verifyRootLogLevel(ctx, Level.INFO); + } + + @Test + public void root_logger_level_changes_with_global_property() { + props.set("sonar.log.level", "TRACE"); + + LoggerContext ctx = underTest.configure(props); + + verifyRootLogLevel(ctx, Level.TRACE); + } + + @Test + public void root_logger_level_changes_with_ce_property() { + props.set("sonar.log.level.ce", "TRACE"); + + LoggerContext ctx = underTest.configure(props); + + verifyRootLogLevel(ctx, Level.TRACE); + } + + @Test + public void root_logger_level_is_configured_from_ce_property_over_global_property() { + props.set("sonar.log.level", "TRACE"); + props.set("sonar.log.level.ce", "DEBUG"); + + LoggerContext ctx = underTest.configure(props); + + verifyRootLogLevel(ctx, Level.DEBUG); + } + + @Test + public void root_logger_level_changes_with_ce_property_and_is_case_insensitive() { + props.set("sonar.log.level.ce", "debug"); + + LoggerContext ctx = underTest.configure(props); + + verifyRootLogLevel(ctx, Level.DEBUG); + } + + @Test + public void sql_logger_level_changes_with_global_property_and_is_case_insensitive() { + props.set("sonar.log.level", "InFO"); + + LoggerContext ctx = underTest.configure(props); + + verifySqlLogLevel(ctx, Level.INFO); + } + + @Test + public void sql_logger_level_changes_with_ce_property_and_is_case_insensitive() { + props.set("sonar.log.level.ce", "TrACe"); + + LoggerContext ctx = underTest.configure(props); + + verifySqlLogLevel(ctx, Level.TRACE); + } + + @Test + public void sql_logger_level_changes_with_ce_sql_property_and_is_case_insensitive() { + props.set("sonar.log.level.ce.sql", "debug"); + + LoggerContext ctx = underTest.configure(props); + + verifySqlLogLevel(ctx, Level.DEBUG); + } + + @Test + public void sql_logger_level_is_configured_from_ce_sql_property_over_ce_property() { + props.set("sonar.log.level.ce.sql", "debug"); + props.set("sonar.log.level.ce", "TRACE"); + + LoggerContext ctx = underTest.configure(props); + + verifySqlLogLevel(ctx, Level.DEBUG); + } + + @Test + public void sql_logger_level_is_configured_from_ce_sql_property_over_global_property() { + props.set("sonar.log.level.ce.sql", "debug"); + props.set("sonar.log.level", "TRACE"); + + LoggerContext ctx = underTest.configure(props); + + verifySqlLogLevel(ctx, Level.DEBUG); + } + + @Test + public void sql_logger_level_is_configured_from_ce_property_over_global_property() { + props.set("sonar.log.level.ce", "debug"); + props.set("sonar.log.level", "TRACE"); + + LoggerContext ctx = underTest.configure(props); + + verifySqlLogLevel(ctx, Level.DEBUG); + } + + @Test + public void es_logger_level_changes_with_global_property_and_is_case_insensitive() { + props.set("sonar.log.level", "InFO"); + + LoggerContext ctx = underTest.configure(props); + + verifyEsLogLevel(ctx, Level.INFO); + } + + @Test + public void es_logger_level_changes_with_ce_property_and_is_case_insensitive() { + props.set("sonar.log.level.ce", "TrACe"); + + LoggerContext ctx = underTest.configure(props); + + verifyEsLogLevel(ctx, Level.TRACE); + } + + @Test + public void es_logger_level_changes_with_ce_es_property_and_is_case_insensitive() { + props.set("sonar.log.level.ce.es", "debug"); + + LoggerContext ctx = underTest.configure(props); + + verifyEsLogLevel(ctx, Level.DEBUG); + } + + @Test + public void es_logger_level_is_configured_from_ce_es_property_over_ce_property() { + props.set("sonar.log.level.ce.es", "debug"); + props.set("sonar.log.level.ce", "TRACE"); + + LoggerContext ctx = underTest.configure(props); + + verifyEsLogLevel(ctx, Level.DEBUG); + } + + @Test + public void es_logger_level_is_configured_from_ce_es_property_over_global_property() { + props.set("sonar.log.level.ce.es", "debug"); + props.set("sonar.log.level", "TRACE"); + + LoggerContext ctx = underTest.configure(props); + + verifyEsLogLevel(ctx, Level.DEBUG); + } + + @Test + public void es_logger_level_is_configured_from_ce_property_over_global_property() { + props.set("sonar.log.level.ce", "debug"); + props.set("sonar.log.level", "TRACE"); + + LoggerContext ctx = underTest.configure(props); + + verifyEsLogLevel(ctx, Level.DEBUG); + } + + @Test + public void jmx_logger_level_changes_with_global_property_and_is_case_insensitive() { + props.set("sonar.log.level", "InFO"); + + LoggerContext ctx = underTest.configure(props); + + verifyJmxLogLevel(ctx, Level.INFO); + } + + @Test + public void jmx_logger_level_changes_with_jmx_property_and_is_case_insensitive() { + props.set("sonar.log.level.ce", "TrACe"); + + LoggerContext ctx = underTest.configure(props); + + verifyJmxLogLevel(ctx, Level.TRACE); + } + + @Test + public void jmx_logger_level_changes_with_ce_jmx_property_and_is_case_insensitive() { + props.set("sonar.log.level.ce.jmx", "debug"); + + LoggerContext ctx = underTest.configure(props); + + verifyJmxLogLevel(ctx, Level.DEBUG); + } + + @Test + public void jmx_logger_level_is_configured_from_ce_jmx_property_over_ce_property() { + props.set("sonar.log.level.ce.jmx", "debug"); + props.set("sonar.log.level.ce", "TRACE"); + + LoggerContext ctx = underTest.configure(props); + + verifyJmxLogLevel(ctx, Level.DEBUG); + } + + @Test + public void jmx_logger_level_is_configured_from_ce_jmx_property_over_global_property() { + props.set("sonar.log.level.ce.jmx", "debug"); + props.set("sonar.log.level", "TRACE"); + + LoggerContext ctx = underTest.configure(props); + + verifyJmxLogLevel(ctx, Level.DEBUG); + } + + @Test + public void jmx_logger_level_is_configured_from_ce_property_over_global_property() { + props.set("sonar.log.level.ce", "debug"); + props.set("sonar.log.level", "TRACE"); + + LoggerContext ctx = underTest.configure(props); + + verifyJmxLogLevel(ctx, Level.DEBUG); + } + + @Test + public void root_logger_level_defaults_to_INFO_if_ce_property_has_invalid_value() { + props.set("sonar.log.level.ce", "DodoDouh!"); + + LoggerContext ctx = underTest.configure(props); + verifyRootLogLevel(ctx, Level.INFO); + } + + @Test + public void sql_logger_level_defaults_to_INFO_if_ce_sql_property_has_invalid_value() { + props.set("sonar.log.level.ce.sql", "DodoDouh!"); + + LoggerContext ctx = underTest.configure(props); + verifySqlLogLevel(ctx, Level.INFO); + } + + @Test + public void es_logger_level_defaults_to_INFO_if_ce_es_property_has_invalid_value() { + props.set("sonar.log.level.ce.es", "DodoDouh!"); + + LoggerContext ctx = underTest.configure(props); + verifyEsLogLevel(ctx, Level.INFO); + } + + @Test + public void jmx_loggers_level_defaults_to_INFO_if_ce_jmx_property_has_invalid_value() { + props.set("sonar.log.level.ce.jmx", "DodoDouh!"); + + LoggerContext ctx = underTest.configure(props); + verifyJmxLogLevel(ctx, Level.INFO); + } + + @Test + public void fail_with_IAE_if_global_property_unsupported_level() { + props.set("sonar.log.level", "ERROR"); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("log level ERROR in property sonar.log.level is not a supported value (allowed levels are [TRACE, DEBUG, INFO])"); + + underTest.configure(props); + } + + @Test + public void fail_with_IAE_if_ce_property_unsupported_level() { + props.set("sonar.log.level.ce", "ERROR"); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("log level ERROR in property sonar.log.level.ce is not a supported value (allowed levels are [TRACE, DEBUG, INFO])"); + + underTest.configure(props); + } + + @Test + public void fail_with_IAE_if_ce_sql_property_unsupported_level() { + props.set("sonar.log.level.ce.sql", "ERROR"); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("log level ERROR in property sonar.log.level.ce.sql is not a supported value (allowed levels are [TRACE, DEBUG, INFO])"); + + underTest.configure(props); + } + + @Test + public void fail_with_IAE_if_ce_es_property_unsupported_level() { + props.set("sonar.log.level.ce.es", "ERROR"); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("log level ERROR in property sonar.log.level.ce.es is not a supported value (allowed levels are [TRACE, DEBUG, INFO])"); + + underTest.configure(props); + } + + @Test + public void fail_with_IAE_if_ce_jmx_property_unsupported_level() { + props.set("sonar.log.level.ce.jmx", "ERROR"); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("log level ERROR in property sonar.log.level.ce.jmx is not a supported value (allowed levels are [TRACE, DEBUG, INFO])"); + + underTest.configure(props); + } + + @Test + public void configure_defines_hardcoded_levels() { + LoggerContext context = underTest.configure(props); + + verifyImmutableLogLevels(context); + } + + @Test + public void configure_defines_hardcoded_levels_unchanged_by_global_property() { + props.set("sonar.log.level", "TRACE"); + + LoggerContext context = underTest.configure(props); + + verifyImmutableLogLevels(context); + } + + @Test + public void configure_defines_hardcoded_levels_unchanged_by_ce_property() { + props.set("sonar.log.level.ce", "TRACE"); + + LoggerContext context = underTest.configure(props); + + verifyImmutableLogLevels(context); + } + + @Test + public void configure_turns_off_some_MsSQL_driver_logger() { + LoggerContext context = underTest.configure(props); + + Stream.of("com.microsoft.sqlserver.jdbc.internals", + "com.microsoft.sqlserver.jdbc.ResultSet", + "com.microsoft.sqlserver.jdbc.Statement", + "com.microsoft.sqlserver.jdbc.Connection") + .forEach(loggerName -> assertThat(context.getLogger(loggerName).getLevel()).isEqualTo(Level.OFF)); + } + + private void verifyRootLogLevel(LoggerContext ctx, Level expected) { + assertThat(ctx.getLogger(ROOT_LOGGER_NAME).getLevel()).isEqualTo(expected); + } + + private void verifySqlLogLevel(LoggerContext ctx, Level expected) { + assertThat(ctx.getLogger("sql").getLevel()).isEqualTo(expected); + } + + private void verifyEsLogLevel(LoggerContext ctx, Level expected) { + assertThat(ctx.getLogger("es").getLevel()).isEqualTo(expected); + } + + private void verifyJmxLogLevel(LoggerContext ctx, Level expected) { + assertThat(ctx.getLogger("javax.management.remote.timeout").getLevel()).isEqualTo(expected); + assertThat(ctx.getLogger("javax.management.remote.misc").getLevel()).isEqualTo(expected); + assertThat(ctx.getLogger("javax.management.remote.rmi").getLevel()).isEqualTo(expected); + assertThat(ctx.getLogger("javax.management.mbeanserver").getLevel()).isEqualTo(expected); + assertThat(ctx.getLogger("sun.rmi.loader").getLevel()).isEqualTo(expected); + assertThat(ctx.getLogger("sun.rmi.transport.tcp").getLevel()).isEqualTo(expected); + assertThat(ctx.getLogger("sun.rmi.transport.misc").getLevel()).isEqualTo(expected); + assertThat(ctx.getLogger("sun.rmi.server.call").getLevel()).isEqualTo(expected); + assertThat(ctx.getLogger("sun.rmi.dgc").getLevel()).isEqualTo(expected); + } + + private void verifyImmutableLogLevels(LoggerContext ctx) { + assertThat(ctx.getLogger("org.apache.ibatis").getLevel()).isEqualTo(Level.WARN); + assertThat(ctx.getLogger("java.sql").getLevel()).isEqualTo(Level.WARN); + assertThat(ctx.getLogger("java.sql.ResultSet").getLevel()).isEqualTo(Level.WARN); + assertThat(ctx.getLogger("org.sonar.MEASURE_FILTER").getLevel()).isEqualTo(Level.WARN); + assertThat(ctx.getLogger("org.elasticsearch").getLevel()).isEqualTo(Level.INFO); + assertThat(ctx.getLogger("org.elasticsearch.node").getLevel()).isEqualTo(Level.INFO); + assertThat(ctx.getLogger("org.elasticsearch.http").getLevel()).isEqualTo(Level.INFO); + assertThat(ctx.getLogger("ch.qos.logback").getLevel()).isEqualTo(Level.WARN); + assertThat(ctx.getLogger("org.apache.catalina").getLevel()).isEqualTo(Level.INFO); + assertThat(ctx.getLogger("org.apache.coyote").getLevel()).isEqualTo(Level.INFO); + assertThat(ctx.getLogger("org.apache.jasper").getLevel()).isEqualTo(Level.INFO); + assertThat(ctx.getLogger("org.apache.tomcat").getLevel()).isEqualTo(Level.INFO); + } +} diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepositoryImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepositoryImplTest.java index 04cdeac6834..4108af81183 100644 --- a/server/sonar-ce/src/test/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepositoryImplTest.java +++ b/server/sonar-ce/src/test/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepositoryImplTest.java @@ -26,6 +26,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.ce.task.CeTask; import org.sonar.ce.task.CeTaskResult; +import org.sonar.ce.task.taskprocessor.CeTaskProcessor; import static org.assertj.core.api.Assertions.assertThat; diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepositoryRule.java b/server/sonar-ce/src/test/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepositoryRule.java index 451ebf1fefd..036ccd52ab9 100644 --- a/server/sonar-ce/src/test/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepositoryRule.java +++ b/server/sonar-ce/src/test/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepositoryRule.java @@ -26,6 +26,7 @@ import java.util.Set; import org.junit.rules.ExternalResource; import org.sonar.ce.task.CeTask; import org.sonar.ce.task.CeTaskResult; +import org.sonar.ce.task.taskprocessor.CeTaskProcessor; import static com.google.common.base.Preconditions.checkState; import static java.util.Objects.requireNonNull; diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/log/ServerProcessLogging.java b/server/sonar-server-common/src/main/java/org/sonar/server/log/ServerProcessLogging.java new file mode 100644 index 00000000000..ad6711df615 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/log/ServerProcessLogging.java @@ -0,0 +1,142 @@ +/* + * 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.log; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.ConsoleAppender; +import com.google.common.collect.ImmutableSet; +import java.util.Set; +import org.sonar.process.ProcessId; +import org.sonar.process.Props; +import org.sonar.process.logging.LogLevelConfig; +import org.sonar.process.logging.LogbackHelper; +import org.sonar.process.logging.RootLoggerConfig; + +import static org.sonar.process.logging.RootLoggerConfig.newRootLoggerConfigBuilder; + +public abstract class ServerProcessLogging { + + public static final String STARTUP_LOGGER_NAME = "startup"; + protected static final Set JMX_RMI_LOGGER_NAMES = ImmutableSet.of( + "javax.management.remote.timeout", + "javax.management.remote.misc", + "javax.management.remote.rmi", + "javax.management.mbeanserver", + "sun.rmi.loader", + "sun.rmi.transport.tcp", + "sun.rmi.transport.misc", + "sun.rmi.server.call", + "sun.rmi.dgc"); + protected static final Set LOGGER_NAMES_TO_TURN_OFF = ImmutableSet.of( + // mssql driver + "com.microsoft.sqlserver.jdbc.internals", + "com.microsoft.sqlserver.jdbc.ResultSet", + "com.microsoft.sqlserver.jdbc.Statement", + "com.microsoft.sqlserver.jdbc.Connection"); + private final ProcessId processId; + private final String threadIdFieldPattern; + private final LogbackHelper helper = new LogbackHelper(); + private final LogLevelConfig logLevelConfig; + + protected ServerProcessLogging(ProcessId processId, String threadIdFieldPattern) { + this.processId = processId; + this.threadIdFieldPattern = threadIdFieldPattern; + this.logLevelConfig = createLogLevelConfiguration(processId); + } + + private LogLevelConfig createLogLevelConfiguration(ProcessId processId) { + LogLevelConfig.Builder builder = LogLevelConfig.newBuilder(helper.getRootLoggerName()); + builder.rootLevelFor(processId); + builder.immutableLevel("org.apache.ibatis", Level.WARN); + builder.immutableLevel("java.sql", Level.WARN); + builder.immutableLevel("java.sql.ResultSet", Level.WARN); + builder.immutableLevel("org.sonar.MEASURE_FILTER", Level.WARN); + builder.immutableLevel("org.elasticsearch", Level.INFO); + builder.immutableLevel("org.elasticsearch.node", Level.INFO); + builder.immutableLevel("org.elasticsearch.http", Level.INFO); + builder.immutableLevel("ch.qos.logback", Level.WARN); + builder.immutableLevel("org.apache.catalina", Level.INFO); + builder.immutableLevel("org.apache.coyote", Level.INFO); + builder.immutableLevel("org.apache.jasper", Level.INFO); + builder.immutableLevel("org.apache.tomcat", Level.INFO); + builder.immutableLevel("org.postgresql.core.v3.QueryExecutorImpl", Level.INFO); + builder.immutableLevel("org.postgresql.jdbc.PgConnection", Level.INFO); + + extendLogLevelConfiguration(builder); + + return builder.build(); + } + + public LoggerContext configure(Props props) { + LoggerContext ctx = helper.getRootContext(); + ctx.reset(); + + configureRootLogger(props); + helper.apply(logLevelConfig, props); + configureDirectToConsoleLoggers(ctx, STARTUP_LOGGER_NAME); + extendConfigure(); + + helper.enableJulChangePropagation(ctx); + + return ctx; + } + + public LogLevelConfig getLogLevelConfig() { + return this.logLevelConfig; + } + + protected abstract void extendLogLevelConfiguration(LogLevelConfig.Builder logLevelConfigBuilder); + + protected abstract void extendConfigure(); + + private void configureRootLogger(Props props) { + RootLoggerConfig config = newRootLoggerConfigBuilder() + .setProcessId(processId) + .setThreadIdFieldPattern(threadIdFieldPattern) + .build(); + String logPattern = helper.buildLogPattern(config); + + helper.configureGlobalFileLog(props, config, logPattern); + helper.configureForSubprocessGobbler(props, logPattern); + } + + /** + * Setup one or more specified loggers to be non additive and to print to System.out which will be caught by the Main + * Process and written to sonar.log. + */ + private void configureDirectToConsoleLoggers(LoggerContext context, String... loggerNames) { + RootLoggerConfig config = newRootLoggerConfigBuilder() + .setProcessId(ProcessId.APP) + .setThreadIdFieldPattern("") + .build(); + String logPattern = helper.buildLogPattern(config); + ConsoleAppender consoleAppender = helper.newConsoleAppender(context, "CONSOLE", logPattern); + + for (String loggerName : loggerNames) { + Logger consoleLogger = context.getLogger(loggerName); + consoleLogger.setAdditive(false); + consoleLogger.addAppender(consoleAppender); + } + } + +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/log/package-info.java b/server/sonar-server-common/src/main/java/org/sonar/server/log/package-info.java new file mode 100644 index 00000000000..53c0093ae8d --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/log/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.log; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/notification/DefaultNotificationManager.java b/server/sonar-server-common/src/main/java/org/sonar/server/notification/DefaultNotificationManager.java new file mode 100644 index 00000000000..932c8475394 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/notification/DefaultNotificationManager.java @@ -0,0 +1,217 @@ +/* + * 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.notification; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Multimap; +import java.io.IOException; +import java.io.InvalidClassException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nullable; +import org.sonar.api.notifications.Notification; +import org.sonar.api.notifications.NotificationChannel; +import org.sonar.api.utils.SonarException; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.notification.NotificationQueueDto; +import org.sonar.db.property.Subscriber; + +import static java.util.Collections.singletonList; +import static java.util.Objects.requireNonNull; + +public class DefaultNotificationManager implements NotificationManager { + + private static final Logger LOG = Loggers.get(DefaultNotificationManager.class); + + private static final String UNABLE_TO_READ_NOTIFICATION = "Unable to read notification"; + + private NotificationChannel[] notificationChannels; + private final DbClient dbClient; + + private boolean alreadyLoggedDeserializationIssue = false; + + /** + * Default constructor used by Pico + */ + public DefaultNotificationManager(NotificationChannel[] channels, + DbClient dbClient) { + this.notificationChannels = channels; + this.dbClient = dbClient; + } + + /** + * {@inheritDoc} + */ + @Override + public void scheduleForSending(Notification notification) { + NotificationQueueDto dto = NotificationQueueDto.toNotificationQueueDto(notification); + dbClient.notificationQueueDao().insert(singletonList(dto)); + } + + /** + * Give the notification queue so that it can be processed + */ + public Notification getFromQueue() { + int batchSize = 1; + List notificationDtos = dbClient.notificationQueueDao().selectOldest(batchSize); + if (notificationDtos.isEmpty()) { + return null; + } + dbClient.notificationQueueDao().delete(notificationDtos); + + return convertToNotification(notificationDtos); + } + + private Notification convertToNotification(List notifications) { + try { + // If batchSize is increased then we should return a list instead of a single element + return notifications.get(0).toNotification(); + } catch (InvalidClassException e) { + // SONAR-4739 + if (!alreadyLoggedDeserializationIssue) { + logDeserializationIssue(); + alreadyLoggedDeserializationIssue = true; + } + return null; + } catch (IOException | ClassNotFoundException e) { + throw new SonarException(UNABLE_TO_READ_NOTIFICATION, e); + } + } + + @VisibleForTesting + void logDeserializationIssue() { + LOG.warn("It is impossible to send pending notifications which existed prior to the upgrade of SonarQube. They will be ignored."); + } + + public long count() { + return dbClient.notificationQueueDao().count(); + } + + /** + * {@inheritDoc} + */ + @Override + public Multimap findSubscribedRecipientsForDispatcher(NotificationDispatcher dispatcher, + String projectKey, SubscriberPermissionsOnProject subscriberPermissionsOnProject) { + requireNonNull(projectKey, "projectKey is mandatory"); + String dispatcherKey = dispatcher.getKey(); + + Set subscriberAndChannels = Arrays.stream(notificationChannels) + .flatMap(notificationChannel -> toSubscriberAndChannels(dispatcherKey, projectKey, notificationChannel)) + .collect(Collectors.toSet()); + + if (subscriberAndChannels.isEmpty()) { + return ImmutableMultimap.of(); + } + + ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); + try (DbSession dbSession = dbClient.openSession(false)) { + Set authorizedLogins = keepAuthorizedLogins(dbSession, projectKey, subscriberAndChannels, subscriberPermissionsOnProject); + subscriberAndChannels.stream() + .filter(subscriberAndChannel -> authorizedLogins.contains(subscriberAndChannel.getSubscriber().getLogin())) + .forEach(subscriberAndChannel -> builder.put(subscriberAndChannel.getSubscriber().getLogin(), subscriberAndChannel.getChannel())); + } + return builder.build(); + } + + private Stream toSubscriberAndChannels(String dispatcherKey, String projectKey, NotificationChannel notificationChannel) { + Set usersForNotification = dbClient.propertiesDao().findUsersForNotification(dispatcherKey, notificationChannel.getKey(), projectKey); + return usersForNotification + .stream() + .map(login -> new SubscriberAndChannel(login, notificationChannel)); + } + + private Set keepAuthorizedLogins(DbSession dbSession, String projectKey, Set subscriberAndChannels, + SubscriberPermissionsOnProject requiredPermissions) { + if (requiredPermissions.getGlobalSubscribers().equals(requiredPermissions.getProjectSubscribers())) { + return keepAuthorizedLogins(dbSession, projectKey, subscriberAndChannels, null, requiredPermissions.getGlobalSubscribers()); + } else { + return Stream + .concat( + keepAuthorizedLogins(dbSession, projectKey, subscriberAndChannels, true, requiredPermissions.getGlobalSubscribers()).stream(), + keepAuthorizedLogins(dbSession, projectKey, subscriberAndChannels, false, requiredPermissions.getProjectSubscribers()).stream()) + .collect(Collectors.toSet()); + } + } + + private Set keepAuthorizedLogins(DbSession dbSession, String projectKey, Set subscriberAndChannels, + @Nullable Boolean global, String permission) { + Set logins = subscriberAndChannels.stream() + .filter(s -> global == null || s.getSubscriber().isGlobal() == global) + .map(s -> s.getSubscriber().getLogin()) + .collect(Collectors.toSet()); + if (logins.isEmpty()) { + return Collections.emptySet(); + } + return dbClient.authorizationDao().keepAuthorizedLoginsOnProject(dbSession, logins, projectKey, permission); + } + + private static final class SubscriberAndChannel { + private final Subscriber subscriber; + private final NotificationChannel channel; + + private SubscriberAndChannel(Subscriber subscriber, NotificationChannel channel) { + this.subscriber = subscriber; + this.channel = channel; + } + + Subscriber getSubscriber() { + return subscriber; + } + + NotificationChannel getChannel() { + return channel; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SubscriberAndChannel that = (SubscriberAndChannel) o; + return Objects.equals(subscriber, that.subscriber) && + Objects.equals(channel, that.channel); + } + + @Override + public int hashCode() { + return Objects.hash(subscriber, channel); + } + } + + @VisibleForTesting + protected List getChannels() { + return Arrays.asList(notificationChannels); + } + +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/notification/NotificationDispatcher.java b/server/sonar-server-common/src/main/java/org/sonar/server/notification/NotificationDispatcher.java new file mode 100644 index 00000000000..2c236373c6a --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/notification/NotificationDispatcher.java @@ -0,0 +1,122 @@ +/* + * 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.notification; + +import org.apache.commons.lang.StringUtils; +import org.sonar.api.ExtensionPoint; +import org.sonar.api.ce.ComputeEngineSide; +import org.sonar.api.notifications.Notification; +import org.sonar.api.notifications.NotificationChannel; +import org.sonar.api.server.ServerSide; + +/** + *

+ * Plugins should extend this class to provide logic to determine which users are interested in receiving notifications, + * along with which delivery channels they selected. + *

+ * For example: + *
    + *
  • notify me by email when someone comments an issue reported by me
  • + *
  • notify me by twitter when someone comments an issue assigned to me
  • + *
  • notify me by Jabber when someone mentions me in an issue comment
  • + *
  • send me by SMS when there are system notifications (like password reset, account creation, ...)
  • + *
+ */ +@ServerSide +@ComputeEngineSide +@ExtensionPoint +public abstract class NotificationDispatcher { + + private final String notificationType; + + /** + * Additional information related to the notification, which will be used + * to know who should receive the notification. + */ + public interface Context { + /** + * Adds a user that will be notified through the given notification channel. + * + * @param userLogin the user login + * @param notificationChannel the notification channel to use for this user + */ + void addUser(String userLogin, NotificationChannel notificationChannel); + } + + /** + * Creates a new dispatcher for notifications of the given type. + * + * @param notificationType the type of notifications handled by this dispatcher + */ + public NotificationDispatcher(String notificationType) { + this.notificationType = notificationType; + } + + /** + * Creates a new generic dispatcher, used for any kind of notification. + *

+ * Should be avoided and replaced by the other constructor - as it is easier to understand that a + * dispatcher listens for a specific type of notification. + */ + public NotificationDispatcher() { + this(""); + } + + /** + * The unique key of this dispatcher. By default it's the class name without the package prefix. + *

+ * The related label in l10n bundles is 'notification.dispatcher.', for example 'notification.dispatcher.NewFalsePositive'. + */ + public String getKey() { + return getClass().getSimpleName(); + } + + /** + * @since 5.1 + */ + public String getType() { + return notificationType; + } + + /** + *

+ * Performs the dispatch. + *

+ */ + public final void performDispatch(Notification notification, Context context) { + if (StringUtils.equals(notification.getType(), notificationType) || StringUtils.equals("", notificationType)) { + dispatch(notification, context); + } + } + + /** + *

+ * Implements the logic that defines which users will receive the notification. + *

+ * The purpose of this method is to populate the context object with users, based on the type of notification and the content of the notification. + */ + public abstract void dispatch(Notification notification, Context context); + + @Override + public String toString() { + return getKey(); + } + +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/notification/NotificationDispatcherMetadata.java b/server/sonar-server-common/src/main/java/org/sonar/server/notification/NotificationDispatcherMetadata.java new file mode 100644 index 00000000000..aceed9efa88 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/notification/NotificationDispatcherMetadata.java @@ -0,0 +1,102 @@ +/* + * 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.notification; + +import com.google.common.collect.Maps; +import java.util.Map; +import org.sonar.api.ce.ComputeEngineSide; +import org.sonar.api.server.ServerSide; + +/** + * Notification dispatchers (see {@link NotificationDispatcher}) can define their own metadata class in order + * to tell more about them. + *

+ * Instances of these classes must be declared by {@link org.sonar.api.Plugin}. + *

+ */ +@ServerSide +@ComputeEngineSide +public final class NotificationDispatcherMetadata { + + public static final String GLOBAL_NOTIFICATION = "globalNotification"; + public static final String PER_PROJECT_NOTIFICATION = "perProjectNotification"; + + private String dispatcherKey; + private Map properties; + + private NotificationDispatcherMetadata(String dispatcherKey) { + this.dispatcherKey = dispatcherKey; + this.properties = Maps.newHashMap(); + } + + /** + * Creates a new metadata instance for the given dispatcher. + *

+ * By default the key is the class name without package. It can be changed by overriding + * {@link NotificationDispatcher#getKey()}. + */ + public static NotificationDispatcherMetadata create(String dispatcherKey) { + return new NotificationDispatcherMetadata(dispatcherKey); + } + + /** + * Sets a property on this metadata object. + */ + public NotificationDispatcherMetadata setProperty(String key, String value) { + properties.put(key, value); + return this; + } + + /** + * Gives the property for the given key. + */ + public String getProperty(String key) { + return properties.get(key); + } + + /** + * Returns the unique key of the dispatcher. + */ + public String getDispatcherKey() { + return dispatcherKey; + } + + @Override + public String toString() { + return dispatcherKey; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NotificationDispatcherMetadata that = (NotificationDispatcherMetadata) o; + return dispatcherKey.equals(that.dispatcherKey); + } + + @Override + public int hashCode() { + return dispatcherKey.hashCode(); + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/notification/NotificationManager.java b/server/sonar-server-common/src/main/java/org/sonar/server/notification/NotificationManager.java new file mode 100644 index 00000000000..e7a4ab23501 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/notification/NotificationManager.java @@ -0,0 +1,104 @@ +/* + * 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.notification; + +import com.google.common.collect.Multimap; +import java.util.Objects; +import org.sonar.api.notifications.Notification; +import org.sonar.api.notifications.NotificationChannel; +import org.sonar.api.web.UserRole; + +import static java.util.Objects.requireNonNull; + +/** + * The notification manager receives notifications and is in charge of storing them so that they are processed by the notification service. + *

+ * Pico provides an instance of this class, and plugins just need to create notifications and pass them to this manager with + * the {@link NotificationManager#scheduleForSending(Notification)} method. + *

+ */ +public interface NotificationManager { + + /** + * Receives a notification and stores it so that it is processed by the notification service. + * + * @param notification the notification. + */ + void scheduleForSending(Notification notification); + + /** + *

+ * Returns the list of users who subscribed to the given dispatcher, along with the notification channels (email, twitter, ...) that they choose + * for this dispatcher. + *

+ *

+ * The resource ID can be null in case of notifications that have nothing to do with a specific project (like system notifications). + *

+ * + * @param dispatcher the dispatcher for which this list of users is requested + * @param projectUuid UUID of the project + * @param subscriberPermissionsOnProject the required permission for global and project subscribers + * + * @return the list of user login along with the subscribed channels + */ + Multimap findSubscribedRecipientsForDispatcher(NotificationDispatcher dispatcher, String projectUuid, + SubscriberPermissionsOnProject subscriberPermissionsOnProject); + + final class SubscriberPermissionsOnProject { + public static final SubscriberPermissionsOnProject ALL_MUST_HAVE_ROLE_USER = new SubscriberPermissionsOnProject(UserRole.USER); + + private final String globalSubscribers; + private final String projectSubscribers; + + public SubscriberPermissionsOnProject(String globalAndProjectSubscribers) { + this(globalAndProjectSubscribers, globalAndProjectSubscribers); + } + + public SubscriberPermissionsOnProject(String globalSubscribers, String projectSubscribers) { + this.globalSubscribers = requireNonNull(globalSubscribers, "global subscribers's required permission can't be null"); + this.projectSubscribers = requireNonNull(projectSubscribers, "project subscribers's required permission can't be null"); + } + + public String getGlobalSubscribers() { + return globalSubscribers; + } + + public String getProjectSubscribers() { + return projectSubscribers; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SubscriberPermissionsOnProject that = (SubscriberPermissionsOnProject) o; + return globalSubscribers.equals(that.globalSubscribers) && projectSubscribers.equals(that.projectSubscribers); + } + + @Override + public int hashCode() { + return Objects.hash(globalSubscribers, projectSubscribers); + } + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/notification/package-info.java b/server/sonar-server-common/src/main/java/org/sonar/server/notification/package-info.java new file mode 100644 index 00000000000..f6a66524408 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/notification/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.notification; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/organization/DefaultOrganization.java b/server/sonar-server-common/src/main/java/org/sonar/server/organization/DefaultOrganization.java new file mode 100644 index 00000000000..24cce29fca9 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/organization/DefaultOrganization.java @@ -0,0 +1,110 @@ +/* + * 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.organization; + +import static java.util.Objects.requireNonNull; + +public class DefaultOrganization { + private final String uuid; + private final String key; + private final String name; + private final long createdAt; + private final long updatedAt; + + private DefaultOrganization(Builder builder) { + this.uuid = requireNonNull(builder.uuid, "uuid can't be null"); + this.key = requireNonNull(builder.key, "key can't be null"); + this.name = requireNonNull(builder.name, "name can't be null"); + this.createdAt = requireNonNull(builder.createdAt, "createdAt can't be null"); + this.updatedAt = requireNonNull(builder.updatedAt, "updatedAt can't be null"); + } + + public static Builder newBuilder() { + return new Builder(); + } + + public String getUuid() { + return uuid; + } + + public String getKey() { + return key; + } + + public String getName() { + return name; + } + + public long getCreatedAt() { + return createdAt; + } + + public long getUpdatedAt() { + return updatedAt; + } + + @Override + public String toString() { + return "DefaultOrganization{" + + "uuid='" + uuid + '\'' + + ", key='" + key + '\'' + + ", name='" + name + '\'' + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } + + public static final class Builder { + private String uuid; + private String key; + private String name; + private Long createdAt; + private Long updatedAt; + + public Builder setUuid(String uuid) { + this.uuid = uuid; + return this; + } + + public Builder setKey(String key) { + this.key = key; + return this; + } + + public Builder setName(String name) { + this.name = name; + return this; + } + + public Builder setCreatedAt(long createdAt) { + this.createdAt = createdAt; + return this; + } + + public Builder setUpdatedAt(long updatedAt) { + this.updatedAt = updatedAt; + return this; + } + + public DefaultOrganization build() { + return new DefaultOrganization(this); + } + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/organization/DefaultOrganizationCache.java b/server/sonar-server-common/src/main/java/org/sonar/server/organization/DefaultOrganizationCache.java new file mode 100644 index 00000000000..76168e963c6 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/organization/DefaultOrganizationCache.java @@ -0,0 +1,33 @@ +/* + * 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.organization; + +public interface DefaultOrganizationCache { + + /** + * Loads {@link DefaultOrganization} in cache. + */ + void load(); + + /** + * Unloads {@link DefaultOrganization} from cache. + */ + void unload(); +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/organization/DefaultOrganizationProvider.java b/server/sonar-server-common/src/main/java/org/sonar/server/organization/DefaultOrganizationProvider.java new file mode 100644 index 00000000000..4cd76b2d623 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/organization/DefaultOrganizationProvider.java @@ -0,0 +1,28 @@ +/* + * 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.organization; + +public interface DefaultOrganizationProvider { + /** + * @throws IllegalStateException if there is no default organization + */ + DefaultOrganization get(); + +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/organization/DefaultOrganizationProviderImpl.java b/server/sonar-server-common/src/main/java/org/sonar/server/organization/DefaultOrganizationProviderImpl.java new file mode 100644 index 00000000000..72ef09bed1f --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/organization/DefaultOrganizationProviderImpl.java @@ -0,0 +1,97 @@ +/* + * 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.organization; + +import java.util.Optional; +import java.util.function.Supplier; +import javax.annotation.CheckForNull; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.server.property.InternalProperties; + +import static com.google.common.base.Preconditions.checkState; + +public class DefaultOrganizationProviderImpl implements DefaultOrganizationProvider, DefaultOrganizationCache { + private static final ThreadLocal CACHE = new ThreadLocal<>(); + + private final DbClient dbClient; + + public DefaultOrganizationProviderImpl(DbClient dbClient) { + this.dbClient = dbClient; + } + + @Override + public DefaultOrganization get() { + Cache cache = CACHE.get(); + if (cache != null) { + return cache.get(() -> getDefaultOrganization(dbClient)); + } + + return getDefaultOrganization(dbClient); + } + + private static DefaultOrganization getDefaultOrganization(DbClient dbClient) { + try (DbSession dbSession = dbClient.openSession(false)) { + Optional uuid = dbClient.internalPropertiesDao().selectByKey(dbSession, InternalProperties.DEFAULT_ORGANIZATION); + checkState(uuid.isPresent() && !uuid.get().isEmpty(), "No Default organization uuid configured"); + Optional dto = dbClient.organizationDao().selectByUuid(dbSession, uuid.get()); + checkState(dto.isPresent(), "Default organization with uuid '%s' does not exist", uuid.get()); + return toDefaultOrganization(dto.get()); + } + } + + private static DefaultOrganization toDefaultOrganization(OrganizationDto organizationDto) { + return DefaultOrganization.newBuilder() + .setUuid(organizationDto.getUuid()) + .setKey(organizationDto.getKey()) + .setName(organizationDto.getName()) + .setCreatedAt(organizationDto.getCreatedAt()) + .setUpdatedAt(organizationDto.getUpdatedAt()) + .build(); + } + + @Override + public void load() { + checkState( + CACHE.get() == null, + "load called twice for thread '%s' or state wasn't cleared last time it was used", + Thread.currentThread().getName()); + CACHE.set(new Cache()); + } + + @Override + public void unload() { + CACHE.remove(); + } + + private static final class Cache { + @CheckForNull + private DefaultOrganization defaultOrganization; + + public DefaultOrganization get(Supplier supplier) { + if (defaultOrganization == null) { + defaultOrganization = supplier.get(); + } + return defaultOrganization; + } + + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/organization/package-info.java b/server/sonar-server-common/src/main/java/org/sonar/server/organization/package-info.java new file mode 100644 index 00000000000..6af0dea31d7 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/organization/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.organization; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/property/InternalProperties.java b/server/sonar-server-common/src/main/java/org/sonar/server/property/InternalProperties.java new file mode 100644 index 00000000000..15260eaf075 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/property/InternalProperties.java @@ -0,0 +1,64 @@ +/* + * 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.property; + +import java.util.Optional; +import javax.annotation.Nullable; + +/** + * Allows to read and write internal properties. + */ +public interface InternalProperties { + /** + * The UUID of the default organization. + * Can't be null unless SQ is strongly corrupted. + */ + String DEFAULT_ORGANIZATION = "organization.default"; + + String ORGANIZATION_ENABLED = "organization.enabled"; + + String SERVER_ID_CHECKSUM = "server.idChecksum"; + + /** + * Compute Engine is pausing/paused if property value is "true". + */ + String COMPUTE_ENGINE_PAUSE = "ce.pause"; + + String BITBUCKETCLOUD_APP_SHAREDSECRET = "bbc.app.sharedSecret"; + /** + * Read the value of the specified property. + * + * @return {@link Optional#empty()} if the property does not exist, an empty string if the property is empty, + * otherwise the value of the property as a String. + * + * @throws IllegalArgumentException if {@code propertyKey} is {@code null} or empty + */ + Optional read(String propertyKey); + + /** + * Write the value of the specified property. + *

+ * {@code null} and empty string are valid values which will persist the specified property as empty. + *

+ * + * @throws IllegalArgumentException if {@code propertyKey} is {@code null} or empty + */ + void write(String propertyKey, @Nullable String value); +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/property/InternalPropertiesImpl.java b/server/sonar-server-common/src/main/java/org/sonar/server/property/InternalPropertiesImpl.java new file mode 100644 index 00000000000..55cc690e93e --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/property/InternalPropertiesImpl.java @@ -0,0 +1,66 @@ +/* + * 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.property; + +import java.util.Optional; +import javax.annotation.Nullable; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * A cache-less implementation of {@link InternalProperties} reading and writing to DB table INTERNAL_PROPERTIES. + */ +public class InternalPropertiesImpl implements InternalProperties { + private final DbClient dbClient; + + public InternalPropertiesImpl(DbClient dbClient) { + this.dbClient = dbClient; + } + + @Override + public Optional read(String propertyKey) { + checkPropertyKey(propertyKey); + + try (DbSession dbSession = dbClient.openSession(false)) { + return dbClient.internalPropertiesDao().selectByKey(dbSession, propertyKey); + } + } + + @Override + public void write(String propertyKey, @Nullable String value) { + checkPropertyKey(propertyKey); + + try (DbSession dbSession = dbClient.openSession(false)) { + if (value == null || value.isEmpty()) { + dbClient.internalPropertiesDao().saveAsEmpty(dbSession, propertyKey); + } else { + dbClient.internalPropertiesDao().save(dbSession, propertyKey, value); + } + dbSession.commit(); + } + } + + private static void checkPropertyKey(@Nullable String propertyKey) { + checkArgument(propertyKey != null && !propertyKey.isEmpty(), "property key can't be null nor empty"); + } + +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/property/MapInternalProperties.java b/server/sonar-server-common/src/main/java/org/sonar/server/property/MapInternalProperties.java new file mode 100644 index 00000000000..ff799f47cea --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/property/MapInternalProperties.java @@ -0,0 +1,50 @@ +/* + * 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.property; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * Map based implementation of {@link InternalProperties} to be used for unit testing. + */ +public class MapInternalProperties implements InternalProperties { + private final Map values = new HashMap<>(1); + + @Override + public Optional read(String propertyKey) { + checkPropertyKey(propertyKey); + return Optional.ofNullable(values.get(propertyKey)); + } + + @Override + public void write(String propertyKey, @Nullable String value) { + checkPropertyKey(propertyKey); + values.put(propertyKey, value); + } + + private static void checkPropertyKey(@Nullable String propertyKey) { + checkArgument(propertyKey != null && !propertyKey.isEmpty(), "property key can't be null nor empty"); + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/property/package-info.java b/server/sonar-server-common/src/main/java/org/sonar/server/property/package-info.java new file mode 100644 index 00000000000..d5d722326dc --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/property/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.property; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/notification/DefaultNotificationManagerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/notification/DefaultNotificationManagerTest.java new file mode 100644 index 00000000000..dcd3e1981a5 --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/notification/DefaultNotificationManagerTest.java @@ -0,0 +1,246 @@ +/* + * 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.notification; + +import com.google.common.collect.Multimap; +import java.io.InvalidClassException; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import org.apache.commons.lang.RandomStringUtils; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.sonar.api.notifications.Notification; +import org.sonar.api.notifications.NotificationChannel; +import org.sonar.api.web.UserRole; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.notification.NotificationQueueDao; +import org.sonar.db.notification.NotificationQueueDto; +import org.sonar.db.permission.AuthorizationDao; +import org.sonar.db.property.PropertiesDao; +import org.sonar.db.property.Subscriber; +import org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject; + +import static com.google.common.collect.Sets.newHashSet; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.only; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.internal.verification.VerificationModeFactory.times; + +public class DefaultNotificationManagerTest { + + private DefaultNotificationManager underTest; + + private PropertiesDao propertiesDao = mock(PropertiesDao.class); + private NotificationDispatcher dispatcher = mock(NotificationDispatcher.class); + private NotificationChannel emailChannel = mock(NotificationChannel.class); + private NotificationChannel twitterChannel = mock(NotificationChannel.class); + private NotificationQueueDao notificationQueueDao = mock(NotificationQueueDao.class); + private AuthorizationDao authorizationDao = mock(AuthorizationDao.class); + private DbClient dbClient = mock(DbClient.class); + private DbSession dbSession = mock(DbSession.class); + + @Before + public void setUp() { + when(dispatcher.getKey()).thenReturn("NewViolations"); + when(emailChannel.getKey()).thenReturn("Email"); + when(twitterChannel.getKey()).thenReturn("Twitter"); + when(dbClient.openSession(anyBoolean())).thenReturn(dbSession); + when(dbClient.propertiesDao()).thenReturn(propertiesDao); + when(dbClient.notificationQueueDao()).thenReturn(notificationQueueDao); + when(dbClient.authorizationDao()).thenReturn(authorizationDao); + + underTest = new DefaultNotificationManager(new NotificationChannel[] {emailChannel, twitterChannel}, dbClient); + } + + @Test + public void shouldProvideChannelList() { + assertThat(underTest.getChannels()).containsOnly(emailChannel, twitterChannel); + + underTest = new DefaultNotificationManager(new NotificationChannel[] {}, dbClient); + assertThat(underTest.getChannels()).hasSize(0); + } + + @Test + public void shouldPersist() { + Notification notification = new Notification("test"); + underTest.scheduleForSending(notification); + + verify(notificationQueueDao, only()).insert(any(List.class)); + } + + @Test + public void shouldGetFromQueueAndDelete() { + Notification notification = new Notification("test"); + NotificationQueueDto dto = NotificationQueueDto.toNotificationQueueDto(notification); + List dtos = Arrays.asList(dto); + when(notificationQueueDao.selectOldest(1)).thenReturn(dtos); + + assertThat(underTest.getFromQueue()).isNotNull(); + + InOrder inOrder = inOrder(notificationQueueDao); + inOrder.verify(notificationQueueDao).selectOldest(1); + inOrder.verify(notificationQueueDao).delete(dtos); + } + + // SONAR-4739 + @Test + public void shouldNotFailWhenUnableToDeserialize() throws Exception { + NotificationQueueDto dto1 = mock(NotificationQueueDto.class); + when(dto1.toNotification()).thenThrow(new InvalidClassException("Pouet")); + List dtos = Arrays.asList(dto1); + when(notificationQueueDao.selectOldest(1)).thenReturn(dtos); + + underTest = spy(underTest); + assertThat(underTest.getFromQueue()).isNull(); + assertThat(underTest.getFromQueue()).isNull(); + + verify(underTest, times(1)).logDeserializationIssue(); + } + + @Test + public void shouldFindNoRecipient() { + assertThat(underTest.findSubscribedRecipientsForDispatcher(dispatcher, "uuid_45", new SubscriberPermissionsOnProject(UserRole.USER)).asMap().entrySet()) + .hasSize(0); + } + + @Test + public void shouldFindSubscribedRecipientForGivenResource() { + String projectUuid = "uuid_45"; + when(propertiesDao.findUsersForNotification("NewViolations", "Email", projectUuid)) + .thenReturn(newHashSet(new Subscriber("user1", false), new Subscriber("user3", false), new Subscriber("user3", true))); + when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", "uuid_56")) + .thenReturn(newHashSet(new Subscriber("user2", false))); + when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", projectUuid)) + .thenReturn(newHashSet(new Subscriber("user3", true))); + when(propertiesDao.findUsersForNotification("NewAlerts", "Twitter", projectUuid)) + .thenReturn(newHashSet(new Subscriber("user4", false))); + + when(authorizationDao.keepAuthorizedLoginsOnProject(dbSession, newHashSet("user1", "user3"), projectUuid, "user")) + .thenReturn(newHashSet("user1", "user3")); + + Multimap multiMap = underTest.findSubscribedRecipientsForDispatcher(dispatcher, projectUuid, + SubscriberPermissionsOnProject.ALL_MUST_HAVE_ROLE_USER); + assertThat(multiMap.entries()).hasSize(3); + + Map> map = multiMap.asMap(); + assertThat(map.get("user1")).containsOnly(emailChannel); + assertThat(map.get("user2")).isNull(); + assertThat(map.get("user3")).containsOnly(emailChannel, twitterChannel); + assertThat(map.get("user4")).isNull(); + + // code is optimized to perform only 1 SQL requests for all channels + verify(authorizationDao, times(1)).keepAuthorizedLoginsOnProject(eq(dbSession), anySet(), anyString(), anyString()); + } + + @Test + public void should_apply_distinct_permission_filtering_global_or_project_subscribers() { + String globalPermission = RandomStringUtils.randomAlphanumeric(4); + String projectPermission = RandomStringUtils.randomAlphanumeric(5); + String projectUuid = "uuid_45"; + when(propertiesDao.findUsersForNotification("NewViolations", "Email", projectUuid)) + .thenReturn(newHashSet(new Subscriber("user1", false), new Subscriber("user3", false), new Subscriber("user3", true))); + when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", "uuid_56")) + .thenReturn(newHashSet(new Subscriber("user2", false))); + when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", projectUuid)) + .thenReturn(newHashSet(new Subscriber("user3", true))); + when(propertiesDao.findUsersForNotification("NewAlerts", "Twitter", projectUuid)) + .thenReturn(newHashSet(new Subscriber("user4", false))); + + when(authorizationDao.keepAuthorizedLoginsOnProject(dbSession, newHashSet("user3", "user4"), projectUuid, globalPermission)) + .thenReturn(newHashSet("user3")); + when(authorizationDao.keepAuthorizedLoginsOnProject(dbSession, newHashSet("user1", "user3"), projectUuid, projectPermission)) + .thenReturn(newHashSet("user1", "user3")); + + Multimap multiMap = underTest.findSubscribedRecipientsForDispatcher(dispatcher, projectUuid, + new SubscriberPermissionsOnProject(globalPermission, projectPermission)); + assertThat(multiMap.entries()).hasSize(3); + + Map> map = multiMap.asMap(); + assertThat(map.get("user1")).containsOnly(emailChannel); + assertThat(map.get("user2")).isNull(); + assertThat(map.get("user3")).containsOnly(emailChannel, twitterChannel); + assertThat(map.get("user4")).isNull(); + + // code is optimized to perform only 2 SQL requests for all channels + verify(authorizationDao, times(1)).keepAuthorizedLoginsOnProject(eq(dbSession), anySet(), anyString(), eq(globalPermission)); + verify(authorizationDao, times(1)).keepAuthorizedLoginsOnProject(eq(dbSession), anySet(), anyString(), eq(projectPermission)); + } + + @Test + public void do_not_call_db_for_project_permission_filtering_if_there_is_no_project_subscriber() { + String globalPermission = RandomStringUtils.randomAlphanumeric(4); + String projectPermission = RandomStringUtils.randomAlphanumeric(5); + String projectUuid = "uuid_45"; + when(propertiesDao.findUsersForNotification("NewViolations", "Email", projectUuid)) + .thenReturn(newHashSet(new Subscriber("user3", true))); + when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", projectUuid)) + .thenReturn(newHashSet(new Subscriber("user3", true))); + + when(authorizationDao.keepAuthorizedLoginsOnProject(dbSession, newHashSet("user3"), projectUuid, globalPermission)) + .thenReturn(newHashSet("user3")); + + Multimap multiMap = underTest.findSubscribedRecipientsForDispatcher(dispatcher, projectUuid, + new SubscriberPermissionsOnProject(globalPermission, projectPermission)); + assertThat(multiMap.entries()).hasSize(2); + + Map> map = multiMap.asMap(); + assertThat(map.get("user3")).containsOnly(emailChannel, twitterChannel); + + verify(authorizationDao, times(1)).keepAuthorizedLoginsOnProject(eq(dbSession), anySet(), anyString(), eq(globalPermission)); + verify(authorizationDao, times(0)).keepAuthorizedLoginsOnProject(eq(dbSession), anySet(), anyString(), eq(projectPermission)); + } + + @Test + public void do_not_call_db_for_project_permission_filtering_if_there_is_no_global_subscriber() { + String globalPermission = RandomStringUtils.randomAlphanumeric(4); + String projectPermission = RandomStringUtils.randomAlphanumeric(5); + String projectUuid = "uuid_45"; + when(propertiesDao.findUsersForNotification("NewViolations", "Email", projectUuid)) + .thenReturn(newHashSet(new Subscriber("user3", false))); + when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", projectUuid)) + .thenReturn(newHashSet(new Subscriber("user3", false))); + + when(authorizationDao.keepAuthorizedLoginsOnProject(dbSession, newHashSet("user3"), projectUuid, projectPermission)) + .thenReturn(newHashSet("user3")); + + Multimap multiMap = underTest.findSubscribedRecipientsForDispatcher(dispatcher, projectUuid, + new SubscriberPermissionsOnProject(globalPermission, projectPermission)); + assertThat(multiMap.entries()).hasSize(2); + + Map> map = multiMap.asMap(); + assertThat(map.get("user3")).containsOnly(emailChannel, twitterChannel); + + verify(authorizationDao, times(0)).keepAuthorizedLoginsOnProject(eq(dbSession), anySet(), anyString(), eq(globalPermission)); + verify(authorizationDao, times(1)).keepAuthorizedLoginsOnProject(eq(dbSession), anySet(), anyString(), eq(projectPermission)); + } +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/notification/NotificationDispatcherMetadataTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/notification/NotificationDispatcherMetadataTest.java new file mode 100644 index 00000000000..eea8d05fcf7 --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/notification/NotificationDispatcherMetadataTest.java @@ -0,0 +1,46 @@ +/* + * 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.notification; + +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class NotificationDispatcherMetadataTest { + + private NotificationDispatcherMetadata metadata; + + @Before + public void init() { + metadata = NotificationDispatcherMetadata.create("NewViolations").setProperty("global", "true"); + } + + @Test + public void shouldReturnDispatcherKey() { + assertThat(metadata.getDispatcherKey()).isEqualTo("NewViolations"); + } + + @Test + public void shouldReturnProperty() { + assertThat(metadata.getProperty("global")).isEqualTo("true"); + assertThat(metadata.getProperty("per-project")).isNull(); + } +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/notification/NotificationDispatcherTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/notification/NotificationDispatcherTest.java new file mode 100644 index 00000000000..e3532ce6be1 --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/notification/NotificationDispatcherTest.java @@ -0,0 +1,101 @@ +/* + * 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.notification; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.sonar.api.notifications.Notification; +import org.sonar.api.notifications.NotificationChannel; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class NotificationDispatcherTest { + + @Mock + private NotificationChannel channel; + + @Mock + private Notification notification; + + @Mock + private NotificationDispatcher.Context context; + + @Before + public void init() { + MockitoAnnotations.initMocks(this); + when(notification.getType()).thenReturn("event1"); + } + + @Test + public void defaultMethods() { + NotificationDispatcher dispatcher = new FakeGenericNotificationDispatcher(); + assertThat(dispatcher.getKey(), is("FakeGenericNotificationDispatcher")); + assertThat(dispatcher.toString(), is("FakeGenericNotificationDispatcher")); + } + + @Test + public void shouldAlwaysRunDispatchForGenericDispatcher() { + NotificationDispatcher dispatcher = new FakeGenericNotificationDispatcher(); + dispatcher.performDispatch(notification, context); + + verify(context, times(1)).addUser("user1", channel); + } + + @Test + public void shouldNotAlwaysRunDispatchForSpecificDispatcher() { + NotificationDispatcher dispatcher = new FakeSpecificNotificationDispatcher(); + + // a "event1" notif is sent + dispatcher.performDispatch(notification, context); + verify(context, never()).addUser("user1", channel); + + // now, a "specific-event" notif is sent + when(notification.getType()).thenReturn("specific-event"); + dispatcher.performDispatch(notification, context); + verify(context, times(1)).addUser("user1", channel); + } + + class FakeGenericNotificationDispatcher extends NotificationDispatcher { + @Override + public void dispatch(Notification notification, Context context) { + context.addUser("user1", channel); + } + } + + class FakeSpecificNotificationDispatcher extends NotificationDispatcher { + + public FakeSpecificNotificationDispatcher() { + super("specific-event"); + } + + @Override + public void dispatch(Notification notification, Context context) { + context.addUser("user1", channel); + } + } + +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/organization/DefaultOrganizationProviderImplTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/organization/DefaultOrganizationProviderImplTest.java new file mode 100644 index 00000000000..75ef3d76540 --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/organization/DefaultOrganizationProviderImplTest.java @@ -0,0 +1,186 @@ +/* + * 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.organization; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.utils.System2; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.organization.OrganizationDto; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.db.organization.OrganizationTesting.newOrganizationDto; +import static org.sonar.server.property.InternalProperties.DEFAULT_ORGANIZATION; + +public class DefaultOrganizationProviderImplTest { + private static final OrganizationDto ORGANIZATION_DTO_1 = newOrganizationDto() + .setUuid("uuid1") + .setName("the name of 1") + .setKey("the key 1"); + private static final long DATE_1 = 1_999_888L; + + private System2 system2 = mock(System2.class); + + @Rule + public DbTester dbTester = DbTester.create(system2).setDisableDefaultOrganization(true); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private DbClient dbClient = dbTester.getDbClient(); + private DbSession dbSession = dbTester.getSession(); + + private DefaultOrganizationProviderImpl underTest = new DefaultOrganizationProviderImpl(dbClient); + + @Test + public void get_fails_with_ISE_if_default_organization_internal_property_does_not_exist() { + expectISENoDefaultOrganizationUuid(); + + underTest.get(); + } + + @Test + public void get_fails_with_ISE_if_default_organization_internal_property_is_empty() { + dbClient.internalPropertiesDao().saveAsEmpty(dbSession, DEFAULT_ORGANIZATION); + dbSession.commit(); + + expectISENoDefaultOrganizationUuid(); + + underTest.get(); + } + + private void expectISENoDefaultOrganizationUuid() { + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("No Default organization uuid configured"); + } + + @Test + public void get_fails_with_ISE_if_default_organization_does_not_exist() { + dbClient.internalPropertiesDao().save(dbSession, DEFAULT_ORGANIZATION, "bla"); + dbSession.commit(); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("Default organization with uuid 'bla' does not exist"); + + underTest.get(); + } + + @Test + public void get_returns_DefaultOrganization_populated_from_DB() { + insertOrganization(ORGANIZATION_DTO_1, DATE_1); + dbClient.internalPropertiesDao().save(dbSession, DEFAULT_ORGANIZATION, ORGANIZATION_DTO_1.getUuid()); + dbSession.commit(); + + DefaultOrganization defaultOrganization = underTest.get(); + assertThat(defaultOrganization.getUuid()).isEqualTo(ORGANIZATION_DTO_1.getUuid()); + assertThat(defaultOrganization.getKey()).isEqualTo(ORGANIZATION_DTO_1.getKey()); + assertThat(defaultOrganization.getName()).isEqualTo(ORGANIZATION_DTO_1.getName()); + assertThat(defaultOrganization.getCreatedAt()).isEqualTo(DATE_1); + assertThat(defaultOrganization.getUpdatedAt()).isEqualTo(DATE_1); + } + + @Test + public void get_returns_new_DefaultOrganization_with_each_call_when_cache_is_not_loaded() { + insertOrganization(ORGANIZATION_DTO_1, DATE_1); + dbClient.internalPropertiesDao().save(dbSession, DEFAULT_ORGANIZATION, ORGANIZATION_DTO_1.getUuid()); + dbSession.commit(); + + assertThat(underTest.get()).isNotSameAs(underTest.get()); + } + + @Test + public void unload_does_not_fail_if_load_has_not_been_called() { + underTest.unload(); + } + + @Test + public void load_fails_with_ISE_when_called_twice_without_unload_in_between() { + underTest.load(); + + try { + underTest.load(); + fail("A IllegalStateException should have been raised"); + } catch (IllegalStateException e) { + assertThat(e).hasMessage("load called twice for thread '" + Thread.currentThread().getName() + "' or state wasn't cleared last time it was used"); + } finally { + underTest.unload(); + } + } + + @Test + public void load_and_unload_cache_DefaultOrganization_object_by_thread() throws InterruptedException { + insertOrganization(ORGANIZATION_DTO_1, DATE_1); + dbClient.internalPropertiesDao().save(dbSession, DEFAULT_ORGANIZATION, ORGANIZATION_DTO_1.getUuid()); + dbSession.commit(); + + try { + underTest.load(); + + DefaultOrganization cachedForThread1 = underTest.get(); + assertThat(cachedForThread1).isSameAs(underTest.get()); + + Thread otherThread = new Thread(() -> { + try { + underTest.load(); + + assertThat(underTest.get()) + .isNotSameAs(cachedForThread1) + .isSameAs(underTest.get()); + } finally { + underTest.unload(); + } + }); + otherThread.start(); + otherThread.join(); + } finally { + underTest.unload(); + } + } + + @Test + public void get_returns_new_instance_for_each_call_once_unload_has_been_called() { + insertOrganization(ORGANIZATION_DTO_1, DATE_1); + dbClient.internalPropertiesDao().save(dbSession, DEFAULT_ORGANIZATION, ORGANIZATION_DTO_1.getUuid()); + dbSession.commit(); + + try { + underTest.load(); + DefaultOrganization cached = underTest.get(); + assertThat(cached).isSameAs(underTest.get()); + + underTest.unload(); + assertThat(underTest.get()).isNotSameAs(underTest.get()).isNotSameAs(cached); + } finally { + // fail safe + underTest.unload(); + } + } + + private void insertOrganization(OrganizationDto dto, long createdAt) { + when(system2.now()).thenReturn(createdAt); + dbClient.organizationDao().insert(dbSession, dto, false); + dbSession.commit(); + } +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/organization/DefaultOrganizationTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/organization/DefaultOrganizationTest.java new file mode 100644 index 00000000000..88ce2ad1740 --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/organization/DefaultOrganizationTest.java @@ -0,0 +1,116 @@ +/* + * 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.organization; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DefaultOrganizationTest { + private static final long DATE_2 = 2_000_000L; + private static final long DATE_1 = 1_000_000L; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private DefaultOrganization.Builder populatedBuilder = new DefaultOrganization.Builder() + .setUuid("uuid") + .setKey("key") + .setName("name") + .setCreatedAt(DATE_1) + .setUpdatedAt(DATE_2); + + @Test + public void build_fails_if_uuid_is_null() { + populatedBuilder.setUuid(null); + + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("uuid can't be null"); + + populatedBuilder.build(); + } + + @Test + public void build_fails_if_key_is_null() { + populatedBuilder.setKey(null); + + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("key can't be null"); + + populatedBuilder.build(); + } + + @Test + public void build_fails_if_name_is_null() { + populatedBuilder.setName(null); + + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("name can't be null"); + + populatedBuilder.build(); + } + + @Test + public void build_fails_if_createdAt_not_set() { + DefaultOrganization.Builder underTest = new DefaultOrganization.Builder() + .setUuid("uuid") + .setKey("key") + .setName("name") + .setUpdatedAt(DATE_2); + + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("createdAt can't be null"); + + underTest.build(); + } + + @Test + public void build_fails_if_updatedAt_not_set() { + DefaultOrganization.Builder underTest = new DefaultOrganization.Builder() + .setUuid("uuid") + .setKey("key") + .setName("name") + .setCreatedAt(DATE_1); + + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("updatedAt can't be null"); + + underTest.build(); + } + + @Test + public void verify_toString() { + assertThat(populatedBuilder.build().toString()) + .isEqualTo("DefaultOrganization{uuid='uuid', key='key', name='name', createdAt=1000000, updatedAt=2000000}"); + } + + @Test + public void verify_getters() { + DefaultOrganization underTest = populatedBuilder.build(); + + assertThat(underTest.getUuid()).isEqualTo("uuid"); + assertThat(underTest.getKey()).isEqualTo("key"); + assertThat(underTest.getName()).isEqualTo("name"); + assertThat(underTest.getCreatedAt()).isEqualTo(DATE_1); + assertThat(underTest.getUpdatedAt()).isEqualTo(DATE_2); + } +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/property/InternalPropertiesImplTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/property/InternalPropertiesImplTest.java new file mode 100644 index 00000000000..c3c5fd7601a --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/property/InternalPropertiesImplTest.java @@ -0,0 +1,120 @@ +/* + * 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.property; + +import java.util.Optional; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.property.InternalPropertiesDao; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class InternalPropertiesImplTest { + private static final String EMPTY_STRING = ""; + public static final String SOME_VALUE = "a value"; + public static final String SOME_KEY = "some key"; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private DbClient dbClient = mock(DbClient.class); + private DbSession dbSession = mock(DbSession.class); + private InternalPropertiesDao internalPropertiesDao = mock(InternalPropertiesDao.class); + private InternalPropertiesImpl underTest = new InternalPropertiesImpl(dbClient); + + @Before + public void setUp() throws Exception { + when(dbClient.openSession(false)).thenReturn(dbSession); + when(dbClient.internalPropertiesDao()).thenReturn(internalPropertiesDao); + } + + @Test + public void reads_throws_IAE_if_key_is_null() { + expectKeyNullOrEmptyIAE(); + + underTest.read(null); + } + + @Test + public void reads_throws_IAE_if_key_is_empty() { + expectKeyNullOrEmptyIAE(); + + underTest.read(EMPTY_STRING); + } + + @Test + public void reads_returns_optional_from_DAO() { + Optional value = Optional.of("bablabla"); + + when(internalPropertiesDao.selectByKey(dbSession, SOME_KEY)).thenReturn(value); + + assertThat(underTest.read(SOME_KEY)).isSameAs(value); + } + + @Test + public void write_throws_IAE_if_key_is_null() { + expectKeyNullOrEmptyIAE(); + + underTest.write(null, SOME_VALUE); + } + + @Test + public void writes_throws_IAE_if_key_is_empty() { + expectKeyNullOrEmptyIAE(); + + underTest.write(EMPTY_STRING, SOME_VALUE); + } + + @Test + public void write_calls_dao_saveAsEmpty_when_value_is_null() { + underTest.write(SOME_KEY, null); + + verify(internalPropertiesDao).saveAsEmpty(dbSession, SOME_KEY); + verify(dbSession).commit(); + } + + @Test + public void write_calls_dao_saveAsEmpty_when_value_is_empty() { + underTest.write(SOME_KEY, EMPTY_STRING); + + verify(internalPropertiesDao).saveAsEmpty(dbSession, SOME_KEY); + verify(dbSession).commit(); + } + + @Test + public void write_calls_dao_save_when_value_is_neither_null_nor_empty() { + underTest.write(SOME_KEY, SOME_VALUE); + + verify(internalPropertiesDao).save(dbSession, SOME_KEY, SOME_VALUE); + verify(dbSession).commit(); + } + + private void expectKeyNullOrEmptyIAE() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("key can't be null nor empty"); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/ce/CeModule.java b/server/sonar-server/src/main/java/org/sonar/ce/CeModule.java deleted file mode 100644 index 74e5b22bf6e..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/ce/CeModule.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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; - -import org.sonar.ce.http.CeHttpClientImpl; -import org.sonar.ce.queue.CeQueueImpl; -import org.sonar.ce.task.log.CeTaskLogging; -import org.sonar.ce.taskprocessor.ReportTaskProcessorDeclaration; -import org.sonar.core.platform.Module; -import org.sonar.server.ce.queue.ReportSubmitter; - -public class CeModule extends Module { - @Override - protected void configureModule() { - add(CeTaskLogging.class, - CeHttpClientImpl.class, - - // Queue - CeQueueImpl.class, - ReportSubmitter.class, - - // Core tasks processors - ReportTaskProcessorDeclaration.class); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/ce/log/CeProcessLogging.java b/server/sonar-server/src/main/java/org/sonar/ce/log/CeProcessLogging.java deleted file mode 100644 index 2788981d9a6..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/ce/log/CeProcessLogging.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.log; - -import ch.qos.logback.classic.Level; -import org.sonar.process.ProcessId; -import org.sonar.process.logging.LogDomain; -import org.sonar.process.logging.LogLevelConfig; -import org.sonar.server.app.ServerProcessLogging; - -import static org.sonar.ce.task.log.CeTaskLogging.MDC_CE_TASK_UUID; - -/** - * Configure logback for the Compute Engine process. Logs are written to file "ce.log" in SQ's log directory. - */ -public class CeProcessLogging extends ServerProcessLogging { - - public CeProcessLogging() { - super(ProcessId.COMPUTE_ENGINE, "%X{" + MDC_CE_TASK_UUID + "}"); - } - - @Override - protected void extendLogLevelConfiguration(LogLevelConfig.Builder logLevelConfigBuilder) { - logLevelConfigBuilder.levelByDomain("sql", ProcessId.COMPUTE_ENGINE, LogDomain.SQL); - logLevelConfigBuilder.levelByDomain("es", ProcessId.COMPUTE_ENGINE, LogDomain.ES); - JMX_RMI_LOGGER_NAMES.forEach(loggerName -> logLevelConfigBuilder.levelByDomain(loggerName, ProcessId.COMPUTE_ENGINE, LogDomain.JMX)); - LOGGER_NAMES_TO_TURN_OFF.forEach(loggerName -> logLevelConfigBuilder.immutableLevel(loggerName, Level.OFF)); - } - - @Override - protected void extendConfigure() { - // nothing to do - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/ce/log/package-info.java b/server/sonar-server/src/main/java/org/sonar/ce/log/package-info.java deleted file mode 100644 index 7c5ada78eae..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/ce/log/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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.ce.log; - -import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotification.java b/server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotification.java deleted file mode 100644 index a2f446e3eb4..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotification.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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.notification; - -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; - -import static java.util.Objects.requireNonNull; - -public class ReportAnalysisFailureNotification { - public static final String TYPE = "ce-report-task-failure"; - - private final Project project; - private final Task task; - private final String errorMessage; - - public ReportAnalysisFailureNotification(Project project, Task task, @Nullable String errorMessage) { - this.project = requireNonNull(project, "project can't be null"); - this.task = requireNonNull(task, "task can't be null"); - this.errorMessage = errorMessage; - } - - public Project getProject() { - return project; - } - - public Task getTask() { - return task; - } - - @CheckForNull - public String getErrorMessage() { - return errorMessage; - } - - public static final class Project { - private final String uuid; - private final String key; - private final String name; - private final String branchName; - - public Project(String uuid, String key, String name, @Nullable String branchName) { - this.uuid = requireNonNull(uuid, "uuid can't be null"); - this.key = requireNonNull(key, "key can't be null"); - this.name = requireNonNull(name, "name can't be null"); - this.branchName = branchName; - } - - public String getUuid() { - return uuid; - } - - public String getKey() { - return key; - } - - public String getName() { - return name; - } - - @CheckForNull - public String getBranchName() { - return branchName; - } - } - - public static final class Task { - private final String uuid; - private final long createdAt; - private final long failedAt; - - public Task(String uuid, long createdAt, long failedAt) { - this.uuid = requireNonNull(uuid, "uuid can't be null"); - this.createdAt = createdAt; - this.failedAt = failedAt; - } - - public String getUuid() { - return uuid; - } - - public long getCreatedAt() { - return createdAt; - } - - public long getFailedAt() { - return failedAt; - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationDispatcher.java b/server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationDispatcher.java deleted file mode 100644 index c7b956522fc..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationDispatcher.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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.notification; - -import com.google.common.collect.Multimap; -import java.util.Collection; -import java.util.Map; -import org.sonar.api.notifications.Notification; -import org.sonar.api.notifications.NotificationChannel; -import org.sonar.api.web.UserRole; -import org.sonar.server.notification.NotificationDispatcher; -import org.sonar.server.notification.NotificationDispatcherMetadata; -import org.sonar.server.notification.NotificationManager; -import org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject; - -public class ReportAnalysisFailureNotificationDispatcher extends NotificationDispatcher { - - public static final String KEY = "CeReportTaskFailure"; - private static final SubscriberPermissionsOnProject REQUIRED_SUBSCRIBER_PERMISSIONS = new SubscriberPermissionsOnProject(UserRole.ADMIN, UserRole.USER); - - private final NotificationManager manager; - - public ReportAnalysisFailureNotificationDispatcher(NotificationManager manager) { - super(ReportAnalysisFailureNotification.TYPE); - this.manager = manager; - } - - public static NotificationDispatcherMetadata newMetadata() { - return NotificationDispatcherMetadata.create(KEY) - .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true)) - .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true)); - } - - @Override - public String getKey() { - return KEY; - } - - @Override - public void dispatch(Notification notification, Context context) { - String projectKey = notification.getFieldValue("project.key"); - Multimap subscribedRecipients = manager - .findSubscribedRecipientsForDispatcher(this, projectKey, REQUIRED_SUBSCRIBER_PERMISSIONS); - - for (Map.Entry> channelsByRecipients : subscribedRecipients.asMap().entrySet()) { - String userLogin = channelsByRecipients.getKey(); - for (NotificationChannel channel : channelsByRecipients.getValue()) { - context.addUser(userLogin, channel); - } - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationEmailTemplate.java b/server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationEmailTemplate.java deleted file mode 100644 index 01475382e0b..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationEmailTemplate.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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.notification; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import org.sonar.api.config.EmailSettings; -import org.sonar.api.notifications.Notification; -import org.sonar.plugins.emailnotifications.api.EmailMessage; -import org.sonar.plugins.emailnotifications.api.EmailTemplate; - -import static org.sonar.api.utils.DateUtils.formatDateTime; - -public class ReportAnalysisFailureNotificationEmailTemplate extends EmailTemplate { - private static final char LINE_RETURN = '\n'; - private static final char TAB = '\t'; - - private final ReportAnalysisFailureNotificationSerializer serializer; - protected final EmailSettings settings; - - public ReportAnalysisFailureNotificationEmailTemplate(ReportAnalysisFailureNotificationSerializer serializer, EmailSettings settings) { - this.serializer = serializer; - this.settings = settings; - } - - @Override - public EmailMessage format(Notification notification) { - if (!ReportAnalysisFailureNotification.TYPE.equals(notification.getType())) { - return null; - } - - ReportAnalysisFailureNotification taskFailureNotification = serializer.fromNotification(notification); - String projectUuid = taskFailureNotification.getProject().getUuid(); - String projectFullName = computeProjectFullName(taskFailureNotification.getProject()); - - return new EmailMessage() - .setMessageId(notification.getType() + "/" + projectUuid) - .setSubject(subject(projectFullName)) - .setMessage(message(projectFullName, taskFailureNotification)); - } - - private static String computeProjectFullName(ReportAnalysisFailureNotification.Project project) { - String branchName = project.getBranchName(); - if (branchName != null) { - return String.format("%s (%s)", project.getName(), branchName); - } - return project.getName(); - } - - private static String subject(String projectFullName) { - return String.format("%s: Background task in failure", projectFullName); - } - - private String message(String projectFullName, ReportAnalysisFailureNotification taskFailureNotification) { - ReportAnalysisFailureNotification.Project project = taskFailureNotification.getProject(); - ReportAnalysisFailureNotification.Task task = taskFailureNotification.getTask(); - - StringBuilder res = new StringBuilder(); - res.append("Project:").append(TAB).append(projectFullName).append(LINE_RETURN); - res.append("Background task:").append(TAB).append(task.getUuid()).append(LINE_RETURN); - res.append("Submission time:").append(TAB).append(formatDateTime(task.getCreatedAt())).append(LINE_RETURN); - res.append("Failure time:").append(TAB).append(formatDateTime(task.getFailedAt())).append(LINE_RETURN); - - String errorMessage = taskFailureNotification.getErrorMessage(); - if (errorMessage != null) { - res.append(LINE_RETURN); - res.append("Error message:").append(TAB).append(errorMessage).append(LINE_RETURN); - } - - res.append(LINE_RETURN); - res.append("More details at: ").append(String.format("%s/project/background_tasks?id=%s", settings.getServerBaseURL(), encode(project.getKey()))); - - return res.toString(); - } - - private static String encode(String toEncode) { - try { - return URLEncoder.encode(toEncode, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new IllegalStateException("Encoding not supported", e); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationModule.java b/server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationModule.java deleted file mode 100644 index 7e84b21f85e..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationModule.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.notification; - -import org.sonar.core.platform.Module; - -public class ReportAnalysisFailureNotificationModule extends Module { - @Override - protected void configureModule() { - add( - ReportAnalysisFailureNotificationDispatcher.class, - ReportAnalysisFailureNotificationDispatcher.newMetadata(), - ReportAnalysisFailureNotificationSerializerImpl.class, - ReportAnalysisFailureNotificationEmailTemplate.class); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationSerializer.java b/server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationSerializer.java deleted file mode 100644 index 50079eb86e1..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationSerializer.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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.notification; - -import org.sonar.api.notifications.Notification; - -public interface ReportAnalysisFailureNotificationSerializer { - Notification toNotification(ReportAnalysisFailureNotification reportAnalysisFailureNotification); - - ReportAnalysisFailureNotification fromNotification(Notification notification); -} diff --git a/server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationSerializerImpl.java b/server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationSerializerImpl.java deleted file mode 100644 index 0da6c354eec..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationSerializerImpl.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.notification; - -import org.sonar.api.notifications.Notification; - -import static java.lang.String.valueOf; - -public class ReportAnalysisFailureNotificationSerializerImpl implements ReportAnalysisFailureNotificationSerializer { - private static final String FIELD_PROJECT_UUID = "project.uuid"; - private static final String FIELD_PROJECT_KEY = "project.key"; - private static final String FIELD_PROJECT_NAME = "project.name"; - private static final String FIELD_PROJECT_BRANCH = "project.branchName"; - private static final String FIELD_TASK_UUID = "task.uuid"; - private static final String FIELD_TASK_CREATED_AT = "task.createdAt"; - private static final String FIELD_TASK_FAILED_AT = "task.failedAt"; - private static final String FIELD_ERROR_MESSAGE = "error.message"; - - @Override - public Notification toNotification(ReportAnalysisFailureNotification reportAnalysisFailureNotification) { - return new Notification(ReportAnalysisFailureNotification.TYPE) - .setFieldValue(FIELD_PROJECT_UUID, reportAnalysisFailureNotification.getProject().getUuid()) - .setFieldValue(FIELD_PROJECT_KEY, reportAnalysisFailureNotification.getProject().getKey()) - .setFieldValue(FIELD_PROJECT_NAME, reportAnalysisFailureNotification.getProject().getName()) - .setFieldValue(FIELD_PROJECT_BRANCH, reportAnalysisFailureNotification.getProject().getBranchName()) - .setFieldValue(FIELD_TASK_UUID, reportAnalysisFailureNotification.getTask().getUuid()) - .setFieldValue(FIELD_TASK_CREATED_AT, valueOf(reportAnalysisFailureNotification.getTask().getCreatedAt())) - .setFieldValue(FIELD_TASK_FAILED_AT, valueOf(reportAnalysisFailureNotification.getTask().getFailedAt())) - .setFieldValue(FIELD_ERROR_MESSAGE, reportAnalysisFailureNotification.getErrorMessage()); - } - - @Override - public ReportAnalysisFailureNotification fromNotification(Notification notification) { - return new ReportAnalysisFailureNotification( - new ReportAnalysisFailureNotification.Project( - notification.getFieldValue(FIELD_PROJECT_UUID), - notification.getFieldValue(FIELD_PROJECT_KEY), - notification.getFieldValue(FIELD_PROJECT_NAME), - notification.getFieldValue(FIELD_PROJECT_BRANCH)), - new ReportAnalysisFailureNotification.Task( - notification.getFieldValue(FIELD_TASK_UUID), - Long.valueOf(notification.getFieldValue(FIELD_TASK_CREATED_AT)), - Long.valueOf(notification.getFieldValue(FIELD_TASK_FAILED_AT))), - notification.getFieldValue(FIELD_ERROR_MESSAGE)); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/ce/notification/package-info.java b/server/sonar-server/src/main/java/org/sonar/ce/notification/package-info.java deleted file mode 100644 index 0e90a860936..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/ce/notification/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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.ce.notification; - -import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/ce/package-info.java b/server/sonar-server/src/main/java/org/sonar/ce/package-info.java deleted file mode 100644 index 530ad8c9cc2..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/ce/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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.ce; - -import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/ReportTaskProcessorDeclaration.java b/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/ReportTaskProcessorDeclaration.java new file mode 100644 index 00000000000..c2a19e56b3e --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/ReportTaskProcessorDeclaration.java @@ -0,0 +1,45 @@ +/* + * 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.task.projectanalysis; + +import java.util.Collections; +import java.util.Set; +import org.sonar.ce.task.CeTask; +import org.sonar.ce.task.CeTaskResult; +import org.sonar.ce.task.taskprocessor.CeTaskProcessor; +import org.sonar.db.ce.CeTaskTypes; + +/** + * CeTaskProcessor without any real implementation used to declare the CeTask type to the WebServer only. + */ +public class ReportTaskProcessorDeclaration implements CeTaskProcessor { + + private static final Set HANDLED_TYPES = Collections.singleton(CeTaskTypes.REPORT); + + @Override + public Set getHandledCeTaskTypes() { + return HANDLED_TYPES; + } + + @Override + public CeTaskResult process(CeTask task) { + throw new UnsupportedOperationException("process must not be called in WebServer"); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotification.java b/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotification.java new file mode 100644 index 00000000000..a2f446e3eb4 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotification.java @@ -0,0 +1,107 @@ +/* + * 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.notification; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + +import static java.util.Objects.requireNonNull; + +public class ReportAnalysisFailureNotification { + public static final String TYPE = "ce-report-task-failure"; + + private final Project project; + private final Task task; + private final String errorMessage; + + public ReportAnalysisFailureNotification(Project project, Task task, @Nullable String errorMessage) { + this.project = requireNonNull(project, "project can't be null"); + this.task = requireNonNull(task, "task can't be null"); + this.errorMessage = errorMessage; + } + + public Project getProject() { + return project; + } + + public Task getTask() { + return task; + } + + @CheckForNull + public String getErrorMessage() { + return errorMessage; + } + + public static final class Project { + private final String uuid; + private final String key; + private final String name; + private final String branchName; + + public Project(String uuid, String key, String name, @Nullable String branchName) { + this.uuid = requireNonNull(uuid, "uuid can't be null"); + this.key = requireNonNull(key, "key can't be null"); + this.name = requireNonNull(name, "name can't be null"); + this.branchName = branchName; + } + + public String getUuid() { + return uuid; + } + + public String getKey() { + return key; + } + + public String getName() { + return name; + } + + @CheckForNull + public String getBranchName() { + return branchName; + } + } + + public static final class Task { + private final String uuid; + private final long createdAt; + private final long failedAt; + + public Task(String uuid, long createdAt, long failedAt) { + this.uuid = requireNonNull(uuid, "uuid can't be null"); + this.createdAt = createdAt; + this.failedAt = failedAt; + } + + public String getUuid() { + return uuid; + } + + public long getCreatedAt() { + return createdAt; + } + + public long getFailedAt() { + return failedAt; + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationDispatcher.java b/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationDispatcher.java new file mode 100644 index 00000000000..c7b956522fc --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationDispatcher.java @@ -0,0 +1,69 @@ +/* + * 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.notification; + +import com.google.common.collect.Multimap; +import java.util.Collection; +import java.util.Map; +import org.sonar.api.notifications.Notification; +import org.sonar.api.notifications.NotificationChannel; +import org.sonar.api.web.UserRole; +import org.sonar.server.notification.NotificationDispatcher; +import org.sonar.server.notification.NotificationDispatcherMetadata; +import org.sonar.server.notification.NotificationManager; +import org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject; + +public class ReportAnalysisFailureNotificationDispatcher extends NotificationDispatcher { + + public static final String KEY = "CeReportTaskFailure"; + private static final SubscriberPermissionsOnProject REQUIRED_SUBSCRIBER_PERMISSIONS = new SubscriberPermissionsOnProject(UserRole.ADMIN, UserRole.USER); + + private final NotificationManager manager; + + public ReportAnalysisFailureNotificationDispatcher(NotificationManager manager) { + super(ReportAnalysisFailureNotification.TYPE); + this.manager = manager; + } + + public static NotificationDispatcherMetadata newMetadata() { + return NotificationDispatcherMetadata.create(KEY) + .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true)) + .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true)); + } + + @Override + public String getKey() { + return KEY; + } + + @Override + public void dispatch(Notification notification, Context context) { + String projectKey = notification.getFieldValue("project.key"); + Multimap subscribedRecipients = manager + .findSubscribedRecipientsForDispatcher(this, projectKey, REQUIRED_SUBSCRIBER_PERMISSIONS); + + for (Map.Entry> channelsByRecipients : subscribedRecipients.asMap().entrySet()) { + String userLogin = channelsByRecipients.getKey(); + for (NotificationChannel channel : channelsByRecipients.getValue()) { + context.addUser(userLogin, channel); + } + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationEmailTemplate.java b/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationEmailTemplate.java new file mode 100644 index 00000000000..01475382e0b --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationEmailTemplate.java @@ -0,0 +1,100 @@ +/* + * 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.notification; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import org.sonar.api.config.EmailSettings; +import org.sonar.api.notifications.Notification; +import org.sonar.plugins.emailnotifications.api.EmailMessage; +import org.sonar.plugins.emailnotifications.api.EmailTemplate; + +import static org.sonar.api.utils.DateUtils.formatDateTime; + +public class ReportAnalysisFailureNotificationEmailTemplate extends EmailTemplate { + private static final char LINE_RETURN = '\n'; + private static final char TAB = '\t'; + + private final ReportAnalysisFailureNotificationSerializer serializer; + protected final EmailSettings settings; + + public ReportAnalysisFailureNotificationEmailTemplate(ReportAnalysisFailureNotificationSerializer serializer, EmailSettings settings) { + this.serializer = serializer; + this.settings = settings; + } + + @Override + public EmailMessage format(Notification notification) { + if (!ReportAnalysisFailureNotification.TYPE.equals(notification.getType())) { + return null; + } + + ReportAnalysisFailureNotification taskFailureNotification = serializer.fromNotification(notification); + String projectUuid = taskFailureNotification.getProject().getUuid(); + String projectFullName = computeProjectFullName(taskFailureNotification.getProject()); + + return new EmailMessage() + .setMessageId(notification.getType() + "/" + projectUuid) + .setSubject(subject(projectFullName)) + .setMessage(message(projectFullName, taskFailureNotification)); + } + + private static String computeProjectFullName(ReportAnalysisFailureNotification.Project project) { + String branchName = project.getBranchName(); + if (branchName != null) { + return String.format("%s (%s)", project.getName(), branchName); + } + return project.getName(); + } + + private static String subject(String projectFullName) { + return String.format("%s: Background task in failure", projectFullName); + } + + private String message(String projectFullName, ReportAnalysisFailureNotification taskFailureNotification) { + ReportAnalysisFailureNotification.Project project = taskFailureNotification.getProject(); + ReportAnalysisFailureNotification.Task task = taskFailureNotification.getTask(); + + StringBuilder res = new StringBuilder(); + res.append("Project:").append(TAB).append(projectFullName).append(LINE_RETURN); + res.append("Background task:").append(TAB).append(task.getUuid()).append(LINE_RETURN); + res.append("Submission time:").append(TAB).append(formatDateTime(task.getCreatedAt())).append(LINE_RETURN); + res.append("Failure time:").append(TAB).append(formatDateTime(task.getFailedAt())).append(LINE_RETURN); + + String errorMessage = taskFailureNotification.getErrorMessage(); + if (errorMessage != null) { + res.append(LINE_RETURN); + res.append("Error message:").append(TAB).append(errorMessage).append(LINE_RETURN); + } + + res.append(LINE_RETURN); + res.append("More details at: ").append(String.format("%s/project/background_tasks?id=%s", settings.getServerBaseURL(), encode(project.getKey()))); + + return res.toString(); + } + + private static String encode(String toEncode) { + try { + return URLEncoder.encode(toEncode, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException("Encoding not supported", e); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationModule.java b/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationModule.java new file mode 100644 index 00000000000..7e84b21f85e --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationModule.java @@ -0,0 +1,33 @@ +/* + * 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.notification; + +import org.sonar.core.platform.Module; + +public class ReportAnalysisFailureNotificationModule extends Module { + @Override + protected void configureModule() { + add( + ReportAnalysisFailureNotificationDispatcher.class, + ReportAnalysisFailureNotificationDispatcher.newMetadata(), + ReportAnalysisFailureNotificationSerializerImpl.class, + ReportAnalysisFailureNotificationEmailTemplate.class); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationSerializer.java b/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationSerializer.java new file mode 100644 index 00000000000..50079eb86e1 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationSerializer.java @@ -0,0 +1,28 @@ +/* + * 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.notification; + +import org.sonar.api.notifications.Notification; + +public interface ReportAnalysisFailureNotificationSerializer { + Notification toNotification(ReportAnalysisFailureNotification reportAnalysisFailureNotification); + + ReportAnalysisFailureNotification fromNotification(Notification notification); +} diff --git a/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationSerializerImpl.java b/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationSerializerImpl.java new file mode 100644 index 00000000000..0da6c354eec --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationSerializerImpl.java @@ -0,0 +1,63 @@ +/* + * 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.notification; + +import org.sonar.api.notifications.Notification; + +import static java.lang.String.valueOf; + +public class ReportAnalysisFailureNotificationSerializerImpl implements ReportAnalysisFailureNotificationSerializer { + private static final String FIELD_PROJECT_UUID = "project.uuid"; + private static final String FIELD_PROJECT_KEY = "project.key"; + private static final String FIELD_PROJECT_NAME = "project.name"; + private static final String FIELD_PROJECT_BRANCH = "project.branchName"; + private static final String FIELD_TASK_UUID = "task.uuid"; + private static final String FIELD_TASK_CREATED_AT = "task.createdAt"; + private static final String FIELD_TASK_FAILED_AT = "task.failedAt"; + private static final String FIELD_ERROR_MESSAGE = "error.message"; + + @Override + public Notification toNotification(ReportAnalysisFailureNotification reportAnalysisFailureNotification) { + return new Notification(ReportAnalysisFailureNotification.TYPE) + .setFieldValue(FIELD_PROJECT_UUID, reportAnalysisFailureNotification.getProject().getUuid()) + .setFieldValue(FIELD_PROJECT_KEY, reportAnalysisFailureNotification.getProject().getKey()) + .setFieldValue(FIELD_PROJECT_NAME, reportAnalysisFailureNotification.getProject().getName()) + .setFieldValue(FIELD_PROJECT_BRANCH, reportAnalysisFailureNotification.getProject().getBranchName()) + .setFieldValue(FIELD_TASK_UUID, reportAnalysisFailureNotification.getTask().getUuid()) + .setFieldValue(FIELD_TASK_CREATED_AT, valueOf(reportAnalysisFailureNotification.getTask().getCreatedAt())) + .setFieldValue(FIELD_TASK_FAILED_AT, valueOf(reportAnalysisFailureNotification.getTask().getFailedAt())) + .setFieldValue(FIELD_ERROR_MESSAGE, reportAnalysisFailureNotification.getErrorMessage()); + } + + @Override + public ReportAnalysisFailureNotification fromNotification(Notification notification) { + return new ReportAnalysisFailureNotification( + new ReportAnalysisFailureNotification.Project( + notification.getFieldValue(FIELD_PROJECT_UUID), + notification.getFieldValue(FIELD_PROJECT_KEY), + notification.getFieldValue(FIELD_PROJECT_NAME), + notification.getFieldValue(FIELD_PROJECT_BRANCH)), + new ReportAnalysisFailureNotification.Task( + notification.getFieldValue(FIELD_TASK_UUID), + Long.valueOf(notification.getFieldValue(FIELD_TASK_CREATED_AT)), + Long.valueOf(notification.getFieldValue(FIELD_TASK_FAILED_AT))), + notification.getFieldValue(FIELD_ERROR_MESSAGE)); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/package-info.java b/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/package-info.java new file mode 100644 index 00000000000..908ed6991b5 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.ce.task.projectanalysis; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/ce/taskprocessor/CeTaskProcessor.java b/server/sonar-server/src/main/java/org/sonar/ce/taskprocessor/CeTaskProcessor.java deleted file mode 100644 index 441c227d0a3..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/ce/taskprocessor/CeTaskProcessor.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.taskprocessor; - -import java.util.Set; -import javax.annotation.CheckForNull; -import org.sonar.api.ce.ComputeEngineSide; -import org.sonar.api.server.ServerSide; -import org.sonar.ce.task.CeTask; -import org.sonar.ce.task.CeTaskResult; - -/** - * This interface is used to provide the processing code for {@link CeTask}s of one or more type to be called by the - * Compute Engine. - */ -@ComputeEngineSide -@ServerSide -public interface CeTaskProcessor { - - /** - * The {@link CeTask#getType()} for which this {@link CeTaskProcessor} provides the processing code. - *

- * The match of type is done using {@link String#equals(Object)} and if more than one {@link CeTaskProcessor} declares - * itself had handler for the same {@link CeTask#getType()}, an error will be raised at startup and startup will - * fail. - *

- *

- * If an empty {@link Set} is returned, the {@link CeTaskProcessor} will be ignored. - *

- */ - Set getHandledCeTaskTypes(); - - /** - * Calls the processing code for a specific {@link CeTask} which will optionally return a {@link CeTaskResult} - * holding information to be persisted in the processing history of the Compute Engine (currently the {@code CE_ACTIVITY} table). - *

- * The specified is guaranteed to be non {@code null} and its {@link CeTask#getType()} to be one of the values - * of {@link #getHandledCeTaskTypes()}. - *

- * - * @throws RuntimeException when thrown, it will be caught and logged by the Compute Engine and the processing of the - * specified {@link CeTask} will be flagged as failed. - */ - @CheckForNull - CeTaskResult process(CeTask task); -} diff --git a/server/sonar-server/src/main/java/org/sonar/ce/taskprocessor/ReportTaskProcessorDeclaration.java b/server/sonar-server/src/main/java/org/sonar/ce/taskprocessor/ReportTaskProcessorDeclaration.java deleted file mode 100644 index 9c2c6f98b31..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/ce/taskprocessor/ReportTaskProcessorDeclaration.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.taskprocessor; - -import java.util.Collections; -import java.util.Set; -import org.sonar.ce.task.CeTask; -import org.sonar.ce.task.CeTaskResult; -import org.sonar.db.ce.CeTaskTypes; - -/** - * CeTaskProcessor without any real implementation used to declare the CeTask type to the WebServer only. - */ -public class ReportTaskProcessorDeclaration implements CeTaskProcessor { - - private static final Set HANDLED_TYPES = Collections.singleton(CeTaskTypes.REPORT); - - @Override - public Set getHandledCeTaskTypes() { - return HANDLED_TYPES; - } - - @Override - public CeTaskResult process(CeTask task) { - throw new UnsupportedOperationException("process must not be called in WebServer"); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/ce/taskprocessor/package-info.java b/server/sonar-server/src/main/java/org/sonar/ce/taskprocessor/package-info.java deleted file mode 100644 index 86727e023f3..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/ce/taskprocessor/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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.ce.taskprocessor; - -import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/ServerProcessLogging.java b/server/sonar-server/src/main/java/org/sonar/server/app/ServerProcessLogging.java deleted file mode 100644 index cf7fe6bc37e..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/app/ServerProcessLogging.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * 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.app; - -import ch.qos.logback.classic.Level; -import ch.qos.logback.classic.Logger; -import ch.qos.logback.classic.LoggerContext; -import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.core.ConsoleAppender; -import com.google.common.collect.ImmutableSet; -import java.util.Set; -import org.sonar.process.ProcessId; -import org.sonar.process.Props; -import org.sonar.process.logging.LogLevelConfig; -import org.sonar.process.logging.LogbackHelper; -import org.sonar.process.logging.RootLoggerConfig; - -import static org.sonar.process.logging.RootLoggerConfig.newRootLoggerConfigBuilder; - -public abstract class ServerProcessLogging { - - public static final String STARTUP_LOGGER_NAME = "startup"; - protected static final Set JMX_RMI_LOGGER_NAMES = ImmutableSet.of( - "javax.management.remote.timeout", - "javax.management.remote.misc", - "javax.management.remote.rmi", - "javax.management.mbeanserver", - "sun.rmi.loader", - "sun.rmi.transport.tcp", - "sun.rmi.transport.misc", - "sun.rmi.server.call", - "sun.rmi.dgc"); - protected static final Set LOGGER_NAMES_TO_TURN_OFF = ImmutableSet.of( - // mssql driver - "com.microsoft.sqlserver.jdbc.internals", - "com.microsoft.sqlserver.jdbc.ResultSet", - "com.microsoft.sqlserver.jdbc.Statement", - "com.microsoft.sqlserver.jdbc.Connection"); - private final ProcessId processId; - private final String threadIdFieldPattern; - private final LogbackHelper helper = new LogbackHelper(); - private final LogLevelConfig logLevelConfig; - - protected ServerProcessLogging(ProcessId processId, String threadIdFieldPattern) { - this.processId = processId; - this.threadIdFieldPattern = threadIdFieldPattern; - this.logLevelConfig = createLogLevelConfiguration(processId); - } - - private LogLevelConfig createLogLevelConfiguration(ProcessId processId) { - LogLevelConfig.Builder builder = LogLevelConfig.newBuilder(helper.getRootLoggerName()); - builder.rootLevelFor(processId); - builder.immutableLevel("org.apache.ibatis", Level.WARN); - builder.immutableLevel("java.sql", Level.WARN); - builder.immutableLevel("java.sql.ResultSet", Level.WARN); - builder.immutableLevel("org.sonar.MEASURE_FILTER", Level.WARN); - builder.immutableLevel("org.elasticsearch", Level.INFO); - builder.immutableLevel("org.elasticsearch.node", Level.INFO); - builder.immutableLevel("org.elasticsearch.http", Level.INFO); - builder.immutableLevel("ch.qos.logback", Level.WARN); - builder.immutableLevel("org.apache.catalina", Level.INFO); - builder.immutableLevel("org.apache.coyote", Level.INFO); - builder.immutableLevel("org.apache.jasper", Level.INFO); - builder.immutableLevel("org.apache.tomcat", Level.INFO); - builder.immutableLevel("org.postgresql.core.v3.QueryExecutorImpl", Level.INFO); - builder.immutableLevel("org.postgresql.jdbc.PgConnection", Level.INFO); - - extendLogLevelConfiguration(builder); - - return builder.build(); - } - - public LoggerContext configure(Props props) { - LoggerContext ctx = helper.getRootContext(); - ctx.reset(); - - configureRootLogger(props); - helper.apply(logLevelConfig, props); - configureDirectToConsoleLoggers(ctx, STARTUP_LOGGER_NAME); - extendConfigure(); - - helper.enableJulChangePropagation(ctx); - - return ctx; - } - - public LogLevelConfig getLogLevelConfig() { - return this.logLevelConfig; - } - - protected abstract void extendLogLevelConfiguration(LogLevelConfig.Builder logLevelConfigBuilder); - - protected abstract void extendConfigure(); - - private void configureRootLogger(Props props) { - RootLoggerConfig config = newRootLoggerConfigBuilder() - .setProcessId(processId) - .setThreadIdFieldPattern(threadIdFieldPattern) - .build(); - String logPattern = helper.buildLogPattern(config); - - helper.configureGlobalFileLog(props, config, logPattern); - helper.configureForSubprocessGobbler(props, logPattern); - } - - /** - * Setup one or more specified loggers to be non additive and to print to System.out which will be caught by the Main - * Process and written to sonar.log. - */ - private void configureDirectToConsoleLoggers(LoggerContext context, String... loggerNames) { - RootLoggerConfig config = newRootLoggerConfigBuilder() - .setProcessId(ProcessId.APP) - .setThreadIdFieldPattern("") - .build(); - String logPattern = helper.buildLogPattern(config); - ConsoleAppender consoleAppender = helper.newConsoleAppender(context, "CONSOLE", logPattern); - - for (String loggerName : loggerNames) { - Logger consoleLogger = context.getLogger(loggerName); - consoleLogger.setAdditive(false); - consoleLogger.addAppender(consoleAppender); - } - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/WebServerProcessLogging.java b/server/sonar-server/src/main/java/org/sonar/server/app/WebServerProcessLogging.java index b79929db34f..2d6f0168455 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/app/WebServerProcessLogging.java +++ b/server/sonar-server/src/main/java/org/sonar/server/app/WebServerProcessLogging.java @@ -23,6 +23,7 @@ import ch.qos.logback.classic.Level; import org.sonar.process.ProcessId; import org.sonar.process.logging.LogDomain; import org.sonar.process.logging.LogLevelConfig; +import org.sonar.server.log.ServerProcessLogging; import static org.sonar.server.platform.web.requestid.RequestIdMDCStorage.HTTP_REQUEST_ID_MDC_KEY; diff --git a/server/sonar-server/src/main/java/org/sonar/server/ce/CeModule.java b/server/sonar-server/src/main/java/org/sonar/server/ce/CeModule.java new file mode 100644 index 00000000000..00442c1ef3c --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/ce/CeModule.java @@ -0,0 +1,42 @@ +/* + * 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.ce; + +import org.sonar.ce.http.CeHttpClientImpl; +import org.sonar.ce.queue.CeQueueImpl; +import org.sonar.ce.task.log.CeTaskLogging; +import org.sonar.ce.task.projectanalysis.ReportTaskProcessorDeclaration; +import org.sonar.core.platform.Module; +import org.sonar.server.ce.queue.ReportSubmitter; + +public class CeModule extends Module { + @Override + protected void configureModule() { + add(CeTaskLogging.class, + CeHttpClientImpl.class, + + // Queue + CeQueueImpl.class, + ReportSubmitter.class, + + // Core tasks processors + ReportTaskProcessorDeclaration.class); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/ce/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/ce/package-info.java new file mode 100644 index 00000000000..259e9b9cbaa --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/ce/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.ce; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityAction.java b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityAction.java index 67724db7f62..164ca9fd7c5 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityAction.java @@ -35,7 +35,7 @@ import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService.Param; import org.sonar.api.web.UserRole; -import org.sonar.ce.taskprocessor.CeTaskProcessor; +import org.sonar.ce.task.taskprocessor.CeTaskProcessor; import org.sonar.core.util.Uuids; import org.sonar.db.DbClient; import org.sonar.db.DbSession; diff --git a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/TaskTypesAction.java b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/TaskTypesAction.java index c401e6dbdac..15dd8ad7ef3 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/TaskTypesAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/TaskTypesAction.java @@ -24,7 +24,7 @@ import java.util.Set; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; -import org.sonar.ce.taskprocessor.CeTaskProcessor; +import org.sonar.ce.task.taskprocessor.CeTaskProcessor; import org.sonarqube.ws.Ce; import static org.sonar.server.ws.WsUtils.writeProtobuf; diff --git a/server/sonar-server/src/main/java/org/sonar/server/notification/NotificationManager.java b/server/sonar-server/src/main/java/org/sonar/server/notification/NotificationManager.java deleted file mode 100644 index e7a4ab23501..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/notification/NotificationManager.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * 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.notification; - -import com.google.common.collect.Multimap; -import java.util.Objects; -import org.sonar.api.notifications.Notification; -import org.sonar.api.notifications.NotificationChannel; -import org.sonar.api.web.UserRole; - -import static java.util.Objects.requireNonNull; - -/** - * The notification manager receives notifications and is in charge of storing them so that they are processed by the notification service. - *

- * Pico provides an instance of this class, and plugins just need to create notifications and pass them to this manager with - * the {@link NotificationManager#scheduleForSending(Notification)} method. - *

- */ -public interface NotificationManager { - - /** - * Receives a notification and stores it so that it is processed by the notification service. - * - * @param notification the notification. - */ - void scheduleForSending(Notification notification); - - /** - *

- * Returns the list of users who subscribed to the given dispatcher, along with the notification channels (email, twitter, ...) that they choose - * for this dispatcher. - *

- *

- * The resource ID can be null in case of notifications that have nothing to do with a specific project (like system notifications). - *

- * - * @param dispatcher the dispatcher for which this list of users is requested - * @param projectUuid UUID of the project - * @param subscriberPermissionsOnProject the required permission for global and project subscribers - * - * @return the list of user login along with the subscribed channels - */ - Multimap findSubscribedRecipientsForDispatcher(NotificationDispatcher dispatcher, String projectUuid, - SubscriberPermissionsOnProject subscriberPermissionsOnProject); - - final class SubscriberPermissionsOnProject { - public static final SubscriberPermissionsOnProject ALL_MUST_HAVE_ROLE_USER = new SubscriberPermissionsOnProject(UserRole.USER); - - private final String globalSubscribers; - private final String projectSubscribers; - - public SubscriberPermissionsOnProject(String globalAndProjectSubscribers) { - this(globalAndProjectSubscribers, globalAndProjectSubscribers); - } - - public SubscriberPermissionsOnProject(String globalSubscribers, String projectSubscribers) { - this.globalSubscribers = requireNonNull(globalSubscribers, "global subscribers's required permission can't be null"); - this.projectSubscribers = requireNonNull(projectSubscribers, "project subscribers's required permission can't be null"); - } - - public String getGlobalSubscribers() { - return globalSubscribers; - } - - public String getProjectSubscribers() { - return projectSubscribers; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - SubscriberPermissionsOnProject that = (SubscriberPermissionsOnProject) o; - return globalSubscribers.equals(that.globalSubscribers) && projectSubscribers.equals(that.projectSubscribers); - } - - @Override - public int hashCode() { - return Objects.hash(globalSubscribers, projectSubscribers); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganization.java b/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganization.java deleted file mode 100644 index 24cce29fca9..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganization.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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.organization; - -import static java.util.Objects.requireNonNull; - -public class DefaultOrganization { - private final String uuid; - private final String key; - private final String name; - private final long createdAt; - private final long updatedAt; - - private DefaultOrganization(Builder builder) { - this.uuid = requireNonNull(builder.uuid, "uuid can't be null"); - this.key = requireNonNull(builder.key, "key can't be null"); - this.name = requireNonNull(builder.name, "name can't be null"); - this.createdAt = requireNonNull(builder.createdAt, "createdAt can't be null"); - this.updatedAt = requireNonNull(builder.updatedAt, "updatedAt can't be null"); - } - - public static Builder newBuilder() { - return new Builder(); - } - - public String getUuid() { - return uuid; - } - - public String getKey() { - return key; - } - - public String getName() { - return name; - } - - public long getCreatedAt() { - return createdAt; - } - - public long getUpdatedAt() { - return updatedAt; - } - - @Override - public String toString() { - return "DefaultOrganization{" + - "uuid='" + uuid + '\'' + - ", key='" + key + '\'' + - ", name='" + name + '\'' + - ", createdAt=" + createdAt + - ", updatedAt=" + updatedAt + - '}'; - } - - public static final class Builder { - private String uuid; - private String key; - private String name; - private Long createdAt; - private Long updatedAt; - - public Builder setUuid(String uuid) { - this.uuid = uuid; - return this; - } - - public Builder setKey(String key) { - this.key = key; - return this; - } - - public Builder setName(String name) { - this.name = name; - return this; - } - - public Builder setCreatedAt(long createdAt) { - this.createdAt = createdAt; - return this; - } - - public Builder setUpdatedAt(long updatedAt) { - this.updatedAt = updatedAt; - return this; - } - - public DefaultOrganization build() { - return new DefaultOrganization(this); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationCache.java b/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationCache.java deleted file mode 100644 index 76168e963c6..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationCache.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.organization; - -public interface DefaultOrganizationCache { - - /** - * Loads {@link DefaultOrganization} in cache. - */ - void load(); - - /** - * Unloads {@link DefaultOrganization} from cache. - */ - void unload(); -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationProvider.java b/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationProvider.java deleted file mode 100644 index 4cd76b2d623..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationProvider.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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.organization; - -public interface DefaultOrganizationProvider { - /** - * @throws IllegalStateException if there is no default organization - */ - DefaultOrganization get(); - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationProviderImpl.java b/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationProviderImpl.java deleted file mode 100644 index 72ef09bed1f..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationProviderImpl.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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.organization; - -import java.util.Optional; -import java.util.function.Supplier; -import javax.annotation.CheckForNull; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.server.property.InternalProperties; - -import static com.google.common.base.Preconditions.checkState; - -public class DefaultOrganizationProviderImpl implements DefaultOrganizationProvider, DefaultOrganizationCache { - private static final ThreadLocal CACHE = new ThreadLocal<>(); - - private final DbClient dbClient; - - public DefaultOrganizationProviderImpl(DbClient dbClient) { - this.dbClient = dbClient; - } - - @Override - public DefaultOrganization get() { - Cache cache = CACHE.get(); - if (cache != null) { - return cache.get(() -> getDefaultOrganization(dbClient)); - } - - return getDefaultOrganization(dbClient); - } - - private static DefaultOrganization getDefaultOrganization(DbClient dbClient) { - try (DbSession dbSession = dbClient.openSession(false)) { - Optional uuid = dbClient.internalPropertiesDao().selectByKey(dbSession, InternalProperties.DEFAULT_ORGANIZATION); - checkState(uuid.isPresent() && !uuid.get().isEmpty(), "No Default organization uuid configured"); - Optional dto = dbClient.organizationDao().selectByUuid(dbSession, uuid.get()); - checkState(dto.isPresent(), "Default organization with uuid '%s' does not exist", uuid.get()); - return toDefaultOrganization(dto.get()); - } - } - - private static DefaultOrganization toDefaultOrganization(OrganizationDto organizationDto) { - return DefaultOrganization.newBuilder() - .setUuid(organizationDto.getUuid()) - .setKey(organizationDto.getKey()) - .setName(organizationDto.getName()) - .setCreatedAt(organizationDto.getCreatedAt()) - .setUpdatedAt(organizationDto.getUpdatedAt()) - .build(); - } - - @Override - public void load() { - checkState( - CACHE.get() == null, - "load called twice for thread '%s' or state wasn't cleared last time it was used", - Thread.currentThread().getName()); - CACHE.set(new Cache()); - } - - @Override - public void unload() { - CACHE.remove(); - } - - private static final class Cache { - @CheckForNull - private DefaultOrganization defaultOrganization; - - public DefaultOrganization get(Supplier supplier) { - if (defaultOrganization == null) { - defaultOrganization = supplier.get(); - } - return defaultOrganization; - } - - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/DatabaseServerCompatibility.java b/server/sonar-server/src/main/java/org/sonar/server/platform/DatabaseServerCompatibility.java index d252a90156a..8f132fd72ee 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/DatabaseServerCompatibility.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/DatabaseServerCompatibility.java @@ -27,7 +27,7 @@ import org.sonar.api.utils.log.Loggers; import org.sonar.process.ProcessProperties; import org.sonar.server.platform.db.migration.version.DatabaseVersion; -import static org.sonar.server.app.ServerProcessLogging.STARTUP_LOGGER_NAME; +import static org.sonar.server.log.ServerProcessLogging.STARTUP_LOGGER_NAME; public class DatabaseServerCompatibility implements Startable { diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerLogging.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerLogging.java index 1473be4594e..c1e4b87780d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerLogging.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerLogging.java @@ -32,7 +32,7 @@ import org.sonar.api.utils.log.LoggerLevel; import org.sonar.api.utils.log.Loggers; import org.sonar.db.Database; import org.sonar.process.logging.LogbackHelper; -import org.sonar.server.app.ServerProcessLogging; +import org.sonar.server.log.ServerProcessLogging; import static org.sonar.api.utils.log.LoggerLevel.TRACE; import static org.sonar.process.ProcessProperties.Property.PATH_LOGS; diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index 3b9dc86758c..0b1e7bcfbab 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -28,7 +28,7 @@ import org.sonar.api.resources.ResourceTypes; import org.sonar.api.rules.AnnotationRuleParser; import org.sonar.api.rules.XMLRuleParser; import org.sonar.api.server.rule.RulesDefinitionXmlLoader; -import org.sonar.ce.CeModule; +import org.sonar.server.ce.CeModule; import org.sonar.ce.notification.ReportAnalysisFailureNotificationModule; import org.sonar.core.component.DefaultResourceTypes; import org.sonar.core.extension.CoreExtensionsInstaller; diff --git a/server/sonar-server/src/main/java/org/sonar/server/property/InternalProperties.java b/server/sonar-server/src/main/java/org/sonar/server/property/InternalProperties.java deleted file mode 100644 index 15260eaf075..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/property/InternalProperties.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.property; - -import java.util.Optional; -import javax.annotation.Nullable; - -/** - * Allows to read and write internal properties. - */ -public interface InternalProperties { - /** - * The UUID of the default organization. - * Can't be null unless SQ is strongly corrupted. - */ - String DEFAULT_ORGANIZATION = "organization.default"; - - String ORGANIZATION_ENABLED = "organization.enabled"; - - String SERVER_ID_CHECKSUM = "server.idChecksum"; - - /** - * Compute Engine is pausing/paused if property value is "true". - */ - String COMPUTE_ENGINE_PAUSE = "ce.pause"; - - String BITBUCKETCLOUD_APP_SHAREDSECRET = "bbc.app.sharedSecret"; - /** - * Read the value of the specified property. - * - * @return {@link Optional#empty()} if the property does not exist, an empty string if the property is empty, - * otherwise the value of the property as a String. - * - * @throws IllegalArgumentException if {@code propertyKey} is {@code null} or empty - */ - Optional read(String propertyKey); - - /** - * Write the value of the specified property. - *

- * {@code null} and empty string are valid values which will persist the specified property as empty. - *

- * - * @throws IllegalArgumentException if {@code propertyKey} is {@code null} or empty - */ - void write(String propertyKey, @Nullable String value); -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/property/InternalPropertiesImpl.java b/server/sonar-server/src/main/java/org/sonar/server/property/InternalPropertiesImpl.java deleted file mode 100644 index 55cc690e93e..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/property/InternalPropertiesImpl.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.property; - -import java.util.Optional; -import javax.annotation.Nullable; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; - -import static com.google.common.base.Preconditions.checkArgument; - -/** - * A cache-less implementation of {@link InternalProperties} reading and writing to DB table INTERNAL_PROPERTIES. - */ -public class InternalPropertiesImpl implements InternalProperties { - private final DbClient dbClient; - - public InternalPropertiesImpl(DbClient dbClient) { - this.dbClient = dbClient; - } - - @Override - public Optional read(String propertyKey) { - checkPropertyKey(propertyKey); - - try (DbSession dbSession = dbClient.openSession(false)) { - return dbClient.internalPropertiesDao().selectByKey(dbSession, propertyKey); - } - } - - @Override - public void write(String propertyKey, @Nullable String value) { - checkPropertyKey(propertyKey); - - try (DbSession dbSession = dbClient.openSession(false)) { - if (value == null || value.isEmpty()) { - dbClient.internalPropertiesDao().saveAsEmpty(dbSession, propertyKey); - } else { - dbClient.internalPropertiesDao().save(dbSession, propertyKey, value); - } - dbSession.commit(); - } - } - - private static void checkPropertyKey(@Nullable String propertyKey) { - checkArgument(propertyKey != null && !propertyKey.isEmpty(), "property key can't be null nor empty"); - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/property/MapInternalProperties.java b/server/sonar-server/src/main/java/org/sonar/server/property/MapInternalProperties.java deleted file mode 100644 index ff799f47cea..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/property/MapInternalProperties.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.property; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import javax.annotation.Nullable; - -import static com.google.common.base.Preconditions.checkArgument; - -/** - * Map based implementation of {@link InternalProperties} to be used for unit testing. - */ -public class MapInternalProperties implements InternalProperties { - private final Map values = new HashMap<>(1); - - @Override - public Optional read(String propertyKey) { - checkPropertyKey(propertyKey); - return Optional.ofNullable(values.get(propertyKey)); - } - - @Override - public void write(String propertyKey, @Nullable String value) { - checkPropertyKey(propertyKey); - values.put(propertyKey, value); - } - - private static void checkPropertyKey(@Nullable String propertyKey) { - checkArgument(propertyKey != null && !propertyKey.isEmpty(), "property key can't be null nor empty"); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/property/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/property/package-info.java deleted file mode 100644 index d5d722326dc..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/property/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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.property; - -import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/test/java/org/sonar/ce/log/CeProcessLoggingTest.java b/server/sonar-server/src/test/java/org/sonar/ce/log/CeProcessLoggingTest.java deleted file mode 100644 index 27dcd888502..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/ce/log/CeProcessLoggingTest.java +++ /dev/null @@ -1,479 +0,0 @@ -/* - * 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.log; - -import ch.qos.logback.classic.Level; -import ch.qos.logback.classic.Logger; -import ch.qos.logback.classic.LoggerContext; -import ch.qos.logback.classic.encoder.PatternLayoutEncoder; -import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.core.Appender; -import ch.qos.logback.core.ConsoleAppender; -import ch.qos.logback.core.FileAppender; -import ch.qos.logback.core.joran.spi.JoranException; -import java.io.File; -import java.io.IOException; -import java.util.Properties; -import java.util.stream.Stream; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.rules.TemporaryFolder; -import org.sonar.process.Props; -import org.sonar.process.logging.LogbackHelper; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.slf4j.Logger.ROOT_LOGGER_NAME; -import static org.sonar.process.ProcessProperties.Property.PATH_LOGS; - -public class CeProcessLoggingTest { - - @Rule - public TemporaryFolder temp = new TemporaryFolder(); - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - private File logDir; - private Props props = new Props(new Properties()); - private CeProcessLogging underTest = new CeProcessLogging(); - - @Before - public void setUp() throws IOException { - logDir = temp.newFolder(); - props.set(PATH_LOGS.getKey(), logDir.getAbsolutePath()); - } - - @AfterClass - public static void resetLogback() throws JoranException { - new LogbackHelper().resetFromXml("/logback-test.xml"); - } - - @Test - public void do_not_log_to_console() { - LoggerContext ctx = underTest.configure(props); - - Logger root = ctx.getLogger(Logger.ROOT_LOGGER_NAME); - Appender appender = root.getAppender("CONSOLE"); - assertThat(appender).isNull(); - } - - @Test - public void startup_logger_prints_to_only_to_system_out() { - LoggerContext ctx = underTest.configure(props); - - Logger startup = ctx.getLogger("startup"); - assertThat(startup.isAdditive()).isFalse(); - Appender appender = startup.getAppender("CONSOLE"); - assertThat(appender).isInstanceOf(ConsoleAppender.class); - ConsoleAppender consoleAppender = (ConsoleAppender) appender; - assertThat(consoleAppender.getTarget()).isEqualTo("System.out"); - assertThat(consoleAppender.getEncoder()).isInstanceOf(PatternLayoutEncoder.class); - PatternLayoutEncoder patternEncoder = (PatternLayoutEncoder) consoleAppender.getEncoder(); - assertThat(patternEncoder.getPattern()).isEqualTo("%d{yyyy.MM.dd HH:mm:ss} %-5level app[][%logger{20}] %msg%n"); - } - - @Test - public void log_to_ce_file() { - LoggerContext ctx = underTest.configure(props); - - Logger root = ctx.getLogger(Logger.ROOT_LOGGER_NAME); - Appender appender = root.getAppender("file_ce"); - assertThat(appender).isInstanceOf(FileAppender.class); - FileAppender fileAppender = (FileAppender) appender; - assertThat(fileAppender.getFile()).isEqualTo(new File(logDir, "ce.log").getAbsolutePath()); - assertThat(fileAppender.getEncoder()).isInstanceOf(PatternLayoutEncoder.class); - PatternLayoutEncoder encoder = (PatternLayoutEncoder) fileAppender.getEncoder(); - assertThat(encoder.getPattern()).isEqualTo("%d{yyyy.MM.dd HH:mm:ss} %-5level ce[%X{ceTaskUuid}][%logger{20}] %msg%n"); - } - - @Test - public void default_level_for_root_logger_is_INFO() { - LoggerContext ctx = underTest.configure(props); - - verifyRootLogLevel(ctx, Level.INFO); - } - - @Test - public void root_logger_level_changes_with_global_property() { - props.set("sonar.log.level", "TRACE"); - - LoggerContext ctx = underTest.configure(props); - - verifyRootLogLevel(ctx, Level.TRACE); - } - - @Test - public void root_logger_level_changes_with_ce_property() { - props.set("sonar.log.level.ce", "TRACE"); - - LoggerContext ctx = underTest.configure(props); - - verifyRootLogLevel(ctx, Level.TRACE); - } - - @Test - public void root_logger_level_is_configured_from_ce_property_over_global_property() { - props.set("sonar.log.level", "TRACE"); - props.set("sonar.log.level.ce", "DEBUG"); - - LoggerContext ctx = underTest.configure(props); - - verifyRootLogLevel(ctx, Level.DEBUG); - } - - @Test - public void root_logger_level_changes_with_ce_property_and_is_case_insensitive() { - props.set("sonar.log.level.ce", "debug"); - - LoggerContext ctx = underTest.configure(props); - - verifyRootLogLevel(ctx, Level.DEBUG); - } - - @Test - public void sql_logger_level_changes_with_global_property_and_is_case_insensitive() { - props.set("sonar.log.level", "InFO"); - - LoggerContext ctx = underTest.configure(props); - - verifySqlLogLevel(ctx, Level.INFO); - } - - @Test - public void sql_logger_level_changes_with_ce_property_and_is_case_insensitive() { - props.set("sonar.log.level.ce", "TrACe"); - - LoggerContext ctx = underTest.configure(props); - - verifySqlLogLevel(ctx, Level.TRACE); - } - - @Test - public void sql_logger_level_changes_with_ce_sql_property_and_is_case_insensitive() { - props.set("sonar.log.level.ce.sql", "debug"); - - LoggerContext ctx = underTest.configure(props); - - verifySqlLogLevel(ctx, Level.DEBUG); - } - - @Test - public void sql_logger_level_is_configured_from_ce_sql_property_over_ce_property() { - props.set("sonar.log.level.ce.sql", "debug"); - props.set("sonar.log.level.ce", "TRACE"); - - LoggerContext ctx = underTest.configure(props); - - verifySqlLogLevel(ctx, Level.DEBUG); - } - - @Test - public void sql_logger_level_is_configured_from_ce_sql_property_over_global_property() { - props.set("sonar.log.level.ce.sql", "debug"); - props.set("sonar.log.level", "TRACE"); - - LoggerContext ctx = underTest.configure(props); - - verifySqlLogLevel(ctx, Level.DEBUG); - } - - @Test - public void sql_logger_level_is_configured_from_ce_property_over_global_property() { - props.set("sonar.log.level.ce", "debug"); - props.set("sonar.log.level", "TRACE"); - - LoggerContext ctx = underTest.configure(props); - - verifySqlLogLevel(ctx, Level.DEBUG); - } - - @Test - public void es_logger_level_changes_with_global_property_and_is_case_insensitive() { - props.set("sonar.log.level", "InFO"); - - LoggerContext ctx = underTest.configure(props); - - verifyEsLogLevel(ctx, Level.INFO); - } - - @Test - public void es_logger_level_changes_with_ce_property_and_is_case_insensitive() { - props.set("sonar.log.level.ce", "TrACe"); - - LoggerContext ctx = underTest.configure(props); - - verifyEsLogLevel(ctx, Level.TRACE); - } - - @Test - public void es_logger_level_changes_with_ce_es_property_and_is_case_insensitive() { - props.set("sonar.log.level.ce.es", "debug"); - - LoggerContext ctx = underTest.configure(props); - - verifyEsLogLevel(ctx, Level.DEBUG); - } - - @Test - public void es_logger_level_is_configured_from_ce_es_property_over_ce_property() { - props.set("sonar.log.level.ce.es", "debug"); - props.set("sonar.log.level.ce", "TRACE"); - - LoggerContext ctx = underTest.configure(props); - - verifyEsLogLevel(ctx, Level.DEBUG); - } - - @Test - public void es_logger_level_is_configured_from_ce_es_property_over_global_property() { - props.set("sonar.log.level.ce.es", "debug"); - props.set("sonar.log.level", "TRACE"); - - LoggerContext ctx = underTest.configure(props); - - verifyEsLogLevel(ctx, Level.DEBUG); - } - - @Test - public void es_logger_level_is_configured_from_ce_property_over_global_property() { - props.set("sonar.log.level.ce", "debug"); - props.set("sonar.log.level", "TRACE"); - - LoggerContext ctx = underTest.configure(props); - - verifyEsLogLevel(ctx, Level.DEBUG); - } - - @Test - public void jmx_logger_level_changes_with_global_property_and_is_case_insensitive() { - props.set("sonar.log.level", "InFO"); - - LoggerContext ctx = underTest.configure(props); - - verifyJmxLogLevel(ctx, Level.INFO); - } - - @Test - public void jmx_logger_level_changes_with_jmx_property_and_is_case_insensitive() { - props.set("sonar.log.level.ce", "TrACe"); - - LoggerContext ctx = underTest.configure(props); - - verifyJmxLogLevel(ctx, Level.TRACE); - } - - @Test - public void jmx_logger_level_changes_with_ce_jmx_property_and_is_case_insensitive() { - props.set("sonar.log.level.ce.jmx", "debug"); - - LoggerContext ctx = underTest.configure(props); - - verifyJmxLogLevel(ctx, Level.DEBUG); - } - - @Test - public void jmx_logger_level_is_configured_from_ce_jmx_property_over_ce_property() { - props.set("sonar.log.level.ce.jmx", "debug"); - props.set("sonar.log.level.ce", "TRACE"); - - LoggerContext ctx = underTest.configure(props); - - verifyJmxLogLevel(ctx, Level.DEBUG); - } - - @Test - public void jmx_logger_level_is_configured_from_ce_jmx_property_over_global_property() { - props.set("sonar.log.level.ce.jmx", "debug"); - props.set("sonar.log.level", "TRACE"); - - LoggerContext ctx = underTest.configure(props); - - verifyJmxLogLevel(ctx, Level.DEBUG); - } - - @Test - public void jmx_logger_level_is_configured_from_ce_property_over_global_property() { - props.set("sonar.log.level.ce", "debug"); - props.set("sonar.log.level", "TRACE"); - - LoggerContext ctx = underTest.configure(props); - - verifyJmxLogLevel(ctx, Level.DEBUG); - } - - @Test - public void root_logger_level_defaults_to_INFO_if_ce_property_has_invalid_value() { - props.set("sonar.log.level.ce", "DodoDouh!"); - - LoggerContext ctx = underTest.configure(props); - verifyRootLogLevel(ctx, Level.INFO); - } - - @Test - public void sql_logger_level_defaults_to_INFO_if_ce_sql_property_has_invalid_value() { - props.set("sonar.log.level.ce.sql", "DodoDouh!"); - - LoggerContext ctx = underTest.configure(props); - verifySqlLogLevel(ctx, Level.INFO); - } - - @Test - public void es_logger_level_defaults_to_INFO_if_ce_es_property_has_invalid_value() { - props.set("sonar.log.level.ce.es", "DodoDouh!"); - - LoggerContext ctx = underTest.configure(props); - verifyEsLogLevel(ctx, Level.INFO); - } - - @Test - public void jmx_loggers_level_defaults_to_INFO_if_ce_jmx_property_has_invalid_value() { - props.set("sonar.log.level.ce.jmx", "DodoDouh!"); - - LoggerContext ctx = underTest.configure(props); - verifyJmxLogLevel(ctx, Level.INFO); - } - - @Test - public void fail_with_IAE_if_global_property_unsupported_level() { - props.set("sonar.log.level", "ERROR"); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("log level ERROR in property sonar.log.level is not a supported value (allowed levels are [TRACE, DEBUG, INFO])"); - - underTest.configure(props); - } - - @Test - public void fail_with_IAE_if_ce_property_unsupported_level() { - props.set("sonar.log.level.ce", "ERROR"); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("log level ERROR in property sonar.log.level.ce is not a supported value (allowed levels are [TRACE, DEBUG, INFO])"); - - underTest.configure(props); - } - - @Test - public void fail_with_IAE_if_ce_sql_property_unsupported_level() { - props.set("sonar.log.level.ce.sql", "ERROR"); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("log level ERROR in property sonar.log.level.ce.sql is not a supported value (allowed levels are [TRACE, DEBUG, INFO])"); - - underTest.configure(props); - } - - @Test - public void fail_with_IAE_if_ce_es_property_unsupported_level() { - props.set("sonar.log.level.ce.es", "ERROR"); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("log level ERROR in property sonar.log.level.ce.es is not a supported value (allowed levels are [TRACE, DEBUG, INFO])"); - - underTest.configure(props); - } - - @Test - public void fail_with_IAE_if_ce_jmx_property_unsupported_level() { - props.set("sonar.log.level.ce.jmx", "ERROR"); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("log level ERROR in property sonar.log.level.ce.jmx is not a supported value (allowed levels are [TRACE, DEBUG, INFO])"); - - underTest.configure(props); - } - - @Test - public void configure_defines_hardcoded_levels() { - LoggerContext context = underTest.configure(props); - - verifyImmutableLogLevels(context); - } - - @Test - public void configure_defines_hardcoded_levels_unchanged_by_global_property() { - props.set("sonar.log.level", "TRACE"); - - LoggerContext context = underTest.configure(props); - - verifyImmutableLogLevels(context); - } - - @Test - public void configure_defines_hardcoded_levels_unchanged_by_ce_property() { - props.set("sonar.log.level.ce", "TRACE"); - - LoggerContext context = underTest.configure(props); - - verifyImmutableLogLevels(context); - } - - @Test - public void configure_turns_off_some_MsSQL_driver_logger() { - LoggerContext context = underTest.configure(props); - - Stream.of("com.microsoft.sqlserver.jdbc.internals", - "com.microsoft.sqlserver.jdbc.ResultSet", - "com.microsoft.sqlserver.jdbc.Statement", - "com.microsoft.sqlserver.jdbc.Connection") - .forEach(loggerName -> assertThat(context.getLogger(loggerName).getLevel()).isEqualTo(Level.OFF)); - } - - private void verifyRootLogLevel(LoggerContext ctx, Level expected) { - assertThat(ctx.getLogger(ROOT_LOGGER_NAME).getLevel()).isEqualTo(expected); - } - - private void verifySqlLogLevel(LoggerContext ctx, Level expected) { - assertThat(ctx.getLogger("sql").getLevel()).isEqualTo(expected); - } - - private void verifyEsLogLevel(LoggerContext ctx, Level expected) { - assertThat(ctx.getLogger("es").getLevel()).isEqualTo(expected); - } - - private void verifyJmxLogLevel(LoggerContext ctx, Level expected) { - assertThat(ctx.getLogger("javax.management.remote.timeout").getLevel()).isEqualTo(expected); - assertThat(ctx.getLogger("javax.management.remote.misc").getLevel()).isEqualTo(expected); - assertThat(ctx.getLogger("javax.management.remote.rmi").getLevel()).isEqualTo(expected); - assertThat(ctx.getLogger("javax.management.mbeanserver").getLevel()).isEqualTo(expected); - assertThat(ctx.getLogger("sun.rmi.loader").getLevel()).isEqualTo(expected); - assertThat(ctx.getLogger("sun.rmi.transport.tcp").getLevel()).isEqualTo(expected); - assertThat(ctx.getLogger("sun.rmi.transport.misc").getLevel()).isEqualTo(expected); - assertThat(ctx.getLogger("sun.rmi.server.call").getLevel()).isEqualTo(expected); - assertThat(ctx.getLogger("sun.rmi.dgc").getLevel()).isEqualTo(expected); - } - - private void verifyImmutableLogLevels(LoggerContext ctx) { - assertThat(ctx.getLogger("org.apache.ibatis").getLevel()).isEqualTo(Level.WARN); - assertThat(ctx.getLogger("java.sql").getLevel()).isEqualTo(Level.WARN); - assertThat(ctx.getLogger("java.sql.ResultSet").getLevel()).isEqualTo(Level.WARN); - assertThat(ctx.getLogger("org.sonar.MEASURE_FILTER").getLevel()).isEqualTo(Level.WARN); - assertThat(ctx.getLogger("org.elasticsearch").getLevel()).isEqualTo(Level.INFO); - assertThat(ctx.getLogger("org.elasticsearch.node").getLevel()).isEqualTo(Level.INFO); - assertThat(ctx.getLogger("org.elasticsearch.http").getLevel()).isEqualTo(Level.INFO); - assertThat(ctx.getLogger("ch.qos.logback").getLevel()).isEqualTo(Level.WARN); - assertThat(ctx.getLogger("org.apache.catalina").getLevel()).isEqualTo(Level.INFO); - assertThat(ctx.getLogger("org.apache.coyote").getLevel()).isEqualTo(Level.INFO); - assertThat(ctx.getLogger("org.apache.jasper").getLevel()).isEqualTo(Level.INFO); - assertThat(ctx.getLogger("org.apache.tomcat").getLevel()).isEqualTo(Level.INFO); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationDispatcherTest.java b/server/sonar-server/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationDispatcherTest.java deleted file mode 100644 index 92b1c0b126e..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationDispatcherTest.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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.notification; - -import com.google.common.collect.HashMultimap; -import org.junit.Test; -import org.sonar.api.notifications.Notification; -import org.sonar.api.notifications.NotificationChannel; -import org.sonar.api.web.UserRole; -import org.sonar.server.notification.NotificationDispatcher; -import org.sonar.server.notification.NotificationDispatcherMetadata; -import org.sonar.server.notification.NotificationManager; -import org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject; - -import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; -import static org.assertj.core.api.Assertions.assertThat; -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 ReportAnalysisFailureNotificationDispatcherTest { - private NotificationManager notificationManager = mock(NotificationManager.class); - private Notification notificationMock = mock(Notification.class); - private NotificationDispatcher.Context contextMock = mock(NotificationDispatcher.Context.class); - private ReportAnalysisFailureNotificationDispatcher underTest = new ReportAnalysisFailureNotificationDispatcher(notificationManager); - - @Test - public void dispatcher_defines_key() { - assertThat(underTest.getKey()).isNotEmpty(); - } - - @Test - public void newMetadata_indicates_enabled_global_and_project_level_notifications() { - NotificationDispatcherMetadata metadata = ReportAnalysisFailureNotificationDispatcher.newMetadata(); - - assertThat(metadata.getProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION)).isEqualTo("true"); - assertThat(metadata.getProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION)).isEqualTo("true"); - } - - @Test - public void performDispatch_has_no_effect_if_type_is_empty() { - when(notificationMock.getType()).thenReturn(""); - - underTest.performDispatch(notificationMock, contextMock); - - verify(notificationMock).getType(); - verifyNoMoreInteractions(notificationMock, contextMock); - } - - @Test - public void performDispatch_has_no_effect_if_type_is_not_ReportAnalysisFailureNotification_TYPE() { - when(notificationMock.getType()).thenReturn(randomAlphanumeric(6)); - - underTest.performDispatch(notificationMock, contextMock); - - verify(notificationMock).getType(); - verifyNoMoreInteractions(notificationMock, contextMock); - } - - @Test - public void performDispatch_adds_user_for_each_recipient_and_channel_for_the_component_uuid_in_the_notification() { - when(notificationMock.getType()).thenReturn(ReportAnalysisFailureNotification.TYPE); - String projectKey = randomAlphanumeric(9); - when(notificationMock.getFieldValue("project.key")).thenReturn(projectKey); - HashMultimap multimap = HashMultimap.create(); - String login1 = randomAlphanumeric(3); - String login2 = randomAlphanumeric(3); - NotificationChannel channel1 = mock(NotificationChannel.class); - NotificationChannel channel2 = mock(NotificationChannel.class); - NotificationChannel channel3 = mock(NotificationChannel.class); - multimap.put(login1, channel1); - multimap.put(login1, channel2); - multimap.put(login2, channel2); - multimap.put(login2, channel3); - when(notificationManager.findSubscribedRecipientsForDispatcher(underTest, projectKey, new SubscriberPermissionsOnProject(UserRole.ADMIN, UserRole.USER))) - .thenReturn(multimap); - - underTest.performDispatch(notificationMock, contextMock); - - verify(contextMock).addUser(login1, channel1); - verify(contextMock).addUser(login1, channel2); - verify(contextMock).addUser(login2, channel2); - verify(contextMock).addUser(login2, channel3); - verifyNoMoreInteractions(contextMock); - } - - @Test - public void performDispatch_adds_no_user_if_notification_manager_returns_none() { - when(notificationMock.getType()).thenReturn(ReportAnalysisFailureNotification.TYPE); - String projectKey = randomAlphanumeric(9); - when(notificationMock.getFieldValue("project.key")).thenReturn(projectKey); - HashMultimap multimap = HashMultimap.create(); - when(notificationManager.findSubscribedRecipientsForDispatcher(underTest, projectKey, new SubscriberPermissionsOnProject(UserRole.ADMIN, UserRole.USER))) - .thenReturn(multimap); - - underTest.performDispatch(notificationMock, contextMock); - - verifyNoMoreInteractions(contextMock); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationEmailTemplateTest.java b/server/sonar-server/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationEmailTemplateTest.java deleted file mode 100644 index daf31c43421..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationEmailTemplateTest.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * 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.notification; - -import java.util.Random; -import org.apache.commons.lang.RandomStringUtils; -import org.junit.Before; -import org.junit.Test; -import org.sonar.api.config.EmailSettings; -import org.sonar.api.notifications.Notification; -import org.sonar.api.utils.DateUtils; -import org.sonar.plugins.emailnotifications.api.EmailMessage; - -import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class ReportAnalysisFailureNotificationEmailTemplateTest { - private String serverUrl = RandomStringUtils.randomAlphanumeric(12); - private Notification notification = new Notification(ReportAnalysisFailureNotification.TYPE); - private Random random = new Random(); - private ReportAnalysisFailureNotification.Project projectNoBranch = new ReportAnalysisFailureNotification.Project( - randomAlphanumeric(2), - randomAlphanumeric(3), - randomAlphanumeric(4), - null); - private ReportAnalysisFailureNotification.Project projectWithBranch = new ReportAnalysisFailureNotification.Project( - randomAlphanumeric(6), - randomAlphanumeric(7), - randomAlphanumeric(8), - randomAlphanumeric(9)); - private ReportAnalysisFailureNotification.Task task = new ReportAnalysisFailureNotification.Task( - randomAlphanumeric(10), - random.nextInt(99), - random.nextInt(99)); - private String errorMessage = randomAlphanumeric(11); - - private ReportAnalysisFailureNotificationSerializer serializer = mock(ReportAnalysisFailureNotificationSerializer.class); - private EmailSettings emailSettings = mock(EmailSettings.class); - private ReportAnalysisFailureNotificationEmailTemplate underTest = new ReportAnalysisFailureNotificationEmailTemplate(serializer, emailSettings); - - @Before - public void setUp() throws Exception { - when(emailSettings.getServerBaseURL()).thenReturn(serverUrl); - } - - @Test - public void returns_null_if_notification_type_is_not_ReportAnalysisFailureNotification_TYPE() { - Notification notification = new Notification(RandomStringUtils.randomAlphanumeric(5)); - - EmailMessage emailMessage = underTest.format(notification); - - assertThat(emailMessage).isNull(); - } - - @Test - public void format_returns_email_with_subject_without_branch() { - when(serializer.fromNotification(notification)).thenReturn(new ReportAnalysisFailureNotification( - projectNoBranch, task, errorMessage)); - - EmailMessage emailMessage = underTest.format(notification); - - assertThat(emailMessage.getSubject()).isEqualTo(projectNoBranch.getName() + ": Background task in failure"); - } - - @Test - public void format_returns_email_with_subject_with_branch() { - when(serializer.fromNotification(notification)).thenReturn(new ReportAnalysisFailureNotification( - projectWithBranch, task, errorMessage)); - - EmailMessage emailMessage = underTest.format(notification); - - assertThat(emailMessage.getSubject()).isEqualTo(projectWithBranch.getName() + " (" + projectWithBranch.getBranchName() + "): Background task in failure"); - } - - @Test - public void format_returns_email_with_message_without_branch() { - when(serializer.fromNotification(notification)).thenReturn(new ReportAnalysisFailureNotification( - projectNoBranch, task, errorMessage)); - - EmailMessage emailMessage = underTest.format(notification); - - assertThat(emailMessage.getMessage()) - .contains("Project:\t" + projectNoBranch.getName() + "\n"); - } - - @Test - public void format_returns_email_with_message_with_branch() { - when(serializer.fromNotification(notification)).thenReturn(new ReportAnalysisFailureNotification( - projectWithBranch, task, errorMessage)); - - EmailMessage emailMessage = underTest.format(notification); - - assertThat(emailMessage.getMessage()) - .contains("Project:\t" + projectWithBranch.getName() + " (" + projectWithBranch.getBranchName() + ")\n"); - } - - @Test - public void format_returns_email_with_message_containing_all_information() { - when(serializer.fromNotification(notification)).thenReturn(new ReportAnalysisFailureNotification( - projectNoBranch, task, errorMessage)); - - EmailMessage emailMessage = underTest.format(notification); - - assertThat(emailMessage.getMessage()) - .isEqualTo("Project:\t" + projectNoBranch.getName() + "\n" + - "Background task:\t" + task.getUuid() + "\n" + - "Submission time:\t" + DateUtils.formatDateTime(task.getCreatedAt()) + "\n" + - "Failure time:\t" + DateUtils.formatDateTime(task.getFailedAt()) + "\n" + - "\n" + - "Error message:\t" + errorMessage + "\n" + - "\n" + - "More details at: " + serverUrl + "/project/background_tasks?id=" + projectNoBranch.getKey()); - } - - @Test - public void format_returns_email_with_message_with_error_message_when_null() { - when(serializer.fromNotification(notification)).thenReturn(new ReportAnalysisFailureNotification( - projectNoBranch, task, null)); - - EmailMessage emailMessage = underTest.format(notification); - - assertThat(emailMessage.getMessage()) - .doesNotContain("Error message:\t"); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationModuleTest.java b/server/sonar-server/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationModuleTest.java deleted file mode 100644 index aa1db53ec57..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationModuleTest.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.notification; - -import org.junit.Test; -import org.sonar.core.platform.ComponentContainer; -import org.sonar.server.notification.NotificationDispatcherMetadata; - -import static org.assertj.core.api.Assertions.assertThat; - -public class ReportAnalysisFailureNotificationModuleTest { - private ReportAnalysisFailureNotificationModule underTest = new ReportAnalysisFailureNotificationModule(); - - @Test - public void adds_dispatcher_and_its_metadata() { - ComponentContainer container = new ComponentContainer(); - - underTest.configure(container); - - assertThat(container.getPicoContainer().getComponentAdapters(NotificationDispatcherMetadata.class)).isNotNull(); - assertThat(container.getPicoContainer().getComponentAdapters(ReportAnalysisFailureNotificationDispatcher.class)).isNotNull(); - } - - @Test - public void adds_template_and_serializer() { - ComponentContainer container = new ComponentContainer(); - - underTest.configure(container); - - assertThat(container.getPicoContainer().getComponentAdapters(ReportAnalysisFailureNotificationEmailTemplate.class)).isNotNull(); - assertThat(container.getPicoContainer().getComponentAdapters(ReportAnalysisFailureNotificationSerializer.class)).isNotNull(); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationSerializerImplTest.java b/server/sonar-server/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationSerializerImplTest.java deleted file mode 100644 index 81bfbf5233b..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationSerializerImplTest.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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.notification; - -import java.util.Random; -import org.junit.Test; -import org.sonar.api.notifications.Notification; - -import static java.lang.String.valueOf; -import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; -import static org.assertj.core.api.Assertions.assertThat; - -public class ReportAnalysisFailureNotificationSerializerImplTest { - private Random random = new Random(); - private ReportAnalysisFailureNotification.Project project = new ReportAnalysisFailureNotification.Project( - randomAlphanumeric(2), - randomAlphanumeric(3), - randomAlphanumeric(4), - randomAlphanumeric(5)); - private ReportAnalysisFailureNotification.Task task = new ReportAnalysisFailureNotification.Task( - randomAlphanumeric(6), - random.nextInt(99), - random.nextInt(99)); - private String errorMessage = randomAlphanumeric(7); - private ReportAnalysisFailureNotificationSerializerImpl underTest = new ReportAnalysisFailureNotificationSerializerImpl(); - - @Test - public void verify_toNotification() { - - Notification notification = underTest.toNotification(new ReportAnalysisFailureNotification(project, task, errorMessage)); - - assertThat(notification.getFieldValue("project.uuid")).isEqualTo(project.getUuid()); - assertThat(notification.getFieldValue("project.name")).isEqualTo(project.getName()); - assertThat(notification.getFieldValue("project.key")).isEqualTo(project.getKey()); - assertThat(notification.getFieldValue("project.branchName")).isEqualTo(project.getBranchName()); - assertThat(notification.getFieldValue("task.uuid")).isEqualTo(task.getUuid()); - assertThat(notification.getFieldValue("task.createdAt")).isEqualTo(valueOf(task.getCreatedAt())); - assertThat(notification.getFieldValue("task.failedAt")).isEqualTo(valueOf(task.getFailedAt())); - assertThat(notification.getFieldValue("error.message")).isEqualTo(errorMessage); - } - - @Test - public void verify_fromNotification() { - Notification notification = new Notification(randomAlphanumeric(1)) - .setFieldValue("project.uuid", project.getUuid()) - .setFieldValue("project.name", project.getName()) - .setFieldValue("project.key", project.getKey()) - .setFieldValue("project.branchName", project.getBranchName()) - .setFieldValue("task.uuid", task.getUuid()) - .setFieldValue("task.createdAt", valueOf(task.getCreatedAt())) - .setFieldValue("task.failedAt", valueOf(task.getFailedAt())) - .setFieldValue("error.message", errorMessage); - - ReportAnalysisFailureNotification reportAnalysisFailureNotification = underTest.fromNotification(notification); - - assertThat(reportAnalysisFailureNotification.getProject().getUuid()).isEqualTo(project.getUuid()); - assertThat(reportAnalysisFailureNotification.getProject().getKey()).isEqualTo(project.getKey()); - assertThat(reportAnalysisFailureNotification.getProject().getName()).isEqualTo(project.getName()); - assertThat(reportAnalysisFailureNotification.getProject().getBranchName()).isEqualTo(project.getBranchName()); - assertThat(reportAnalysisFailureNotification.getTask().getUuid()).isEqualTo(task.getUuid()); - assertThat(reportAnalysisFailureNotification.getTask().getCreatedAt()).isEqualTo(task.getCreatedAt()); - assertThat(reportAnalysisFailureNotification.getTask().getFailedAt()).isEqualTo(task.getFailedAt()); - assertThat(reportAnalysisFailureNotification.getErrorMessage()).isEqualTo(errorMessage); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationTest.java b/server/sonar-server/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationTest.java deleted file mode 100644 index 0652c345a45..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationTest.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * 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.notification; - -import java.util.Random; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; -import static org.assertj.core.api.Assertions.assertThat; - -public class ReportAnalysisFailureNotificationTest { - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - private Random random = new Random(); - private ReportAnalysisFailureNotification.Task task = new ReportAnalysisFailureNotification.Task( - randomAlphanumeric(2), random.nextInt(5_996), random.nextInt(9_635)); - private ReportAnalysisFailureNotification.Project project = new ReportAnalysisFailureNotification.Project( - randomAlphanumeric(6), randomAlphanumeric(7), randomAlphanumeric(8), randomAlphanumeric(9)); - - @Test - public void project_constructor_fails_if_uuid_is_null() { - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("uuid can't be null"); - - new ReportAnalysisFailureNotification.Project(null, randomAlphanumeric(2), randomAlphanumeric(3), randomAlphanumeric(4)); - } - - @Test - public void project_constructor_fails_if_key_is_null() { - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("key can't be null"); - - new ReportAnalysisFailureNotification.Project(randomAlphanumeric(2), null, randomAlphanumeric(3), randomAlphanumeric(4)); - } - - @Test - public void project_constructor_fails_if_name_is_null() { - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("name can't be null"); - - new ReportAnalysisFailureNotification.Project(randomAlphanumeric(2), randomAlphanumeric(3), null, randomAlphanumeric(4)); - } - - @Test - public void verify_report_getters() { - String uuid = randomAlphanumeric(2); - String key = randomAlphanumeric(3); - String name = randomAlphanumeric(4); - String branchName = randomAlphanumeric(5); - ReportAnalysisFailureNotification.Project underTest = new ReportAnalysisFailureNotification.Project(uuid, key, name, branchName); - - assertThat(underTest.getUuid()).isEqualTo(uuid); - assertThat(underTest.getName()).isEqualTo(name); - assertThat(underTest.getKey()).isEqualTo(key); - assertThat(underTest.getBranchName()).isEqualTo(branchName); - } - - @Test - public void project_supports_null_branch() { - ReportAnalysisFailureNotification.Project underTest = new ReportAnalysisFailureNotification.Project(randomAlphanumeric(2), randomAlphanumeric(3), randomAlphanumeric(4), null); - - assertThat(underTest.getBranchName()).isNull(); - } - - @Test - public void task_constructor_fails_if_uuid_is_null() { - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("uuid can't be null"); - - new ReportAnalysisFailureNotification.Task(null, random.nextInt(5_996), random.nextInt(9_635)); - } - - @Test - public void verify_task_getters() { - String uuid = randomAlphanumeric(6); - int createdAt = random.nextInt(5_996); - int failedAt = random.nextInt(9_635); - - ReportAnalysisFailureNotification.Task underTest = new ReportAnalysisFailureNotification.Task(uuid, createdAt, failedAt); - - assertThat(underTest.getUuid()).isEqualTo(uuid); - assertThat(underTest.getCreatedAt()).isEqualTo(createdAt); - assertThat(underTest.getFailedAt()).isEqualTo(failedAt); - } - - @Test - public void constructor_fails_with_NPE_if_Project_is_null() { - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("project can't be null"); - - new ReportAnalysisFailureNotification(null, task, randomAlphanumeric(99)); - } - - @Test - public void constructor_fails_with_NPE_if_Task_is_null() { - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("task can't be null"); - - new ReportAnalysisFailureNotification(project, null, randomAlphanumeric(99)); - } - - @Test - public void verify_getters() { - String message = randomAlphanumeric(99); - ReportAnalysisFailureNotification underTest = new ReportAnalysisFailureNotification(project, task, message); - - assertThat(underTest.getProject()).isSameAs(project); - assertThat(underTest.getTask()).isSameAs(task); - assertThat(underTest.getErrorMessage()).isSameAs(message); - } - - @Test - public void null_error_message_is_supported() { - ReportAnalysisFailureNotification underTest = new ReportAnalysisFailureNotification(project, task, null); - - assertThat(underTest.getErrorMessage()).isNull(); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/ReportTaskProcessorDeclarationTest.java b/server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/ReportTaskProcessorDeclarationTest.java new file mode 100644 index 00000000000..971c3e8cf93 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/ReportTaskProcessorDeclarationTest.java @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.ce.task.CeTask; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class ReportTaskProcessorDeclarationTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private ReportTaskProcessorDeclaration underTest = new ReportTaskProcessorDeclaration(); + + @Test + public void getHandledCeTaskTypes_returns_REPORT() { + assertThat(underTest.getHandledCeTaskTypes()).containsOnly("REPORT"); + } + + @Test + public void process_throws_UOE() { + expectedException.expect(UnsupportedOperationException.class); + expectedException.expectMessage("process must not be called in WebServer"); + + underTest.process(mock(CeTask.class)); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationDispatcherTest.java b/server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationDispatcherTest.java new file mode 100644 index 00000000000..92b1c0b126e --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationDispatcherTest.java @@ -0,0 +1,118 @@ +/* + * 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.notification; + +import com.google.common.collect.HashMultimap; +import org.junit.Test; +import org.sonar.api.notifications.Notification; +import org.sonar.api.notifications.NotificationChannel; +import org.sonar.api.web.UserRole; +import org.sonar.server.notification.NotificationDispatcher; +import org.sonar.server.notification.NotificationDispatcherMetadata; +import org.sonar.server.notification.NotificationManager; +import org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject; + +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; +import static org.assertj.core.api.Assertions.assertThat; +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 ReportAnalysisFailureNotificationDispatcherTest { + private NotificationManager notificationManager = mock(NotificationManager.class); + private Notification notificationMock = mock(Notification.class); + private NotificationDispatcher.Context contextMock = mock(NotificationDispatcher.Context.class); + private ReportAnalysisFailureNotificationDispatcher underTest = new ReportAnalysisFailureNotificationDispatcher(notificationManager); + + @Test + public void dispatcher_defines_key() { + assertThat(underTest.getKey()).isNotEmpty(); + } + + @Test + public void newMetadata_indicates_enabled_global_and_project_level_notifications() { + NotificationDispatcherMetadata metadata = ReportAnalysisFailureNotificationDispatcher.newMetadata(); + + assertThat(metadata.getProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION)).isEqualTo("true"); + assertThat(metadata.getProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION)).isEqualTo("true"); + } + + @Test + public void performDispatch_has_no_effect_if_type_is_empty() { + when(notificationMock.getType()).thenReturn(""); + + underTest.performDispatch(notificationMock, contextMock); + + verify(notificationMock).getType(); + verifyNoMoreInteractions(notificationMock, contextMock); + } + + @Test + public void performDispatch_has_no_effect_if_type_is_not_ReportAnalysisFailureNotification_TYPE() { + when(notificationMock.getType()).thenReturn(randomAlphanumeric(6)); + + underTest.performDispatch(notificationMock, contextMock); + + verify(notificationMock).getType(); + verifyNoMoreInteractions(notificationMock, contextMock); + } + + @Test + public void performDispatch_adds_user_for_each_recipient_and_channel_for_the_component_uuid_in_the_notification() { + when(notificationMock.getType()).thenReturn(ReportAnalysisFailureNotification.TYPE); + String projectKey = randomAlphanumeric(9); + when(notificationMock.getFieldValue("project.key")).thenReturn(projectKey); + HashMultimap multimap = HashMultimap.create(); + String login1 = randomAlphanumeric(3); + String login2 = randomAlphanumeric(3); + NotificationChannel channel1 = mock(NotificationChannel.class); + NotificationChannel channel2 = mock(NotificationChannel.class); + NotificationChannel channel3 = mock(NotificationChannel.class); + multimap.put(login1, channel1); + multimap.put(login1, channel2); + multimap.put(login2, channel2); + multimap.put(login2, channel3); + when(notificationManager.findSubscribedRecipientsForDispatcher(underTest, projectKey, new SubscriberPermissionsOnProject(UserRole.ADMIN, UserRole.USER))) + .thenReturn(multimap); + + underTest.performDispatch(notificationMock, contextMock); + + verify(contextMock).addUser(login1, channel1); + verify(contextMock).addUser(login1, channel2); + verify(contextMock).addUser(login2, channel2); + verify(contextMock).addUser(login2, channel3); + verifyNoMoreInteractions(contextMock); + } + + @Test + public void performDispatch_adds_no_user_if_notification_manager_returns_none() { + when(notificationMock.getType()).thenReturn(ReportAnalysisFailureNotification.TYPE); + String projectKey = randomAlphanumeric(9); + when(notificationMock.getFieldValue("project.key")).thenReturn(projectKey); + HashMultimap multimap = HashMultimap.create(); + when(notificationManager.findSubscribedRecipientsForDispatcher(underTest, projectKey, new SubscriberPermissionsOnProject(UserRole.ADMIN, UserRole.USER))) + .thenReturn(multimap); + + underTest.performDispatch(notificationMock, contextMock); + + verifyNoMoreInteractions(contextMock); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationEmailTemplateTest.java b/server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationEmailTemplateTest.java new file mode 100644 index 00000000000..daf31c43421 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationEmailTemplateTest.java @@ -0,0 +1,144 @@ +/* + * 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.notification; + +import java.util.Random; +import org.apache.commons.lang.RandomStringUtils; +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.config.EmailSettings; +import org.sonar.api.notifications.Notification; +import org.sonar.api.utils.DateUtils; +import org.sonar.plugins.emailnotifications.api.EmailMessage; + +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ReportAnalysisFailureNotificationEmailTemplateTest { + private String serverUrl = RandomStringUtils.randomAlphanumeric(12); + private Notification notification = new Notification(ReportAnalysisFailureNotification.TYPE); + private Random random = new Random(); + private ReportAnalysisFailureNotification.Project projectNoBranch = new ReportAnalysisFailureNotification.Project( + randomAlphanumeric(2), + randomAlphanumeric(3), + randomAlphanumeric(4), + null); + private ReportAnalysisFailureNotification.Project projectWithBranch = new ReportAnalysisFailureNotification.Project( + randomAlphanumeric(6), + randomAlphanumeric(7), + randomAlphanumeric(8), + randomAlphanumeric(9)); + private ReportAnalysisFailureNotification.Task task = new ReportAnalysisFailureNotification.Task( + randomAlphanumeric(10), + random.nextInt(99), + random.nextInt(99)); + private String errorMessage = randomAlphanumeric(11); + + private ReportAnalysisFailureNotificationSerializer serializer = mock(ReportAnalysisFailureNotificationSerializer.class); + private EmailSettings emailSettings = mock(EmailSettings.class); + private ReportAnalysisFailureNotificationEmailTemplate underTest = new ReportAnalysisFailureNotificationEmailTemplate(serializer, emailSettings); + + @Before + public void setUp() throws Exception { + when(emailSettings.getServerBaseURL()).thenReturn(serverUrl); + } + + @Test + public void returns_null_if_notification_type_is_not_ReportAnalysisFailureNotification_TYPE() { + Notification notification = new Notification(RandomStringUtils.randomAlphanumeric(5)); + + EmailMessage emailMessage = underTest.format(notification); + + assertThat(emailMessage).isNull(); + } + + @Test + public void format_returns_email_with_subject_without_branch() { + when(serializer.fromNotification(notification)).thenReturn(new ReportAnalysisFailureNotification( + projectNoBranch, task, errorMessage)); + + EmailMessage emailMessage = underTest.format(notification); + + assertThat(emailMessage.getSubject()).isEqualTo(projectNoBranch.getName() + ": Background task in failure"); + } + + @Test + public void format_returns_email_with_subject_with_branch() { + when(serializer.fromNotification(notification)).thenReturn(new ReportAnalysisFailureNotification( + projectWithBranch, task, errorMessage)); + + EmailMessage emailMessage = underTest.format(notification); + + assertThat(emailMessage.getSubject()).isEqualTo(projectWithBranch.getName() + " (" + projectWithBranch.getBranchName() + "): Background task in failure"); + } + + @Test + public void format_returns_email_with_message_without_branch() { + when(serializer.fromNotification(notification)).thenReturn(new ReportAnalysisFailureNotification( + projectNoBranch, task, errorMessage)); + + EmailMessage emailMessage = underTest.format(notification); + + assertThat(emailMessage.getMessage()) + .contains("Project:\t" + projectNoBranch.getName() + "\n"); + } + + @Test + public void format_returns_email_with_message_with_branch() { + when(serializer.fromNotification(notification)).thenReturn(new ReportAnalysisFailureNotification( + projectWithBranch, task, errorMessage)); + + EmailMessage emailMessage = underTest.format(notification); + + assertThat(emailMessage.getMessage()) + .contains("Project:\t" + projectWithBranch.getName() + " (" + projectWithBranch.getBranchName() + ")\n"); + } + + @Test + public void format_returns_email_with_message_containing_all_information() { + when(serializer.fromNotification(notification)).thenReturn(new ReportAnalysisFailureNotification( + projectNoBranch, task, errorMessage)); + + EmailMessage emailMessage = underTest.format(notification); + + assertThat(emailMessage.getMessage()) + .isEqualTo("Project:\t" + projectNoBranch.getName() + "\n" + + "Background task:\t" + task.getUuid() + "\n" + + "Submission time:\t" + DateUtils.formatDateTime(task.getCreatedAt()) + "\n" + + "Failure time:\t" + DateUtils.formatDateTime(task.getFailedAt()) + "\n" + + "\n" + + "Error message:\t" + errorMessage + "\n" + + "\n" + + "More details at: " + serverUrl + "/project/background_tasks?id=" + projectNoBranch.getKey()); + } + + @Test + public void format_returns_email_with_message_with_error_message_when_null() { + when(serializer.fromNotification(notification)).thenReturn(new ReportAnalysisFailureNotification( + projectNoBranch, task, null)); + + EmailMessage emailMessage = underTest.format(notification); + + assertThat(emailMessage.getMessage()) + .doesNotContain("Error message:\t"); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationModuleTest.java b/server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationModuleTest.java new file mode 100644 index 00000000000..aa1db53ec57 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationModuleTest.java @@ -0,0 +1,50 @@ +/* + * 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.notification; + +import org.junit.Test; +import org.sonar.core.platform.ComponentContainer; +import org.sonar.server.notification.NotificationDispatcherMetadata; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ReportAnalysisFailureNotificationModuleTest { + private ReportAnalysisFailureNotificationModule underTest = new ReportAnalysisFailureNotificationModule(); + + @Test + public void adds_dispatcher_and_its_metadata() { + ComponentContainer container = new ComponentContainer(); + + underTest.configure(container); + + assertThat(container.getPicoContainer().getComponentAdapters(NotificationDispatcherMetadata.class)).isNotNull(); + assertThat(container.getPicoContainer().getComponentAdapters(ReportAnalysisFailureNotificationDispatcher.class)).isNotNull(); + } + + @Test + public void adds_template_and_serializer() { + ComponentContainer container = new ComponentContainer(); + + underTest.configure(container); + + assertThat(container.getPicoContainer().getComponentAdapters(ReportAnalysisFailureNotificationEmailTemplate.class)).isNotNull(); + assertThat(container.getPicoContainer().getComponentAdapters(ReportAnalysisFailureNotificationSerializer.class)).isNotNull(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationSerializerImplTest.java b/server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationSerializerImplTest.java new file mode 100644 index 00000000000..81bfbf5233b --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationSerializerImplTest.java @@ -0,0 +1,82 @@ +/* + * 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.notification; + +import java.util.Random; +import org.junit.Test; +import org.sonar.api.notifications.Notification; + +import static java.lang.String.valueOf; +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; +import static org.assertj.core.api.Assertions.assertThat; + +public class ReportAnalysisFailureNotificationSerializerImplTest { + private Random random = new Random(); + private ReportAnalysisFailureNotification.Project project = new ReportAnalysisFailureNotification.Project( + randomAlphanumeric(2), + randomAlphanumeric(3), + randomAlphanumeric(4), + randomAlphanumeric(5)); + private ReportAnalysisFailureNotification.Task task = new ReportAnalysisFailureNotification.Task( + randomAlphanumeric(6), + random.nextInt(99), + random.nextInt(99)); + private String errorMessage = randomAlphanumeric(7); + private ReportAnalysisFailureNotificationSerializerImpl underTest = new ReportAnalysisFailureNotificationSerializerImpl(); + + @Test + public void verify_toNotification() { + + Notification notification = underTest.toNotification(new ReportAnalysisFailureNotification(project, task, errorMessage)); + + assertThat(notification.getFieldValue("project.uuid")).isEqualTo(project.getUuid()); + assertThat(notification.getFieldValue("project.name")).isEqualTo(project.getName()); + assertThat(notification.getFieldValue("project.key")).isEqualTo(project.getKey()); + assertThat(notification.getFieldValue("project.branchName")).isEqualTo(project.getBranchName()); + assertThat(notification.getFieldValue("task.uuid")).isEqualTo(task.getUuid()); + assertThat(notification.getFieldValue("task.createdAt")).isEqualTo(valueOf(task.getCreatedAt())); + assertThat(notification.getFieldValue("task.failedAt")).isEqualTo(valueOf(task.getFailedAt())); + assertThat(notification.getFieldValue("error.message")).isEqualTo(errorMessage); + } + + @Test + public void verify_fromNotification() { + Notification notification = new Notification(randomAlphanumeric(1)) + .setFieldValue("project.uuid", project.getUuid()) + .setFieldValue("project.name", project.getName()) + .setFieldValue("project.key", project.getKey()) + .setFieldValue("project.branchName", project.getBranchName()) + .setFieldValue("task.uuid", task.getUuid()) + .setFieldValue("task.createdAt", valueOf(task.getCreatedAt())) + .setFieldValue("task.failedAt", valueOf(task.getFailedAt())) + .setFieldValue("error.message", errorMessage); + + ReportAnalysisFailureNotification reportAnalysisFailureNotification = underTest.fromNotification(notification); + + assertThat(reportAnalysisFailureNotification.getProject().getUuid()).isEqualTo(project.getUuid()); + assertThat(reportAnalysisFailureNotification.getProject().getKey()).isEqualTo(project.getKey()); + assertThat(reportAnalysisFailureNotification.getProject().getName()).isEqualTo(project.getName()); + assertThat(reportAnalysisFailureNotification.getProject().getBranchName()).isEqualTo(project.getBranchName()); + assertThat(reportAnalysisFailureNotification.getTask().getUuid()).isEqualTo(task.getUuid()); + assertThat(reportAnalysisFailureNotification.getTask().getCreatedAt()).isEqualTo(task.getCreatedAt()); + assertThat(reportAnalysisFailureNotification.getTask().getFailedAt()).isEqualTo(task.getFailedAt()); + assertThat(reportAnalysisFailureNotification.getErrorMessage()).isEqualTo(errorMessage); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationTest.java b/server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationTest.java new file mode 100644 index 00000000000..0652c345a45 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationTest.java @@ -0,0 +1,138 @@ +/* + * 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.notification; + +import java.util.Random; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; +import static org.assertj.core.api.Assertions.assertThat; + +public class ReportAnalysisFailureNotificationTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private Random random = new Random(); + private ReportAnalysisFailureNotification.Task task = new ReportAnalysisFailureNotification.Task( + randomAlphanumeric(2), random.nextInt(5_996), random.nextInt(9_635)); + private ReportAnalysisFailureNotification.Project project = new ReportAnalysisFailureNotification.Project( + randomAlphanumeric(6), randomAlphanumeric(7), randomAlphanumeric(8), randomAlphanumeric(9)); + + @Test + public void project_constructor_fails_if_uuid_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("uuid can't be null"); + + new ReportAnalysisFailureNotification.Project(null, randomAlphanumeric(2), randomAlphanumeric(3), randomAlphanumeric(4)); + } + + @Test + public void project_constructor_fails_if_key_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("key can't be null"); + + new ReportAnalysisFailureNotification.Project(randomAlphanumeric(2), null, randomAlphanumeric(3), randomAlphanumeric(4)); + } + + @Test + public void project_constructor_fails_if_name_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("name can't be null"); + + new ReportAnalysisFailureNotification.Project(randomAlphanumeric(2), randomAlphanumeric(3), null, randomAlphanumeric(4)); + } + + @Test + public void verify_report_getters() { + String uuid = randomAlphanumeric(2); + String key = randomAlphanumeric(3); + String name = randomAlphanumeric(4); + String branchName = randomAlphanumeric(5); + ReportAnalysisFailureNotification.Project underTest = new ReportAnalysisFailureNotification.Project(uuid, key, name, branchName); + + assertThat(underTest.getUuid()).isEqualTo(uuid); + assertThat(underTest.getName()).isEqualTo(name); + assertThat(underTest.getKey()).isEqualTo(key); + assertThat(underTest.getBranchName()).isEqualTo(branchName); + } + + @Test + public void project_supports_null_branch() { + ReportAnalysisFailureNotification.Project underTest = new ReportAnalysisFailureNotification.Project(randomAlphanumeric(2), randomAlphanumeric(3), randomAlphanumeric(4), null); + + assertThat(underTest.getBranchName()).isNull(); + } + + @Test + public void task_constructor_fails_if_uuid_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("uuid can't be null"); + + new ReportAnalysisFailureNotification.Task(null, random.nextInt(5_996), random.nextInt(9_635)); + } + + @Test + public void verify_task_getters() { + String uuid = randomAlphanumeric(6); + int createdAt = random.nextInt(5_996); + int failedAt = random.nextInt(9_635); + + ReportAnalysisFailureNotification.Task underTest = new ReportAnalysisFailureNotification.Task(uuid, createdAt, failedAt); + + assertThat(underTest.getUuid()).isEqualTo(uuid); + assertThat(underTest.getCreatedAt()).isEqualTo(createdAt); + assertThat(underTest.getFailedAt()).isEqualTo(failedAt); + } + + @Test + public void constructor_fails_with_NPE_if_Project_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("project can't be null"); + + new ReportAnalysisFailureNotification(null, task, randomAlphanumeric(99)); + } + + @Test + public void constructor_fails_with_NPE_if_Task_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("task can't be null"); + + new ReportAnalysisFailureNotification(project, null, randomAlphanumeric(99)); + } + + @Test + public void verify_getters() { + String message = randomAlphanumeric(99); + ReportAnalysisFailureNotification underTest = new ReportAnalysisFailureNotification(project, task, message); + + assertThat(underTest.getProject()).isSameAs(project); + assertThat(underTest.getTask()).isSameAs(task); + assertThat(underTest.getErrorMessage()).isSameAs(message); + } + + @Test + public void null_error_message_is_supported() { + ReportAnalysisFailureNotification underTest = new ReportAnalysisFailureNotification(project, task, null); + + assertThat(underTest.getErrorMessage()).isNull(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/ce/taskprocessor/ReportTaskProcessorDeclarationTest.java b/server/sonar-server/src/test/java/org/sonar/ce/taskprocessor/ReportTaskProcessorDeclarationTest.java deleted file mode 100644 index fd967656814..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/ce/taskprocessor/ReportTaskProcessorDeclarationTest.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.taskprocessor; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.ce.task.CeTask; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -public class ReportTaskProcessorDeclarationTest { - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - private ReportTaskProcessorDeclaration underTest = new ReportTaskProcessorDeclaration(); - - @Test - public void getHandledCeTaskTypes_returns_REPORT() { - assertThat(underTest.getHandledCeTaskTypes()).containsOnly("REPORT"); - } - - @Test - public void process_throws_UOE() { - expectedException.expect(UnsupportedOperationException.class); - expectedException.expectMessage("process must not be called in WebServer"); - - underTest.process(mock(CeTask.class)); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java index a55739065f3..6a584456b73 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java @@ -29,7 +29,7 @@ import org.junit.rules.ExpectedException; import org.sonar.api.server.ws.WebService.Param; import org.sonar.api.utils.System2; import org.sonar.api.web.UserRole; -import org.sonar.ce.taskprocessor.CeTaskProcessor; +import org.sonar.ce.task.taskprocessor.CeTaskProcessor; import org.sonar.core.util.Uuids; import org.sonar.db.DbTester; import org.sonar.db.ce.CeActivityDto; diff --git a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/TaskTypesActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/TaskTypesActionTest.java index 18aec801e52..94ce532bb9d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/TaskTypesActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/TaskTypesActionTest.java @@ -24,7 +24,7 @@ import java.util.Set; import org.junit.Test; import org.sonar.ce.task.CeTask; import org.sonar.ce.task.CeTaskResult; -import org.sonar.ce.taskprocessor.CeTaskProcessor; +import org.sonar.ce.task.taskprocessor.CeTaskProcessor; import org.sonar.server.ws.WsActionTester; import static org.sonar.test.JsonAssert.assertJson; diff --git a/server/sonar-server/src/test/java/org/sonar/server/notification/DefaultNotificationManagerTest.java b/server/sonar-server/src/test/java/org/sonar/server/notification/DefaultNotificationManagerTest.java deleted file mode 100644 index dcd3e1981a5..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/notification/DefaultNotificationManagerTest.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * 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.notification; - -import com.google.common.collect.Multimap; -import java.io.InvalidClassException; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import org.apache.commons.lang.RandomStringUtils; -import org.junit.Before; -import org.junit.Test; -import org.mockito.InOrder; -import org.sonar.api.notifications.Notification; -import org.sonar.api.notifications.NotificationChannel; -import org.sonar.api.web.UserRole; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.notification.NotificationQueueDao; -import org.sonar.db.notification.NotificationQueueDto; -import org.sonar.db.permission.AuthorizationDao; -import org.sonar.db.property.PropertiesDao; -import org.sonar.db.property.Subscriber; -import org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject; - -import static com.google.common.collect.Sets.newHashSet; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anySet; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.only; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.mockito.internal.verification.VerificationModeFactory.times; - -public class DefaultNotificationManagerTest { - - private DefaultNotificationManager underTest; - - private PropertiesDao propertiesDao = mock(PropertiesDao.class); - private NotificationDispatcher dispatcher = mock(NotificationDispatcher.class); - private NotificationChannel emailChannel = mock(NotificationChannel.class); - private NotificationChannel twitterChannel = mock(NotificationChannel.class); - private NotificationQueueDao notificationQueueDao = mock(NotificationQueueDao.class); - private AuthorizationDao authorizationDao = mock(AuthorizationDao.class); - private DbClient dbClient = mock(DbClient.class); - private DbSession dbSession = mock(DbSession.class); - - @Before - public void setUp() { - when(dispatcher.getKey()).thenReturn("NewViolations"); - when(emailChannel.getKey()).thenReturn("Email"); - when(twitterChannel.getKey()).thenReturn("Twitter"); - when(dbClient.openSession(anyBoolean())).thenReturn(dbSession); - when(dbClient.propertiesDao()).thenReturn(propertiesDao); - when(dbClient.notificationQueueDao()).thenReturn(notificationQueueDao); - when(dbClient.authorizationDao()).thenReturn(authorizationDao); - - underTest = new DefaultNotificationManager(new NotificationChannel[] {emailChannel, twitterChannel}, dbClient); - } - - @Test - public void shouldProvideChannelList() { - assertThat(underTest.getChannels()).containsOnly(emailChannel, twitterChannel); - - underTest = new DefaultNotificationManager(new NotificationChannel[] {}, dbClient); - assertThat(underTest.getChannels()).hasSize(0); - } - - @Test - public void shouldPersist() { - Notification notification = new Notification("test"); - underTest.scheduleForSending(notification); - - verify(notificationQueueDao, only()).insert(any(List.class)); - } - - @Test - public void shouldGetFromQueueAndDelete() { - Notification notification = new Notification("test"); - NotificationQueueDto dto = NotificationQueueDto.toNotificationQueueDto(notification); - List dtos = Arrays.asList(dto); - when(notificationQueueDao.selectOldest(1)).thenReturn(dtos); - - assertThat(underTest.getFromQueue()).isNotNull(); - - InOrder inOrder = inOrder(notificationQueueDao); - inOrder.verify(notificationQueueDao).selectOldest(1); - inOrder.verify(notificationQueueDao).delete(dtos); - } - - // SONAR-4739 - @Test - public void shouldNotFailWhenUnableToDeserialize() throws Exception { - NotificationQueueDto dto1 = mock(NotificationQueueDto.class); - when(dto1.toNotification()).thenThrow(new InvalidClassException("Pouet")); - List dtos = Arrays.asList(dto1); - when(notificationQueueDao.selectOldest(1)).thenReturn(dtos); - - underTest = spy(underTest); - assertThat(underTest.getFromQueue()).isNull(); - assertThat(underTest.getFromQueue()).isNull(); - - verify(underTest, times(1)).logDeserializationIssue(); - } - - @Test - public void shouldFindNoRecipient() { - assertThat(underTest.findSubscribedRecipientsForDispatcher(dispatcher, "uuid_45", new SubscriberPermissionsOnProject(UserRole.USER)).asMap().entrySet()) - .hasSize(0); - } - - @Test - public void shouldFindSubscribedRecipientForGivenResource() { - String projectUuid = "uuid_45"; - when(propertiesDao.findUsersForNotification("NewViolations", "Email", projectUuid)) - .thenReturn(newHashSet(new Subscriber("user1", false), new Subscriber("user3", false), new Subscriber("user3", true))); - when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", "uuid_56")) - .thenReturn(newHashSet(new Subscriber("user2", false))); - when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", projectUuid)) - .thenReturn(newHashSet(new Subscriber("user3", true))); - when(propertiesDao.findUsersForNotification("NewAlerts", "Twitter", projectUuid)) - .thenReturn(newHashSet(new Subscriber("user4", false))); - - when(authorizationDao.keepAuthorizedLoginsOnProject(dbSession, newHashSet("user1", "user3"), projectUuid, "user")) - .thenReturn(newHashSet("user1", "user3")); - - Multimap multiMap = underTest.findSubscribedRecipientsForDispatcher(dispatcher, projectUuid, - SubscriberPermissionsOnProject.ALL_MUST_HAVE_ROLE_USER); - assertThat(multiMap.entries()).hasSize(3); - - Map> map = multiMap.asMap(); - assertThat(map.get("user1")).containsOnly(emailChannel); - assertThat(map.get("user2")).isNull(); - assertThat(map.get("user3")).containsOnly(emailChannel, twitterChannel); - assertThat(map.get("user4")).isNull(); - - // code is optimized to perform only 1 SQL requests for all channels - verify(authorizationDao, times(1)).keepAuthorizedLoginsOnProject(eq(dbSession), anySet(), anyString(), anyString()); - } - - @Test - public void should_apply_distinct_permission_filtering_global_or_project_subscribers() { - String globalPermission = RandomStringUtils.randomAlphanumeric(4); - String projectPermission = RandomStringUtils.randomAlphanumeric(5); - String projectUuid = "uuid_45"; - when(propertiesDao.findUsersForNotification("NewViolations", "Email", projectUuid)) - .thenReturn(newHashSet(new Subscriber("user1", false), new Subscriber("user3", false), new Subscriber("user3", true))); - when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", "uuid_56")) - .thenReturn(newHashSet(new Subscriber("user2", false))); - when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", projectUuid)) - .thenReturn(newHashSet(new Subscriber("user3", true))); - when(propertiesDao.findUsersForNotification("NewAlerts", "Twitter", projectUuid)) - .thenReturn(newHashSet(new Subscriber("user4", false))); - - when(authorizationDao.keepAuthorizedLoginsOnProject(dbSession, newHashSet("user3", "user4"), projectUuid, globalPermission)) - .thenReturn(newHashSet("user3")); - when(authorizationDao.keepAuthorizedLoginsOnProject(dbSession, newHashSet("user1", "user3"), projectUuid, projectPermission)) - .thenReturn(newHashSet("user1", "user3")); - - Multimap multiMap = underTest.findSubscribedRecipientsForDispatcher(dispatcher, projectUuid, - new SubscriberPermissionsOnProject(globalPermission, projectPermission)); - assertThat(multiMap.entries()).hasSize(3); - - Map> map = multiMap.asMap(); - assertThat(map.get("user1")).containsOnly(emailChannel); - assertThat(map.get("user2")).isNull(); - assertThat(map.get("user3")).containsOnly(emailChannel, twitterChannel); - assertThat(map.get("user4")).isNull(); - - // code is optimized to perform only 2 SQL requests for all channels - verify(authorizationDao, times(1)).keepAuthorizedLoginsOnProject(eq(dbSession), anySet(), anyString(), eq(globalPermission)); - verify(authorizationDao, times(1)).keepAuthorizedLoginsOnProject(eq(dbSession), anySet(), anyString(), eq(projectPermission)); - } - - @Test - public void do_not_call_db_for_project_permission_filtering_if_there_is_no_project_subscriber() { - String globalPermission = RandomStringUtils.randomAlphanumeric(4); - String projectPermission = RandomStringUtils.randomAlphanumeric(5); - String projectUuid = "uuid_45"; - when(propertiesDao.findUsersForNotification("NewViolations", "Email", projectUuid)) - .thenReturn(newHashSet(new Subscriber("user3", true))); - when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", projectUuid)) - .thenReturn(newHashSet(new Subscriber("user3", true))); - - when(authorizationDao.keepAuthorizedLoginsOnProject(dbSession, newHashSet("user3"), projectUuid, globalPermission)) - .thenReturn(newHashSet("user3")); - - Multimap multiMap = underTest.findSubscribedRecipientsForDispatcher(dispatcher, projectUuid, - new SubscriberPermissionsOnProject(globalPermission, projectPermission)); - assertThat(multiMap.entries()).hasSize(2); - - Map> map = multiMap.asMap(); - assertThat(map.get("user3")).containsOnly(emailChannel, twitterChannel); - - verify(authorizationDao, times(1)).keepAuthorizedLoginsOnProject(eq(dbSession), anySet(), anyString(), eq(globalPermission)); - verify(authorizationDao, times(0)).keepAuthorizedLoginsOnProject(eq(dbSession), anySet(), anyString(), eq(projectPermission)); - } - - @Test - public void do_not_call_db_for_project_permission_filtering_if_there_is_no_global_subscriber() { - String globalPermission = RandomStringUtils.randomAlphanumeric(4); - String projectPermission = RandomStringUtils.randomAlphanumeric(5); - String projectUuid = "uuid_45"; - when(propertiesDao.findUsersForNotification("NewViolations", "Email", projectUuid)) - .thenReturn(newHashSet(new Subscriber("user3", false))); - when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", projectUuid)) - .thenReturn(newHashSet(new Subscriber("user3", false))); - - when(authorizationDao.keepAuthorizedLoginsOnProject(dbSession, newHashSet("user3"), projectUuid, projectPermission)) - .thenReturn(newHashSet("user3")); - - Multimap multiMap = underTest.findSubscribedRecipientsForDispatcher(dispatcher, projectUuid, - new SubscriberPermissionsOnProject(globalPermission, projectPermission)); - assertThat(multiMap.entries()).hasSize(2); - - Map> map = multiMap.asMap(); - assertThat(map.get("user3")).containsOnly(emailChannel, twitterChannel); - - verify(authorizationDao, times(0)).keepAuthorizedLoginsOnProject(eq(dbSession), anySet(), anyString(), eq(globalPermission)); - verify(authorizationDao, times(1)).keepAuthorizedLoginsOnProject(eq(dbSession), anySet(), anyString(), eq(projectPermission)); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/notification/NotificationDispatcherMetadataTest.java b/server/sonar-server/src/test/java/org/sonar/server/notification/NotificationDispatcherMetadataTest.java deleted file mode 100644 index eea8d05fcf7..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/notification/NotificationDispatcherMetadataTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.notification; - -import org.junit.Before; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class NotificationDispatcherMetadataTest { - - private NotificationDispatcherMetadata metadata; - - @Before - public void init() { - metadata = NotificationDispatcherMetadata.create("NewViolations").setProperty("global", "true"); - } - - @Test - public void shouldReturnDispatcherKey() { - assertThat(metadata.getDispatcherKey()).isEqualTo("NewViolations"); - } - - @Test - public void shouldReturnProperty() { - assertThat(metadata.getProperty("global")).isEqualTo("true"); - assertThat(metadata.getProperty("per-project")).isNull(); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/notification/NotificationDispatcherTest.java b/server/sonar-server/src/test/java/org/sonar/server/notification/NotificationDispatcherTest.java deleted file mode 100644 index e3532ce6be1..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/notification/NotificationDispatcherTest.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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.notification; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.sonar.api.notifications.Notification; -import org.sonar.api.notifications.NotificationChannel; - -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class NotificationDispatcherTest { - - @Mock - private NotificationChannel channel; - - @Mock - private Notification notification; - - @Mock - private NotificationDispatcher.Context context; - - @Before - public void init() { - MockitoAnnotations.initMocks(this); - when(notification.getType()).thenReturn("event1"); - } - - @Test - public void defaultMethods() { - NotificationDispatcher dispatcher = new FakeGenericNotificationDispatcher(); - assertThat(dispatcher.getKey(), is("FakeGenericNotificationDispatcher")); - assertThat(dispatcher.toString(), is("FakeGenericNotificationDispatcher")); - } - - @Test - public void shouldAlwaysRunDispatchForGenericDispatcher() { - NotificationDispatcher dispatcher = new FakeGenericNotificationDispatcher(); - dispatcher.performDispatch(notification, context); - - verify(context, times(1)).addUser("user1", channel); - } - - @Test - public void shouldNotAlwaysRunDispatchForSpecificDispatcher() { - NotificationDispatcher dispatcher = new FakeSpecificNotificationDispatcher(); - - // a "event1" notif is sent - dispatcher.performDispatch(notification, context); - verify(context, never()).addUser("user1", channel); - - // now, a "specific-event" notif is sent - when(notification.getType()).thenReturn("specific-event"); - dispatcher.performDispatch(notification, context); - verify(context, times(1)).addUser("user1", channel); - } - - class FakeGenericNotificationDispatcher extends NotificationDispatcher { - @Override - public void dispatch(Notification notification, Context context) { - context.addUser("user1", channel); - } - } - - class FakeSpecificNotificationDispatcher extends NotificationDispatcher { - - public FakeSpecificNotificationDispatcher() { - super("specific-event"); - } - - @Override - public void dispatch(Notification notification, Context context) { - context.addUser("user1", channel); - } - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/DefaultOrganizationProviderImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/DefaultOrganizationProviderImplTest.java deleted file mode 100644 index 75ef3d76540..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/organization/DefaultOrganizationProviderImplTest.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * 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.organization; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.utils.System2; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.DbTester; -import org.sonar.db.organization.OrganizationDto; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.sonar.db.organization.OrganizationTesting.newOrganizationDto; -import static org.sonar.server.property.InternalProperties.DEFAULT_ORGANIZATION; - -public class DefaultOrganizationProviderImplTest { - private static final OrganizationDto ORGANIZATION_DTO_1 = newOrganizationDto() - .setUuid("uuid1") - .setName("the name of 1") - .setKey("the key 1"); - private static final long DATE_1 = 1_999_888L; - - private System2 system2 = mock(System2.class); - - @Rule - public DbTester dbTester = DbTester.create(system2).setDisableDefaultOrganization(true); - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - private DbClient dbClient = dbTester.getDbClient(); - private DbSession dbSession = dbTester.getSession(); - - private DefaultOrganizationProviderImpl underTest = new DefaultOrganizationProviderImpl(dbClient); - - @Test - public void get_fails_with_ISE_if_default_organization_internal_property_does_not_exist() { - expectISENoDefaultOrganizationUuid(); - - underTest.get(); - } - - @Test - public void get_fails_with_ISE_if_default_organization_internal_property_is_empty() { - dbClient.internalPropertiesDao().saveAsEmpty(dbSession, DEFAULT_ORGANIZATION); - dbSession.commit(); - - expectISENoDefaultOrganizationUuid(); - - underTest.get(); - } - - private void expectISENoDefaultOrganizationUuid() { - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage("No Default organization uuid configured"); - } - - @Test - public void get_fails_with_ISE_if_default_organization_does_not_exist() { - dbClient.internalPropertiesDao().save(dbSession, DEFAULT_ORGANIZATION, "bla"); - dbSession.commit(); - - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage("Default organization with uuid 'bla' does not exist"); - - underTest.get(); - } - - @Test - public void get_returns_DefaultOrganization_populated_from_DB() { - insertOrganization(ORGANIZATION_DTO_1, DATE_1); - dbClient.internalPropertiesDao().save(dbSession, DEFAULT_ORGANIZATION, ORGANIZATION_DTO_1.getUuid()); - dbSession.commit(); - - DefaultOrganization defaultOrganization = underTest.get(); - assertThat(defaultOrganization.getUuid()).isEqualTo(ORGANIZATION_DTO_1.getUuid()); - assertThat(defaultOrganization.getKey()).isEqualTo(ORGANIZATION_DTO_1.getKey()); - assertThat(defaultOrganization.getName()).isEqualTo(ORGANIZATION_DTO_1.getName()); - assertThat(defaultOrganization.getCreatedAt()).isEqualTo(DATE_1); - assertThat(defaultOrganization.getUpdatedAt()).isEqualTo(DATE_1); - } - - @Test - public void get_returns_new_DefaultOrganization_with_each_call_when_cache_is_not_loaded() { - insertOrganization(ORGANIZATION_DTO_1, DATE_1); - dbClient.internalPropertiesDao().save(dbSession, DEFAULT_ORGANIZATION, ORGANIZATION_DTO_1.getUuid()); - dbSession.commit(); - - assertThat(underTest.get()).isNotSameAs(underTest.get()); - } - - @Test - public void unload_does_not_fail_if_load_has_not_been_called() { - underTest.unload(); - } - - @Test - public void load_fails_with_ISE_when_called_twice_without_unload_in_between() { - underTest.load(); - - try { - underTest.load(); - fail("A IllegalStateException should have been raised"); - } catch (IllegalStateException e) { - assertThat(e).hasMessage("load called twice for thread '" + Thread.currentThread().getName() + "' or state wasn't cleared last time it was used"); - } finally { - underTest.unload(); - } - } - - @Test - public void load_and_unload_cache_DefaultOrganization_object_by_thread() throws InterruptedException { - insertOrganization(ORGANIZATION_DTO_1, DATE_1); - dbClient.internalPropertiesDao().save(dbSession, DEFAULT_ORGANIZATION, ORGANIZATION_DTO_1.getUuid()); - dbSession.commit(); - - try { - underTest.load(); - - DefaultOrganization cachedForThread1 = underTest.get(); - assertThat(cachedForThread1).isSameAs(underTest.get()); - - Thread otherThread = new Thread(() -> { - try { - underTest.load(); - - assertThat(underTest.get()) - .isNotSameAs(cachedForThread1) - .isSameAs(underTest.get()); - } finally { - underTest.unload(); - } - }); - otherThread.start(); - otherThread.join(); - } finally { - underTest.unload(); - } - } - - @Test - public void get_returns_new_instance_for_each_call_once_unload_has_been_called() { - insertOrganization(ORGANIZATION_DTO_1, DATE_1); - dbClient.internalPropertiesDao().save(dbSession, DEFAULT_ORGANIZATION, ORGANIZATION_DTO_1.getUuid()); - dbSession.commit(); - - try { - underTest.load(); - DefaultOrganization cached = underTest.get(); - assertThat(cached).isSameAs(underTest.get()); - - underTest.unload(); - assertThat(underTest.get()).isNotSameAs(underTest.get()).isNotSameAs(cached); - } finally { - // fail safe - underTest.unload(); - } - } - - private void insertOrganization(OrganizationDto dto, long createdAt) { - when(system2.now()).thenReturn(createdAt); - dbClient.organizationDao().insert(dbSession, dto, false); - dbSession.commit(); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/DefaultOrganizationTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/DefaultOrganizationTest.java deleted file mode 100644 index 88ce2ad1740..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/organization/DefaultOrganizationTest.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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.organization; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import static org.assertj.core.api.Assertions.assertThat; - -public class DefaultOrganizationTest { - private static final long DATE_2 = 2_000_000L; - private static final long DATE_1 = 1_000_000L; - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - private DefaultOrganization.Builder populatedBuilder = new DefaultOrganization.Builder() - .setUuid("uuid") - .setKey("key") - .setName("name") - .setCreatedAt(DATE_1) - .setUpdatedAt(DATE_2); - - @Test - public void build_fails_if_uuid_is_null() { - populatedBuilder.setUuid(null); - - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("uuid can't be null"); - - populatedBuilder.build(); - } - - @Test - public void build_fails_if_key_is_null() { - populatedBuilder.setKey(null); - - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("key can't be null"); - - populatedBuilder.build(); - } - - @Test - public void build_fails_if_name_is_null() { - populatedBuilder.setName(null); - - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("name can't be null"); - - populatedBuilder.build(); - } - - @Test - public void build_fails_if_createdAt_not_set() { - DefaultOrganization.Builder underTest = new DefaultOrganization.Builder() - .setUuid("uuid") - .setKey("key") - .setName("name") - .setUpdatedAt(DATE_2); - - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("createdAt can't be null"); - - underTest.build(); - } - - @Test - public void build_fails_if_updatedAt_not_set() { - DefaultOrganization.Builder underTest = new DefaultOrganization.Builder() - .setUuid("uuid") - .setKey("key") - .setName("name") - .setCreatedAt(DATE_1); - - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("updatedAt can't be null"); - - underTest.build(); - } - - @Test - public void verify_toString() { - assertThat(populatedBuilder.build().toString()) - .isEqualTo("DefaultOrganization{uuid='uuid', key='key', name='name', createdAt=1000000, updatedAt=2000000}"); - } - - @Test - public void verify_getters() { - DefaultOrganization underTest = populatedBuilder.build(); - - assertThat(underTest.getUuid()).isEqualTo("uuid"); - assertThat(underTest.getKey()).isEqualTo("key"); - assertThat(underTest.getName()).isEqualTo("name"); - assertThat(underTest.getCreatedAt()).isEqualTo(DATE_1); - assertThat(underTest.getUpdatedAt()).isEqualTo(DATE_2); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ServerLoggingTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ServerLoggingTest.java index 5d97692eb8f..c69492a19ee 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/ServerLoggingTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ServerLoggingTest.java @@ -37,7 +37,7 @@ import org.sonar.api.utils.log.LoggerLevel; import org.sonar.db.Database; import org.sonar.process.logging.LogLevelConfig; import org.sonar.process.logging.LogbackHelper; -import org.sonar.server.app.ServerProcessLogging; +import org.sonar.server.log.ServerProcessLogging; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; diff --git a/server/sonar-server/src/test/java/org/sonar/server/property/InternalPropertiesImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/property/InternalPropertiesImplTest.java deleted file mode 100644 index c3c5fd7601a..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/property/InternalPropertiesImplTest.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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.property; - -import java.util.Optional; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.property.InternalPropertiesDao; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class InternalPropertiesImplTest { - private static final String EMPTY_STRING = ""; - public static final String SOME_VALUE = "a value"; - public static final String SOME_KEY = "some key"; - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - private DbClient dbClient = mock(DbClient.class); - private DbSession dbSession = mock(DbSession.class); - private InternalPropertiesDao internalPropertiesDao = mock(InternalPropertiesDao.class); - private InternalPropertiesImpl underTest = new InternalPropertiesImpl(dbClient); - - @Before - public void setUp() throws Exception { - when(dbClient.openSession(false)).thenReturn(dbSession); - when(dbClient.internalPropertiesDao()).thenReturn(internalPropertiesDao); - } - - @Test - public void reads_throws_IAE_if_key_is_null() { - expectKeyNullOrEmptyIAE(); - - underTest.read(null); - } - - @Test - public void reads_throws_IAE_if_key_is_empty() { - expectKeyNullOrEmptyIAE(); - - underTest.read(EMPTY_STRING); - } - - @Test - public void reads_returns_optional_from_DAO() { - Optional value = Optional.of("bablabla"); - - when(internalPropertiesDao.selectByKey(dbSession, SOME_KEY)).thenReturn(value); - - assertThat(underTest.read(SOME_KEY)).isSameAs(value); - } - - @Test - public void write_throws_IAE_if_key_is_null() { - expectKeyNullOrEmptyIAE(); - - underTest.write(null, SOME_VALUE); - } - - @Test - public void writes_throws_IAE_if_key_is_empty() { - expectKeyNullOrEmptyIAE(); - - underTest.write(EMPTY_STRING, SOME_VALUE); - } - - @Test - public void write_calls_dao_saveAsEmpty_when_value_is_null() { - underTest.write(SOME_KEY, null); - - verify(internalPropertiesDao).saveAsEmpty(dbSession, SOME_KEY); - verify(dbSession).commit(); - } - - @Test - public void write_calls_dao_saveAsEmpty_when_value_is_empty() { - underTest.write(SOME_KEY, EMPTY_STRING); - - verify(internalPropertiesDao).saveAsEmpty(dbSession, SOME_KEY); - verify(dbSession).commit(); - } - - @Test - public void write_calls_dao_save_when_value_is_neither_null_nor_empty() { - underTest.write(SOME_KEY, SOME_VALUE); - - verify(internalPropertiesDao).save(dbSession, SOME_KEY, SOME_VALUE); - verify(dbSession).commit(); - } - - private void expectKeyNullOrEmptyIAE() { - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("key can't be null nor empty"); - } -}