]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9943 add WS api/editions/status
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Thu, 12 Oct 2017 11:30:14 +0000 (13:30 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Mon, 23 Oct 2017 15:01:13 +0000 (08:01 -0700)
16 files changed:
server/sonar-server/src/main/java/org/sonar/server/edition/EditionManagementState.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/edition/EditionsWsModule.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/edition/License.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/edition/MutableEditionManagementState.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/edition/StandaloneEditionManagementStateImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/edition/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/edition/ws/EditionsWs.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/edition/ws/EditionsWsAction.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/edition/ws/StatusAction.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/edition/ws/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-server/src/main/resources/org/sonar/server/edition/ws/example-edition-status.json [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/edition/EditionsWsModuleTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/edition/LicenseTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/edition/ws/StatusActionTest.java [new file with mode: 0644]
sonar-ws/src/main/protobuf/ws-editions.proto [new file with mode: 0644]

diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/EditionManagementState.java b/server/sonar-server/src/main/java/org/sonar/server/edition/EditionManagementState.java
new file mode 100644 (file)
index 0000000..2a595a5
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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.edition;
+
+import java.util.Optional;
+
+public interface EditionManagementState {
+  /**
+   * @return {@link Optional#empty() empty} if there is no edition installed
+   */
+  Optional<String> getCurrentEditionKey();
+
+  /**
+   * @return the pending installation status.
+   */
+  PendingStatus getPendingInstallationStatus();
+
+  /**
+   * @return {@link Optional#empty() empty} when {@link #getPendingInstallationStatus()} returns {@link PendingStatus#NONE},
+   *         otherwise a {@link String}
+   */
+  Optional<String> getPendingEditionKey();
+
+  /**
+   * The license string.
+   *
+   * @return {@link Optional#empty() empty} when {@link #getPendingInstallationStatus()} returns {@link PendingStatus#NONE},
+   *         otherwise a {@link String}
+   */
+  Optional<String> getPendingLicense();
+
+  enum PendingStatus {
+    NONE,
+    AUTOMATIC_IN_PROGRESS,
+    AUTOMATIC_READY,
+    AUTOMATIC_FAILED,
+    MANUAL_IN_PROGRESS
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/EditionsWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/edition/EditionsWsModule.java
new file mode 100644 (file)
index 0000000..fb05d18
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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.edition;
+
+import org.sonar.core.platform.Module;
+import org.sonar.server.edition.ws.EditionsWs;
+import org.sonar.server.edition.ws.StatusAction;
+
+public class EditionsWsModule extends Module {
+  @Override
+  protected void configureModule() {
+    add(
+      StandaloneEditionManagementStateImpl.class,
+      StatusAction.class,
+      EditionsWs.class);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/License.java b/server/sonar-server/src/main/java/org/sonar/server/edition/License.java
new file mode 100644 (file)
index 0000000..f430d26
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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.edition;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import javax.annotation.concurrent.Immutable;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Immutable
+public class License {
+  private final String editionKey;
+  private final List<String> pluginKeys;
+  private final String content;
+
+  public License(String editionKey, List<String> pluginKeys, String content) {
+    this.editionKey = enforceNotNullNorEmpty(editionKey, "editionKey");
+    this.pluginKeys = ImmutableList.copyOf(pluginKeys);
+    this.content = enforceNotNullNorEmpty(content, "content");
+  }
+
+  private static String enforceNotNullNorEmpty(String str, String propertyName) {
+    checkNotNull(str, "%s can't be null", propertyName);
+    checkArgument(!str.isEmpty(), "%s can't be empty", propertyName);
+    return str;
+  }
+
+  public String getEditionKey() {
+    return editionKey;
+  }
+
+  public List<String> getPluginKeys() {
+    return pluginKeys;
+  }
+
+  public String getContent() {
+    return content;
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/MutableEditionManagementState.java b/server/sonar-server/src/main/java/org/sonar/server/edition/MutableEditionManagementState.java
new file mode 100644 (file)
index 0000000..afa9b30
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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.edition;
+
+/**
+ * Provides access to operations which will alter the Edition management state.
+ */
+public interface MutableEditionManagementState extends EditionManagementState {
+  /**
+   * Stage the specified license and records that an automatic install of the plugins will be performed.
+   *
+   * @return the new {@link PendingStatus}, always {@link PendingStatus#AUTOMATIC_IN_PROGRESS}
+   *         an exception.
+   *
+   * @throws IllegalStateException if current status is not {@link PendingStatus#NONE}
+   */
+  PendingStatus startAutomaticInstall(License license);
+
+  /**
+   * Stage the specified license and records that a manual install of the plugins will be performed.
+   *
+   * @return the new {@link PendingStatus}, always {@link PendingStatus#MANUAL_IN_PROGRESS}
+   *
+   * @throws IllegalStateException if current status is not {@link PendingStatus#NONE}
+   */
+  PendingStatus startManualInstall(License license);
+
+  /**
+   * Records that the specified edition has been installed without plugin changes.
+   *
+   * @param newEditionKey can't be {@code null} nor empty
+   *
+   * @return the new {@link PendingStatus}, always {@link PendingStatus#NONE}
+   *
+   * @throws IllegalStateException if current status is not {@link PendingStatus#NONE}
+   */
+  PendingStatus newEditionWithoutInstall(String newEditionKey);
+
+  /**
+   * Records that automatic install is ready to be finalized.
+   *
+   * @return the new pending status, always {@link PendingStatus#AUTOMATIC_READY}
+   *
+   * @throws IllegalStateException if current status is not {@link PendingStatus#AUTOMATIC_IN_PROGRESS}
+   */
+  PendingStatus automaticInstallReady();
+
+  /**
+   * Finalize an automatic or manual install
+   *
+   * @return the new pending status, always {@link PendingStatus#NONE}
+   *
+   * @throws IllegalStateException if current status is neither {@link PendingStatus#AUTOMATIC_READY} nor
+   *         {@link PendingStatus#MANUAL_IN_PROGRESS}
+   */
+  PendingStatus finalizeInstallation();
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/StandaloneEditionManagementStateImpl.java b/server/sonar-server/src/main/java/org/sonar/server/edition/StandaloneEditionManagementStateImpl.java
new file mode 100644 (file)
index 0000000..1ca7eb3
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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.edition;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Map;
+import java.util.Optional;
+import org.picocontainer.Startable;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.property.InternalPropertiesDao;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+import static java.util.Optional.empty;
+import static org.sonar.server.edition.EditionManagementState.PendingStatus.AUTOMATIC_IN_PROGRESS;
+import static org.sonar.server.edition.EditionManagementState.PendingStatus.AUTOMATIC_READY;
+import static org.sonar.server.edition.EditionManagementState.PendingStatus.MANUAL_IN_PROGRESS;
+import static org.sonar.server.edition.EditionManagementState.PendingStatus.NONE;
+
+public class StandaloneEditionManagementStateImpl implements MutableEditionManagementState, Startable {
+  private static final String CURRENT_EDITION_KEY = "sonar.editionManagement.currentEditionKey";
+  private static final String PENDING_INSTALLATION_STATUS = "sonar.editionManagement.pendingInstallationStatus";
+  private static final String PENDING_EDITION_KEY = "sonar.editionManagement.pendingEditionKey";
+  private static final String PENDING_LICENSE = "sonar.editionManagement.pendingLicense";
+
+  private final DbClient dbClient;
+  private String currentEditionKey;
+  private PendingStatus pendingInstallationStatus = NONE;
+  private String pendingEditionKey;
+  private String pendingLicense;
+
+  public StandaloneEditionManagementStateImpl(DbClient dbClient) {
+    this.dbClient = dbClient;
+  }
+
+  @Override
+  public void start() {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      // load current state value
+      Map<String, Optional<String>> internalPropertyValues = dbClient.internalPropertiesDao().selectByKeys(dbSession,
+        ImmutableSet.of(CURRENT_EDITION_KEY, PENDING_INSTALLATION_STATUS, PENDING_EDITION_KEY, PENDING_LICENSE));
+      this.currentEditionKey = internalPropertyValues.getOrDefault(CURRENT_EDITION_KEY, empty())
+        .map(StandaloneEditionManagementStateImpl::emptyToNull)
+        .orElse(null);
+      this.pendingInstallationStatus = internalPropertyValues.getOrDefault(PENDING_INSTALLATION_STATUS, empty())
+        .map(PendingStatus::valueOf)
+        .orElse(NONE);
+      this.pendingEditionKey = internalPropertyValues.getOrDefault(PENDING_EDITION_KEY, empty())
+        .map(StandaloneEditionManagementStateImpl::emptyToNull)
+        .orElse(null);
+      this.pendingLicense = internalPropertyValues.getOrDefault(PENDING_LICENSE, empty()).orElse(null);
+    }
+  }
+
+  @Override
+  public void stop() {
+    // nothing to do
+  }
+
+  @Override
+  public Optional<String> getCurrentEditionKey() {
+    return Optional.ofNullable(currentEditionKey);
+  }
+
+  @Override
+  public PendingStatus getPendingInstallationStatus() {
+    return pendingInstallationStatus;
+  }
+
+  @Override
+  public Optional<String> getPendingEditionKey() {
+    return Optional.ofNullable(pendingEditionKey);
+  }
+
+  @Override
+  public Optional<String> getPendingLicense() {
+    return Optional.ofNullable(pendingLicense);
+  }
+
+  @Override
+  public synchronized PendingStatus startAutomaticInstall(License license) {
+    checkLicense(license);
+    changeStatusFromTo(NONE, AUTOMATIC_IN_PROGRESS);
+    this.pendingLicense = license.getContent();
+    this.pendingEditionKey = license.getEditionKey();
+    persistProperties();
+    return this.pendingInstallationStatus;
+  }
+
+  @Override
+  public synchronized PendingStatus startManualInstall(License license) {
+    checkLicense(license);
+    changeStatusFromTo(NONE, MANUAL_IN_PROGRESS);
+    this.pendingLicense = license.getContent();
+    this.pendingEditionKey = license.getEditionKey();
+    this.pendingInstallationStatus = MANUAL_IN_PROGRESS;
+    persistProperties();
+    return this.pendingInstallationStatus;
+  }
+
+  @Override
+  public synchronized PendingStatus newEditionWithoutInstall(String newEditionKey) {
+    requireNonNull(newEditionKey, "newEditionKey can't be null");
+    checkArgument(!newEditionKey.isEmpty(), "newEditionKey can't be empty");
+    changeStatusFromTo(NONE, NONE);
+    this.currentEditionKey = newEditionKey;
+    persistProperties();
+    return this.pendingInstallationStatus;
+  }
+
+  @Override
+  public synchronized PendingStatus automaticInstallReady() {
+    changeStatusFromTo(AUTOMATIC_IN_PROGRESS, AUTOMATIC_READY);
+    persistProperties();
+    return this.pendingInstallationStatus;
+  }
+
+  @Override
+  public synchronized PendingStatus finalizeInstallation() {
+    checkState(this.pendingInstallationStatus == AUTOMATIC_READY || this.pendingInstallationStatus == MANUAL_IN_PROGRESS,
+      "Can't finalize installation when state is %s", this.pendingInstallationStatus);
+
+    this.pendingInstallationStatus = NONE;
+    this.currentEditionKey = this.pendingEditionKey;
+    this.pendingEditionKey = null;
+    this.pendingLicense = null;
+    persistProperties();
+    return this.pendingInstallationStatus;
+  }
+
+  private void changeStatusFromTo(PendingStatus expectedStatus, PendingStatus newStatus) {
+    checkState(pendingInstallationStatus == expectedStatus,
+      "Can't move to {} when status is {} (should be {})",
+      newStatus, pendingInstallationStatus, expectedStatus);
+    this.pendingInstallationStatus = newStatus;
+  }
+
+  private void persistProperties() {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      InternalPropertiesDao internalPropertiesDao = dbClient.internalPropertiesDao();
+      if (pendingInstallationStatus == NONE) {
+        internalPropertiesDao.saveAsEmpty(dbSession, PENDING_EDITION_KEY);
+        internalPropertiesDao.saveAsEmpty(dbSession, PENDING_LICENSE);
+      } else {
+        internalPropertiesDao.save(dbSession, PENDING_EDITION_KEY, pendingEditionKey);
+        internalPropertiesDao.save(dbSession, PENDING_LICENSE, pendingLicense);
+      }
+      if (currentEditionKey == null) {
+        internalPropertiesDao.saveAsEmpty(dbSession, CURRENT_EDITION_KEY);
+      } else {
+        internalPropertiesDao.save(dbSession, CURRENT_EDITION_KEY, currentEditionKey);
+      }
+      internalPropertiesDao.save(dbSession, PENDING_INSTALLATION_STATUS, pendingInstallationStatus.name());
+      dbSession.commit();
+    }
+  }
+
+  private static void checkLicense(License license) {
+    requireNonNull(license, "license can't be null");
+  }
+
+  private static String emptyToNull(String s) {
+    return s.isEmpty() ? null : s;
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/edition/package-info.java
new file mode 100644 (file)
index 0000000..952be45
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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.edition;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/ws/EditionsWs.java b/server/sonar-server/src/main/java/org/sonar/server/edition/ws/EditionsWs.java
new file mode 100644 (file)
index 0000000..d93af9c
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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.edition.ws;
+
+import java.util.Arrays;
+import org.sonar.api.server.ws.WebService;
+
+public class EditionsWs implements WebService {
+  private final EditionsWsAction[] actions;
+
+  public EditionsWs(EditionsWsAction[] actions) {
+    this.actions = actions;
+  }
+
+  @Override
+  public void define(Context context) {
+    NewController controller = context.createController("api/editions")
+      .setSince("6.7")
+      .setDescription("Manage SonarSource commercial editions.");
+    Arrays.stream(actions).forEach(action -> action.define(controller));
+    controller.done();
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/ws/EditionsWsAction.java b/server/sonar-server/src/main/java/org/sonar/server/edition/ws/EditionsWsAction.java
new file mode 100644 (file)
index 0000000..a1de9b1
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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.edition.ws;
+
+import org.sonar.server.ws.WsAction;
+
+/**
+ * Marker interface for actions of {@link EditionsWs}.
+ */
+public interface EditionsWsAction extends WsAction {
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/ws/StatusAction.java b/server/sonar-server/src/main/java/org/sonar/server/edition/ws/StatusAction.java
new file mode 100644 (file)
index 0000000..4c44999
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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.edition.ws;
+
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.server.edition.EditionManagementState;
+import org.sonar.server.user.UserSession;
+import org.sonar.server.ws.WsUtils;
+import org.sonarqube.ws.WsEditions;
+
+public class StatusAction implements EditionsWsAction {
+  private final UserSession userSession;
+  private final EditionManagementState editionManagementState;
+
+  public StatusAction(UserSession userSession, EditionManagementState editionManagementState) {
+    this.userSession = userSession;
+    this.editionManagementState = editionManagementState;
+  }
+
+  @Override
+  public void define(WebService.NewController controller) {
+    controller.createAction("status")
+      .setSince("6.7")
+      .setPost(false)
+      .setDescription("Provide status of SonarSource commercial edition of the current SonarQube. Require 'Administer System' permission.")
+      .setResponseExample(getClass().getResource("example-edition-status.json"))
+      .setHandler(this);
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    userSession
+      .checkLoggedIn()
+      .checkIsSystemAdministrator();
+
+    WsEditions.StatusResponse.Builder responseBuilder = WsEditions.StatusResponse.newBuilder()
+      .setCurrentEditionKey(editionManagementState.getCurrentEditionKey().orElse(""))
+      .setNextEditionKey(editionManagementState.getPendingEditionKey().orElse(""))
+      .setInstallationStatus(WsEditions.InstallationStatus.valueOf(editionManagementState.getPendingInstallationStatus().name()));
+
+    WsUtils.writeProtobuf(responseBuilder.build(), request, response);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/edition/ws/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/edition/ws/package-info.java
new file mode 100644 (file)
index 0000000..39e2c64
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 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.edition.ws;
+
+import javax.annotation.ParametersAreNonnullByDefault;
index 56376b8e7a24aacf88b5dfea8cdaba9eb599b8c9..7038d965aefd9aa4860c6aad1bcc3109b848b008 100644 (file)
@@ -53,6 +53,7 @@ import org.sonar.server.debt.DebtRulesXMLImporter;
 import org.sonar.server.duplication.ws.DuplicationsParser;
 import org.sonar.server.duplication.ws.DuplicationsWs;
 import org.sonar.server.duplication.ws.ShowResponseBuilder;
+import org.sonar.server.edition.EditionsWsModule;
 import org.sonar.server.email.ws.EmailsWsModule;
 import org.sonar.server.es.IndexCreator;
 import org.sonar.server.es.IndexDefinitions;
@@ -527,6 +528,9 @@ public class PlatformLevel4 extends PlatformLevel {
       CeModule.class,
       CeWsModule.class,
 
+      // SonarSource editions
+      EditionsWsModule.class,
+
       InternalPropertiesImpl.class,
       ProjectConfigurationFactory.class,
 
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/edition/ws/example-edition-status.json b/server/sonar-server/src/main/resources/org/sonar/server/edition/ws/example-edition-status.json
new file mode 100644 (file)
index 0000000..aff7273
--- /dev/null
@@ -0,0 +1,5 @@
+{
+  "currentEditionKey": "",
+  "installationStatus": "AUTOMATIC_READY",
+  "nextEditionKey": "developer-edition"
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/edition/EditionsWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/edition/EditionsWsModuleTest.java
new file mode 100644 (file)
index 0000000..c4fae54
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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.edition;
+
+import org.junit.Test;
+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 EditionsWsModuleTest {
+  private EditionsWsModule underTest = new EditionsWsModule();
+
+  @Test
+  public void verify_component_count() {
+    ComponentContainer container = new ComponentContainer();
+    underTest.configure(container);
+
+    assertThat(container.getPicoContainer().getComponentAdapters())
+      .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 3);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/edition/LicenseTest.java b/server/sonar-server/src/test/java/org/sonar/server/edition/LicenseTest.java
new file mode 100644 (file)
index 0000000..51c0e27
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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.edition;
+
+import com.google.common.collect.ImmutableList;
+import java.util.Collections;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class LicenseTest {
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Test
+  public void constructor_fails_if_editionKey_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("editionKey can't be null");
+
+    new License(null, Collections.emptyList(), "content");
+  }
+
+  @Test
+  public void constructor_fails_if_editionKey_is_empty() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("editionKey can't be empty");
+
+    new License("", Collections.emptyList(), "content");
+  }
+
+  @Test
+  public void constructor_fails_if_plugins_is_null() {
+    expectedException.expect(NullPointerException.class);
+
+    new License("edition-key", null, "content");
+  }
+
+  @Test
+  public void constructor_fails_if_content_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("content can't be null");
+
+    new License("edition-key", Collections.emptyList(), null);
+  }
+
+  @Test
+  public void constructor_fails_if_content_is_empty() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("content can't be empty");
+
+    new License("edition-key", Collections.emptyList(), "");
+  }
+
+  @Test
+  public void verify_getters() {
+    ImmutableList<String> pluginKeys = ImmutableList.of("a", "b", "c");
+
+    License underTest = new License("edition-key", pluginKeys, "content");
+
+    assertThat(underTest.getEditionKey()).isEqualTo("edition-key");
+    assertThat(underTest.getPluginKeys()).isEqualTo(pluginKeys);
+    assertThat(underTest.getContent()).isEqualTo("content");
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/edition/ws/StatusActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/edition/ws/StatusActionTest.java
new file mode 100644 (file)
index 0000000..e37a88f
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with 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.edition.ws;
+
+import java.util.Optional;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.server.edition.EditionManagementState;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.WsActionTester;
+import org.sonar.test.JsonAssert;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class StatusActionTest {
+  @Rule
+  public UserSessionRule userSessionRule = UserSessionRule.standalone();
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private EditionManagementState editionManagementState = mock(EditionManagementState.class);
+  private StatusAction underTest = new StatusAction(userSessionRule, editionManagementState);
+  private WsActionTester actionTester = new WsActionTester(underTest);
+
+  @Test
+  public void verify_definition() {
+    WebService.Action def = actionTester.getDef();
+    assertThat(def.key()).isEqualTo("status");
+    assertThat(def.since()).isEqualTo("6.7");
+    assertThat(def.isPost()).isFalse();
+    assertThat(def.description()).isNotEmpty();
+    assertThat(def.params()).isEmpty();
+  }
+
+  @Test
+  public void request_fails_if_user_not_logged_in() {
+    userSessionRule.anonymous();
+    TestRequest request = actionTester.newRequest();
+
+    expectedException.expect(UnauthorizedException.class);
+    expectedException.expectMessage("Authentication is required");
+
+    request.execute();
+  }
+
+  @Test
+  public void request_fails_if_user_is_not_system_administer() {
+    userSessionRule.logIn();
+    TestRequest request = actionTester.newRequest();
+
+    expectedException.expect(ForbiddenException.class);
+    expectedException.expectMessage("Insufficient privileges");
+
+    request.execute();
+  }
+
+  @Test
+  public void verify_example() {
+    userSessionRule.logIn().setSystemAdministrator();
+    when(editionManagementState.getCurrentEditionKey()).thenReturn(Optional.empty());
+    when(editionManagementState.getPendingEditionKey()).thenReturn(Optional.of("developer-edition"));
+    when(editionManagementState.getPendingInstallationStatus()).thenReturn(EditionManagementState.PendingStatus.AUTOMATIC_READY);
+
+    TestRequest request = actionTester.newRequest();
+
+    JsonAssert.assertJson(request.execute().getInput()).isSimilarTo(actionTester.getDef().responseExampleAsString());
+  }
+
+  @Test
+  public void response_contains_optional_fields_as_empty_string() {
+    userSessionRule.logIn().setSystemAdministrator();
+    when(editionManagementState.getCurrentEditionKey()).thenReturn(Optional.empty());
+    when(editionManagementState.getPendingEditionKey()).thenReturn(Optional.empty());
+    when(editionManagementState.getPendingInstallationStatus()).thenReturn(EditionManagementState.PendingStatus.NONE);
+
+    TestRequest request = actionTester.newRequest();
+
+    JsonAssert.assertJson(request.execute().getInput())
+      .isSimilarTo("{" +
+        "  \"currentEditionKey\": \"\"," +
+        "  \"installationStatus\": \"NONE\"," +
+        "  \"nextEditionKey\": \"\"" +
+        "}");
+  }
+}
diff --git a/sonar-ws/src/main/protobuf/ws-editions.proto b/sonar-ws/src/main/protobuf/ws-editions.proto
new file mode 100644 (file)
index 0000000..9e48671
--- /dev/null
@@ -0,0 +1,40 @@
+// SonarQube, open source software quality management tool.
+// Copyright (C) 2008-2016 SonarSource
+// mailto:contact AT sonarsource DOT com
+//
+// SonarQube is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 3 of the License, or (at your option) any later version.
+//
+// SonarQube is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+syntax = "proto2";
+
+package sonarqube.ws.editions;
+
+option java_package = "org.sonarqube.ws";
+option java_outer_classname = "WsEditions";
+option optimize_for = SPEED;
+
+// GET api/editions/status
+message StatusResponse {
+  optional string currentEditionKey = 1;
+  optional InstallationStatus installationStatus = 2;
+  optional string nextEditionKey = 3;
+}
+
+enum InstallationStatus {
+  NONE = 0;
+  AUTOMATIC_IN_PROGRESS = 1;
+  AUTOMATIC_READY = 2;
+  AUTOMATIC_FAILED = 3;
+  MANUAL_IN_PROGRESS = 4;
+}