001/*-
002 * #%L
003 * HAPI FHIR Server - SQL Migration
004 * %%
005 * Copyright (C) 2014 - 2024 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 ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
025import jakarta.annotation.Nonnull;
026import org.apache.commons.dbcp2.BasicDataSource;
027import org.apache.commons.lang3.Validate;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030import org.springframework.beans.factory.DisposableBean;
031import org.springframework.jdbc.core.JdbcTemplate;
032import org.springframework.jdbc.datasource.DataSourceTransactionManager;
033import org.springframework.transaction.TransactionDefinition;
034import org.springframework.transaction.support.TransactionTemplate;
035
036import java.lang.reflect.InvocationTargetException;
037import java.sql.Connection;
038import java.sql.SQLException;
039import javax.sql.DataSource;
040
041public enum DriverTypeEnum {
042        H2_EMBEDDED("org.h2.Driver", false),
043        DERBY_EMBEDDED("org.apache.derby.jdbc.EmbeddedDriver", true),
044        MARIADB_10_1("org.mariadb.jdbc.Driver", false),
045
046        // Formerly com.mysql.jdbc.Driver
047        MYSQL_5_7("com.mysql.cj.jdbc.Driver", false),
048
049        POSTGRES_9_4("org.postgresql.Driver", false),
050
051        ORACLE_12C("oracle.jdbc.OracleDriver", false),
052
053        MSSQL_2012("com.microsoft.sqlserver.jdbc.SQLServerDriver", false),
054
055        COCKROACHDB_21_1("org.postgresql.Driver", false),
056        ;
057
058        private static final Logger ourLog = LoggerFactory.getLogger(DriverTypeEnum.class);
059        private String myDriverClassName;
060        private boolean myDerby;
061
062        /**
063         * Constructor
064         */
065        DriverTypeEnum(String theDriverClassName, boolean theDerby) {
066                myDriverClassName = theDriverClassName;
067                myDerby = theDerby;
068        }
069
070        public static DriverTypeEnum fromDriverClassName(String theDriverClassName) {
071                for (DriverTypeEnum driverTypeEnum : DriverTypeEnum.values()) {
072                        if (driverTypeEnum.myDriverClassName.equals(theDriverClassName)) {
073                                return driverTypeEnum;
074                        }
075                }
076                return null;
077        }
078
079        public String getDriverClassName() {
080                return myDriverClassName;
081        }
082
083        public String getSchemaFilename() {
084                String retval;
085                switch (this) {
086                        case H2_EMBEDDED:
087                                retval = "h2.sql";
088                                break;
089                        case DERBY_EMBEDDED:
090                                retval = "derby.sql";
091                                break;
092                        case MYSQL_5_7:
093                        case MARIADB_10_1:
094                                retval = "mysql.sql";
095                                break;
096                        case POSTGRES_9_4:
097                                retval = "postgres.sql";
098                                break;
099                        case ORACLE_12C:
100                                retval = "oracle.sql";
101                                break;
102                        case MSSQL_2012:
103                                retval = "sqlserver.sql";
104                                break;
105                        case COCKROACHDB_21_1:
106                                retval = "cockroachdb.sql";
107                                break;
108                        default:
109                                throw new ConfigurationException(
110                                                Msg.code(45) + "No schema initialization script available for driver " + this);
111                }
112                return retval;
113        }
114
115        public ConnectionProperties newConnectionProperties(String theUrl, String theUsername, String thePassword) {
116
117                BasicDataSource dataSource = new BasicDataSource() {
118                        @Override
119                        public Connection getConnection() throws SQLException {
120                                ourLog.debug("Creating new DB connection");
121                                return super.getConnection();
122                        }
123                };
124                dataSource.setDriverClassName(myDriverClassName);
125                dataSource.setUrl(theUrl);
126                dataSource.setUsername(theUsername);
127                dataSource.setPassword(thePassword);
128
129                // A check for WS-2020-0287
130                assert dataSource.getJmxName() == null;
131
132                return newConnectionProperties(dataSource);
133        }
134
135        @Nonnull
136        public ConnectionProperties newConnectionProperties(DataSource theDataSource) {
137                try {
138                        Class.forName(myDriverClassName).getConstructor().newInstance();
139                } catch (ClassNotFoundException
140                                | InstantiationException
141                                | IllegalAccessException
142                                | NoSuchMethodException
143                                | InvocationTargetException e) {
144                        throw new InternalErrorException(Msg.code(46) + "Unable to find driver class: " + myDriverClassName, e);
145                }
146
147                DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
148                transactionManager.setDataSource(theDataSource);
149                transactionManager.afterPropertiesSet();
150
151                TransactionTemplate txTemplate = new TransactionTemplate();
152                txTemplate.setTransactionManager(transactionManager);
153                txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
154                txTemplate.afterPropertiesSet();
155
156                return new ConnectionProperties(theDataSource, txTemplate, this);
157        }
158
159        public static class ConnectionProperties implements AutoCloseable {
160
161                private final DriverTypeEnum myDriverType;
162                private final DataSource myDataSource;
163                private final TransactionTemplate myTxTemplate;
164
165                /**
166                 * Constructor
167                 */
168                public ConnectionProperties(
169                                DataSource theDataSource, TransactionTemplate theTxTemplate, DriverTypeEnum theDriverType) {
170                        Validate.notNull(theDataSource);
171                        Validate.notNull(theTxTemplate);
172                        Validate.notNull(theDriverType);
173
174                        myDataSource = theDataSource;
175                        myTxTemplate = theTxTemplate;
176                        myDriverType = theDriverType;
177                }
178
179                public DriverTypeEnum getDriverType() {
180                        return myDriverType;
181                }
182
183                @Nonnull
184                public DataSource getDataSource() {
185                        return myDataSource;
186                }
187
188                @Nonnull
189                public JdbcTemplate newJdbcTemplate() {
190                        JdbcTemplate jdbcTemplate = new JdbcTemplate();
191                        jdbcTemplate.setDataSource(myDataSource);
192                        return jdbcTemplate;
193                }
194
195                @Nonnull
196                public TransactionTemplate getTxTemplate() {
197                        return myTxTemplate;
198                }
199
200                @Override
201                public void close() {
202                        if (myDataSource instanceof DisposableBean) {
203                                try {
204                                        ((DisposableBean) myDataSource).destroy();
205                                } catch (Exception e) {
206                                        ourLog.warn("Could not dispose of driver", e);
207                                }
208                        }
209                }
210        }
211}