001/*- 002 * #%L 003 * HAPI FHIR Server - SQL Migration 004 * %% 005 * Copyright (C) 2014 - 2025 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}