Skip to content

Commit fd8f653

Browse files
authored
Merge pull request #51478 from gastaldi/jdbc_build_time_properties
Improve OpenTelemetry handling with Agroal integration with MySQL driver
2 parents ec03049 + a1adfcd commit fd8f653

File tree

7 files changed

+88
-8
lines changed

7 files changed

+88
-8
lines changed

docs/src/main/asciidoc/datasource.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,7 @@ When compiling a Quarkus application to a native image, the MySQL support for JM
815815

816816
* The lack of JMX support is a natural consequence of running in native mode and is unlikely to be resolved.
817817
* The integration with OCI is not supported.
818+
* The OpenTelemetry integration included in the MySQL Connector/J driver is disabled in favor of the one provided by Agroal. If you really need to use the OpenTelemetry integration provided by the MySQL Connector/J driver, enable it explicitly by setting `quarkus.datasource.jdbc.additional-jdbc-properties.openTelemetry=PREFERRED` (https://dev.mysql.com/doc/connector-j/en/connector-j-connp-props-debugging-profiling.html#cj-conn-prop_openTelemetry[documentation]). Be aware that by enabling it, if your application is using the `quarkus-opentelemetry` extension, the application will likely fail to start with an `java.lang.IllegalStateException: GlobalOpenTelemetry.set has already been called.` error.
818819

819820
==== Oracle
820821

extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AggregatedDataSourceBuildTimeConfigBuildItem.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
import io.quarkus.datasource.common.runtime.DataSourceUtil;
66
import io.quarkus.datasource.runtime.DataSourceBuildTimeConfig;
77

8-
final class AggregatedDataSourceBuildTimeConfigBuildItem extends MultiBuildItem {
8+
/**
9+
* An aggregated build item holding the build time configuration for a given datasource,
10+
* including both the build and the runtime datasource config.
11+
*/
12+
public final class AggregatedDataSourceBuildTimeConfigBuildItem extends MultiBuildItem {
913

1014
private final String name;
1115

extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import io.quarkus.agroal.runtime.TransactionIntegration;
3737
import io.quarkus.agroal.spi.JdbcDataSourceBuildItem;
3838
import io.quarkus.agroal.spi.JdbcDriverBuildItem;
39+
import io.quarkus.agroal.spi.JdbcPropertyBuildItem;
3940
import io.quarkus.arc.BeanDestroyer;
4041
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
4142
import io.quarkus.arc.deployment.OpenTelemetrySdkBuildItem;
@@ -256,7 +257,8 @@ void generateDataSourceBeans(AgroalRecorder recorder,
256257
Capabilities capabilities,
257258
Optional<OpenTelemetrySdkBuildItem> openTelemetrySdkBuildItem,
258259
BuildProducer<SyntheticBeanBuildItem> syntheticBeanBuildItemBuildProducer,
259-
BuildProducer<JdbcDataSourceBuildItem> jdbcDataSource) {
260+
BuildProducer<JdbcDataSourceBuildItem> jdbcDataSource,
261+
List<JdbcPropertyBuildItem> jdbcPropertyBuildItems) {
260262
if (aggregatedBuildTimeConfigBuildItems.isEmpty()) {
261263
// No datasource has been configured so bail out
262264
return;
@@ -266,6 +268,11 @@ void generateDataSourceBeans(AgroalRecorder recorder,
266268

267269
String dataSourceName = aggregatedBuildTimeConfigBuildItem.getName();
268270

271+
// Filter the JDBC properties for the current datasource
272+
Map<String, String> jdbcProperties = jdbcPropertyBuildItems.stream()
273+
.filter(p -> dataSourceName.equals(p.dataSourceName()))
274+
.collect(Collectors.toMap(JdbcPropertyBuildItem::propertyName, JdbcPropertyBuildItem::propertyValue));
275+
269276
SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem
270277
.configure(AgroalDataSource.class)
271278
.addType(DATA_SOURCE)
@@ -277,7 +284,8 @@ void generateDataSourceBeans(AgroalRecorder recorder,
277284
.addInjectionPoint(ClassType.create(DotName.createSimple(DataSources.class)))
278285
.startup()
279286
.checkActive(recorder.agroalDataSourceCheckActiveSupplier(dataSourceName))
280-
.createWith(recorder.agroalDataSourceSupplier(dataSourceName, isOtelSdkEnabled(openTelemetrySdkBuildItem)))
287+
.createWith(recorder.agroalDataSourceSupplier(dataSourceName, isOtelSdkEnabled(openTelemetrySdkBuildItem),
288+
jdbcProperties))
281289
.destroyer(BeanDestroyer.AutoCloseableDestroyer.class);
282290

283291
if (!DataSourceUtil.isDefault(dataSourceName)) {

extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/AgroalRecorder.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.quarkus.agroal.runtime;
22

3+
import java.util.Map;
34
import java.util.Optional;
45
import java.util.function.Function;
56
import java.util.function.Supplier;
@@ -55,14 +56,15 @@ public ActiveResult get() {
5556

5657
public Function<SyntheticCreationalContext<AgroalDataSource>, AgroalDataSource> agroalDataSourceSupplier(
5758
String dataSourceName,
58-
Optional<RuntimeValue<Boolean>> otelEnabled) {
59+
Optional<RuntimeValue<Boolean>> otelEnabled,
60+
Map<String, String> jdbcProperties) {
5961
return new Function<>() {
6062
@SuppressWarnings("deprecation")
6163
@Override
6264
public AgroalDataSource apply(SyntheticCreationalContext<AgroalDataSource> context) {
6365
DataSources dataSources = context.getInjectedReference(DataSources.class);
6466
return dataSources.createDataSource(dataSourceName,
65-
otelEnabled.isPresent() ? otelEnabled.get().getValue() : false);
67+
otelEnabled.isPresent() ? otelEnabled.get().getValue() : false, jdbcProperties);
6668
}
6769
};
6870
}

extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ public AgroalDataSource getDataSource(String dataSourceName) {
141141
}
142142

143143
@SuppressWarnings("resource")
144-
public AgroalDataSource createDataSource(String dataSourceName, boolean otelEnabled) {
144+
public AgroalDataSource createDataSource(String dataSourceName, boolean otelEnabled,
145+
Map<String, String> buildTimeJdbcProperties) {
145146
if (!agroalDataSourceSupport.entries.containsKey(dataSourceName)) {
146147
throw new IllegalArgumentException("No datasource named '" + dataSourceName + "' exists");
147148
}
@@ -192,7 +193,7 @@ public AgroalDataSource createDataSource(String dataSourceName, boolean otelEnab
192193
applyNewConfiguration(dataSourceName, dataSourceConfiguration, poolConfiguration, connectionFactoryConfiguration,
193194
driver, jdbcUrl,
194195
dataSourceJdbcBuildTimeConfig, dataSourceRuntimeConfig, dataSourceJdbcRuntimeConfig, transactionRuntimeConfig,
195-
mpMetricsPresent);
196+
mpMetricsPresent, buildTimeJdbcProperties);
196197

197198
if (agroalDataSourceSupport.disableSslSupport) {
198199
agroalConnectionConfigurer.disableSslSupport(resolvedDbKind, dataSourceConfiguration,
@@ -243,7 +244,7 @@ private void applyNewConfiguration(String dataSourceName, AgroalDataSourceConfig
243244
AgroalConnectionFactoryConfigurationSupplier connectionFactoryConfiguration, Class<?> driver, String jdbcUrl,
244245
DataSourceJdbcBuildTimeConfig dataSourceJdbcBuildTimeConfig, DataSourceRuntimeConfig dataSourceRuntimeConfig,
245246
DataSourceJdbcRuntimeConfig dataSourceJdbcRuntimeConfig, TransactionManagerConfiguration transactionRuntimeConfig,
246-
boolean mpMetricsPresent) {
247+
boolean mpMetricsPresent, Map<String, String> buildTimeJdbcProperties) {
247248
connectionFactoryConfiguration.jdbcUrl(jdbcUrl);
248249
connectionFactoryConfiguration.connectionProviderClass(driver);
249250
connectionFactoryConfiguration.trackJdbcResources(dataSourceJdbcRuntimeConfig.detectStatementLeaks());
@@ -304,6 +305,11 @@ private void applyNewConfiguration(String dataSourceName, AgroalDataSourceConfig
304305
.credential(new AgroalVaultCredentialsProviderPassword(name, credentialsProvider));
305306
}
306307

308+
// Additional JDBC properties from build time config
309+
for (Map.Entry<String, String> entry : buildTimeJdbcProperties.entrySet()) {
310+
connectionFactoryConfiguration.jdbcProperty(entry.getKey(), entry.getValue());
311+
}
312+
307313
// Extra JDBC properties
308314
for (Map.Entry<String, String> entry : dataSourceJdbcRuntimeConfig.additionalJdbcProperties().entrySet()) {
309315
connectionFactoryConfiguration.jdbcProperty(entry.getKey(), entry.getValue());
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package io.quarkus.agroal.spi;
2+
3+
import io.quarkus.builder.item.MultiBuildItem;
4+
5+
/**
6+
* A build item to represent a JDBC property to be set as an additional JDBC property when the datasource is setup.
7+
*/
8+
public final class JdbcPropertyBuildItem extends MultiBuildItem {
9+
10+
private String dataSourceName;
11+
private String propertyName;
12+
private String propertyValue;
13+
14+
public JdbcPropertyBuildItem(String dataSourceName, String propertyName, String propertyValue) {
15+
this.dataSourceName = dataSourceName;
16+
this.propertyName = propertyName;
17+
this.propertyValue = propertyValue;
18+
}
19+
20+
public String dataSourceName() {
21+
return dataSourceName;
22+
}
23+
24+
public String propertyName() {
25+
return propertyName;
26+
}
27+
28+
public String propertyValue() {
29+
return propertyValue;
30+
}
31+
}

extensions/jdbc/jdbc-mysql/deployment/src/main/java/io/quarkus/jdbc/mysql/deployment/JDBCMySQLProcessor.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
import com.mysql.cj.MysqlConnection;
1111
import com.mysql.cj.WarningListener;
12+
import com.mysql.cj.conf.PropertyDefinitions;
13+
import com.mysql.cj.conf.PropertyKey;
1214
import com.mysql.cj.conf.PropertySet;
1315
import com.mysql.cj.jdbc.JdbcConnection;
1416
import com.mysql.cj.jdbc.JdbcPreparedStatement;
@@ -19,7 +21,9 @@
1921
import com.mysql.cj.jdbc.result.ResultSetInternalMethods;
2022
import com.mysql.cj.protocol.Resultset;
2123

24+
import io.quarkus.agroal.deployment.AggregatedDataSourceBuildTimeConfigBuildItem;
2225
import io.quarkus.agroal.spi.JdbcDriverBuildItem;
26+
import io.quarkus.agroal.spi.JdbcPropertyBuildItem;
2327
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
2428
import io.quarkus.arc.processor.BuiltinScope;
2529
import io.quarkus.datasource.common.runtime.DatabaseKind;
@@ -129,4 +133,28 @@ void registerServiceBinding(Capabilities capabilities,
129133
}
130134
dbKind.produce(new DefaultDataSourceDbKindBuildItem(DatabaseKind.MYSQL));
131135
}
136+
137+
/**
138+
* Disable built-in OpenTelemetry support for MySQL JDBC driver when both
139+
* OpenTelemetry Tracer and Agroal capabilities are present.
140+
*/
141+
@BuildStep
142+
void disableBuiltInOpenTelemetry(BuildProducer<JdbcPropertyBuildItem> properties,
143+
List<AggregatedDataSourceBuildTimeConfigBuildItem> datasources,
144+
Capabilities capabilities) {
145+
// If OpenTelemetry Tracer or Agroal capabilities are missing, skip this step
146+
if (capabilities.isMissing(Capability.OPENTELEMETRY_TRACER) ||
147+
capabilities.isMissing(Capability.AGROAL)) {
148+
return;
149+
}
150+
for (AggregatedDataSourceBuildTimeConfigBuildItem ds : datasources) {
151+
if (ds.getDbKind().equals(DatabaseKind.MYSQL)) {
152+
// NOTE: Checking if the "quarkus.datasource.jdbc.telemetry=true" property is set doesn't work because
153+
// the driver checks for the OpenTelemetry classes in the classpath
154+
properties.produce(new JdbcPropertyBuildItem(ds.getName(),
155+
PropertyKey.openTelemetry.getKeyName(),
156+
PropertyDefinitions.OpenTelemetry.DISABLED.name()));
157+
}
158+
}
159+
}
132160
}

0 commit comments

Comments
 (0)