]> source.dussan.org Git - sonarqube.git/commitdiff
move some classes (including webhooks) to server-common
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 22 Jun 2018 15:01:46 +0000 (17:01 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 29 Jun 2018 07:10:15 +0000 (09:10 +0200)
110 files changed:
server/sonar-server-common/build.gradle
server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecution.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionExecutorService.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionExecutorServiceImpl.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionImpl.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionMBean.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionMBeanImpl.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionModule.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionMonitoring.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/async/package-info.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/project/Project.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/project/package-info.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/Condition.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGate.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/package-info.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/util/OkHttpClientProvider.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/webhook/Analysis.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/webhook/Branch.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/webhook/CeTask.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/webhook/ProjectAnalysis.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebHooks.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebHooksImpl.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/webhook/Webhook.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookCaller.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookCallerImpl.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookDelivery.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookDeliveryStorage.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookModule.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookPayload.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookPayloadFactory.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookPayloadFactoryImpl.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/webhook/package-info.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/async/AsyncExecutionExecutorServiceImplTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/async/AsyncExecutionImplTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/async/AsyncExecutionMBeanImplTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/project/ProjectTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/ConditionTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/util/OkHttpClientProviderTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/webhook/AnalysisTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/webhook/AsynchronousWebHooksImplTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/webhook/BranchTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/webhook/CeTaskTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/webhook/ProjectAnalysisTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/webhook/SynchronousWebHooksImplTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/webhook/TestWebhookCaller.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookCallerImplTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookDeliveryStorageTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookDeliveryTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookModuleTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookPayloadFactoryImplTest.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookTest.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecution.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionExecutorService.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionExecutorServiceImpl.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionImpl.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionMBean.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionMBeanImpl.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionModule.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionMonitoring.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/async/package-info.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-server/src/main/java/org/sonar/server/project/Project.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/qualitygate/Condition.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGate.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/util/OkHttpClientProvider.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/webhook/Analysis.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/webhook/Branch.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/webhook/CeTask.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/webhook/ProjectAnalysis.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/webhook/WebHooks.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/webhook/WebHooksImpl.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/webhook/Webhook.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookCaller.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookCallerImpl.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookDelivery.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookDeliveryStorage.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookModule.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookPayload.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookPayloadFactory.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookPayloadFactoryImpl.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/webhook/package-info.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionExecutorServiceImplTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionImplTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionMBeanImplTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/project/ProjectTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/qualitygate/ConditionTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/util/OkHttpClientProviderTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/webhook/AnalysisTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/webhook/AsynchronousWebHooksImplTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/webhook/BranchTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/webhook/CeTaskTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/webhook/ProjectAnalysisTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/webhook/SynchronousWebHooksImplTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/webhook/TestWebhookCaller.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookCallerImplTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookDeliveryStorageTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookDeliveryTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookModuleTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookPayloadFactoryImplTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookTest.java [deleted file]

index 6e189c3febf5802408a34119c291c1c58d25231c..3b2443c6f2ad0ed37470a3f43c7a657a92fe8d50 100644 (file)
@@ -15,8 +15,10 @@ dependencies {
 
   compile 'com.google.guava:guava'
   compile 'org.slf4j:slf4j-api'
-  compile project(':sonar-core')
+  compile 'com.squareup.okhttp3:okhttp'
   compile project(':server:sonar-db-dao')
+  compile project(':sonar-core')
+  compile project(':sonar-ws')
 
   compileOnly project(path: ':sonar-plugin-api')
   compileOnly project(path: ':server:sonar-process')
@@ -25,6 +27,7 @@ dependencies {
 
   testCompile 'com.google.code.findbugs:jsr305'
   testCompile 'com.h2database:h2'
+  testCompile 'com.squareup.okhttp3:mockwebserver'
   testCompile 'com.tngtech.java:junit-dataprovider'
   testCompile 'junit:junit'
   testCompile 'org.assertj:assertj-core'
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecution.java b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecution.java
new file mode 100644 (file)
index 0000000..053ab2c
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * 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.async;
+
+public interface AsyncExecution {
+  /**
+   * Add the specified {@link Runnable} in queue for asynchronous processing.
+   *
+   * This method returns instantly and {@code r} is executed in another thread.
+   *
+   * @throws NullPointerException if r is {@code null}
+   */
+  void addToQueue(Runnable r);
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionExecutorService.java b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionExecutorService.java
new file mode 100644 (file)
index 0000000..1458e9a
--- /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.
+ */
+package org.sonar.server.async;
+
+public interface AsyncExecutionExecutorService {
+  void addToQueue(Runnable r);
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionExecutorServiceImpl.java b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionExecutorServiceImpl.java
new file mode 100644 (file)
index 0000000..ce5a82d
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * 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.async;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.server.util.AbstractStoppableExecutorService;
+
+import static java.util.concurrent.TimeUnit.MINUTES;
+
+public class AsyncExecutionExecutorServiceImpl
+  extends AbstractStoppableExecutorService<ThreadPoolExecutor>
+  implements AsyncExecutionExecutorService, AsyncExecutionMonitoring {
+  private static final Logger LOG = Loggers.get(AsyncExecutionExecutorServiceImpl.class);
+
+  private static final int MAX_THREAD_COUNT = 10;
+  private static final int UNLIMITED_QUEUE = Integer.MAX_VALUE;
+  private static final long KEEP_ALIVE_TIME_IN_MINUTES = 5L;
+
+  public AsyncExecutionExecutorServiceImpl() {
+    super(createDelegate());
+  }
+
+  private static ThreadPoolExecutor createDelegate() {
+    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
+      MAX_THREAD_COUNT, MAX_THREAD_COUNT,
+      KEEP_ALIVE_TIME_IN_MINUTES, MINUTES,
+      new LinkedBlockingQueue<>(UNLIMITED_QUEUE),
+      new ThreadFactoryBuilder()
+        .setDaemon(false)
+        .setNameFormat("SQ_async-%d")
+        .setUncaughtExceptionHandler(((t, e) -> LOG.error("Thread " + t + " failed unexpectedly", e)))
+        .build());
+    threadPoolExecutor.allowCoreThreadTimeOut(true);
+    return threadPoolExecutor;
+  }
+
+  @Override
+  public void addToQueue(Runnable r) {
+    this.submit(r);
+  }
+
+  @Override
+  public int getQueueSize() {
+    return delegate.getQueue().size();
+  }
+
+  @Override
+  public int getWorkerCount() {
+    return delegate.getPoolSize();
+  }
+
+  @Override
+  public int getLargestWorkerCount() {
+    return delegate.getLargestPoolSize();
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionImpl.java b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionImpl.java
new file mode 100644 (file)
index 0000000..7940fc3
--- /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.async;
+
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import static java.util.Objects.requireNonNull;
+
+public class AsyncExecutionImpl implements AsyncExecution {
+  private static final Logger LOG = Loggers.get(AsyncExecutionImpl.class);
+  private final AsyncExecutionExecutorService executorService;
+
+  public AsyncExecutionImpl(AsyncExecutionExecutorService executorService) {
+    this.executorService = executorService;
+  }
+
+  @Override
+  public void addToQueue(Runnable r) {
+    requireNonNull(r);
+    executorService.addToQueue(() -> {
+      try {
+        r.run();
+      } catch (Exception e) {
+        LOG.error("Asynchronous task failed", e);
+      }
+    });
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionMBean.java b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionMBean.java
new file mode 100644 (file)
index 0000000..d908702
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * 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.async;
+
+public interface AsyncExecutionMBean {
+
+  String OBJECT_NAME = "SonarQube:name=AsyncExecution";
+
+  long getQueueSize();
+
+  long getWorkerCount();
+
+  long getLargestWorkerCount();
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionMBeanImpl.java b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionMBeanImpl.java
new file mode 100644 (file)
index 0000000..f155a95
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.async;
+
+import org.picocontainer.Startable;
+import org.sonar.process.Jmx;
+
+public class AsyncExecutionMBeanImpl implements AsyncExecutionMBean, Startable {
+
+  private final AsyncExecutionMonitoring asyncExecutionMonitoring;
+
+  public AsyncExecutionMBeanImpl(AsyncExecutionMonitoring asyncExecutionMonitoring) {
+    this.asyncExecutionMonitoring = asyncExecutionMonitoring;
+  }
+
+  @Override
+  public void start() {
+    Jmx.register(OBJECT_NAME, this);
+  }
+
+  @Override
+  public void stop() {
+    Jmx.unregister(OBJECT_NAME);
+  }
+
+  @Override
+  public long getQueueSize() {
+    return asyncExecutionMonitoring.getQueueSize();
+  }
+
+  @Override
+  public long getWorkerCount() {
+    return asyncExecutionMonitoring.getWorkerCount();
+  }
+
+  @Override
+  public long getLargestWorkerCount() {
+    return asyncExecutionMonitoring.getLargestWorkerCount();
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionModule.java b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionModule.java
new file mode 100644 (file)
index 0000000..d1ef086
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.async;
+
+import org.sonar.core.platform.Module;
+
+public class AsyncExecutionModule extends Module {
+  @Override
+  protected void configureModule() {
+    add(
+      AsyncExecutionMBeanImpl.class,
+      AsyncExecutionExecutorServiceImpl.class,
+      AsyncExecutionImpl.class);
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionMonitoring.java b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionMonitoring.java
new file mode 100644 (file)
index 0000000..ecc6e67
--- /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.async;
+
+public interface AsyncExecutionMonitoring {
+  int getQueueSize();
+
+  int getWorkerCount();
+
+  int getLargestWorkerCount();
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/async/package-info.java b/server/sonar-server-common/src/main/java/org/sonar/server/async/package-info.java
new file mode 100644 (file)
index 0000000..a940f44
--- /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.async;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/project/Project.java b/server/sonar-server-common/src/main/java/org/sonar/server/project/Project.java
new file mode 100644 (file)
index 0000000..af22037
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.project;
+
+import java.util.Objects;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.db.component.ComponentDto;
+
+@Immutable
+public class Project {
+
+  private final String uuid;
+  private final String key;
+  private final String name;
+  private final String description;
+
+  public Project(String uuid, String key, String name) {
+    this(uuid, key, name, null);
+  }
+
+  public Project(String uuid, String key, String name, @Nullable String description) {
+    this.uuid = uuid;
+    this.key = key;
+    this.name = name;
+    this.description = description;
+  }
+
+  public static Project from(ComponentDto project) {
+    return new Project(project.uuid(), project.getDbKey(), project.name(), project.description());
+  }
+
+  /**
+   * Always links to a row that exists in database.
+   */
+  public String getUuid() {
+    return uuid;
+  }
+
+  /**
+   * Always links to a row that exists in database.
+   */
+  public String getKey() {
+    return key;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    Project project = (Project) o;
+    return uuid.equals(project.uuid)
+      && key.equals(project.key)
+      && name.equals(project.name);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(uuid, key, name);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("Project{");
+    sb.append("uuid='").append(uuid).append('\'');
+    sb.append(", key='").append(key).append('\'');
+    sb.append(", name='").append(name).append('\'');
+    sb.append(", description=").append(toString(this.description));
+    sb.append('}');
+    return sb.toString();
+  }
+
+  private static String toString(@Nullable String s) {
+    if (s == null) {
+      return null;
+    }
+    return '\'' + s + '\'';
+  }
+
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/project/package-info.java b/server/sonar-server-common/src/main/java/org/sonar/server/project/package-info.java
new file mode 100644 (file)
index 0000000..7771f38
--- /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.project;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/Condition.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/Condition.java
new file mode 100644 (file)
index 0000000..3319b92
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * 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.qualitygate;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Stream;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.db.qualitygate.QualityGateConditionDto;
+
+import static com.google.common.base.Strings.emptyToNull;
+import static java.util.Objects.requireNonNull;
+
+@Immutable
+public class Condition {
+
+  private final String metricKey;
+  private final Operator operator;
+  @CheckForNull
+  private final String warningThreshold;
+  @CheckForNull
+  private final String errorThreshold;
+  private final boolean onLeakPeriod;
+
+  public Condition(String metricKey, Operator operator,
+    @Nullable String errorThreshold, @Nullable String warningThreshold,
+    boolean onLeakPeriod) {
+    this.metricKey = requireNonNull(metricKey, "metricKey can't be null");
+    this.operator = requireNonNull(operator, "operator can't be null");
+    this.onLeakPeriod = onLeakPeriod;
+    this.errorThreshold = emptyToNull(errorThreshold);
+    this.warningThreshold = emptyToNull(warningThreshold);
+  }
+
+  public String getMetricKey() {
+    return metricKey;
+  }
+
+  public boolean isOnLeakPeriod() {
+    return onLeakPeriod;
+  }
+
+  public Operator getOperator() {
+    return operator;
+  }
+
+  public Optional<String> getWarningThreshold() {
+    return Optional.ofNullable(warningThreshold);
+  }
+
+  public Optional<String> getErrorThreshold() {
+    return Optional.ofNullable(errorThreshold);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    Condition condition = (Condition) o;
+    return onLeakPeriod == condition.onLeakPeriod &&
+      Objects.equals(metricKey, condition.metricKey) &&
+      operator == condition.operator &&
+      Objects.equals(warningThreshold, condition.warningThreshold) &&
+      Objects.equals(errorThreshold, condition.errorThreshold);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(metricKey, operator, warningThreshold, errorThreshold, onLeakPeriod);
+  }
+
+  @Override
+  public String toString() {
+    return "Condition{" +
+      "metricKey='" + metricKey + '\'' +
+      ", operator=" + operator +
+      ", warningThreshold=" + toString(warningThreshold) +
+      ", errorThreshold=" + toString(errorThreshold) +
+      ", onLeakPeriod=" + onLeakPeriod +
+      '}';
+  }
+
+  private static String toString(@Nullable String errorThreshold) {
+    if (errorThreshold == null) {
+      return null;
+    }
+    return '\'' + errorThreshold + '\'';
+  }
+
+  public enum Operator {
+    EQUALS(QualityGateConditionDto.OPERATOR_EQUALS),
+    NOT_EQUALS(QualityGateConditionDto.OPERATOR_NOT_EQUALS),
+    GREATER_THAN(QualityGateConditionDto.OPERATOR_GREATER_THAN),
+    LESS_THAN(QualityGateConditionDto.OPERATOR_LESS_THAN);
+
+    private final String dbValue;
+
+    Operator(String dbValue) {
+      this.dbValue = dbValue;
+    }
+
+    public String getDbValue() {
+      return dbValue;
+    }
+
+    public static Operator fromDbValue(String s) {
+      return Stream.of(values())
+        .filter(o -> o.getDbValue().equals(s))
+        .findFirst()
+        .orElseThrow(() -> new IllegalArgumentException("Unsupported operator db value: " + s));
+    }
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java
new file mode 100644 (file)
index 0000000..b47c176
--- /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.qualitygate;
+
+import java.util.Objects;
+import java.util.Optional;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+import static java.util.Objects.requireNonNull;
+
+@Immutable
+public class EvaluatedCondition {
+  private final Condition condition;
+  private final EvaluationStatus status;
+  @Nullable
+  private final String value;
+
+  public EvaluatedCondition(Condition condition, EvaluationStatus status, @Nullable String value) {
+    this.condition = requireNonNull(condition, "condition can't be null");
+    this.status = requireNonNull(status, "status can't be null");
+    this.value = value;
+  }
+
+  public Condition getCondition() {
+    return condition;
+  }
+
+  public EvaluationStatus getStatus() {
+    return status;
+  }
+
+  public Optional<String> getValue() {
+    return Optional.ofNullable(value);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    EvaluatedCondition that = (EvaluatedCondition) o;
+    return Objects.equals(condition, that.condition) &&
+      status == that.status &&
+      Objects.equals(value, that.value);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(condition, status, value);
+  }
+
+  @Override
+  public String toString() {
+    return "EvaluatedCondition{" +
+      "condition=" + condition +
+      ", status=" + status +
+      ", value=" + (value == null ? null : ('\'' + value + '\'')) +
+      '}';
+  }
+
+  /**
+   * Quality gate condition evaluation status.
+   */
+  public enum EvaluationStatus {
+    /**
+     * No measure found or measure had no value. The condition has not been evaluated and therefor ignored in
+     * the computation of the Quality Gate status.
+     */
+    NO_VALUE,
+    /**
+     * Condition evaluated as OK, neither error nor warning thresholds have been reached.
+     */
+    OK,
+    /**
+     * Condition evaluated as WARN, only warning thresholds has been reached.
+     */
+    WARN,
+    /**
+     * Condition evaluated as ERROR, error thresholds has been reached (and most likely warning thresholds too).
+     */
+    ERROR
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java
new file mode 100644 (file)
index 0000000..f568d24
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * 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.qualitygate;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.api.measures.Metric;
+import org.sonar.server.qualitygate.EvaluatedCondition.EvaluationStatus;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+@Immutable
+public class EvaluatedQualityGate {
+  private final QualityGate qualityGate;
+  private final Metric.Level status;
+  private final Set<EvaluatedCondition> evaluatedConditions;
+  private final boolean ignoredConditionsOnSmallChangeset;
+
+  private EvaluatedQualityGate(QualityGate qualityGate, Metric.Level status, Set<EvaluatedCondition> evaluatedConditions, boolean ignoredConditionsOnSmallChangeset) {
+    this.qualityGate = requireNonNull(qualityGate, "qualityGate can't be null");
+    this.status = requireNonNull(status, "status can't be null");
+    this.evaluatedConditions = evaluatedConditions;
+    this.ignoredConditionsOnSmallChangeset = ignoredConditionsOnSmallChangeset;
+  }
+
+  public QualityGate getQualityGate() {
+    return qualityGate;
+  }
+
+  public Metric.Level getStatus() {
+    return status;
+  }
+
+  public Set<EvaluatedCondition> getEvaluatedConditions() {
+    return evaluatedConditions;
+  }
+
+  public boolean hasIgnoredConditionsOnSmallChangeset() {
+    return ignoredConditionsOnSmallChangeset;
+  }
+
+  public static Builder newBuilder() {
+    return new Builder();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    EvaluatedQualityGate that = (EvaluatedQualityGate) o;
+    return Objects.equals(qualityGate, that.qualityGate) &&
+      status == that.status &&
+      Objects.equals(evaluatedConditions, that.evaluatedConditions);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(qualityGate, status, evaluatedConditions);
+  }
+
+  @Override
+  public String toString() {
+    return "EvaluatedQualityGate{" +
+      "qualityGate=" + qualityGate +
+      ", status=" + status +
+      ", evaluatedConditions=" + evaluatedConditions +
+      '}';
+  }
+
+  public static final class Builder {
+    private QualityGate qualityGate;
+    private Metric.Level status;
+    private final Map<Condition, EvaluatedCondition> evaluatedConditions = new HashMap<>();
+    private boolean ignoredConditionsOnSmallChangeset = false;
+
+    private Builder() {
+      // use static factory method
+    }
+
+    public Builder setQualityGate(QualityGate qualityGate) {
+      this.qualityGate = qualityGate;
+      return this;
+    }
+
+    public Builder setStatus(Metric.Level status) {
+      this.status = status;
+      return this;
+    }
+
+    public Builder setIgnoredConditionsOnSmallChangeset(boolean b) {
+      this.ignoredConditionsOnSmallChangeset = b;
+      return this;
+    }
+
+    public Builder addCondition(Condition condition, EvaluationStatus status, @Nullable String value) {
+      evaluatedConditions.put(condition, new EvaluatedCondition(condition, status, value));
+      return this;
+    }
+
+    public Builder addCondition(EvaluatedCondition c) {
+      evaluatedConditions.put(c.getCondition(), c);
+      return this;
+    }
+
+    public Set<EvaluatedCondition> getEvaluatedConditions() {
+      return ImmutableSet.copyOf(evaluatedConditions.values());
+    }
+
+    public EvaluatedQualityGate build() {
+      return new EvaluatedQualityGate(
+        this.qualityGate,
+        this.status,
+        checkEvaluatedConditions(qualityGate, evaluatedConditions),
+        ignoredConditionsOnSmallChangeset);
+    }
+
+    private static Set<EvaluatedCondition> checkEvaluatedConditions(QualityGate qualityGate, Map<Condition, EvaluatedCondition> evaluatedConditions) {
+      Set<Condition> conditions = qualityGate.getConditions();
+
+      Set<Condition> conditionsNotEvaluated = conditions.stream()
+        .filter(c -> !evaluatedConditions.containsKey(c))
+        .collect(Collectors.toSet());
+      checkArgument(conditionsNotEvaluated.isEmpty(), "Evaluation missing for the following conditions: %s", conditionsNotEvaluated);
+
+      Set<Condition> unknownConditions = evaluatedConditions.keySet().stream()
+        .filter(c -> !conditions.contains(c))
+        .collect(Collectors.toSet());
+      checkArgument(unknownConditions.isEmpty(), "Evaluation provided for unknown conditions: %s", unknownConditions);
+
+      return ImmutableSet.copyOf(evaluatedConditions.values());
+    }
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGate.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGate.java
new file mode 100644 (file)
index 0000000..d047423
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * 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.qualitygate;
+
+import java.util.Objects;
+import java.util.Set;
+import javax.annotation.concurrent.Immutable;
+
+import static java.util.Objects.requireNonNull;
+import static org.sonar.core.util.stream.MoreCollectors.toSet;
+
+@Immutable
+public class QualityGate {
+  private final String id;
+  private final String name;
+  private final Set<Condition> conditions;
+
+  public QualityGate(String id, String name, Set<Condition> conditions) {
+    this.id = requireNonNull(id, "id can't be null");
+    this.name = requireNonNull(name, "name can't be null");
+    this.conditions = requireNonNull(conditions, "conditions can't be null")
+      .stream()
+      .map(c -> requireNonNull(c, "condition can't be null"))
+      .collect(toSet(conditions.size()));
+  }
+
+  public String getId() {
+    return id;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public Set<Condition> getConditions() {
+    return conditions;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    QualityGate that = (QualityGate) o;
+    return Objects.equals(id, that.id) &&
+      Objects.equals(name, that.name) &&
+      Objects.equals(conditions, that.conditions);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(id, name, conditions);
+  }
+
+  @Override
+  public String toString() {
+    return "QualityGate{" +
+      "id=" + id +
+      ", name='" + name + '\'' +
+      ", conditions=" + conditions +
+      '}';
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/package-info.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/package-info.java
new file mode 100644 (file)
index 0000000..c378d26
--- /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.qualitygate;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/util/OkHttpClientProvider.java b/server/sonar-server-common/src/main/java/org/sonar/server/util/OkHttpClientProvider.java
new file mode 100644 (file)
index 0000000..d28be20
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.util;
+
+import okhttp3.OkHttpClient;
+import org.picocontainer.injectors.ProviderAdapter;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.server.ServerSide;
+import org.sonarqube.ws.client.OkHttpClientBuilder;
+
+import static java.lang.String.format;
+import static org.sonar.process.ProcessProperties.Property.HTTP_PROXY_PASSWORD;
+import static org.sonar.process.ProcessProperties.Property.HTTP_PROXY_USER;
+
+/**
+ * Provide a unique instance of {@link OkHttpClient} which configuration:
+ * <ul>
+ *   <li>supports HTTPS</li>
+ *   <li>supports proxy, including authentication, as defined by the properties like "http.proxyHost" in
+ *   conf/sonar.properties</li>
+ *   <li>has connect and read timeouts of 10 seconds each</li>
+ *   <li>sends automatically the HTTP header "User-Agent" with value "SonarQube/{version}", for instance "SonarQube/6.2"</li>
+ * </ul>
+ */
+@ServerSide
+@ComputeEngineSide
+public class OkHttpClientProvider extends ProviderAdapter {
+
+  private static final int DEFAULT_CONNECT_TIMEOUT_IN_MS = 10_000;
+  private static final int DEFAULT_READ_TIMEOUT_IN_MS = 10_000;
+
+  private okhttp3.OkHttpClient okHttpClient;
+
+  /**
+   * @return a {@link OkHttpClient} singleton
+   */
+  public OkHttpClient provide(Configuration config, SonarRuntime runtime) {
+    if (okHttpClient == null) {
+      OkHttpClientBuilder builder = new OkHttpClientBuilder();
+      builder.setConnectTimeoutMs(DEFAULT_CONNECT_TIMEOUT_IN_MS);
+      builder.setReadTimeoutMs(DEFAULT_READ_TIMEOUT_IN_MS);
+      // no need to define proxy URL as system-wide proxy is used and properly
+      // configured by bootstrap process.
+      builder.setProxyLogin(config.get(HTTP_PROXY_USER.getKey()).orElse(null));
+      builder.setProxyPassword(config.get(HTTP_PROXY_PASSWORD.getKey()).orElse(null));
+      builder.setUserAgent(format("SonarQube/%s", runtime.getApiVersion().toString()));
+      okHttpClient = builder.build();
+    }
+    return okHttpClient;
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/Analysis.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/Analysis.java
new file mode 100644 (file)
index 0000000..5d1c24b
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.webhook;
+
+import com.google.common.base.Objects;
+import java.util.Date;
+
+import static java.util.Objects.requireNonNull;
+
+public final class Analysis {
+  private final String uuid;
+  private final long date;
+
+  public Analysis(String uuid, long date) {
+    requireNonNull(uuid, "uuid must not be null");
+    this.uuid = uuid;
+    this.date = date;
+  }
+
+  public String getUuid() {
+    return uuid;
+  }
+
+  public Date getDate() {
+    return new Date(date);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof Analysis)) {
+      return false;
+    }
+    Analysis analysis = (Analysis) o;
+    return Objects.equal(uuid, analysis.uuid) &&
+      Objects.equal(date, analysis.date);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(uuid, date);
+  }
+
+  @Override
+  public String toString() {
+    return "Analysis{" +
+      "uuid='" + uuid + '\'' +
+      ", date=" + date +
+      '}';
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/Branch.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/Branch.java
new file mode 100644 (file)
index 0000000..2d62e58
--- /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.server.webhook;
+
+import java.util.Objects;
+import java.util.Optional;
+import javax.annotation.Nullable;
+
+import static java.util.Objects.requireNonNull;
+
+public final class Branch {
+  private final boolean main;
+  private final String name;
+  private final Type type;
+
+  public Branch(boolean main, @Nullable String name, Type type) {
+    this.main = main;
+    this.name = name;
+    this.type = requireNonNull(type, "type can't be null");
+  }
+
+  public boolean isMain() {
+    return main;
+  }
+
+  public Optional<String> getName() {
+    return Optional.ofNullable(name);
+  }
+
+  public Type getType() {
+    return type;
+  }
+
+  public enum Type {
+    LONG, SHORT, PULL_REQUEST
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    Branch branch = (Branch) o;
+    return main == branch.main &&
+      Objects.equals(name, branch.name) &&
+      type == branch.type;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(main, name, type);
+  }
+
+  @Override
+  public String toString() {
+    return "Branch{" +
+      "main=" + main +
+      ", name='" + name + '\'' +
+      ", type=" + type +
+      '}';
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/CeTask.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/CeTask.java
new file mode 100644 (file)
index 0000000..226e1f8
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * 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.webhook;
+
+import java.util.Objects;
+
+import static java.util.Objects.requireNonNull;
+
+public final class CeTask {
+  private final String id;
+  private final Status status;
+
+  public CeTask(String id, Status status) {
+    this.id = requireNonNull(id, "id can't be null");
+    this.status = requireNonNull(status, "status can't be null");
+  }
+
+  public String getId() {
+    return id;
+  }
+
+  public Status getStatus() {
+    return status;
+  }
+
+  public enum Status {
+    SUCCESS, FAILED
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    CeTask task = (CeTask) o;
+    return Objects.equals(id, task.id) &&
+      status == task.status;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(id, status);
+  }
+
+  @Override
+  public String toString() {
+    return "CeTask{" +
+      "id='" + id + '\'' +
+      ", status=" + status +
+      '}';
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/ProjectAnalysis.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/ProjectAnalysis.java
new file mode 100644 (file)
index 0000000..3a9175a
--- /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.webhook;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import javax.annotation.Nullable;
+import org.sonar.server.project.Project;
+import org.sonar.server.qualitygate.EvaluatedQualityGate;
+
+import static com.google.common.collect.ImmutableMap.copyOf;
+import static java.util.Objects.requireNonNull;
+
+public class ProjectAnalysis {
+  private final Project project;
+  private final CeTask ceTask;
+  private final Branch branch;
+  private final EvaluatedQualityGate qualityGate;
+  private final Long updatedAt;
+  private final Map<String, String> properties;
+  private final Analysis analysis;
+
+  public ProjectAnalysis(Project project, @Nullable CeTask ceTask, @Nullable Analysis analysis,
+    @Nullable Branch branch, @Nullable EvaluatedQualityGate qualityGate, @Nullable Long updatedAt,
+    Map<String, String> properties) {
+    this.project = requireNonNull(project, "project can't be null");
+    this.ceTask = ceTask;
+    this.branch = branch;
+    this.qualityGate = qualityGate;
+    this.updatedAt = updatedAt;
+    this.properties = copyOf(requireNonNull(properties, "properties can't be null"));
+    this.analysis = analysis;
+  }
+
+  public Optional<CeTask> getCeTask() {
+    return Optional.ofNullable(ceTask);
+  }
+
+  public Project getProject() {
+    return project;
+  }
+
+  public Optional<Branch> getBranch() {
+    return Optional.ofNullable(branch);
+  }
+
+  public Optional<EvaluatedQualityGate> getQualityGate() {
+    return Optional.ofNullable(qualityGate);
+  }
+
+  public Map<String, String> getProperties() {
+    return properties;
+  }
+
+  public Optional<Analysis> getAnalysis() {
+    return Optional.ofNullable(analysis);
+  }
+
+  public Optional<Long> getUpdatedAt() {
+    return Optional.ofNullable(updatedAt);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    ProjectAnalysis that = (ProjectAnalysis) o;
+    return Objects.equals(project, that.project) &&
+      Objects.equals(ceTask, that.ceTask) &&
+      Objects.equals(branch, that.branch) &&
+      Objects.equals(qualityGate, that.qualityGate) &&
+      Objects.equals(updatedAt, that.updatedAt) &&
+      Objects.equals(properties, that.properties) &&
+      Objects.equals(analysis, that.analysis);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(project, ceTask, branch, qualityGate, updatedAt, properties, analysis);
+  }
+
+  @Override
+  public String toString() {
+    return "ProjectAnalysis{" +
+      "project=" + project +
+      ", ceTask=" + ceTask +
+      ", branch=" + branch +
+      ", qualityGate=" + qualityGate +
+      ", updatedAt=" + updatedAt +
+      ", properties=" + properties +
+      ", analysis=" + analysis +
+      '}';
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebHooks.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebHooks.java
new file mode 100644 (file)
index 0000000..b1c339f
--- /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.server.webhook;
+
+import java.util.Objects;
+import java.util.function.Supplier;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.config.Configuration;
+import org.sonar.db.component.ComponentDto;
+
+import static java.util.Objects.requireNonNull;
+
+public interface WebHooks {
+
+  /**
+   * Tells whether any webHook is configured for the specified {@link Configuration}.
+   *
+   * <p>
+   * This can be used to not do consuming operations before calling
+   * {@link #sendProjectAnalysisUpdate(Analysis, Supplier)}
+   */
+  boolean isEnabled(ComponentDto projectDto);
+
+  /**
+   * Calls all WebHooks configured in the specified {@link Configuration} for the specified analysis with the
+   * {@link WebhookPayload} provided by the specified Supplier.
+   */
+  void sendProjectAnalysisUpdate(Analysis analysis, Supplier<WebhookPayload> payloadSupplier);
+
+  final class Analysis {
+    private final String projectUuid;
+    private final String ceTaskUuid;
+    private final String analysisUuid;
+
+    public Analysis(String projectUuid, @Nullable String analysisUuid, @Nullable  String ceTaskUuid) {
+      this.projectUuid = requireNonNull(projectUuid, "projectUuid can't be null");
+      this.analysisUuid = analysisUuid;
+      this.ceTaskUuid = ceTaskUuid;
+    }
+
+    public String getProjectUuid() {
+      return projectUuid;
+    }
+
+    @CheckForNull
+    public String getCeTaskUuid() {
+      return ceTaskUuid;
+    }
+
+    @CheckForNull
+    public String getAnalysisUuid() {
+      return analysisUuid;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      Analysis analysis = (Analysis) o;
+      return Objects.equals(projectUuid, analysis.projectUuid) &&
+        Objects.equals(ceTaskUuid, analysis.ceTaskUuid) &&
+        Objects.equals(analysisUuid, analysis.analysisUuid);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(projectUuid, ceTaskUuid, analysisUuid);
+    }
+
+    @Override
+    public String toString() {
+      return "Analysis{" +
+        "projectUuid='" + projectUuid + '\'' +
+        ", ceTaskUuid='" + ceTaskUuid + '\'' +
+        '}';
+    }
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebHooksImpl.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebHooksImpl.java
new file mode 100644 (file)
index 0000000..26c4326
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * 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.webhook;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.webhook.WebhookDao;
+import org.sonar.db.webhook.WebhookDto;
+import org.sonar.server.async.AsyncExecution;
+
+import static java.lang.String.format;
+import static java.util.Optional.ofNullable;
+
+@ServerSide
+@ComputeEngineSide
+public class WebHooksImpl implements WebHooks {
+
+  private static final Logger LOGGER = Loggers.get(WebHooksImpl.class);
+
+  private final WebhookCaller caller;
+  private final WebhookDeliveryStorage deliveryStorage;
+  private final AsyncExecution asyncExecution;
+  private final DbClient dbClient;
+
+  public WebHooksImpl(WebhookCaller caller, WebhookDeliveryStorage deliveryStorage, AsyncExecution asyncExecution, DbClient dbClient) {
+    this.caller = caller;
+    this.deliveryStorage = deliveryStorage;
+    this.asyncExecution = asyncExecution;
+    this.dbClient = dbClient;
+  }
+
+  @Override
+  public boolean isEnabled(ComponentDto projectDto) {
+    return readWebHooksFrom(projectDto.uuid())
+      .findAny()
+      .isPresent();
+  }
+
+  private Stream<WebhookDto> readWebHooksFrom(String projectUuid) {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+
+      Optional<ComponentDto> optionalComponentDto = ofNullable(dbClient.componentDao().selectByUuid(dbSession, projectUuid).orNull());
+      ComponentDto componentDto = checkStateWithOptional(optionalComponentDto, "the requested project '%s' was not found", projectUuid);
+
+      if (componentDto.getMainBranchProjectUuid() != null && !componentDto.uuid().equals(componentDto.getMainBranchProjectUuid())) {
+        Optional<ComponentDto> mainBranchComponentDto = ofNullable(dbClient.componentDao().selectByUuid(dbSession, componentDto.getMainBranchProjectUuid()).orNull());
+        componentDto = checkStateWithOptional(mainBranchComponentDto, "the requested project '%s' was not found", projectUuid);
+      }
+
+      WebhookDao dao = dbClient.webhookDao();
+      return Stream.concat(
+        dao.selectByProject(dbSession, componentDto).stream(),
+        dao.selectByOrganizationUuid(dbSession, componentDto.getOrganizationUuid()).stream());
+    }
+  }
+
+  private static <T> T checkStateWithOptional(java.util.Optional<T> value, String message, Object... messageArguments) {
+    if (!value.isPresent()) {
+      throw new IllegalStateException(format(message, messageArguments));
+    }
+
+    return value.get();
+  }
+
+  @Override
+  public void sendProjectAnalysisUpdate(Analysis analysis, Supplier<WebhookPayload> payloadSupplier) {
+    List<Webhook> webhooks = readWebHooksFrom(analysis.getProjectUuid())
+      .map(dto -> new Webhook(dto.getUuid(), analysis.getProjectUuid(), analysis.getCeTaskUuid(), analysis.getAnalysisUuid(), dto.getName(), dto.getUrl()))
+      .collect(MoreCollectors.toList());
+    if (webhooks.isEmpty()) {
+      return;
+    }
+
+    WebhookPayload payload = payloadSupplier.get();
+    webhooks.forEach(webhook -> asyncExecution.addToQueue(() -> {
+      WebhookDelivery delivery = caller.call(webhook, payload);
+      log(delivery);
+      deliveryStorage.persist(delivery);
+    }));
+    asyncExecution.addToQueue(() -> deliveryStorage.purge(analysis.getProjectUuid()));
+  }
+
+  private static void log(WebhookDelivery delivery) {
+    Optional<String> error = delivery.getErrorMessage();
+    if (error.isPresent()) {
+      LOGGER.debug("Failed to send webhook '{}' | url={} | message={}",
+        delivery.getWebhook().getName(), delivery.getWebhook().getUrl(), error.get());
+    } else {
+      LOGGER.debug("Sent webhook '{}' | url={} | time={}ms | status={}",
+        delivery.getWebhook().getName(), delivery.getWebhook().getUrl(), delivery.getDurationInMs().orElse(-1), delivery.getHttpStatus().orElse(-1));
+    }
+  }
+
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/Webhook.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/Webhook.java
new file mode 100644 (file)
index 0000000..8ac3b4c
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * 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.webhook;
+
+import java.util.Optional;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+import static java.util.Objects.requireNonNull;
+import static java.util.Optional.ofNullable;
+
+@Immutable
+public class Webhook {
+
+  private final String uuid;
+  private final String componentUuid;
+  private final String ceTaskUuid;
+  private final String analysisUuid;
+  private final String name;
+  private final String url;
+
+  public Webhook(String uuid, String componentUuid, @Nullable String ceTaskUuid, @Nullable String analysisUuid, String name, String url) {
+    this.uuid = uuid;
+    this.componentUuid = requireNonNull(componentUuid);
+    this.ceTaskUuid = ceTaskUuid;
+    this.analysisUuid = analysisUuid;
+    this.name = requireNonNull(name);
+    this.url = requireNonNull(url);
+  }
+
+  public String getComponentUuid() {
+    return componentUuid;
+  }
+
+  public Optional<String> getCeTaskUuid() {
+    return ofNullable(ceTaskUuid);
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public String getUrl() {
+    return url;
+  }
+
+  public String getUuid() {
+    return uuid;
+  }
+
+  public Optional<String> getAnalysisUuid() {
+    return ofNullable(analysisUuid);
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookCaller.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookCaller.java
new file mode 100644 (file)
index 0000000..979386a
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * 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.webhook;
+
+public interface WebhookCaller {
+
+  /**
+   * Call webhook by sending a HTTP(S) POST request containing
+   * the JSON payload.
+   * <br/>
+   * Errors are silently ignored. They don't generate logs or
+   * throw exceptions. The error status is stored in the
+   * returned {@link WebhookDelivery}.
+   */
+  WebhookDelivery call(Webhook webhook, WebhookPayload payload);
+
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookCallerImpl.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookCallerImpl.java
new file mode 100644 (file)
index 0000000..4f0624b
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * 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.webhook;
+
+import java.io.IOException;
+import okhttp3.Credentials;
+import okhttp3.HttpUrl;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.System2;
+
+import static java.lang.String.format;
+import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
+import static java.net.HttpURLConnection.HTTP_MOVED_TEMP;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static okhttp3.internal.http.StatusLine.HTTP_PERM_REDIRECT;
+import static okhttp3.internal.http.StatusLine.HTTP_TEMP_REDIRECT;
+import static org.apache.commons.lang.StringUtils.isNotEmpty;
+
+@ServerSide
+@ComputeEngineSide
+public class WebhookCallerImpl implements WebhookCaller {
+
+  private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
+  private static final String PROJECT_KEY_HEADER = "X-SonarQube-Project";
+
+  private final System2 system;
+  private final OkHttpClient okHttpClient;
+
+  public WebhookCallerImpl(System2 system, OkHttpClient okHttpClient) {
+    this.system = system;
+    this.okHttpClient = newClientWithoutRedirect(okHttpClient);
+  }
+
+  @Override
+  public WebhookDelivery call(Webhook webhook, WebhookPayload payload) {
+    WebhookDelivery.Builder builder = new WebhookDelivery.Builder();
+    long startedAt = system.now();
+    builder
+      .setAt(startedAt)
+      .setPayload(payload)
+      .setWebhook(webhook);
+
+    try {
+      Request request = buildHttpRequest(webhook, payload);
+      try (Response response = execute(request)) {
+        builder.setHttpStatus(response.code());
+      }
+    } catch (Exception e) {
+      builder.setError(e);
+    }
+
+    return builder
+      .setDurationInMs((int) (system.now() - startedAt))
+      .build();
+  }
+
+  private static Request buildHttpRequest(Webhook webhook, WebhookPayload payload) {
+    HttpUrl url = HttpUrl.parse(webhook.getUrl());
+    if (url == null) {
+      throw new IllegalArgumentException("Webhook URL is not valid: " + webhook.getUrl());
+    }
+    Request.Builder request = new Request.Builder();
+    request.url(url);
+    request.header(PROJECT_KEY_HEADER, payload.getProjectKey());
+    if (isNotEmpty(url.username())) {
+      request.header("Authorization", Credentials.basic(url.username(), url.password(), UTF_8));
+    }
+
+    RequestBody body = RequestBody.create(JSON, payload.getJson());
+    request.post(body);
+    return request.build();
+  }
+
+  private Response execute(Request request) throws IOException {
+    Response response = okHttpClient.newCall(request).execute();
+    switch (response.code()) {
+      case HTTP_MOVED_PERM:
+      case HTTP_MOVED_TEMP:
+      case HTTP_TEMP_REDIRECT:
+      case HTTP_PERM_REDIRECT:
+        // OkHttpClient does not follow the redirect with the same HTTP method. A POST is
+        // redirected to a GET. Because of that the redirect must be manually
+        // implemented.
+        // See:
+        // https://github.com/square/okhttp/blob/07309c1c7d9e296014268ebd155ebf7ef8679f6c/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java#L316
+        // https://github.com/square/okhttp/issues/936#issuecomment-266430151
+        return followPostRedirect(response);
+      default:
+        return response;
+    }
+  }
+
+  /**
+   * Inspired by https://github.com/square/okhttp/blob/parent-3.6.0/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java#L286
+   */
+  private Response followPostRedirect(Response response) throws IOException {
+    String location = response.header("Location");
+    if (location == null) {
+      throw new IllegalStateException(format("Missing HTTP header 'Location' in redirect of %s", response.request().url()));
+    }
+    HttpUrl url = response.request().url().resolve(location);
+
+    // Don't follow redirects to unsupported protocols.
+    if (url == null) {
+      throw new IllegalStateException(format("Unsupported protocol in redirect of %s to %s", response.request().url(), location));
+    }
+
+    Request.Builder redirectRequest = response.request().newBuilder();
+    redirectRequest.post(response.request().body());
+    response.body().close();
+    return okHttpClient.newCall(redirectRequest.url(url).build()).execute();
+  }
+
+  private static OkHttpClient newClientWithoutRedirect(OkHttpClient client) {
+    return client.newBuilder()
+      .followRedirects(false)
+      .followSslRedirects(false)
+      .build();
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookDelivery.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookDelivery.java
new file mode 100644 (file)
index 0000000..38194e0
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * 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.webhook;
+
+import java.util.Optional;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+import static com.google.common.base.Throwables.getRootCause;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * A {@link WebhookDelivery} represents the result of a webhook call.
+ */
+@Immutable
+public class WebhookDelivery {
+
+  private final Webhook webhook;
+  private final WebhookPayload payload;
+  private final Integer httpStatus;
+  private final Integer durationInMs;
+  private final long at;
+  private final Throwable error;
+
+  private WebhookDelivery(Builder builder) {
+    this.webhook = requireNonNull(builder.webhook);
+    this.payload = requireNonNull(builder.payload);
+    this.httpStatus = builder.httpStatus;
+    this.durationInMs = builder.durationInMs;
+    this.at = builder.at;
+    this.error = builder.error;
+  }
+
+  public Webhook getWebhook() {
+    return webhook;
+  }
+
+  public WebhookPayload getPayload() {
+    return payload;
+  }
+
+  /**
+   * @return the HTTP status if {@link #getError()} is empty, else returns
+   * {@link Optional#empty()}
+   */
+  public Optional<Integer> getHttpStatus() {
+    return Optional.ofNullable(httpStatus);
+  }
+
+  /**
+   * @return the duration in milliseconds if {@link #getError()} is empty,
+   * else returns {@link Optional#empty()}
+   */
+  public Optional<Integer> getDurationInMs() {
+    return Optional.ofNullable(durationInMs);
+  }
+
+  /**
+   * @return the date of sending
+   */
+  public long getAt() {
+    return at;
+  }
+
+  /**
+   * @return the error raised if the request could not be executed due to a connectivity
+   * problem or timeout
+   */
+  public Optional<Throwable> getError() {
+    return Optional.ofNullable(error);
+  }
+
+  /**
+   * @return the cause message of {@link #getError()}, Optional.empty() is error is not set.
+   */
+  public Optional<String> getErrorMessage() {
+    return error != null ? Optional.ofNullable(getRootCause(error).getMessage()) : Optional.empty();
+  }
+
+  public boolean isSuccess() {
+    return httpStatus != null && httpStatus >= 200 && httpStatus < 300;
+  }
+
+  public static class Builder {
+    private Webhook webhook;
+    private WebhookPayload payload;
+    private Integer httpStatus;
+    private Integer durationInMs;
+    private long at;
+    private Throwable error;
+
+    public Builder setWebhook(Webhook w) {
+      this.webhook = w;
+      return this;
+    }
+
+    public Builder setPayload(WebhookPayload payload) {
+      this.payload = payload;
+      return this;
+    }
+
+    public Builder setHttpStatus(@Nullable Integer httpStatus) {
+      this.httpStatus = httpStatus;
+      return this;
+    }
+
+    public Builder setDurationInMs(@Nullable Integer durationInMs) {
+      this.durationInMs = durationInMs;
+      return this;
+    }
+
+    public Builder setAt(long at) {
+      this.at = at;
+      return this;
+    }
+
+    public Builder setError(@Nullable Throwable t) {
+      this.error = t;
+      return this;
+    }
+
+    public WebhookDelivery build() {
+      return new WebhookDelivery(this);
+    }
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookDeliveryStorage.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookDeliveryStorage.java
new file mode 100644 (file)
index 0000000..7bf13c4
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * 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.webhook;
+
+import com.google.common.base.Throwables;
+import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.webhook.WebhookDeliveryDao;
+import org.sonar.db.webhook.WebhookDeliveryDto;
+
+/**
+ * Persist and purge {@link WebhookDelivery} into database
+ */
+@ServerSide
+@ComputeEngineSide
+public class WebhookDeliveryStorage {
+
+  private static final long ALIVE_DELAY_MS = 30L * 24 * 60 * 60 * 1000;
+
+  private final DbClient dbClient;
+  private final System2 system;
+  private final UuidFactory uuidFactory;
+
+  public WebhookDeliveryStorage(DbClient dbClient, System2 system, UuidFactory uuidFactory) {
+    this.dbClient = dbClient;
+    this.system = system;
+    this.uuidFactory = uuidFactory;
+  }
+
+  public void persist(WebhookDelivery delivery) {
+    WebhookDeliveryDao dao = dbClient.webhookDeliveryDao();
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      dao.insert(dbSession, toDto(delivery));
+      dbSession.commit();
+    }
+  }
+
+  public void purge(String componentUuid) {
+    long beforeDate = system.now() - ALIVE_DELAY_MS;
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      dbClient.webhookDeliveryDao().deleteComponentBeforeDate(dbSession, componentUuid, beforeDate);
+      dbSession.commit();
+    }
+  }
+
+  private WebhookDeliveryDto toDto(WebhookDelivery delivery) {
+    WebhookDeliveryDto dto = new WebhookDeliveryDto();
+    dto.setUuid(uuidFactory.create());
+    dto.setWebhookUuid(delivery.getWebhook().getUuid());
+    dto.setComponentUuid(delivery.getWebhook().getComponentUuid());
+    delivery.getWebhook().getCeTaskUuid().ifPresent(dto::setCeTaskUuid);
+    delivery.getWebhook().getAnalysisUuid().ifPresent(dto::setAnalysisUuid);
+    dto.setName(delivery.getWebhook().getName());
+    dto.setUrl(delivery.getWebhook().getUrl());
+    dto.setSuccess(delivery.isSuccess());
+    dto.setHttpStatus(delivery.getHttpStatus().orElse(null));
+    dto.setDurationMs(delivery.getDurationInMs().orElse(null));
+    dto.setErrorStacktrace(delivery.getError().map(Throwables::getStackTraceAsString).orElse(null));
+    dto.setPayload(delivery.getPayload().getJson());
+    dto.setCreatedAt(delivery.getAt());
+    return dto;
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookModule.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookModule.java
new file mode 100644 (file)
index 0000000..1768b27
--- /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.webhook;
+
+import org.sonar.core.platform.Module;
+
+public class WebhookModule extends Module {
+  @Override
+  protected void configureModule() {
+    add(
+      WebhookCallerImpl.class,
+      WebhookDeliveryStorage.class,
+      WebHooksImpl.class,
+      WebhookPayloadFactoryImpl.class);
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookPayload.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookPayload.java
new file mode 100644 (file)
index 0000000..197f472
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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.webhook;
+
+import javax.annotation.concurrent.Immutable;
+
+import static java.util.Objects.requireNonNull;
+
+@Immutable
+public class WebhookPayload {
+
+  private final String projectKey;
+  private final String json;
+
+  public WebhookPayload(String projectKey, String json) {
+    this.projectKey = requireNonNull(projectKey);
+    this.json = requireNonNull(json);
+  }
+
+  public String getProjectKey() {
+    return projectKey;
+  }
+
+  public String getJson() {
+    return json;
+  }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookPayloadFactory.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookPayloadFactory.java
new file mode 100644 (file)
index 0000000..cf2751f
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * 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.webhook;
+
+@FunctionalInterface
+public interface WebhookPayloadFactory {
+
+  WebhookPayload create(ProjectAnalysis analysis);
+
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookPayloadFactoryImpl.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookPayloadFactoryImpl.java
new file mode 100644 (file)
index 0000000..d6b9b29
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * 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.webhook;
+
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.net.URLEncoder;
+import java.util.Date;
+import java.util.Map;
+import java.util.Optional;
+import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.platform.Server;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.server.project.Project;
+import org.sonar.server.qualitygate.Condition;
+import org.sonar.server.qualitygate.EvaluatedCondition;
+import org.sonar.server.qualitygate.EvaluatedQualityGate;
+
+import static java.lang.String.format;
+import static org.sonar.core.config.CorePropertyDefinitions.SONAR_ANALYSIS;
+
+@ServerSide
+@ComputeEngineSide
+public class WebhookPayloadFactoryImpl implements WebhookPayloadFactory {
+
+  private static final String PROPERTY_STATUS = "status";
+  private final Server server;
+  private final System2 system2;
+
+  public WebhookPayloadFactoryImpl(Server server, System2 system2) {
+    this.server = server;
+    this.system2 = system2;
+  }
+
+  @Override
+  public WebhookPayload create(ProjectAnalysis analysis) {
+    Writer string = new StringWriter();
+    try (JsonWriter writer = JsonWriter.of(string)) {
+      writer.beginObject();
+      writeServer(writer);
+      writeTask(writer, analysis.getCeTask());
+      writeDates(writer, analysis, system2);
+      writeProject(analysis, writer, analysis.getProject());
+      analysis.getBranch().ifPresent(b -> writeBranch(writer, analysis.getProject(), b));
+      analysis.getQualityGate().ifPresent(qualityGate -> writeQualityGate(writer, qualityGate));
+      writeAnalysisProperties(writer, analysis.getProperties());
+      writer.endObject().close();
+      return new WebhookPayload(analysis.getProject().getKey(), string.toString());
+    }
+  }
+
+  private void writeServer(JsonWriter writer) {
+    writer.prop("serverUrl", server.getPublicRootUrl());
+  }
+
+  private static void writeDates(JsonWriter writer, ProjectAnalysis analysis, System2 system2) {
+    analysis.getAnalysis().ifPresent(a -> writer.propDateTime("analysedAt", a.getDate()));
+    writer.propDateTime("changedAt", new Date(analysis.getUpdatedAt().orElse(system2.now())));
+  }
+
+  private void writeProject(ProjectAnalysis analysis, JsonWriter writer, Project project) {
+    writer
+      .name("project")
+      .beginObject()
+      .prop("key", project.getKey())
+      .prop("name", analysis.getProject().getName())
+      .prop("url", projectUrlOf(project))
+      .endObject();
+  }
+
+  private static void writeAnalysisProperties(JsonWriter writer, Map<String, String> properties) {
+    writer
+      .name("properties")
+      .beginObject();
+    properties.entrySet()
+      .stream()
+      .filter(prop -> prop.getKey().startsWith(SONAR_ANALYSIS))
+      .forEach(prop -> writer.prop(prop.getKey(), prop.getValue()));
+    writer.endObject();
+  }
+
+  private static void writeTask(JsonWriter writer, Optional<CeTask> ceTask) {
+    ceTask.ifPresent(ceTask1 -> writer.prop("taskId", ceTask1.getId()));
+    writer.prop(PROPERTY_STATUS, ceTask.map(CeTask::getStatus).orElse(CeTask.Status.SUCCESS).toString());
+  }
+
+  private void writeBranch(JsonWriter writer, Project project, Branch branch) {
+    writer
+      .name("branch")
+      .beginObject()
+      .prop("name", branch.getName().orElse(null))
+      .prop("type", branch.getType().name())
+      .prop("isMain", branch.isMain())
+      .prop("url", branchUrlOf(project, branch))
+      .endObject();
+  }
+
+  private String projectUrlOf(Project project) {
+    return format("%s/dashboard?id=%s", server.getPublicRootUrl(), encode(project.getKey()));
+  }
+
+  private String branchUrlOf(Project project, Branch branch) {
+    if (branch.getType() == Branch.Type.LONG) {
+      if (branch.isMain()) {
+        return projectUrlOf(project);
+      }
+      return format("%s/dashboard?branch=%s&id=%s",
+        server.getPublicRootUrl(), encode(branch.getName().orElse("")), encode(project.getKey()));
+    }
+    if (branch.getType() == Branch.Type.SHORT) {
+      return format("%s/project/issues?branch=%s&id=%s&resolved=false",
+        server.getPublicRootUrl(), encode(branch.getName().orElse("")), encode(project.getKey()));
+    }
+    if (branch.getType() == Branch.Type.PULL_REQUEST) {
+      return format("%s/project/issues?pullRequest=%s&id=%s&resolved=false",
+        server.getPublicRootUrl(), encode(branch.getName().orElse("")), encode(project.getKey()));
+    }
+    return projectUrlOf(project);
+  }
+
+  private static void writeQualityGate(JsonWriter writer, EvaluatedQualityGate gate) {
+    writer
+      .name("qualityGate")
+      .beginObject()
+      .prop("name", gate.getQualityGate().getName())
+      .prop(PROPERTY_STATUS, gate.getStatus().toString())
+      .name("conditions")
+      .beginArray();
+    for (EvaluatedCondition evaluatedCondition : gate.getEvaluatedConditions()) {
+      Condition condition = evaluatedCondition.getCondition();
+      writer
+        .beginObject()
+        .prop("metric", condition.getMetricKey())
+        .prop("operator", condition.getOperator().name());
+      evaluatedCondition.getValue().ifPresent(t -> writer.prop("value", t));
+      writer
+        .prop(PROPERTY_STATUS, evaluatedCondition.getStatus().name())
+        .prop("onLeakPeriod", condition.isOnLeakPeriod())
+        .prop("errorThreshold", condition.getErrorThreshold().orElse(null))
+        .prop("warningThreshold", condition.getWarningThreshold().orElse(null))
+        .endObject();
+    }
+    writer
+      .endArray()
+      .endObject();
+  }
+
+  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-common/src/main/java/org/sonar/server/webhook/package-info.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/package-info.java
new file mode 100644 (file)
index 0000000..e4048c7
--- /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.webhook;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/async/AsyncExecutionExecutorServiceImplTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/async/AsyncExecutionExecutorServiceImplTest.java
new file mode 100644 (file)
index 0000000..8aa2ee5
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.async;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AsyncExecutionExecutorServiceImplTest {
+  private AsyncExecutionExecutorServiceImpl underTest = new AsyncExecutionExecutorServiceImpl();
+
+  @Test
+  public void submit_executes_runnable_in_another_thread() {
+    try (SlowRunnable slowRunnable = new SlowRunnable()) {
+      underTest.submit(slowRunnable);
+      assertThat(slowRunnable.executed).isFalse();
+    }
+  }
+
+  private static final class SlowRunnable implements Runnable, AutoCloseable {
+    private final CountDownLatch latch = new CountDownLatch(1);
+    private volatile boolean executed = false;
+
+    @Override
+    public void run() {
+      try {
+        latch.await(30, TimeUnit.SECONDS);
+      } catch (InterruptedException e) {
+        // ignore
+      }
+      executed = true;
+    }
+
+    @Override
+    public void close() {
+      latch.countDown();
+    }
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/async/AsyncExecutionImplTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/async/AsyncExecutionImplTest.java
new file mode 100644 (file)
index 0000000..0ff2d3c
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * 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.async;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AsyncExecutionImplTest {
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  private AsyncExecutionExecutorService synchronousExecutorService = Runnable::run;
+  private AsyncExecutionImpl underTest = new AsyncExecutionImpl(synchronousExecutorService);
+
+  @Test
+  public void addToQueue_fails_with_NPE_if_Runnable_is_null() {
+    expectedException.expect(NullPointerException.class);
+
+    underTest.addToQueue(null);
+  }
+
+  @Test
+  public void addToQueue_submits_runnable_to_executorService_which_does_not_fail_if_Runnable_argument_throws_exception() {
+    underTest.addToQueue(() -> {
+      throw new RuntimeException("Faking an exception thrown by Runnable argument");
+    });
+
+    assertThat(logTester.logs()).hasSize(1);
+    assertThat(logTester.logs(LoggerLevel.ERROR)).containsOnly("Asynchronous task failed");
+  }
+
+  @Test
+  public void addToQueue_submits_runnable_that_fails_if_Runnable_argument_throws_Error() {
+    Error expected = new Error("Faking an exception thrown by Runnable argument");
+    Runnable runnable = () -> {
+      throw expected;
+    };
+
+    expectedException.expect(Error.class);
+    expectedException.expectMessage(expected.getMessage());
+
+    underTest.addToQueue(runnable);
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/async/AsyncExecutionMBeanImplTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/async/AsyncExecutionMBeanImplTest.java
new file mode 100644 (file)
index 0000000..e534273
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * 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.async;
+
+import java.lang.management.ManagementFactory;
+import javax.annotation.CheckForNull;
+import javax.management.InstanceNotFoundException;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class AsyncExecutionMBeanImplTest {
+  private AsyncExecutionMonitoring asyncExecutionMonitoring = Mockito.mock(AsyncExecutionMonitoring.class);
+
+  private AsyncExecutionMBeanImpl underTest = new AsyncExecutionMBeanImpl(asyncExecutionMonitoring);
+
+  @Test
+  public void register_and_unregister() throws Exception {
+    assertThat(getMBean()).isNull();
+
+    underTest.start();
+    assertThat(getMBean()).isNotNull();
+
+    underTest.stop();
+    assertThat(getMBean()).isNull();
+  }
+
+  @Test
+  public void getQueueSize_delegates_to_AsyncExecutionMonitoring() {
+    when(asyncExecutionMonitoring.getQueueSize()).thenReturn(12);
+
+    assertThat(underTest.getQueueSize()).isEqualTo(12);
+
+    verify(asyncExecutionMonitoring).getQueueSize();
+  }
+
+  @Test
+  public void getWorkerCount_delegates_to_AsyncExecutionMonitoring() {
+    when(asyncExecutionMonitoring.getWorkerCount()).thenReturn(12);
+
+    assertThat(underTest.getWorkerCount()).isEqualTo(12);
+
+    verify(asyncExecutionMonitoring).getWorkerCount();
+  }
+
+  @Test
+  public void getLargestWorkerCount_delegates_to_AsyncExecutionMonitoring() {
+    when(asyncExecutionMonitoring.getLargestWorkerCount()).thenReturn(12);
+
+    assertThat(underTest.getLargestWorkerCount()).isEqualTo(12);
+
+    verify(asyncExecutionMonitoring).getLargestWorkerCount();
+  }
+
+  @CheckForNull
+  private ObjectInstance getMBean() throws Exception {
+    try {
+      return ManagementFactory.getPlatformMBeanServer().getObjectInstance(new ObjectName(AsyncExecutionMBean.OBJECT_NAME));
+    } catch (InstanceNotFoundException e) {
+      return null;
+    }
+  }
+
+
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/project/ProjectTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/project/ProjectTest.java
new file mode 100644 (file)
index 0000000..4f2a351
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.project;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ProjectTest {
+  @Test
+  public void test_bean_without_description() {
+    Project project1 = new Project("U1", "K1", "N1");
+    Project project2 = new Project("U1", "K1", "N1", null);
+
+    assertThat(project1.getUuid()).isEqualTo(project2.getUuid()).isEqualTo("U1");
+    assertThat(project1.getKey()).isEqualTo(project2.getKey()).isEqualTo("K1");
+    assertThat(project1.getName()).isEqualTo(project2.getName()).isEqualTo("N1");
+    assertThat(project1.getDescription()).isEqualTo(project2.getDescription()).isNull();
+
+    assertThat(project1.toString())
+      .isEqualTo(project2.toString())
+      .isEqualTo("Project{uuid='U1', key='K1', name='N1', description=null}");
+  }
+
+  @Test
+  public void test_bean_with_description() {
+    Project project1 = new Project("U1", "K1", "N1", "D1");
+
+    assertThat(project1.getUuid()).isEqualTo("U1");
+    assertThat(project1.getKey()).isEqualTo("K1");
+    assertThat(project1.getName()).isEqualTo("N1");
+    assertThat(project1.getDescription()).isEqualTo("D1");
+
+    assertThat(project1.toString())
+      .isEqualTo(project1.toString())
+      .isEqualTo("Project{uuid='U1', key='K1', name='N1', description='D1'}");
+  }
+
+  @Test
+  public void test_equals_and_hashCode() {
+    Project project1 = new Project("U1", "K1", "N1");
+    Project project2 = new Project("U1", "K1", "N1", "D1");
+
+    assertThat(project1).isEqualTo(project1);
+    assertThat(project1).isNotEqualTo(null);
+    assertThat(project1).isNotEqualTo(new Object());
+    assertThat(project1).isEqualTo(new Project("U1", "K1", "N1", null));
+    assertThat(project1).isNotEqualTo(new Project("U1", "K2", "N1", null));
+    assertThat(project1).isNotEqualTo(new Project("U1", "K1", "N2", null));
+    assertThat(project1).isEqualTo(project2);
+
+    assertThat(project1.hashCode()).isEqualTo(project1.hashCode());
+    assertThat(project1.hashCode()).isNotEqualTo(null);
+    assertThat(project1.hashCode()).isNotEqualTo(new Object().hashCode());
+    assertThat(project1.hashCode()).isEqualTo(new Project("U1", "K1", "N1", null).hashCode());
+    assertThat(project1.hashCode()).isNotEqualTo(new Project("U1", "K2", "N1", null).hashCode());
+    assertThat(project1.hashCode()).isNotEqualTo(new Project("U1", "K1", "N2", null).hashCode());
+    assertThat(project1.hashCode()).isEqualTo(project2.hashCode());
+  }
+
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/ConditionTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/ConditionTest.java
new file mode 100644 (file)
index 0000000..ba0b79a
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * 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.qualitygate;
+
+import java.util.Arrays;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ConditionTest {
+  private static final String METRIC_KEY = "metric_key";
+  private static final Condition.Operator OPERATOR = Condition.Operator.EQUALS;
+  private static final String ERROR_THRESHOLD = "2";
+  private static final String WARN_THRESHOLD = "4";
+  private static final boolean ON_LEAK_PERIOD = true;
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private Condition underTest = new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD);
+
+  @Test
+  public void constructor_throws_NPE_if_metricKey_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("metricKey can't be null");
+
+    new Condition(null, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD);
+  }
+
+  @Test
+  public void constructor_throws_NPE_if_operator_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("operator can't be null");
+
+    new Condition(METRIC_KEY, null, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD);
+  }
+
+  @Test
+  public void errorThreshold_can_be_null() {
+    Condition underTest = new Condition(METRIC_KEY, OPERATOR, null, WARN_THRESHOLD, ON_LEAK_PERIOD);
+
+    assertThat(underTest.getErrorThreshold()).isEmpty();
+  }
+
+  @Test
+  public void warnThreshold_can_be_null() {
+    Condition underTest = new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, null, ON_LEAK_PERIOD);
+
+    assertThat(underTest.getWarningThreshold()).isEmpty();
+  }
+
+  @Test
+  public void verify_getters() {
+    assertThat(underTest.getMetricKey()).isEqualTo(METRIC_KEY);
+    assertThat(underTest.getOperator()).isEqualTo(OPERATOR);
+    assertThat(underTest.getErrorThreshold()).contains(ERROR_THRESHOLD);
+    assertThat(underTest.getWarningThreshold()).contains(WARN_THRESHOLD);
+    assertThat(underTest.isOnLeakPeriod()).isEqualTo(ON_LEAK_PERIOD);
+  }
+
+  @Test
+  public void toString_is_override() {
+    assertThat(underTest.toString())
+      .isEqualTo("Condition{metricKey='metric_key', operator=EQUALS, warningThreshold='4', errorThreshold='2', onLeakPeriod=true}");
+  }
+
+  @Test
+  public void toString_does_not_quote_nulls() {
+    Condition withNulls = new Condition("metric_key", Condition.Operator.LESS_THAN, null, null, false);
+    assertThat(withNulls.toString())
+      .isEqualTo("Condition{metricKey='metric_key', operator=LESS_THAN, warningThreshold=null, errorThreshold=null, onLeakPeriod=false}");
+  }
+
+  @Test
+  public void equals_is_based_on_all_fields() {
+    assertThat(underTest).isEqualTo(underTest);
+    assertThat(underTest).isNotEqualTo(null);
+    assertThat(underTest).isNotEqualTo(new Object());
+    assertThat(underTest).isEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD));
+    assertThat(underTest).isNotEqualTo(new Condition("other_metric_key", OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD));
+    Arrays.stream(Condition.Operator.values())
+        .filter(s -> !OPERATOR.equals(s))
+        .forEach(otherOperator ->  assertThat(underTest)
+            .isNotEqualTo(new Condition(METRIC_KEY, otherOperator, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD)));
+    assertThat(underTest).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, null, WARN_THRESHOLD, ON_LEAK_PERIOD));
+    assertThat(underTest).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, "other_error_threshold", WARN_THRESHOLD, ON_LEAK_PERIOD));
+    assertThat(underTest).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, null, ON_LEAK_PERIOD));
+    assertThat(underTest).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, "other_warn_threshold", ON_LEAK_PERIOD));
+    assertThat(underTest).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, !ON_LEAK_PERIOD));
+  }
+
+  @Test
+  public void hashcode_is_based_on_all_fields() {
+    assertThat(underTest.hashCode()).isEqualTo(underTest.hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(null);
+    assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode());
+    assertThat(underTest.hashCode()).isEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new Condition("other_metric_key", OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD).hashCode());
+    Arrays.stream(Condition.Operator.values())
+        .filter(s -> !OPERATOR.equals(s))
+        .forEach(otherOperator ->  assertThat(underTest.hashCode())
+            .isNotEqualTo(new Condition(METRIC_KEY, otherOperator, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD).hashCode()));
+    assertThat(underTest.hashCode()).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, null, WARN_THRESHOLD, ON_LEAK_PERIOD).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, "other_error_threshold", WARN_THRESHOLD, ON_LEAK_PERIOD).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, null, ON_LEAK_PERIOD).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, "other_warn_threshold", ON_LEAK_PERIOD).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, !ON_LEAK_PERIOD).hashCode());
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java
new file mode 100644 (file)
index 0000000..7cadd61
--- /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.qualitygate;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.qualitygate.Condition.Operator.EQUALS;
+import static org.sonar.server.qualitygate.EvaluatedCondition.EvaluationStatus.OK;
+import static org.sonar.server.qualitygate.EvaluatedCondition.EvaluationStatus.WARN;
+
+public class EvaluatedConditionTest {
+  private static final Condition CONDITION_1 = new Condition("metricKey", EQUALS, "2", "4", false);
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private EvaluatedCondition underTest = new EvaluatedCondition(CONDITION_1, WARN, "value");
+
+  @Test
+  public void constructor_throws_NPE_if_condition_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("condition can't be null");
+
+    new EvaluatedCondition(null, WARN, "value");
+  }
+
+  @Test
+  public void constructor_throws_NPE_if_EvaluationStatus_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("status can't be null");
+
+    new EvaluatedCondition(CONDITION_1, null, "value");
+  }
+
+  @Test
+  public void constructor_accepts_null_value() {
+    EvaluatedCondition underTest = new EvaluatedCondition(CONDITION_1, WARN, null);
+
+    assertThat(underTest.getValue()).isEmpty();
+  }
+
+  @Test
+  public void verify_getters() {
+    EvaluatedCondition underTest = new EvaluatedCondition(CONDITION_1, WARN, "value");
+
+    assertThat(underTest.getCondition()).isEqualTo(CONDITION_1);
+    assertThat(underTest.getStatus()).isEqualTo(WARN);
+    assertThat(underTest.getValue()).contains("value");
+  }
+
+  @Test
+  public void override_toString() {
+    assertThat(underTest.toString()).isEqualTo("EvaluatedCondition{condition=" +
+      "Condition{metricKey='metricKey', operator=EQUALS, warningThreshold='4', errorThreshold='2', onLeakPeriod=false}, " +
+      "status=WARN, value='value'}");
+  }
+
+  @Test
+  public void toString_does_not_quote_null_value() {
+    EvaluatedCondition underTest = new EvaluatedCondition(CONDITION_1, WARN, null);
+
+    assertThat(underTest.toString()).isEqualTo("EvaluatedCondition{condition=" +
+      "Condition{metricKey='metricKey', operator=EQUALS, warningThreshold='4', errorThreshold='2', onLeakPeriod=false}, " +
+      "status=WARN, value=null}");
+  }
+
+  @Test
+  public void equals_is_based_on_all_fields() {
+    assertThat(underTest).isEqualTo(underTest);
+    assertThat(underTest).isEqualTo(new EvaluatedCondition(CONDITION_1, WARN, "value"));
+    assertThat(underTest).isNotEqualTo(null);
+    assertThat(underTest).isNotEqualTo(new Object());
+    assertThat(underTest).isNotEqualTo(new EvaluatedCondition(new Condition("other_metric", EQUALS, "a", "b", true), WARN, "value"));
+    assertThat(underTest).isNotEqualTo(new EvaluatedCondition(CONDITION_1, OK, "value"));
+    assertThat(underTest).isNotEqualTo(new EvaluatedCondition(CONDITION_1, WARN, null));
+    assertThat(underTest).isNotEqualTo(new EvaluatedCondition(CONDITION_1, WARN, "other_value"));
+  }
+
+  @Test
+  public void hashcode_is_based_on_all_fields() {
+    assertThat(underTest.hashCode()).isEqualTo(underTest.hashCode());
+    assertThat(underTest.hashCode()).isEqualTo(new EvaluatedCondition(CONDITION_1, WARN, "value").hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(null);
+    assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new EvaluatedCondition(new Condition("other_metric", EQUALS, "a", "b", true), WARN, "value").hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new EvaluatedCondition(CONDITION_1, OK, "value").hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new EvaluatedCondition(CONDITION_1, WARN, null).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new EvaluatedCondition(CONDITION_1, WARN, "other_value").hashCode());
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java
new file mode 100644 (file)
index 0000000..b02d5dd
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * 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.qualitygate;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Random;
+import org.apache.commons.lang.RandomStringUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.measures.Metric.Level;
+
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singleton;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.qualitygate.EvaluatedQualityGate.newBuilder;
+
+public class EvaluatedQualityGateTest {
+  private static final String QUALITY_GATE_ID = "qg_id";
+  private static final String QUALITY_GATE_NAME = "qg_name";
+  private static final QualityGate NO_CONDITION_QUALITY_GATE = new QualityGate(QUALITY_GATE_ID, QUALITY_GATE_NAME, emptySet());
+  private static final Condition CONDITION_1 = new Condition("metric_key", Condition.Operator.LESS_THAN, "2", "4", true);
+  private static final Condition CONDITION_2 = new Condition("metric_key_2", Condition.Operator.GREATER_THAN, "6", "12", false);
+  private static final QualityGate ONE_CONDITION_QUALITY_GATE = new QualityGate(QUALITY_GATE_ID, QUALITY_GATE_NAME, singleton(CONDITION_1));
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private final Random random = new Random();
+  private final Level randomStatus = Level.values()[random.nextInt(Level.values().length)];
+  private final EvaluatedCondition.EvaluationStatus randomEvaluationStatus = EvaluatedCondition.EvaluationStatus.values()[random
+    .nextInt(EvaluatedCondition.EvaluationStatus.values().length)];
+  private final String randomValue = random.nextBoolean() ? null : RandomStringUtils.randomAlphanumeric(3);
+
+  private EvaluatedQualityGate.Builder builder = newBuilder();
+
+  @Test
+  public void build_fails_with_NPE_if_status_not_set() {
+    builder.setQualityGate(NO_CONDITION_QUALITY_GATE);
+
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("status can't be null");
+
+    builder.build();
+  }
+
+  @Test
+  public void addCondition_fails_with_NPE_if_condition_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("condition can't be null");
+
+    builder.addCondition(null, EvaluatedCondition.EvaluationStatus.WARN, "a_value");
+  }
+
+  @Test
+  public void addCondition_fails_with_NPE_if_status_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("status can't be null");
+
+    builder.addCondition(new Condition("metric_key", Condition.Operator.LESS_THAN, "2", "4", true), null, "a_value");
+  }
+
+  @Test
+  public void addCondition_accepts_null_value() {
+    builder.addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.NO_VALUE, null);
+
+    assertThat(builder.getEvaluatedConditions())
+      .containsOnly(new EvaluatedCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.NO_VALUE, null));
+  }
+
+  @Test
+  public void getEvaluatedConditions_returns_empty_with_no_condition_added_to_builder() {
+    assertThat(builder.getEvaluatedConditions()).isEmpty();
+  }
+
+  @Test
+  public void build_fails_with_IAE_if_condition_added_and_no_on_QualityGate() {
+    builder.setQualityGate(NO_CONDITION_QUALITY_GATE)
+      .setStatus(randomStatus)
+      .addCondition(CONDITION_1, randomEvaluationStatus, randomValue);
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Evaluation provided for unknown conditions: [" + CONDITION_1 + "]");
+
+    builder.build();
+  }
+
+  @Test
+  public void build_fails_with_IAE_if_condition_is_missing_for_one_defined_in_QualityGate() {
+    builder.setQualityGate(ONE_CONDITION_QUALITY_GATE)
+      .setStatus(randomStatus);
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Evaluation missing for the following conditions: [" + CONDITION_1 + "]");
+
+    builder.build();
+  }
+
+  @Test
+  public void verify_getters() {
+    EvaluatedQualityGate underTest = builder
+      .setQualityGate(ONE_CONDITION_QUALITY_GATE)
+      .setStatus(randomStatus)
+      .addCondition(CONDITION_1, randomEvaluationStatus, randomValue)
+      .build();
+
+    assertThat(underTest.getQualityGate()).isEqualTo(ONE_CONDITION_QUALITY_GATE);
+    assertThat(underTest.getStatus()).isEqualTo(randomStatus);
+    assertThat(underTest.getEvaluatedConditions())
+      .containsOnly(new EvaluatedCondition(CONDITION_1, randomEvaluationStatus, randomValue));
+  }
+
+  @Test
+  public void verify_getters_when_no_condition() {
+    EvaluatedQualityGate underTest = builder
+      .setQualityGate(NO_CONDITION_QUALITY_GATE)
+      .setStatus(randomStatus)
+      .build();
+
+    assertThat(underTest.getQualityGate()).isEqualTo(NO_CONDITION_QUALITY_GATE);
+    assertThat(underTest.getStatus()).isEqualTo(randomStatus);
+    assertThat(underTest.getEvaluatedConditions()).isEmpty();
+  }
+
+  @Test
+  public void verify_getters_when_multiple_conditions() {
+    QualityGate qualityGate = new QualityGate(QUALITY_GATE_ID, QUALITY_GATE_NAME, ImmutableSet.of(CONDITION_1, CONDITION_2));
+    EvaluatedQualityGate underTest = builder
+      .setQualityGate(qualityGate)
+      .setStatus(randomStatus)
+      .addCondition(CONDITION_1, randomEvaluationStatus, randomValue)
+      .addCondition(CONDITION_2, EvaluatedCondition.EvaluationStatus.WARN, "bad")
+      .build();
+
+    assertThat(underTest.getQualityGate()).isEqualTo(qualityGate);
+    assertThat(underTest.getStatus()).isEqualTo(randomStatus);
+    assertThat(underTest.getEvaluatedConditions()).containsOnly(
+      new EvaluatedCondition(CONDITION_1, randomEvaluationStatus, randomValue),
+      new EvaluatedCondition(CONDITION_2, EvaluatedCondition.EvaluationStatus.WARN, "bad"));
+  }
+
+  @Test
+  public void equals_is_based_on_all_fields() {
+    EvaluatedQualityGate.Builder builder = this.builder
+      .setQualityGate(ONE_CONDITION_QUALITY_GATE)
+      .setStatus(Level.WARN)
+      .addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.WARN, "foo");
+
+    EvaluatedQualityGate underTest = builder.build();
+    assertThat(underTest).isEqualTo(builder.build());
+    assertThat(underTest).isNotSameAs(builder.build());
+    assertThat(underTest).isNotEqualTo(null);
+    assertThat(underTest).isNotEqualTo(new Object());
+    assertThat(underTest).isNotEqualTo(builder.setQualityGate(new QualityGate("other_id", QUALITY_GATE_NAME, singleton(CONDITION_1))).build());
+    assertThat(underTest).isNotEqualTo(builder.setQualityGate(ONE_CONDITION_QUALITY_GATE).setStatus(Level.OK).build());
+    assertThat(underTest).isNotEqualTo(newBuilder()
+      .setQualityGate(ONE_CONDITION_QUALITY_GATE)
+      .setStatus(Level.WARN)
+      .addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.OK, "foo")
+      .build());
+  }
+
+  @Test
+  public void hashcode_is_based_on_all_fields() {
+    EvaluatedQualityGate.Builder builder = this.builder
+      .setQualityGate(ONE_CONDITION_QUALITY_GATE)
+      .setStatus(Level.WARN)
+      .addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.WARN, "foo");
+
+    EvaluatedQualityGate underTest = builder.build();
+    assertThat(underTest.hashCode()).isEqualTo(builder.build().hashCode());
+    assertThat(underTest.hashCode()).isNotSameAs(builder.build().hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(null);
+    assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(builder.setQualityGate(new QualityGate("other_id", QUALITY_GATE_NAME, singleton(CONDITION_1))).build().hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(builder.setQualityGate(ONE_CONDITION_QUALITY_GATE).setStatus(Level.OK).build().hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(newBuilder()
+      .setQualityGate(ONE_CONDITION_QUALITY_GATE)
+      .setStatus(Level.WARN)
+      .addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.OK, "foo")
+      .build().hashCode());
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateTest.java
new file mode 100644 (file)
index 0000000..d1fe1dd
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * 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.qualitygate;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static java.util.Collections.emptySet;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class QualityGateTest {
+  private static final String QUALIGATE_ID = "qg_id";
+  private static final String QUALIGATE_NAME = "qg_name";
+  private static final Condition CONDITION_1 = new Condition("m1", Condition.Operator.EQUALS, "1", "2", false);
+  private static final Condition CONDITION_2 = new Condition("m2", Condition.Operator.LESS_THAN, "2", "4", true);
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private QualityGate underTest = new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_1, CONDITION_2));
+
+  @Test
+  public void constructor_fails_with_NPE_if_id_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("id can't be null");
+
+    new QualityGate(null, "name", emptySet());
+  }
+
+  @Test
+  public void constructor_fails_with_NPE_if_name_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("name can't be null");
+
+    new QualityGate("id", null, emptySet());
+  }
+
+  @Test
+  public void constructor_fails_with_NPE_if_conditions_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("conditions can't be null");
+
+    new QualityGate("id", "name", null);
+  }
+
+  @Test
+  public void constructor_fails_with_NPE_if_conditions_contains_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("condition can't be null");
+    Random random = new Random();
+    Set<Condition> conditions = Stream.of(
+      IntStream.range(0, random.nextInt(5))
+        .mapToObj(i -> new Condition("m_before_" + i, Condition.Operator.EQUALS, null, null, false)),
+      Stream.of((Condition) null),
+      IntStream.range(0, random.nextInt(5))
+        .mapToObj(i -> new Condition("m_after_" + i, Condition.Operator.EQUALS, null, null, false)))
+      .flatMap(s -> s)
+      .collect(Collectors.toSet());
+
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("condition can't be null");
+
+    new QualityGate("id", "name", conditions);
+  }
+
+  @Test
+  public void verify_getters() {
+    assertThat(underTest.getId()).isEqualTo(QUALIGATE_ID);
+    assertThat(underTest.getName()).isEqualTo(QUALIGATE_NAME);
+    assertThat(underTest.getConditions()).containsOnly(CONDITION_1, CONDITION_2);
+  }
+
+  @Test
+  public void toString_is_override() {
+    QualityGate underTest = new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_2));
+
+    assertThat(underTest.toString()).isEqualTo("QualityGate{id=qg_id, name='qg_name', conditions=[" +
+      "Condition{metricKey='m2', operator=LESS_THAN, warningThreshold='4', errorThreshold='2', onLeakPeriod=true}" +
+      "]}");
+  }
+
+  @Test
+  public void equals_is_based_on_all_fields() {
+    assertThat(underTest).isEqualTo(underTest);
+    assertThat(underTest).isEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_2, CONDITION_1)));
+    assertThat(underTest).isNotEqualTo(null);
+    assertThat(underTest).isNotEqualTo(new Object());
+    assertThat(underTest).isNotEqualTo(new QualityGate("other_id", QUALIGATE_NAME, ImmutableSet.of(CONDITION_2, CONDITION_1)));
+    assertThat(underTest).isNotEqualTo(new QualityGate(QUALIGATE_ID, "other_name", ImmutableSet.of(CONDITION_2, CONDITION_1)));
+    assertThat(underTest).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, emptySet()));
+    assertThat(underTest).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_1)));
+    assertThat(underTest).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_2)));
+    assertThat(underTest).isNotEqualTo(
+      new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_1, CONDITION_2, new Condition("new", Condition.Operator.GREATER_THAN, "a", "b", false))));
+  }
+
+  @Test
+  public void hashcode_is_based_on_all_fields() {
+    assertThat(underTest.hashCode()).isEqualTo(underTest.hashCode());
+    assertThat(underTest.hashCode()).isEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_2, CONDITION_1)).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(null);
+    assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new QualityGate("other_id", QUALIGATE_NAME, ImmutableSet.of(CONDITION_2, CONDITION_1)).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new QualityGate(QUALIGATE_ID, "other_name", ImmutableSet.of(CONDITION_2, CONDITION_1)).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, emptySet()).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_1)).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_2)).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(
+      new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_1, CONDITION_2, new Condition("new", Condition.Operator.GREATER_THAN, "a", "b", false))).hashCode());
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/util/OkHttpClientProviderTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/util/OkHttpClientProviderTest.java
new file mode 100644 (file)
index 0000000..b6f2f03
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * 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.util;
+
+import java.io.IOException;
+import java.util.Base64;
+import okhttp3.OkHttpClient;
+import okhttp3.Protocol;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.RecordedRequest;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.SonarQubeSide;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.internal.SonarRuntimeImpl;
+import org.sonar.api.utils.Version;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class OkHttpClientProviderTest {
+
+  private MapSettings settings = new MapSettings();
+  private SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.parse("6.2"), SonarQubeSide.SERVER);
+  private final OkHttpClientProvider underTest = new OkHttpClientProvider();
+
+  @Rule
+  public MockWebServer server = new MockWebServer();
+
+  @Test
+  public void get_returns_a_OkHttpClient_with_default_configuration() throws Exception {
+    OkHttpClient client = underTest.provide(settings.asConfig(), runtime);
+
+    assertThat(client.connectTimeoutMillis()).isEqualTo(10_000);
+    assertThat(client.readTimeoutMillis()).isEqualTo(10_000);
+    assertThat(client.proxy()).isNull();
+
+    RecordedRequest recordedRequest = call(client);
+    assertThat(recordedRequest.getHeader("User-Agent")).isEqualTo("SonarQube/6.2");
+    assertThat(recordedRequest.getHeader("Proxy-Authorization")).isNull();
+  }
+
+  @Test
+  public void get_returns_a_OkHttpClient_with_proxy_authentication() throws Exception {
+    settings.setProperty("http.proxyUser", "the-login");
+    settings.setProperty("http.proxyPassword", "the-password");
+
+    OkHttpClient client = underTest.provide(settings.asConfig(), runtime);
+    Response response = new Response.Builder().protocol(Protocol.HTTP_1_1).request(new Request.Builder().url("http://foo").build()).code(407).build();
+    Request request = client.proxyAuthenticator().authenticate(null, response);
+
+    assertThat(request.header("Proxy-Authorization")).isEqualTo("Basic " + Base64.getEncoder().encodeToString("the-login:the-password".getBytes()));
+  }
+
+  @Test
+  public void get_returns_a_singleton() {
+    OkHttpClient client1 = underTest.provide(settings.asConfig(), runtime);
+    OkHttpClient client2 = underTest.provide(settings.asConfig(), runtime);
+    assertThat(client2).isNotNull().isSameAs(client1);
+  }
+
+  private RecordedRequest call(OkHttpClient client) throws IOException, InterruptedException {
+    server.enqueue(new MockResponse().setBody("pong"));
+    client.newCall(new Request.Builder().url(server.url("/ping")).build()).execute();
+
+    return server.takeRequest();
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/AnalysisTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/AnalysisTest.java
new file mode 100644 (file)
index 0000000..5f307d9
--- /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.server.webhook;
+
+import java.util.Date;
+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 AnalysisTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Test
+  public void constructor_throws_NPE_when_uuid_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("uuid must not be null");
+
+    new Analysis(null, 1_990L);
+  }
+
+  @Test
+  public void test_equality() {
+    String uuid = randomAlphanumeric(35);
+    long date = new Random().nextLong();
+    Analysis underTest = new Analysis(uuid, date);
+
+    assertThat(underTest).isEqualTo(underTest);
+    assertThat(underTest.getUuid()).isEqualTo(uuid);
+    assertThat(underTest.getDate()).isEqualTo(new Date(date));
+
+    assertThat(underTest).isNotEqualTo(null);
+    assertThat(underTest).isNotEqualTo(new Analysis(uuid + "1", date));
+    assertThat(underTest).isNotEqualTo(new Analysis(uuid, date + 1_000L));
+  }
+
+  @Test
+  public void test_hashcode() {
+    String uuid = randomAlphanumeric(35);
+    long date = new Random().nextLong();
+    Analysis underTest = new Analysis(uuid, date);
+
+    assertThat(underTest.hashCode()).isEqualTo(underTest.hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new Analysis(uuid + "1", date).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new Analysis(uuid, date + 1_000).hashCode());
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/AsynchronousWebHooksImplTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/AsynchronousWebHooksImplTest.java
new file mode 100644 (file)
index 0000000..b7ca6d7
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * 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.webhook;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDbTester;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.webhook.WebhookDbTester;
+import org.sonar.server.async.AsyncExecution;
+import org.sonar.server.organization.DefaultOrganizationProvider;
+
+import static java.util.Objects.requireNonNull;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.sonar.db.DbTester.create;
+import static org.sonar.db.webhook.WebhookTesting.newWebhook;
+import static org.sonar.server.organization.TestDefaultOrganizationProvider.from;
+
+public class AsynchronousWebHooksImplTest {
+
+  private System2 system2 = mock(System2.class);
+
+  @Rule
+  public DbTester db = create(system2);
+  private WebhookDbTester webhookDbTester = db.webhooks();
+  private ComponentDbTester componentDbTester = db.components();
+  private OrganizationDbTester organizationDbTester = db.organizations();
+  private DefaultOrganizationProvider defaultOrganizationProvider = from(db);
+
+  private static final long NOW = 1_500_000_000_000L;
+
+
+  private final TestWebhookCaller caller = new TestWebhookCaller();
+  private final WebhookDeliveryStorage deliveryStorage = mock(WebhookDeliveryStorage.class);
+  private final WebhookPayload mock = mock(WebhookPayload.class);
+  private final RecordingAsyncExecution asyncExecution = new RecordingAsyncExecution();
+
+  private final WebHooksImpl underTest = new WebHooksImpl(caller, deliveryStorage, asyncExecution, db.getDbClient());
+
+  @Test
+  public void send_global_webhooks() {
+
+    OrganizationDto organizationDto = db.getDefaultOrganization() ;
+    ComponentDto project = componentDbTester.insertPrivateProject(componentDto -> componentDto.setOrganizationUuid(organizationDto.getUuid()));
+    webhookDbTester.insert(newWebhook(organizationDto).setName("First").setUrl("http://url1"));
+    webhookDbTester.insert(newWebhook(organizationDto).setName("Second").setUrl("http://url2"));
+
+    caller.enqueueSuccess(NOW, 200, 1_234);
+    caller.enqueueFailure(NOW, new IOException("Fail to connect"));
+
+    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(project.uuid(), "1", "#1"), () -> mock);
+
+    assertThat(caller.countSent()).isZero();
+    verifyZeroInteractions(deliveryStorage);
+
+    asyncExecution.executeRecorded();
+
+    assertThat(caller.countSent()).isEqualTo(2);
+    verify(deliveryStorage, times(2)).persist(any(WebhookDelivery.class));
+    verify(deliveryStorage).purge(project.uuid());
+  }
+
+  private static class RecordingAsyncExecution implements AsyncExecution {
+    private final List<Runnable> runnableList = new ArrayList<>();
+
+    @Override
+    public void addToQueue(Runnable r) {
+      runnableList.add(requireNonNull(r));
+    }
+
+    public void executeRecorded() {
+      ArrayList<Runnable> runnables = new ArrayList<>(runnableList);
+      runnableList.clear();
+      runnables.forEach(Runnable::run);
+    }
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/BranchTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/BranchTest.java
new file mode 100644 (file)
index 0000000..b935d89
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * 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.webhook;
+
+import java.util.Random;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class BranchTest {
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private Branch underTest = new Branch(true, "b", Branch.Type.SHORT);
+
+  @Test
+  public void constructor_throws_NPE_if_type_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("type can't be null");
+
+    new Branch(new Random().nextBoolean(), "s", null);
+  }
+
+  @Test
+  public void verify_getters() {
+    assertThat(underTest.isMain()).isTrue();
+    assertThat(underTest.getName()).contains("b");
+    assertThat(underTest.getType()).isEqualTo(Branch.Type.SHORT);
+
+
+    Branch underTestWithNull = new Branch(false, null, Branch.Type.LONG);
+    assertThat(underTestWithNull.isMain()).isFalse();
+    assertThat(underTestWithNull.getName()).isEmpty();
+    assertThat(underTestWithNull.getType()).isEqualTo(Branch.Type.LONG);
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/CeTaskTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/CeTaskTest.java
new file mode 100644 (file)
index 0000000..0ed58b7
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * 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.webhook;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class CeTaskTest {
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private CeTask underTest = new CeTask("A", CeTask.Status.SUCCESS);
+
+  @Test
+  public void constructor_throws_NPE_if_id_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("id can't be null");
+
+    new CeTask(null, CeTask.Status.SUCCESS);
+  }
+
+  @Test
+  public void constructor_throws_NPE_if_status_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("status can't be null");
+
+    new CeTask("B", null);
+  }
+
+  @Test
+  public void verify_getters() {
+    assertThat(underTest.getId()).isEqualTo("A");
+    assertThat(underTest.getStatus()).isEqualTo(CeTask.Status.SUCCESS);
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/ProjectAnalysisTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/ProjectAnalysisTest.java
new file mode 100644 (file)
index 0000000..0a45370
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * 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.webhook;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.measures.Metric;
+import org.sonar.server.project.Project;
+import org.sonar.server.qualitygate.EvaluatedQualityGate;
+import org.sonar.server.qualitygate.QualityGate;
+
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.emptySet;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ProjectAnalysisTest {
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private final CeTask ceTask = new CeTask("id", CeTask.Status.SUCCESS);
+  private final Project project = new Project("uuid", "key", "name");
+  private final Analysis analysis = new Analysis("analysis_uuid", 1_500L);
+  private final Branch branch = new Branch(true, "name", Branch.Type.SHORT);
+  private final EvaluatedQualityGate qualityGate = EvaluatedQualityGate.newBuilder()
+    .setQualityGate(new QualityGate("id", "name", emptySet()))
+    .setStatus(Metric.Level.WARN)
+    .build();
+  private final Map<String, String> properties = ImmutableMap.of("a", "b");
+  private ProjectAnalysis underTest = new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, 1L, properties);
+
+  @Test
+  public void constructor_throws_NPE_if_project_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("project can't be null");
+
+    new ProjectAnalysis(null,
+      ceTask,
+      analysis,
+      branch,
+      qualityGate,
+      1L,
+      emptyMap());
+  }
+
+  @Test
+  public void constructor_throws_NPE_if_properties_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("properties can't be null");
+
+    new ProjectAnalysis(project,
+      ceTask,
+      analysis,
+      branch,
+      qualityGate,
+      1L,
+      null);
+  }
+
+  @Test
+  public void verify_getters() {
+    assertThat(underTest.getCeTask().get()).isSameAs(ceTask);
+    assertThat(underTest.getProject()).isSameAs(project);
+    assertThat(underTest.getBranch().get()).isSameAs(branch);
+    assertThat(underTest.getQualityGate().get()).isSameAs(qualityGate);
+    assertThat(underTest.getProperties()).isEqualTo(properties);
+    assertThat(underTest.getAnalysis().get()).isEqualTo(analysis);
+
+    ProjectAnalysis underTestWithNulls = new ProjectAnalysis(project, null, null, null, null, null, emptyMap());
+    assertThat(underTestWithNulls.getCeTask()).isEmpty();
+    assertThat(underTestWithNulls.getBranch()).isEmpty();
+    assertThat(underTestWithNulls.getQualityGate()).isEmpty();
+    assertThat(underTestWithNulls.getProperties()).isEmpty();
+    assertThat(underTestWithNulls.getAnalysis()).isEmpty();
+  }
+
+  @Test
+  public void defines_equals_based_on_all_fields() {
+    assertThat(underTest).isEqualTo(underTest);
+    assertThat(underTest).isEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, 1L, properties));
+    assertThat(underTest).isNotEqualTo(null);
+    assertThat(underTest).isNotEqualTo(new Object());
+    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, new CeTask("2", CeTask.Status.SUCCESS), analysis, branch, qualityGate, 1L, properties));
+    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(new Project("A", "B", "C"), ceTask, analysis, branch, qualityGate, 1L, properties));
+    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(new Project("A", "B", "C"), ceTask, analysis, branch, qualityGate, 1L, properties));
+    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, null, null, null, qualityGate, 1L, properties));
+    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, null, null, qualityGate, 1L, properties));
+    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, new Analysis("foo", 1_500L), null, qualityGate, 1L, properties));
+    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, null, qualityGate, 1L, properties));
+    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, new Branch(false, "B", Branch.Type.SHORT), qualityGate, 1L, properties));
+    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, null, 1L, properties));
+    EvaluatedQualityGate otherQualityGate = EvaluatedQualityGate.newBuilder()
+      .setQualityGate(new QualityGate("A", "B", emptySet()))
+      .setStatus(Metric.Level.WARN)
+      .build();
+    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, otherQualityGate, 1L, properties));
+    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, null, properties));
+    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, 2L, properties));
+    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, 1L, emptyMap()));
+    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, 1L, ImmutableMap.of("A", "B")));
+  }
+
+  @Test
+  public void defines_hashcode_based_on_all_fields() {
+    assertThat(underTest.hashCode()).isEqualTo(underTest.hashCode());
+    assertThat(underTest.hashCode()).isEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, 1L, properties).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, new CeTask("2", CeTask.Status.SUCCESS), analysis, branch, qualityGate, 1L, properties).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(new Project("A", "B", "C"), ceTask, analysis, branch, qualityGate, 1L, properties).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(new Project("A", "B", "C"), ceTask, analysis, branch, qualityGate, 1L, properties).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, null, null, null, qualityGate, 1L, properties).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, null, null, qualityGate, 1L, properties).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, new Analysis("foo", 1_500L), null, qualityGate, 1L, properties).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, null, qualityGate, 1L, properties).hashCode());
+    assertThat(underTest.hashCode())
+      .isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, new Branch(false, "B", Branch.Type.SHORT), qualityGate, 1L, properties).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, null, 1L, properties).hashCode());
+    EvaluatedQualityGate otherQualityGate = EvaluatedQualityGate.newBuilder()
+      .setQualityGate(new QualityGate("A", "B", emptySet()))
+      .setStatus(Metric.Level.WARN)
+      .build();
+    assertThat(underTest.hashCode())
+      .isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, otherQualityGate, 1L, properties).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, this.qualityGate, null, properties).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, this.qualityGate, 2L, properties).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, this.qualityGate, 1L, emptyMap()).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, this.qualityGate, 1L, ImmutableMap.of("B", "C")).hashCode());
+  }
+
+  @Test
+  public void verify_toString() {
+    assertThat(underTest.toString()).isEqualTo(
+      "ProjectAnalysis{project=Project{uuid='uuid', key='key', name='name', description=null}, ceTask=CeTask{id='id', status=SUCCESS}, branch=Branch{main=true, name='name', type=SHORT}, qualityGate=EvaluatedQualityGate{qualityGate=QualityGate{id=id, name='name', conditions=[]}, status=WARN, evaluatedConditions=[]}, updatedAt=1, properties={a=b}, analysis=Analysis{uuid='analysis_uuid', date=1500}}");
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/SynchronousWebHooksImplTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/SynchronousWebHooksImplTest.java
new file mode 100644 (file)
index 0000000..de8d298
--- /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.server.webhook;
+
+import java.io.IOException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.webhook.WebhookDbTester;
+import org.sonar.server.async.AsyncExecution;
+import org.sonar.server.organization.DefaultOrganizationProvider;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.sonar.api.utils.log.LoggerLevel.DEBUG;
+import static org.sonar.db.DbTester.create;
+import static org.sonar.db.webhook.WebhookTesting.newWebhook;
+import static org.sonar.server.organization.TestDefaultOrganizationProvider.from;
+
+public class SynchronousWebHooksImplTest {
+
+  private static final long NOW = 1_500_000_000_000L;
+
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  @Rule
+  public DbTester db = create();
+  private DbClient dbClient = db.getDbClient();
+
+  private WebhookDbTester webhookDbTester = db.webhooks();
+  private ComponentDbTester componentDbTester = db.components();
+  private DefaultOrganizationProvider defaultOrganizationProvider = from(db);
+
+  private final TestWebhookCaller caller = new TestWebhookCaller();
+  private final WebhookDeliveryStorage deliveryStorage = mock(WebhookDeliveryStorage.class);
+  private final WebhookPayload mock = mock(WebhookPayload.class);
+  private final AsyncExecution synchronousAsyncExecution = Runnable::run;
+  private final WebHooksImpl underTest = new WebHooksImpl(caller, deliveryStorage, synchronousAsyncExecution, dbClient);
+
+  @Test
+  public void isEnabled_returns_false_if_no_webhooks() {
+    ComponentDto componentDto = componentDbTester.insertPrivateProject();
+
+    assertThat(underTest.isEnabled(componentDto)).isFalse();
+  }
+
+  @Test
+  public void isEnabled_returns_true_if_one_valid_global_webhook() {
+    ComponentDto componentDto = componentDbTester.insertPrivateProject();
+    webhookDbTester.insert(newWebhook(componentDto).setName("First").setUrl("http://url1"));
+
+    assertThat(underTest.isEnabled(componentDto)).isTrue();
+  }
+
+  @Test
+  public void isEnabled_returns_true_if_one_valid_project_webhook() {
+    String organizationUuid = defaultOrganizationProvider.get().getUuid();
+    ComponentDto componentDto = componentDbTester.insertPrivateProject().setOrganizationUuid(organizationUuid);
+    webhookDbTester.insert(newWebhook(componentDto).setName("First").setUrl("http://url1"));
+
+    assertThat(underTest.isEnabled(componentDto)).isTrue();
+  }
+
+
+  @Test
+  public void do_nothing_if_no_webhooks() {
+    ComponentDto componentDto = componentDbTester.insertPrivateProject().setOrganizationUuid(defaultOrganizationProvider.get().getUuid());
+
+    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(componentDto.uuid(), "1", "#1"), () -> mock);
+
+    assertThat(caller.countSent()).isEqualTo(0);
+    assertThat(logTester.logs(DEBUG)).isEmpty();
+    verifyZeroInteractions(deliveryStorage);
+  }
+
+  @Test
+  public void send_global_webhooks() {
+
+    ComponentDto componentDto = componentDbTester.insertPrivateProject();
+    webhookDbTester.insert(newWebhook(componentDto).setName("First").setUrl("http://url1"));
+    webhookDbTester.insert(newWebhook(componentDto).setName("Second").setUrl("http://url2"));
+    caller.enqueueSuccess(NOW, 200, 1_234);
+    caller.enqueueFailure(NOW, new IOException("Fail to connect"));
+
+    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(componentDto.uuid(), "1", "#1"), () -> mock);
+
+    assertThat(caller.countSent()).isEqualTo(2);
+    assertThat(logTester.logs(DEBUG)).contains("Sent webhook 'First' | url=http://url1 | time=1234ms | status=200");
+    assertThat(logTester.logs(DEBUG)).contains("Failed to send webhook 'Second' | url=http://url2 | message=Fail to connect");
+    verify(deliveryStorage, times(2)).persist(any(WebhookDelivery.class));
+    verify(deliveryStorage).purge(componentDto.uuid());
+
+  }
+
+  @Test
+  public void send_project_webhooks() {
+
+    String organizationUuid = defaultOrganizationProvider.get().getUuid();
+    ComponentDto componentDto = componentDbTester.insertPrivateProject().setOrganizationUuid(organizationUuid);
+    webhookDbTester.insert(newWebhook(componentDto).setName("First").setUrl("http://url1"));
+    caller.enqueueSuccess(NOW, 200, 1_234);
+
+    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(componentDto.uuid(), "1", "#1"), () -> mock);
+
+    assertThat(caller.countSent()).isEqualTo(1);
+    assertThat(logTester.logs(DEBUG)).contains("Sent webhook 'First' | url=http://url1 | time=1234ms | status=200");
+    verify(deliveryStorage).persist(any(WebhookDelivery.class));
+    verify(deliveryStorage).purge(componentDto.uuid());
+
+  }
+
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/TestWebhookCaller.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/TestWebhookCaller.java
new file mode 100644 (file)
index 0000000..0634f16
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * 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.webhook;
+
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.annotation.Nullable;
+
+import static java.util.Objects.requireNonNull;
+
+public class TestWebhookCaller implements WebhookCaller {
+
+  private final Queue<Item> deliveries = new LinkedList<>();
+  private final AtomicInteger countSent = new AtomicInteger(0);
+
+  public TestWebhookCaller enqueueSuccess(long at, int httpCode, int durationMs) {
+    deliveries.add(new Item(at, httpCode, durationMs, null));
+    return this;
+  }
+
+  public TestWebhookCaller enqueueFailure(long at, Throwable t) {
+    deliveries.add(new Item(at, null, null, t));
+    return this;
+  }
+
+  @Override
+  public WebhookDelivery call(Webhook webhook, WebhookPayload payload) {
+    Item item = requireNonNull(deliveries.poll(), "Queue is empty");
+    countSent.incrementAndGet();
+    return new WebhookDelivery.Builder()
+      .setAt(item.at)
+      .setHttpStatus(item.httpCode)
+      .setDurationInMs(item.durationMs)
+      .setError(item.throwable)
+      .setPayload(payload)
+      .setWebhook(webhook)
+      .build();
+  }
+
+  public int countSent() {
+    return countSent.get();
+  }
+
+  private static class Item {
+    final long at;
+    final Integer httpCode;
+    final Integer durationMs;
+    final Throwable throwable;
+
+    Item(long at, @Nullable Integer httpCode, @Nullable Integer durationMs, @Nullable Throwable throwable) {
+      this.at = at;
+      this.httpCode = httpCode;
+      this.durationMs = durationMs;
+      this.throwable = throwable;
+    }
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookCallerImplTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookCallerImplTest.java
new file mode 100644 (file)
index 0000000..60ac24e
--- /dev/null
@@ -0,0 +1,218 @@
+/*
+ * 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.webhook;
+
+import okhttp3.Credentials;
+import okhttp3.HttpUrl;
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.RecordedRequest;
+import org.apache.commons.lang.RandomStringUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.DisableOnDebug;
+import org.junit.rules.TestRule;
+import org.junit.rules.Timeout;
+import org.sonar.api.SonarQubeSide;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.internal.SonarRuntimeImpl;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.Version;
+import org.sonar.api.utils.internal.TestSystem2;
+import org.sonar.server.util.OkHttpClientProvider;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class WebhookCallerImplTest {
+
+  private static final long NOW = 1_500_000_000_000L;
+  private static final String PROJECT_UUID = "P_UUID1";
+  private static final String WEBHOOK_UUID = "WH_UUID1";
+  private static final String CE_TASK_UUID = "CE_UUID1";
+  private static final String SOME_JSON = "{\"payload\": {}}";
+  private static final WebhookPayload PAYLOAD = new WebhookPayload("P1", SOME_JSON);
+
+  @Rule
+  public MockWebServer server = new MockWebServer();
+
+  @Rule
+  public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
+
+  private System2 system = new TestSystem2().setNow(NOW);
+
+  @Test
+  public void send_posts_payload_to_http_server() throws Exception {
+    Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", server.url("/ping").toString());
+
+    server.enqueue(new MockResponse().setBody("pong").setResponseCode(201));
+    WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
+
+    assertThat(delivery.getHttpStatus()).hasValue(201);
+    assertThat(delivery.getWebhook().getUuid()).isEqualTo(WEBHOOK_UUID);
+    assertThat(delivery.getDurationInMs().get()).isGreaterThanOrEqualTo(0);
+    assertThat(delivery.getError()).isEmpty();
+    assertThat(delivery.getAt()).isEqualTo(NOW);
+    assertThat(delivery.getWebhook()).isSameAs(webhook);
+    assertThat(delivery.getPayload()).isSameAs(PAYLOAD);
+
+    RecordedRequest recordedRequest = server.takeRequest();
+    assertThat(recordedRequest.getMethod()).isEqualTo("POST");
+    assertThat(recordedRequest.getPath()).isEqualTo("/ping");
+    assertThat(recordedRequest.getBody().readUtf8()).isEqualTo(PAYLOAD.getJson());
+    assertThat(recordedRequest.getHeader("User-Agent")).isEqualTo("SonarQube/6.2");
+    assertThat(recordedRequest.getHeader("Content-Type")).isEqualTo("application/json; charset=utf-8");
+    assertThat(recordedRequest.getHeader("X-SonarQube-Project")).isEqualTo(PAYLOAD.getProjectKey());
+  }
+
+  @Test
+  public void silently_catch_error_when_external_server_does_not_answer() throws Exception {
+    Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", server.url("/ping").toString());
+
+    server.shutdown();
+    WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
+
+    assertThat(delivery.getHttpStatus()).isEmpty();
+    assertThat(delivery.getDurationInMs().get()).isGreaterThanOrEqualTo(0);
+    // message can be "Connection refused" or "connect timed out"
+    assertThat(delivery.getErrorMessage().get()).matches("(.*Connection refused.*)|(.*connect timed out.*)");
+    assertThat(delivery.getAt()).isEqualTo(NOW);
+    assertThat(delivery.getWebhook()).isSameAs(webhook);
+    assertThat(delivery.getPayload()).isSameAs(PAYLOAD);
+  }
+
+  @Test
+  public void silently_catch_error_when_url_is_incorrect() {
+    Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", "this_is_not_an_url");
+
+    WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
+
+    assertThat(delivery.getHttpStatus()).isEmpty();
+    assertThat(delivery.getDurationInMs().get()).isGreaterThanOrEqualTo(0);
+    assertThat(delivery.getError().get()).isInstanceOf(IllegalArgumentException.class);
+    assertThat(delivery.getErrorMessage().get()).isEqualTo("Webhook URL is not valid: this_is_not_an_url");
+    assertThat(delivery.getAt()).isEqualTo(NOW);
+    assertThat(delivery.getWebhook()).isSameAs(webhook);
+    assertThat(delivery.getPayload()).isSameAs(PAYLOAD);
+  }
+
+  /**
+   * SONAR-8799
+   */
+  @Test
+  public void redirects_should_be_followed_with_POST_method() throws Exception {
+    Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", server.url("/redirect").toString());
+
+    // /redirect redirects to /target
+    server.enqueue(new MockResponse().setResponseCode(307).setHeader("Location", server.url("target")));
+    server.enqueue(new MockResponse().setResponseCode(200));
+
+    WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
+
+    assertThat(delivery.getHttpStatus().get()).isEqualTo(200);
+    assertThat(delivery.getDurationInMs().get()).isGreaterThanOrEqualTo(0);
+    assertThat(delivery.getError()).isEmpty();
+    assertThat(delivery.getAt()).isEqualTo(NOW);
+    assertThat(delivery.getWebhook()).isSameAs(webhook);
+    assertThat(delivery.getPayload()).isSameAs(PAYLOAD);
+
+    takeAndVerifyPostRequest("/redirect");
+    takeAndVerifyPostRequest("/target");
+  }
+
+  @Test
+  public void credentials_are_propagated_to_POST_redirects() throws Exception {
+    HttpUrl url = server.url("/redirect").newBuilder().username("theLogin").password("thePassword").build();
+    Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", url.toString());
+
+    // /redirect redirects to /target
+    server.enqueue(new MockResponse().setResponseCode(307).setHeader("Location", server.url("target")));
+    server.enqueue(new MockResponse().setResponseCode(200));
+
+    WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
+
+    assertThat(delivery.getHttpStatus().get()).isEqualTo(200);
+
+    RecordedRequest redirectedRequest = takeAndVerifyPostRequest("/redirect");
+    assertThat(redirectedRequest.getHeader("Authorization")).isEqualTo(Credentials.basic(url.username(), url.password()));
+
+    RecordedRequest targetRequest = takeAndVerifyPostRequest("/target");
+    assertThat(targetRequest.getHeader("Authorization")).isEqualTo(Credentials.basic(url.username(), url.password()));
+  }
+
+  @Test
+  public void redirects_throws_ISE_if_header_Location_is_missing() {
+    HttpUrl url = server.url("/redirect");
+    Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", url.toString());
+
+    server.enqueue(new MockResponse().setResponseCode(307));
+
+    WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
+
+    Throwable error = delivery.getError().get();
+    assertThat(error)
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Missing HTTP header 'Location' in redirect of " + url);
+  }
+
+  @Test
+  public void redirects_throws_ISE_if_header_Location_does_not_relate_to_a_supported_protocol() {
+    HttpUrl url = server.url("/redirect");
+    Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", url.toString());
+
+    server.enqueue(new MockResponse().setResponseCode(307).setHeader("Location", "ftp://foo"));
+
+    WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
+
+    Throwable error = delivery.getError().get();
+    assertThat(error)
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Unsupported protocol in redirect of " + url + " to ftp://foo");
+  }
+
+  @Test
+  public void send_basic_authentication_header_if_url_contains_credentials() throws Exception {
+    HttpUrl url = server.url("/ping").newBuilder().username("theLogin").password("thePassword").build();
+    Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", url.toString());
+    server.enqueue(new MockResponse().setBody("pong"));
+
+    WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
+
+    assertThat(delivery.getWebhook().getUrl())
+      .isEqualTo(url.toString())
+      .contains("://theLogin:thePassword@");
+    RecordedRequest recordedRequest = takeAndVerifyPostRequest("/ping");
+    assertThat(recordedRequest.getHeader("Authorization")).isEqualTo(Credentials.basic(url.username(), url.password()));
+  }
+
+  private RecordedRequest takeAndVerifyPostRequest(String expectedPath) throws Exception {
+    RecordedRequest request = server.takeRequest();
+
+    assertThat(request.getMethod()).isEqualTo("POST");
+    assertThat(request.getPath()).isEqualTo(expectedPath);
+    assertThat(request.getHeader("User-Agent")).isEqualTo("SonarQube/6.2");
+    return request;
+  }
+
+  private WebhookCaller newSender() {
+    SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.parse("6.2"), SonarQubeSide.SERVER);
+    return new WebhookCallerImpl(system, new OkHttpClientProvider().provide(new MapSettings().asConfig(), runtime));
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookDeliveryStorageTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookDeliveryStorageTest.java
new file mode 100644 (file)
index 0000000..7273b94
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * 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.webhook;
+
+import java.io.IOException;
+import org.apache.commons.lang.RandomStringUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.webhook.WebhookDbTesting;
+import org.sonar.db.webhook.WebhookDeliveryDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.db.webhook.WebhookDbTesting.selectAllDeliveryUuids;
+
+public class WebhookDeliveryStorageTest {
+
+  private static final String DELIVERY_UUID = "abcde1234";
+  private static final long NOW = 1_500_000_000_000L;
+  private static final long TWO_MONTHS_AGO = NOW - 60L * 24 * 60 * 60 * 1000;
+  private static final long TWO_WEEKS_AGO = NOW - 14L * 24 * 60 * 60 * 1000;
+
+  private final System2 system = mock(System2.class);
+
+  @Rule
+  public final DbTester dbTester = DbTester.create(system).setDisableDefaultOrganization(true);
+
+  private DbClient dbClient = dbTester.getDbClient();
+  private DbSession dbSession = dbTester.getSession();
+  private UuidFactory uuidFactory = mock(UuidFactory.class);
+  private WebhookDeliveryStorage underTest = new WebhookDeliveryStorage(dbClient, system, uuidFactory);
+
+  @Test
+  public void persist_generates_uuid_then_inserts_record() {
+    when(uuidFactory.create()).thenReturn(DELIVERY_UUID);
+    WebhookDelivery delivery = newBuilderTemplate().build();
+
+    underTest.persist(delivery);
+
+    WebhookDeliveryDto dto = dbClient.webhookDeliveryDao().selectByUuid(dbSession, DELIVERY_UUID).get();
+    assertThat(dto.getUuid()).isEqualTo(DELIVERY_UUID);
+    assertThat(dto.getWebhookUuid()).isEqualTo("WEBHOOK_UUID_1");
+    assertThat(dto.getComponentUuid()).isEqualTo(delivery.getWebhook().getComponentUuid());
+    assertThat(dto.getCeTaskUuid()).isEqualTo(delivery.getWebhook().getCeTaskUuid().get());
+    assertThat(dto.getName()).isEqualTo(delivery.getWebhook().getName());
+    assertThat(dto.getUrl()).isEqualTo(delivery.getWebhook().getUrl());
+    assertThat(dto.getCreatedAt()).isEqualTo(delivery.getAt());
+    assertThat(dto.getHttpStatus()).isEqualTo(delivery.getHttpStatus().get());
+    assertThat(dto.getDurationMs()).isEqualTo(delivery.getDurationInMs().get());
+    assertThat(dto.getPayload()).isEqualTo(delivery.getPayload().getJson());
+    assertThat(dto.getErrorStacktrace()).isNull();
+  }
+
+  @Test
+  public void persist_error_stacktrace() {
+    when(uuidFactory.create()).thenReturn(DELIVERY_UUID);
+    WebhookDelivery delivery = newBuilderTemplate()
+      .setError(new IOException("fail to connect"))
+      .build();
+
+    underTest.persist(delivery);
+
+    WebhookDeliveryDto dto = dbClient.webhookDeliveryDao().selectByUuid(dbSession, DELIVERY_UUID).get();
+    assertThat(dto.getErrorStacktrace()).contains("java.io.IOException", "fail to connect");
+  }
+
+  @Test
+  public void purge_deletes_records_older_than_one_month_on_the_project() {
+    when(system.now()).thenReturn(NOW);
+    dbClient.webhookDeliveryDao().insert(dbSession, newDto("D1", "PROJECT_1", TWO_MONTHS_AGO));
+    dbClient.webhookDeliveryDao().insert(dbSession, newDto("D2", "PROJECT_1", TWO_WEEKS_AGO));
+    dbClient.webhookDeliveryDao().insert(dbSession, newDto("D3", "PROJECT_2", TWO_MONTHS_AGO));
+    dbSession.commit();
+
+    underTest.purge("PROJECT_1");
+
+    // do not purge another project PROJECT_2
+    assertThat(selectAllDeliveryUuids(dbTester, dbSession)).containsOnly("D2", "D3");
+  }
+
+  private static WebhookDelivery.Builder newBuilderTemplate() {
+    return new WebhookDelivery.Builder()
+      .setWebhook(new Webhook("WEBHOOK_UUID_1", "COMPONENT1", "TASK1", RandomStringUtils.randomAlphanumeric(40),"Jenkins", "http://jenkins"))
+      .setPayload(new WebhookPayload("my-project", "{json}"))
+      .setAt(1_000_000L)
+      .setHttpStatus(200)
+      .setDurationInMs(1_000);
+  }
+
+  private static WebhookDeliveryDto newDto(String uuid, String componentUuid, long at) {
+    return WebhookDbTesting.newDto()
+      .setUuid(uuid)
+      .setComponentUuid(componentUuid)
+      .setCreatedAt(at);
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookDeliveryTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookDeliveryTest.java
new file mode 100644 (file)
index 0000000..1e627f5
--- /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.server.webhook;
+
+import java.io.IOException;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+
+public class WebhookDeliveryTest {
+
+  @Test
+  public void isSuccess_returns_false_if_failed_to_send_http_request() {
+    WebhookDelivery delivery = newBuilderTemplate()
+      .setError(new IOException("Fail to connect"))
+      .build();
+
+    assertThat(delivery.isSuccess()).isFalse();
+  }
+
+  @Test
+  public void isSuccess_returns_false_if_http_response_returns_error_status() {
+    WebhookDelivery delivery = newBuilderTemplate()
+      .setHttpStatus(404)
+      .build();
+
+    assertThat(delivery.isSuccess()).isFalse();
+  }
+
+  @Test
+  public void isSuccess_returns_true_if_http_response_returns_2xx_code() {
+    WebhookDelivery delivery = newBuilderTemplate()
+      .setHttpStatus(204)
+      .build();
+
+    assertThat(delivery.isSuccess()).isTrue();
+  }
+
+  @Test
+  public void getErrorMessage_returns_empty_if_no_error() {
+    WebhookDelivery delivery = newBuilderTemplate().build();
+
+    assertThat(delivery.getErrorMessage()).isEmpty();
+  }
+
+  @Test
+  public void getErrorMessage_returns_root_cause_message_if_error() {
+    Exception rootCause = new IOException("fail to connect");
+    Exception cause = new IOException("nested", rootCause);
+    WebhookDelivery delivery = newBuilderTemplate()
+      .setError(cause)
+      .build();
+
+    assertThat(delivery.getErrorMessage().get()).isEqualTo("fail to connect");
+  }
+
+  private static WebhookDelivery.Builder newBuilderTemplate() {
+    return new WebhookDelivery.Builder()
+      .setWebhook(mock(Webhook.class))
+      .setPayload(mock(WebhookPayload.class))
+      .setAt(1_000L);
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookModuleTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookModuleTest.java
new file mode 100644 (file)
index 0000000..58b17bf
--- /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.webhook;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.core.platform.ComponentContainer;
+import org.sonar.server.webhook.WebhookModule;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER;
+
+public class WebhookModuleTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private WebhookModule underTest = new WebhookModule();
+
+  @Test
+  public void verify_count_of_added_components() {
+    ComponentContainer container = new ComponentContainer();
+
+    underTest.configure(container);
+
+    assertThat(container.size()).isEqualTo(4 + COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER);
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookPayloadFactoryImplTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookPayloadFactoryImplTest.java
new file mode 100644 (file)
index 0000000..a494b9a
--- /dev/null
@@ -0,0 +1,329 @@
+/*
+ * 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.webhook;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.platform.Server;
+import org.sonar.api.utils.System2;
+import org.sonar.server.project.Project;
+import org.sonar.server.qualitygate.Condition;
+import org.sonar.server.qualitygate.EvaluatedCondition;
+import org.sonar.server.qualitygate.EvaluatedQualityGate;
+import org.sonar.server.qualitygate.QualityGate;
+
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singleton;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.test.JsonAssert.assertJson;
+
+public class WebhookPayloadFactoryImplTest {
+
+  private static final String PROJECT_KEY = "P1";
+
+  private Server server = mock(Server.class);
+  private System2 system2 = mock(System2.class);
+  private WebhookPayloadFactory underTest = new WebhookPayloadFactoryImpl(server, system2);
+
+  @Before
+  public void setUp() throws Exception {
+    when(server.getPublicRootUrl()).thenReturn("http://foo");
+    when(system2.now()).thenReturn(1_500_999L);
+  }
+
+  @Test
+  public void create_payload_for_successful_analysis() {
+    CeTask task = new CeTask("#1", CeTask.Status.SUCCESS);
+    Condition condition = new Condition("coverage", Condition.Operator.GREATER_THAN, "70.0", "75.0", true);
+    EvaluatedQualityGate gate = EvaluatedQualityGate.newBuilder()
+      .setQualityGate(new QualityGate("G1", "Gate One", singleton(condition)))
+      .setStatus(Metric.Level.WARN)
+      .addCondition(condition, EvaluatedCondition.EvaluationStatus.WARN, "74.0")
+      .build();
+    ProjectAnalysis analysis = newAnalysis(task, gate, null, 1_500_000_000_000L, emptyMap());
+
+    WebhookPayload payload = underTest.create(analysis);
+    assertThat(payload.getProjectKey()).isEqualTo(PROJECT_KEY);
+    assertJson(payload.getJson())
+      .isSimilarTo("{" +
+        "  \"serverUrl\": \"http://foo\"," +
+        "  \"taskId\": \"#1\"," +
+        "  \"status\": \"SUCCESS\"," +
+        "  \"analysedAt\": \"2017-07-14T04:40:00+0200\"," +
+        "  \"changedAt\": \"2017-07-14T04:40:00+0200\"," +
+        "  \"project\": {" +
+        "    \"key\": \"P1\"," +
+        "    \"name\": \"Project One\"," +
+        "    \"url\": \"http://foo/dashboard?id=P1\"" +
+        "  }," +
+        "  \"qualityGate\": {" +
+        "    \"name\": \"Gate One\"," +
+        "    \"status\": \"WARN\"," +
+        "    \"conditions\": [" +
+        "      {" +
+        "        \"metric\": \"coverage\"," +
+        "        \"operator\": \"GREATER_THAN\"," +
+        "        \"value\": \"74.0\"," +
+        "        \"status\": \"WARN\"," +
+        "        \"onLeakPeriod\": true," +
+        "        \"errorThreshold\": \"70.0\"," +
+        "        \"warningThreshold\": \"75.0\"" +
+        "      }" +
+        "    ]" +
+        "  }," +
+        "  \"properties\": {" +
+        "  }" +
+        "}");
+  }
+
+  @Test
+  public void create_payload_with_gate_conditions_without_value() {
+    CeTask task = new CeTask("#1", CeTask.Status.SUCCESS);
+
+    Condition condition = new Condition("coverage", Condition.Operator.GREATER_THAN, "70.0", "75.0", false);
+    EvaluatedQualityGate gate = EvaluatedQualityGate.newBuilder()
+      .setQualityGate(new QualityGate("G1", "Gate One", singleton(condition)))
+      .setStatus(Metric.Level.WARN)
+      .addCondition(condition, EvaluatedCondition.EvaluationStatus.NO_VALUE, null)
+      .build();
+    ProjectAnalysis analysis = newAnalysis(task, gate, null, 1_500_000_000_000L, emptyMap());
+
+    WebhookPayload payload = underTest.create(analysis);
+    assertThat(payload.getProjectKey()).isEqualTo(PROJECT_KEY);
+    assertJson(payload.getJson())
+      .isSimilarTo("{" +
+        "  \"serverUrl\": \"http://foo\"," +
+        "  \"taskId\": \"#1\"," +
+        "  \"status\": \"SUCCESS\"," +
+        "  \"analysedAt\": \"2017-07-14T04:40:00+0200\"," +
+        "  \"changedAt\": \"2017-07-14T04:40:00+0200\"," +
+        "  \"project\": {" +
+        "    \"key\": \"P1\"," +
+        "    \"name\": \"Project One\"," +
+        "    \"url\": \"http://foo/dashboard?id=P1\"" +
+        "  }," +
+        "  \"qualityGate\": {" +
+        "    \"name\": \"Gate One\"," +
+        "    \"status\": \"WARN\"," +
+        "    \"conditions\": [" +
+        "      {" +
+        "        \"metric\": \"coverage\"," +
+        "        \"operator\": \"GREATER_THAN\"," +
+        "        \"status\": \"NO_VALUE\"," +
+        "        \"onLeakPeriod\": false," +
+        "        \"errorThreshold\": \"70.0\"," +
+        "        \"warningThreshold\": \"75.0\"" +
+        "      }" +
+        "    ]" +
+        "  }" +
+        "}");
+  }
+
+  @Test
+  public void create_payload_with_analysis_properties() {
+    CeTask task = new CeTask("#1", CeTask.Status.SUCCESS);
+    EvaluatedQualityGate gate = EvaluatedQualityGate.newBuilder()
+      .setQualityGate(new QualityGate("G1", "Gate One", emptySet()))
+      .setStatus(Metric.Level.WARN)
+      .build();
+    Map<String, String> scannerProperties = ImmutableMap.of(
+      "sonar.analysis.revision", "ab45d24",
+      "sonar.analysis.buildNumber", "B123",
+      "not.prefixed.with.sonar.analysis", "should be ignored",
+      "ignored", "should be ignored too");
+    ProjectAnalysis analysis = newAnalysis(task, gate, null, 1_500_000_000_000L, scannerProperties);
+
+    WebhookPayload payload = underTest.create(analysis);
+    assertJson(payload.getJson())
+      .isSimilarTo("{" +
+        "  \"serverUrl\": \"http://foo\"," +
+        "  \"taskId\": \"#1\"," +
+        "  \"status\": \"SUCCESS\"," +
+        "  \"analysedAt\": \"2017-07-14T04:40:00+0200\"," +
+        "  \"changedAt\": \"2017-07-14T04:40:00+0200\"," +
+        "  \"project\": {" +
+        "    \"key\": \"P1\"," +
+        "    \"name\": \"Project One\"," +
+        "    \"url\": \"http://foo/dashboard?id=P1\"" +
+        "  }," +
+        "  \"qualityGate\": {" +
+        "    \"name\": \"Gate One\"," +
+        "    \"status\": \"WARN\"," +
+        "    \"conditions\": [" +
+        "    ]" +
+        "  }," +
+        "  \"properties\": {" +
+        "    \"sonar.analysis.revision\": \"ab45d24\"," +
+        "    \"sonar.analysis.buildNumber\": \"B123\"" +
+        "  }" +
+        "}");
+    assertThat(payload.getJson())
+      .doesNotContain("not.prefixed.with.sonar.analysis")
+      .doesNotContain("ignored");
+  }
+
+  @Test
+  public void create_payload_for_failed_analysis() {
+    CeTask ceTask = new CeTask("#1", CeTask.Status.FAILED);
+    ProjectAnalysis analysis = newAnalysis(ceTask, null, null, 1_500_000_000_000L, emptyMap());
+
+    WebhookPayload payload = underTest.create(analysis);
+
+    assertThat(payload.getProjectKey()).isEqualTo(PROJECT_KEY);
+    assertJson(payload.getJson())
+      .isSimilarTo("{" +
+        "  \"serverUrl\": \"http://foo\"," +
+        "  \"taskId\": \"#1\"," +
+        "  \"status\": \"FAILED\"," +
+        "  \"changedAt\": \"2017-07-14T04:40:00+0200\"," +
+        "  \"project\": {" +
+        "    \"key\": \"P1\"," +
+        "    \"name\": \"Project One\"," +
+        "    \"url\": \"http://foo/dashboard?id=P1\"" +
+        "  }," +
+        "  \"properties\": {" +
+        "  }" +
+        "}");
+  }
+
+  @Test
+  public void create_payload_for_no_analysis_date() {
+    CeTask ceTask = new CeTask("#1", CeTask.Status.FAILED);
+    ProjectAnalysis analysis = newAnalysis(ceTask, null, null, null, emptyMap());
+
+    WebhookPayload payload = underTest.create(analysis);
+
+    assertThat(payload.getProjectKey()).isEqualTo(PROJECT_KEY);
+    assertJson(payload.getJson())
+      .isSimilarTo("{" +
+        "  \"serverUrl\": \"http://foo\"," +
+        "  \"taskId\": \"#1\"," +
+        "  \"status\": \"FAILED\"," +
+        "  \"changedAt\": \"1970-01-01T01:25:00+0100\"," +
+        "  \"project\": {" +
+        "    \"key\": \"P1\"," +
+        "    \"name\": \"Project One\"" +
+        "  }," +
+        "  \"properties\": {" +
+        "  }" +
+        "}");
+  }
+
+  @Test
+  public void create_payload_on_short_branch() {
+    CeTask task = new CeTask("#1", CeTask.Status.SUCCESS);
+    ProjectAnalysis analysis = newAnalysis(task, null, new Branch(false, "feature/foo", Branch.Type.SHORT), 1_500_000_000_000L, emptyMap());
+
+    WebhookPayload payload = underTest.create(analysis);
+    assertJson(payload.getJson())
+      .isSimilarTo("{" +
+        "\"branch\": {" +
+        "  \"name\": \"feature/foo\"," +
+        "  \"type\": \"SHORT\"," +
+        "  \"isMain\": false," +
+        "  \"url\": \"http://foo/project/issues?branch=feature%2Ffoo&id=P1&resolved=false\"" +
+        "}" +
+        "}");
+  }
+
+  @Test
+  public void create_payload_on_pull_request() {
+    CeTask task = new CeTask("#1", CeTask.Status.SUCCESS);
+    ProjectAnalysis analysis = newAnalysis(task, null, new Branch(false, "pr/foo", Branch.Type.PULL_REQUEST), 1_500_000_000_000L, emptyMap());
+
+    WebhookPayload payload = underTest.create(analysis);
+    assertJson(payload.getJson())
+      .isSimilarTo("{" +
+        "\"branch\": {" +
+        "  \"name\": \"pr/foo\"," +
+        "  \"type\": \"PULL_REQUEST\"," +
+        "  \"isMain\": false," +
+        "  \"url\": \"http://foo/project/issues?pullRequest=pr%2Ffoo&id=P1&resolved=false\"" +
+        "}" +
+        "}");
+  }
+
+  @Test
+  public void create_without_ce_task() {
+    ProjectAnalysis analysis = newAnalysis(null, null, null, null, emptyMap());
+
+    WebhookPayload payload = underTest.create(analysis);
+    String json = payload.getJson();
+    assertThat(json).doesNotContain("taskId");
+    assertJson(json)
+      .isSimilarTo("{" +
+        "  \"serverUrl\": \"http://foo\"," +
+        "  \"status\": \"SUCCESS\"," +
+        "  \"changedAt\": \"1970-01-01T01:25:00+0100\"," +
+        "  \"project\": {" +
+        "    \"key\": \"P1\"," +
+        "    \"name\": \"Project One\"" +
+        "  }," +
+        "  \"properties\": {" +
+        "  }" +
+        "}");
+  }
+
+  @Test
+  public void create_payload_on_long_branch() {
+    CeTask task = new CeTask("#1", CeTask.Status.SUCCESS);
+    ProjectAnalysis analysis = newAnalysis(task, null, new Branch(false, "feature/foo", Branch.Type.LONG), 1_500_000_000_000L, emptyMap());
+
+    WebhookPayload payload = underTest.create(analysis);
+    assertJson(payload.getJson())
+      .isSimilarTo("{" +
+        "\"branch\": {" +
+        "  \"name\": \"feature/foo\"" +
+        "  \"type\": \"LONG\"" +
+        "  \"isMain\": false," +
+        "  \"url\": \"http://foo/dashboard?branch=feature%2Ffoo&id=P1\"" +
+        "}" +
+        "}");
+  }
+
+  @Test
+  public void create_payload_on_main_branch_without_name() {
+    CeTask task = new CeTask("#1", CeTask.Status.SUCCESS);
+    ProjectAnalysis analysis = newAnalysis(task, null, new Branch(true, null, Branch.Type.LONG), 1_500_000_000_000L, emptyMap());
+
+    WebhookPayload payload = underTest.create(analysis);
+    assertJson(payload.getJson())
+      .isSimilarTo("{" +
+        "\"branch\": {" +
+        "  \"type\": \"LONG\"" +
+        "  \"isMain\": true," +
+        "  \"url\": \"http://foo/dashboard?id=P1\"" +
+        "}" +
+        "}");
+  }
+
+  private static ProjectAnalysis newAnalysis(@Nullable CeTask task, @Nullable EvaluatedQualityGate gate,
+    @Nullable Branch branch, @Nullable Long analysisDate, Map<String, String> scannerProperties) {
+    return new ProjectAnalysis(new Project("P1_UUID", PROJECT_KEY, "Project One"), task, analysisDate == null ? null : new Analysis("A_UUID1", analysisDate), branch,
+      gate, analysisDate, scannerProperties);
+  }
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookTest.java
new file mode 100644 (file)
index 0000000..64778c0
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.webhook;
+
+import java.util.Optional;
+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 WebhookTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Test
+  public void constructor_with_null_componentUuid_should_throw_NPE() {
+    expectedException.expect(NullPointerException.class);
+
+    new Webhook(randomAlphanumeric(40), null, null, null, randomAlphanumeric(10), randomAlphanumeric(10));
+  }
+
+  @Test
+  public void constructor_with_null_name_should_throw_NPE() {
+    expectedException.expect(NullPointerException.class);
+
+    new Webhook(randomAlphanumeric(40), randomAlphanumeric(10), null, null, null, randomAlphanumeric(10));
+  }
+
+  @Test
+  public void constructor_with_null_url_should_throw_NPE() {
+    expectedException.expect(NullPointerException.class);
+
+    new Webhook(randomAlphanumeric(40), randomAlphanumeric(10), null, null, randomAlphanumeric(10), null);
+  }
+
+  @Test
+  public void constructor_with_null_ceTaskUuid_or_analysisUuidurl_should_return_Optional_empty() {
+    String componentUuid = randomAlphanumeric(10);
+    String name = randomAlphanumeric(10);
+    String url = randomAlphanumeric(10);
+    Webhook underTest = new Webhook(randomAlphanumeric(40), componentUuid, null, null, name, url);
+
+    assertThat(underTest.getComponentUuid()).isEqualTo(componentUuid);
+    assertThat(underTest.getName()).isEqualTo(name);
+    assertThat(underTest.getUrl()).isEqualTo(url);
+    assertThat(underTest.getCeTaskUuid()).isEqualTo(Optional.empty());
+    assertThat(underTest.getAnalysisUuid()).isEqualTo(Optional.empty());
+
+    String ceTaskUuid = randomAlphanumeric(10);
+    String analysisUuid = randomAlphanumeric(10);
+    underTest = new Webhook(randomAlphanumeric(40), componentUuid, ceTaskUuid, analysisUuid, name, url);
+    assertThat(underTest.getComponentUuid()).isEqualTo(componentUuid);
+    assertThat(underTest.getName()).isEqualTo(name);
+    assertThat(underTest.getUrl()).isEqualTo(url);
+    assertThat(underTest.getCeTaskUuid().get()).isEqualTo(ceTaskUuid);
+    assertThat(underTest.getAnalysisUuid().get()).isEqualTo(analysisUuid);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecution.java b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecution.java
deleted file mode 100644 (file)
index 053ab2c..0000000
+++ /dev/null
@@ -1,31 +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.async;
-
-public interface AsyncExecution {
-  /**
-   * Add the specified {@link Runnable} in queue for asynchronous processing.
-   *
-   * This method returns instantly and {@code r} is executed in another thread.
-   *
-   * @throws NullPointerException if r is {@code null}
-   */
-  void addToQueue(Runnable r);
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionExecutorService.java b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionExecutorService.java
deleted file mode 100644 (file)
index 1458e9a..0000000
+++ /dev/null
@@ -1,24 +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.async;
-
-public interface AsyncExecutionExecutorService {
-  void addToQueue(Runnable r);
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionExecutorServiceImpl.java b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionExecutorServiceImpl.java
deleted file mode 100644 (file)
index ce5a82d..0000000
+++ /dev/null
@@ -1,77 +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.async;
-
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.server.util.AbstractStoppableExecutorService;
-
-import static java.util.concurrent.TimeUnit.MINUTES;
-
-public class AsyncExecutionExecutorServiceImpl
-  extends AbstractStoppableExecutorService<ThreadPoolExecutor>
-  implements AsyncExecutionExecutorService, AsyncExecutionMonitoring {
-  private static final Logger LOG = Loggers.get(AsyncExecutionExecutorServiceImpl.class);
-
-  private static final int MAX_THREAD_COUNT = 10;
-  private static final int UNLIMITED_QUEUE = Integer.MAX_VALUE;
-  private static final long KEEP_ALIVE_TIME_IN_MINUTES = 5L;
-
-  public AsyncExecutionExecutorServiceImpl() {
-    super(createDelegate());
-  }
-
-  private static ThreadPoolExecutor createDelegate() {
-    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
-      MAX_THREAD_COUNT, MAX_THREAD_COUNT,
-      KEEP_ALIVE_TIME_IN_MINUTES, MINUTES,
-      new LinkedBlockingQueue<>(UNLIMITED_QUEUE),
-      new ThreadFactoryBuilder()
-        .setDaemon(false)
-        .setNameFormat("SQ_async-%d")
-        .setUncaughtExceptionHandler(((t, e) -> LOG.error("Thread " + t + " failed unexpectedly", e)))
-        .build());
-    threadPoolExecutor.allowCoreThreadTimeOut(true);
-    return threadPoolExecutor;
-  }
-
-  @Override
-  public void addToQueue(Runnable r) {
-    this.submit(r);
-  }
-
-  @Override
-  public int getQueueSize() {
-    return delegate.getQueue().size();
-  }
-
-  @Override
-  public int getWorkerCount() {
-    return delegate.getPoolSize();
-  }
-
-  @Override
-  public int getLargestWorkerCount() {
-    return delegate.getLargestPoolSize();
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionImpl.java b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionImpl.java
deleted file mode 100644 (file)
index 7940fc3..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.async;
-
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-
-import static java.util.Objects.requireNonNull;
-
-public class AsyncExecutionImpl implements AsyncExecution {
-  private static final Logger LOG = Loggers.get(AsyncExecutionImpl.class);
-  private final AsyncExecutionExecutorService executorService;
-
-  public AsyncExecutionImpl(AsyncExecutionExecutorService executorService) {
-    this.executorService = executorService;
-  }
-
-  @Override
-  public void addToQueue(Runnable r) {
-    requireNonNull(r);
-    executorService.addToQueue(() -> {
-      try {
-        r.run();
-      } catch (Exception e) {
-        LOG.error("Asynchronous task failed", e);
-      }
-    });
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionMBean.java b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionMBean.java
deleted file mode 100644 (file)
index d908702..0000000
+++ /dev/null
@@ -1,31 +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.async;
-
-public interface AsyncExecutionMBean {
-
-  String OBJECT_NAME = "SonarQube:name=AsyncExecution";
-
-  long getQueueSize();
-
-  long getWorkerCount();
-
-  long getLargestWorkerCount();
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionMBeanImpl.java b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionMBeanImpl.java
deleted file mode 100644 (file)
index f155a95..0000000
+++ /dev/null
@@ -1,57 +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.async;
-
-import org.picocontainer.Startable;
-import org.sonar.process.Jmx;
-
-public class AsyncExecutionMBeanImpl implements AsyncExecutionMBean, Startable {
-
-  private final AsyncExecutionMonitoring asyncExecutionMonitoring;
-
-  public AsyncExecutionMBeanImpl(AsyncExecutionMonitoring asyncExecutionMonitoring) {
-    this.asyncExecutionMonitoring = asyncExecutionMonitoring;
-  }
-
-  @Override
-  public void start() {
-    Jmx.register(OBJECT_NAME, this);
-  }
-
-  @Override
-  public void stop() {
-    Jmx.unregister(OBJECT_NAME);
-  }
-
-  @Override
-  public long getQueueSize() {
-    return asyncExecutionMonitoring.getQueueSize();
-  }
-
-  @Override
-  public long getWorkerCount() {
-    return asyncExecutionMonitoring.getWorkerCount();
-  }
-
-  @Override
-  public long getLargestWorkerCount() {
-    return asyncExecutionMonitoring.getLargestWorkerCount();
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionModule.java b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionModule.java
deleted file mode 100644 (file)
index d1ef086..0000000
+++ /dev/null
@@ -1,32 +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.async;
-
-import org.sonar.core.platform.Module;
-
-public class AsyncExecutionModule extends Module {
-  @Override
-  protected void configureModule() {
-    add(
-      AsyncExecutionMBeanImpl.class,
-      AsyncExecutionExecutorServiceImpl.class,
-      AsyncExecutionImpl.class);
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionMonitoring.java b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionMonitoring.java
deleted file mode 100644 (file)
index ecc6e67..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.async;
-
-public interface AsyncExecutionMonitoring {
-  int getQueueSize();
-
-  int getWorkerCount();
-
-  int getLargestWorkerCount();
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/async/package-info.java
deleted file mode 100644 (file)
index a940f44..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.async;
-
-import javax.annotation.ParametersAreNonnullByDefault;
index 0b1e7bcfbab042a1b8da496ba46ce19e655acc66..cf5e8b244cc252f1bfde9727c3fc68e68cf69a8d 100644 (file)
@@ -28,7 +28,6 @@ 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.server.ce.CeModule;
 import org.sonar.ce.notification.ReportAnalysisFailureNotificationModule;
 import org.sonar.core.component.DefaultResourceTypes;
 import org.sonar.core.extension.CoreExtensionsInstaller;
@@ -42,6 +41,7 @@ import org.sonar.server.batch.BatchWsModule;
 import org.sonar.server.branch.BranchFeatureProxyImpl;
 import org.sonar.server.branch.pr.ws.PullRequestWsModule;
 import org.sonar.server.branch.ws.BranchWsModule;
+import org.sonar.server.ce.CeModule;
 import org.sonar.server.ce.ws.CeWsModule;
 import org.sonar.server.component.ComponentCleanerService;
 import org.sonar.server.component.ComponentFinder;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/Project.java b/server/sonar-server/src/main/java/org/sonar/server/project/Project.java
deleted file mode 100644 (file)
index af22037..0000000
+++ /dev/null
@@ -1,109 +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.project;
-
-import java.util.Objects;
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
-import org.sonar.db.component.ComponentDto;
-
-@Immutable
-public class Project {
-
-  private final String uuid;
-  private final String key;
-  private final String name;
-  private final String description;
-
-  public Project(String uuid, String key, String name) {
-    this(uuid, key, name, null);
-  }
-
-  public Project(String uuid, String key, String name, @Nullable String description) {
-    this.uuid = uuid;
-    this.key = key;
-    this.name = name;
-    this.description = description;
-  }
-
-  public static Project from(ComponentDto project) {
-    return new Project(project.uuid(), project.getDbKey(), project.name(), project.description());
-  }
-
-  /**
-   * Always links to a row that exists in database.
-   */
-  public String getUuid() {
-    return uuid;
-  }
-
-  /**
-   * Always links to a row that exists in database.
-   */
-  public String getKey() {
-    return key;
-  }
-
-  public String getName() {
-    return name;
-  }
-
-  public String getDescription() {
-    return description;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-    Project project = (Project) o;
-    return uuid.equals(project.uuid)
-      && key.equals(project.key)
-      && name.equals(project.name);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(uuid, key, name);
-  }
-
-  @Override
-  public String toString() {
-    StringBuilder sb = new StringBuilder("Project{");
-    sb.append("uuid='").append(uuid).append('\'');
-    sb.append(", key='").append(key).append('\'');
-    sb.append(", name='").append(name).append('\'');
-    sb.append(", description=").append(toString(this.description));
-    sb.append('}');
-    return sb.toString();
-  }
-
-  private static String toString(@Nullable String s) {
-    if (s == null) {
-      return null;
-    }
-    return '\'' + s + '\'';
-  }
-
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/Condition.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/Condition.java
deleted file mode 100644 (file)
index 3319b92..0000000
+++ /dev/null
@@ -1,136 +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.qualitygate;
-
-import java.util.Objects;
-import java.util.Optional;
-import java.util.stream.Stream;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
-import org.sonar.db.qualitygate.QualityGateConditionDto;
-
-import static com.google.common.base.Strings.emptyToNull;
-import static java.util.Objects.requireNonNull;
-
-@Immutable
-public class Condition {
-
-  private final String metricKey;
-  private final Operator operator;
-  @CheckForNull
-  private final String warningThreshold;
-  @CheckForNull
-  private final String errorThreshold;
-  private final boolean onLeakPeriod;
-
-  public Condition(String metricKey, Operator operator,
-    @Nullable String errorThreshold, @Nullable String warningThreshold,
-    boolean onLeakPeriod) {
-    this.metricKey = requireNonNull(metricKey, "metricKey can't be null");
-    this.operator = requireNonNull(operator, "operator can't be null");
-    this.onLeakPeriod = onLeakPeriod;
-    this.errorThreshold = emptyToNull(errorThreshold);
-    this.warningThreshold = emptyToNull(warningThreshold);
-  }
-
-  public String getMetricKey() {
-    return metricKey;
-  }
-
-  public boolean isOnLeakPeriod() {
-    return onLeakPeriod;
-  }
-
-  public Operator getOperator() {
-    return operator;
-  }
-
-  public Optional<String> getWarningThreshold() {
-    return Optional.ofNullable(warningThreshold);
-  }
-
-  public Optional<String> getErrorThreshold() {
-    return Optional.ofNullable(errorThreshold);
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-    Condition condition = (Condition) o;
-    return onLeakPeriod == condition.onLeakPeriod &&
-      Objects.equals(metricKey, condition.metricKey) &&
-      operator == condition.operator &&
-      Objects.equals(warningThreshold, condition.warningThreshold) &&
-      Objects.equals(errorThreshold, condition.errorThreshold);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(metricKey, operator, warningThreshold, errorThreshold, onLeakPeriod);
-  }
-
-  @Override
-  public String toString() {
-    return "Condition{" +
-      "metricKey='" + metricKey + '\'' +
-      ", operator=" + operator +
-      ", warningThreshold=" + toString(warningThreshold) +
-      ", errorThreshold=" + toString(errorThreshold) +
-      ", onLeakPeriod=" + onLeakPeriod +
-      '}';
-  }
-
-  private static String toString(@Nullable String errorThreshold) {
-    if (errorThreshold == null) {
-      return null;
-    }
-    return '\'' + errorThreshold + '\'';
-  }
-
-  public enum Operator {
-    EQUALS(QualityGateConditionDto.OPERATOR_EQUALS),
-    NOT_EQUALS(QualityGateConditionDto.OPERATOR_NOT_EQUALS),
-    GREATER_THAN(QualityGateConditionDto.OPERATOR_GREATER_THAN),
-    LESS_THAN(QualityGateConditionDto.OPERATOR_LESS_THAN);
-
-    private final String dbValue;
-
-    Operator(String dbValue) {
-      this.dbValue = dbValue;
-    }
-
-    public String getDbValue() {
-      return dbValue;
-    }
-
-    public static Operator fromDbValue(String s) {
-      return Stream.of(values())
-        .filter(o -> o.getDbValue().equals(s))
-        .findFirst()
-        .orElseThrow(() -> new IllegalArgumentException("Unsupported operator db value: " + s));
-    }
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java
deleted file mode 100644 (file)
index b47c176..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.qualitygate;
-
-import java.util.Objects;
-import java.util.Optional;
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
-
-import static java.util.Objects.requireNonNull;
-
-@Immutable
-public class EvaluatedCondition {
-  private final Condition condition;
-  private final EvaluationStatus status;
-  @Nullable
-  private final String value;
-
-  public EvaluatedCondition(Condition condition, EvaluationStatus status, @Nullable String value) {
-    this.condition = requireNonNull(condition, "condition can't be null");
-    this.status = requireNonNull(status, "status can't be null");
-    this.value = value;
-  }
-
-  public Condition getCondition() {
-    return condition;
-  }
-
-  public EvaluationStatus getStatus() {
-    return status;
-  }
-
-  public Optional<String> getValue() {
-    return Optional.ofNullable(value);
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-    EvaluatedCondition that = (EvaluatedCondition) o;
-    return Objects.equals(condition, that.condition) &&
-      status == that.status &&
-      Objects.equals(value, that.value);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(condition, status, value);
-  }
-
-  @Override
-  public String toString() {
-    return "EvaluatedCondition{" +
-      "condition=" + condition +
-      ", status=" + status +
-      ", value=" + (value == null ? null : ('\'' + value + '\'')) +
-      '}';
-  }
-
-  /**
-   * Quality gate condition evaluation status.
-   */
-  public enum EvaluationStatus {
-    /**
-     * No measure found or measure had no value. The condition has not been evaluated and therefor ignored in
-     * the computation of the Quality Gate status.
-     */
-    NO_VALUE,
-    /**
-     * Condition evaluated as OK, neither error nor warning thresholds have been reached.
-     */
-    OK,
-    /**
-     * Condition evaluated as WARN, only warning thresholds has been reached.
-     */
-    WARN,
-    /**
-     * Condition evaluated as ERROR, error thresholds has been reached (and most likely warning thresholds too).
-     */
-    ERROR
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java
deleted file mode 100644 (file)
index f568d24..0000000
+++ /dev/null
@@ -1,161 +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.qualitygate;
-
-import com.google.common.collect.ImmutableSet;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.stream.Collectors;
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
-import org.sonar.api.measures.Metric;
-import org.sonar.server.qualitygate.EvaluatedCondition.EvaluationStatus;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static java.util.Objects.requireNonNull;
-
-@Immutable
-public class EvaluatedQualityGate {
-  private final QualityGate qualityGate;
-  private final Metric.Level status;
-  private final Set<EvaluatedCondition> evaluatedConditions;
-  private final boolean ignoredConditionsOnSmallChangeset;
-
-  private EvaluatedQualityGate(QualityGate qualityGate, Metric.Level status, Set<EvaluatedCondition> evaluatedConditions, boolean ignoredConditionsOnSmallChangeset) {
-    this.qualityGate = requireNonNull(qualityGate, "qualityGate can't be null");
-    this.status = requireNonNull(status, "status can't be null");
-    this.evaluatedConditions = evaluatedConditions;
-    this.ignoredConditionsOnSmallChangeset = ignoredConditionsOnSmallChangeset;
-  }
-
-  public QualityGate getQualityGate() {
-    return qualityGate;
-  }
-
-  public Metric.Level getStatus() {
-    return status;
-  }
-
-  public Set<EvaluatedCondition> getEvaluatedConditions() {
-    return evaluatedConditions;
-  }
-
-  public boolean hasIgnoredConditionsOnSmallChangeset() {
-    return ignoredConditionsOnSmallChangeset;
-  }
-
-  public static Builder newBuilder() {
-    return new Builder();
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-    EvaluatedQualityGate that = (EvaluatedQualityGate) o;
-    return Objects.equals(qualityGate, that.qualityGate) &&
-      status == that.status &&
-      Objects.equals(evaluatedConditions, that.evaluatedConditions);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(qualityGate, status, evaluatedConditions);
-  }
-
-  @Override
-  public String toString() {
-    return "EvaluatedQualityGate{" +
-      "qualityGate=" + qualityGate +
-      ", status=" + status +
-      ", evaluatedConditions=" + evaluatedConditions +
-      '}';
-  }
-
-  public static final class Builder {
-    private QualityGate qualityGate;
-    private Metric.Level status;
-    private final Map<Condition, EvaluatedCondition> evaluatedConditions = new HashMap<>();
-    private boolean ignoredConditionsOnSmallChangeset = false;
-
-    private Builder() {
-      // use static factory method
-    }
-
-    public Builder setQualityGate(QualityGate qualityGate) {
-      this.qualityGate = qualityGate;
-      return this;
-    }
-
-    public Builder setStatus(Metric.Level status) {
-      this.status = status;
-      return this;
-    }
-
-    public Builder setIgnoredConditionsOnSmallChangeset(boolean b) {
-      this.ignoredConditionsOnSmallChangeset = b;
-      return this;
-    }
-
-    public Builder addCondition(Condition condition, EvaluationStatus status, @Nullable String value) {
-      evaluatedConditions.put(condition, new EvaluatedCondition(condition, status, value));
-      return this;
-    }
-
-    public Builder addCondition(EvaluatedCondition c) {
-      evaluatedConditions.put(c.getCondition(), c);
-      return this;
-    }
-
-    public Set<EvaluatedCondition> getEvaluatedConditions() {
-      return ImmutableSet.copyOf(evaluatedConditions.values());
-    }
-
-    public EvaluatedQualityGate build() {
-      return new EvaluatedQualityGate(
-        this.qualityGate,
-        this.status,
-        checkEvaluatedConditions(qualityGate, evaluatedConditions),
-        ignoredConditionsOnSmallChangeset);
-    }
-
-    private static Set<EvaluatedCondition> checkEvaluatedConditions(QualityGate qualityGate, Map<Condition, EvaluatedCondition> evaluatedConditions) {
-      Set<Condition> conditions = qualityGate.getConditions();
-
-      Set<Condition> conditionsNotEvaluated = conditions.stream()
-        .filter(c -> !evaluatedConditions.containsKey(c))
-        .collect(Collectors.toSet());
-      checkArgument(conditionsNotEvaluated.isEmpty(), "Evaluation missing for the following conditions: %s", conditionsNotEvaluated);
-
-      Set<Condition> unknownConditions = evaluatedConditions.keySet().stream()
-        .filter(c -> !conditions.contains(c))
-        .collect(Collectors.toSet());
-      checkArgument(unknownConditions.isEmpty(), "Evaluation provided for unknown conditions: %s", unknownConditions);
-
-      return ImmutableSet.copyOf(evaluatedConditions.values());
-    }
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGate.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGate.java
deleted file mode 100644 (file)
index d047423..0000000
+++ /dev/null
@@ -1,83 +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.qualitygate;
-
-import java.util.Objects;
-import java.util.Set;
-import javax.annotation.concurrent.Immutable;
-
-import static java.util.Objects.requireNonNull;
-import static org.sonar.core.util.stream.MoreCollectors.toSet;
-
-@Immutable
-public class QualityGate {
-  private final String id;
-  private final String name;
-  private final Set<Condition> conditions;
-
-  public QualityGate(String id, String name, Set<Condition> conditions) {
-    this.id = requireNonNull(id, "id can't be null");
-    this.name = requireNonNull(name, "name can't be null");
-    this.conditions = requireNonNull(conditions, "conditions can't be null")
-      .stream()
-      .map(c -> requireNonNull(c, "condition can't be null"))
-      .collect(toSet(conditions.size()));
-  }
-
-  public String getId() {
-    return id;
-  }
-
-  public String getName() {
-    return name;
-  }
-
-  public Set<Condition> getConditions() {
-    return conditions;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-    QualityGate that = (QualityGate) o;
-    return Objects.equals(id, that.id) &&
-      Objects.equals(name, that.name) &&
-      Objects.equals(conditions, that.conditions);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(id, name, conditions);
-  }
-
-  @Override
-  public String toString() {
-    return "QualityGate{" +
-      "id=" + id +
-      ", name='" + name + '\'' +
-      ", conditions=" + conditions +
-      '}';
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/OkHttpClientProvider.java b/server/sonar-server/src/main/java/org/sonar/server/util/OkHttpClientProvider.java
deleted file mode 100644 (file)
index d28be20..0000000
+++ /dev/null
@@ -1,70 +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.util;
-
-import okhttp3.OkHttpClient;
-import org.picocontainer.injectors.ProviderAdapter;
-import org.sonar.api.SonarRuntime;
-import org.sonar.api.ce.ComputeEngineSide;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.server.ServerSide;
-import org.sonarqube.ws.client.OkHttpClientBuilder;
-
-import static java.lang.String.format;
-import static org.sonar.process.ProcessProperties.Property.HTTP_PROXY_PASSWORD;
-import static org.sonar.process.ProcessProperties.Property.HTTP_PROXY_USER;
-
-/**
- * Provide a unique instance of {@link OkHttpClient} which configuration:
- * <ul>
- *   <li>supports HTTPS</li>
- *   <li>supports proxy, including authentication, as defined by the properties like "http.proxyHost" in
- *   conf/sonar.properties</li>
- *   <li>has connect and read timeouts of 10 seconds each</li>
- *   <li>sends automatically the HTTP header "User-Agent" with value "SonarQube/{version}", for instance "SonarQube/6.2"</li>
- * </ul>
- */
-@ServerSide
-@ComputeEngineSide
-public class OkHttpClientProvider extends ProviderAdapter {
-
-  private static final int DEFAULT_CONNECT_TIMEOUT_IN_MS = 10_000;
-  private static final int DEFAULT_READ_TIMEOUT_IN_MS = 10_000;
-
-  private okhttp3.OkHttpClient okHttpClient;
-
-  /**
-   * @return a {@link OkHttpClient} singleton
-   */
-  public OkHttpClient provide(Configuration config, SonarRuntime runtime) {
-    if (okHttpClient == null) {
-      OkHttpClientBuilder builder = new OkHttpClientBuilder();
-      builder.setConnectTimeoutMs(DEFAULT_CONNECT_TIMEOUT_IN_MS);
-      builder.setReadTimeoutMs(DEFAULT_READ_TIMEOUT_IN_MS);
-      // no need to define proxy URL as system-wide proxy is used and properly
-      // configured by bootstrap process.
-      builder.setProxyLogin(config.get(HTTP_PROXY_USER.getKey()).orElse(null));
-      builder.setProxyPassword(config.get(HTTP_PROXY_PASSWORD.getKey()).orElse(null));
-      builder.setUserAgent(format("SonarQube/%s", runtime.getApiVersion().toString()));
-      okHttpClient = builder.build();
-    }
-    return okHttpClient;
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/Analysis.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/Analysis.java
deleted file mode 100644 (file)
index 5d1c24b..0000000
+++ /dev/null
@@ -1,70 +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.webhook;
-
-import com.google.common.base.Objects;
-import java.util.Date;
-
-import static java.util.Objects.requireNonNull;
-
-public final class Analysis {
-  private final String uuid;
-  private final long date;
-
-  public Analysis(String uuid, long date) {
-    requireNonNull(uuid, "uuid must not be null");
-    this.uuid = uuid;
-    this.date = date;
-  }
-
-  public String getUuid() {
-    return uuid;
-  }
-
-  public Date getDate() {
-    return new Date(date);
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (!(o instanceof Analysis)) {
-      return false;
-    }
-    Analysis analysis = (Analysis) o;
-    return Objects.equal(uuid, analysis.uuid) &&
-      Objects.equal(date, analysis.date);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hashCode(uuid, date);
-  }
-
-  @Override
-  public String toString() {
-    return "Analysis{" +
-      "uuid='" + uuid + '\'' +
-      ", date=" + date +
-      '}';
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/Branch.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/Branch.java
deleted file mode 100644 (file)
index 2d62e58..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.server.webhook;
-
-import java.util.Objects;
-import java.util.Optional;
-import javax.annotation.Nullable;
-
-import static java.util.Objects.requireNonNull;
-
-public final class Branch {
-  private final boolean main;
-  private final String name;
-  private final Type type;
-
-  public Branch(boolean main, @Nullable String name, Type type) {
-    this.main = main;
-    this.name = name;
-    this.type = requireNonNull(type, "type can't be null");
-  }
-
-  public boolean isMain() {
-    return main;
-  }
-
-  public Optional<String> getName() {
-    return Optional.ofNullable(name);
-  }
-
-  public Type getType() {
-    return type;
-  }
-
-  public enum Type {
-    LONG, SHORT, PULL_REQUEST
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-    Branch branch = (Branch) o;
-    return main == branch.main &&
-      Objects.equals(name, branch.name) &&
-      type == branch.type;
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(main, name, type);
-  }
-
-  @Override
-  public String toString() {
-    return "Branch{" +
-      "main=" + main +
-      ", name='" + name + '\'' +
-      ", type=" + type +
-      '}';
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/CeTask.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/CeTask.java
deleted file mode 100644 (file)
index 226e1f8..0000000
+++ /dev/null
@@ -1,72 +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.webhook;
-
-import java.util.Objects;
-
-import static java.util.Objects.requireNonNull;
-
-public final class CeTask {
-  private final String id;
-  private final Status status;
-
-  public CeTask(String id, Status status) {
-    this.id = requireNonNull(id, "id can't be null");
-    this.status = requireNonNull(status, "status can't be null");
-  }
-
-  public String getId() {
-    return id;
-  }
-
-  public Status getStatus() {
-    return status;
-  }
-
-  public enum Status {
-    SUCCESS, FAILED
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-    CeTask task = (CeTask) o;
-    return Objects.equals(id, task.id) &&
-      status == task.status;
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(id, status);
-  }
-
-  @Override
-  public String toString() {
-    return "CeTask{" +
-      "id='" + id + '\'' +
-      ", status=" + status +
-      '}';
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/ProjectAnalysis.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/ProjectAnalysis.java
deleted file mode 100644 (file)
index 3a9175a..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.webhook;
-
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import javax.annotation.Nullable;
-import org.sonar.server.project.Project;
-import org.sonar.server.qualitygate.EvaluatedQualityGate;
-
-import static com.google.common.collect.ImmutableMap.copyOf;
-import static java.util.Objects.requireNonNull;
-
-public class ProjectAnalysis {
-  private final Project project;
-  private final CeTask ceTask;
-  private final Branch branch;
-  private final EvaluatedQualityGate qualityGate;
-  private final Long updatedAt;
-  private final Map<String, String> properties;
-  private final Analysis analysis;
-
-  public ProjectAnalysis(Project project, @Nullable CeTask ceTask, @Nullable Analysis analysis,
-    @Nullable Branch branch, @Nullable EvaluatedQualityGate qualityGate, @Nullable Long updatedAt,
-    Map<String, String> properties) {
-    this.project = requireNonNull(project, "project can't be null");
-    this.ceTask = ceTask;
-    this.branch = branch;
-    this.qualityGate = qualityGate;
-    this.updatedAt = updatedAt;
-    this.properties = copyOf(requireNonNull(properties, "properties can't be null"));
-    this.analysis = analysis;
-  }
-
-  public Optional<CeTask> getCeTask() {
-    return Optional.ofNullable(ceTask);
-  }
-
-  public Project getProject() {
-    return project;
-  }
-
-  public Optional<Branch> getBranch() {
-    return Optional.ofNullable(branch);
-  }
-
-  public Optional<EvaluatedQualityGate> getQualityGate() {
-    return Optional.ofNullable(qualityGate);
-  }
-
-  public Map<String, String> getProperties() {
-    return properties;
-  }
-
-  public Optional<Analysis> getAnalysis() {
-    return Optional.ofNullable(analysis);
-  }
-
-  public Optional<Long> getUpdatedAt() {
-    return Optional.ofNullable(updatedAt);
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-    ProjectAnalysis that = (ProjectAnalysis) o;
-    return Objects.equals(project, that.project) &&
-      Objects.equals(ceTask, that.ceTask) &&
-      Objects.equals(branch, that.branch) &&
-      Objects.equals(qualityGate, that.qualityGate) &&
-      Objects.equals(updatedAt, that.updatedAt) &&
-      Objects.equals(properties, that.properties) &&
-      Objects.equals(analysis, that.analysis);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(project, ceTask, branch, qualityGate, updatedAt, properties, analysis);
-  }
-
-  @Override
-  public String toString() {
-    return "ProjectAnalysis{" +
-      "project=" + project +
-      ", ceTask=" + ceTask +
-      ", branch=" + branch +
-      ", qualityGate=" + qualityGate +
-      ", updatedAt=" + updatedAt +
-      ", properties=" + properties +
-      ", analysis=" + analysis +
-      '}';
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebHooks.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebHooks.java
deleted file mode 100644 (file)
index b1c339f..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.server.webhook;
-
-import java.util.Objects;
-import java.util.function.Supplier;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import org.sonar.api.config.Configuration;
-import org.sonar.db.component.ComponentDto;
-
-import static java.util.Objects.requireNonNull;
-
-public interface WebHooks {
-
-  /**
-   * Tells whether any webHook is configured for the specified {@link Configuration}.
-   *
-   * <p>
-   * This can be used to not do consuming operations before calling
-   * {@link #sendProjectAnalysisUpdate(Analysis, Supplier)}
-   */
-  boolean isEnabled(ComponentDto projectDto);
-
-  /**
-   * Calls all WebHooks configured in the specified {@link Configuration} for the specified analysis with the
-   * {@link WebhookPayload} provided by the specified Supplier.
-   */
-  void sendProjectAnalysisUpdate(Analysis analysis, Supplier<WebhookPayload> payloadSupplier);
-
-  final class Analysis {
-    private final String projectUuid;
-    private final String ceTaskUuid;
-    private final String analysisUuid;
-
-    public Analysis(String projectUuid, @Nullable String analysisUuid, @Nullable  String ceTaskUuid) {
-      this.projectUuid = requireNonNull(projectUuid, "projectUuid can't be null");
-      this.analysisUuid = analysisUuid;
-      this.ceTaskUuid = ceTaskUuid;
-    }
-
-    public String getProjectUuid() {
-      return projectUuid;
-    }
-
-    @CheckForNull
-    public String getCeTaskUuid() {
-      return ceTaskUuid;
-    }
-
-    @CheckForNull
-    public String getAnalysisUuid() {
-      return analysisUuid;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) {
-        return true;
-      }
-      if (o == null || getClass() != o.getClass()) {
-        return false;
-      }
-      Analysis analysis = (Analysis) o;
-      return Objects.equals(projectUuid, analysis.projectUuid) &&
-        Objects.equals(ceTaskUuid, analysis.ceTaskUuid) &&
-        Objects.equals(analysisUuid, analysis.analysisUuid);
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hash(projectUuid, ceTaskUuid, analysisUuid);
-    }
-
-    @Override
-    public String toString() {
-      return "Analysis{" +
-        "projectUuid='" + projectUuid + '\'' +
-        ", ceTaskUuid='" + ceTaskUuid + '\'' +
-        '}';
-    }
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebHooksImpl.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebHooksImpl.java
deleted file mode 100644 (file)
index 5036e81..0000000
+++ /dev/null
@@ -1,109 +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.webhook;
-
-import java.util.List;
-import java.util.Optional;
-import java.util.function.Supplier;
-import java.util.stream.Stream;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.webhook.WebhookDao;
-import org.sonar.db.webhook.WebhookDto;
-import org.sonar.server.async.AsyncExecution;
-
-import static java.util.Optional.ofNullable;
-import static org.sonar.server.ws.WsUtils.checkStateWithOptional;
-
-public class WebHooksImpl implements WebHooks {
-
-  private static final Logger LOGGER = Loggers.get(WebHooksImpl.class);
-
-  private final WebhookCaller caller;
-  private final WebhookDeliveryStorage deliveryStorage;
-  private final AsyncExecution asyncExecution;
-  private final DbClient dbClient;
-
-  public WebHooksImpl(WebhookCaller caller, WebhookDeliveryStorage deliveryStorage, AsyncExecution asyncExecution, DbClient dbClient) {
-    this.caller = caller;
-    this.deliveryStorage = deliveryStorage;
-    this.asyncExecution = asyncExecution;
-    this.dbClient = dbClient;
-  }
-
-  @Override
-  public boolean isEnabled(ComponentDto projectDto) {
-    return readWebHooksFrom(projectDto.uuid())
-      .findAny()
-      .isPresent();
-  }
-
-  private Stream<WebhookDto> readWebHooksFrom(String projectUuid) {
-    try (DbSession dbSession = dbClient.openSession(false)) {
-
-      Optional<ComponentDto> optionalComponentDto = ofNullable(dbClient.componentDao().selectByUuid(dbSession, projectUuid).orNull());
-      ComponentDto componentDto = checkStateWithOptional(optionalComponentDto, "the requested project '%s' was not found", projectUuid);
-
-      if (componentDto.getMainBranchProjectUuid() != null && !componentDto.uuid().equals(componentDto.getMainBranchProjectUuid())) {
-        Optional<ComponentDto> mainBranchComponentDto = ofNullable(dbClient.componentDao().selectByUuid(dbSession, componentDto.getMainBranchProjectUuid()).orNull());
-        componentDto = checkStateWithOptional(mainBranchComponentDto, "the requested project '%s' was not found", projectUuid);
-      }
-
-      WebhookDao dao = dbClient.webhookDao();
-      return Stream.concat(
-        dao.selectByProject(dbSession, componentDto).stream(),
-        dao.selectByOrganizationUuid(dbSession, componentDto.getOrganizationUuid()).stream());
-    }
-  }
-
-  @Override
-  public void sendProjectAnalysisUpdate(Analysis analysis, Supplier<WebhookPayload> payloadSupplier) {
-    List<Webhook> webhooks = readWebHooksFrom(analysis.getProjectUuid())
-      .map(dto -> new Webhook(dto.getUuid(), analysis.getProjectUuid(), analysis.getCeTaskUuid(), analysis.getAnalysisUuid(), dto.getName(), dto.getUrl()))
-      .collect(MoreCollectors.toList());
-    if (webhooks.isEmpty()) {
-      return;
-    }
-
-    WebhookPayload payload = payloadSupplier.get();
-    webhooks.forEach(webhook -> asyncExecution.addToQueue(() -> {
-      WebhookDelivery delivery = caller.call(webhook, payload);
-      log(delivery);
-      deliveryStorage.persist(delivery);
-    }));
-    asyncExecution.addToQueue(() -> deliveryStorage.purge(analysis.getProjectUuid()));
-  }
-
-  private static void log(WebhookDelivery delivery) {
-    Optional<String> error = delivery.getErrorMessage();
-    if (error.isPresent()) {
-      LOGGER.debug("Failed to send webhook '{}' | url={} | message={}",
-        delivery.getWebhook().getName(), delivery.getWebhook().getUrl(), error.get());
-    } else {
-      LOGGER.debug("Sent webhook '{}' | url={} | time={}ms | status={}",
-        delivery.getWebhook().getName(), delivery.getWebhook().getUrl(), delivery.getDurationInMs().orElse(-1), delivery.getHttpStatus().orElse(-1));
-    }
-  }
-
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/Webhook.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/Webhook.java
deleted file mode 100644 (file)
index 8ac3b4c..0000000
+++ /dev/null
@@ -1,71 +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.webhook;
-
-import java.util.Optional;
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
-
-import static java.util.Objects.requireNonNull;
-import static java.util.Optional.ofNullable;
-
-@Immutable
-public class Webhook {
-
-  private final String uuid;
-  private final String componentUuid;
-  private final String ceTaskUuid;
-  private final String analysisUuid;
-  private final String name;
-  private final String url;
-
-  public Webhook(String uuid, String componentUuid, @Nullable String ceTaskUuid, @Nullable String analysisUuid, String name, String url) {
-    this.uuid = uuid;
-    this.componentUuid = requireNonNull(componentUuid);
-    this.ceTaskUuid = ceTaskUuid;
-    this.analysisUuid = analysisUuid;
-    this.name = requireNonNull(name);
-    this.url = requireNonNull(url);
-  }
-
-  public String getComponentUuid() {
-    return componentUuid;
-  }
-
-  public Optional<String> getCeTaskUuid() {
-    return ofNullable(ceTaskUuid);
-  }
-
-  public String getName() {
-    return name;
-  }
-
-  public String getUrl() {
-    return url;
-  }
-
-  public String getUuid() {
-    return uuid;
-  }
-
-  public Optional<String> getAnalysisUuid() {
-    return ofNullable(analysisUuid);
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookCaller.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookCaller.java
deleted file mode 100644 (file)
index 979386a..0000000
+++ /dev/null
@@ -1,34 +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.webhook;
-
-public interface WebhookCaller {
-
-  /**
-   * Call webhook by sending a HTTP(S) POST request containing
-   * the JSON payload.
-   * <br/>
-   * Errors are silently ignored. They don't generate logs or
-   * throw exceptions. The error status is stored in the
-   * returned {@link WebhookDelivery}.
-   */
-  WebhookDelivery call(Webhook webhook, WebhookPayload payload);
-
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookCallerImpl.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookCallerImpl.java
deleted file mode 100644 (file)
index e9e70ca..0000000
+++ /dev/null
@@ -1,141 +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.webhook;
-
-import java.io.IOException;
-import okhttp3.Credentials;
-import okhttp3.HttpUrl;
-import okhttp3.MediaType;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.RequestBody;
-import okhttp3.Response;
-import org.sonar.api.ce.ComputeEngineSide;
-import org.sonar.api.utils.System2;
-
-import static java.lang.String.format;
-import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
-import static java.net.HttpURLConnection.HTTP_MOVED_TEMP;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static okhttp3.internal.http.StatusLine.HTTP_PERM_REDIRECT;
-import static okhttp3.internal.http.StatusLine.HTTP_TEMP_REDIRECT;
-import static org.apache.commons.lang.StringUtils.isNotEmpty;
-
-@ComputeEngineSide
-public class WebhookCallerImpl implements WebhookCaller {
-
-  private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
-  private static final String PROJECT_KEY_HEADER = "X-SonarQube-Project";
-
-  private final System2 system;
-  private final OkHttpClient okHttpClient;
-
-  public WebhookCallerImpl(System2 system, OkHttpClient okHttpClient) {
-    this.system = system;
-    this.okHttpClient = newClientWithoutRedirect(okHttpClient);
-  }
-
-  @Override
-  public WebhookDelivery call(Webhook webhook, WebhookPayload payload) {
-    WebhookDelivery.Builder builder = new WebhookDelivery.Builder();
-    long startedAt = system.now();
-    builder
-      .setAt(startedAt)
-      .setPayload(payload)
-      .setWebhook(webhook);
-
-    try {
-      Request request = buildHttpRequest(webhook, payload);
-      try (Response response = execute(request)) {
-        builder.setHttpStatus(response.code());
-      }
-    } catch (Exception e) {
-      builder.setError(e);
-    }
-
-    return builder
-      .setDurationInMs((int) (system.now() - startedAt))
-      .build();
-  }
-
-  private static Request buildHttpRequest(Webhook webhook, WebhookPayload payload) {
-    HttpUrl url = HttpUrl.parse(webhook.getUrl());
-    if (url == null) {
-      throw new IllegalArgumentException("Webhook URL is not valid: " + webhook.getUrl());
-    }
-    Request.Builder request = new Request.Builder();
-    request.url(url);
-    request.header(PROJECT_KEY_HEADER, payload.getProjectKey());
-    if (isNotEmpty(url.username())) {
-      request.header("Authorization", Credentials.basic(url.username(), url.password(), UTF_8));
-    }
-
-    RequestBody body = RequestBody.create(JSON, payload.getJson());
-    request.post(body);
-    return request.build();
-  }
-
-  private Response execute(Request request) throws IOException {
-    Response response = okHttpClient.newCall(request).execute();
-    switch (response.code()) {
-      case HTTP_MOVED_PERM:
-      case HTTP_MOVED_TEMP:
-      case HTTP_TEMP_REDIRECT:
-      case HTTP_PERM_REDIRECT:
-        // OkHttpClient does not follow the redirect with the same HTTP method. A POST is
-        // redirected to a GET. Because of that the redirect must be manually
-        // implemented.
-        // See:
-        // https://github.com/square/okhttp/blob/07309c1c7d9e296014268ebd155ebf7ef8679f6c/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java#L316
-        // https://github.com/square/okhttp/issues/936#issuecomment-266430151
-        return followPostRedirect(response);
-      default:
-        return response;
-    }
-  }
-
-  /**
-   * Inspired by https://github.com/square/okhttp/blob/parent-3.6.0/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java#L286
-   */
-  private Response followPostRedirect(Response response) throws IOException {
-    String location = response.header("Location");
-    if (location == null) {
-      throw new IllegalStateException(format("Missing HTTP header 'Location' in redirect of %s", response.request().url()));
-    }
-    HttpUrl url = response.request().url().resolve(location);
-
-    // Don't follow redirects to unsupported protocols.
-    if (url == null) {
-      throw new IllegalStateException(format("Unsupported protocol in redirect of %s to %s", response.request().url(), location));
-    }
-
-    Request.Builder redirectRequest = response.request().newBuilder();
-    redirectRequest.post(response.request().body());
-    response.body().close();
-    return okHttpClient.newCall(redirectRequest.url(url).build()).execute();
-  }
-
-  private static OkHttpClient newClientWithoutRedirect(OkHttpClient client) {
-    return client.newBuilder()
-      .followRedirects(false)
-      .followSslRedirects(false)
-      .build();
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookDelivery.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookDelivery.java
deleted file mode 100644 (file)
index 38194e0..0000000
+++ /dev/null
@@ -1,143 +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.webhook;
-
-import java.util.Optional;
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
-
-import static com.google.common.base.Throwables.getRootCause;
-import static java.util.Objects.requireNonNull;
-
-/**
- * A {@link WebhookDelivery} represents the result of a webhook call.
- */
-@Immutable
-public class WebhookDelivery {
-
-  private final Webhook webhook;
-  private final WebhookPayload payload;
-  private final Integer httpStatus;
-  private final Integer durationInMs;
-  private final long at;
-  private final Throwable error;
-
-  private WebhookDelivery(Builder builder) {
-    this.webhook = requireNonNull(builder.webhook);
-    this.payload = requireNonNull(builder.payload);
-    this.httpStatus = builder.httpStatus;
-    this.durationInMs = builder.durationInMs;
-    this.at = builder.at;
-    this.error = builder.error;
-  }
-
-  public Webhook getWebhook() {
-    return webhook;
-  }
-
-  public WebhookPayload getPayload() {
-    return payload;
-  }
-
-  /**
-   * @return the HTTP status if {@link #getError()} is empty, else returns
-   * {@link Optional#empty()}
-   */
-  public Optional<Integer> getHttpStatus() {
-    return Optional.ofNullable(httpStatus);
-  }
-
-  /**
-   * @return the duration in milliseconds if {@link #getError()} is empty,
-   * else returns {@link Optional#empty()}
-   */
-  public Optional<Integer> getDurationInMs() {
-    return Optional.ofNullable(durationInMs);
-  }
-
-  /**
-   * @return the date of sending
-   */
-  public long getAt() {
-    return at;
-  }
-
-  /**
-   * @return the error raised if the request could not be executed due to a connectivity
-   * problem or timeout
-   */
-  public Optional<Throwable> getError() {
-    return Optional.ofNullable(error);
-  }
-
-  /**
-   * @return the cause message of {@link #getError()}, Optional.empty() is error is not set.
-   */
-  public Optional<String> getErrorMessage() {
-    return error != null ? Optional.ofNullable(getRootCause(error).getMessage()) : Optional.empty();
-  }
-
-  public boolean isSuccess() {
-    return httpStatus != null && httpStatus >= 200 && httpStatus < 300;
-  }
-
-  public static class Builder {
-    private Webhook webhook;
-    private WebhookPayload payload;
-    private Integer httpStatus;
-    private Integer durationInMs;
-    private long at;
-    private Throwable error;
-
-    public Builder setWebhook(Webhook w) {
-      this.webhook = w;
-      return this;
-    }
-
-    public Builder setPayload(WebhookPayload payload) {
-      this.payload = payload;
-      return this;
-    }
-
-    public Builder setHttpStatus(@Nullable Integer httpStatus) {
-      this.httpStatus = httpStatus;
-      return this;
-    }
-
-    public Builder setDurationInMs(@Nullable Integer durationInMs) {
-      this.durationInMs = durationInMs;
-      return this;
-    }
-
-    public Builder setAt(long at) {
-      this.at = at;
-      return this;
-    }
-
-    public Builder setError(@Nullable Throwable t) {
-      this.error = t;
-      return this;
-    }
-
-    public WebhookDelivery build() {
-      return new WebhookDelivery(this);
-    }
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookDeliveryStorage.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookDeliveryStorage.java
deleted file mode 100644 (file)
index 0ba9bb9..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.server.webhook;
-
-import com.google.common.base.Throwables;
-import org.sonar.api.ce.ComputeEngineSide;
-import org.sonar.api.utils.System2;
-import org.sonar.core.util.UuidFactory;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.webhook.WebhookDeliveryDao;
-import org.sonar.db.webhook.WebhookDeliveryDto;
-
-/**
- * Persist and purge {@link WebhookDelivery} into database
- */
-@ComputeEngineSide
-public class WebhookDeliveryStorage {
-
-  private static final long ALIVE_DELAY_MS = 30L * 24 * 60 * 60 * 1000;
-
-  private final DbClient dbClient;
-  private final System2 system;
-  private final UuidFactory uuidFactory;
-
-  public WebhookDeliveryStorage(DbClient dbClient, System2 system, UuidFactory uuidFactory) {
-    this.dbClient = dbClient;
-    this.system = system;
-    this.uuidFactory = uuidFactory;
-  }
-
-  public void persist(WebhookDelivery delivery) {
-    WebhookDeliveryDao dao = dbClient.webhookDeliveryDao();
-    try (DbSession dbSession = dbClient.openSession(false)) {
-      dao.insert(dbSession, toDto(delivery));
-      dbSession.commit();
-    }
-  }
-
-  public void purge(String componentUuid) {
-    long beforeDate = system.now() - ALIVE_DELAY_MS;
-    try (DbSession dbSession = dbClient.openSession(false)) {
-      dbClient.webhookDeliveryDao().deleteComponentBeforeDate(dbSession, componentUuid, beforeDate);
-      dbSession.commit();
-    }
-  }
-
-  private WebhookDeliveryDto toDto(WebhookDelivery delivery) {
-    WebhookDeliveryDto dto = new WebhookDeliveryDto();
-    dto.setUuid(uuidFactory.create());
-    dto.setWebhookUuid(delivery.getWebhook().getUuid());
-    dto.setComponentUuid(delivery.getWebhook().getComponentUuid());
-    delivery.getWebhook().getCeTaskUuid().ifPresent(dto::setCeTaskUuid);
-    delivery.getWebhook().getAnalysisUuid().ifPresent(dto::setAnalysisUuid);
-    dto.setName(delivery.getWebhook().getName());
-    dto.setUrl(delivery.getWebhook().getUrl());
-    dto.setSuccess(delivery.isSuccess());
-    dto.setHttpStatus(delivery.getHttpStatus().orElse(null));
-    dto.setDurationMs(delivery.getDurationInMs().orElse(null));
-    dto.setErrorStacktrace(delivery.getError().map(Throwables::getStackTraceAsString).orElse(null));
-    dto.setPayload(delivery.getPayload().getJson());
-    dto.setCreatedAt(delivery.getAt());
-    return dto;
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookModule.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookModule.java
deleted file mode 100644 (file)
index 1768b27..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.webhook;
-
-import org.sonar.core.platform.Module;
-
-public class WebhookModule extends Module {
-  @Override
-  protected void configureModule() {
-    add(
-      WebhookCallerImpl.class,
-      WebhookDeliveryStorage.class,
-      WebHooksImpl.class,
-      WebhookPayloadFactoryImpl.class);
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookPayload.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookPayload.java
deleted file mode 100644 (file)
index 197f472..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.server.webhook;
-
-import javax.annotation.concurrent.Immutable;
-
-import static java.util.Objects.requireNonNull;
-
-@Immutable
-public class WebhookPayload {
-
-  private final String projectKey;
-  private final String json;
-
-  public WebhookPayload(String projectKey, String json) {
-    this.projectKey = requireNonNull(projectKey);
-    this.json = requireNonNull(json);
-  }
-
-  public String getProjectKey() {
-    return projectKey;
-  }
-
-  public String getJson() {
-    return json;
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookPayloadFactory.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookPayloadFactory.java
deleted file mode 100644 (file)
index cf2751f..0000000
+++ /dev/null
@@ -1,27 +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.webhook;
-
-@FunctionalInterface
-public interface WebhookPayloadFactory {
-
-  WebhookPayload create(ProjectAnalysis analysis);
-
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookPayloadFactoryImpl.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookPayloadFactoryImpl.java
deleted file mode 100644 (file)
index 32440c4..0000000
+++ /dev/null
@@ -1,173 +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.webhook;
-
-import java.io.StringWriter;
-import java.io.UnsupportedEncodingException;
-import java.io.Writer;
-import java.net.URLEncoder;
-import java.util.Date;
-import java.util.Map;
-import java.util.Optional;
-import org.sonar.api.ce.ComputeEngineSide;
-import org.sonar.api.platform.Server;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.text.JsonWriter;
-import org.sonar.server.project.Project;
-import org.sonar.server.qualitygate.Condition;
-import org.sonar.server.qualitygate.EvaluatedCondition;
-import org.sonar.server.qualitygate.EvaluatedQualityGate;
-
-import static java.lang.String.format;
-import static org.sonar.core.config.CorePropertyDefinitions.SONAR_ANALYSIS;
-
-@ComputeEngineSide
-public class WebhookPayloadFactoryImpl implements WebhookPayloadFactory {
-
-  private static final String PROPERTY_STATUS = "status";
-  private final Server server;
-  private final System2 system2;
-
-  public WebhookPayloadFactoryImpl(Server server, System2 system2) {
-    this.server = server;
-    this.system2 = system2;
-  }
-
-  @Override
-  public WebhookPayload create(ProjectAnalysis analysis) {
-    Writer string = new StringWriter();
-    try (JsonWriter writer = JsonWriter.of(string)) {
-      writer.beginObject();
-      writeServer(writer);
-      writeTask(writer, analysis.getCeTask());
-      writeDates(writer, analysis, system2);
-      writeProject(analysis, writer, analysis.getProject());
-      analysis.getBranch().ifPresent(b -> writeBranch(writer, analysis.getProject(), b));
-      analysis.getQualityGate().ifPresent(qualityGate -> writeQualityGate(writer, qualityGate));
-      writeAnalysisProperties(writer, analysis.getProperties());
-      writer.endObject().close();
-      return new WebhookPayload(analysis.getProject().getKey(), string.toString());
-    }
-  }
-
-  private void writeServer(JsonWriter writer) {
-    writer.prop("serverUrl", server.getPublicRootUrl());
-  }
-
-  private static void writeDates(JsonWriter writer, ProjectAnalysis analysis, System2 system2) {
-    analysis.getAnalysis().ifPresent(a -> writer.propDateTime("analysedAt", a.getDate()));
-    writer.propDateTime("changedAt", new Date(analysis.getUpdatedAt().orElse(system2.now())));
-  }
-
-  private void writeProject(ProjectAnalysis analysis, JsonWriter writer, Project project) {
-    writer
-      .name("project")
-      .beginObject()
-      .prop("key", project.getKey())
-      .prop("name", analysis.getProject().getName())
-      .prop("url", projectUrlOf(project))
-      .endObject();
-  }
-
-  private static void writeAnalysisProperties(JsonWriter writer, Map<String, String> properties) {
-    writer
-      .name("properties")
-      .beginObject();
-    properties.entrySet()
-      .stream()
-      .filter(prop -> prop.getKey().startsWith(SONAR_ANALYSIS))
-      .forEach(prop -> writer.prop(prop.getKey(), prop.getValue()));
-    writer.endObject();
-  }
-
-  private static void writeTask(JsonWriter writer, Optional<CeTask> ceTask) {
-    ceTask.ifPresent(ceTask1 -> writer.prop("taskId", ceTask1.getId()));
-    writer.prop(PROPERTY_STATUS, ceTask.map(CeTask::getStatus).orElse(CeTask.Status.SUCCESS).toString());
-  }
-
-  private void writeBranch(JsonWriter writer, Project project, Branch branch) {
-    writer
-      .name("branch")
-      .beginObject()
-      .prop("name", branch.getName().orElse(null))
-      .prop("type", branch.getType().name())
-      .prop("isMain", branch.isMain())
-      .prop("url", branchUrlOf(project, branch))
-      .endObject();
-  }
-
-  private String projectUrlOf(Project project) {
-    return format("%s/dashboard?id=%s", server.getPublicRootUrl(), encode(project.getKey()));
-  }
-
-  private String branchUrlOf(Project project, Branch branch) {
-    if (branch.getType() == Branch.Type.LONG) {
-      if (branch.isMain()) {
-        return projectUrlOf(project);
-      }
-      return format("%s/dashboard?branch=%s&id=%s",
-        server.getPublicRootUrl(), encode(branch.getName().orElse("")), encode(project.getKey()));
-    }
-    if (branch.getType() == Branch.Type.SHORT) {
-      return format("%s/project/issues?branch=%s&id=%s&resolved=false",
-        server.getPublicRootUrl(), encode(branch.getName().orElse("")), encode(project.getKey()));
-    }
-    if (branch.getType() == Branch.Type.PULL_REQUEST) {
-      return format("%s/project/issues?pullRequest=%s&id=%s&resolved=false",
-        server.getPublicRootUrl(), encode(branch.getName().orElse("")), encode(project.getKey()));
-    }
-    return projectUrlOf(project);
-  }
-
-  private static void writeQualityGate(JsonWriter writer, EvaluatedQualityGate gate) {
-    writer
-      .name("qualityGate")
-      .beginObject()
-      .prop("name", gate.getQualityGate().getName())
-      .prop(PROPERTY_STATUS, gate.getStatus().toString())
-      .name("conditions")
-      .beginArray();
-    for (EvaluatedCondition evaluatedCondition : gate.getEvaluatedConditions()) {
-      Condition condition = evaluatedCondition.getCondition();
-      writer
-        .beginObject()
-        .prop("metric", condition.getMetricKey())
-        .prop("operator", condition.getOperator().name());
-      evaluatedCondition.getValue().ifPresent(t -> writer.prop("value", t));
-      writer
-        .prop(PROPERTY_STATUS, evaluatedCondition.getStatus().name())
-        .prop("onLeakPeriod", condition.isOnLeakPeriod())
-        .prop("errorThreshold", condition.getErrorThreshold().orElse(null))
-        .prop("warningThreshold", condition.getWarningThreshold().orElse(null))
-        .endObject();
-    }
-    writer
-      .endArray()
-      .endObject();
-  }
-
-  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/server/webhook/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/package-info.java
deleted file mode 100644 (file)
index e4048c7..0000000
+++ /dev/null
@@ -1,24 +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.webhook;
-
-import javax.annotation.ParametersAreNonnullByDefault;
-
diff --git a/server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionExecutorServiceImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionExecutorServiceImplTest.java
deleted file mode 100644 (file)
index 8aa2ee5..0000000
+++ /dev/null
@@ -1,58 +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.async;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class AsyncExecutionExecutorServiceImplTest {
-  private AsyncExecutionExecutorServiceImpl underTest = new AsyncExecutionExecutorServiceImpl();
-
-  @Test
-  public void submit_executes_runnable_in_another_thread() {
-    try (SlowRunnable slowRunnable = new SlowRunnable()) {
-      underTest.submit(slowRunnable);
-      assertThat(slowRunnable.executed).isFalse();
-    }
-  }
-
-  private static final class SlowRunnable implements Runnable, AutoCloseable {
-    private final CountDownLatch latch = new CountDownLatch(1);
-    private volatile boolean executed = false;
-
-    @Override
-    public void run() {
-      try {
-        latch.await(30, TimeUnit.SECONDS);
-      } catch (InterruptedException e) {
-        // ignore
-      }
-      executed = true;
-    }
-
-    @Override
-    public void close() {
-      latch.countDown();
-    }
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionImplTest.java
deleted file mode 100644 (file)
index 0ff2d3c..0000000
+++ /dev/null
@@ -1,68 +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.async;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.utils.log.LogTester;
-import org.sonar.api.utils.log.LoggerLevel;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class AsyncExecutionImplTest {
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  private AsyncExecutionExecutorService synchronousExecutorService = Runnable::run;
-  private AsyncExecutionImpl underTest = new AsyncExecutionImpl(synchronousExecutorService);
-
-  @Test
-  public void addToQueue_fails_with_NPE_if_Runnable_is_null() {
-    expectedException.expect(NullPointerException.class);
-
-    underTest.addToQueue(null);
-  }
-
-  @Test
-  public void addToQueue_submits_runnable_to_executorService_which_does_not_fail_if_Runnable_argument_throws_exception() {
-    underTest.addToQueue(() -> {
-      throw new RuntimeException("Faking an exception thrown by Runnable argument");
-    });
-
-    assertThat(logTester.logs()).hasSize(1);
-    assertThat(logTester.logs(LoggerLevel.ERROR)).containsOnly("Asynchronous task failed");
-  }
-
-  @Test
-  public void addToQueue_submits_runnable_that_fails_if_Runnable_argument_throws_Error() {
-    Error expected = new Error("Faking an exception thrown by Runnable argument");
-    Runnable runnable = () -> {
-      throw expected;
-    };
-
-    expectedException.expect(Error.class);
-    expectedException.expectMessage(expected.getMessage());
-
-    underTest.addToQueue(runnable);
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionMBeanImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionMBeanImplTest.java
deleted file mode 100644 (file)
index e534273..0000000
+++ /dev/null
@@ -1,87 +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.async;
-
-import java.lang.management.ManagementFactory;
-import javax.annotation.CheckForNull;
-import javax.management.InstanceNotFoundException;
-import javax.management.ObjectInstance;
-import javax.management.ObjectName;
-import org.junit.Test;
-import org.mockito.Mockito;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class AsyncExecutionMBeanImplTest {
-  private AsyncExecutionMonitoring asyncExecutionMonitoring = Mockito.mock(AsyncExecutionMonitoring.class);
-
-  private AsyncExecutionMBeanImpl underTest = new AsyncExecutionMBeanImpl(asyncExecutionMonitoring);
-
-  @Test
-  public void register_and_unregister() throws Exception {
-    assertThat(getMBean()).isNull();
-
-    underTest.start();
-    assertThat(getMBean()).isNotNull();
-
-    underTest.stop();
-    assertThat(getMBean()).isNull();
-  }
-
-  @Test
-  public void getQueueSize_delegates_to_AsyncExecutionMonitoring() {
-    when(asyncExecutionMonitoring.getQueueSize()).thenReturn(12);
-
-    assertThat(underTest.getQueueSize()).isEqualTo(12);
-
-    verify(asyncExecutionMonitoring).getQueueSize();
-  }
-
-  @Test
-  public void getWorkerCount_delegates_to_AsyncExecutionMonitoring() {
-    when(asyncExecutionMonitoring.getWorkerCount()).thenReturn(12);
-
-    assertThat(underTest.getWorkerCount()).isEqualTo(12);
-
-    verify(asyncExecutionMonitoring).getWorkerCount();
-  }
-
-  @Test
-  public void getLargestWorkerCount_delegates_to_AsyncExecutionMonitoring() {
-    when(asyncExecutionMonitoring.getLargestWorkerCount()).thenReturn(12);
-
-    assertThat(underTest.getLargestWorkerCount()).isEqualTo(12);
-
-    verify(asyncExecutionMonitoring).getLargestWorkerCount();
-  }
-
-  @CheckForNull
-  private ObjectInstance getMBean() throws Exception {
-    try {
-      return ManagementFactory.getPlatformMBeanServer().getObjectInstance(new ObjectName(AsyncExecutionMBean.OBJECT_NAME));
-    } catch (InstanceNotFoundException e) {
-      return null;
-    }
-  }
-
-
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/ProjectTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/ProjectTest.java
deleted file mode 100644 (file)
index 4f2a351..0000000
+++ /dev/null
@@ -1,78 +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.project;
-
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class ProjectTest {
-  @Test
-  public void test_bean_without_description() {
-    Project project1 = new Project("U1", "K1", "N1");
-    Project project2 = new Project("U1", "K1", "N1", null);
-
-    assertThat(project1.getUuid()).isEqualTo(project2.getUuid()).isEqualTo("U1");
-    assertThat(project1.getKey()).isEqualTo(project2.getKey()).isEqualTo("K1");
-    assertThat(project1.getName()).isEqualTo(project2.getName()).isEqualTo("N1");
-    assertThat(project1.getDescription()).isEqualTo(project2.getDescription()).isNull();
-
-    assertThat(project1.toString())
-      .isEqualTo(project2.toString())
-      .isEqualTo("Project{uuid='U1', key='K1', name='N1', description=null}");
-  }
-
-  @Test
-  public void test_bean_with_description() {
-    Project project1 = new Project("U1", "K1", "N1", "D1");
-
-    assertThat(project1.getUuid()).isEqualTo("U1");
-    assertThat(project1.getKey()).isEqualTo("K1");
-    assertThat(project1.getName()).isEqualTo("N1");
-    assertThat(project1.getDescription()).isEqualTo("D1");
-
-    assertThat(project1.toString())
-      .isEqualTo(project1.toString())
-      .isEqualTo("Project{uuid='U1', key='K1', name='N1', description='D1'}");
-  }
-
-  @Test
-  public void test_equals_and_hashCode() {
-    Project project1 = new Project("U1", "K1", "N1");
-    Project project2 = new Project("U1", "K1", "N1", "D1");
-
-    assertThat(project1).isEqualTo(project1);
-    assertThat(project1).isNotEqualTo(null);
-    assertThat(project1).isNotEqualTo(new Object());
-    assertThat(project1).isEqualTo(new Project("U1", "K1", "N1", null));
-    assertThat(project1).isNotEqualTo(new Project("U1", "K2", "N1", null));
-    assertThat(project1).isNotEqualTo(new Project("U1", "K1", "N2", null));
-    assertThat(project1).isEqualTo(project2);
-
-    assertThat(project1.hashCode()).isEqualTo(project1.hashCode());
-    assertThat(project1.hashCode()).isNotEqualTo(null);
-    assertThat(project1.hashCode()).isNotEqualTo(new Object().hashCode());
-    assertThat(project1.hashCode()).isEqualTo(new Project("U1", "K1", "N1", null).hashCode());
-    assertThat(project1.hashCode()).isNotEqualTo(new Project("U1", "K2", "N1", null).hashCode());
-    assertThat(project1.hashCode()).isNotEqualTo(new Project("U1", "K1", "N2", null).hashCode());
-    assertThat(project1.hashCode()).isEqualTo(project2.hashCode());
-  }
-
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ConditionTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ConditionTest.java
deleted file mode 100644 (file)
index ba0b79a..0000000
+++ /dev/null
@@ -1,128 +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.qualitygate;
-
-import java.util.Arrays;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class ConditionTest {
-  private static final String METRIC_KEY = "metric_key";
-  private static final Condition.Operator OPERATOR = Condition.Operator.EQUALS;
-  private static final String ERROR_THRESHOLD = "2";
-  private static final String WARN_THRESHOLD = "4";
-  private static final boolean ON_LEAK_PERIOD = true;
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  private Condition underTest = new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD);
-
-  @Test
-  public void constructor_throws_NPE_if_metricKey_is_null() {
-    expectedException.expect(NullPointerException.class);
-    expectedException.expectMessage("metricKey can't be null");
-
-    new Condition(null, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD);
-  }
-
-  @Test
-  public void constructor_throws_NPE_if_operator_is_null() {
-    expectedException.expect(NullPointerException.class);
-    expectedException.expectMessage("operator can't be null");
-
-    new Condition(METRIC_KEY, null, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD);
-  }
-
-  @Test
-  public void errorThreshold_can_be_null() {
-    Condition underTest = new Condition(METRIC_KEY, OPERATOR, null, WARN_THRESHOLD, ON_LEAK_PERIOD);
-
-    assertThat(underTest.getErrorThreshold()).isEmpty();
-  }
-
-  @Test
-  public void warnThreshold_can_be_null() {
-    Condition underTest = new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, null, ON_LEAK_PERIOD);
-
-    assertThat(underTest.getWarningThreshold()).isEmpty();
-  }
-
-  @Test
-  public void verify_getters() {
-    assertThat(underTest.getMetricKey()).isEqualTo(METRIC_KEY);
-    assertThat(underTest.getOperator()).isEqualTo(OPERATOR);
-    assertThat(underTest.getErrorThreshold()).contains(ERROR_THRESHOLD);
-    assertThat(underTest.getWarningThreshold()).contains(WARN_THRESHOLD);
-    assertThat(underTest.isOnLeakPeriod()).isEqualTo(ON_LEAK_PERIOD);
-  }
-
-  @Test
-  public void toString_is_override() {
-    assertThat(underTest.toString())
-      .isEqualTo("Condition{metricKey='metric_key', operator=EQUALS, warningThreshold='4', errorThreshold='2', onLeakPeriod=true}");
-  }
-
-  @Test
-  public void toString_does_not_quote_nulls() {
-    Condition withNulls = new Condition("metric_key", Condition.Operator.LESS_THAN, null, null, false);
-    assertThat(withNulls.toString())
-      .isEqualTo("Condition{metricKey='metric_key', operator=LESS_THAN, warningThreshold=null, errorThreshold=null, onLeakPeriod=false}");
-  }
-
-  @Test
-  public void equals_is_based_on_all_fields() {
-    assertThat(underTest).isEqualTo(underTest);
-    assertThat(underTest).isNotEqualTo(null);
-    assertThat(underTest).isNotEqualTo(new Object());
-    assertThat(underTest).isEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD));
-    assertThat(underTest).isNotEqualTo(new Condition("other_metric_key", OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD));
-    Arrays.stream(Condition.Operator.values())
-        .filter(s -> !OPERATOR.equals(s))
-        .forEach(otherOperator ->  assertThat(underTest)
-            .isNotEqualTo(new Condition(METRIC_KEY, otherOperator, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD)));
-    assertThat(underTest).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, null, WARN_THRESHOLD, ON_LEAK_PERIOD));
-    assertThat(underTest).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, "other_error_threshold", WARN_THRESHOLD, ON_LEAK_PERIOD));
-    assertThat(underTest).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, null, ON_LEAK_PERIOD));
-    assertThat(underTest).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, "other_warn_threshold", ON_LEAK_PERIOD));
-    assertThat(underTest).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, !ON_LEAK_PERIOD));
-  }
-
-  @Test
-  public void hashcode_is_based_on_all_fields() {
-    assertThat(underTest.hashCode()).isEqualTo(underTest.hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(null);
-    assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode());
-    assertThat(underTest.hashCode()).isEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new Condition("other_metric_key", OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD).hashCode());
-    Arrays.stream(Condition.Operator.values())
-        .filter(s -> !OPERATOR.equals(s))
-        .forEach(otherOperator ->  assertThat(underTest.hashCode())
-            .isNotEqualTo(new Condition(METRIC_KEY, otherOperator, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD).hashCode()));
-    assertThat(underTest.hashCode()).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, null, WARN_THRESHOLD, ON_LEAK_PERIOD).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, "other_error_threshold", WARN_THRESHOLD, ON_LEAK_PERIOD).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, null, ON_LEAK_PERIOD).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, "other_warn_threshold", ON_LEAK_PERIOD).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, !ON_LEAK_PERIOD).hashCode());
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java
deleted file mode 100644 (file)
index 7cadd61..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.qualitygate;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.server.qualitygate.Condition.Operator.EQUALS;
-import static org.sonar.server.qualitygate.EvaluatedCondition.EvaluationStatus.OK;
-import static org.sonar.server.qualitygate.EvaluatedCondition.EvaluationStatus.WARN;
-
-public class EvaluatedConditionTest {
-  private static final Condition CONDITION_1 = new Condition("metricKey", EQUALS, "2", "4", false);
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  private EvaluatedCondition underTest = new EvaluatedCondition(CONDITION_1, WARN, "value");
-
-  @Test
-  public void constructor_throws_NPE_if_condition_is_null() {
-    expectedException.expect(NullPointerException.class);
-    expectedException.expectMessage("condition can't be null");
-
-    new EvaluatedCondition(null, WARN, "value");
-  }
-
-  @Test
-  public void constructor_throws_NPE_if_EvaluationStatus_is_null() {
-    expectedException.expect(NullPointerException.class);
-    expectedException.expectMessage("status can't be null");
-
-    new EvaluatedCondition(CONDITION_1, null, "value");
-  }
-
-  @Test
-  public void constructor_accepts_null_value() {
-    EvaluatedCondition underTest = new EvaluatedCondition(CONDITION_1, WARN, null);
-
-    assertThat(underTest.getValue()).isEmpty();
-  }
-
-  @Test
-  public void verify_getters() {
-    EvaluatedCondition underTest = new EvaluatedCondition(CONDITION_1, WARN, "value");
-
-    assertThat(underTest.getCondition()).isEqualTo(CONDITION_1);
-    assertThat(underTest.getStatus()).isEqualTo(WARN);
-    assertThat(underTest.getValue()).contains("value");
-  }
-
-  @Test
-  public void override_toString() {
-    assertThat(underTest.toString()).isEqualTo("EvaluatedCondition{condition=" +
-      "Condition{metricKey='metricKey', operator=EQUALS, warningThreshold='4', errorThreshold='2', onLeakPeriod=false}, " +
-      "status=WARN, value='value'}");
-  }
-
-  @Test
-  public void toString_does_not_quote_null_value() {
-    EvaluatedCondition underTest = new EvaluatedCondition(CONDITION_1, WARN, null);
-
-    assertThat(underTest.toString()).isEqualTo("EvaluatedCondition{condition=" +
-      "Condition{metricKey='metricKey', operator=EQUALS, warningThreshold='4', errorThreshold='2', onLeakPeriod=false}, " +
-      "status=WARN, value=null}");
-  }
-
-  @Test
-  public void equals_is_based_on_all_fields() {
-    assertThat(underTest).isEqualTo(underTest);
-    assertThat(underTest).isEqualTo(new EvaluatedCondition(CONDITION_1, WARN, "value"));
-    assertThat(underTest).isNotEqualTo(null);
-    assertThat(underTest).isNotEqualTo(new Object());
-    assertThat(underTest).isNotEqualTo(new EvaluatedCondition(new Condition("other_metric", EQUALS, "a", "b", true), WARN, "value"));
-    assertThat(underTest).isNotEqualTo(new EvaluatedCondition(CONDITION_1, OK, "value"));
-    assertThat(underTest).isNotEqualTo(new EvaluatedCondition(CONDITION_1, WARN, null));
-    assertThat(underTest).isNotEqualTo(new EvaluatedCondition(CONDITION_1, WARN, "other_value"));
-  }
-
-  @Test
-  public void hashcode_is_based_on_all_fields() {
-    assertThat(underTest.hashCode()).isEqualTo(underTest.hashCode());
-    assertThat(underTest.hashCode()).isEqualTo(new EvaluatedCondition(CONDITION_1, WARN, "value").hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(null);
-    assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new EvaluatedCondition(new Condition("other_metric", EQUALS, "a", "b", true), WARN, "value").hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new EvaluatedCondition(CONDITION_1, OK, "value").hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new EvaluatedCondition(CONDITION_1, WARN, null).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new EvaluatedCondition(CONDITION_1, WARN, "other_value").hashCode());
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java
deleted file mode 100644 (file)
index b02d5dd..0000000
+++ /dev/null
@@ -1,200 +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.qualitygate;
-
-import com.google.common.collect.ImmutableSet;
-import java.util.Random;
-import org.apache.commons.lang.RandomStringUtils;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.measures.Metric.Level;
-
-import static java.util.Collections.emptySet;
-import static java.util.Collections.singleton;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.server.qualitygate.EvaluatedQualityGate.newBuilder;
-
-public class EvaluatedQualityGateTest {
-  private static final String QUALITY_GATE_ID = "qg_id";
-  private static final String QUALITY_GATE_NAME = "qg_name";
-  private static final QualityGate NO_CONDITION_QUALITY_GATE = new QualityGate(QUALITY_GATE_ID, QUALITY_GATE_NAME, emptySet());
-  private static final Condition CONDITION_1 = new Condition("metric_key", Condition.Operator.LESS_THAN, "2", "4", true);
-  private static final Condition CONDITION_2 = new Condition("metric_key_2", Condition.Operator.GREATER_THAN, "6", "12", false);
-  private static final QualityGate ONE_CONDITION_QUALITY_GATE = new QualityGate(QUALITY_GATE_ID, QUALITY_GATE_NAME, singleton(CONDITION_1));
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  private final Random random = new Random();
-  private final Level randomStatus = Level.values()[random.nextInt(Level.values().length)];
-  private final EvaluatedCondition.EvaluationStatus randomEvaluationStatus = EvaluatedCondition.EvaluationStatus.values()[random
-    .nextInt(EvaluatedCondition.EvaluationStatus.values().length)];
-  private final String randomValue = random.nextBoolean() ? null : RandomStringUtils.randomAlphanumeric(3);
-
-  private EvaluatedQualityGate.Builder builder = newBuilder();
-
-  @Test
-  public void build_fails_with_NPE_if_status_not_set() {
-    builder.setQualityGate(NO_CONDITION_QUALITY_GATE);
-
-    expectedException.expect(NullPointerException.class);
-    expectedException.expectMessage("status can't be null");
-
-    builder.build();
-  }
-
-  @Test
-  public void addCondition_fails_with_NPE_if_condition_is_null() {
-    expectedException.expect(NullPointerException.class);
-    expectedException.expectMessage("condition can't be null");
-
-    builder.addCondition(null, EvaluatedCondition.EvaluationStatus.WARN, "a_value");
-  }
-
-  @Test
-  public void addCondition_fails_with_NPE_if_status_is_null() {
-    expectedException.expect(NullPointerException.class);
-    expectedException.expectMessage("status can't be null");
-
-    builder.addCondition(new Condition("metric_key", Condition.Operator.LESS_THAN, "2", "4", true), null, "a_value");
-  }
-
-  @Test
-  public void addCondition_accepts_null_value() {
-    builder.addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.NO_VALUE, null);
-
-    assertThat(builder.getEvaluatedConditions())
-      .containsOnly(new EvaluatedCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.NO_VALUE, null));
-  }
-
-  @Test
-  public void getEvaluatedConditions_returns_empty_with_no_condition_added_to_builder() {
-    assertThat(builder.getEvaluatedConditions()).isEmpty();
-  }
-
-  @Test
-  public void build_fails_with_IAE_if_condition_added_and_no_on_QualityGate() {
-    builder.setQualityGate(NO_CONDITION_QUALITY_GATE)
-      .setStatus(randomStatus)
-      .addCondition(CONDITION_1, randomEvaluationStatus, randomValue);
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Evaluation provided for unknown conditions: [" + CONDITION_1 + "]");
-
-    builder.build();
-  }
-
-  @Test
-  public void build_fails_with_IAE_if_condition_is_missing_for_one_defined_in_QualityGate() {
-    builder.setQualityGate(ONE_CONDITION_QUALITY_GATE)
-      .setStatus(randomStatus);
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Evaluation missing for the following conditions: [" + CONDITION_1 + "]");
-
-    builder.build();
-  }
-
-  @Test
-  public void verify_getters() {
-    EvaluatedQualityGate underTest = builder
-      .setQualityGate(ONE_CONDITION_QUALITY_GATE)
-      .setStatus(randomStatus)
-      .addCondition(CONDITION_1, randomEvaluationStatus, randomValue)
-      .build();
-
-    assertThat(underTest.getQualityGate()).isEqualTo(ONE_CONDITION_QUALITY_GATE);
-    assertThat(underTest.getStatus()).isEqualTo(randomStatus);
-    assertThat(underTest.getEvaluatedConditions())
-      .containsOnly(new EvaluatedCondition(CONDITION_1, randomEvaluationStatus, randomValue));
-  }
-
-  @Test
-  public void verify_getters_when_no_condition() {
-    EvaluatedQualityGate underTest = builder
-      .setQualityGate(NO_CONDITION_QUALITY_GATE)
-      .setStatus(randomStatus)
-      .build();
-
-    assertThat(underTest.getQualityGate()).isEqualTo(NO_CONDITION_QUALITY_GATE);
-    assertThat(underTest.getStatus()).isEqualTo(randomStatus);
-    assertThat(underTest.getEvaluatedConditions()).isEmpty();
-  }
-
-  @Test
-  public void verify_getters_when_multiple_conditions() {
-    QualityGate qualityGate = new QualityGate(QUALITY_GATE_ID, QUALITY_GATE_NAME, ImmutableSet.of(CONDITION_1, CONDITION_2));
-    EvaluatedQualityGate underTest = builder
-      .setQualityGate(qualityGate)
-      .setStatus(randomStatus)
-      .addCondition(CONDITION_1, randomEvaluationStatus, randomValue)
-      .addCondition(CONDITION_2, EvaluatedCondition.EvaluationStatus.WARN, "bad")
-      .build();
-
-    assertThat(underTest.getQualityGate()).isEqualTo(qualityGate);
-    assertThat(underTest.getStatus()).isEqualTo(randomStatus);
-    assertThat(underTest.getEvaluatedConditions()).containsOnly(
-      new EvaluatedCondition(CONDITION_1, randomEvaluationStatus, randomValue),
-      new EvaluatedCondition(CONDITION_2, EvaluatedCondition.EvaluationStatus.WARN, "bad"));
-  }
-
-  @Test
-  public void equals_is_based_on_all_fields() {
-    EvaluatedQualityGate.Builder builder = this.builder
-      .setQualityGate(ONE_CONDITION_QUALITY_GATE)
-      .setStatus(Level.WARN)
-      .addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.WARN, "foo");
-
-    EvaluatedQualityGate underTest = builder.build();
-    assertThat(underTest).isEqualTo(builder.build());
-    assertThat(underTest).isNotSameAs(builder.build());
-    assertThat(underTest).isNotEqualTo(null);
-    assertThat(underTest).isNotEqualTo(new Object());
-    assertThat(underTest).isNotEqualTo(builder.setQualityGate(new QualityGate("other_id", QUALITY_GATE_NAME, singleton(CONDITION_1))).build());
-    assertThat(underTest).isNotEqualTo(builder.setQualityGate(ONE_CONDITION_QUALITY_GATE).setStatus(Level.OK).build());
-    assertThat(underTest).isNotEqualTo(newBuilder()
-      .setQualityGate(ONE_CONDITION_QUALITY_GATE)
-      .setStatus(Level.WARN)
-      .addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.OK, "foo")
-      .build());
-  }
-
-  @Test
-  public void hashcode_is_based_on_all_fields() {
-    EvaluatedQualityGate.Builder builder = this.builder
-      .setQualityGate(ONE_CONDITION_QUALITY_GATE)
-      .setStatus(Level.WARN)
-      .addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.WARN, "foo");
-
-    EvaluatedQualityGate underTest = builder.build();
-    assertThat(underTest.hashCode()).isEqualTo(builder.build().hashCode());
-    assertThat(underTest.hashCode()).isNotSameAs(builder.build().hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(null);
-    assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(builder.setQualityGate(new QualityGate("other_id", QUALITY_GATE_NAME, singleton(CONDITION_1))).build().hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(builder.setQualityGate(ONE_CONDITION_QUALITY_GATE).setStatus(Level.OK).build().hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(newBuilder()
-      .setQualityGate(ONE_CONDITION_QUALITY_GATE)
-      .setStatus(Level.WARN)
-      .addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.OK, "foo")
-      .build().hashCode());
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateTest.java
deleted file mode 100644 (file)
index d1fe1dd..0000000
+++ /dev/null
@@ -1,135 +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.qualitygate;
-
-import com.google.common.collect.ImmutableSet;
-import java.util.Random;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import java.util.stream.Stream;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-
-import static java.util.Collections.emptySet;
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class QualityGateTest {
-  private static final String QUALIGATE_ID = "qg_id";
-  private static final String QUALIGATE_NAME = "qg_name";
-  private static final Condition CONDITION_1 = new Condition("m1", Condition.Operator.EQUALS, "1", "2", false);
-  private static final Condition CONDITION_2 = new Condition("m2", Condition.Operator.LESS_THAN, "2", "4", true);
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  private QualityGate underTest = new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_1, CONDITION_2));
-
-  @Test
-  public void constructor_fails_with_NPE_if_id_is_null() {
-    expectedException.expect(NullPointerException.class);
-    expectedException.expectMessage("id can't be null");
-
-    new QualityGate(null, "name", emptySet());
-  }
-
-  @Test
-  public void constructor_fails_with_NPE_if_name_is_null() {
-    expectedException.expect(NullPointerException.class);
-    expectedException.expectMessage("name can't be null");
-
-    new QualityGate("id", null, emptySet());
-  }
-
-  @Test
-  public void constructor_fails_with_NPE_if_conditions_is_null() {
-    expectedException.expect(NullPointerException.class);
-    expectedException.expectMessage("conditions can't be null");
-
-    new QualityGate("id", "name", null);
-  }
-
-  @Test
-  public void constructor_fails_with_NPE_if_conditions_contains_null() {
-    expectedException.expect(NullPointerException.class);
-    expectedException.expectMessage("condition can't be null");
-    Random random = new Random();
-    Set<Condition> conditions = Stream.of(
-      IntStream.range(0, random.nextInt(5))
-        .mapToObj(i -> new Condition("m_before_" + i, Condition.Operator.EQUALS, null, null, false)),
-      Stream.of((Condition) null),
-      IntStream.range(0, random.nextInt(5))
-        .mapToObj(i -> new Condition("m_after_" + i, Condition.Operator.EQUALS, null, null, false)))
-      .flatMap(s -> s)
-      .collect(Collectors.toSet());
-
-    expectedException.expect(NullPointerException.class);
-    expectedException.expectMessage("condition can't be null");
-
-    new QualityGate("id", "name", conditions);
-  }
-
-  @Test
-  public void verify_getters() {
-    assertThat(underTest.getId()).isEqualTo(QUALIGATE_ID);
-    assertThat(underTest.getName()).isEqualTo(QUALIGATE_NAME);
-    assertThat(underTest.getConditions()).containsOnly(CONDITION_1, CONDITION_2);
-  }
-
-  @Test
-  public void toString_is_override() {
-    QualityGate underTest = new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_2));
-
-    assertThat(underTest.toString()).isEqualTo("QualityGate{id=qg_id, name='qg_name', conditions=[" +
-      "Condition{metricKey='m2', operator=LESS_THAN, warningThreshold='4', errorThreshold='2', onLeakPeriod=true}" +
-      "]}");
-  }
-
-  @Test
-  public void equals_is_based_on_all_fields() {
-    assertThat(underTest).isEqualTo(underTest);
-    assertThat(underTest).isEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_2, CONDITION_1)));
-    assertThat(underTest).isNotEqualTo(null);
-    assertThat(underTest).isNotEqualTo(new Object());
-    assertThat(underTest).isNotEqualTo(new QualityGate("other_id", QUALIGATE_NAME, ImmutableSet.of(CONDITION_2, CONDITION_1)));
-    assertThat(underTest).isNotEqualTo(new QualityGate(QUALIGATE_ID, "other_name", ImmutableSet.of(CONDITION_2, CONDITION_1)));
-    assertThat(underTest).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, emptySet()));
-    assertThat(underTest).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_1)));
-    assertThat(underTest).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_2)));
-    assertThat(underTest).isNotEqualTo(
-      new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_1, CONDITION_2, new Condition("new", Condition.Operator.GREATER_THAN, "a", "b", false))));
-  }
-
-  @Test
-  public void hashcode_is_based_on_all_fields() {
-    assertThat(underTest.hashCode()).isEqualTo(underTest.hashCode());
-    assertThat(underTest.hashCode()).isEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_2, CONDITION_1)).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(null);
-    assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new QualityGate("other_id", QUALIGATE_NAME, ImmutableSet.of(CONDITION_2, CONDITION_1)).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new QualityGate(QUALIGATE_ID, "other_name", ImmutableSet.of(CONDITION_2, CONDITION_1)).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, emptySet()).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_1)).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_2)).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(
-      new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_1, CONDITION_2, new Condition("new", Condition.Operator.GREATER_THAN, "a", "b", false))).hashCode());
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/util/OkHttpClientProviderTest.java b/server/sonar-server/src/test/java/org/sonar/server/util/OkHttpClientProviderTest.java
deleted file mode 100644 (file)
index b6f2f03..0000000
+++ /dev/null
@@ -1,88 +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.util;
-
-import java.io.IOException;
-import java.util.Base64;
-import okhttp3.OkHttpClient;
-import okhttp3.Protocol;
-import okhttp3.Request;
-import okhttp3.Response;
-import okhttp3.mockwebserver.MockResponse;
-import okhttp3.mockwebserver.MockWebServer;
-import okhttp3.mockwebserver.RecordedRequest;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.SonarQubeSide;
-import org.sonar.api.SonarRuntime;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.internal.SonarRuntimeImpl;
-import org.sonar.api.utils.Version;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class OkHttpClientProviderTest {
-
-  private MapSettings settings = new MapSettings();
-  private SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.parse("6.2"), SonarQubeSide.SERVER);
-  private final OkHttpClientProvider underTest = new OkHttpClientProvider();
-
-  @Rule
-  public MockWebServer server = new MockWebServer();
-
-  @Test
-  public void get_returns_a_OkHttpClient_with_default_configuration() throws Exception {
-    OkHttpClient client = underTest.provide(settings.asConfig(), runtime);
-
-    assertThat(client.connectTimeoutMillis()).isEqualTo(10_000);
-    assertThat(client.readTimeoutMillis()).isEqualTo(10_000);
-    assertThat(client.proxy()).isNull();
-
-    RecordedRequest recordedRequest = call(client);
-    assertThat(recordedRequest.getHeader("User-Agent")).isEqualTo("SonarQube/6.2");
-    assertThat(recordedRequest.getHeader("Proxy-Authorization")).isNull();
-  }
-
-  @Test
-  public void get_returns_a_OkHttpClient_with_proxy_authentication() throws Exception {
-    settings.setProperty("http.proxyUser", "the-login");
-    settings.setProperty("http.proxyPassword", "the-password");
-
-    OkHttpClient client = underTest.provide(settings.asConfig(), runtime);
-    Response response = new Response.Builder().protocol(Protocol.HTTP_1_1).request(new Request.Builder().url("http://foo").build()).code(407).build();
-    Request request = client.proxyAuthenticator().authenticate(null, response);
-
-    assertThat(request.header("Proxy-Authorization")).isEqualTo("Basic " + Base64.getEncoder().encodeToString("the-login:the-password".getBytes()));
-  }
-
-  @Test
-  public void get_returns_a_singleton() {
-    OkHttpClient client1 = underTest.provide(settings.asConfig(), runtime);
-    OkHttpClient client2 = underTest.provide(settings.asConfig(), runtime);
-    assertThat(client2).isNotNull().isSameAs(client1);
-  }
-
-  private RecordedRequest call(OkHttpClient client) throws IOException, InterruptedException {
-    server.enqueue(new MockResponse().setBody("pong"));
-    client.newCall(new Request.Builder().url(server.url("/ping")).build()).execute();
-
-    return server.takeRequest();
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/AnalysisTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/AnalysisTest.java
deleted file mode 100644 (file)
index 5f307d9..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.server.webhook;
-
-import java.util.Date;
-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 AnalysisTest {
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  @Test
-  public void constructor_throws_NPE_when_uuid_is_null() {
-    expectedException.expect(NullPointerException.class);
-    expectedException.expectMessage("uuid must not be null");
-
-    new Analysis(null, 1_990L);
-  }
-
-  @Test
-  public void test_equality() {
-    String uuid = randomAlphanumeric(35);
-    long date = new Random().nextLong();
-    Analysis underTest = new Analysis(uuid, date);
-
-    assertThat(underTest).isEqualTo(underTest);
-    assertThat(underTest.getUuid()).isEqualTo(uuid);
-    assertThat(underTest.getDate()).isEqualTo(new Date(date));
-
-    assertThat(underTest).isNotEqualTo(null);
-    assertThat(underTest).isNotEqualTo(new Analysis(uuid + "1", date));
-    assertThat(underTest).isNotEqualTo(new Analysis(uuid, date + 1_000L));
-  }
-
-  @Test
-  public void test_hashcode() {
-    String uuid = randomAlphanumeric(35);
-    long date = new Random().nextLong();
-    Analysis underTest = new Analysis(uuid, date);
-
-    assertThat(underTest.hashCode()).isEqualTo(underTest.hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new Analysis(uuid + "1", date).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new Analysis(uuid, date + 1_000).hashCode());
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/AsynchronousWebHooksImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/AsynchronousWebHooksImplTest.java
deleted file mode 100644 (file)
index b7ca6d7..0000000
+++ /dev/null
@@ -1,106 +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.webhook;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.organization.OrganizationDbTester;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.webhook.WebhookDbTester;
-import org.sonar.server.async.AsyncExecution;
-import org.sonar.server.organization.DefaultOrganizationProvider;
-
-import static java.util.Objects.requireNonNull;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.sonar.db.DbTester.create;
-import static org.sonar.db.webhook.WebhookTesting.newWebhook;
-import static org.sonar.server.organization.TestDefaultOrganizationProvider.from;
-
-public class AsynchronousWebHooksImplTest {
-
-  private System2 system2 = mock(System2.class);
-
-  @Rule
-  public DbTester db = create(system2);
-  private WebhookDbTester webhookDbTester = db.webhooks();
-  private ComponentDbTester componentDbTester = db.components();
-  private OrganizationDbTester organizationDbTester = db.organizations();
-  private DefaultOrganizationProvider defaultOrganizationProvider = from(db);
-
-  private static final long NOW = 1_500_000_000_000L;
-
-
-  private final TestWebhookCaller caller = new TestWebhookCaller();
-  private final WebhookDeliveryStorage deliveryStorage = mock(WebhookDeliveryStorage.class);
-  private final WebhookPayload mock = mock(WebhookPayload.class);
-  private final RecordingAsyncExecution asyncExecution = new RecordingAsyncExecution();
-
-  private final WebHooksImpl underTest = new WebHooksImpl(caller, deliveryStorage, asyncExecution, db.getDbClient());
-
-  @Test
-  public void send_global_webhooks() {
-
-    OrganizationDto organizationDto = db.getDefaultOrganization() ;
-    ComponentDto project = componentDbTester.insertPrivateProject(componentDto -> componentDto.setOrganizationUuid(organizationDto.getUuid()));
-    webhookDbTester.insert(newWebhook(organizationDto).setName("First").setUrl("http://url1"));
-    webhookDbTester.insert(newWebhook(organizationDto).setName("Second").setUrl("http://url2"));
-
-    caller.enqueueSuccess(NOW, 200, 1_234);
-    caller.enqueueFailure(NOW, new IOException("Fail to connect"));
-
-    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(project.uuid(), "1", "#1"), () -> mock);
-
-    assertThat(caller.countSent()).isZero();
-    verifyZeroInteractions(deliveryStorage);
-
-    asyncExecution.executeRecorded();
-
-    assertThat(caller.countSent()).isEqualTo(2);
-    verify(deliveryStorage, times(2)).persist(any(WebhookDelivery.class));
-    verify(deliveryStorage).purge(project.uuid());
-  }
-
-  private static class RecordingAsyncExecution implements AsyncExecution {
-    private final List<Runnable> runnableList = new ArrayList<>();
-
-    @Override
-    public void addToQueue(Runnable r) {
-      runnableList.add(requireNonNull(r));
-    }
-
-    public void executeRecorded() {
-      ArrayList<Runnable> runnables = new ArrayList<>(runnableList);
-      runnableList.clear();
-      runnables.forEach(Runnable::run);
-    }
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/BranchTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/BranchTest.java
deleted file mode 100644 (file)
index b935d89..0000000
+++ /dev/null
@@ -1,55 +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.webhook;
-
-import java.util.Random;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class BranchTest {
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  private Branch underTest = new Branch(true, "b", Branch.Type.SHORT);
-
-  @Test
-  public void constructor_throws_NPE_if_type_is_null() {
-    expectedException.expect(NullPointerException.class);
-    expectedException.expectMessage("type can't be null");
-
-    new Branch(new Random().nextBoolean(), "s", null);
-  }
-
-  @Test
-  public void verify_getters() {
-    assertThat(underTest.isMain()).isTrue();
-    assertThat(underTest.getName()).contains("b");
-    assertThat(underTest.getType()).isEqualTo(Branch.Type.SHORT);
-
-
-    Branch underTestWithNull = new Branch(false, null, Branch.Type.LONG);
-    assertThat(underTestWithNull.isMain()).isFalse();
-    assertThat(underTestWithNull.getName()).isEmpty();
-    assertThat(underTestWithNull.getType()).isEqualTo(Branch.Type.LONG);
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/CeTaskTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/CeTaskTest.java
deleted file mode 100644 (file)
index 0ed58b7..0000000
+++ /dev/null
@@ -1,55 +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.webhook;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class CeTaskTest {
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  private CeTask underTest = new CeTask("A", CeTask.Status.SUCCESS);
-
-  @Test
-  public void constructor_throws_NPE_if_id_is_null() {
-    expectedException.expect(NullPointerException.class);
-    expectedException.expectMessage("id can't be null");
-
-    new CeTask(null, CeTask.Status.SUCCESS);
-  }
-
-  @Test
-  public void constructor_throws_NPE_if_status_is_null() {
-    expectedException.expect(NullPointerException.class);
-    expectedException.expectMessage("status can't be null");
-
-    new CeTask("B", null);
-  }
-
-  @Test
-  public void verify_getters() {
-    assertThat(underTest.getId()).isEqualTo("A");
-    assertThat(underTest.getStatus()).isEqualTo(CeTask.Status.SUCCESS);
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/ProjectAnalysisTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/ProjectAnalysisTest.java
deleted file mode 100644 (file)
index 0a45370..0000000
+++ /dev/null
@@ -1,154 +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.webhook;
-
-import com.google.common.collect.ImmutableMap;
-import java.util.Map;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.measures.Metric;
-import org.sonar.server.project.Project;
-import org.sonar.server.qualitygate.EvaluatedQualityGate;
-import org.sonar.server.qualitygate.QualityGate;
-
-import static java.util.Collections.emptyMap;
-import static java.util.Collections.emptySet;
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class ProjectAnalysisTest {
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  private final CeTask ceTask = new CeTask("id", CeTask.Status.SUCCESS);
-  private final Project project = new Project("uuid", "key", "name");
-  private final Analysis analysis = new Analysis("analysis_uuid", 1_500L);
-  private final Branch branch = new Branch(true, "name", Branch.Type.SHORT);
-  private final EvaluatedQualityGate qualityGate = EvaluatedQualityGate.newBuilder()
-    .setQualityGate(new QualityGate("id", "name", emptySet()))
-    .setStatus(Metric.Level.WARN)
-    .build();
-  private final Map<String, String> properties = ImmutableMap.of("a", "b");
-  private ProjectAnalysis underTest = new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, 1L, properties);
-
-  @Test
-  public void constructor_throws_NPE_if_project_is_null() {
-    expectedException.expect(NullPointerException.class);
-    expectedException.expectMessage("project can't be null");
-
-    new ProjectAnalysis(null,
-      ceTask,
-      analysis,
-      branch,
-      qualityGate,
-      1L,
-      emptyMap());
-  }
-
-  @Test
-  public void constructor_throws_NPE_if_properties_is_null() {
-    expectedException.expect(NullPointerException.class);
-    expectedException.expectMessage("properties can't be null");
-
-    new ProjectAnalysis(project,
-      ceTask,
-      analysis,
-      branch,
-      qualityGate,
-      1L,
-      null);
-  }
-
-  @Test
-  public void verify_getters() {
-    assertThat(underTest.getCeTask().get()).isSameAs(ceTask);
-    assertThat(underTest.getProject()).isSameAs(project);
-    assertThat(underTest.getBranch().get()).isSameAs(branch);
-    assertThat(underTest.getQualityGate().get()).isSameAs(qualityGate);
-    assertThat(underTest.getProperties()).isEqualTo(properties);
-    assertThat(underTest.getAnalysis().get()).isEqualTo(analysis);
-
-    ProjectAnalysis underTestWithNulls = new ProjectAnalysis(project, null, null, null, null, null, emptyMap());
-    assertThat(underTestWithNulls.getCeTask()).isEmpty();
-    assertThat(underTestWithNulls.getBranch()).isEmpty();
-    assertThat(underTestWithNulls.getQualityGate()).isEmpty();
-    assertThat(underTestWithNulls.getProperties()).isEmpty();
-    assertThat(underTestWithNulls.getAnalysis()).isEmpty();
-  }
-
-  @Test
-  public void defines_equals_based_on_all_fields() {
-    assertThat(underTest).isEqualTo(underTest);
-    assertThat(underTest).isEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, 1L, properties));
-    assertThat(underTest).isNotEqualTo(null);
-    assertThat(underTest).isNotEqualTo(new Object());
-    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, new CeTask("2", CeTask.Status.SUCCESS), analysis, branch, qualityGate, 1L, properties));
-    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(new Project("A", "B", "C"), ceTask, analysis, branch, qualityGate, 1L, properties));
-    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(new Project("A", "B", "C"), ceTask, analysis, branch, qualityGate, 1L, properties));
-    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, null, null, null, qualityGate, 1L, properties));
-    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, null, null, qualityGate, 1L, properties));
-    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, new Analysis("foo", 1_500L), null, qualityGate, 1L, properties));
-    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, null, qualityGate, 1L, properties));
-    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, new Branch(false, "B", Branch.Type.SHORT), qualityGate, 1L, properties));
-    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, null, 1L, properties));
-    EvaluatedQualityGate otherQualityGate = EvaluatedQualityGate.newBuilder()
-      .setQualityGate(new QualityGate("A", "B", emptySet()))
-      .setStatus(Metric.Level.WARN)
-      .build();
-    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, otherQualityGate, 1L, properties));
-    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, null, properties));
-    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, 2L, properties));
-    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, 1L, emptyMap()));
-    assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, 1L, ImmutableMap.of("A", "B")));
-  }
-
-  @Test
-  public void defines_hashcode_based_on_all_fields() {
-    assertThat(underTest.hashCode()).isEqualTo(underTest.hashCode());
-    assertThat(underTest.hashCode()).isEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, 1L, properties).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, new CeTask("2", CeTask.Status.SUCCESS), analysis, branch, qualityGate, 1L, properties).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(new Project("A", "B", "C"), ceTask, analysis, branch, qualityGate, 1L, properties).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(new Project("A", "B", "C"), ceTask, analysis, branch, qualityGate, 1L, properties).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, null, null, null, qualityGate, 1L, properties).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, null, null, qualityGate, 1L, properties).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, new Analysis("foo", 1_500L), null, qualityGate, 1L, properties).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, null, qualityGate, 1L, properties).hashCode());
-    assertThat(underTest.hashCode())
-      .isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, new Branch(false, "B", Branch.Type.SHORT), qualityGate, 1L, properties).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, null, 1L, properties).hashCode());
-    EvaluatedQualityGate otherQualityGate = EvaluatedQualityGate.newBuilder()
-      .setQualityGate(new QualityGate("A", "B", emptySet()))
-      .setStatus(Metric.Level.WARN)
-      .build();
-    assertThat(underTest.hashCode())
-      .isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, otherQualityGate, 1L, properties).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, this.qualityGate, null, properties).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, this.qualityGate, 2L, properties).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, this.qualityGate, 1L, emptyMap()).hashCode());
-    assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, this.qualityGate, 1L, ImmutableMap.of("B", "C")).hashCode());
-  }
-
-  @Test
-  public void verify_toString() {
-    assertThat(underTest.toString()).isEqualTo(
-      "ProjectAnalysis{project=Project{uuid='uuid', key='key', name='name', description=null}, ceTask=CeTask{id='id', status=SUCCESS}, branch=Branch{main=true, name='name', type=SHORT}, qualityGate=EvaluatedQualityGate{qualityGate=QualityGate{id=id, name='name', conditions=[]}, status=WARN, evaluatedConditions=[]}, updatedAt=1, properties={a=b}, analysis=Analysis{uuid='analysis_uuid', date=1500}}");
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/SynchronousWebHooksImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/SynchronousWebHooksImplTest.java
deleted file mode 100644 (file)
index de8d298..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.server.webhook;
-
-import java.io.IOException;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.log.LogTester;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.webhook.WebhookDbTester;
-import org.sonar.server.async.AsyncExecution;
-import org.sonar.server.organization.DefaultOrganizationProvider;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.sonar.api.utils.log.LoggerLevel.DEBUG;
-import static org.sonar.db.DbTester.create;
-import static org.sonar.db.webhook.WebhookTesting.newWebhook;
-import static org.sonar.server.organization.TestDefaultOrganizationProvider.from;
-
-public class SynchronousWebHooksImplTest {
-
-  private static final long NOW = 1_500_000_000_000L;
-
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  @Rule
-  public DbTester db = create();
-  private DbClient dbClient = db.getDbClient();
-
-  private WebhookDbTester webhookDbTester = db.webhooks();
-  private ComponentDbTester componentDbTester = db.components();
-  private DefaultOrganizationProvider defaultOrganizationProvider = from(db);
-
-  private final TestWebhookCaller caller = new TestWebhookCaller();
-  private final WebhookDeliveryStorage deliveryStorage = mock(WebhookDeliveryStorage.class);
-  private final WebhookPayload mock = mock(WebhookPayload.class);
-  private final AsyncExecution synchronousAsyncExecution = Runnable::run;
-  private final WebHooksImpl underTest = new WebHooksImpl(caller, deliveryStorage, synchronousAsyncExecution, dbClient);
-
-  @Test
-  public void isEnabled_returns_false_if_no_webhooks() {
-    ComponentDto componentDto = componentDbTester.insertPrivateProject();
-
-    assertThat(underTest.isEnabled(componentDto)).isFalse();
-  }
-
-  @Test
-  public void isEnabled_returns_true_if_one_valid_global_webhook() {
-    ComponentDto componentDto = componentDbTester.insertPrivateProject();
-    webhookDbTester.insert(newWebhook(componentDto).setName("First").setUrl("http://url1"));
-
-    assertThat(underTest.isEnabled(componentDto)).isTrue();
-  }
-
-  @Test
-  public void isEnabled_returns_true_if_one_valid_project_webhook() {
-    String organizationUuid = defaultOrganizationProvider.get().getUuid();
-    ComponentDto componentDto = componentDbTester.insertPrivateProject().setOrganizationUuid(organizationUuid);
-    webhookDbTester.insert(newWebhook(componentDto).setName("First").setUrl("http://url1"));
-
-    assertThat(underTest.isEnabled(componentDto)).isTrue();
-  }
-
-
-  @Test
-  public void do_nothing_if_no_webhooks() {
-    ComponentDto componentDto = componentDbTester.insertPrivateProject().setOrganizationUuid(defaultOrganizationProvider.get().getUuid());
-
-    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(componentDto.uuid(), "1", "#1"), () -> mock);
-
-    assertThat(caller.countSent()).isEqualTo(0);
-    assertThat(logTester.logs(DEBUG)).isEmpty();
-    verifyZeroInteractions(deliveryStorage);
-  }
-
-  @Test
-  public void send_global_webhooks() {
-
-    ComponentDto componentDto = componentDbTester.insertPrivateProject();
-    webhookDbTester.insert(newWebhook(componentDto).setName("First").setUrl("http://url1"));
-    webhookDbTester.insert(newWebhook(componentDto).setName("Second").setUrl("http://url2"));
-    caller.enqueueSuccess(NOW, 200, 1_234);
-    caller.enqueueFailure(NOW, new IOException("Fail to connect"));
-
-    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(componentDto.uuid(), "1", "#1"), () -> mock);
-
-    assertThat(caller.countSent()).isEqualTo(2);
-    assertThat(logTester.logs(DEBUG)).contains("Sent webhook 'First' | url=http://url1 | time=1234ms | status=200");
-    assertThat(logTester.logs(DEBUG)).contains("Failed to send webhook 'Second' | url=http://url2 | message=Fail to connect");
-    verify(deliveryStorage, times(2)).persist(any(WebhookDelivery.class));
-    verify(deliveryStorage).purge(componentDto.uuid());
-
-  }
-
-  @Test
-  public void send_project_webhooks() {
-
-    String organizationUuid = defaultOrganizationProvider.get().getUuid();
-    ComponentDto componentDto = componentDbTester.insertPrivateProject().setOrganizationUuid(organizationUuid);
-    webhookDbTester.insert(newWebhook(componentDto).setName("First").setUrl("http://url1"));
-    caller.enqueueSuccess(NOW, 200, 1_234);
-
-    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(componentDto.uuid(), "1", "#1"), () -> mock);
-
-    assertThat(caller.countSent()).isEqualTo(1);
-    assertThat(logTester.logs(DEBUG)).contains("Sent webhook 'First' | url=http://url1 | time=1234ms | status=200");
-    verify(deliveryStorage).persist(any(WebhookDelivery.class));
-    verify(deliveryStorage).purge(componentDto.uuid());
-
-  }
-
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/TestWebhookCaller.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/TestWebhookCaller.java
deleted file mode 100644 (file)
index 0634f16..0000000
+++ /dev/null
@@ -1,75 +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.webhook;
-
-import java.util.LinkedList;
-import java.util.Queue;
-import java.util.concurrent.atomic.AtomicInteger;
-import javax.annotation.Nullable;
-
-import static java.util.Objects.requireNonNull;
-
-public class TestWebhookCaller implements WebhookCaller {
-
-  private final Queue<Item> deliveries = new LinkedList<>();
-  private final AtomicInteger countSent = new AtomicInteger(0);
-
-  public TestWebhookCaller enqueueSuccess(long at, int httpCode, int durationMs) {
-    deliveries.add(new Item(at, httpCode, durationMs, null));
-    return this;
-  }
-
-  public TestWebhookCaller enqueueFailure(long at, Throwable t) {
-    deliveries.add(new Item(at, null, null, t));
-    return this;
-  }
-
-  @Override
-  public WebhookDelivery call(Webhook webhook, WebhookPayload payload) {
-    Item item = requireNonNull(deliveries.poll(), "Queue is empty");
-    countSent.incrementAndGet();
-    return new WebhookDelivery.Builder()
-      .setAt(item.at)
-      .setHttpStatus(item.httpCode)
-      .setDurationInMs(item.durationMs)
-      .setError(item.throwable)
-      .setPayload(payload)
-      .setWebhook(webhook)
-      .build();
-  }
-
-  public int countSent() {
-    return countSent.get();
-  }
-
-  private static class Item {
-    final long at;
-    final Integer httpCode;
-    final Integer durationMs;
-    final Throwable throwable;
-
-    Item(long at, @Nullable Integer httpCode, @Nullable Integer durationMs, @Nullable Throwable throwable) {
-      this.at = at;
-      this.httpCode = httpCode;
-      this.durationMs = durationMs;
-      this.throwable = throwable;
-    }
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookCallerImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookCallerImplTest.java
deleted file mode 100644 (file)
index 60ac24e..0000000
+++ /dev/null
@@ -1,218 +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.webhook;
-
-import okhttp3.Credentials;
-import okhttp3.HttpUrl;
-import okhttp3.mockwebserver.MockResponse;
-import okhttp3.mockwebserver.MockWebServer;
-import okhttp3.mockwebserver.RecordedRequest;
-import org.apache.commons.lang.RandomStringUtils;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.DisableOnDebug;
-import org.junit.rules.TestRule;
-import org.junit.rules.Timeout;
-import org.sonar.api.SonarQubeSide;
-import org.sonar.api.SonarRuntime;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.internal.SonarRuntimeImpl;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.Version;
-import org.sonar.api.utils.internal.TestSystem2;
-import org.sonar.server.util.OkHttpClientProvider;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class WebhookCallerImplTest {
-
-  private static final long NOW = 1_500_000_000_000L;
-  private static final String PROJECT_UUID = "P_UUID1";
-  private static final String WEBHOOK_UUID = "WH_UUID1";
-  private static final String CE_TASK_UUID = "CE_UUID1";
-  private static final String SOME_JSON = "{\"payload\": {}}";
-  private static final WebhookPayload PAYLOAD = new WebhookPayload("P1", SOME_JSON);
-
-  @Rule
-  public MockWebServer server = new MockWebServer();
-
-  @Rule
-  public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
-
-  private System2 system = new TestSystem2().setNow(NOW);
-
-  @Test
-  public void send_posts_payload_to_http_server() throws Exception {
-    Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", server.url("/ping").toString());
-
-    server.enqueue(new MockResponse().setBody("pong").setResponseCode(201));
-    WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
-
-    assertThat(delivery.getHttpStatus()).hasValue(201);
-    assertThat(delivery.getWebhook().getUuid()).isEqualTo(WEBHOOK_UUID);
-    assertThat(delivery.getDurationInMs().get()).isGreaterThanOrEqualTo(0);
-    assertThat(delivery.getError()).isEmpty();
-    assertThat(delivery.getAt()).isEqualTo(NOW);
-    assertThat(delivery.getWebhook()).isSameAs(webhook);
-    assertThat(delivery.getPayload()).isSameAs(PAYLOAD);
-
-    RecordedRequest recordedRequest = server.takeRequest();
-    assertThat(recordedRequest.getMethod()).isEqualTo("POST");
-    assertThat(recordedRequest.getPath()).isEqualTo("/ping");
-    assertThat(recordedRequest.getBody().readUtf8()).isEqualTo(PAYLOAD.getJson());
-    assertThat(recordedRequest.getHeader("User-Agent")).isEqualTo("SonarQube/6.2");
-    assertThat(recordedRequest.getHeader("Content-Type")).isEqualTo("application/json; charset=utf-8");
-    assertThat(recordedRequest.getHeader("X-SonarQube-Project")).isEqualTo(PAYLOAD.getProjectKey());
-  }
-
-  @Test
-  public void silently_catch_error_when_external_server_does_not_answer() throws Exception {
-    Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", server.url("/ping").toString());
-
-    server.shutdown();
-    WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
-
-    assertThat(delivery.getHttpStatus()).isEmpty();
-    assertThat(delivery.getDurationInMs().get()).isGreaterThanOrEqualTo(0);
-    // message can be "Connection refused" or "connect timed out"
-    assertThat(delivery.getErrorMessage().get()).matches("(.*Connection refused.*)|(.*connect timed out.*)");
-    assertThat(delivery.getAt()).isEqualTo(NOW);
-    assertThat(delivery.getWebhook()).isSameAs(webhook);
-    assertThat(delivery.getPayload()).isSameAs(PAYLOAD);
-  }
-
-  @Test
-  public void silently_catch_error_when_url_is_incorrect() {
-    Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", "this_is_not_an_url");
-
-    WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
-
-    assertThat(delivery.getHttpStatus()).isEmpty();
-    assertThat(delivery.getDurationInMs().get()).isGreaterThanOrEqualTo(0);
-    assertThat(delivery.getError().get()).isInstanceOf(IllegalArgumentException.class);
-    assertThat(delivery.getErrorMessage().get()).isEqualTo("Webhook URL is not valid: this_is_not_an_url");
-    assertThat(delivery.getAt()).isEqualTo(NOW);
-    assertThat(delivery.getWebhook()).isSameAs(webhook);
-    assertThat(delivery.getPayload()).isSameAs(PAYLOAD);
-  }
-
-  /**
-   * SONAR-8799
-   */
-  @Test
-  public void redirects_should_be_followed_with_POST_method() throws Exception {
-    Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", server.url("/redirect").toString());
-
-    // /redirect redirects to /target
-    server.enqueue(new MockResponse().setResponseCode(307).setHeader("Location", server.url("target")));
-    server.enqueue(new MockResponse().setResponseCode(200));
-
-    WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
-
-    assertThat(delivery.getHttpStatus().get()).isEqualTo(200);
-    assertThat(delivery.getDurationInMs().get()).isGreaterThanOrEqualTo(0);
-    assertThat(delivery.getError()).isEmpty();
-    assertThat(delivery.getAt()).isEqualTo(NOW);
-    assertThat(delivery.getWebhook()).isSameAs(webhook);
-    assertThat(delivery.getPayload()).isSameAs(PAYLOAD);
-
-    takeAndVerifyPostRequest("/redirect");
-    takeAndVerifyPostRequest("/target");
-  }
-
-  @Test
-  public void credentials_are_propagated_to_POST_redirects() throws Exception {
-    HttpUrl url = server.url("/redirect").newBuilder().username("theLogin").password("thePassword").build();
-    Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", url.toString());
-
-    // /redirect redirects to /target
-    server.enqueue(new MockResponse().setResponseCode(307).setHeader("Location", server.url("target")));
-    server.enqueue(new MockResponse().setResponseCode(200));
-
-    WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
-
-    assertThat(delivery.getHttpStatus().get()).isEqualTo(200);
-
-    RecordedRequest redirectedRequest = takeAndVerifyPostRequest("/redirect");
-    assertThat(redirectedRequest.getHeader("Authorization")).isEqualTo(Credentials.basic(url.username(), url.password()));
-
-    RecordedRequest targetRequest = takeAndVerifyPostRequest("/target");
-    assertThat(targetRequest.getHeader("Authorization")).isEqualTo(Credentials.basic(url.username(), url.password()));
-  }
-
-  @Test
-  public void redirects_throws_ISE_if_header_Location_is_missing() {
-    HttpUrl url = server.url("/redirect");
-    Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", url.toString());
-
-    server.enqueue(new MockResponse().setResponseCode(307));
-
-    WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
-
-    Throwable error = delivery.getError().get();
-    assertThat(error)
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Missing HTTP header 'Location' in redirect of " + url);
-  }
-
-  @Test
-  public void redirects_throws_ISE_if_header_Location_does_not_relate_to_a_supported_protocol() {
-    HttpUrl url = server.url("/redirect");
-    Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", url.toString());
-
-    server.enqueue(new MockResponse().setResponseCode(307).setHeader("Location", "ftp://foo"));
-
-    WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
-
-    Throwable error = delivery.getError().get();
-    assertThat(error)
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Unsupported protocol in redirect of " + url + " to ftp://foo");
-  }
-
-  @Test
-  public void send_basic_authentication_header_if_url_contains_credentials() throws Exception {
-    HttpUrl url = server.url("/ping").newBuilder().username("theLogin").password("thePassword").build();
-    Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", url.toString());
-    server.enqueue(new MockResponse().setBody("pong"));
-
-    WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
-
-    assertThat(delivery.getWebhook().getUrl())
-      .isEqualTo(url.toString())
-      .contains("://theLogin:thePassword@");
-    RecordedRequest recordedRequest = takeAndVerifyPostRequest("/ping");
-    assertThat(recordedRequest.getHeader("Authorization")).isEqualTo(Credentials.basic(url.username(), url.password()));
-  }
-
-  private RecordedRequest takeAndVerifyPostRequest(String expectedPath) throws Exception {
-    RecordedRequest request = server.takeRequest();
-
-    assertThat(request.getMethod()).isEqualTo("POST");
-    assertThat(request.getPath()).isEqualTo(expectedPath);
-    assertThat(request.getHeader("User-Agent")).isEqualTo("SonarQube/6.2");
-    return request;
-  }
-
-  private WebhookCaller newSender() {
-    SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.parse("6.2"), SonarQubeSide.SERVER);
-    return new WebhookCallerImpl(system, new OkHttpClientProvider().provide(new MapSettings().asConfig(), runtime));
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookDeliveryStorageTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookDeliveryStorageTest.java
deleted file mode 100644 (file)
index 1fac932..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.webhook;
-
-import java.io.IOException;
-import org.apache.commons.lang.RandomStringUtils;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.core.util.UuidFactory;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.webhook.WebhookDbTesting;
-import org.sonar.db.webhook.WebhookDeliveryDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.db.webhook.WebhookDbTesting.newDto;
-import static org.sonar.db.webhook.WebhookDbTesting.selectAllDeliveryUuids;
-
-public class WebhookDeliveryStorageTest {
-
-  private static final String DELIVERY_UUID = "abcde1234";
-  private static final long NOW = 1_500_000_000_000L;
-  private static final long TWO_MONTHS_AGO = NOW - 60L * 24 * 60 * 60 * 1000;
-  private static final long TWO_WEEKS_AGO = NOW - 14L * 24 * 60 * 60 * 1000;
-
-  private final System2 system = mock(System2.class);
-
-  @Rule
-  public final DbTester dbTester = DbTester.create(system).setDisableDefaultOrganization(true);
-
-  private DbClient dbClient = dbTester.getDbClient();
-  private DbSession dbSession = dbTester.getSession();
-  private UuidFactory uuidFactory = mock(UuidFactory.class);
-  private WebhookDeliveryStorage underTest = new WebhookDeliveryStorage(dbClient, system, uuidFactory);
-
-  @Test
-  public void persist_generates_uuid_then_inserts_record() {
-    when(uuidFactory.create()).thenReturn(DELIVERY_UUID);
-    WebhookDelivery delivery = newBuilderTemplate().build();
-
-    underTest.persist(delivery);
-
-    WebhookDeliveryDto dto = dbClient.webhookDeliveryDao().selectByUuid(dbSession, DELIVERY_UUID).get();
-    assertThat(dto.getUuid()).isEqualTo(DELIVERY_UUID);
-    assertThat(dto.getWebhookUuid()).isEqualTo("WEBHOOK_UUID_1");
-    assertThat(dto.getComponentUuid()).isEqualTo(delivery.getWebhook().getComponentUuid());
-    assertThat(dto.getCeTaskUuid()).isEqualTo(delivery.getWebhook().getCeTaskUuid().get());
-    assertThat(dto.getName()).isEqualTo(delivery.getWebhook().getName());
-    assertThat(dto.getUrl()).isEqualTo(delivery.getWebhook().getUrl());
-    assertThat(dto.getCreatedAt()).isEqualTo(delivery.getAt());
-    assertThat(dto.getHttpStatus()).isEqualTo(delivery.getHttpStatus().get());
-    assertThat(dto.getDurationMs()).isEqualTo(delivery.getDurationInMs().get());
-    assertThat(dto.getPayload()).isEqualTo(delivery.getPayload().getJson());
-    assertThat(dto.getErrorStacktrace()).isNull();
-  }
-
-  @Test
-  public void persist_error_stacktrace() {
-    when(uuidFactory.create()).thenReturn(DELIVERY_UUID);
-    WebhookDelivery delivery = newBuilderTemplate()
-      .setError(new IOException("fail to connect"))
-      .build();
-
-    underTest.persist(delivery);
-
-    WebhookDeliveryDto dto = dbClient.webhookDeliveryDao().selectByUuid(dbSession, DELIVERY_UUID).get();
-    assertThat(dto.getErrorStacktrace()).contains("java.io.IOException", "fail to connect");
-  }
-
-  @Test
-  public void purge_deletes_records_older_than_one_month_on_the_project() {
-    when(system.now()).thenReturn(NOW);
-    dbClient.webhookDeliveryDao().insert(dbSession, newDto("D1", "PROJECT_1", TWO_MONTHS_AGO));
-    dbClient.webhookDeliveryDao().insert(dbSession, newDto("D2", "PROJECT_1", TWO_WEEKS_AGO));
-    dbClient.webhookDeliveryDao().insert(dbSession, newDto("D3", "PROJECT_2", TWO_MONTHS_AGO));
-    dbSession.commit();
-
-    underTest.purge("PROJECT_1");
-
-    // do not purge another project PROJECT_2
-    assertThat(selectAllDeliveryUuids(dbTester, dbSession)).containsOnly("D2", "D3");
-  }
-
-  private static WebhookDelivery.Builder newBuilderTemplate() {
-    return new WebhookDelivery.Builder()
-      .setWebhook(new Webhook("WEBHOOK_UUID_1", "COMPONENT1", "TASK1", RandomStringUtils.randomAlphanumeric(40),"Jenkins", "http://jenkins"))
-      .setPayload(new WebhookPayload("my-project", "{json}"))
-      .setAt(1_000_000L)
-      .setHttpStatus(200)
-      .setDurationInMs(1_000);
-  }
-
-  private static WebhookDeliveryDto newDto(String uuid, String componentUuid, long at) {
-    return WebhookDbTesting.newDto()
-      .setUuid(uuid)
-      .setComponentUuid(componentUuid)
-      .setCreatedAt(at);
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookDeliveryTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookDeliveryTest.java
deleted file mode 100644 (file)
index 5347028..0000000
+++ /dev/null
@@ -1,85 +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.webhook;
-
-import java.io.IOException;
-import org.junit.Test;
-import org.sonar.server.webhook.Webhook;
-import org.sonar.server.webhook.WebhookDelivery;
-import org.sonar.server.webhook.WebhookPayload;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-
-
-public class WebhookDeliveryTest {
-
-  @Test
-  public void isSuccess_returns_false_if_failed_to_send_http_request() {
-    WebhookDelivery delivery = newBuilderTemplate()
-      .setError(new IOException("Fail to connect"))
-      .build();
-
-    assertThat(delivery.isSuccess()).isFalse();
-  }
-
-  @Test
-  public void isSuccess_returns_false_if_http_response_returns_error_status() {
-    WebhookDelivery delivery = newBuilderTemplate()
-      .setHttpStatus(404)
-      .build();
-
-    assertThat(delivery.isSuccess()).isFalse();
-  }
-
-  @Test
-  public void isSuccess_returns_true_if_http_response_returns_2xx_code() {
-    WebhookDelivery delivery = newBuilderTemplate()
-      .setHttpStatus(204)
-      .build();
-
-    assertThat(delivery.isSuccess()).isTrue();
-  }
-
-  @Test
-  public void getErrorMessage_returns_empty_if_no_error() {
-    WebhookDelivery delivery = newBuilderTemplate().build();
-
-    assertThat(delivery.getErrorMessage()).isEmpty();
-  }
-
-  @Test
-  public void getErrorMessage_returns_root_cause_message_if_error() {
-    Exception rootCause = new IOException("fail to connect");
-    Exception cause = new IOException("nested", rootCause);
-    WebhookDelivery delivery = newBuilderTemplate()
-      .setError(cause)
-      .build();
-
-    assertThat(delivery.getErrorMessage().get()).isEqualTo("fail to connect");
-  }
-
-  private static WebhookDelivery.Builder newBuilderTemplate() {
-    return new WebhookDelivery.Builder()
-      .setWebhook(mock(Webhook.class))
-      .setPayload(mock(WebhookPayload.class))
-      .setAt(1_000L);
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookModuleTest.java
deleted file mode 100644 (file)
index 2615c87..0000000
+++ /dev/null
@@ -1,45 +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.webhook;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.core.platform.ComponentContainer;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER;
-
-public class WebhookModuleTest {
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  private WebhookModule underTest = new WebhookModule();
-
-  @Test
-  public void verify_count_of_added_components() {
-    ComponentContainer container = new ComponentContainer();
-
-    underTest.configure(container);
-
-    assertThat(container.size()).isEqualTo(4 + COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER);
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookPayloadFactoryImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookPayloadFactoryImplTest.java
deleted file mode 100644 (file)
index a494b9a..0000000
+++ /dev/null
@@ -1,329 +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.webhook;
-
-import com.google.common.collect.ImmutableMap;
-import java.util.Map;
-import javax.annotation.Nullable;
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.measures.Metric;
-import org.sonar.api.platform.Server;
-import org.sonar.api.utils.System2;
-import org.sonar.server.project.Project;
-import org.sonar.server.qualitygate.Condition;
-import org.sonar.server.qualitygate.EvaluatedCondition;
-import org.sonar.server.qualitygate.EvaluatedQualityGate;
-import org.sonar.server.qualitygate.QualityGate;
-
-import static java.util.Collections.emptyMap;
-import static java.util.Collections.emptySet;
-import static java.util.Collections.singleton;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.test.JsonAssert.assertJson;
-
-public class WebhookPayloadFactoryImplTest {
-
-  private static final String PROJECT_KEY = "P1";
-
-  private Server server = mock(Server.class);
-  private System2 system2 = mock(System2.class);
-  private WebhookPayloadFactory underTest = new WebhookPayloadFactoryImpl(server, system2);
-
-  @Before
-  public void setUp() throws Exception {
-    when(server.getPublicRootUrl()).thenReturn("http://foo");
-    when(system2.now()).thenReturn(1_500_999L);
-  }
-
-  @Test
-  public void create_payload_for_successful_analysis() {
-    CeTask task = new CeTask("#1", CeTask.Status.SUCCESS);
-    Condition condition = new Condition("coverage", Condition.Operator.GREATER_THAN, "70.0", "75.0", true);
-    EvaluatedQualityGate gate = EvaluatedQualityGate.newBuilder()
-      .setQualityGate(new QualityGate("G1", "Gate One", singleton(condition)))
-      .setStatus(Metric.Level.WARN)
-      .addCondition(condition, EvaluatedCondition.EvaluationStatus.WARN, "74.0")
-      .build();
-    ProjectAnalysis analysis = newAnalysis(task, gate, null, 1_500_000_000_000L, emptyMap());
-
-    WebhookPayload payload = underTest.create(analysis);
-    assertThat(payload.getProjectKey()).isEqualTo(PROJECT_KEY);
-    assertJson(payload.getJson())
-      .isSimilarTo("{" +
-        "  \"serverUrl\": \"http://foo\"," +
-        "  \"taskId\": \"#1\"," +
-        "  \"status\": \"SUCCESS\"," +
-        "  \"analysedAt\": \"2017-07-14T04:40:00+0200\"," +
-        "  \"changedAt\": \"2017-07-14T04:40:00+0200\"," +
-        "  \"project\": {" +
-        "    \"key\": \"P1\"," +
-        "    \"name\": \"Project One\"," +
-        "    \"url\": \"http://foo/dashboard?id=P1\"" +
-        "  }," +
-        "  \"qualityGate\": {" +
-        "    \"name\": \"Gate One\"," +
-        "    \"status\": \"WARN\"," +
-        "    \"conditions\": [" +
-        "      {" +
-        "        \"metric\": \"coverage\"," +
-        "        \"operator\": \"GREATER_THAN\"," +
-        "        \"value\": \"74.0\"," +
-        "        \"status\": \"WARN\"," +
-        "        \"onLeakPeriod\": true," +
-        "        \"errorThreshold\": \"70.0\"," +
-        "        \"warningThreshold\": \"75.0\"" +
-        "      }" +
-        "    ]" +
-        "  }," +
-        "  \"properties\": {" +
-        "  }" +
-        "}");
-  }
-
-  @Test
-  public void create_payload_with_gate_conditions_without_value() {
-    CeTask task = new CeTask("#1", CeTask.Status.SUCCESS);
-
-    Condition condition = new Condition("coverage", Condition.Operator.GREATER_THAN, "70.0", "75.0", false);
-    EvaluatedQualityGate gate = EvaluatedQualityGate.newBuilder()
-      .setQualityGate(new QualityGate("G1", "Gate One", singleton(condition)))
-      .setStatus(Metric.Level.WARN)
-      .addCondition(condition, EvaluatedCondition.EvaluationStatus.NO_VALUE, null)
-      .build();
-    ProjectAnalysis analysis = newAnalysis(task, gate, null, 1_500_000_000_000L, emptyMap());
-
-    WebhookPayload payload = underTest.create(analysis);
-    assertThat(payload.getProjectKey()).isEqualTo(PROJECT_KEY);
-    assertJson(payload.getJson())
-      .isSimilarTo("{" +
-        "  \"serverUrl\": \"http://foo\"," +
-        "  \"taskId\": \"#1\"," +
-        "  \"status\": \"SUCCESS\"," +
-        "  \"analysedAt\": \"2017-07-14T04:40:00+0200\"," +
-        "  \"changedAt\": \"2017-07-14T04:40:00+0200\"," +
-        "  \"project\": {" +
-        "    \"key\": \"P1\"," +
-        "    \"name\": \"Project One\"," +
-        "    \"url\": \"http://foo/dashboard?id=P1\"" +
-        "  }," +
-        "  \"qualityGate\": {" +
-        "    \"name\": \"Gate One\"," +
-        "    \"status\": \"WARN\"," +
-        "    \"conditions\": [" +
-        "      {" +
-        "        \"metric\": \"coverage\"," +
-        "        \"operator\": \"GREATER_THAN\"," +
-        "        \"status\": \"NO_VALUE\"," +
-        "        \"onLeakPeriod\": false," +
-        "        \"errorThreshold\": \"70.0\"," +
-        "        \"warningThreshold\": \"75.0\"" +
-        "      }" +
-        "    ]" +
-        "  }" +
-        "}");
-  }
-
-  @Test
-  public void create_payload_with_analysis_properties() {
-    CeTask task = new CeTask("#1", CeTask.Status.SUCCESS);
-    EvaluatedQualityGate gate = EvaluatedQualityGate.newBuilder()
-      .setQualityGate(new QualityGate("G1", "Gate One", emptySet()))
-      .setStatus(Metric.Level.WARN)
-      .build();
-    Map<String, String> scannerProperties = ImmutableMap.of(
-      "sonar.analysis.revision", "ab45d24",
-      "sonar.analysis.buildNumber", "B123",
-      "not.prefixed.with.sonar.analysis", "should be ignored",
-      "ignored", "should be ignored too");
-    ProjectAnalysis analysis = newAnalysis(task, gate, null, 1_500_000_000_000L, scannerProperties);
-
-    WebhookPayload payload = underTest.create(analysis);
-    assertJson(payload.getJson())
-      .isSimilarTo("{" +
-        "  \"serverUrl\": \"http://foo\"," +
-        "  \"taskId\": \"#1\"," +
-        "  \"status\": \"SUCCESS\"," +
-        "  \"analysedAt\": \"2017-07-14T04:40:00+0200\"," +
-        "  \"changedAt\": \"2017-07-14T04:40:00+0200\"," +
-        "  \"project\": {" +
-        "    \"key\": \"P1\"," +
-        "    \"name\": \"Project One\"," +
-        "    \"url\": \"http://foo/dashboard?id=P1\"" +
-        "  }," +
-        "  \"qualityGate\": {" +
-        "    \"name\": \"Gate One\"," +
-        "    \"status\": \"WARN\"," +
-        "    \"conditions\": [" +
-        "    ]" +
-        "  }," +
-        "  \"properties\": {" +
-        "    \"sonar.analysis.revision\": \"ab45d24\"," +
-        "    \"sonar.analysis.buildNumber\": \"B123\"" +
-        "  }" +
-        "}");
-    assertThat(payload.getJson())
-      .doesNotContain("not.prefixed.with.sonar.analysis")
-      .doesNotContain("ignored");
-  }
-
-  @Test
-  public void create_payload_for_failed_analysis() {
-    CeTask ceTask = new CeTask("#1", CeTask.Status.FAILED);
-    ProjectAnalysis analysis = newAnalysis(ceTask, null, null, 1_500_000_000_000L, emptyMap());
-
-    WebhookPayload payload = underTest.create(analysis);
-
-    assertThat(payload.getProjectKey()).isEqualTo(PROJECT_KEY);
-    assertJson(payload.getJson())
-      .isSimilarTo("{" +
-        "  \"serverUrl\": \"http://foo\"," +
-        "  \"taskId\": \"#1\"," +
-        "  \"status\": \"FAILED\"," +
-        "  \"changedAt\": \"2017-07-14T04:40:00+0200\"," +
-        "  \"project\": {" +
-        "    \"key\": \"P1\"," +
-        "    \"name\": \"Project One\"," +
-        "    \"url\": \"http://foo/dashboard?id=P1\"" +
-        "  }," +
-        "  \"properties\": {" +
-        "  }" +
-        "}");
-  }
-
-  @Test
-  public void create_payload_for_no_analysis_date() {
-    CeTask ceTask = new CeTask("#1", CeTask.Status.FAILED);
-    ProjectAnalysis analysis = newAnalysis(ceTask, null, null, null, emptyMap());
-
-    WebhookPayload payload = underTest.create(analysis);
-
-    assertThat(payload.getProjectKey()).isEqualTo(PROJECT_KEY);
-    assertJson(payload.getJson())
-      .isSimilarTo("{" +
-        "  \"serverUrl\": \"http://foo\"," +
-        "  \"taskId\": \"#1\"," +
-        "  \"status\": \"FAILED\"," +
-        "  \"changedAt\": \"1970-01-01T01:25:00+0100\"," +
-        "  \"project\": {" +
-        "    \"key\": \"P1\"," +
-        "    \"name\": \"Project One\"" +
-        "  }," +
-        "  \"properties\": {" +
-        "  }" +
-        "}");
-  }
-
-  @Test
-  public void create_payload_on_short_branch() {
-    CeTask task = new CeTask("#1", CeTask.Status.SUCCESS);
-    ProjectAnalysis analysis = newAnalysis(task, null, new Branch(false, "feature/foo", Branch.Type.SHORT), 1_500_000_000_000L, emptyMap());
-
-    WebhookPayload payload = underTest.create(analysis);
-    assertJson(payload.getJson())
-      .isSimilarTo("{" +
-        "\"branch\": {" +
-        "  \"name\": \"feature/foo\"," +
-        "  \"type\": \"SHORT\"," +
-        "  \"isMain\": false," +
-        "  \"url\": \"http://foo/project/issues?branch=feature%2Ffoo&id=P1&resolved=false\"" +
-        "}" +
-        "}");
-  }
-
-  @Test
-  public void create_payload_on_pull_request() {
-    CeTask task = new CeTask("#1", CeTask.Status.SUCCESS);
-    ProjectAnalysis analysis = newAnalysis(task, null, new Branch(false, "pr/foo", Branch.Type.PULL_REQUEST), 1_500_000_000_000L, emptyMap());
-
-    WebhookPayload payload = underTest.create(analysis);
-    assertJson(payload.getJson())
-      .isSimilarTo("{" +
-        "\"branch\": {" +
-        "  \"name\": \"pr/foo\"," +
-        "  \"type\": \"PULL_REQUEST\"," +
-        "  \"isMain\": false," +
-        "  \"url\": \"http://foo/project/issues?pullRequest=pr%2Ffoo&id=P1&resolved=false\"" +
-        "}" +
-        "}");
-  }
-
-  @Test
-  public void create_without_ce_task() {
-    ProjectAnalysis analysis = newAnalysis(null, null, null, null, emptyMap());
-
-    WebhookPayload payload = underTest.create(analysis);
-    String json = payload.getJson();
-    assertThat(json).doesNotContain("taskId");
-    assertJson(json)
-      .isSimilarTo("{" +
-        "  \"serverUrl\": \"http://foo\"," +
-        "  \"status\": \"SUCCESS\"," +
-        "  \"changedAt\": \"1970-01-01T01:25:00+0100\"," +
-        "  \"project\": {" +
-        "    \"key\": \"P1\"," +
-        "    \"name\": \"Project One\"" +
-        "  }," +
-        "  \"properties\": {" +
-        "  }" +
-        "}");
-  }
-
-  @Test
-  public void create_payload_on_long_branch() {
-    CeTask task = new CeTask("#1", CeTask.Status.SUCCESS);
-    ProjectAnalysis analysis = newAnalysis(task, null, new Branch(false, "feature/foo", Branch.Type.LONG), 1_500_000_000_000L, emptyMap());
-
-    WebhookPayload payload = underTest.create(analysis);
-    assertJson(payload.getJson())
-      .isSimilarTo("{" +
-        "\"branch\": {" +
-        "  \"name\": \"feature/foo\"" +
-        "  \"type\": \"LONG\"" +
-        "  \"isMain\": false," +
-        "  \"url\": \"http://foo/dashboard?branch=feature%2Ffoo&id=P1\"" +
-        "}" +
-        "}");
-  }
-
-  @Test
-  public void create_payload_on_main_branch_without_name() {
-    CeTask task = new CeTask("#1", CeTask.Status.SUCCESS);
-    ProjectAnalysis analysis = newAnalysis(task, null, new Branch(true, null, Branch.Type.LONG), 1_500_000_000_000L, emptyMap());
-
-    WebhookPayload payload = underTest.create(analysis);
-    assertJson(payload.getJson())
-      .isSimilarTo("{" +
-        "\"branch\": {" +
-        "  \"type\": \"LONG\"" +
-        "  \"isMain\": true," +
-        "  \"url\": \"http://foo/dashboard?id=P1\"" +
-        "}" +
-        "}");
-  }
-
-  private static ProjectAnalysis newAnalysis(@Nullable CeTask task, @Nullable EvaluatedQualityGate gate,
-    @Nullable Branch branch, @Nullable Long analysisDate, Map<String, String> scannerProperties) {
-    return new ProjectAnalysis(new Project("P1_UUID", PROJECT_KEY, "Project One"), task, analysisDate == null ? null : new Analysis("A_UUID1", analysisDate), branch,
-      gate, analysisDate, scannerProperties);
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookTest.java
deleted file mode 100644 (file)
index 64778c0..0000000
+++ /dev/null
@@ -1,78 +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.webhook;
-
-import java.util.Optional;
-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 WebhookTest {
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  @Test
-  public void constructor_with_null_componentUuid_should_throw_NPE() {
-    expectedException.expect(NullPointerException.class);
-
-    new Webhook(randomAlphanumeric(40), null, null, null, randomAlphanumeric(10), randomAlphanumeric(10));
-  }
-
-  @Test
-  public void constructor_with_null_name_should_throw_NPE() {
-    expectedException.expect(NullPointerException.class);
-
-    new Webhook(randomAlphanumeric(40), randomAlphanumeric(10), null, null, null, randomAlphanumeric(10));
-  }
-
-  @Test
-  public void constructor_with_null_url_should_throw_NPE() {
-    expectedException.expect(NullPointerException.class);
-
-    new Webhook(randomAlphanumeric(40), randomAlphanumeric(10), null, null, randomAlphanumeric(10), null);
-  }
-
-  @Test
-  public void constructor_with_null_ceTaskUuid_or_analysisUuidurl_should_return_Optional_empty() {
-    String componentUuid = randomAlphanumeric(10);
-    String name = randomAlphanumeric(10);
-    String url = randomAlphanumeric(10);
-    Webhook underTest = new Webhook(randomAlphanumeric(40), componentUuid, null, null, name, url);
-
-    assertThat(underTest.getComponentUuid()).isEqualTo(componentUuid);
-    assertThat(underTest.getName()).isEqualTo(name);
-    assertThat(underTest.getUrl()).isEqualTo(url);
-    assertThat(underTest.getCeTaskUuid()).isEqualTo(Optional.empty());
-    assertThat(underTest.getAnalysisUuid()).isEqualTo(Optional.empty());
-
-    String ceTaskUuid = randomAlphanumeric(10);
-    String analysisUuid = randomAlphanumeric(10);
-    underTest = new Webhook(randomAlphanumeric(40), componentUuid, ceTaskUuid, analysisUuid, name, url);
-    assertThat(underTest.getComponentUuid()).isEqualTo(componentUuid);
-    assertThat(underTest.getName()).isEqualTo(name);
-    assertThat(underTest.getUrl()).isEqualTo(url);
-    assertThat(underTest.getCeTaskUuid().get()).isEqualTo(ceTaskUuid);
-    assertThat(underTest.getAnalysisUuid().get()).isEqualTo(analysisUuid);
-  }
-}