You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SQDatabase.java 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2019 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  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.
  10. *
  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.
  15. *
  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.
  19. */
  20. package org.sonar.db;
  21. import java.io.PrintWriter;
  22. import java.sql.Connection;
  23. import java.sql.SQLException;
  24. import java.util.List;
  25. import javax.sql.DataSource;
  26. import org.apache.commons.dbutils.DbUtils;
  27. import org.apache.commons.io.output.NullWriter;
  28. import org.apache.ibatis.io.Resources;
  29. import org.apache.ibatis.jdbc.ScriptRunner;
  30. import org.sonar.api.SonarEdition;
  31. import org.sonar.api.SonarQubeSide;
  32. import org.sonar.api.config.Settings;
  33. import org.sonar.api.config.internal.MapSettings;
  34. import org.sonar.api.internal.SonarRuntimeImpl;
  35. import org.sonar.api.utils.System2;
  36. import org.sonar.api.utils.Version;
  37. import org.sonar.api.utils.log.Loggers;
  38. import org.sonar.core.platform.ComponentContainer;
  39. import org.sonar.core.util.UuidFactoryFast;
  40. import org.sonar.core.util.logs.Profiler;
  41. import org.sonar.db.dialect.Dialect;
  42. import org.sonar.process.logging.LogbackHelper;
  43. import org.sonar.server.platform.db.migration.MigrationConfigurationModule;
  44. import org.sonar.server.platform.db.migration.engine.MigrationContainer;
  45. import org.sonar.server.platform.db.migration.engine.MigrationContainerImpl;
  46. import org.sonar.server.platform.db.migration.engine.MigrationContainerPopulator;
  47. import org.sonar.server.platform.db.migration.engine.MigrationContainerPopulatorImpl;
  48. import org.sonar.server.platform.db.migration.history.MigrationHistoryTableImpl;
  49. import org.sonar.server.platform.db.migration.step.MigrationStep;
  50. import org.sonar.server.platform.db.migration.step.MigrationStepExecutionException;
  51. import org.sonar.server.platform.db.migration.step.MigrationSteps;
  52. import org.sonar.server.platform.db.migration.step.MigrationStepsExecutor;
  53. import org.sonar.server.platform.db.migration.step.RegisteredMigrationStep;
  54. import org.sonar.server.platform.db.migration.version.DbVersion;
  55. import static com.google.common.base.Preconditions.checkState;
  56. public class SQDatabase extends DefaultDatabase {
  57. private final boolean createSchema;
  58. private SQDatabase(Settings settings, boolean createSchema) {
  59. super(new LogbackHelper(), settings);
  60. this.createSchema = createSchema;
  61. }
  62. public static SQDatabase newDatabase(Settings settings, boolean createSchema) {
  63. return new SQDatabase(settings, createSchema);
  64. }
  65. public static SQDatabase newH2Database(String name, boolean createSchema) {
  66. MapSettings settings = new MapSettings()
  67. .setProperty("sonar.jdbc.dialect", "h2")
  68. .setProperty("sonar.jdbc.driverClassName", "org.h2.Driver")
  69. .setProperty("sonar.jdbc.url", "jdbc:h2:mem:" + name)
  70. .setProperty("sonar.jdbc.username", "sonar")
  71. .setProperty("sonar.jdbc.password", "sonar");
  72. return new SQDatabase(settings, createSchema);
  73. }
  74. @Override
  75. public void start() {
  76. super.start();
  77. if (createSchema) {
  78. createSchema();
  79. }
  80. }
  81. private void createSchema() {
  82. Connection connection = null;
  83. try {
  84. connection = getDataSource().getConnection();
  85. NoopDatabase noopDatabase = new NoopDatabase(getDialect(), getDataSource());
  86. // create and populate schema
  87. createMigrationHistoryTable(noopDatabase);
  88. executeDbMigrations(noopDatabase);
  89. } catch (SQLException e) {
  90. throw new IllegalStateException("Fail to create schema", e);
  91. } finally {
  92. DbUtils.closeQuietly(connection);
  93. }
  94. }
  95. public static final class H2MigrationContainerPopulator extends MigrationContainerPopulatorImpl {
  96. public H2MigrationContainerPopulator(DbVersion... dbVersions) {
  97. super(H2StepExecutor.class, dbVersions);
  98. }
  99. }
  100. public static final class H2StepExecutor implements MigrationStepsExecutor {
  101. private static final String STEP_START_PATTERN = "{}...";
  102. private static final String STEP_STOP_PATTERN = "{}: {}";
  103. private final ComponentContainer componentContainer;
  104. public H2StepExecutor(ComponentContainer componentContainer) {
  105. this.componentContainer = componentContainer;
  106. }
  107. @Override
  108. public void execute(List<RegisteredMigrationStep> steps) {
  109. steps.forEach(step -> execute(step, componentContainer));
  110. }
  111. private void execute(RegisteredMigrationStep step, ComponentContainer componentContainer) {
  112. MigrationStep migrationStep = componentContainer.getComponentByType(step.getStepClass());
  113. checkState(migrationStep != null, "Can not find instance of " + step.getStepClass());
  114. execute(step, migrationStep);
  115. }
  116. private void execute(RegisteredMigrationStep step, MigrationStep migrationStep) {
  117. Profiler stepProfiler = Profiler.create(Loggers.get(SQDatabase.class));
  118. stepProfiler.startInfo(STEP_START_PATTERN, step);
  119. boolean done = false;
  120. try {
  121. migrationStep.execute();
  122. done = true;
  123. } catch (Exception e) {
  124. throw new MigrationStepExecutionException(step, e);
  125. } finally {
  126. if (done) {
  127. stepProfiler.stopInfo(STEP_STOP_PATTERN, step, "success");
  128. } else {
  129. stepProfiler.stopError(STEP_STOP_PATTERN, step, "failure");
  130. }
  131. }
  132. }
  133. }
  134. private void executeDbMigrations(NoopDatabase noopDatabase) {
  135. ComponentContainer parentContainer = new ComponentContainer();
  136. parentContainer.add(noopDatabase);
  137. parentContainer.add(H2MigrationContainerPopulator.class);
  138. MigrationConfigurationModule migrationConfigurationModule = new MigrationConfigurationModule();
  139. migrationConfigurationModule.configure(parentContainer);
  140. // dependencies required by DB migrations
  141. parentContainer.add(SonarRuntimeImpl.forSonarQube(Version.create(8, 0), SonarQubeSide.SERVER, SonarEdition.COMMUNITY));
  142. parentContainer.add(UuidFactoryFast.getInstance());
  143. parentContainer.add(System2.INSTANCE);
  144. parentContainer.startComponents();
  145. MigrationContainer migrationContainer = new MigrationContainerImpl(parentContainer, parentContainer.getComponentByType(MigrationContainerPopulator.class));
  146. MigrationSteps migrationSteps = migrationContainer.getComponentByType(MigrationSteps.class);
  147. migrationContainer.getComponentByType(MigrationStepsExecutor.class)
  148. .execute(migrationSteps.readAll());
  149. }
  150. private void createMigrationHistoryTable(NoopDatabase noopDatabase) {
  151. new MigrationHistoryTableImpl(noopDatabase).start();
  152. }
  153. private class NoopDatabase implements Database {
  154. private final Dialect dialect;
  155. private final DataSource dataSource;
  156. private NoopDatabase(Dialect dialect, DataSource dataSource) {
  157. this.dialect = dialect;
  158. this.dataSource = dataSource;
  159. }
  160. @Override
  161. public DataSource getDataSource() {
  162. return dataSource;
  163. }
  164. @Override
  165. public Dialect getDialect() {
  166. return dialect;
  167. }
  168. @Override
  169. public void enableSqlLogging(boolean enable) {
  170. }
  171. @Override
  172. public void start() {
  173. // do nothing
  174. }
  175. @Override
  176. public void stop() {
  177. // do nothing
  178. }
  179. }
  180. public void executeScript(String classloaderPath) {
  181. try (Connection connection = getDataSource().getConnection()) {
  182. executeScript(connection, classloaderPath);
  183. } catch (SQLException e) {
  184. throw new IllegalStateException("Fail to execute script: " + classloaderPath, e);
  185. }
  186. }
  187. private static void executeScript(Connection connection, String path) {
  188. ScriptRunner scriptRunner = newScriptRunner(connection);
  189. try {
  190. scriptRunner.runScript(Resources.getResourceAsReader(path));
  191. connection.commit();
  192. } catch (Exception e) {
  193. throw new IllegalStateException("Fail to restore: " + path, e);
  194. }
  195. }
  196. private static ScriptRunner newScriptRunner(Connection connection) {
  197. ScriptRunner scriptRunner = new ScriptRunner(connection);
  198. scriptRunner.setDelimiter(";");
  199. scriptRunner.setStopOnError(true);
  200. scriptRunner.setLogWriter(new PrintWriter(new NullWriter()));
  201. return scriptRunner;
  202. }
  203. }