package databaseconnector.impl;

import com.mendix.core.Core;
import com.mendix.logging.ILogNode;

import com.zaxxer.hikari.HikariDataSource;
import databaseconnector.interfaces.ConnectionManager;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
 * Simple connection manager backed by HikariCP. It does not support shutdown at
 * this moment that may lead to memory leak in case of hot deployment.
 * <p>
 * TODO: REMOVE TECHNICAL DEBT - extract connection manager into shared
 * component, so that : 1. the same connection manager may be used by actions,
 * data storage layer (ConnectionBus), etc 2. all connections may be properly
 * closed during application shutdown
 */
public class JdbcConnectionManager implements ConnectionManager {
	private final Map<Integer, HikariDataSource> connectionPool = new ConcurrentHashMap<>();
	private final ILogNode logNode;
	private boolean hasDriversInitialized;

	public JdbcConnectionManager(final ILogNode logNode) {
		this.logNode = logNode;
	}

	public JdbcConnectionManager() {
		this(Core.getLogger(JdbcConnectionManager.class.getName()));
	}

	@Override
	public Connection getConnection(final String jdbcUrl, final String userName, final String password)
			throws SQLException {
		initializeDrivers();

		final Integer connPoolKey = toConnPoolKey(jdbcUrl, userName);
		final HikariDataSource dataSource = connectionPool.computeIfAbsent(connPoolKey, k -> {
			if (logNode.isTraceEnabled()) {
				logNode.trace(String.format("Creating data source in connection pool for [url=%s, user=%s]", jdbcUrl, userName));
			}
			return createHikariDataSource(jdbcUrl, userName, password, connPoolKey);
		});

		if (logNode.isTraceEnabled()) {
			logNode.trace(String.format("Getting connection from data source in connection pool for [url=%s, user=%s]",
				jdbcUrl, userName));
		}
		return dataSource.getConnection();
	}

	/**
	 * The JDBC drivers in the userlib folder of a project are not automatically
	 * correctly registered to the DriverManager. The cause is maybe the fact that
	 * the drivers are put into the project.jar on deployment. Iterating through the
	 * drivers found by ServiceLoader.load(Driver.class) will force them to load and
	 * register to the DriverManager.
	 */
	private synchronized void initializeDrivers() {
		if (!hasDriversInitialized) {
			ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
			Stream<String> driverNames = StreamSupport.stream(loader.spliterator(), false)
					.map(a -> a.getClass().getName());
			String logMessage = driverNames.collect(Collectors.joining(", ", "Found JDBC Drivers: ", ""));
			if (logNode.isTraceEnabled()) {
				logNode.trace(logMessage);
			}
			hasDriversInitialized = true;
		}
	}

	private Integer toConnPoolKey(final String jdbcUrl, final String userName) {
		return (jdbcUrl + userName).hashCode();
	}

	private HikariDataSource createHikariDataSource(final String jdbcUrl, final String userName, final String password,
			Integer connPoolKey) {
		final HikariDataSource dataSource = new HikariDataSource();
		dataSource.setPoolName(String.format("MxDbConnector-HikaryCP-%d", connPoolKey));
		dataSource.setJdbcUrl(jdbcUrl);
		dataSource.setUsername(userName);
		dataSource.setPassword(password);
		dataSource.setMinimumIdle(0);

		return dataSource;
	}
}

@@ -259,28 +259,82 @@
 ava.sql.
-SQLException
+DriverManager;%0Aimport java.sql.SQLException;%0Aimport java.util.List
 ;%0Aimport
@@ -2384,111 +2384,28 @@
 nt. 
-Iterating through the%0A%09 * drivers found by ServiceLoader.load(Driver.class) will force them to load and
+Hence, we explicitly
 %0A%09 *
@@ -2414,36 +2414,27 @@
 egister 
-to 
 the 
-D
+d
 river
-Manager
+s
 .%0A%09 */%0A%09
@@ -2588,29 +2588,27 @@
 %0A%09%09%09
-Stream%3CString
+List%3CDriver
 %3E driver
 Name
@@ -2603,20 +2603,16 @@
 %3E driver
-Name
 s = Stre
@@ -2660,25 +2660,234 @@
 lse)
-%0A%09%09%09%09%09.map(a -%3E a
+.collect(Collectors.toList());%0A%0A%09%09%09for (Driver driver : drivers) %7B%0A%09%09%09%09try %7B%0A%09%09%09%09%09DriverManager.registerDriver(driver);%0A%09%09%09%09%7D catch (SQLException e) %7B%0A%09%09%09%09%09throw new RuntimeException(%22Failed to register JDBC driver: %22 + driver
 .get
@@ -2907,45 +2907,141 @@
 me()
+, e
 );%0A%09%09%09
-String logMessage = driverNames
+%09%7D%0A%09%09%09%7D%0A%0A%09%09%09if (logNode.isTraceEnabled()) %7B%0A%09%09%09%09String logMessage = drivers.stream().map(a -%3E a.getClass().getName())%0A%09%09%09%09%09%09
 .col
@@ -3104,43 +3104,8 @@
 ));%0A
-%09%09%09if (logNode.isTraceEnabled()) %7B%0A
 %09%09%09%09
@@ -3136,16 +3136,17 @@
 );%0A%09%09%09%7D%0A
+%0A
 %09%09%09hasDr

Use DriverManager.registerDriver to explicitly load the drivers.