]> source.dussan.org Git - sonarqube.git/commitdiff
move some classes in preparation of creation of sonar-ce-common
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 22 Jun 2018 14:08:21 +0000 (16:08 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 29 Jun 2018 07:10:14 +0000 (09:10 +0200)
96 files changed:
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/taskprocessor/ReportTaskProcessor.java
server/sonar-ce-task/src/main/java/org/sonar/ce/task/taskprocessor/CeTaskProcessor.java [new file with mode: 0644]
server/sonar-ce/src/main/java/org/sonar/ce/app/CeServer.java
server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
server/sonar-ce/src/main/java/org/sonar/ce/logging/CeProcessLogging.java [new file with mode: 0644]
server/sonar-ce/src/main/java/org/sonar/ce/notification/package-info.java
server/sonar-ce/src/main/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepository.java
server/sonar-ce/src/main/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepositoryImpl.java
server/sonar-ce/src/main/java/org/sonar/ce/taskprocessor/CeWorkerImpl.java
server/sonar-ce/src/test/java/org/sonar/ce/logging/CeProcessLoggingTest.java [new file with mode: 0644]
server/sonar-ce/src/test/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepositoryImplTest.java
server/sonar-ce/src/test/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepositoryRule.java
server/sonar-server-common/src/main/java/org/sonar/server/log/ServerProcessLogging.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/log/package-info.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/notification/DefaultNotificationManager.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/notification/NotificationDispatcher.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/notification/NotificationDispatcherMetadata.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/notification/NotificationManager.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/notification/package-info.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/organization/DefaultOrganization.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/organization/DefaultOrganizationCache.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/organization/DefaultOrganizationProvider.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/organization/DefaultOrganizationProviderImpl.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/organization/package-info.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/property/InternalProperties.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/property/InternalPropertiesImpl.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/property/MapInternalProperties.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/property/package-info.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/notification/DefaultNotificationManagerTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/notification/NotificationDispatcherMetadataTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/notification/NotificationDispatcherTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/organization/DefaultOrganizationProviderImplTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/organization/DefaultOrganizationTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/property/InternalPropertiesImplTest.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/ce/CeModule.java [deleted file]
server/sonar-server/src/main/java/org/sonar/ce/log/CeProcessLogging.java [deleted file]
server/sonar-server/src/main/java/org/sonar/ce/log/package-info.java [deleted file]
server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotification.java [deleted file]
server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationDispatcher.java [deleted file]
server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationEmailTemplate.java [deleted file]
server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationModule.java [deleted file]
server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationSerializer.java [deleted file]
server/sonar-server/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationSerializerImpl.java [deleted file]
server/sonar-server/src/main/java/org/sonar/ce/notification/package-info.java [deleted file]
server/sonar-server/src/main/java/org/sonar/ce/package-info.java [deleted file]
server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/ReportTaskProcessorDeclaration.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotification.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationDispatcher.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationEmailTemplate.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationModule.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationSerializer.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationSerializerImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/ce/task/projectanalysis/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/ce/taskprocessor/CeTaskProcessor.java [deleted file]
server/sonar-server/src/main/java/org/sonar/ce/taskprocessor/ReportTaskProcessorDeclaration.java [deleted file]
server/sonar-server/src/main/java/org/sonar/ce/taskprocessor/package-info.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/app/ServerProcessLogging.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/app/WebServerProcessLogging.java
server/sonar-server/src/main/java/org/sonar/server/ce/CeModule.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/ce/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityAction.java
server/sonar-server/src/main/java/org/sonar/server/ce/ws/TaskTypesAction.java
server/sonar-server/src/main/java/org/sonar/server/notification/NotificationManager.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganization.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationCache.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationProvider.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationProviderImpl.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/DatabaseServerCompatibility.java
server/sonar-server/src/main/java/org/sonar/server/platform/ServerLogging.java
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-server/src/main/java/org/sonar/server/property/InternalProperties.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/property/InternalPropertiesImpl.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/property/MapInternalProperties.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/property/package-info.java [deleted file]
server/sonar-server/src/test/java/org/sonar/ce/log/CeProcessLoggingTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationDispatcherTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationEmailTemplateTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationModuleTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationSerializerImplTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/ReportTaskProcessorDeclarationTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationDispatcherTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationEmailTemplateTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationModuleTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationSerializerImplTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/ce/taskprocessor/ReportTaskProcessorDeclarationTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java
server/sonar-server/src/test/java/org/sonar/server/ce/ws/TaskTypesActionTest.java
server/sonar-server/src/test/java/org/sonar/server/notification/DefaultNotificationManagerTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/notification/NotificationDispatcherMetadataTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/notification/NotificationDispatcherTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/organization/DefaultOrganizationProviderImplTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/organization/DefaultOrganizationTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/platform/ServerLoggingTest.java
server/sonar-server/src/test/java/org/sonar/server/property/InternalPropertiesImplTest.java [deleted file]

index 512a5d17e00e8404334ce4c2b184412ba355f054..9bc1c36570eb58f8621a43b6d61100826af137b2 100644 (file)
@@ -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 (file)
index 0000000..8814013
--- /dev/null
@@ -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.
+   * <p>
+   * 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.
+   * </p>
+   * <p>
+   * If an empty {@link Set} is returned, the {@link CeTaskProcessor} will be ignored.
+   * </p>
+   */
+  Set<String> 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).
+   * <p>
+   * The specified is guaranteed to be non {@code null} and its {@link CeTask#getType()} to be one of the values
+   * of {@link #getHandledCeTaskTypes()}.
+   * </p>
+   *
+   * @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);
+}
index 41f9c883f0900bb0f41430231709ecf9576d6c05..63b0b4b2b07a4f3afedfcd9eb88f8243ac60a7b0 100644 (file)
@@ -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;
index 1812686e410e7ec9f3ba98fc7f041ae57e5c70ca..275d844fd6891cca81b3158fd0a1b62fb7348089 100644 (file)
@@ -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 (file)
index 0000000..0429c4b
--- /dev/null
@@ -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
+  }
+}
index 0e90a86093637d2973a31c3960cfc7ce6a9bbdc1..a3ca1a7fd1e54fbf1e3d347b6ca0c70ecdb4a396 100644 (file)
@@ -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;
index 9f9f25112dbe3d2745ef6a3ab1bf095218942493..2a4e9b5acc8ba9250643f618a58ffbaa5101868c 100644 (file)
@@ -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 {
 
index ba0c97d34e94e4780ac872671930460bd183a3a3..1d5b936bf35e615092cf2ed2ce2e11e4957cf0b1 100644 (file)
@@ -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;
index 06cc90b2546aa82e9ea37ec919437819aba4c9dc..831d040691343b790e66bb82dd6851a282fb4b83 100644 (file)
@@ -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 (file)
index 0000000..d151152
--- /dev/null
@@ -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<ILoggingEvent> consoleAppender = (ConsoleAppender<ILoggingEvent>) 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<ILoggingEvent> 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);
+  }
+}
index 04cdeac683436f44d9124b7ee87e78cfafc7d9e2..4108af81183055c6c4554269c471fad1c674be1d 100644 (file)
@@ -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;
 
