From 80942bfb9760881f2c26969c885d370fd4abd92e Mon Sep 17 00:00:00 2001 From: lukasz-jarocki-sonarsource Date: Mon, 29 Apr 2024 12:22:39 +0200 Subject: [PATCH] SONAR-22141 added new endpoint /api/v2/system/migrations-status --- .../db/migration/DatabaseMigrationState.java | 28 +++- .../org/sonar/server/v2/WebApiEndpoints.java | 1 + .../DatabaseMigrationsController.java | 86 ++++++++++ .../sonar/server/v2/common/DateString.java | 37 +++++ .../v2/config/PlatformLevel4WebConfig.java | 10 ++ .../server/v2/config/SafeModeWebConfig.java | 10 ++ .../DatabaseMigrationsControllerTest.java | 152 ++++++++++++++++++ .../platform/ws/DbMigrationJsonWriter.java | 81 +++------- .../platform/ws/DbMigrationStatusAction.java | 4 +- .../server/platform/ws/MigrateDbAction.java | 4 +- .../ws/DbMigrationStatusActionTest.java | 28 ++-- .../server/platform/ws/StatusActionTest.java | 7 +- 12 files changed, 358 insertions(+), 90 deletions(-) create mode 100644 server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsController.java create mode 100644 server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/common/DateString.java create mode 100644 server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsControllerTest.java diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationState.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationState.java index 2ee65bc6623..82bb1204e1c 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationState.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationState.java @@ -24,8 +24,34 @@ import javax.annotation.CheckForNull; public interface DatabaseMigrationState { + String UNSUPPORTED_DATABASE_MIGRATION_STATUS = "Unsupported DatabaseMigration status"; + String NO_CONNECTION_TO_DB = "Cannot connect to Database."; + enum Status { - NONE, RUNNING, FAILED, SUCCEEDED + NONE("NO_MIGRATION", "Database is up-to-date, no migration needed."), + RUNNING("MIGRATION_RUNNING", "Database migration is running."), + FAILED("MIGRATION_FAILED", "Migration failed: %s.
Please check logs."), + SUCCEEDED("MIGRATION_SUCCEEDED", "Migration succeeded."), + STATUS_NOT_SUPPORTED("NOT_SUPPORTED", "Upgrade is not supported on embedded database."), + MIGRATION_REQUIRED("MIGRATION_REQUIRED", "Database migration is required. DB migration can be started using WS /api/system/migrate_db."); + + private final String stringRepresentation; + private final String message; + + Status(String stringRepresentation, String message) { + this.stringRepresentation = stringRepresentation; + this.message = message; + } + + public String getMessage() { + return message; + } + + @Override + public String toString() { + return stringRepresentation; + } + } /** diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java index b1abd6013b4..a4e820e1be5 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java @@ -23,6 +23,7 @@ public class WebApiEndpoints { private static final String SYSTEM_ENDPOINTS = "/system"; public static final String LIVENESS_ENDPOINT = SYSTEM_ENDPOINTS + "/liveness"; public static final String HEALTH_ENDPOINT = SYSTEM_ENDPOINTS + "/health"; + public static final String DATABASE_MIGRATIONS_ENDPOINT = SYSTEM_ENDPOINTS + "/migrations-status"; public static final String USERS_MANAGEMENT_DOMAIN = "/users-management"; public static final String USER_ENDPOINT = USERS_MANAGEMENT_DOMAIN + "/users"; diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsController.java new file mode 100644 index 00000000000..d585473bc56 --- /dev/null +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsController.java @@ -0,0 +1,86 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with 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.v2.api.system.controller; + +import io.swagger.v3.oas.annotations.Operation; +import java.util.Optional; +import javax.annotation.Nullable; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.DatabaseMigrationState; +import org.sonar.server.platform.db.migration.version.DatabaseVersion; +import org.sonar.server.v2.common.DateString; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static com.google.common.base.Preconditions.checkState; +import static org.sonar.server.platform.db.migration.DatabaseMigrationState.NO_CONNECTION_TO_DB; +import static org.sonar.server.platform.db.migration.DatabaseMigrationState.UNSUPPORTED_DATABASE_MIGRATION_STATUS; +import static org.sonar.server.v2.WebApiEndpoints.DATABASE_MIGRATIONS_ENDPOINT; + +@RestController +@RequestMapping(DATABASE_MIGRATIONS_ENDPOINT) +public class DatabaseMigrationsController { + + private final DatabaseVersion databaseVersion; + private final DatabaseMigrationState databaseMigrationState; + private final Database database; + + public DatabaseMigrationsController(DatabaseVersion databaseVersion, DatabaseMigrationState databaseMigrationState, Database database) { + this.databaseVersion = databaseVersion; + this.databaseMigrationState = databaseMigrationState; + this.database = database; + } + + @Operation(summary = "Gets the status of ongoing database migrations, if any", description = "Return the detailed status of ongoing database migrations" + + " including starting date. If no migration is ongoing or needed it is still possible to call this endpoint and receive appropriate information.") + @GetMapping + public DatabaseMigrationsResponse getStatus() { + Optional currentVersion = databaseVersion.getVersion(); + checkState(currentVersion.isPresent(), NO_CONNECTION_TO_DB); + DatabaseVersion.Status status = databaseVersion.getStatus(); + if (status == DatabaseVersion.Status.UP_TO_DATE || status == DatabaseVersion.Status.REQUIRES_DOWNGRADE) { + return new DatabaseMigrationsResponse(databaseMigrationState); + } else if (!database.getDialect().supportsMigration()) { + return new DatabaseMigrationsResponse(DatabaseMigrationState.Status.STATUS_NOT_SUPPORTED); + } else { + return switch (databaseMigrationState.getStatus()) { + case RUNNING, FAILED, SUCCEEDED -> new DatabaseMigrationsResponse(databaseMigrationState); + case NONE -> new DatabaseMigrationsResponse(DatabaseMigrationState.Status.MIGRATION_REQUIRED); + default -> throw new IllegalArgumentException(UNSUPPORTED_DATABASE_MIGRATION_STATUS); + }; + } + + } + + public record DatabaseMigrationsResponse(String status, @Nullable String startedAt, @Nullable String message) { + + public DatabaseMigrationsResponse(DatabaseMigrationState state) { + this(state.getStatus().toString(), + DateString.from(state.getStartedAt()), + state.getError() != null ? state.getError().getMessage() : state.getStatus().getMessage()); + } + + public DatabaseMigrationsResponse(DatabaseMigrationState.Status status) { + this(status.toString(), null, status.getMessage()); + } + } + +} diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/common/DateString.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/common/DateString.java new file mode 100644 index 00000000000..1f7cbd6c483 --- /dev/null +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/common/DateString.java @@ -0,0 +1,37 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with 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.v2.common; + +import java.util.Date; +import javax.annotation.Nullable; + +public class DateString { + + private DateString() { + // intentionally empty + } + + public static String from(@Nullable Date date) { + if (date == null) { + return null; + } + return date.toString(); + } +} diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java index d63cef69138..7ec4a6387b8 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java @@ -22,6 +22,7 @@ package org.sonar.server.v2.config; import javax.annotation.Nullable; import org.sonar.api.platform.Server; import org.sonar.api.resources.Languages; +import org.sonar.db.Database; import org.sonar.db.DbClient; import org.sonar.server.common.gitlab.config.GitlabConfigurationService; import org.sonar.server.common.group.service.GroupMembershipService; @@ -41,6 +42,8 @@ import org.sonar.server.common.user.service.UserService; import org.sonar.server.health.HealthChecker; import org.sonar.server.platform.NodeInformation; import org.sonar.server.platform.ServerFileSystem; +import org.sonar.server.platform.db.migration.DatabaseMigrationState; +import org.sonar.server.platform.db.migration.version.DatabaseVersion; import org.sonar.server.rule.RuleDescriptionFormatter; import org.sonar.server.user.SystemPasscode; import org.sonar.server.user.UserSession; @@ -69,6 +72,7 @@ import org.sonar.server.v2.api.projects.controller.DefaultBoundProjectsControlle import org.sonar.server.v2.api.rule.controller.DefaultRuleController; import org.sonar.server.v2.api.rule.controller.RuleController; import org.sonar.server.v2.api.rule.converter.RuleRestResponseGenerator; +import org.sonar.server.v2.api.system.controller.DatabaseMigrationsController; import org.sonar.server.v2.api.system.controller.DefaultLivenessController; import org.sonar.server.v2.api.system.controller.HealthController; import org.sonar.server.v2.api.system.controller.LivenessController; @@ -102,6 +106,12 @@ public class PlatformLevel4WebConfig { return new HealthController(healthChecker, systemPasscode, nodeInformation, userSession); } + @Bean + public DatabaseMigrationsController databaseMigrationsController(DatabaseVersion databaseVersion, DatabaseMigrationState databaseMigrationState, + Database database) { + return new DatabaseMigrationsController(databaseVersion, databaseMigrationState, database); + } + @Bean public UsersSearchRestResponseGenerator usersSearchResponseGenerator(UserSession userSession) { return new UsersSearchRestResponseGenerator(userSession); diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/SafeModeWebConfig.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/SafeModeWebConfig.java index 04d43567764..f235bd848b3 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/SafeModeWebConfig.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/SafeModeWebConfig.java @@ -19,11 +19,15 @@ */ package org.sonar.server.v2.config; +import org.sonar.db.Database; import org.sonar.server.common.health.DbConnectionNodeCheck; import org.sonar.server.common.platform.LivenessChecker; import org.sonar.server.common.platform.SafeModeLivenessCheckerImpl; import org.sonar.server.health.HealthChecker; +import org.sonar.server.platform.db.migration.DatabaseMigrationState; +import org.sonar.server.platform.db.migration.version.DatabaseVersion; import org.sonar.server.user.SystemPasscode; +import org.sonar.server.v2.api.system.controller.DatabaseMigrationsController; import org.sonar.server.v2.api.system.controller.DefaultLivenessController; import org.sonar.server.v2.api.system.controller.HealthController; import org.sonar.server.v2.api.system.controller.LivenessController; @@ -51,4 +55,10 @@ public class SafeModeWebConfig { public HealthController healthController(HealthChecker healthChecker, SystemPasscode systemPasscode) { return new HealthController(healthChecker, systemPasscode); } + + @Bean + public DatabaseMigrationsController databaseMigrationsController(DatabaseVersion databaseVersion, DatabaseMigrationState databaseMigrationState, + Database database) { + return new DatabaseMigrationsController(databaseVersion, databaseMigrationState, database); + } } diff --git a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsControllerTest.java b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsControllerTest.java new file mode 100644 index 00000000000..5b2bc2d5f05 --- /dev/null +++ b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/system/controller/DatabaseMigrationsControllerTest.java @@ -0,0 +1,152 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with 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.v2.api.system.controller; + +import java.util.Date; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.sonar.db.Database; +import org.sonar.db.dialect.Dialect; +import org.sonar.server.platform.db.migration.DatabaseMigrationState; +import org.sonar.server.platform.db.migration.version.DatabaseVersion; +import org.sonar.server.v2.api.ControllerTester; +import org.springframework.test.web.servlet.MockMvc; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.server.platform.db.migration.DatabaseMigrationState.Status.FAILED; +import static org.sonar.server.platform.db.migration.DatabaseMigrationState.Status.NONE; +import static org.sonar.server.platform.db.migration.DatabaseMigrationState.Status.RUNNING; +import static org.sonar.server.v2.WebApiEndpoints.DATABASE_MIGRATIONS_ENDPOINT; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +class DatabaseMigrationsControllerTest { + + private static final Date SOME_DATE = new Date(); + private final DatabaseVersion databaseVersion = mock(); + private final DatabaseMigrationState migrationState = mock(); + private final Dialect dialect = mock(Dialect.class); + private final Database database = mock(); + private final MockMvc mockMvc = ControllerTester.getMockMvc(new DatabaseMigrationsController(databaseVersion, migrationState, database)); + + @BeforeEach + public void before() { + when(database.getDialect()).thenReturn(dialect); + when(databaseVersion.getVersion()).thenReturn(Optional.of(1L)); + } + + @Test + void getStatus_whenDatabaseHasNoVersion_return500() throws Exception { + Mockito.reset(databaseVersion); + when(databaseVersion.getVersion()).thenReturn(Optional.empty()); + + mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().is5xxServerError(), + content().json("{\"message\":\"Cannot connect to Database.\"}")); + } + + @Test + void getStatus_migrationNotNeeded_returnUpToDateStatus() throws Exception { + when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.UP_TO_DATE); + when(migrationState.getStatus()).thenReturn(NONE); + + mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().isOk(), + content().json("{\"status\":\"NO_MIGRATION\",\"message\":\"Database is up-to-date, no migration needed.\"}")); + } + + @Test + void getStatus_whenDowngradeRequired_returnNone() throws Exception { + when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.REQUIRES_DOWNGRADE); + when(migrationState.getStatus()).thenReturn(NONE); + + mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().isOk(), + content().json("{\"status\":\"NO_MIGRATION\",\"message\":\"Database is up-to-date, no migration needed.\"}")); + } + + @Test + void getStatus_whenDbRequiresUpgradeButDialectIsNotSupported_returnNotSupported() throws Exception { + when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.FRESH_INSTALL); + when(dialect.supportsMigration()).thenReturn(false); + + mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().isOk(), + content().json("{\"status\":\"NOT_SUPPORTED\",\"message\":\"Upgrade is not supported on embedded database.\"}")); + } + + @Test + void getStatus_whenDbMigrationsRunning_returnRunning() throws Exception { + when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.REQUIRES_UPGRADE); + when(dialect.supportsMigration()).thenReturn(true); + when(migrationState.getStatus()).thenReturn(RUNNING); + when(migrationState.getStartedAt()).thenReturn(SOME_DATE); + + mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().isOk(), + content().json("{\"status\":\"MIGRATION_RUNNING\",\"message\":\"Database migration is running.\"}")); + } + + @Test + void getStatus_whenDbMigrationsFailed_returnFailed() throws Exception { + when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.REQUIRES_UPGRADE); + when(dialect.supportsMigration()).thenReturn(true); + when(migrationState.getStatus()).thenReturn(DatabaseMigrationState.Status.FAILED); + when(migrationState.getStartedAt()).thenReturn(SOME_DATE); + + mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().isOk(), + content().json("{\"status\":\"MIGRATION_FAILED\",\"message\":\"Migration failed: %s.
Please check logs.\"}")); + } + + @Test + void getStatus_whenDbMigrationsSucceeded_returnSucceeded() throws Exception { + when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.REQUIRES_UPGRADE); + when(dialect.supportsMigration()).thenReturn(true); + when(migrationState.getStatus()).thenReturn(DatabaseMigrationState.Status.SUCCEEDED); + when(migrationState.getStartedAt()).thenReturn(SOME_DATE); + + mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().isOk(), + content().json("{\"status\":\"MIGRATION_SUCCEEDED\",\"message\":\"Migration succeeded.\"}")); + } + + + @Test + void getStatus_whenMigrationRequired_returnMigrationRequired() throws Exception { + when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.REQUIRES_UPGRADE); + when(dialect.supportsMigration()).thenReturn(true); + when(migrationState.getStatus()).thenReturn(NONE); + when(migrationState.getStartedAt()).thenReturn(SOME_DATE); + + mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().isOk(), + content().json("{\"status\":\"MIGRATION_REQUIRED\",\"message\":\"Database migration is required. DB migration " + + "can be started using WS /api/system/migrate_db.\"}")); + } + + @Test + void getStatus_whenMigrationFailedWithError_IncludeErrorInResponse() throws Exception { + when(databaseVersion.getStatus()).thenReturn(DatabaseVersion.Status.FRESH_INSTALL); + when(dialect.supportsMigration()).thenReturn(true); + when(migrationState.getStatus()).thenReturn(FAILED); + when(migrationState.getStartedAt()).thenReturn(SOME_DATE); + when(migrationState.getError()).thenReturn(new UnsupportedOperationException("error message")); + + mockMvc.perform(get(DATABASE_MIGRATIONS_ENDPOINT)).andExpectAll(status().isOk(), + content().json("{\"status\":\"MIGRATION_FAILED\",\"message\":\"error message\"}")); + } +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/DbMigrationJsonWriter.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/DbMigrationJsonWriter.java index 4668fbfa52a..29bbe25079c 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/DbMigrationJsonWriter.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/DbMigrationJsonWriter.java @@ -22,6 +22,8 @@ package org.sonar.server.platform.ws; import org.sonar.api.utils.text.JsonWriter; import org.sonar.server.platform.db.migration.DatabaseMigrationState; +import static org.sonar.server.platform.db.migration.DatabaseMigrationState.Status.FAILED; +import static org.sonar.server.platform.db.migration.DatabaseMigrationState.Status.MIGRATION_REQUIRED; import static org.sonar.server.platform.db.migration.DatabaseMigrationState.Status.RUNNING; public class DbMigrationJsonWriter { @@ -29,21 +31,6 @@ public class DbMigrationJsonWriter { static final String FIELD_MESSAGE = "message"; static final String FIELD_STARTED_AT = "startedAt"; - static final String STATUS_NO_MIGRATION = "NO_MIGRATION"; - static final String STATUS_NOT_SUPPORTED = "NOT_SUPPORTED"; - static final String STATUS_MIGRATION_RUNNING = "MIGRATION_RUNNING"; - static final String STATUS_MIGRATION_FAILED = "MIGRATION_FAILED"; - static final String STATUS_MIGRATION_SUCCEEDED = "MIGRATION_SUCCEEDED"; - static final String STATUS_MIGRATION_REQUIRED = "MIGRATION_REQUIRED"; - - static final String NO_CONNECTION_TO_DB = "Cannot connect to Database."; - static final String UNSUPPORTED_DATABASE_MIGRATION_STATUS = "Unsupported DatabaseMigration status"; - static final String MESSAGE_NO_MIGRATION_ON_EMBEDDED_DATABASE = "Upgrade is not supported on embedded database."; - static final String MESSAGE_MIGRATION_REQUIRED = "Database migration is required. DB migration can be started using WS /api/system/migrate_db."; - static final String MESSAGE_STATUS_NONE = "Database is up-to-date, no migration needed."; - static final String MESSAGE_STATUS_RUNNING = "Database migration is running."; - static final String MESSAGE_STATUS_SUCCEEDED = "Migration succeeded."; - static final String MESSAGE_STATUS_FAILED = "Migration failed: %s.
Please check logs."; private DbMigrationJsonWriter() { // static methods only @@ -51,73 +38,43 @@ public class DbMigrationJsonWriter { static void write(JsonWriter json, DatabaseMigrationState databaseMigrationState) { json.beginObject() - .prop(FIELD_STATE, statusToJson(databaseMigrationState.getStatus())) - .prop(FIELD_MESSAGE, buildMessage(databaseMigrationState)) + .prop(FIELD_STATE, databaseMigrationState.getStatus().toString()) + .prop(FIELD_MESSAGE, writeMessageIncludingError(databaseMigrationState)) .propDateTime(FIELD_STARTED_AT, databaseMigrationState.getStartedAt()) .endObject(); } + private static String writeMessageIncludingError(DatabaseMigrationState state) { + if (state.getStatus() == FAILED) { + Throwable error = state.getError(); + return String.format(state.getStatus().getMessage(), error != null ? error.getMessage() : "No failure error"); + } else { + return state.getStatus().getMessage(); + } + } + static void writeNotSupportedResponse(JsonWriter json) { json.beginObject() - .prop(FIELD_STATE, STATUS_NOT_SUPPORTED) - .prop(FIELD_MESSAGE, MESSAGE_NO_MIGRATION_ON_EMBEDDED_DATABASE) + .prop(FIELD_STATE, DatabaseMigrationState.Status.STATUS_NOT_SUPPORTED.toString()) + .prop(FIELD_MESSAGE, DatabaseMigrationState.Status.STATUS_NOT_SUPPORTED.getMessage()) .endObject(); } static void writeJustStartedResponse(JsonWriter json, DatabaseMigrationState databaseMigrationState) { json.beginObject() - .prop(FIELD_STATE, statusToJson(RUNNING)) - .prop(FIELD_MESSAGE, MESSAGE_STATUS_RUNNING) + .prop(FIELD_STATE, RUNNING.toString()) + .prop(FIELD_MESSAGE, RUNNING.getMessage()) .propDateTime(FIELD_STARTED_AT, databaseMigrationState.getStartedAt()) .endObject(); } static void writeMigrationRequiredResponse(JsonWriter json) { json.beginObject() - .prop(FIELD_STATE, STATUS_MIGRATION_REQUIRED) - .prop(FIELD_MESSAGE, MESSAGE_MIGRATION_REQUIRED) + .prop(FIELD_STATE, MIGRATION_REQUIRED.toString()) + .prop(FIELD_MESSAGE, MIGRATION_REQUIRED.getMessage()) .endObject(); } - private static String statusToJson(DatabaseMigrationState.Status status) { - switch (status) { - case NONE: - return STATUS_NO_MIGRATION; - case RUNNING: - return STATUS_MIGRATION_RUNNING; - case FAILED: - return STATUS_MIGRATION_FAILED; - case SUCCEEDED: - return STATUS_MIGRATION_SUCCEEDED; - default: - throw new IllegalArgumentException( - "Unsupported DatabaseMigration.Status " + status + " can not be converted to JSON value"); - } - } - - private static String buildMessage(DatabaseMigrationState databaseMigrationState) { - switch (databaseMigrationState.getStatus()) { - case NONE: - return MESSAGE_STATUS_NONE; - case RUNNING: - return MESSAGE_STATUS_RUNNING; - case SUCCEEDED: - return MESSAGE_STATUS_SUCCEEDED; - case FAILED: - return String.format(MESSAGE_STATUS_FAILED, failureMessage(databaseMigrationState)); - default: - return UNSUPPORTED_DATABASE_MIGRATION_STATUS; - } - } - - private static String failureMessage(DatabaseMigrationState databaseMigrationState) { - Throwable failureError = databaseMigrationState.getError(); - if (failureError == null) { - return "No failure error"; - } - return failureError.getMessage(); - } - static String statusDescription() { return "State values are:" + "