Skip to content

Commit

Permalink
Merge pull request #29397 from geoand/#29348
Browse files Browse the repository at this point in the history
Provide a way for users to customize PgPool creation
  • Loading branch information
cescoffier authored Jan 29, 2023
2 parents 24bae58 + fcca2fa commit ef5b122
Show file tree
Hide file tree
Showing 40 changed files with 1,520 additions and 5 deletions.
52 changes: 52 additions & 0 deletions docs/src/main/asciidoc/reactive-sql-clients.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,58 @@ For example, you could expire idle connections after 60 minutes:
quarkus.datasource.reactive.idle-timeout=PT60M
----

== Customizing pool creation

Sometimes, the database connection pool cannot be configured only by declaration.

You may need to read a specific file only present in production, or retrieve configuration data from a proprietary configuration server.

In this case, you can customize pool creation by creating a class implementing an interface which depends on the target database:

[cols="30,70"]
|===
|Database |Pool creator class name

|IBM Db2
|`io.quarkus.reactive.db2.client.DB2PoolCreator`

|MariaDB/MySQL
|`io.quarkus.reactive.mysql.client.MySQLPoolCreator`

|Microsoft SQL Server
|`io.quarkus.reactive.mssql.client.MSSQLPoolCreator`

|Oracle
|`io.quarkus.reactive.oracle.client.OraclePoolCreator`

|PostgreSQL
|`io.quarkus.reactive.pg.client.PgPoolCreator`
|===

Here's an example for PostgreSQL:

[source,java]
----
import javax.inject.Singleton;
import io.quarkus.reactive.pg.client.PgPoolCreator;
import io.vertx.pgclient.PgConnectOptions;
import io.vertx.pgclient.PgPool;
import io.vertx.sqlclient.PoolOptions;
@Singleton
public class CustomPgPoolCreator implements PgPoolCreator {
@Override
public PgPool create(Input input) {
PgConnectOptions connectOptions = input.pgConnectOptions();
PoolOptions poolOptions = input.poolOptions();
// Customize connectOptions, poolOptions or both, as required
return PgPool.pool(input.vertx(), connectOptions, poolOptions);
}
}
----

== Configuration Reference

=== Common Datasource
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
package io.quarkus.reactive.db2.client.deployment;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import javax.enterprise.context.ApplicationScoped;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.DotName;
import org.jboss.jandex.Type;

import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem.ExtendedBeanConfigurator;
import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
import io.quarkus.arc.deployment.ValidationPhaseBuildItem;
import io.quarkus.arc.deployment.devconsole.Name;
import io.quarkus.arc.processor.BeanInfo;
import io.quarkus.arc.processor.DotNames;
import io.quarkus.datasource.common.runtime.DataSourceUtil;
import io.quarkus.datasource.common.runtime.DatabaseKind;
Expand All @@ -33,6 +47,7 @@
import io.quarkus.reactive.datasource.runtime.DataSourceReactiveBuildTimeConfig;
import io.quarkus.reactive.datasource.runtime.DataSourcesReactiveBuildTimeConfig;
import io.quarkus.reactive.datasource.runtime.DataSourcesReactiveRuntimeConfig;
import io.quarkus.reactive.db2.client.DB2PoolCreator;
import io.quarkus.reactive.db2.client.runtime.DB2PoolRecorder;
import io.quarkus.reactive.db2.client.runtime.DB2ServiceBindingConverter;
import io.quarkus.reactive.db2.client.runtime.DataSourcesReactiveDB2Config;
Expand Down Expand Up @@ -88,6 +103,34 @@ DevServicesDatasourceConfigurationHandlerBuildItem devDbHandler() {
return DevServicesDatasourceConfigurationHandlerBuildItem.reactive(DatabaseKind.DB2);
}

@BuildStep
void unremoveableBeans(BuildProducer<UnremovableBeanBuildItem> producer) {
producer.produce(UnremovableBeanBuildItem.beanTypes(DB2PoolCreator.class));
}

@BuildStep
void validateBeans(ValidationPhaseBuildItem validationPhase,
BuildProducer<ValidationPhaseBuildItem.ValidationErrorBuildItem> errors) {
// no two Db2PoolCreator beans can be associated with the same datasource
Map<String, Boolean> seen = new HashMap<>();
for (BeanInfo beanInfo : validationPhase.getContext().beans()
.matchBeanTypes(new DB2PoolCreatorBeanClassPredicate())) {
Set<Name> qualifiers = new TreeSet<>(); // use a TreeSet in order to get a predictable iteration order
for (AnnotationInstance qualifier : beanInfo.getQualifiers()) {
qualifiers.add(Name.from(qualifier));
}
String qualifiersStr = qualifiers.stream().map(Name::toString).collect(Collectors.joining("_"));
if (seen.getOrDefault(qualifiersStr, false)) {
errors.produce(new ValidationPhaseBuildItem.ValidationErrorBuildItem(
new IllegalStateException(
"There can be at most one bean of type '" + DB2PoolCreator.class.getName()
+ "' for each datasource.")));
} else {
seen.put(qualifiersStr, true);
}
}
}

