
org.nuiton.topia.persistence.internal.TopiaConnectionProvider Maven / Gradle / Ivy
package org.nuiton.topia.persistence.internal;
/*
* #%L
* ToPIA Extension :: API
* %%
* Copyright (C) 2018 - 2022 Ultreia.io
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* .
* #L%
*/
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hibernate.HibernateException;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Environment;
import org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.service.UnknownUnwrapTypeException;
import org.hibernate.service.spi.Configurable;
import org.hibernate.service.spi.Stoppable;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
*
* Customized connection provider.
*
*
* This provider fix the following bug : http://nuiton.org/issues/show/561
*
* To use this connection provider, add this property to topia configuration
*
* config.setProperty(Environment.CONNECTION_PROVIDER, TopiaConnectionProvider.class.getName());
*
* or in a properties file :
*
* hibernate.connection.provider_class=org.nuiton.topia.persistence.internal.TopiaConnectionProvider
*
*
* @author Tony Chemit - [email protected]
* @since 2.5.3
*/
public class TopiaConnectionProvider implements ConnectionProvider, Configurable, Stoppable {
private static final Logger log = LogManager.getLogger(TopiaConnectionProvider.class);
private static final long serialVersionUID = -8190835231054317644L;
/**
* JDBC url of connection.
*
* This is a mandatory hibernate configuration vi the property
* {@link Environment#URL}.
*/
protected String url;
/**
* All grabbed connection properties
*/
protected Properties connectionProps;
/**
* Sql isolation level to use in connection.
*
* Can be configured by hibernate property {@link Environment#ISOLATION}.
*
* @see Connection#getTransactionIsolation()
*/
protected Integer isolation;
/**
* auto commit connection state.
*
* Can be configured by hibernate property {@link Environment#AUTOCOMMIT}.
*
* @see Connection#getAutoCommit()
*/
protected boolean autocommit;
/**
* Size of connection pool.
*
* By default use {@code 20}, can be specify by using the hibernate
* configuration property {@link Environment#POOL_SIZE}.
*/
protected int poolSize;
private boolean stopped;
/**
* Our pool of connections which are not closed and availables.
*/
protected final List pool;
public TopiaConnectionProvider() {
pool = new ArrayList<>();
}
@Override
public void configure(Map configurationValues) throws HibernateException {
poolSize = ConfigurationHelper.getInt(AvailableSettings.POOL_SIZE, configurationValues, 20); //default pool size 20
if (log.isDebugEnabled()) {
log.debug("Connection pool size: " + poolSize);
}
autocommit = ConfigurationHelper.getBoolean(AvailableSettings.AUTOCOMMIT, configurationValues);
if (log.isDebugEnabled()) {
log.debug("autocommit mode: " + autocommit);
}
isolation = ConfigurationHelper.getInteger(AvailableSettings.ISOLATION, configurationValues);
if (isolation != null) {
if (log.isDebugEnabled()) {
log.debug("JDBC isolation level: " +
Environment.isolationLevelToString(isolation));
}
}
String driverClass = ConfigurationHelper.getString(AvailableSettings.DRIVER, configurationValues);
if (driverClass == null) {
if (log.isWarnEnabled()) {
log.warn("no JDBC Driver class was specified by property " +
AvailableSettings.DRIVER);
}
} else {
try {
// trying via forName() first to be as close to DriverManager's semantics
Class.forName(driverClass);
} catch (ClassNotFoundException cnfe) {
try {
ReflectHelper.classForName(driverClass);
} catch (ClassNotFoundException e) {
String msg = "JDBC Driver class not found: " + driverClass;
log.error(msg, e);
throw new HibernateException(msg, e);
}
}
}
url = (String) configurationValues.get(AvailableSettings.URL);
if (url == null) {
String msg = "JDBC URL was not specified by property " + AvailableSettings.URL;
if (log.isErrorEnabled()) {
log.error(msg);
}
throw new HibernateException(msg);
}
connectionProps =
ConnectionProviderInitiator.getConnectionProperties(configurationValues);
if (log.isDebugEnabled()) {
log.debug("using driver: " + driverClass + " at URL: " + url);
}
// if debug level is enabled, then log the password, otherwise mask it
if (log.isTraceEnabled()) {
log.debug("connection properties: " + connectionProps);
} else if (log.isDebugEnabled()) {
log.debug("connection properties: " +
ConfigurationHelper.maskOut(connectionProps, "password"));
}
}
@Override
public Connection getConnection() throws SQLException {
Connection connection = null;
synchronized (pool) {
// try to use a connection from the pool (if any)
while (!pool.isEmpty() && connection == null) {
int last = pool.size() - 1;
if (log.isTraceEnabled()) {
log.trace("using pooled JDBC connection, pool size: " +
last);
}
connection = pool.remove(last);
if (connection.isClosed()) {
// this connection is closed!, don't use it
connection = null;
if (log.isDebugEnabled()) {
log.debug("Remove already closed connection from pool " +
connection);
}
}
}
}
if (connection == null) {
// the pool was empty, creates a new connection
if (log.isDebugEnabled()) {
log.debug("opening new JDBC connection to " + url);
}
connection = DriverManager.getConnection(url, connectionProps);
}
// configure connection
if (isolation != null) {
connection.setTransactionIsolation(isolation);
}
if (connection.getAutoCommit() != autocommit) {
connection.setAutoCommit(autocommit);
}
return connection;
}
@Override
public void closeConnection(Connection conn) throws SQLException {
if (conn == null) {
if (log.isDebugEnabled()) {
log.debug("No JDBC connection to close");
}
return;
}
// if connection is already closed, nothing has to be done
// we can't keep this connection (and can not be push in pool)
if (conn.isClosed()) {
if (log.isDebugEnabled()) {
log.debug("Connection [" + conn +
"] alreay closed!, will not use it any longer ");
}
return;
}
// connection was not closed, can push it in the pool (if pool is not
// full)
synchronized (pool) {
int currentSize = pool.size();
if (currentSize < getPoolSize()) {
if (log.isTraceEnabled()) {
log.trace("returning connection to pool, pool size: " +
(currentSize + 1));
}
pool.add(conn);
return;
}
}
// pool was full, must release the connection which will be loose
if (log.isDebugEnabled()) {
log.debug("closing JDBC connection");
}
conn.close();
}
@Override
protected void finalize() throws Throwable {
if (!stopped) {
stop();
}
super.finalize();
}
@Override
public void stop() {
if (log.isDebugEnabled()) {
log.debug("cleaning up connection pool: " + url);
}
for (Connection connection : pool) {
try {
connection.close();
} catch (SQLException sqle) {
if (log.isWarnEnabled()) {
log.warn("problem closing pooled connection", sqle);
}
}
}
pool.clear();
stopped = true;
}
@Override
public boolean supportsAggressiveRelease() {
return false;
}
public String getUrl() {
return url;
}
public Properties getConnectionProps() {
return connectionProps;
}
public Integer getIsolation() {
return isolation;
}
public List getPool() {
return pool;
}
public int getPoolSize() {
return poolSize;
}
public boolean isAutocommit() {
return autocommit;
}
@Override
public boolean isUnwrappableAs(Class unwrapType) {
return ConnectionProvider.class.equals(unwrapType) ||
getClass().isAssignableFrom(unwrapType);
}
@Override
@SuppressWarnings({"unchecked"})
public T unwrap(Class unwrapType) {
if (ConnectionProvider.class.equals(unwrapType) ||
getClass().isAssignableFrom(unwrapType)) {
return (T) this;
} else {
throw new UnknownUnwrapTypeException(unwrapType);
}
}
}