diff options
author | Sébastien Lesaint <sebastien.lesaint@sonarsource.com> | 2017-10-18 18:09:16 +0200 |
---|---|---|
committer | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2017-10-23 08:01:13 -0700 |
commit | 88bccc2a748c04602940dd41c9c8fbe09dacbc7c (patch) | |
tree | 02dd9eb95c74d282364d2d5aa30757ad59c4e352 /server/sonar-server | |
parent | 9c48d41cee8baa7ae557e77faf22afce0c4a0b85 (diff) | |
download | sonarqube-88bccc2a748c04602940dd41c9c8fbe09dacbc7c.tar.gz sonarqube-88bccc2a748c04602940dd41c9c8fbe09dacbc7c.zip |
SONAR-10002 make StandaloneEditionManagementStateImpl thread safe
and no state not persisted to DB can be read
Diffstat (limited to 'server/sonar-server')
2 files changed, 202 insertions, 83 deletions
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 index 25b68072782..475ed421c36 100644 --- 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 @@ -23,7 +23,9 @@ import com.google.common.collect.ImmutableSet; import java.util.Arrays; import java.util.Map; import java.util.Optional; +import javax.annotation.CheckForNull; import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; import org.picocontainer.Startable; import org.sonar.db.DbClient; import org.sonar.db.DbSession; @@ -47,11 +49,8 @@ public class StandaloneEditionManagementStateImpl implements MutableEditionManag private static final String INSTALL_ERROR_MESSAGE = "installError"; private final DbClient dbClient; - private String currentEditionKey; - private PendingStatus pendingInstallationStatus; - private String pendingEditionKey; - private String pendingLicense; - private String installErrorMessage; + @CheckForNull + private State state; public StandaloneEditionManagementStateImpl(DbClient dbClient) { this.dbClient = dbClient; @@ -63,22 +62,26 @@ public class StandaloneEditionManagementStateImpl implements MutableEditionManag // 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, INSTALL_ERROR_MESSAGE)); - this.currentEditionKey = internalPropertyValues.getOrDefault(CURRENT_EDITION_KEY, empty()) - .map(StandaloneEditionManagementStateImpl::emptyToNull) - .orElse(null); - this.pendingInstallationStatus = internalPropertyValues.getOrDefault(PENDING_INSTALLATION_STATUS, empty()) + + PendingStatus pendingInstallationStatus = internalPropertyValues.getOrDefault(PENDING_INSTALLATION_STATUS, empty()) .map(StandaloneEditionManagementStateImpl::emptyToNull) .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()) - .map(StandaloneEditionManagementStateImpl::emptyToNull) - .orElse(null); - this.installErrorMessage = internalPropertyValues.getOrDefault(INSTALL_ERROR_MESSAGE, empty()) - .map(StandaloneEditionManagementStateImpl::emptyToNull) - .orElse(null); + State.Builder builder = State.newBuilder(pendingInstallationStatus); + builder + .setCurrentEditionKey(internalPropertyValues.getOrDefault(CURRENT_EDITION_KEY, empty()) + .map(StandaloneEditionManagementStateImpl::emptyToNull) + .orElse(null)) + .setPendingEditionKey(internalPropertyValues.getOrDefault(PENDING_EDITION_KEY, empty()) + .map(StandaloneEditionManagementStateImpl::emptyToNull) + .orElse(null)) + .setPendingLicense(internalPropertyValues.getOrDefault(PENDING_LICENSE, empty()) + .map(StandaloneEditionManagementStateImpl::emptyToNull) + .orElse(null)) + .setInstallErrorMessage(internalPropertyValues.getOrDefault(INSTALL_ERROR_MESSAGE, empty()) + .map(StandaloneEditionManagementStateImpl::emptyToNull) + .orElse(null)); + state = builder.build(); } } @@ -90,56 +93,57 @@ public class StandaloneEditionManagementStateImpl implements MutableEditionManag @Override public Optional<String> getCurrentEditionKey() { ensureStarted(); - return Optional.ofNullable(currentEditionKey); + return Optional.ofNullable(state.getCurrentEditionKey()); } @Override public PendingStatus getPendingInstallationStatus() { ensureStarted(); - return pendingInstallationStatus; + return state.getPendingInstallationStatus(); } @Override public Optional<String> getPendingEditionKey() { ensureStarted(); - return Optional.ofNullable(pendingEditionKey); + return Optional.ofNullable(state.getPendingEditionKey()); } @Override public Optional<String> getPendingLicense() { ensureStarted(); - return Optional.ofNullable(pendingLicense); + return Optional.ofNullable(state.getPendingLicense()); } @Override public Optional<String> getInstallErrorMessage() { ensureStarted(); - return Optional.ofNullable(installErrorMessage); + return Optional.ofNullable(state.getInstallErrorMessage()); } @Override public synchronized PendingStatus startAutomaticInstall(License license) { ensureStarted(); checkLicense(license); - changeStatusToFrom(AUTOMATIC_IN_PROGRESS, NONE); - this.pendingLicense = license.getContent(); - this.pendingEditionKey = license.getEditionKey(); - this.installErrorMessage = null; - persistProperties(); - return this.pendingInstallationStatus; + State newState = changeStatusToFrom(AUTOMATIC_IN_PROGRESS, NONE) + .setPendingLicense(license.getContent()) + .setPendingEditionKey(license.getEditionKey()) + .clearAutomaticInstallErrorMessage() + .build(); + persistProperties(newState); + return newState.getPendingInstallationStatus(); } @Override public synchronized PendingStatus startManualInstall(License license) { ensureStarted(); checkLicense(license); - changeStatusToFrom(MANUAL_IN_PROGRESS, NONE); - this.pendingLicense = license.getContent(); - this.pendingEditionKey = license.getEditionKey(); - this.pendingInstallationStatus = MANUAL_IN_PROGRESS; - this.installErrorMessage = null; - persistProperties(); - return this.pendingInstallationStatus; + State newState = changeStatusToFrom(MANUAL_IN_PROGRESS, NONE) + .setPendingLicense(license.getContent()) + .setPendingEditionKey(license.getEditionKey()) + .clearAutomaticInstallErrorMessage() + .build(); + persistProperties(newState); + return newState.getPendingInstallationStatus(); } @Override @@ -147,89 +151,94 @@ public class StandaloneEditionManagementStateImpl implements MutableEditionManag ensureStarted(); requireNonNull(newEditionKey, "newEditionKey can't be null"); checkArgument(!newEditionKey.isEmpty(), "newEditionKey can't be empty"); - changeStatusToFrom(NONE, NONE); - this.currentEditionKey = newEditionKey; - this.installErrorMessage = null; - persistProperties(); - return this.pendingInstallationStatus; + State newState = changeStatusToFrom(NONE, NONE) + .setCurrentEditionKey(newEditionKey) + .clearAutomaticInstallErrorMessage() + .build(); + persistProperties(newState); + return newState.getPendingInstallationStatus(); } @Override public synchronized PendingStatus automaticInstallReady() { ensureStarted(); - changeStatusToFrom(AUTOMATIC_READY, AUTOMATIC_IN_PROGRESS); - this.installErrorMessage = null; - persistProperties(); - return this.pendingInstallationStatus; + State newState = changeStatusToFrom(AUTOMATIC_READY, AUTOMATIC_IN_PROGRESS) + .clearAutomaticInstallErrorMessage() + .build(); + persistProperties(newState); + return newState.getPendingInstallationStatus(); } @Override public synchronized PendingStatus installFailed(@Nullable String errorMessage) { ensureStarted(); - changeStatusToFrom(NONE, AUTOMATIC_IN_PROGRESS, AUTOMATIC_READY, MANUAL_IN_PROGRESS); - this.installErrorMessage = nullableTrimmedEmptyToNull(errorMessage); - this.pendingEditionKey = null; - this.pendingLicense = null; - persistProperties(); - return this.pendingInstallationStatus; + State newState = changeStatusToFrom(NONE, AUTOMATIC_IN_PROGRESS, AUTOMATIC_READY, MANUAL_IN_PROGRESS) + .setInstallErrorMessage(nullableTrimmedEmptyToNull(errorMessage)) + .clearPendingFields() + .build(); + persistProperties(newState); + return newState.getPendingInstallationStatus(); } @Override public synchronized void clearInstallErrorMessage() { ensureStarted(); - if (this.installErrorMessage != null) { - this.installErrorMessage = null; - persistProperties(); + State currentState = this.state; + if (currentState.getInstallErrorMessage() != null) { + State newState = State.newBuilder(currentState) + .clearAutomaticInstallErrorMessage() + .build(); + persistProperties(newState); } } @Override public synchronized PendingStatus finalizeInstallation() { ensureStarted(); - changeStatusToFrom(NONE, AUTOMATIC_READY, MANUAL_IN_PROGRESS, UNINSTALL_IN_PROGRESS); - - this.pendingInstallationStatus = NONE; - this.currentEditionKey = this.pendingEditionKey; - this.pendingEditionKey = null; - this.pendingLicense = null; - persistProperties(); - return this.pendingInstallationStatus; + State newState = changeStatusToFrom(NONE, AUTOMATIC_READY, MANUAL_IN_PROGRESS, UNINSTALL_IN_PROGRESS) + .commitPendingEditionKey() + .clearPendingFields() + .build(); + persistProperties(newState); + return newState.getPendingInstallationStatus(); } @Override public synchronized PendingStatus uninstall() { ensureStarted(); - changeStatusToFrom(UNINSTALL_IN_PROGRESS, NONE); - checkState(currentEditionKey != null, "There is no edition currently installed"); - - this.pendingInstallationStatus = UNINSTALL_IN_PROGRESS; - this.pendingEditionKey = null; - this.pendingLicense = null; - this.currentEditionKey = null; - persistProperties(); - return this.pendingInstallationStatus; + State.Builder builder = changeStatusToFrom(UNINSTALL_IN_PROGRESS, NONE); + checkState(state.currentEditionKey != null, "There is no edition currently installed"); + State newState = builder + .clearPendingFields() + .clearCurrentEditionKey() + .clearAutomaticInstallErrorMessage() + .build(); + persistProperties(newState); + return newState.getPendingInstallationStatus(); } private void ensureStarted() { - checkState(pendingInstallationStatus != null, "%s is not started", getClass().getSimpleName()); + checkState(state != null, "%s is not started", getClass().getSimpleName()); } - private void changeStatusToFrom(PendingStatus newStatus, PendingStatus... validPendingStatuses) { - checkState(Arrays.stream(validPendingStatuses).anyMatch(s -> s == pendingInstallationStatus), + private State.Builder changeStatusToFrom(PendingStatus newStatus, PendingStatus... validPendingStatuses) { + State currentState = this.state; + checkState(Arrays.stream(validPendingStatuses).anyMatch(s -> s == currentState.getPendingInstallationStatus()), "Can't move to %s when status is %s (should be any of %s)", - newStatus, pendingInstallationStatus, Arrays.toString(validPendingStatuses)); - this.pendingInstallationStatus = newStatus; + newStatus, currentState.getPendingInstallationStatus(), Arrays.toString(validPendingStatuses)); + return State.newBuilder(currentState, newStatus); } - private void persistProperties() { + private void persistProperties(State newState) { try (DbSession dbSession = dbClient.openSession(false)) { InternalPropertiesDao internalPropertiesDao = dbClient.internalPropertiesDao(); - saveInternalProperty(internalPropertiesDao, dbSession, PENDING_EDITION_KEY, pendingEditionKey); - saveInternalProperty(internalPropertiesDao, dbSession, PENDING_LICENSE, pendingLicense); - saveInternalProperty(internalPropertiesDao, dbSession, INSTALL_ERROR_MESSAGE, installErrorMessage); - saveInternalProperty(internalPropertiesDao, dbSession, CURRENT_EDITION_KEY, currentEditionKey); - saveInternalProperty(internalPropertiesDao, dbSession, PENDING_INSTALLATION_STATUS, pendingInstallationStatus.name()); + saveInternalProperty(internalPropertiesDao, dbSession, PENDING_EDITION_KEY, newState.getPendingEditionKey()); + saveInternalProperty(internalPropertiesDao, dbSession, PENDING_LICENSE, newState.getPendingLicense()); + saveInternalProperty(internalPropertiesDao, dbSession, INSTALL_ERROR_MESSAGE, newState.getInstallErrorMessage()); + saveInternalProperty(internalPropertiesDao, dbSession, CURRENT_EDITION_KEY, newState.getCurrentEditionKey()); + saveInternalProperty(internalPropertiesDao, dbSession, PENDING_INSTALLATION_STATUS, newState.getPendingInstallationStatus().name()); dbSession.commit(); + this.state = newState; } } @@ -256,4 +265,114 @@ public class StandaloneEditionManagementStateImpl implements MutableEditionManag private static String emptyToNull(String s) { return s.isEmpty() ? null : s; } + + @Immutable + private static final class State { + private final String currentEditionKey; + private final PendingStatus pendingInstallationStatus; + private final String pendingEditionKey; + private final String pendingLicense; + private final String installErrorMessage; + + public State(Builder builder) { + this.currentEditionKey = builder.currentEditionKey; + this.pendingInstallationStatus = builder.pendingInstallationStatus; + this.pendingEditionKey = builder.pendingEditionKey; + this.pendingLicense = builder.pendingLicense; + this.installErrorMessage = builder.installErrorMessage; + } + + public String getCurrentEditionKey() { + return currentEditionKey; + } + + public PendingStatus getPendingInstallationStatus() { + return pendingInstallationStatus; + } + + public String getPendingEditionKey() { + return pendingEditionKey; + } + + public String getPendingLicense() { + return pendingLicense; + } + + public String getInstallErrorMessage() { + return installErrorMessage; + } + + public static Builder newBuilder(PendingStatus pendingInstallationStatus) { + return new Builder(pendingInstallationStatus); + } + + public static Builder newBuilder(State from) { + return newBuilder(from, from.getPendingInstallationStatus()); + } + + public static Builder newBuilder(State from, PendingStatus newStatus) { + return new Builder(newStatus) + .setCurrentEditionKey(from.currentEditionKey) + .setPendingEditionKey(from.pendingEditionKey) + .setPendingLicense(from.pendingLicense) + .setInstallErrorMessage(from.installErrorMessage); + } + + private static class Builder { + private PendingStatus pendingInstallationStatus; + private String currentEditionKey; + private String pendingEditionKey; + private String pendingLicense; + private String installErrorMessage; + + private Builder(PendingStatus pendingInstallationStatus) { + this.pendingInstallationStatus = requireNonNull(pendingInstallationStatus); + } + + public Builder setCurrentEditionKey(@Nullable String currentEditionKey) { + this.currentEditionKey = currentEditionKey; + return this; + } + + public Builder setPendingEditionKey(@Nullable String pendingEditionKey) { + this.pendingEditionKey = pendingEditionKey; + return this; + } + + public Builder setPendingLicense(@Nullable String pendingLicense) { + this.pendingLicense = pendingLicense; + return this; + } + + public Builder setInstallErrorMessage(@Nullable String installErrorMessage) { + this.installErrorMessage = installErrorMessage; + return this; + } + + public Builder commitPendingEditionKey() { + this.currentEditionKey = pendingEditionKey; + return this; + } + + public Builder clearCurrentEditionKey() { + this.currentEditionKey = null; + return this; + } + + public Builder clearPendingFields() { + this.pendingEditionKey = null; + this.pendingLicense = null; + return this; + } + + public Builder clearAutomaticInstallErrorMessage() { + this.installErrorMessage = null; + return this; + } + + public State build() { + return new State(this); + } + } + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/edition/StandaloneEditionManagementStateImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/edition/StandaloneEditionManagementStateImplTest.java index ea16c7da7e1..f999efc09e4 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/edition/StandaloneEditionManagementStateImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/edition/StandaloneEditionManagementStateImplTest.java @@ -1070,7 +1070,7 @@ public class StandaloneEditionManagementStateImplTest { } @Test - public void installFailed_after_installFailed() { + public void installFailed_fails_with_ISE_after_installFailed() { underTest.start(); underTest.startAutomaticInstall(LICENSE_WITHOUT_PLUGINS); underTest.installFailed(nullableErrorMessage); |