001/*-
002 * #%L
003 * HAPI FHIR Server - SQL Migration
004 * %%
005 * Copyright (C) 2014 - 2023 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.jpa.migrate;
021
022import ca.uhn.fhir.context.ConfigurationException;
023import ca.uhn.fhir.i18n.Msg;
024import org.hibernate.cfg.AvailableSettings;
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027
028import javax.sql.DataSource;
029import java.sql.Connection;
030import java.sql.SQLException;
031import java.util.Collections;
032import java.util.List;
033import java.util.Properties;
034
035public class SchemaMigrator {
036        public static final String HAPI_FHIR_MIGRATION_TABLENAME = "FLY_HFJ_MIGRATION";
037        private static final Logger ourLog = LoggerFactory.getLogger(SchemaMigrator.class);
038        private final String mySchemaName;
039        private final DataSource myDataSource;
040        private final boolean mySkipValidation;
041        private final String myMigrationTableName;
042        private final MigrationTaskList myMigrationTasks;
043        private DriverTypeEnum myDriverType;
044        private List<IHapiMigrationCallback> myCallbacks = Collections.emptyList();
045        private final HapiMigrationStorageSvc myHapiMigrationStorageSvc;
046
047        /**
048         * Constructor
049         */
050        public SchemaMigrator(String theSchemaName, String theMigrationTableName, DataSource theDataSource, Properties jpaProperties, MigrationTaskList theMigrationTasks, HapiMigrationStorageSvc theHapiMigrationStorageSvc) {
051                mySchemaName = theSchemaName;
052                myDataSource = theDataSource;
053                myMigrationTableName = theMigrationTableName;
054                myMigrationTasks = theMigrationTasks;
055
056                mySkipValidation = jpaProperties.containsKey(AvailableSettings.HBM2DDL_AUTO) && "update".equals(jpaProperties.getProperty(AvailableSettings.HBM2DDL_AUTO));
057                myHapiMigrationStorageSvc = theHapiMigrationStorageSvc;
058        }
059
060        public void validate() {
061                if (mySkipValidation) {
062                        ourLog.warn("Database running in hibernate auto-update mode.  Skipping schema validation.");
063                        return;
064                }
065                try (Connection connection = myDataSource.getConnection()) {
066                        MigrationTaskList unappliedMigrations = myHapiMigrationStorageSvc.diff(myMigrationTasks);
067
068                        if (unappliedMigrations.size() > 0) {
069
070                                String url = connection.getMetaData().getURL();
071                                throw new ConfigurationException(Msg.code(27) + "The database schema for " + url + " is out of date.  " +
072                                        "Current database schema version is " + myHapiMigrationStorageSvc.getLatestAppliedVersion() + ".  Schema version required by application is " +
073                                        unappliedMigrations.getLastVersion() + ".  Please run the database migrator.");
074                        }
075                        ourLog.info("Database schema confirmed at expected version " + myHapiMigrationStorageSvc.getLatestAppliedVersion());
076                } catch (SQLException e) {
077                        throw new ConfigurationException(Msg.code(28) + "Unable to connect to " + myDataSource, e);
078                }
079
080        }
081
082        public MigrationResult migrate() {
083                if (mySkipValidation) {
084                        ourLog.warn("Database running in hibernate auto-update mode.  Skipping schema migration.");
085                        return null;
086                }
087                try {
088                        ourLog.info("Migrating " + mySchemaName);
089                        MigrationResult retval = newMigrator().migrate();
090                        ourLog.info(mySchemaName + " migrated successfully: {}", retval.summary());
091                        return retval;
092                } catch (Exception e) {
093                        ourLog.error("Failed to migrate " + mySchemaName, e);
094                        throw e;
095                }
096        }
097
098        private HapiMigrator newMigrator() {
099                HapiMigrator migrator;
100                migrator = new HapiMigrator(myMigrationTableName, myDataSource, myDriverType);
101                migrator.addTasks(myMigrationTasks);
102                migrator.setCallbacks(myCallbacks);
103                return migrator;
104        }
105
106        public void setDriverType(DriverTypeEnum theDriverType) {
107                myDriverType = theDriverType;
108        }
109
110        public void setCallbacks(List<IHapiMigrationCallback> theCallbacks) {
111                myCallbacks = theCallbacks;
112        }
113
114        public void createMigrationTableIfRequired() {
115                myHapiMigrationStorageSvc.createMigrationTableIfRequired();
116        }
117}