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.i18n.Msg; 025import org.hibernate.cfg.AvailableSettings; 026import org.slf4j.Logger; 027import org.slf4j.LoggerFactory; 028 029import javax.sql.DataSource; 030import java.sql.Connection; 031import java.sql.SQLException; 032import java.util.Collections; 033import java.util.List; 034import java.util.Properties; 035 036public class SchemaMigrator { 037 public static final String HAPI_FHIR_MIGRATION_TABLENAME = "FLY_HFJ_MIGRATION"; 038 private static final Logger ourLog = LoggerFactory.getLogger(SchemaMigrator.class); 039 private final String mySchemaName; 040 private final DataSource myDataSource; 041 private final boolean mySkipValidation; 042 private final String myMigrationTableName; 043 private final MigrationTaskList myMigrationTasks; 044 private DriverTypeEnum myDriverType; 045 private List<IHapiMigrationCallback> myCallbacks = Collections.emptyList(); 046 private final HapiMigrationStorageSvc myHapiMigrationStorageSvc; 047 048 /** 049 * Constructor 050 */ 051 public SchemaMigrator(String theSchemaName, String theMigrationTableName, DataSource theDataSource, Properties jpaProperties, MigrationTaskList theMigrationTasks, HapiMigrationStorageSvc theHapiMigrationStorageSvc) { 052 mySchemaName = theSchemaName; 053 myDataSource = theDataSource; 054 myMigrationTableName = theMigrationTableName; 055 myMigrationTasks = theMigrationTasks; 056 057 mySkipValidation = jpaProperties.containsKey(AvailableSettings.HBM2DDL_AUTO) && "update".equals(jpaProperties.getProperty(AvailableSettings.HBM2DDL_AUTO)); 058 myHapiMigrationStorageSvc = theHapiMigrationStorageSvc; 059 } 060 061 public void validate() { 062 if (mySkipValidation) { 063 ourLog.warn("Database running in hibernate auto-update mode. Skipping schema validation."); 064 return; 065 } 066 try (Connection connection = myDataSource.getConnection()) { 067 MigrationTaskList unappliedMigrations = myHapiMigrationStorageSvc.diff(myMigrationTasks); 068 069 if (unappliedMigrations.size() > 0) { 070 071 String url = connection.getMetaData().getURL(); 072 throw new ConfigurationException(Msg.code(27) + "The database schema for " + url + " is out of date. " + 073 "Current database schema version is " + myHapiMigrationStorageSvc.getLatestAppliedVersion() + ". Schema version required by application is " + 074 unappliedMigrations.getLastVersion() + ". Please run the database migrator."); 075 } 076 ourLog.info("Database schema confirmed at expected version " + myHapiMigrationStorageSvc.getLatestAppliedVersion()); 077 } catch (SQLException e) { 078 throw new ConfigurationException(Msg.code(28) + "Unable to connect to " + myDataSource, e); 079 } 080 081 } 082 083 public MigrationResult migrate() { 084 if (mySkipValidation) { 085 ourLog.warn("Database running in hibernate auto-update mode. Skipping schema migration."); 086 return null; 087 } 088 try { 089 ourLog.info("Migrating " + mySchemaName); 090 MigrationResult retval = newMigrator().migrate(); 091 ourLog.info(mySchemaName + " migrated successfully: {}", retval.summary()); 092 return retval; 093 } catch (Exception e) { 094 ourLog.error("Failed to migrate " + mySchemaName, e); 095 throw e; 096 } 097 } 098 099 private HapiMigrator newMigrator() { 100 HapiMigrator migrator; 101 migrator = new HapiMigrator(myMigrationTableName, myDataSource, myDriverType); 102 migrator.addTasks(myMigrationTasks); 103 migrator.setCallbacks(myCallbacks); 104 return migrator; 105 } 106 107 public void setDriverType(DriverTypeEnum theDriverType) { 108 myDriverType = theDriverType; 109 } 110 111 public void setCallbacks(List<IHapiMigrationCallback> theCallbacks) { 112 myCallbacks = theCallbacks; 113 } 114 115 public void createMigrationTableIfRequired() { 116 myHapiMigrationStorageSvc.createMigrationTableIfRequired(); 117 } 118}