3 * Copyright (C) 2009-2017 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.server.edition;
22 import com.google.common.collect.ImmutableSet;
23 import java.util.Arrays;
25 import java.util.Optional;
26 import javax.annotation.CheckForNull;
27 import javax.annotation.Nullable;
28 import javax.annotation.concurrent.Immutable;
29 import org.picocontainer.Startable;
30 import org.sonar.db.DbClient;
31 import org.sonar.db.DbSession;
32 import org.sonar.db.property.InternalPropertiesDao;
34 import static com.google.common.base.Preconditions.checkArgument;
35 import static com.google.common.base.Preconditions.checkState;
36 import static java.util.Objects.requireNonNull;
37 import static java.util.Optional.empty;
38 import static org.sonar.server.edition.EditionManagementState.PendingStatus.AUTOMATIC_IN_PROGRESS;
39 import static org.sonar.server.edition.EditionManagementState.PendingStatus.AUTOMATIC_READY;
40 import static org.sonar.server.edition.EditionManagementState.PendingStatus.MANUAL_IN_PROGRESS;
41 import static org.sonar.server.edition.EditionManagementState.PendingStatus.NONE;
42 import static org.sonar.server.edition.EditionManagementState.PendingStatus.UNINSTALL_IN_PROGRESS;
44 public class StandaloneEditionManagementStateImpl implements MutableEditionManagementState, Startable {
45 private static final String CURRENT_EDITION_KEY = "currentEditionKey";
46 private static final String PENDING_INSTALLATION_STATUS = "pendingInstallStatus";
47 private static final String PENDING_EDITION_KEY = "pendingEditionKey";
48 private static final String PENDING_LICENSE = "pendingLicense";
49 private static final String INSTALL_ERROR_MESSAGE = "installError";
51 private final DbClient dbClient;
55 public StandaloneEditionManagementStateImpl(DbClient dbClient) {
56 this.dbClient = dbClient;
61 try (DbSession dbSession = dbClient.openSession(false)) {
62 // load current state value
63 Map<String, Optional<String>> internalPropertyValues = dbClient.internalPropertiesDao().selectByKeys(dbSession,
64 ImmutableSet.of(CURRENT_EDITION_KEY, PENDING_INSTALLATION_STATUS, PENDING_EDITION_KEY, PENDING_LICENSE, INSTALL_ERROR_MESSAGE));
66 PendingStatus pendingInstallationStatus = internalPropertyValues.getOrDefault(PENDING_INSTALLATION_STATUS, empty())
67 .map(StandaloneEditionManagementStateImpl::emptyToNull)
68 .map(PendingStatus::valueOf)
70 State.Builder builder = State.newBuilder(pendingInstallationStatus);
72 .setCurrentEditionKey(internalPropertyValues.getOrDefault(CURRENT_EDITION_KEY, empty())
73 .map(StandaloneEditionManagementStateImpl::emptyToNull)
75 .setPendingEditionKey(internalPropertyValues.getOrDefault(PENDING_EDITION_KEY, empty())
76 .map(StandaloneEditionManagementStateImpl::emptyToNull)
78 .setPendingLicense(internalPropertyValues.getOrDefault(PENDING_LICENSE, empty())
79 .map(StandaloneEditionManagementStateImpl::emptyToNull)
81 .setInstallErrorMessage(internalPropertyValues.getOrDefault(INSTALL_ERROR_MESSAGE, empty())
82 .map(StandaloneEditionManagementStateImpl::emptyToNull)
84 state = builder.build();
94 public Optional<String> getCurrentEditionKey() {
96 return Optional.ofNullable(state.getCurrentEditionKey());
100 public PendingStatus getPendingInstallationStatus() {
102 return state.getPendingInstallationStatus();
106 public Optional<String> getPendingEditionKey() {
108 return Optional.ofNullable(state.getPendingEditionKey());
112 public Optional<String> getPendingLicense() {
114 return Optional.ofNullable(state.getPendingLicense());
118 public Optional<String> getInstallErrorMessage() {
120 return Optional.ofNullable(state.getInstallErrorMessage());
124 public synchronized PendingStatus startAutomaticInstall(License license) {
126 checkLicense(license);
127 State newState = changeStatusToFrom(AUTOMATIC_IN_PROGRESS, NONE)
128 .setPendingLicense(license.getContent())
129 .setPendingEditionKey(license.getEditionKey())
130 .clearAutomaticInstallErrorMessage()
132 persistProperties(newState);
133 return newState.getPendingInstallationStatus();
137 public synchronized PendingStatus startManualInstall(License license) {
139 checkLicense(license);
140 State newState = changeStatusToFrom(MANUAL_IN_PROGRESS, NONE)
141 .setPendingLicense(license.getContent())
142 .setPendingEditionKey(license.getEditionKey())
143 .clearAutomaticInstallErrorMessage()
145 persistProperties(newState);
146 return newState.getPendingInstallationStatus();
150 public synchronized PendingStatus newEditionWithoutInstall(String newEditionKey) {
152 requireNonNull(newEditionKey, "newEditionKey can't be null");
153 checkArgument(!newEditionKey.isEmpty(), "newEditionKey can't be empty");
154 State newState = changeStatusToFrom(NONE, NONE)
155 .setCurrentEditionKey(newEditionKey)
156 .clearAutomaticInstallErrorMessage()
158 persistProperties(newState);
159 return newState.getPendingInstallationStatus();
163 public synchronized PendingStatus automaticInstallReady() {
165 State newState = changeStatusToFrom(AUTOMATIC_READY, AUTOMATIC_IN_PROGRESS)
166 .clearAutomaticInstallErrorMessage()
168 persistProperties(newState);
169 return newState.getPendingInstallationStatus();
173 public synchronized PendingStatus installFailed(@Nullable String errorMessage) {
175 State newState = changeStatusToFrom(NONE, AUTOMATIC_IN_PROGRESS, AUTOMATIC_READY, MANUAL_IN_PROGRESS)
176 .setInstallErrorMessage(nullableTrimmedEmptyToNull(errorMessage))
177 .clearPendingFields()
179 persistProperties(newState);
180 return newState.getPendingInstallationStatus();
184 public synchronized void clearInstallErrorMessage() {
186 State currentState = this.state;
187 if (currentState.getInstallErrorMessage() != null) {
188 State newState = State.newBuilder(currentState)
189 .clearAutomaticInstallErrorMessage()
191 persistProperties(newState);
196 public synchronized PendingStatus finalizeInstallation() {
198 State newState = changeStatusToFrom(NONE, AUTOMATIC_READY, MANUAL_IN_PROGRESS, UNINSTALL_IN_PROGRESS)
199 .commitPendingEditionKey()
200 .clearPendingFields()
202 persistProperties(newState);
203 return newState.getPendingInstallationStatus();
207 public synchronized PendingStatus uninstall() {
209 State.Builder builder = changeStatusToFrom(UNINSTALL_IN_PROGRESS, NONE);
210 checkState(state.currentEditionKey != null, "There is no edition currently installed");
211 State newState = builder
212 .clearPendingFields()
213 .clearCurrentEditionKey()
214 .clearAutomaticInstallErrorMessage()
216 persistProperties(newState);
217 return newState.getPendingInstallationStatus();
220 private void ensureStarted() {
221 checkState(state != null, "%s is not started", getClass().getSimpleName());
224 private State.Builder changeStatusToFrom(PendingStatus newStatus, PendingStatus... validPendingStatuses) {
225 State currentState = this.state;
226 checkState(Arrays.stream(validPendingStatuses).anyMatch(s -> s == currentState.getPendingInstallationStatus()),
227 "Can't move to %s when status is %s (should be any of %s)",
228 newStatus, currentState.getPendingInstallationStatus(), Arrays.toString(validPendingStatuses));
229 return State.newBuilder(currentState, newStatus);
232 private void persistProperties(State newState) {
233 try (DbSession dbSession = dbClient.openSession(false)) {
234 InternalPropertiesDao internalPropertiesDao = dbClient.internalPropertiesDao();
235 saveInternalProperty(internalPropertiesDao, dbSession, PENDING_EDITION_KEY, newState.getPendingEditionKey());
236 saveInternalProperty(internalPropertiesDao, dbSession, PENDING_LICENSE, newState.getPendingLicense());
237 saveInternalProperty(internalPropertiesDao, dbSession, INSTALL_ERROR_MESSAGE, newState.getInstallErrorMessage());
238 saveInternalProperty(internalPropertiesDao, dbSession, CURRENT_EDITION_KEY, newState.getCurrentEditionKey());
239 saveInternalProperty(internalPropertiesDao, dbSession, PENDING_INSTALLATION_STATUS, newState.getPendingInstallationStatus().name());
241 this.state = newState;
245 private static void saveInternalProperty(InternalPropertiesDao dao, DbSession dbSession, String key, @Nullable String value) {
247 dao.saveAsEmpty(dbSession, key);
249 dao.save(dbSession, key, value);
253 private static void checkLicense(License license) {
254 requireNonNull(license, "license can't be null");
257 private static String nullableTrimmedEmptyToNull(@Nullable String s) {
262 return v.isEmpty() ? null : v;
265 private static String emptyToNull(String s) {
266 return s.isEmpty() ? null : s;
270 private static final class State {
271 private final String currentEditionKey;
272 private final PendingStatus pendingInstallationStatus;
273 private final String pendingEditionKey;
274 private final String pendingLicense;
275 private final String installErrorMessage;
277 public State(Builder builder) {
278 this.currentEditionKey = builder.currentEditionKey;
279 this.pendingInstallationStatus = builder.pendingInstallationStatus;
280 this.pendingEditionKey = builder.pendingEditionKey;
281 this.pendingLicense = builder.pendingLicense;
282 this.installErrorMessage = builder.installErrorMessage;
285 public String getCurrentEditionKey() {
286 return currentEditionKey;
289 public PendingStatus getPendingInstallationStatus() {
290 return pendingInstallationStatus;
293 public String getPendingEditionKey() {
294 return pendingEditionKey;
297 public String getPendingLicense() {
298 return pendingLicense;
301 public String getInstallErrorMessage() {
302 return installErrorMessage;
305 public static Builder newBuilder(PendingStatus pendingInstallationStatus) {
306 return new Builder(pendingInstallationStatus);
309 public static Builder newBuilder(State from) {
310 return newBuilder(from, from.getPendingInstallationStatus());
313 public static Builder newBuilder(State from, PendingStatus newStatus) {
314 return new Builder(newStatus)
315 .setCurrentEditionKey(from.currentEditionKey)
316 .setPendingEditionKey(from.pendingEditionKey)
317 .setPendingLicense(from.pendingLicense)
318 .setInstallErrorMessage(from.installErrorMessage);
321 private static class Builder {
322 private PendingStatus pendingInstallationStatus;
323 private String currentEditionKey;
324 private String pendingEditionKey;
325 private String pendingLicense;
326 private String installErrorMessage;
328 private Builder(PendingStatus pendingInstallationStatus) {
329 this.pendingInstallationStatus = requireNonNull(pendingInstallationStatus);
332 public Builder setCurrentEditionKey(@Nullable String currentEditionKey) {
333 this.currentEditionKey = currentEditionKey;
337 public Builder setPendingEditionKey(@Nullable String pendingEditionKey) {
338 this.pendingEditionKey = pendingEditionKey;
342 public Builder setPendingLicense(@Nullable String pendingLicense) {
343 this.pendingLicense = pendingLicense;
347 public Builder setInstallErrorMessage(@Nullable String installErrorMessage) {
348 this.installErrorMessage = installErrorMessage;
352 public Builder commitPendingEditionKey() {
353 this.currentEditionKey = pendingEditionKey;
357 public Builder clearCurrentEditionKey() {
358 this.currentEditionKey = null;
362 public Builder clearPendingFields() {
363 this.pendingEditionKey = null;
364 this.pendingLicense = null;
368 public Builder clearAutomaticInstallErrorMessage() {
369 this.installErrorMessage = null;
373 public State build() {
374 return new State(this);