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}