/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jdbi.v3.core;
import javax.sql.DataSource;
import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Objects;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;
import org.jdbi.v3.core.config.ConfigRegistry;
import org.jdbi.v3.core.config.Configurable;
import org.jdbi.v3.core.extension.ExtensionCallback;
import org.jdbi.v3.core.extension.ExtensionConsumer;
import org.jdbi.v3.core.extension.ExtensionFactory;
import org.jdbi.v3.core.extension.Extensions;
import org.jdbi.v3.core.extension.NoSuchExtensionException;
import org.jdbi.v3.core.spi.JdbiPlugin;
import org.jdbi.v3.core.statement.DefaultStatementBuilderFactory;
import org.jdbi.v3.core.statement.StatementBuilder;
import org.jdbi.v3.core.statement.StatementBuilderFactory;
import org.jdbi.v3.core.transaction.LocalTransactionHandler;
import org.jdbi.v3.core.transaction.TransactionHandler;
import org.jdbi.v3.core.transaction.TransactionIsolationLevel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class provides the access point for Jdbi. Use it to obtain Handle instances
* and provide "global" configuration for all handles obtained from it.
*/
public class Jdbi implements Configurable<Jdbi>
{
private static final Logger LOG = LoggerFactory.getLogger(Jdbi.class);
private final ConfigRegistry config = new ConfigRegistry();
private final ConnectionFactory connectionFactory;
private final AtomicReference<TransactionHandler> transactionhandler = new AtomicReference<>(new LocalTransactionHandler());
private final AtomicReference<StatementBuilderFactory> statementBuilderFactory = new AtomicReference<>(new DefaultStatementBuilderFactory());
private final CopyOnWriteArrayList<JdbiPlugin> plugins = new CopyOnWriteArrayList<>();
private Jdbi(ConnectionFactory connectionFactory)
{
Objects.requireNonNull(connectionFactory, "null connectionFactory");
this.connectionFactory = connectionFactory;
}
/**
* @param dataSource the data source.
*
* @return a Jdbi which uses the given data source as a connection factory.
*/
public static Jdbi create(DataSource dataSource)
{
return create(dataSource::getConnection);
}
/**
* Factory used to allow for obtaining a Connection in a customized manner.
*
* <p>
* The {@link ConnectionFactory#openConnection()} method will be invoked to obtain a connection instance
* whenever a Handle is opened.
* </p>
*
* @param connectionFactory Provides JDBC connections to Handle instances
*
* @return a Jdbi which uses the given connection factory.
*/
public static Jdbi create(ConnectionFactory connectionFactory) {
return new Jdbi(connectionFactory);
}
/**
* @param url JDBC URL for connections
*
* @return a Jdbi which uses {@link DriverManager} as a connection factory.
*/
public static Jdbi create(final String url)
{
Objects.requireNonNull(url, "null url");
return create(() -> DriverManager.getConnection(url));
}
/**
* @param url JDBC URL for connections
* @param properties Properties to pass to DriverManager.getConnection(url, props) for each new handle
*
* @return a Jdbi which uses {@link DriverManager} as a connection factory.
*/
public static Jdbi create(final String url, final Properties properties)
{
Objects.requireNonNull(url, "null url");
Objects.requireNonNull(properties, "null properties");
return create(() -> DriverManager.getConnection(url, properties));
}
/**
* @param url JDBC URL for connections
* @param username User name for connection authentication
* @param password Password for connection authentication
*
* @return a Jdbi which uses {@link DriverManager} as a connection factory.
*/
public static Jdbi create(final String url, final String username, final String password)
{
Objects.requireNonNull(url, "null url");
Objects.requireNonNull(username, "null username");
Objects.requireNonNull(password, "null password");
return create(() -> DriverManager.getConnection(url, username, password));
}
/**
* Convenience method used to obtain a handle from a specific data source
*
* @param dataSource the JDBC data source.
*
* @return Handle using a Connection obtained from the provided DataSource
*/
public static Handle open(DataSource dataSource)
{
return create(dataSource).open();
}
/**
* Create a Handle wrapping a particular JDBC Connection
*
* @param connection the JDBC connection
*
* @return Handle bound to connection
*/
public static Handle open(final Connection connection)
{
Objects.requireNonNull(connection, "null connection");
return create(() -> connection).open();
}
/**
* Obtain a handle with just a JDBC URL
*
* @param url JDBC Url
*
* @return newly opened Handle
*/
public static Handle open(final String url)
{
return create(url).open();
}
/**
* Obtain a handle with just a JDBC URL
*
* @param url JDBC Url
* @param username JDBC username for authentication
* @param password JDBC password for authentication
*
* @return newly opened Handle
*/
public static Handle open(final String url, final String username, final String password)
{
return create(url, username, password).open();
}
/**
* Obtain a handle with just a JDBC URL
*
* @param url JDBC Url
* @param props JDBC properties
*
* @return newly opened Handle
*/
public static Handle open(final String url, final Properties props)
{
return create(url, props).open();
}
public Jdbi installPlugins()
{
ServiceLoader.load(JdbiPlugin.class).forEach(this::installPlugin);
LOG.debug("Automatically installed plugins {}", plugins);
return this;
}
public Jdbi installPlugin(JdbiPlugin plugin)
{
plugin.customizeJdbi(this);
plugins.add(plugin);
return this;
}
/**
* Allows customization of how prepared statements are created. When a Handle is created
* against this Jdbi instance the factory will be used to create a StatementBuilder for
* that specific handle. When the handle is closed, the StatementBuilder's close method
* will be invoked.
*
* @param factory the new statement builder factory.
* @return this
*/
public Jdbi setStatementBuilderFactory(StatementBuilderFactory factory)
{
this.statementBuilderFactory.set(factory);
return this;
}
public StatementBuilderFactory getStatementBuilderFactory()
{
return this.statementBuilderFactory.get();
}
@Override
public ConfigRegistry getConfig() {
return config;
}
/**
* Specify the TransactionHandler instance to use. This allows overriding
* transaction semantics, or mapping into different transaction
* management systems.
* <p>
* The default version uses local transactions on the database Connection
* instances obtained.
* </p>
*
* @param handler The TransactionHandler to use for all Handle instances obtained
* from this Jdbi
* @return this
*/
public Jdbi setTransactionHandler(TransactionHandler handler)
{
Objects.requireNonNull(handler, "null transaction handler");
this.transactionhandler.set(handler);
return this;
}
public TransactionHandler getTransactionHandler()
{
return this.transactionhandler.get();
}
/**
* Obtain a Handle to the data source wrapped by this Jdbi instance
*
* @return an open Handle instance
*/
public Handle open()
{
try {
final long start = System.nanoTime();
Connection conn = connectionFactory.openConnection();
final long stop = System.nanoTime();
for (JdbiPlugin p : plugins) {
conn = p.customizeConnection(conn);
}
StatementBuilder cache = statementBuilderFactory.get().createStatementBuilder(conn);
Handle h = new Handle(config.createCopy(), transactionhandler.get(), cache, conn);
for (JdbiPlugin p : plugins) {
h = p.customizeHandle(h);
}
LOG.trace("Jdbi [{}] obtain handle [{}] in {}ms", this, h, (stop - start) / 1000000L);
return h;
}
catch (SQLException e) {
throw new ConnectionException(e);
}
}
/**
* A convenience function which manages the lifecycle of a handle and yields it to a callback
* for use by clients.
*
* @param callback A callback which will receive an open Handle
* @param <R> type returned by the callback
* @param <X> exception type thrown by the callback, if any.
*
* @return the value returned by callback
*
* @throws X any exception thrown by the callback
*/
public <R, X extends Exception> R withHandle(HandleCallback<R, X> callback) throws X
{
try (Handle h = this.open()) {
return callback.withHandle(h);
}
}
/**
* A convenience function which manages the lifecycle of a handle and yields it to a callback
* for use by clients.
*
* @param callback A callback which will receive an open Handle
* @param <X> exception type thrown by the callback, if any.
*
* @throws X any exception thrown by the callback
*/
public <X extends Exception> void useHandle(final HandleConsumer<X> callback) throws X
{
withHandle(h -> { callback.useHandle(h); return null; });
}
/**
* A convenience function which manages the lifecycle of a handle and yields it to a callback
* for use by clients. The handle will be in a transaction when the callback is invoked, and
* that transaction will be committed if the callback finishes normally, or rolled back if the
* callback raises an exception.
*
* @param callback A callback which will receive an open Handle, in a transaction
* @param <R> type returned by the callback
* @param <X> exception type thrown by the callback, if any.
*
* @return the value returned by callback
*
* @throws X any exception thrown by the callback
*/
public <R, X extends Exception> R inTransaction(final HandleCallback<R, X> callback) throws X
{
return withHandle(handle -> handle.<R, X>inTransaction(callback));
}
/**
* A convenience function which manages the lifecycle of a handle and yields it to a callback
* for use by clients. The handle will be in a transaction when the callback is invoked, and
* that transaction will be committed if the callback finishes normally, or rolled back if the
* callback raises an exception.
*
* @param callback A callback which will receive an open Handle, in a transaction
* @param <X> exception type thrown by the callback, if any.
*
* @throws X any exception thrown by the callback
*/
public <X extends Exception> void useTransaction(final HandleConsumer<X> callback) throws X
{
useHandle(handle -> handle.useTransaction(callback));
}
/**
* A convenience function which manages the lifecycle of a handle and yields it to a callback
* for use by clients. The handle will be in a transaction when the callback is invoked, and
* that transaction will be committed if the callback finishes normally, or rolled back if the
* callback raises an exception.
*
* <p>
* This form accepts a transaction isolation level which will be applied to the connection
* for the scope of this transaction, after which the original isolation level will be restored.
* </p>
*
* @param level the transaction isolation level which will be applied to the connection for the scope of this
* transaction, after which the original isolation level will be restored.
* @param callback A callback which will receive an open Handle, in a transaction
* @param <R> type returned by the callback
* @param <X> exception type thrown by the callback, if any.
*
* @return the value returned by callback
*
* @throws X any exception thrown by the callback
*/
public <R, X extends Exception> R inTransaction(final TransactionIsolationLevel level, final HandleCallback<R, X> callback) throws X
{
return withHandle(handle -> handle.<R, X>inTransaction(level, callback));
}
/**
* A convenience function which manages the lifecycle of a handle and yields it to a callback
* for use by clients. The handle will be in a transaction when the callback is invoked, and
* that transaction will be committed if the callback finishes normally, or rolled back if the
* callback raises an exception.
*
* <p>
* This form accepts a transaction isolation level which will be applied to the connection
* for the scope of this transaction, after which the original isolation level will be restored.
* </p>
*
* @param level the transaction isolation level which will be applied to the connection for the scope of this
* transaction, after which the original isolation level will be restored.
* @param callback A callback which will receive an open Handle, in a transaction
* @param <X> exception type thrown by the callback, if any.
*
* @throws X any exception thrown by the callback
*/
public <X extends Exception> void useTransaction(final TransactionIsolationLevel level, final HandleConsumer<X> callback) throws X
{
useHandle(handle -> handle.useTransaction(level, callback));
}
/**
* A convenience method which opens an extension of the given type, yields it to a callback, and returns the result
* of the callback. A handle is opened if needed by the extension, and closed before returning to the caller.
*
* @param extensionType the type of extension.
* @param callback a callback which will receive the extension.
* @param <R> the return type
* @param <E> the extension type
* @param <X> the exception type optionally thrown by the callback
* @return the value returned by the callback.
* @throws NoSuchExtensionException if no {@link ExtensionFactory} is registered which supports the given extension
* type.
* @throws X if thrown by the callback.
*/
public <R, E, X extends Exception> R withExtension(Class<E> extensionType, ExtensionCallback<R, E, X> callback)
throws NoSuchExtensionException, X
{
try (LazyHandleSupplier handle = new LazyHandleSupplier(this, config)) {
E extension = getConfig(Extensions.class)
.findFor(extensionType, handle)
.orElseThrow(() -> new NoSuchExtensionException("Extension not found: " + extensionType));
return callback.withExtension(extension);
}
}
/**
* A convenience method which opens an extension of the given type, and yields it to a callback. A handle is opened
* if needed by the extention, and closed before returning to the caller.
*
* @param extensionType the type of extension
* @param callback a callback which will receive the extension
* @param <E> the extension type
* @param <X> the exception type optionally thrown by the callback
* @throws NoSuchExtensionException if no {@link ExtensionFactory} is registered which supports the given extension type.
* @throws X if thrown by the callback.
*/
public <E, X extends Exception> void useExtension(Class<E> extensionType, ExtensionConsumer<E, X> callback)
throws NoSuchExtensionException, X {
withExtension(extensionType, extension -> {
callback.useExtension(extension);
return null;
});
}
/**
* @param extensionType the type of extension. Must be a public interface type.
* @param <E> the extension type
*
* @return an extension which opens and closes handles (as needed) for individual method calls. Only public
* interface types may be used as on-demand extensions.
*/
public <E> E onDemand(Class<E> extensionType) throws NoSuchExtensionException {
if (!extensionType.isInterface()) {
throw new IllegalArgumentException("On-demand extensions are only supported for interfaces.");
}
if (!Modifier.isPublic(extensionType.getModifiers())) {
throw new IllegalArgumentException("On-demand extensions types must be public.");
}
if (!getConfig(Extensions.class).hasExtensionFor(extensionType)) {
throw new NoSuchExtensionException("Extension not found: " + extensionType);
}
return OnDemandExtensions.create(this, extensionType);
}
}