All Downloads are FREE. Search and download functionalities are using the official Maven repository.

is.codion.common.db.pool.AbstractConnectionPoolWrapper Maven / Gradle / Ivy

/*
 * This file is part of Codion.
 *
 * Codion 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.
 *
 * Codion 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 Codion.  If not, see .
 *
 * Copyright (c) 2013 - 2024, Björn Darri Sigurðsson.
 */
package is.codion.common.db.pool;

import is.codion.common.db.connection.ConnectionFactory;
import is.codion.common.db.exception.AuthenticationException;
import is.codion.common.db.exception.DatabaseException;
import is.codion.common.proxy.ProxyBuilder;
import is.codion.common.proxy.ProxyBuilder.ProxyMethod;
import is.codion.common.user.User;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.function.Function;

import static java.util.Arrays.asList;
import static java.util.Objects.requireNonNull;

/**
 * A default base implementation of the ConnectionPool wrapper, handling the collection of statistics
 * @param  the type representing the actual pool object
 */
public abstract class AbstractConnectionPoolWrapper implements ConnectionPoolWrapper {

	private static final String GET_CONNECTION = "getConnection";
	private static final String CLOSE = "close";

	/**
	 * The actual connection pool object
	 */
	private final T connectionPool;
	private final ConnectionFactory connectionFactory;
	private final User user;
	private final DefaultConnectionPoolCounter counter;

	/**
	 * Instantiates a new AbstractConnectionPool instance.
	 * @param connectionFactory the connection factory
	 * @param user the connection pool user
	 * @param dataSource the data source
	 * @param poolFactory creates the actual connection pool based on the given data source
	 */
	protected AbstractConnectionPoolWrapper(ConnectionFactory connectionFactory, User user, DataSource dataSource,
																					Function poolFactory) {
		this.connectionFactory = requireNonNull(connectionFactory);
		this.user = requireNonNull(user);
		this.counter = new DefaultConnectionPoolCounter(this);
		this.connectionPool = requireNonNull(poolFactory).apply(createDataSourceProxy(requireNonNull(dataSource)));
	}

	@Override
	public final User user() {
		return user;
	}

	@Override
	public final Connection connection(User user) {
		requireNonNull(user);
		checkConnectionPoolCredentials(user);
		long startTime = counter.isCollectCheckOutTimes() ? System.nanoTime() : 0;
		try {
			counter.incrementRequestCounter();

			return fetchConnection();
		}
		catch (SQLException e) {
			counter.incrementFailedRequestCounter();
			throw new DatabaseException(e);
		}
		finally {
			if (counter.isCollectCheckOutTimes() && startTime > 0L) {
				counter.addCheckOutTime((int) (System.nanoTime() - startTime) / 1_000_000);
			}
		}
	}

	@Override
	public final void resetStatistics() {
		counter.resetStatistics();
	}

	@Override
	public final boolean isCollectSnapshotStatistics() {
		return counter.isCollectSnapshotStatistics();
	}

	@Override
	public final void setCollectSnapshotStatistics(boolean collectSnapshotStatistics) {
		counter.setCollectSnapshotStatistics(collectSnapshotStatistics);
	}

	@Override
	public final boolean isCollectCheckOutTimes() {
		return counter.isCollectCheckOutTimes();
	}

	@Override
	public final void setCollectCheckOutTimes(boolean collectCheckOutTimes) {
		counter.setCollectCheckOutTimes(collectCheckOutTimes);
	}

	@Override
	public final ConnectionPoolStatistics statistics(long since) {
		return counter.collectStatistics(since);
	}

	/**
	 * Fetches a connection from the underlying pool.
	 * @return a connection from the underlying pool
	 * @throws SQLException in case of an exception.
	 */
	protected abstract Connection fetchConnection() throws SQLException;

	/**
	 * @return the underlying connection pool instance
	 */
	protected final T connectionPool() {
		return connectionPool;
	}

	/**
	 * @return the number of available connections in this pool
	 */
	protected abstract int available();

	/**
	 * @return the number of connections in active use
	 */
	protected abstract int inUse();

	/**
	 * @return the number of waiting connection requests
	 */
	protected abstract int waiting();

	/**
	 * Updates the given state instance with the current pool state.
	 * @param state the state to update
	 * @return the updated state
	 */
	final DefaultConnectionPoolState updateState(DefaultConnectionPoolState state) {
		return state.set(System.currentTimeMillis(), available(), inUse(), waiting());
	}

	/**
	 * Checks the given credentials against the credentials found in the connection pool user
	 * @param user the user credentials to check
	 * @throws AuthenticationException in case the username or password do not match the ones in the connection pool
	 */
	private void checkConnectionPoolCredentials(User user) throws AuthenticationException {
		if (!this.user.username().equalsIgnoreCase(user.username()) || !Arrays.equals(this.user.password(), user.password())) {
			throw new AuthenticationException("Wrong username or password");
		}
	}

	private DataSource createDataSourceProxy(DataSource dataSource) {
		GetConnection getConnection = new GetConnection();

		return ProxyBuilder.builder(DataSource.class)
						.delegate(dataSource)
						.method(GET_CONNECTION, getConnection)
						.method(GET_CONNECTION, asList(String.class, String.class), getConnection)
						.build();
	}

	private final class GetConnection implements ProxyMethod {

		@Override
		public Object invoke(Parameters parameters) throws Throwable {
			Connection connection = connectionFactory.createConnection(user);
			counter.incrementConnectionsCreatedCounter();

			return ProxyBuilder.builder(Connection.class)
							.delegate(connection)
							.method(CLOSE, new Close())
							.build();
		}
	}

	private final class Close implements ProxyMethod {

		@Override
		public Object invoke(Parameters parameters) throws Throwable {
			Connection connection = parameters.delegate();
			if (!connection.isClosed()) {
				counter.incrementConnectionsDestroyedCounter();
			}
			connection.close();
			return null;
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy