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.context.ConfigurationException; 024import ca.uhn.fhir.jpa.migrate.taskdef.BaseTask; 025import org.flywaydb.core.api.MigrationInfo; 026import org.flywaydb.core.api.MigrationInfoService; 027import org.flywaydb.core.api.callback.Callback; 028import org.hibernate.cfg.AvailableSettings; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031import org.springframework.util.Assert; 032 033import javax.sql.DataSource; 034import java.sql.Connection; 035import java.sql.SQLException; 036import java.util.Collections; 037import java.util.List; 038import java.util.Optional; 039import java.util.Properties; 040 041public class SchemaMigrator { 042 public static final String HAPI_FHIR_MIGRATION_TABLENAME = "FLY_HFJ_MIGRATION"; 043 private static final Logger ourLog = LoggerFactory.getLogger(SchemaMigrator.class); 044 private final String mySchemaName; 045 private final DataSource myDataSource; 046 private final boolean mySkipValidation; 047 private final String myMigrationTableName; 048 private final List<BaseTask> myMigrationTasks; 049 private boolean myDontUseFlyway; 050 private boolean myStrictOrder; 051 private DriverTypeEnum myDriverType; 052 private List<Callback> myCallbacks = Collections.emptyList(); 053 054 /** 055 * Constructor 056 */ 057 public SchemaMigrator(String theSchemaName, String theMigrationTableName, DataSource theDataSource, Properties jpaProperties, List<BaseTask> theMigrationTasks) { 058 mySchemaName = theSchemaName; 059 myDataSource = theDataSource; 060 myMigrationTableName = theMigrationTableName; 061 myMigrationTasks = theMigrationTasks; 062 063 mySkipValidation = jpaProperties.containsKey(AvailableSettings.HBM2DDL_AUTO) && "update".equals(jpaProperties.getProperty(AvailableSettings.HBM2DDL_AUTO)); 064 } 065 066 public void setCallbacks(List<Callback> theCallbacks) { 067 Assert.notNull(theCallbacks); 068 myCallbacks = theCallbacks; 069 } 070 071 public void setDontUseFlyway(boolean theDontUseFlyway) { 072 myDontUseFlyway = theDontUseFlyway; 073 } 074 075 public void setStrictOrder(boolean theStrictOrder) { 076 myStrictOrder = theStrictOrder; 077 } 078 079 public void validate() { 080 if (mySkipValidation) { 081 ourLog.warn("Database running in hibernate auto-update mode. Skipping schema validation."); 082 return; 083 } 084 try (Connection connection = myDataSource.getConnection()) { 085 Optional<MigrationInfoService> migrationInfo = newMigrator().getMigrationInfo(); 086 if (migrationInfo.isPresent()) { 087 if (migrationInfo.get().pending().length > 0) { 088 089 String url = connection.getMetaData().getURL(); 090 throw new ConfigurationException("The database schema for " + url + " is out of date. " + 091 "Current database schema version is " + getCurrentVersion(migrationInfo.get()) + ". Schema version required by application is " + 092 getLastVersion(migrationInfo.get()) + ". Please run the database migrator."); 093 } 094 ourLog.info("Database schema confirmed at expected version " + getCurrentVersion(migrationInfo.get())); 095 } 096 } catch (SQLException e) { 097 throw new ConfigurationException("Unable to connect to " + myDataSource, e); 098 } 099 } 100 101 public void migrate() { 102 if (mySkipValidation) { 103 ourLog.warn("Database running in hibernate auto-update mode. Skipping schema migration."); 104 return; 105 } 106 try { 107 ourLog.info("Migrating " + mySchemaName); 108 newMigrator().migrate(); 109 ourLog.info(mySchemaName + " migrated successfully."); 110 } catch (Exception e) { 111 ourLog.error("Failed to migrate " + mySchemaName, e); 112 throw e; 113 } 114 } 115 116 private BaseMigrator newMigrator() { 117 BaseMigrator migrator; 118 if (myDontUseFlyway) { 119 migrator = new TaskOnlyMigrator(); 120 migrator.setDriverType(myDriverType); 121 migrator.setDataSource(myDataSource); 122 } else { 123 migrator = new FlywayMigrator(myMigrationTableName, myDataSource, myDriverType); 124 migrator.setStrictOrder(myStrictOrder); 125 } 126 migrator.addTasks(myMigrationTasks); 127 migrator.setCallbacks(myCallbacks); 128 return migrator; 129 } 130 131 private String getCurrentVersion(MigrationInfoService theMigrationInfo) { 132 MigrationInfo migrationInfo = theMigrationInfo.current(); 133 if (migrationInfo == null) { 134 return "unknown"; 135 } 136 return migrationInfo.getVersion().toString(); 137 } 138 139 private String getLastVersion(MigrationInfoService theMigrationInfo) { 140 MigrationInfo[] pending = theMigrationInfo.pending(); 141 if (pending.length > 0) { 142 return pending[pending.length - 1].getVersion().toString(); 143 } 144 return "unknown"; 145 } 146 147 public void setDriverType(DriverTypeEnum theDriverType) { 148 myDriverType = theDriverType; 149 } 150}