001package ca.uhn.fhir.jpa.migrate; 002 003/*- 004 * #%L 005 * HAPI FHIR Server - SQL Migration 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.jpa.migrate.taskdef.BaseTask; 024import ca.uhn.fhir.jpa.migrate.taskdef.InitializeSchemaTask; 025import com.google.common.annotations.VisibleForTesting; 026import org.flywaydb.core.Flyway; 027import org.flywaydb.core.api.MigrationInfoService; 028import org.flywaydb.core.api.callback.Callback; 029import org.flywaydb.core.api.migration.JavaMigration; 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032 033import javax.sql.DataSource; 034import java.util.ArrayList; 035import java.util.List; 036import java.util.Optional; 037 038public class FlywayMigrator extends BaseMigrator { 039 040 private static final Logger ourLog = LoggerFactory.getLogger(FlywayMigrator.class); 041 042 private final String myMigrationTableName; 043 private final List<FlywayMigrationTask> myTasks = new ArrayList<>(); 044 045 public FlywayMigrator(String theMigrationTableName, DataSource theDataSource, DriverTypeEnum theDriverType) { 046 this(theMigrationTableName); 047 setDataSource(theDataSource); 048 setDriverType(theDriverType); 049 } 050 051 public FlywayMigrator(String theMigrationTableName) { 052 myMigrationTableName = theMigrationTableName; 053 } 054 055 public void addTask(BaseTask theTask) { 056 myTasks.add(new FlywayMigrationTask(theTask, this)); 057 } 058 059 @Override 060 public void migrate() { 061 try (DriverTypeEnum.ConnectionProperties connectionProperties = getDriverType().newConnectionProperties(getDataSource())) { 062 Flyway flyway = initFlyway(connectionProperties); 063 flyway.repair(); 064 flyway.migrate(); 065 if (isDryRun()) { 066 StringBuilder statementBuilder = buildExecutedStatementsString(); 067 ourLog.info("SQL that would be executed:\n\n***********************************\n{}***********************************", statementBuilder); 068 } 069 } catch (Exception e) { 070 throw e; 071 } 072 } 073 074 private Flyway initFlyway(DriverTypeEnum.ConnectionProperties theConnectionProperties) { 075 Flyway flyway = Flyway.configure() 076 .table(myMigrationTableName) 077 .dataSource(theConnectionProperties.getDataSource()) 078 .baselineOnMigrate(true) 079 // By default, migrations are allowed to be run out of order. You can enforce strict order by setting strictOrder=true. 080 .outOfOrder(!isStrictOrder()) 081 .javaMigrations(myTasks.toArray(new JavaMigration[0])) 082 .callbacks(getCallbacks().toArray(new Callback[0])) 083 .load(); 084 for (FlywayMigrationTask task : myTasks) { 085 task.setConnectionProperties(theConnectionProperties); 086 } 087 return flyway; 088 } 089 090 @Override 091 public void addTasks(List<BaseTask> theTasks) { 092 if ("true".equals(System.getProperty("unit_test_mode"))) { 093 theTasks.stream().filter(task -> task instanceof InitializeSchemaTask).forEach(this::addTask); 094 } else { 095 theTasks.forEach(this::addTask); 096 } 097 } 098 099 @Override 100 public Optional<MigrationInfoService> getMigrationInfo() { 101 if (getDriverType() == null) { 102 return Optional.empty(); 103 } 104 try (DriverTypeEnum.ConnectionProperties connectionProperties = getDriverType().newConnectionProperties(getDataSource())) { 105 Flyway flyway = initFlyway(connectionProperties); 106 return Optional.of(flyway.info()); 107 } 108 } 109 110 @VisibleForTesting 111 public void removeAllTasksForUnitTest() { 112 myTasks.clear(); 113 } 114}