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.i18n.Msg;
024import ca.uhn.fhir.jpa.migrate.taskdef.BaseTask;
025import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028
029import java.sql.SQLException;
030import java.util.ArrayList;
031import java.util.List;
032import java.util.Objects;
033
034import static org.apache.commons.lang3.StringUtils.isBlank;
035
036public class Migrator {
037
038        private static final Logger ourLog = LoggerFactory.getLogger(Migrator.class);
039        private DriverTypeEnum myDriverType;
040        private String myConnectionUrl;
041        private String myUsername;
042        private String myPassword;
043        private List<BaseTask> myTasks = new ArrayList<>();
044        private DriverTypeEnum.ConnectionProperties myConnectionProperties;
045        private int myChangesCount;
046        private boolean myDryRun;
047        private List<BaseTask.ExecutedStatement> myExecutedStatements = new ArrayList<>();
048        private boolean myNoColumnShrink;
049
050        public int getChangesCount() {
051                return myChangesCount;
052        }
053
054        public void setDriverType(DriverTypeEnum theDriverType) {
055                myDriverType = theDriverType;
056        }
057
058        public void setConnectionUrl(String theConnectionUrl) {
059                myConnectionUrl = theConnectionUrl;
060        }
061
062        public void setUsername(String theUsername) {
063                myUsername = theUsername;
064        }
065
066        public void setPassword(String thePassword) {
067                myPassword = thePassword;
068        }
069
070        public void addTask(BaseTask theTask) {
071                myTasks.add(theTask);
072        }
073
074        public void setDryRun(boolean theDryRun) {
075                myDryRun = theDryRun;
076        }
077
078        public void migrate() {
079                ourLog.info("Starting migration with {} tasks", myTasks.size());
080
081                myConnectionProperties = myDriverType.newConnectionProperties(myConnectionUrl, myUsername, myPassword);
082                try {
083                        for (BaseTask next : myTasks) {
084                                next.setDriverType(myDriverType);
085                                next.setConnectionProperties(myConnectionProperties);
086                                next.setDryRun(myDryRun);
087                                next.setNoColumnShrink(myNoColumnShrink);
088                                try {
089                                        next.execute();
090                                } catch (SQLException e) {
091                                        String description = next.getDescription();
092                                        if (isBlank(description)) {
093                                                description = next.getClass().getSimpleName();
094                                        }
095                                        String prefix = "Failure executing task \"" + description + "\", aborting! Cause: ";
096                                        throw new InternalErrorException(Msg.code(44) + prefix + e.toString(), e);
097                                }
098
099                                myChangesCount += next.getChangesCount();
100                                myExecutedStatements.addAll(next.getExecutedStatements());
101                        }
102                } finally {
103                        myConnectionProperties.close();
104                }
105
106                ourLog.info("Finished migration of {} tasks", myTasks.size());
107
108                if (myDryRun) {
109                        StringBuilder statementBuilder = new StringBuilder();
110                        String lastTable = null;
111                        for (BaseTask.ExecutedStatement next : myExecutedStatements) {
112                                if (!Objects.equals(lastTable, next.getTableName())) {
113                                        statementBuilder.append("\n\n-- Table: ").append(next.getTableName()).append("\n");
114                                        lastTable = next.getTableName();
115                                }
116
117                                statementBuilder.append(next.getSql()).append(";\n");
118
119                                for (Object nextArg : next.getArguments()) {
120                                        statementBuilder.append("  -- Arg: ").append(nextArg).append("\n");
121                                }
122                        }
123
124                        ourLog.info("SQL that would be executed:\n\n***********************************\n{}***********************************", statementBuilder);
125                }
126
127        }
128
129        public void addTasks(List<BaseTask> theTasks) {
130                theTasks.forEach(this::addTask);
131        }
132
133        public void setNoColumnShrink(boolean theNoColumnShrink) {
134                myNoColumnShrink = theNoColumnShrink;
135        }
136
137
138}