index 451ebf1fefd7d864f681aa0d66340429dabd345c..036ccd52ab9a227646b120b1cfa610b2ee6a3948 100644 (file)
@@ -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 (file)
index 0000000..ad6711d
--- /dev/null
@@ -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<String> 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<String> 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<ILoggingEvent> 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 (file)
index 0000000..53c0093
--- /dev/null
@@ -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 (file)
index 0000000..932c847
--- /dev/null
@@ -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<NotificationQueueDto> notificationDtos = dbClient.notificationQueueDao().selectOldest(batchSize);
+    if (notificationDtos.isEmpty()) {
+      return null;
+    }
+    dbClient.notificationQueueDao().delete(notificationDtos);
+
+    return convertToNotification(notificationDtos);
+  }
+
+  private Notification convertToNotification(List<NotificationQueueDto> 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<String, NotificationChannel> findSubscribedRecipientsForDispatcher(NotificationDispatcher dispatcher,
+    String projectKey, SubscriberPermissionsOnProject subscriberPermissionsOnProject) {
+    requireNonNull(projectKey, "projectKey is mandatory");
+    String dispatcherKey = dispatcher.getKey();
+
+    Set<SubscriberAndChannel> subscriberAndChannels = Arrays.stream(notificationChannels)
+      .flatMap(notificationChannel -> toSubscriberAndChannels(dispatcherKey, projectKey, notificationChannel))
+      .collect(Collectors.toSet());
+
+    if (subscriberAndChannels.isEmpty()) {
+      return ImmutableMultimap.of();
+    }
+
+    ImmutableSetMultimap.Builder<String, NotificationChannel> builder = ImmutableSetMultimap.builder();
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      Set<String> 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<SubscriberAndChannel> toSubscriberAndChannels(String dispatcherKey, String projectKey, NotificationChannel notificationChannel) {
+    Set<Subscriber> usersForNotification = dbClient.propertiesDao().findUsersForNotification(dispatcherKey, notificationChannel.getKey(), projectKey);
+    return usersForNotification
+      .stream()
+      .map(login -> new SubscriberAndChannel(login, notificationChannel));
+  }
+
+  private Set<String> keepAuthorizedLogins(DbSession dbSession, String projectKey, Set<SubscriberAndChannel> 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<String> keepAuthorizedLogins(DbSession dbSession, String projectKey, Set<SubscriberAndChannel> subscriberAndChannels,
+    @Nullable Boolean global, String permission) {
+    Set<String> 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<NotificationChannel> 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 (file)
index 0000000..2c23637
--- /dev/null
@@ -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;
+
+/**
+ * <p>
+ * Plugins should extend this class to provide logic to determine which users are interested in receiving notifications,
+ * along with which delivery channels they selected.
+ * </p>
+ * For example:
+ * <ul>
+ * <li>notify me by email when someone comments an issue reported by me</li>
+ * <li>notify me by twitter when someone comments an issue assigned to me</li>
+ * <li>notify me by Jabber when someone mentions me in an issue comment</li>
+ * <li>send me by SMS when there are system notifications (like password reset, account creation, ...)</li>
+ * </ul> 
+  */
+@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. 
+   * <p/>
+   * 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.
+   * <p/>
+   * The related label in l10n bundles is 'notification.dispatcher.<key>', for example 'notification.dispatcher.NewFalsePositive'.
+   */
+  public String getKey() {
+    return getClass().getSimpleName();
+  }
+
+  /**
+   * @since 5.1
+   */
+  public String getType() {
+    return notificationType;
+  }
+
+  /**
+   * <p>
+   * Performs the dispatch.
+   * </p>
+   */
+  public final void performDispatch(Notification notification, Context context) {
+    if (StringUtils.equals(notification.getType(), notificationType) || StringUtils.equals("", notificationType)) {
+      dispatch(notification, context);
+    }
+  }
+
+  /**
+   * <p>
+   * Implements the logic that defines which users will receive the notification.
+   * </p>
+   * 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 (file)
index 0000000..aceed9e
--- /dev/null
@@ -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.
+ * <p>
+ * Instances of these classes must be declared by {@link org.sonar.api.Plugin}.
+ * </p>
+ */
+@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<String, String> properties;
+
+  private NotificationDispatcherMetadata(String dispatcherKey) {
+    this.dispatcherKey = dispatcherKey;
+    this.properties = Maps.newHashMap();
+  }
+
+  /**
+   * Creates a new metadata instance for the given dispatcher.
+   * <p/>
+   * 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 (file)
index 0000000..e7a4ab2
--- /dev/null
@@ -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.
+ * <p>
+ * 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.
+ * </p>
+ */
+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);
+
+  /**
+   * <p>
+   * Returns the list of users who subscribed to the given dispatcher, along with the notification channels (email, twitter, ...) that they choose
+   * for this dispatcher.
+   * </p>
+   * <p>
+   * The resource ID can be null in case of notifications that have nothing to do with a specific project (like system notifications).
+   * </p>
+   *
+   * @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<String, NotificationChannel> 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 (file)
index 0000000..f6a6652
--- /dev/null
@@ -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 (file)
index 0000000..24cce29
--- /dev/null
@@ -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 (file)
index 0000000..76168e9
--- /dev/null
@@ -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 (file)
index 0000000..4cd76b2
--- /dev/null
@@ -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 (file)
index 0000000..72ef09b
--- /dev/null
@@ -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> 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<String> uuid = dbClient.internalPropertiesDao().selectByKey(dbSession, InternalProperties.DEFAULT_ORGANIZATION);
+      checkState(uuid.isPresent() && !uuid.get().isEmpty(), "No Default organization uuid configured");
+      Optional<OrganizationDto> 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<DefaultOrganization> 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 (file)
index 0000000..6af0dea
--- /dev/null
@@ -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 (file)
index 0000000..15260ea
--- /dev/null
@@ -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<String> read(String propertyKey);
+
+  /**
+   * Write the value of the specified property.
+   * <p>
+   *   {@code null} and empty string are valid values which will persist the specified property as empty.
+   * </p>
+   *
+   * @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 (file)
index 0000000..55cc690
--- /dev/null
@@ -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<String> 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 (file)
index 0000000..ff799f4
--- /dev/null
@@ -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<String, String> values = new HashMap<>(1);
+
+  @Override
+  public Optional<String> 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 (file)
index 0000000..d5d7223
--- /dev/null
@@ -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 (file)
index 0000000..dcd3e19
--- /dev/null
@@ -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<NotificationQueueDto> 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<NotificationQueueDto> 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<String, NotificationChannel> multiMap = underTest.findSubscribedRecipientsForDispatcher(dispatcher, projectUuid,
+      SubscriberPermissionsOnProject.ALL_MUST_HAVE_ROLE_USER);
+    assertThat(multiMap.entries()).hasSize(3);
+
+    Map<String, Collection<NotificationChannel>> 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<String, NotificationChannel> multiMap = underTest.findSubscribedRecipientsForDispatcher(dispatcher, projectUuid,
+      new SubscriberPermissionsOnProject(globalPermission, projectPermission));
+    assertThat(multiMap.entries()).hasSize(3);
+
+    Map<String, Collection<NotificationChannel>> 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<String, NotificationChannel> multiMap = underTest.findSubscribedRecipientsForDispatcher(dispatcher, projectUuid,
+        new SubscriberPermissionsOnProject(globalPermission, projectPermission));
+    assertThat(multiMap.entries()).hasSize(2);
+
+    Map<String, Collection<NotificationChannel>> 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<String, NotificationChannel> multiMap = underTest.findSubscribedRecipientsForDispatcher(dispatcher, projectUuid,
+        new SubscriberPermissionsOnProject(globalPermission, projectPermission));
+    assertThat(multiMap.entries()).hasSize(2);
+
+    Map<String, Collection<NotificationChannel>> 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 (file)
index 0000000..eea8d05
--- /dev/null
@@ -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 (file)
index 0000000..e3532ce
--- /dev/null
@@ -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 (file)
index 0000000..75ef3d7
--- /dev/null
@@ -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 (file)
index 0000000..88ce2ad
--- /dev/null
@@ -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 (file)
index 0000000..c3c5fd7
--- /dev/null
@@ -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<String> 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 (file)
index 74e5b22..0000000
+++ /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 (file)
index 2788981..0000000
+++ /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 (file)
index 7c5ada7..0000000
+++ /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 (file)
index a2f446e..0000000
+++ /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 (file)
index c7b9565..0000000
+++ /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<String, NotificationChannel> subscribedRecipients = manager
-      .findSubscribedRecipientsForDispatcher(this, projectKey, REQUIRED_SUBSCRIBER_PERMISSIONS);
-
-    for (Map.Entry<String, Collection<NotificationChannel>> 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 (file)
index 0147538..0000000
+++ /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 (file)
index 7e84b21..0000000
+++ /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 (file)
index 50079eb..0000000
+++ /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 (file)
index 0da6c35..0000000
+++ /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 (file)
index 0e90a86..0000000
+++ /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 (file)
index 530ad8c..0000000
+++ /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 (file)
index 0000000..c2a19e5
--- /dev/null
@@ -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<String> HANDLED_TYPES = Collections.singleton(CeTaskTypes.REPORT);
+
+  @Override
+  public Set<String> 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 (file)
index 0000000..a2f446e
--- /dev/null
@@ -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 (file)
index 0000000..c7b9565
--- /dev/null
@@ -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<String, NotificationChannel> subscribedRecipients = manager
+      .findSubscribedRecipientsForDispatcher(this, projectKey, REQUIRED_SUBSCRIBER_PERMISSIONS);
+
+    for (Map.Entry<String, Collection<NotificationChannel>> 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 (file)
index 0000000..0147538
--- /dev/null
@@ -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 (file)
index 0000000..7e84b21
--- /dev/null
@@ -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 (file)
index 0000000..50079eb
--- /dev/null
@@ -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 (file)
index 0000000..0da6c35
--- /dev/null
@@ -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 (file)
index 0000000..908ed69
--- /dev/null
@@ -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 (file)
index 441c227..0000000
+++ /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.
-   * <p>
-   * 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.
-   * </p>
-   * <p>
-   * If an empty {@link Set} is returned, the {@link CeTaskProcessor} will be ignored.
-   * </p>
-   */
-  Set<String> 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).
-   * <p>
-   * The specified is guaranteed to be non {@code null} and its {@link CeTask#getType()} to be one of the values
-   * of {@link #getHandledCeTaskTypes()}.
-   * </p>
-   *
-   * @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 (file)
index 9c2c6f9..0000000
+++ /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<String> HANDLED_TYPES = Collections.singleton(CeTaskTypes.REPORT);
-
-  @Override
-  public Set<String> 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 (file)
index 86727e0..0000000
+++ /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 (file)
index cf7fe6b..0000000
+++ /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<String> 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<String> 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<ILoggingEvent> consoleAppender = helper.newConsoleAppender(context, "CONSOLE", logPattern);
-
-    for (String loggerName : loggerNames) {
-      Logger consoleLogger = context.getLogger(loggerName);
-      consoleLogger.setAdditive(false);
-      consoleLogger.addAppender(consoleAppender);
-    }
-  }
-
-}
index b79929db34f5c84f93008cbb0a9febe123444f8b..2d6f016845529d9e59a4d38e0b9e0178b160d511 100644 (file)
@@ -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 (file)
index 0000000..00442c1
--- /dev/null
@@ -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 (file)
index 0000000..259e9b9
--- /dev/null
@@ -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;
index 67724db7f62ec4c26954d3092bd698707e0be1ce..164ca9fd7c5488b4003cf54877b806ad05877658 100644 (file)
@@ -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;
index c401e6dbdacb1f3874e771b5e2d314b092490c20..15dd8ad7ef3e7e98fa73dd93b725ca916e291731 100644 (file)
@@ -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 (file)
index e7a4ab2..0000000
+++ /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.
- * <p>
- * 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.
- * </p>
- */
-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);
-
-  /**
-   * <p>
-   * Returns the list of users who subscribed to the given dispatcher, along with the notification channels (email, twitter, ...) that they choose
-   * for this dispatcher.
-   * </p>
-   * <p>
-   * The resource ID can be null in case of notifications that have nothing to do with a specific project (like system notifications).
-   * </p>
-   *
-   * @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<String, NotificationChannel> 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 (file)
index 24cce29..0000000
+++ /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 (file)
index 76168e9..0000000
+++ /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 (file)
index 4cd76b2..0000000
+++ /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 (file)
index 72ef09b..0000000
+++ /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> 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<String> uuid = dbClient.internalPropertiesDao().selectByKey(dbSession, InternalProperties.DEFAULT_ORGANIZATION);
-      checkState(uuid.isPresent() && !uuid.get().isEmpty(), "No Default organization uuid configured");
-      Optional<OrganizationDto> 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<DefaultOrganization> supplier) {
-      if (defaultOrganization == null) {
-        defaultOrganization = supplier.get();
-      }
-      return defaultOrganization;
-    }
-
-  }
-}
index d252a90156a99f26c4bab16716808c32eb87f11c..8f132fd72eeacd982dba52c2e9b73a4669c7d850 100644 (file)
@@ -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 {
 
index 1473be4594eae881e4701b61a4541a0685d88332..c1e4b87780dbdcb3a86a37a00a2e065f9d8a16f2 100644 (file)
@@ -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;
index 3b9dc86758cb4b73c87f6b723be3c85de226faef..0b1e7bcfbab042a1b8da496ba46ce19e655acc66 100644 (file)
@@ -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 (file)
index 15260ea..0000000
+++ /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<String> read(String propertyKey);
-
-  /**
-   * Write the value of the specified property.
-   * <p>
-   *   {@code null} and empty string are valid values which will persist the specified property as empty.
-   * </p>
-   *
-   * @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 (file)
index 55cc690..0000000
+++ /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<String> 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 (file)
index ff799f4..0000000
+++ /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<String, String> values = new HashMap<>(1);
-
-  @Override
-  public Optional<String> 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 (file)
index d5d7223..0000000
+++ /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 (file)
index 27dcd88..0000000
+++ /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<ILoggingEvent> consoleAppender = (ConsoleAppender<ILoggingEvent>) 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<ILoggingEvent> 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 (file)
index 92b1c0b..0000000
+++ /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<String, NotificationChannel> 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<String, NotificationChannel> 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 (file)
index daf31c4..0000000
+++ /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 (file)
index aa1db53..0000000
+++ /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 (file)
index 81bfbf5..0000000
+++ /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 (file)
index 0652c34..0000000
+++ /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 (file)
index 0000000..971c3e8
--- /dev/null
@@ -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 (file)
index 0000000..92b1c0b
--- /dev/null
@@ -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<String, NotificationChannel> 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<String, NotificationChannel> 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 (file)
index 0000000..daf31c4
--- /dev/null
@@ -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 (file)
index 0000000..aa1db53
--- /dev/null
@@ -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 (file)
index 0000000..81bfbf5
--- /dev/null
@@ -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 (file)
index 0000000..0652c34
--- /dev/null
@@ -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 (file)
index fd96765..0000000
+++ /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));
-  }
-}
index a55739065f3930d8e0ff49b5e32309f253d8d155..6a584456b73a26c6e81bf3adb1b7affe1ab070b0 100644 (file)
@@ -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;
index 18aec801e5240f8f12b536d5b5bc237732501b39..94ce532bb9dbefc678e775a5bc5cb2773769f79e 100644 (file)
@@ -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 (file)
index dcd3e19..0000000
+++ /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<NotificationQueueDto> 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<NotificationQueueDto> 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<String, NotificationChannel> multiMap = underTest.findSubscribedRecipientsForDispatcher(dispatcher, projectUuid,
-      SubscriberPermissionsOnProject.ALL_MUST_HAVE_ROLE_USER);
-    assertThat(multiMap.entries()).hasSize(3);
-
-    Map<String, Collection<NotificationChannel>> 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<String, NotificationChannel> multiMap = underTest.findSubscribedRecipientsForDispatcher(dispatcher, projectUuid,
-      new SubscriberPermissionsOnProject(globalPermission, projectPermission));
-    assertThat(multiMap.entries()).hasSize(3);
-
-    Map<String, Collection<NotificationChannel>> 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<String, NotificationChannel> multiMap = underTest.findSubscribedRecipientsForDispatcher(dispatcher, projectUuid,
-        new SubscriberPermissionsOnProject(globalPermission, projectPermission));
-    assertThat(multiMap.entries()).hasSize(2);
-
-    Map<String, Collection<NotificationChannel>> 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<String, NotificationChannel> multiMap = underTest.findSubscribedRecipientsForDispatcher(dispatcher, projectUuid,
-        new SubscriberPermissionsOnProject(globalPermission, projectPermission));
-    assertThat(multiMap.entries()).hasSize(2);
-
-    Map<String, Collection<NotificationChannel>> 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 (file)
index eea8d05..0000000
+++ /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 (file)
index e3532ce..0000000
+++ /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 (file)
index 75ef3d7..0000000
+++ /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 (file)
index 88ce2ad..0000000
+++ /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);
-  }
-}
index 5d97692eb8f1f31027a79eedeff0acea83115289..c69492a19eece453dc827d99cd5cd3ea4884b5b7 100644 (file)
@@ -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 (file)
index c3c5fd7..0000000
+++ /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<String> 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");
-  }
-}