@BuildStep
void registerServiceBinding(Capabilities capabilities, BuildProducer<ServiceProviderBuildItem> serviceProvider,
BuildProducer<DefaultDataSourceDbKindBuildItem> dbKind) {
Expand Down Expand Up @@ -236,4 +279,14 @@ private static void addQualifiers(ExtendedBeanConfigurator configurator, String
.done();
}
}

private static class DB2PoolCreatorBeanClassPredicate implements Predicate<Set<Type>> {
private static final Type DB2_POOL_CREATOR = Type.create(DotName.createSimple(DB2PoolCreator.class.getName()),
Type.Kind.CLASS);

@Override
public boolean test(Set<Type> types) {
return types.contains(DB2_POOL_CREATOR);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.quarkus.reactive.db2.client;

import io.quarkus.reactive.datasource.ReactiveDataSource;
import io.vertx.core.Vertx;
import io.vertx.db2client.DB2ConnectOptions;
import io.vertx.db2client.DB2Pool;
import io.vertx.sqlclient.PoolOptions;

/**
* This interface is an integration point that allows users to use the {@link Vertx}, {@link PoolOptions} and
* {@link DB2ConnectOptions} objects configured automatically by Quarkus, in addition to a custom strategy
* for creating the final {@link DB2Pool}.
*
* Implementations of this class are meant to be used as CDI beans.
* If a bean of this type is used without a {@link ReactiveDataSource} qualifier, then it's applied to the default datasource,
* otherwise it applies to the datasource matching the name of the annotation.
*/
public interface DB2PoolCreator {

DB2Pool create(Input input);

interface Input {

Vertx vertx();

PoolOptions poolOptions();

DB2ConnectOptions db2ConnectOptions();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,20 @@
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import javax.enterprise.inject.Instance;

import org.jboss.logging.Logger;

import io.quarkus.arc.Arc;
import io.quarkus.credentials.CredentialsProvider;
import io.quarkus.credentials.runtime.CredentialsProviderFinder;
import io.quarkus.datasource.common.runtime.DataSourceUtil;
import io.quarkus.datasource.runtime.DataSourceRuntimeConfig;
import io.quarkus.datasource.runtime.DataSourcesRuntimeConfig;
import io.quarkus.reactive.datasource.ReactiveDataSource;
import io.quarkus.reactive.datasource.runtime.DataSourceReactiveRuntimeConfig;
import io.quarkus.reactive.datasource.runtime.DataSourcesReactiveRuntimeConfig;
import io.quarkus.reactive.db2.client.DB2PoolCreator;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.ShutdownContext;
import io.quarkus.runtime.annotations.Recorder;
Expand Down Expand Up @@ -78,7 +84,7 @@ private DB2Pool initialize(Vertx vertx,
log.warn(
"Configuration element 'thread-local' on Reactive datasource connections is deprecated and will be ignored. The started pool will always be based on a per-thread separate pool now.");
}
return DB2Pool.pool(vertx, connectOptions, poolOptions);
return createPool(vertx, poolOptions, connectOptions, dataSourceName);
}

private PoolOptions toPoolOptions(Integer eventLoopCount,
Expand Down Expand Up @@ -186,4 +192,47 @@ private DB2ConnectOptions toConnectOptions(DataSourceRuntimeConfig dataSourceRun

return connectOptions;
}

private DB2Pool createPool(Vertx vertx, PoolOptions poolOptions, DB2ConnectOptions dB2ConnectOptions,
String dataSourceName) {
Instance<DB2PoolCreator> instance;
if (DataSourceUtil.isDefault(dataSourceName)) {
instance = Arc.container().select(DB2PoolCreator.class);
} else {
instance = Arc.container().select(DB2PoolCreator.class,
new ReactiveDataSource.ReactiveDataSourceLiteral(dataSourceName));
}
if (instance.isResolvable()) {
DB2PoolCreator.Input input = new DefaultInput(vertx, poolOptions, dB2ConnectOptions);
return instance.get().create(input);
}
return DB2Pool.pool(vertx, dB2ConnectOptions, poolOptions);
}

private static class DefaultInput implements DB2PoolCreator.Input {
private final Vertx vertx;
private final PoolOptions poolOptions;
private final DB2ConnectOptions dB2ConnectOptions;

public DefaultInput(Vertx vertx, PoolOptions poolOptions, DB2ConnectOptions dB2ConnectOptions) {
this.vertx = vertx;
this.poolOptions = poolOptions;
this.dB2ConnectOptions = dB2ConnectOptions;
}

@Override
public Vertx vertx() {
return vertx;
}

@Override
public PoolOptions poolOptions() {
return poolOptions;
}

@Override
public DB2ConnectOptions db2ConnectOptions() {
return dB2ConnectOptions;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
package io.quarkus.reactive.mssql.client.deployment;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import javax.enterprise.context.ApplicationScoped;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.DotName;
import org.jboss.jandex.Type;

import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem.ExtendedBeanConfigurator;
import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
import io.quarkus.arc.deployment.ValidationPhaseBuildItem;
import io.quarkus.arc.deployment.devconsole.Name;
import io.quarkus.arc.processor.BeanInfo;
import io.quarkus.arc.processor.DotNames;
import io.quarkus.datasource.common.runtime.DataSourceUtil;
import io.quarkus.datasource.common.runtime.DatabaseKind;
Expand All @@ -33,6 +47,7 @@
import io.quarkus.reactive.datasource.runtime.DataSourceReactiveBuildTimeConfig;
import io.quarkus.reactive.datasource.runtime.DataSourcesReactiveBuildTimeConfig;
import io.quarkus.reactive.datasource.runtime.DataSourcesReactiveRuntimeConfig;
import io.quarkus.reactive.mssql.client.MSSQLPoolCreator;
import io.quarkus.reactive.mssql.client.runtime.DataSourcesReactiveMSSQLConfig;
import io.quarkus.reactive.mssql.client.runtime.MSSQLPoolRecorder;
import io.quarkus.reactive.mssql.client.runtime.MsSQLServiceBindingConverter;
Expand Down Expand Up @@ -88,6 +103,34 @@ DevServicesDatasourceConfigurationHandlerBuildItem devDbHandler() {
return DevServicesDatasourceConfigurationHandlerBuildItem.reactive(DatabaseKind.MSSQL);
}

@BuildStep
void unremoveableBeans(BuildProducer<UnremovableBeanBuildItem> producer) {
producer.produce(UnremovableBeanBuildItem.beanTypes(MSSQLPoolCreator.class));
}

@BuildStep
void validateBeans(ValidationPhaseBuildItem validationPhase,
BuildProducer<ValidationPhaseBuildItem.ValidationErrorBuildItem> errors) {
// no two MssqlPoolCreator beans can be associated with the same datasource
Map<String, Boolean> seen = new HashMap<>();
for (BeanInfo beanInfo : validationPhase.getContext().beans()
.matchBeanTypes(new MSSQLPoolCreatorBeanClassPredicate())) {
Set<Name> qualifiers = new TreeSet<>(); // use a TreeSet in order to get a predictable iteration order
for (AnnotationInstance qualifier : beanInfo.getQualifiers()) {
qualifiers.add(Name.from(qualifier));
}
String qualifiersStr = qualifiers.stream().map(Name::toString).collect(Collectors.joining("_"));
if (seen.getOrDefault(qualifiersStr, false)) {
errors.produce(new ValidationPhaseBuildItem.ValidationErrorBuildItem(
new IllegalStateException(
"There can be at most one bean of type '" + MSSQLPoolCreator.class.getName()
+ "' for each datasource.")));
} else {
seen.put(qualifiersStr, true);
}
}
}

@BuildStep
void registerServiceBinding(Capabilities capabilities, BuildProducer<ServiceProviderBuildItem> serviceProvider,
BuildProducer<DefaultDataSourceDbKindBuildItem> dbKind) {
Expand Down Expand Up @@ -235,4 +278,14 @@ private static void addQualifiers(ExtendedBeanConfigurator configurator, String
.done();
}
}

private static class MSSQLPoolCreatorBeanClassPredicate implements Predicate<Set<Type>> {
private static final Type MSSQL_POOL_CREATOR = Type.create(DotName.createSimple(MSSQLPoolCreator.class.getName()),
Type.Kind.CLASS);

@Override
public boolean test(Set<Type> types) {
return types.contains(MSSQL_POOL_CREATOR);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.quarkus.reactive.mssql.client;

import javax.inject.Singleton;

import io.vertx.mssqlclient.MSSQLPool;

@Singleton
public class LocalhostMSSQLPoolCreator implements MSSQLPoolCreator {

@Override
public MSSQLPool create(Input input) {
return MSSQLPool.pool(input.vertx(), input.msSQLConnectOptions().setHost("localhost").setPort(1435),
input.poolOptions());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.quarkus.reactive.mssql.client;

import static io.restassured.RestAssured.given;

import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;

public class MSSQLPoolCreatorTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClass(CustomCredentialsProvider.class)
.addClass(CredentialsTestResource.class)
.addClass(LocalhostMSSQLPoolCreator.class)
.addAsResource("application-credentials-with-erroneous-url.properties", "application.properties"));

@Test
public void testConnect() {
given()
.when().get("/test")
.then()
.statusCode(200)
.body(CoreMatchers.equalTo("OK"));
}

}
Loading

0 comments on commit ef5b122

Please sign in to comment.