From b45b532d6c5fa4ab91d01e6cb51603286d46018b Mon Sep 17 00:00:00 2001 From: radovanradic Date: Tue, 15 Oct 2024 17:18:05 +0200 Subject: [PATCH 01/35] Investigation for setting connection client info --- data-connection-jdbc/build.gradle | 1 + .../jdbc/config/DataJdbcConfiguration.java | 21 +++++++- ...DefaultDataSourceConnectionOperations.java | 37 +++++++++++++- .../data/connection/ConnectionDefinition.java | 9 ++++ .../DefaultConnectionDefinition.java | 24 +++++---- .../connection/annotation/ClientInfo.java | 49 +++++++++++++++++++ .../interceptor/ConnectableInterceptor.java | 30 ++++++++++-- .../support/ConnectionClientInformation.java | 15 ++++++ .../data/jdbc/config/SchemaGenerator.java | 1 + .../data/jdbc/mapper/JdbcQueryStatement.java | 2 +- .../DefaultJdbcRepositoryOperations.java | 2 +- .../jdbc/AbstractJdbcMultitenancySpec.groovy | 2 +- .../jdbc/h2/H2DisabledDataSourceSpec.groovy | 2 +- .../PostgresDisabledDataSourceSpec.groovy | 2 +- 14 files changed, 176 insertions(+), 21 deletions(-) rename {data-jdbc/src/main/java/io/micronaut/data => data-connection-jdbc/src/main/java/io/micronaut/data/connection}/jdbc/config/DataJdbcConfiguration.java (88%) create mode 100644 data-connection/src/main/java/io/micronaut/data/connection/annotation/ClientInfo.java create mode 100644 data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientInformation.java diff --git a/data-connection-jdbc/build.gradle b/data-connection-jdbc/build.gradle index 7df18343c9b..9e89fdf778d 100644 --- a/data-connection-jdbc/build.gradle +++ b/data-connection-jdbc/build.gradle @@ -11,6 +11,7 @@ dependencies { annotationProcessor libs.micronaut.docs api projects.micronautDataConnection + api projects.micronautDataRuntime implementation mnSql.micronaut.jdbc implementation mn.micronaut.aop diff --git a/data-jdbc/src/main/java/io/micronaut/data/jdbc/config/DataJdbcConfiguration.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/config/DataJdbcConfiguration.java similarity index 88% rename from data-jdbc/src/main/java/io/micronaut/data/jdbc/config/DataJdbcConfiguration.java rename to data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/config/DataJdbcConfiguration.java index 819e9c7a390..60b66514e5d 100644 --- a/data-jdbc/src/main/java/io/micronaut/data/jdbc/config/DataJdbcConfiguration.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/config/DataJdbcConfiguration.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.micronaut.data.jdbc.config; +package io.micronaut.data.connection.jdbc.config; import io.micronaut.context.annotation.EachProperty; import io.micronaut.context.annotation.Parameter; @@ -55,6 +55,7 @@ public class DataJdbcConfiguration implements Named, Toggleable { */ private boolean allowConnectionPerOperation = true; private boolean enabled = true; + private boolean clientInfoTracing; /** * The configuration. @@ -190,4 +191,22 @@ public boolean isEnabled() { public void setEnabled(boolean enabled) { this.enabled = enabled; } + + /** + * Checks if client info tracing is enabled for this data source. + * + * @return true if client info tracing is enabled, false otherwise. + */ + public boolean isClientInfoTracing() { + return clientInfoTracing; + } + + /** + * Sets an indicator telling whether client info tracing is enabled for this data source. + * + * @param clientInfoTracing an indicator telling whether client info tracing is enabled + */ + public void setClientInfoTracing(boolean clientInfoTracing) { + this.clientInfoTracing = clientInfoTracing; + } } diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java index e54c24d7911..44ccc276088 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java @@ -16,20 +16,25 @@ package io.micronaut.data.connection.jdbc.operations; import io.micronaut.context.annotation.EachBean; +import io.micronaut.context.annotation.Parameter; import io.micronaut.core.annotation.Internal; import io.micronaut.data.connection.exceptions.ConnectionException; import io.micronaut.data.connection.jdbc.advice.DelegatingDataSource; +import io.micronaut.data.connection.jdbc.config.DataJdbcConfiguration; import io.micronaut.data.connection.jdbc.exceptions.CannotGetJdbcConnectionException; import io.micronaut.data.connection.ConnectionDefinition; import io.micronaut.data.connection.ConnectionStatus; import io.micronaut.data.connection.ConnectionSynchronization; import io.micronaut.data.connection.support.AbstractConnectionOperations; +import io.micronaut.data.connection.support.ConnectionClientInformation; import io.micronaut.data.connection.support.JdbcConnectionUtils; +import io.micronaut.data.model.query.builder.sql.Dialect; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.sql.DataSource; import java.sql.Connection; +import java.sql.SQLClientInfoException; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; @@ -46,9 +51,12 @@ public final class DefaultDataSourceConnectionOperations extends AbstractConnect private static final Logger LOG = LoggerFactory.getLogger(DefaultDataSourceConnectionOperations.class); private final DataSource dataSource; + private final DataJdbcConfiguration dataJdbcConfiguration; - DefaultDataSourceConnectionOperations(DataSource dataSource) { + DefaultDataSourceConnectionOperations(DataSource dataSource, + @Parameter DataJdbcConfiguration dataJdbcConfiguration) { this.dataSource = DelegatingDataSource.unwrapDataSource(dataSource); + this.dataJdbcConfiguration = dataJdbcConfiguration; } @Override @@ -62,7 +70,8 @@ protected Connection openConnection(ConnectionDefinition definition) { @Override protected void setupConnection(ConnectionStatus connectionStatus) { - connectionStatus.getDefinition().isReadOnly().ifPresent(readOnly -> { + ConnectionDefinition connectionDefinition = connectionStatus.getDefinition(); + connectionDefinition.isReadOnly().ifPresent(readOnly -> { List onCompleteCallbacks = new ArrayList<>(1); JdbcConnectionUtils.applyReadOnly(LOG, connectionStatus.getConnection(), readOnly, onCompleteCallbacks); if (!onCompleteCallbacks.isEmpty()) { @@ -76,6 +85,30 @@ public void executionComplete() { }); } }); + if (!dataJdbcConfiguration.isClientInfoTracing()) { + return; + } + if (dataJdbcConfiguration.getDialect() != Dialect.ORACLE) { + LOG.warn("Client info tracing is supported only for Oracle database connections."); + return; + } + ConnectionClientInformation connectionClientInformation = connectionDefinition.connectionClientInformation(); + if (connectionClientInformation == null) { + LOG.warn("ConnectionClientInformation not provided for the connection."); + return; + } + LOG.debug("Setting client info to the Oracle connection"); + Connection conn = connectionStatus.getConnection(); + + try { + if (connectionClientInformation.clientId() != null) { + conn.setClientInfo("OCSID.CLIENTID", connectionClientInformation.clientId()); + } + conn.setClientInfo("OCSID.MODULE", connectionClientInformation.module()); + conn.setClientInfo("OCSID.ACTION", connectionClientInformation.action()); + } catch (SQLClientInfoException e) { + LOG.warn("Failed to set client info", e); + } } @Override diff --git a/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java b/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java index 705f1965f67..a4d0b83000d 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java @@ -18,6 +18,7 @@ import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; +import io.micronaut.data.connection.support.ConnectionClientInformation; import java.time.Duration; import java.util.Optional; @@ -101,6 +102,14 @@ enum Propagation { @Nullable String getName(); + /** + * Returns the client information associated with this connection definition. + * If no client information has been set, this method will return null. + * + * @return An instance of {@link ConnectionClientInformation} representing the client information, or null if not set. + */ + @Nullable ConnectionClientInformation connectionClientInformation(); + /** * Connection definition with specific propagation. * @param propagation The new propagation diff --git a/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java b/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java index 3bfbd7a46f0..b656473106b 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java @@ -18,6 +18,7 @@ import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; +import io.micronaut.data.connection.support.ConnectionClientInformation; import java.time.Duration; import java.util.Optional; @@ -25,10 +26,11 @@ /** * Default implementation of the {@link ConnectionDefinition} interface. * - * @param name The connection name - * @param propagationBehavior The propagation behaviour - * @param timeout The timeout - * @param readOnlyValue The read only + * @param name The connection name + * @param propagationBehavior The propagation behaviour + * @param timeout The timeout + * @param readOnlyValue The read only + * @param connectionClientInformation The connection client information * @author Denis Stepanov * @since 4.0.0 */ @@ -37,19 +39,21 @@ public record DefaultConnectionDefinition( @Nullable String name, Propagation propagationBehavior, @Nullable Duration timeout, - Boolean readOnlyValue + Boolean readOnlyValue, + + @Nullable ConnectionClientInformation connectionClientInformation ) implements ConnectionDefinition { DefaultConnectionDefinition(String name) { - this(name, PROPAGATION_DEFAULT, null, null); + this(name, PROPAGATION_DEFAULT, null, null, null); } public DefaultConnectionDefinition(Propagation propagationBehaviour) { - this(null, propagationBehaviour, null, null); + this(null, propagationBehaviour, null, null, null); } public DefaultConnectionDefinition(String name, boolean readOnly) { - this(name, PROPAGATION_DEFAULT, null, readOnly); + this(name, PROPAGATION_DEFAULT, null, readOnly, null); } @Override @@ -76,12 +80,12 @@ public String getName() { @Override public ConnectionDefinition withPropagation(Propagation propagation) { - return new DefaultConnectionDefinition(name, propagation, timeout, readOnlyValue); + return new DefaultConnectionDefinition(name, propagation, timeout, readOnlyValue, connectionClientInformation); } @Override public ConnectionDefinition withName(String name) { - return new DefaultConnectionDefinition(name, propagationBehavior, timeout, readOnlyValue); + return new DefaultConnectionDefinition(name, propagationBehavior, timeout, readOnlyValue, connectionClientInformation); } } diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ClientInfo.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ClientInfo.java new file mode 100644 index 00000000000..dff2b024ce5 --- /dev/null +++ b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ClientInfo.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017-2024 original authors + * + * 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 + * + * https://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 io.micronaut.data.connection.annotation; + + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Defines query result for database query execution. Is query result type is TABULAR it means default query result + * and JSON will mean result will contain single column with the JSON value. + * + * @author radovanradic + * @since 4.10.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Documented +@Inherited +public @interface ClientInfo { + + /** + * @return The module name, if not provided then will use class name + */ + String module() default ""; + + /** + * @return The action name, if not provided then will use method name + */ + String action() default ""; + +} diff --git a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java index a8b1b102a1b..4fc62621a15 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java @@ -20,6 +20,7 @@ import io.micronaut.aop.InterceptorBean; import io.micronaut.aop.MethodInterceptor; import io.micronaut.aop.MethodInvocationContext; +import io.micronaut.context.annotation.Value; import io.micronaut.core.annotation.AnnotationValue; import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; @@ -29,10 +30,12 @@ import io.micronaut.data.connection.ConnectionOperations; import io.micronaut.data.connection.ConnectionOperationsRegistry; import io.micronaut.data.connection.DefaultConnectionDefinition; +import io.micronaut.data.connection.annotation.ClientInfo; import io.micronaut.data.connection.annotation.Connectable; import io.micronaut.data.connection.async.AsyncConnectionOperations; import io.micronaut.data.connection.reactive.ReactiveStreamsConnectionOperations; import io.micronaut.data.connection.reactive.ReactorConnectionOperations; +import io.micronaut.data.connection.support.ConnectionClientInformation; import io.micronaut.inject.ExecutableMethod; import jakarta.inject.Singleton; import reactor.core.publisher.Flux; @@ -63,6 +66,9 @@ public final class ConnectableInterceptor implements MethodInterceptor context) { final ConnectionInvocation connectionInvocation = connectionInvocationMap .computeIfAbsent(new TenantExecutableMethod(tenantDataSourceName, executableMethod), ignore -> { final String dataSource = tenantDataSourceName == null ? executableMethod.stringValue(Connectable.class).orElse(null) : tenantDataSourceName; - final ConnectionDefinition connectionDefinition = getConnectionDefinition(executableMethod); + final ConnectionDefinition connectionDefinition = getConnectionDefinition(executableMethod, appName); switch (interceptedMethod.resultType()) { case PUBLISHER -> { @@ -150,17 +158,33 @@ public Object intercept(MethodInvocationContext context) { } @NonNull - public static ConnectionDefinition getConnectionDefinition(ExecutableMethod executableMethod) { + public static ConnectionDefinition getConnectionDefinition(ExecutableMethod executableMethod, String appName) { AnnotationValue annotation = executableMethod.getAnnotation(Connectable.class); if (annotation == null) { throw new IllegalStateException("No declared @Connectable annotation present"); } + AnnotationValue clientInfoAnnotationValue = executableMethod.getAnnotation(ClientInfo.class); + String module = null; + String action = null; + if (clientInfoAnnotationValue != null) { + module = clientInfoAnnotationValue.stringValue("module").orElse(null); + action = clientInfoAnnotationValue.stringValue("action").orElse(null); + } + if (module == null) { + module = executableMethod.getDeclaringType().getName(); + } + if (action == null) { + action = executableMethod.getMethodName(); + } + ConnectionClientInformation connectionClientInformation = new ConnectionClientInformation(appName, module, action); + return new DefaultConnectionDefinition( executableMethod.getDeclaringType().getSimpleName() + "." + executableMethod.getMethodName(), annotation.enumValue("propagation", ConnectionDefinition.Propagation.class).orElse(ConnectionDefinition.PROPAGATION_DEFAULT), annotation.longValue("timeout").stream().mapToObj(Duration::ofSeconds).findFirst().orElse(null), - annotation.booleanValue("readOnly").orElse(null) + annotation.booleanValue("readOnly").orElse(null), + connectionClientInformation ); } diff --git a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientInformation.java b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientInformation.java new file mode 100644 index 00000000000..87cf991a110 --- /dev/null +++ b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientInformation.java @@ -0,0 +1,15 @@ +package io.micronaut.data.connection.support; + +import io.micronaut.core.annotation.Nullable; + +import java.util.Properties; + +/** + * The client information that can be used to set to {@link java.sql.Connection#setClientInfo(Properties)}. + * + * @param clientId The client ID + * @param module The module + * @param action The action + */ +public record ConnectionClientInformation(@Nullable String clientId, String module, String action) { +} diff --git a/data-jdbc/src/main/java/io/micronaut/data/jdbc/config/SchemaGenerator.java b/data-jdbc/src/main/java/io/micronaut/data/jdbc/config/SchemaGenerator.java index 2da5d4b8369..f302e85a3a3 100644 --- a/data-jdbc/src/main/java/io/micronaut/data/jdbc/config/SchemaGenerator.java +++ b/data-jdbc/src/main/java/io/micronaut/data/jdbc/config/SchemaGenerator.java @@ -26,6 +26,7 @@ import io.micronaut.core.util.CollectionUtils; import io.micronaut.data.annotation.JsonView; import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.connection.jdbc.config.DataJdbcConfiguration; import io.micronaut.data.exceptions.DataAccessException; import io.micronaut.data.jdbc.operations.JdbcSchemaHandler; import io.micronaut.data.model.PersistentEntity; diff --git a/data-jdbc/src/main/java/io/micronaut/data/jdbc/mapper/JdbcQueryStatement.java b/data-jdbc/src/main/java/io/micronaut/data/jdbc/mapper/JdbcQueryStatement.java index 494cffe7d97..444256b5ae4 100644 --- a/data-jdbc/src/main/java/io/micronaut/data/jdbc/mapper/JdbcQueryStatement.java +++ b/data-jdbc/src/main/java/io/micronaut/data/jdbc/mapper/JdbcQueryStatement.java @@ -19,7 +19,7 @@ import io.micronaut.core.annotation.NonNull; import io.micronaut.core.convert.ConversionService; import io.micronaut.data.exceptions.DataAccessException; -import io.micronaut.data.jdbc.config.DataJdbcConfiguration; +import io.micronaut.data.connection.jdbc.config.DataJdbcConfiguration; import io.micronaut.data.model.query.builder.sql.Dialect; import io.micronaut.data.runtime.convert.DataConversionService; import io.micronaut.data.runtime.mapper.QueryStatement; diff --git a/data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/DefaultJdbcRepositoryOperations.java b/data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/DefaultJdbcRepositoryOperations.java index 33e0f42d904..99cc5d935e5 100644 --- a/data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/DefaultJdbcRepositoryOperations.java +++ b/data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/DefaultJdbcRepositoryOperations.java @@ -32,7 +32,7 @@ import io.micronaut.data.connection.ConnectionOperations; import io.micronaut.data.connection.annotation.Connectable; import io.micronaut.data.exceptions.DataAccessException; -import io.micronaut.data.jdbc.config.DataJdbcConfiguration; +import io.micronaut.data.connection.jdbc.config.DataJdbcConfiguration; import io.micronaut.data.jdbc.convert.JdbcConversionContext; import io.micronaut.data.jdbc.mapper.ColumnIndexCallableResultReader; import io.micronaut.data.jdbc.mapper.ColumnIndexResultSetReader; diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/AbstractJdbcMultitenancySpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/AbstractJdbcMultitenancySpec.groovy index 4dd8dbd9291..e0b6f7d30fd 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/AbstractJdbcMultitenancySpec.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/AbstractJdbcMultitenancySpec.groovy @@ -17,7 +17,7 @@ package io.micronaut.data.jdbc import io.micronaut.context.ApplicationContext import io.micronaut.context.BeanContext -import io.micronaut.data.jdbc.config.DataJdbcConfiguration +import io.micronaut.data.connection.jdbc.config.DataJdbcConfiguration import io.micronaut.data.jdbc.operations.JdbcSchemaHandler import io.micronaut.data.tck.tests.AbstractMultitenancySpec import io.micronaut.inject.qualifiers.Qualifiers diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2DisabledDataSourceSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2DisabledDataSourceSpec.groovy index be67080c9fd..bd2178924ac 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2DisabledDataSourceSpec.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2DisabledDataSourceSpec.groovy @@ -3,7 +3,7 @@ package io.micronaut.data.jdbc.h2 import io.micronaut.context.ApplicationContext import io.micronaut.context.annotation.Property import io.micronaut.context.exceptions.NoSuchBeanException -import io.micronaut.data.jdbc.config.DataJdbcConfiguration +import io.micronaut.data.connection.jdbc.config.DataJdbcConfiguration import io.micronaut.test.extensions.spock.annotation.MicronautTest import jakarta.inject.Inject import spock.lang.Shared diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/postgres/PostgresDisabledDataSourceSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/postgres/PostgresDisabledDataSourceSpec.groovy index 20ebe66dd2e..444e587aaf2 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/postgres/PostgresDisabledDataSourceSpec.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/postgres/PostgresDisabledDataSourceSpec.groovy @@ -2,7 +2,7 @@ package io.micronaut.data.jdbc.postgres import io.micronaut.context.ApplicationContext import io.micronaut.context.exceptions.NoSuchBeanException -import io.micronaut.data.jdbc.config.DataJdbcConfiguration +import io.micronaut.data.connection.jdbc.config.DataJdbcConfiguration import io.micronaut.test.extensions.spock.annotation.MicronautTest import jakarta.inject.Inject import spock.lang.Shared From 19e58f0ad51d551aa7dc07cbdf0f8a240e0fb05c Mon Sep 17 00:00:00 2001 From: radovanradic Date: Wed, 16 Oct 2024 15:01:33 +0200 Subject: [PATCH 02/35] Use Connectable interface to pass connection client tracing info --- ...DefaultDataSourceConnectionOperations.java | 54 +++++++++++-------- .../data/connection/ConnectionDefinition.java | 10 ++-- .../DefaultConnectionDefinition.java | 10 ++-- .../connection/annotation/ClientInfo.java | 49 ----------------- .../connection/annotation/Connectable.java | 23 ++++++++ .../interceptor/ConnectableInterceptor.java | 48 +++++++++++------ .../support/ConnectionClientInformation.java | 15 ------ .../support/ConnectionClientTracingInfo.java | 29 ++++++++++ .../jdbc/config/DataJdbcConfiguration.java | 20 +------ .../data/jdbc/config/SchemaGenerator.java | 1 - .../data/jdbc/mapper/JdbcQueryStatement.java | 2 +- .../DefaultJdbcRepositoryOperations.java | 2 +- .../jdbc/AbstractJdbcMultitenancySpec.groovy | 2 +- .../jdbc/h2/H2DisabledDataSourceSpec.groovy | 2 +- .../PostgresDisabledDataSourceSpec.groovy | 2 +- 15 files changed, 131 insertions(+), 138 deletions(-) delete mode 100644 data-connection/src/main/java/io/micronaut/data/connection/annotation/ClientInfo.java delete mode 100644 data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientInformation.java create mode 100644 data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientTracingInfo.java rename {data-connection-jdbc/src/main/java/io/micronaut/data/connection => data-jdbc/src/main/java/io/micronaut/data}/jdbc/config/DataJdbcConfiguration.java (88%) diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java index 44ccc276088..8ca2030906d 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java @@ -16,19 +16,17 @@ package io.micronaut.data.connection.jdbc.operations; import io.micronaut.context.annotation.EachBean; -import io.micronaut.context.annotation.Parameter; import io.micronaut.core.annotation.Internal; +import io.micronaut.core.util.StringUtils; import io.micronaut.data.connection.exceptions.ConnectionException; import io.micronaut.data.connection.jdbc.advice.DelegatingDataSource; -import io.micronaut.data.connection.jdbc.config.DataJdbcConfiguration; import io.micronaut.data.connection.jdbc.exceptions.CannotGetJdbcConnectionException; import io.micronaut.data.connection.ConnectionDefinition; import io.micronaut.data.connection.ConnectionStatus; import io.micronaut.data.connection.ConnectionSynchronization; import io.micronaut.data.connection.support.AbstractConnectionOperations; -import io.micronaut.data.connection.support.ConnectionClientInformation; +import io.micronaut.data.connection.support.ConnectionClientTracingInfo; import io.micronaut.data.connection.support.JdbcConnectionUtils; -import io.micronaut.data.model.query.builder.sql.Dialect; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,14 +47,15 @@ @EachBean(DataSource.class) public final class DefaultDataSourceConnectionOperations extends AbstractConnectionOperations { + private static final String ORACLE_TRACE_CLIENTID = "OCSID.CLIENTID"; + private static final String ORACLE_TRACE_MODULE = "OCSID.MODULE"; + private static final String ORACLE_TRACE_ACTION = "OCSID.ACTION"; + private static final Logger LOG = LoggerFactory.getLogger(DefaultDataSourceConnectionOperations.class); private final DataSource dataSource; - private final DataJdbcConfiguration dataJdbcConfiguration; - DefaultDataSourceConnectionOperations(DataSource dataSource, - @Parameter DataJdbcConfiguration dataJdbcConfiguration) { + DefaultDataSourceConnectionOperations(DataSource dataSource) { this.dataSource = DelegatingDataSource.unwrapDataSource(dataSource); - this.dataJdbcConfiguration = dataJdbcConfiguration; } @Override @@ -85,29 +84,25 @@ public void executionComplete() { }); } }); - if (!dataJdbcConfiguration.isClientInfoTracing()) { - return; - } - if (dataJdbcConfiguration.getDialect() != Dialect.ORACLE) { - LOG.warn("Client info tracing is supported only for Oracle database connections."); - return; - } - ConnectionClientInformation connectionClientInformation = connectionDefinition.connectionClientInformation(); + ConnectionClientTracingInfo connectionClientInformation = connectionDefinition.connectionClientTracingInfo(); if (connectionClientInformation == null) { - LOG.warn("ConnectionClientInformation not provided for the connection."); return; } - LOG.debug("Setting client info to the Oracle connection"); Connection conn = connectionStatus.getConnection(); + if (!isOracleConnection(conn)) { + LOG.debug("Client tracing info is supported only for Oracle database connections."); + return; + } + LOG.trace("Setting connection client tracing info to the Oracle connection"); try { if (connectionClientInformation.clientId() != null) { - conn.setClientInfo("OCSID.CLIENTID", connectionClientInformation.clientId()); + conn.setClientInfo(ORACLE_TRACE_CLIENTID, connectionClientInformation.clientId()); } - conn.setClientInfo("OCSID.MODULE", connectionClientInformation.module()); - conn.setClientInfo("OCSID.ACTION", connectionClientInformation.action()); + conn.setClientInfo(ORACLE_TRACE_MODULE, connectionClientInformation.module()); + conn.setClientInfo(ORACLE_TRACE_ACTION, connectionClientInformation.action()); } catch (SQLClientInfoException e) { - LOG.warn("Failed to set client info", e); + LOG.debug("Failed to set connection client tracing info", e); } } @@ -120,4 +115,19 @@ protected void closeConnection(ConnectionStatus connectionStatus) { } } + /** + * Checks whether current connection is Oracle database connection. + * + * @param connection The connection + * @return true if current connection is Oracle database connection + */ + private boolean isOracleConnection(Connection connection) { + try { + String databaseProductName = connection.getMetaData().getDatabaseProductName(); + return StringUtils.isNotEmpty(databaseProductName) && databaseProductName.toUpperCase().contains("ORACLE"); + } catch (SQLException e) { + LOG.debug("Failed to get database product name from the connection", e); + return false; + } + } } diff --git a/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java b/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java index a4d0b83000d..a73a721995c 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java @@ -18,7 +18,7 @@ import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; -import io.micronaut.data.connection.support.ConnectionClientInformation; +import io.micronaut.data.connection.support.ConnectionClientTracingInfo; import java.time.Duration; import java.util.Optional; @@ -103,12 +103,12 @@ enum Propagation { String getName(); /** - * Returns the client information associated with this connection definition. - * If no client information has been set, this method will return null. + * Returns the connection client tracing information associated with this connection definition. + * If no connection client tracing information has been set, this method will return null. * - * @return An instance of {@link ConnectionClientInformation} representing the client information, or null if not set. + * @return An instance of {@link ConnectionClientTracingInfo} representing the client tracing information, or null if not set. */ - @Nullable ConnectionClientInformation connectionClientInformation(); + @Nullable ConnectionClientTracingInfo connectionClientTracingInfo(); /** * Connection definition with specific propagation. diff --git a/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java b/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java index b656473106b..c12bb4dd61b 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java @@ -18,7 +18,7 @@ import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; -import io.micronaut.data.connection.support.ConnectionClientInformation; +import io.micronaut.data.connection.support.ConnectionClientTracingInfo; import java.time.Duration; import java.util.Optional; @@ -30,7 +30,7 @@ * @param propagationBehavior The propagation behaviour * @param timeout The timeout * @param readOnlyValue The read only - * @param connectionClientInformation The connection client information + * @param connectionClientTracingInfo The connection client information * @author Denis Stepanov * @since 4.0.0 */ @@ -41,7 +41,7 @@ public record DefaultConnectionDefinition( @Nullable Duration timeout, Boolean readOnlyValue, - @Nullable ConnectionClientInformation connectionClientInformation + @Nullable ConnectionClientTracingInfo connectionClientTracingInfo ) implements ConnectionDefinition { DefaultConnectionDefinition(String name) { @@ -80,12 +80,12 @@ public String getName() { @Override public ConnectionDefinition withPropagation(Propagation propagation) { - return new DefaultConnectionDefinition(name, propagation, timeout, readOnlyValue, connectionClientInformation); + return new DefaultConnectionDefinition(name, propagation, timeout, readOnlyValue, connectionClientTracingInfo); } @Override public ConnectionDefinition withName(String name) { - return new DefaultConnectionDefinition(name, propagationBehavior, timeout, readOnlyValue, connectionClientInformation); + return new DefaultConnectionDefinition(name, propagationBehavior, timeout, readOnlyValue, connectionClientTracingInfo); } } diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ClientInfo.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ClientInfo.java deleted file mode 100644 index dff2b024ce5..00000000000 --- a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ClientInfo.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2017-2024 original authors - * - * 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 - * - * https://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 io.micronaut.data.connection.annotation; - - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Defines query result for database query execution. Is query result type is TABULAR it means default query result - * and JSON will mean result will contain single column with the JSON value. - * - * @author radovanradic - * @since 4.10.0 - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE, ElementType.METHOD}) -@Documented -@Inherited -public @interface ClientInfo { - - /** - * @return The module name, if not provided then will use class name - */ - String module() default ""; - - /** - * @return The action name, if not provided then will use method name - */ - String action() default ""; - -} diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/Connectable.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/Connectable.java index 7c03b25474e..6701246fda1 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/annotation/Connectable.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/annotation/Connectable.java @@ -83,4 +83,27 @@ */ boolean readOnly() default false; + /** + * If true, then when connection is established {@link java.sql.Connection#setClientInfo(String, String)} will be called + * if it is connected to the Oracle database. It will issue calls to set MODULE, ACTION and CLIENT_IDENTIFIER. + * + * @return whether connection should trace/set client info + */ + boolean traceClientInfo() default false; + + /** + * The module name for tracing if {@link #traceClientInfo()} is true. + * If not provided, then it will fall back to the name of the class currently being intercepted in {@link io.micronaut.data.connection.interceptor.ConnectableInterceptor}. + * + * @return the custom module name for tracing + */ + String tracingModule() default ""; + + /** + * The action name for tracing if {@link #traceClientInfo()} is true. + * If not provided, then it will fall back to the name of the method currently being intercepted in {@link io.micronaut.data.connection.interceptor.ConnectableInterceptor}. + * + * @return the custom module name for tracing + */ + String tracingAction() default ""; } diff --git a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java index 4fc62621a15..4c7d82c802d 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java @@ -30,12 +30,11 @@ import io.micronaut.data.connection.ConnectionOperations; import io.micronaut.data.connection.ConnectionOperationsRegistry; import io.micronaut.data.connection.DefaultConnectionDefinition; -import io.micronaut.data.connection.annotation.ClientInfo; import io.micronaut.data.connection.annotation.Connectable; import io.micronaut.data.connection.async.AsyncConnectionOperations; import io.micronaut.data.connection.reactive.ReactiveStreamsConnectionOperations; import io.micronaut.data.connection.reactive.ReactorConnectionOperations; -import io.micronaut.data.connection.support.ConnectionClientInformation; +import io.micronaut.data.connection.support.ConnectionClientTracingInfo; import io.micronaut.inject.ExecutableMethod; import jakarta.inject.Singleton; import reactor.core.publisher.Flux; @@ -57,6 +56,10 @@ @InterceptorBean(Connectable.class) public final class ConnectableInterceptor implements MethodInterceptor { + private static final String TRACE_CLIENT_INFO_MEMBER = "traceClientInfo"; + private static final String TRACING_MODULE_MEMBER = "tracingModule"; + private static final String TRACING_ACTION_MEMBER = "tracingAction"; + private final Map connectionInvocationMap = new ConcurrentHashMap<>(30); @NonNull @@ -164,21 +167,7 @@ public static ConnectionDefinition getConnectionDefinition(ExecutableMethod clientInfoAnnotationValue = executableMethod.getAnnotation(ClientInfo.class); - String module = null; - String action = null; - if (clientInfoAnnotationValue != null) { - module = clientInfoAnnotationValue.stringValue("module").orElse(null); - action = clientInfoAnnotationValue.stringValue("action").orElse(null); - } - if (module == null) { - module = executableMethod.getDeclaringType().getName(); - } - if (action == null) { - action = executableMethod.getMethodName(); - } - ConnectionClientInformation connectionClientInformation = new ConnectionClientInformation(appName, module, action); - + ConnectionClientTracingInfo connectionClientInformation = getConnectionClientTracingInfo(annotation, executableMethod, appName); return new DefaultConnectionDefinition( executableMethod.getDeclaringType().getSimpleName() + "." + executableMethod.getMethodName(), annotation.enumValue("propagation", ConnectionDefinition.Propagation.class).orElse(ConnectionDefinition.PROPAGATION_DEFAULT), @@ -188,6 +177,31 @@ public static ConnectionDefinition getConnectionDefinition(ExecutableMethod annotation, + ExecutableMethod executableMethod, + String appName) { + boolean traceClientInfo = annotation.booleanValue(TRACE_CLIENT_INFO_MEMBER).orElse(false); + if (!traceClientInfo) { + return null; + } + String module = annotation.stringValue(TRACING_MODULE_MEMBER).orElse(null); + String action = annotation.stringValue(TRACING_ACTION_MEMBER).orElse(null); + if (module == null) { + module = executableMethod.getDeclaringType().getName(); + } + if (action == null) { + action = executableMethod.getMethodName(); + } + return new ConnectionClientTracingInfo(appName, module, action); + } + /** * Cached invocation associating a method with a definition a connection manager. * diff --git a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientInformation.java b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientInformation.java deleted file mode 100644 index 87cf991a110..00000000000 --- a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientInformation.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.micronaut.data.connection.support; - -import io.micronaut.core.annotation.Nullable; - -import java.util.Properties; - -/** - * The client information that can be used to set to {@link java.sql.Connection#setClientInfo(Properties)}. - * - * @param clientId The client ID - * @param module The module - * @param action The action - */ -public record ConnectionClientInformation(@Nullable String clientId, String module, String action) { -} diff --git a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientTracingInfo.java b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientTracingInfo.java new file mode 100644 index 00000000000..b59298a0910 --- /dev/null +++ b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientTracingInfo.java @@ -0,0 +1,29 @@ +/* + * Copyright 2017-2024 original authors + * + * 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 + * + * https://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 io.micronaut.data.connection.support; + +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.annotation.Nullable; + +/** + * The client information that can be used to set to {@link java.sql.Connection#setClientInfo(String, String)} )}. + * + * @param clientId The client ID + * @param module The module + * @param action The action + */ +public record ConnectionClientTracingInfo(@Nullable String clientId, @NonNull String module, @NonNull String action) { +} diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/config/DataJdbcConfiguration.java b/data-jdbc/src/main/java/io/micronaut/data/jdbc/config/DataJdbcConfiguration.java similarity index 88% rename from data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/config/DataJdbcConfiguration.java rename to data-jdbc/src/main/java/io/micronaut/data/jdbc/config/DataJdbcConfiguration.java index 60b66514e5d..8e03ef2c732 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/config/DataJdbcConfiguration.java +++ b/data-jdbc/src/main/java/io/micronaut/data/jdbc/config/DataJdbcConfiguration.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.micronaut.data.connection.jdbc.config; +package io.micronaut.data.jdbc.config; import io.micronaut.context.annotation.EachProperty; import io.micronaut.context.annotation.Parameter; @@ -55,7 +55,6 @@ public class DataJdbcConfiguration implements Named, Toggleable { */ private boolean allowConnectionPerOperation = true; private boolean enabled = true; - private boolean clientInfoTracing; /** * The configuration. @@ -192,21 +191,4 @@ public void setEnabled(boolean enabled) { this.enabled = enabled; } - /** - * Checks if client info tracing is enabled for this data source. - * - * @return true if client info tracing is enabled, false otherwise. - */ - public boolean isClientInfoTracing() { - return clientInfoTracing; - } - - /** - * Sets an indicator telling whether client info tracing is enabled for this data source. - * - * @param clientInfoTracing an indicator telling whether client info tracing is enabled - */ - public void setClientInfoTracing(boolean clientInfoTracing) { - this.clientInfoTracing = clientInfoTracing; - } } diff --git a/data-jdbc/src/main/java/io/micronaut/data/jdbc/config/SchemaGenerator.java b/data-jdbc/src/main/java/io/micronaut/data/jdbc/config/SchemaGenerator.java index f302e85a3a3..2da5d4b8369 100644 --- a/data-jdbc/src/main/java/io/micronaut/data/jdbc/config/SchemaGenerator.java +++ b/data-jdbc/src/main/java/io/micronaut/data/jdbc/config/SchemaGenerator.java @@ -26,7 +26,6 @@ import io.micronaut.core.util.CollectionUtils; import io.micronaut.data.annotation.JsonView; import io.micronaut.data.annotation.MappedEntity; -import io.micronaut.data.connection.jdbc.config.DataJdbcConfiguration; import io.micronaut.data.exceptions.DataAccessException; import io.micronaut.data.jdbc.operations.JdbcSchemaHandler; import io.micronaut.data.model.PersistentEntity; diff --git a/data-jdbc/src/main/java/io/micronaut/data/jdbc/mapper/JdbcQueryStatement.java b/data-jdbc/src/main/java/io/micronaut/data/jdbc/mapper/JdbcQueryStatement.java index 444256b5ae4..494cffe7d97 100644 --- a/data-jdbc/src/main/java/io/micronaut/data/jdbc/mapper/JdbcQueryStatement.java +++ b/data-jdbc/src/main/java/io/micronaut/data/jdbc/mapper/JdbcQueryStatement.java @@ -19,7 +19,7 @@ import io.micronaut.core.annotation.NonNull; import io.micronaut.core.convert.ConversionService; import io.micronaut.data.exceptions.DataAccessException; -import io.micronaut.data.connection.jdbc.config.DataJdbcConfiguration; +import io.micronaut.data.jdbc.config.DataJdbcConfiguration; import io.micronaut.data.model.query.builder.sql.Dialect; import io.micronaut.data.runtime.convert.DataConversionService; import io.micronaut.data.runtime.mapper.QueryStatement; diff --git a/data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/DefaultJdbcRepositoryOperations.java b/data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/DefaultJdbcRepositoryOperations.java index 99cc5d935e5..33e0f42d904 100644 --- a/data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/DefaultJdbcRepositoryOperations.java +++ b/data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/DefaultJdbcRepositoryOperations.java @@ -32,7 +32,7 @@ import io.micronaut.data.connection.ConnectionOperations; import io.micronaut.data.connection.annotation.Connectable; import io.micronaut.data.exceptions.DataAccessException; -import io.micronaut.data.connection.jdbc.config.DataJdbcConfiguration; +import io.micronaut.data.jdbc.config.DataJdbcConfiguration; import io.micronaut.data.jdbc.convert.JdbcConversionContext; import io.micronaut.data.jdbc.mapper.ColumnIndexCallableResultReader; import io.micronaut.data.jdbc.mapper.ColumnIndexResultSetReader; diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/AbstractJdbcMultitenancySpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/AbstractJdbcMultitenancySpec.groovy index e0b6f7d30fd..4dd8dbd9291 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/AbstractJdbcMultitenancySpec.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/AbstractJdbcMultitenancySpec.groovy @@ -17,7 +17,7 @@ package io.micronaut.data.jdbc import io.micronaut.context.ApplicationContext import io.micronaut.context.BeanContext -import io.micronaut.data.connection.jdbc.config.DataJdbcConfiguration +import io.micronaut.data.jdbc.config.DataJdbcConfiguration import io.micronaut.data.jdbc.operations.JdbcSchemaHandler import io.micronaut.data.tck.tests.AbstractMultitenancySpec import io.micronaut.inject.qualifiers.Qualifiers diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2DisabledDataSourceSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2DisabledDataSourceSpec.groovy index bd2178924ac..be67080c9fd 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2DisabledDataSourceSpec.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/H2DisabledDataSourceSpec.groovy @@ -3,7 +3,7 @@ package io.micronaut.data.jdbc.h2 import io.micronaut.context.ApplicationContext import io.micronaut.context.annotation.Property import io.micronaut.context.exceptions.NoSuchBeanException -import io.micronaut.data.connection.jdbc.config.DataJdbcConfiguration +import io.micronaut.data.jdbc.config.DataJdbcConfiguration import io.micronaut.test.extensions.spock.annotation.MicronautTest import jakarta.inject.Inject import spock.lang.Shared diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/postgres/PostgresDisabledDataSourceSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/postgres/PostgresDisabledDataSourceSpec.groovy index 444e587aaf2..20ebe66dd2e 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/postgres/PostgresDisabledDataSourceSpec.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/postgres/PostgresDisabledDataSourceSpec.groovy @@ -2,7 +2,7 @@ package io.micronaut.data.jdbc.postgres import io.micronaut.context.ApplicationContext import io.micronaut.context.exceptions.NoSuchBeanException -import io.micronaut.data.connection.jdbc.config.DataJdbcConfiguration +import io.micronaut.data.jdbc.config.DataJdbcConfiguration import io.micronaut.test.extensions.spock.annotation.MicronautTest import jakarta.inject.Inject import spock.lang.Shared From 92902bdff9818c319dc824be5d0bccc99ac31d60 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Wed, 16 Oct 2024 15:05:22 +0200 Subject: [PATCH 03/35] Remove unneeded changes --- data-connection-jdbc/build.gradle | 1 - .../io/micronaut/data/jdbc/config/DataJdbcConfiguration.java | 1 - 2 files changed, 2 deletions(-) diff --git a/data-connection-jdbc/build.gradle b/data-connection-jdbc/build.gradle index 9e89fdf778d..7df18343c9b 100644 --- a/data-connection-jdbc/build.gradle +++ b/data-connection-jdbc/build.gradle @@ -11,7 +11,6 @@ dependencies { annotationProcessor libs.micronaut.docs api projects.micronautDataConnection - api projects.micronautDataRuntime implementation mnSql.micronaut.jdbc implementation mn.micronaut.aop diff --git a/data-jdbc/src/main/java/io/micronaut/data/jdbc/config/DataJdbcConfiguration.java b/data-jdbc/src/main/java/io/micronaut/data/jdbc/config/DataJdbcConfiguration.java index 8e03ef2c732..819e9c7a390 100644 --- a/data-jdbc/src/main/java/io/micronaut/data/jdbc/config/DataJdbcConfiguration.java +++ b/data-jdbc/src/main/java/io/micronaut/data/jdbc/config/DataJdbcConfiguration.java @@ -190,5 +190,4 @@ public boolean isEnabled() { public void setEnabled(boolean enabled) { this.enabled = enabled; } - } From afb396d6089b9a65673e8319f0704c4bcbd2572a Mon Sep 17 00:00:00 2001 From: radovanradic Date: Wed, 16 Oct 2024 16:33:50 +0200 Subject: [PATCH 04/35] Use connectable for Oracle Book repo --- .../data/jdbc/oraclexe/OracleXEBookRepository.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java index 83be0b5b6f2..d58a4ee7aab 100644 --- a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java +++ b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java @@ -15,11 +15,13 @@ */ package io.micronaut.data.jdbc.oraclexe; +import io.micronaut.core.annotation.NonNull; import io.micronaut.data.annotation.Expandable; import io.micronaut.data.annotation.Id; import io.micronaut.data.annotation.Query; import io.micronaut.data.annotation.TypeDef; import io.micronaut.data.annotation.sql.Procedure; +import io.micronaut.data.connection.annotation.Connectable; import io.micronaut.data.jdbc.annotation.JdbcRepository; import io.micronaut.data.model.DataType; import io.micronaut.data.model.query.builder.sql.Dialect; @@ -31,6 +33,7 @@ import java.util.List; @JdbcRepository(dialect = Dialect.ORACLE) +@Connectable(traceClientInfo = true, tracingModule = "BOOKS") public abstract class OracleXEBookRepository extends BookRepository { public OracleXEBookRepository(OracleXEAuthorRepository authorRepository) { super(authorRepository); @@ -45,6 +48,7 @@ public OracleXEBookRepository(OracleXEAuthorRepository authorRepository) { @Override @Query(value = "select * from book b where b.title = ANY (:arg0)", nativeQuery = true) + @Connectable(traceClientInfo = false) public abstract List listNativeBooksWithTitleAnyArray(@Expandable @TypeDef(type = DataType.STRING) @Nullable String[] arg0); @Procedure @@ -53,7 +57,11 @@ public OracleXEBookRepository(OracleXEAuthorRepository authorRepository) { @Procedure("add1") public abstract int add1Aliased(int input); -// public abstract Book updateReturning(Book book); + @Override + @Connectable(traceClientInfo = true, tracingAction = "INSERT") + public abstract @NonNull Book save(@NonNull Book book); + + // public abstract Book updateReturning(Book book); // // public abstract String updateReturningTitle(Book book); // From 27bdc07ff9634e01ca86918ee76def02a493dc07 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Thu, 17 Oct 2024 14:19:36 +0200 Subject: [PATCH 05/35] Documentation, comments and code cleanup. --- .../DefaultDataSourceConnectionOperations.java | 4 ++-- .../data/connection/DefaultConnectionDefinition.java | 2 +- .../data/connection/annotation/Connectable.java | 2 ++ .../support/ConnectionClientTracingInfo.java | 7 ++++--- src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc | 12 ++++++++++++ 5 files changed, 21 insertions(+), 6 deletions(-) diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java index 8ca2030906d..55dacfe1a9a 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java @@ -96,8 +96,8 @@ public void executionComplete() { LOG.trace("Setting connection client tracing info to the Oracle connection"); try { - if (connectionClientInformation.clientId() != null) { - conn.setClientInfo(ORACLE_TRACE_CLIENTID, connectionClientInformation.clientId()); + if (connectionClientInformation.appName() != null) { + conn.setClientInfo(ORACLE_TRACE_CLIENTID, connectionClientInformation.appName()); } conn.setClientInfo(ORACLE_TRACE_MODULE, connectionClientInformation.module()); conn.setClientInfo(ORACLE_TRACE_ACTION, connectionClientInformation.action()); diff --git a/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java b/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java index c12bb4dd61b..fda16502ac2 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java @@ -30,7 +30,7 @@ * @param propagationBehavior The propagation behaviour * @param timeout The timeout * @param readOnlyValue The read only - * @param connectionClientTracingInfo The connection client information + * @param connectionClientTracingInfo The connection client tracing info, can be null * @author Denis Stepanov * @since 4.0.0 */ diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/Connectable.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/Connectable.java index 6701246fda1..f4b7745e7a5 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/annotation/Connectable.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/annotation/Connectable.java @@ -94,6 +94,7 @@ /** * The module name for tracing if {@link #traceClientInfo()} is true. * If not provided, then it will fall back to the name of the class currently being intercepted in {@link io.micronaut.data.connection.interceptor.ConnectableInterceptor}. + * Currently supported only for Oracle database connections. * * @return the custom module name for tracing */ @@ -102,6 +103,7 @@ /** * The action name for tracing if {@link #traceClientInfo()} is true. * If not provided, then it will fall back to the name of the method currently being intercepted in {@link io.micronaut.data.connection.interceptor.ConnectableInterceptor}. + * Currently supported only for Oracle database connections. * * @return the custom module name for tracing */ diff --git a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientTracingInfo.java b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientTracingInfo.java index b59298a0910..a0ad011ece6 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientTracingInfo.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientTracingInfo.java @@ -19,11 +19,12 @@ import io.micronaut.core.annotation.Nullable; /** - * The client information that can be used to set to {@link java.sql.Connection#setClientInfo(String, String)} )}. + * The client information that can be used to set to {@link java.sql.Connection#setClientInfo(String, String)}. + * Currently used only for Oracle database connections. * - * @param clientId The client ID + * @param appName The app name corresponding to the micronaut.application.name config value and can be null * @param module The module * @param action The action */ -public record ConnectionClientTracingInfo(@Nullable String clientId, @NonNull String module, @NonNull String action) { +public record ConnectionClientTracingInfo(@Nullable String appName, @NonNull String module, @NonNull String action) { } diff --git a/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc b/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc index 8b5a2299ecc..103bd353c07 100644 --- a/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc +++ b/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc @@ -61,5 +61,17 @@ As seen in the configuration above you should also configure the dialect. Althou IMPORTANT: The dialect setting in configuration does *not* replace the need to ensure the correct dialect is set at the repository. If the dialect is H2 in configuration, the repository should have `@JdbcRepository(dialect = Dialect.H2)` / `@R2dbcRepository(dialect = Dialect.H2)`. Because repositories are computed at compile time, the configuration value is not known at that time. +=== Connection client info tracing (Oracle) + +In order to trace SQL calls using `java.sql.Connection.setClientInfo(String, String)` method, you can +annotate repository with @api:data.connection.annotation.Connectable[] annotation. If boolean field `traceClientInfo` is not set to true, then Micronaut will not issue `setClientInfo` calls to the JDBC connection. +Fields in given annotation `tracingModule` and `tracingAction` can set custom module and action. If omitted, then module value will default +to the class name (usually Micronaut Data repository class) annotated with the given annotation and action to the name of the method +being executed. Annotation can be used on class or method, customizing module or action. +These values will be set to the JDBC connection using `setClientInfo("OCSID.MODULE", module)` and `setClientInfo("OCSID.ACTION", action)` respectively. +Additionally, if `${micronaut.application.name}` is set, then it will be used to call JDBC connection `setClientInfo("OCSID.CLIENTID", appName)`. + +Please note this is currently supported only for Oracle database connections. + TIP: See the guide for https://guides.micronaut.io/latest/micronaut-data-jdbc-repository.html[Access a Database with Micronaut Data JDBC] to learn more. From 7874bdf1e5606c160c5fa2efdab53db3f3c3970a Mon Sep 17 00:00:00 2001 From: radovanradic Date: Thu, 17 Oct 2024 14:45:53 +0200 Subject: [PATCH 06/35] Rename ConnectionClientTracingInfo to ConnectionTracingInfo --- ...DefaultDataSourceConnectionOperations.java | 20 ++++++++-------- .../data/connection/ConnectionDefinition.java | 10 ++++---- .../DefaultConnectionDefinition.java | 18 +++++++-------- .../interceptor/ConnectableInterceptor.java | 23 ++++++++++--------- ...ngInfo.java => ConnectionTracingInfo.java} | 4 ++-- 5 files changed, 38 insertions(+), 37 deletions(-) rename data-connection/src/main/java/io/micronaut/data/connection/support/{ConnectionClientTracingInfo.java => ConnectionTracingInfo.java} (80%) diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java index 55dacfe1a9a..f7380eb3c6a 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java @@ -25,7 +25,7 @@ import io.micronaut.data.connection.ConnectionStatus; import io.micronaut.data.connection.ConnectionSynchronization; import io.micronaut.data.connection.support.AbstractConnectionOperations; -import io.micronaut.data.connection.support.ConnectionClientTracingInfo; +import io.micronaut.data.connection.support.ConnectionTracingInfo; import io.micronaut.data.connection.support.JdbcConnectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -84,25 +84,25 @@ public void executionComplete() { }); } }); - ConnectionClientTracingInfo connectionClientInformation = connectionDefinition.connectionClientTracingInfo(); - if (connectionClientInformation == null) { + ConnectionTracingInfo connectionTracingInfo = connectionDefinition.connectionTracingInfo(); + if (connectionTracingInfo == null) { return; } Connection conn = connectionStatus.getConnection(); if (!isOracleConnection(conn)) { - LOG.debug("Client tracing info is supported only for Oracle database connections."); + LOG.debug("Connection tracing info is supported only for Oracle database connections."); return; } - LOG.trace("Setting connection client tracing info to the Oracle connection"); + LOG.trace("Setting connection tracing info to the Oracle connection"); try { - if (connectionClientInformation.appName() != null) { - conn.setClientInfo(ORACLE_TRACE_CLIENTID, connectionClientInformation.appName()); + if (connectionTracingInfo.appName() != null) { + conn.setClientInfo(ORACLE_TRACE_CLIENTID, connectionTracingInfo.appName()); } - conn.setClientInfo(ORACLE_TRACE_MODULE, connectionClientInformation.module()); - conn.setClientInfo(ORACLE_TRACE_ACTION, connectionClientInformation.action()); + conn.setClientInfo(ORACLE_TRACE_MODULE, connectionTracingInfo.module()); + conn.setClientInfo(ORACLE_TRACE_ACTION, connectionTracingInfo.action()); } catch (SQLClientInfoException e) { - LOG.debug("Failed to set connection client tracing info", e); + LOG.debug("Failed to set connection tracing info", e); } } diff --git a/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java b/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java index a73a721995c..00c58bc80be 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java @@ -18,7 +18,7 @@ import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; -import io.micronaut.data.connection.support.ConnectionClientTracingInfo; +import io.micronaut.data.connection.support.ConnectionTracingInfo; import java.time.Duration; import java.util.Optional; @@ -103,12 +103,12 @@ enum Propagation { String getName(); /** - * Returns the connection client tracing information associated with this connection definition. - * If no connection client tracing information has been set, this method will return null. + * Returns the connection tracing information associated with this connection definition. + * If no connection tracing information has been set, this method will return null. * - * @return An instance of {@link ConnectionClientTracingInfo} representing the client tracing information, or null if not set. + * @return An instance of {@link ConnectionTracingInfo} representing the connection tracing information, or null if not set. */ - @Nullable ConnectionClientTracingInfo connectionClientTracingInfo(); + @Nullable ConnectionTracingInfo connectionTracingInfo(); /** * Connection definition with specific propagation. diff --git a/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java b/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java index fda16502ac2..d504cf6c716 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java @@ -18,7 +18,7 @@ import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; -import io.micronaut.data.connection.support.ConnectionClientTracingInfo; +import io.micronaut.data.connection.support.ConnectionTracingInfo; import java.time.Duration; import java.util.Optional; @@ -26,11 +26,11 @@ /** * Default implementation of the {@link ConnectionDefinition} interface. * - * @param name The connection name - * @param propagationBehavior The propagation behaviour - * @param timeout The timeout - * @param readOnlyValue The read only - * @param connectionClientTracingInfo The connection client tracing info, can be null + * @param name The connection name + * @param propagationBehavior The propagation behaviour + * @param timeout The timeout + * @param readOnlyValue The read only + * @param connectionTracingInfo The connection client tracing info, can be null * @author Denis Stepanov * @since 4.0.0 */ @@ -41,7 +41,7 @@ public record DefaultConnectionDefinition( @Nullable Duration timeout, Boolean readOnlyValue, - @Nullable ConnectionClientTracingInfo connectionClientTracingInfo + @Nullable ConnectionTracingInfo connectionTracingInfo ) implements ConnectionDefinition { DefaultConnectionDefinition(String name) { @@ -80,12 +80,12 @@ public String getName() { @Override public ConnectionDefinition withPropagation(Propagation propagation) { - return new DefaultConnectionDefinition(name, propagation, timeout, readOnlyValue, connectionClientTracingInfo); + return new DefaultConnectionDefinition(name, propagation, timeout, readOnlyValue, connectionTracingInfo); } @Override public ConnectionDefinition withName(String name) { - return new DefaultConnectionDefinition(name, propagationBehavior, timeout, readOnlyValue, connectionClientTracingInfo); + return new DefaultConnectionDefinition(name, propagationBehavior, timeout, readOnlyValue, connectionTracingInfo); } } diff --git a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java index 4c7d82c802d..7f0148926f5 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java @@ -34,7 +34,7 @@ import io.micronaut.data.connection.async.AsyncConnectionOperations; import io.micronaut.data.connection.reactive.ReactiveStreamsConnectionOperations; import io.micronaut.data.connection.reactive.ReactorConnectionOperations; -import io.micronaut.data.connection.support.ConnectionClientTracingInfo; +import io.micronaut.data.connection.support.ConnectionTracingInfo; import io.micronaut.inject.ExecutableMethod; import jakarta.inject.Singleton; import reactor.core.publisher.Flux; @@ -167,26 +167,27 @@ public static ConnectionDefinition getConnectionDefinition(ExecutableMethod annotation, - ExecutableMethod executableMethod, - String appName) { + private static ConnectionTracingInfo getConnectionClientTracingInfo(AnnotationValue annotation, + ExecutableMethod executableMethod, + String appName) { boolean traceClientInfo = annotation.booleanValue(TRACE_CLIENT_INFO_MEMBER).orElse(false); if (!traceClientInfo) { return null; @@ -199,7 +200,7 @@ private static ConnectionClientTracingInfo getConnectionClientTracingInfo(Annota if (action == null) { action = executableMethod.getMethodName(); } - return new ConnectionClientTracingInfo(appName, module, action); + return new ConnectionTracingInfo(appName, module, action); } /** diff --git a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientTracingInfo.java b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionTracingInfo.java similarity index 80% rename from data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientTracingInfo.java rename to data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionTracingInfo.java index a0ad011ece6..fc878bd06d3 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientTracingInfo.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionTracingInfo.java @@ -19,12 +19,12 @@ import io.micronaut.core.annotation.Nullable; /** - * The client information that can be used to set to {@link java.sql.Connection#setClientInfo(String, String)}. + * The connection tracing information that can be used to set to {@link java.sql.Connection#setClientInfo(String, String)}. * Currently used only for Oracle database connections. * * @param appName The app name corresponding to the micronaut.application.name config value and can be null * @param module The module * @param action The action */ -public record ConnectionClientTracingInfo(@Nullable String appName, @NonNull String module, @NonNull String action) { +public record ConnectionTracingInfo(@Nullable String appName, @NonNull String module, @NonNull String action) { } From 41fd2578bbad6f9ca4ce148d4ee78fadc84c0024 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Mon, 21 Oct 2024 10:56:13 +0200 Subject: [PATCH 07/35] Changes per CR comments, still not there. --- ...DefaultDataSourceConnectionOperations.java | 68 ++++++++++++------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java index f7380eb3c6a..a8ce05ab87f 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java @@ -36,6 +36,8 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * The {@link DataSource} connection operations. @@ -50,9 +52,11 @@ public final class DefaultDataSourceConnectionOperations extends AbstractConnect private static final String ORACLE_TRACE_CLIENTID = "OCSID.CLIENTID"; private static final String ORACLE_TRACE_MODULE = "OCSID.MODULE"; private static final String ORACLE_TRACE_ACTION = "OCSID.ACTION"; + private static final String ORACLE_CONNECTION_DATABASE_PRODUCT_NAME = "Oracle"; private static final Logger LOG = LoggerFactory.getLogger(DefaultDataSourceConnectionOperations.class); private final DataSource dataSource; + private final Map connectionIsOracleMap = new ConcurrentHashMap<>(20); DefaultDataSourceConnectionOperations(DataSource dataSource) { this.dataSource = DelegatingDataSource.unwrapDataSource(dataSource); @@ -60,11 +64,33 @@ public final class DefaultDataSourceConnectionOperations extends AbstractConnect @Override protected Connection openConnection(ConnectionDefinition definition) { + Connection connection; try { - return dataSource.getConnection(); + connection = dataSource.getConnection(); } catch (SQLException e) { throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", e); } + + // Set client info for connection if Oracle connection after connection is opened + ConnectionTracingInfo connectionTracingInfo = definition.connectionTracingInfo(); + if (connectionTracingInfo != null) { + boolean oracleConnection = connectionIsOracleMap.computeIfAbsent(connection, this::isOracleConnection); + if (!oracleConnection) { + LOG.debug("Connection tracing info is supported only for Oracle database connections."); + } else { + LOG.trace("Setting connection tracing info to the Oracle connection"); + try { + if (connectionTracingInfo.appName() != null) { + connection.setClientInfo(ORACLE_TRACE_CLIENTID, connectionTracingInfo.appName()); + } + connection.setClientInfo(ORACLE_TRACE_MODULE, connectionTracingInfo.module()); + connection.setClientInfo(ORACLE_TRACE_ACTION, connectionTracingInfo.action()); + } catch (SQLClientInfoException e) { + LOG.debug("Failed to set connection tracing info", e); + } + } + } + return connection; } @Override @@ -84,32 +110,28 @@ public void executionComplete() { }); } }); - ConnectionTracingInfo connectionTracingInfo = connectionDefinition.connectionTracingInfo(); - if (connectionTracingInfo == null) { - return; - } - Connection conn = connectionStatus.getConnection(); - if (!isOracleConnection(conn)) { - LOG.debug("Connection tracing info is supported only for Oracle database connections."); - return; - } - - LOG.trace("Setting connection tracing info to the Oracle connection"); - try { - if (connectionTracingInfo.appName() != null) { - conn.setClientInfo(ORACLE_TRACE_CLIENTID, connectionTracingInfo.appName()); - } - conn.setClientInfo(ORACLE_TRACE_MODULE, connectionTracingInfo.module()); - conn.setClientInfo(ORACLE_TRACE_ACTION, connectionTracingInfo.action()); - } catch (SQLClientInfoException e) { - LOG.debug("Failed to set connection tracing info", e); - } } @Override protected void closeConnection(ConnectionStatus connectionStatus) { + Connection connection = connectionStatus.getConnection(); + // Clear client info for connection if it was Oracle connection and client info was set previously + ConnectionTracingInfo connectionTracingInfo = connectionStatus.getDefinition().connectionTracingInfo(); + if (connectionTracingInfo != null) { + boolean oracleConnection = connectionIsOracleMap.computeIfAbsent(connection, this::isOracleConnection); + if (oracleConnection) { + try { + connection.setClientInfo(ORACLE_TRACE_CLIENTID, connectionTracingInfo.appName()); + connection.setClientInfo(ORACLE_TRACE_MODULE, connectionTracingInfo.module()); + connection.setClientInfo(ORACLE_TRACE_ACTION, connectionTracingInfo.action()); + } catch (SQLClientInfoException e) { + LOG.debug("Failed to clear connection tracing info", e); + } + } + } + try { - connectionStatus.getConnection().close(); + connection.close(); } catch (SQLException e) { throw new ConnectionException("Failed to close the connection: " + e.getMessage(), e); } @@ -124,7 +146,7 @@ protected void closeConnection(ConnectionStatus connectionStatus) { private boolean isOracleConnection(Connection connection) { try { String databaseProductName = connection.getMetaData().getDatabaseProductName(); - return StringUtils.isNotEmpty(databaseProductName) && databaseProductName.toUpperCase().contains("ORACLE"); + return StringUtils.isNotEmpty(databaseProductName) && databaseProductName.equalsIgnoreCase(ORACLE_CONNECTION_DATABASE_PRODUCT_NAME); } catch (SQLException e) { LOG.debug("Failed to get database product name from the connection", e); return false; From 71e66de9de0103b6cfacfeeadf6bc9b63789a1be Mon Sep 17 00:00:00 2001 From: radovanradic Date: Mon, 21 Oct 2024 11:13:21 +0200 Subject: [PATCH 08/35] Change as suggested in PR comment --- .../data/connection/interceptor/ConnectableInterceptor.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java index 7f0148926f5..4b79a96996b 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java @@ -20,7 +20,6 @@ import io.micronaut.aop.InterceptorBean; import io.micronaut.aop.MethodInterceptor; import io.micronaut.aop.MethodInvocationContext; -import io.micronaut.context.annotation.Value; import io.micronaut.core.annotation.AnnotationValue; import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; @@ -36,6 +35,7 @@ import io.micronaut.data.connection.reactive.ReactorConnectionOperations; import io.micronaut.data.connection.support.ConnectionTracingInfo; import io.micronaut.inject.ExecutableMethod; +import io.micronaut.runtime.ApplicationConfiguration; import jakarta.inject.Singleton; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -81,11 +81,11 @@ public final class ConnectableInterceptor implements MethodInterceptor Date: Mon, 21 Oct 2024 11:34:13 +0200 Subject: [PATCH 09/35] Properly clear connection client info --- .../operations/DefaultDataSourceConnectionOperations.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java index a8ce05ab87f..270ae431117 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java @@ -121,9 +121,9 @@ protected void closeConnection(ConnectionStatus connectionStatus) { boolean oracleConnection = connectionIsOracleMap.computeIfAbsent(connection, this::isOracleConnection); if (oracleConnection) { try { - connection.setClientInfo(ORACLE_TRACE_CLIENTID, connectionTracingInfo.appName()); - connection.setClientInfo(ORACLE_TRACE_MODULE, connectionTracingInfo.module()); - connection.setClientInfo(ORACLE_TRACE_ACTION, connectionTracingInfo.action()); + connection.setClientInfo(ORACLE_TRACE_CLIENTID, null); + connection.setClientInfo(ORACLE_TRACE_MODULE, null); + connection.setClientInfo(ORACLE_TRACE_ACTION, null); } catch (SQLClientInfoException e) { LOG.debug("Failed to clear connection tracing info", e); } From 2785d8d8d77b19655e604be63a80b5e32838e09c Mon Sep 17 00:00:00 2001 From: radovanradic Date: Mon, 21 Oct 2024 15:25:26 +0200 Subject: [PATCH 10/35] Introduce new OracleConnectionClientInfo annotation for connection client info tracing --- .../connection/annotation/Connectable.java | 26 --------- .../OracleConnectionClientInfo.java | 58 +++++++++++++++++++ .../interceptor/ConnectableInterceptor.java | 17 +++--- .../jdbc/oraclexe/OracleXEBookRepository.java | 9 ++- .../guide/dbc/jdbc/jdbcConfiguration.adoc | 2 +- 5 files changed, 72 insertions(+), 40 deletions(-) create mode 100644 data-connection/src/main/java/io/micronaut/data/connection/annotation/OracleConnectionClientInfo.java diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/Connectable.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/Connectable.java index f4b7745e7a5..0f1c6a2d1aa 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/annotation/Connectable.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/annotation/Connectable.java @@ -82,30 +82,4 @@ * @return Whether is read-only connection */ boolean readOnly() default false; - - /** - * If true, then when connection is established {@link java.sql.Connection#setClientInfo(String, String)} will be called - * if it is connected to the Oracle database. It will issue calls to set MODULE, ACTION and CLIENT_IDENTIFIER. - * - * @return whether connection should trace/set client info - */ - boolean traceClientInfo() default false; - - /** - * The module name for tracing if {@link #traceClientInfo()} is true. - * If not provided, then it will fall back to the name of the class currently being intercepted in {@link io.micronaut.data.connection.interceptor.ConnectableInterceptor}. - * Currently supported only for Oracle database connections. - * - * @return the custom module name for tracing - */ - String tracingModule() default ""; - - /** - * The action name for tracing if {@link #traceClientInfo()} is true. - * If not provided, then it will fall back to the name of the method currently being intercepted in {@link io.micronaut.data.connection.interceptor.ConnectableInterceptor}. - * Currently supported only for Oracle database connections. - * - * @return the custom module name for tracing - */ - String tracingAction() default ""; } diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/OracleConnectionClientInfo.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/OracleConnectionClientInfo.java new file mode 100644 index 00000000000..65e006fbc3b --- /dev/null +++ b/data-connection/src/main/java/io/micronaut/data/connection/annotation/OracleConnectionClientInfo.java @@ -0,0 +1,58 @@ +/* + * Copyright 2017-2024 original authors + * + * 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 + * + * https://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 io.micronaut.data.connection.annotation; + + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation used to set client info for the connection. Assumed it is applied only for Oracle + * database connections. + */ +@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Connectable +public @interface OracleConnectionClientInfo { + + /** + * If this flag is not disabled then when connection is established {@link java.sql.Connection#setClientInfo(String, String)} will be called + * if it is connected to the Oracle database. It will issue calls to set MODULE, ACTION and CLIENT_IDENTIFIER. + * + * @return whether connection should trace/set client info + */ + boolean disableClientInfoTracing() default false; + + /** + * The module name for tracing if {@link #disableClientInfoTracing()} ()} is not set to true. + * If not provided, then it will fall back to the name of the class currently being intercepted in {@link io.micronaut.data.connection.interceptor.ConnectableInterceptor}. + * Currently supported only for Oracle database connections. + * + * @return the custom module name for tracing + */ + String tracingModule() default ""; + + /** + * The action name for tracing if {@link #disableClientInfoTracing()} is not set to true. + * If not provided, then it will fall back to the name of the method currently being intercepted in {@link io.micronaut.data.connection.interceptor.ConnectableInterceptor}. + * Currently supported only for Oracle database connections. + * + * @return the custom module name for tracing + */ + String tracingAction() default ""; +} diff --git a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java index 4b79a96996b..26bee20230f 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java @@ -30,6 +30,7 @@ import io.micronaut.data.connection.ConnectionOperationsRegistry; import io.micronaut.data.connection.DefaultConnectionDefinition; import io.micronaut.data.connection.annotation.Connectable; +import io.micronaut.data.connection.annotation.OracleConnectionClientInfo; import io.micronaut.data.connection.async.AsyncConnectionOperations; import io.micronaut.data.connection.reactive.ReactiveStreamsConnectionOperations; import io.micronaut.data.connection.reactive.ReactorConnectionOperations; @@ -56,7 +57,7 @@ @InterceptorBean(Connectable.class) public final class ConnectableInterceptor implements MethodInterceptor { - private static final String TRACE_CLIENT_INFO_MEMBER = "traceClientInfo"; + private static final String DISABLE_CLIENT_INFO_TRACING_MEMBER = "disableClientInfoTracing"; private static final String TRACING_MODULE_MEMBER = "tracingModule"; private static final String TRACING_ACTION_MEMBER = "tracingAction"; @@ -166,8 +167,8 @@ public static ConnectionDefinition getConnectionDefinition(ExecutableMethod oracleConnectionClientInfoAnnotationValue = executableMethod.getAnnotation(OracleConnectionClientInfo.class); + ConnectionTracingInfo connectionTracingInfo = oracleConnectionClientInfoAnnotationValue == null ? null : getConnectionClientTracingInfo(oracleConnectionClientInfoAnnotationValue, executableMethod, appName); return new DefaultConnectionDefinition( executableMethod.getDeclaringType().getSimpleName() + "." + executableMethod.getMethodName(), annotation.enumValue("propagation", ConnectionDefinition.Propagation.class).orElse(ConnectionDefinition.PROPAGATION_DEFAULT), @@ -178,18 +179,18 @@ public static ConnectionDefinition getConnectionDefinition(ExecutableMethod annotation, + private static @Nullable ConnectionTracingInfo getConnectionClientTracingInfo(AnnotationValue annotation, ExecutableMethod executableMethod, String appName) { - boolean traceClientInfo = annotation.booleanValue(TRACE_CLIENT_INFO_MEMBER).orElse(false); - if (!traceClientInfo) { + boolean disableClientInfoTracing = annotation.booleanValue(DISABLE_CLIENT_INFO_TRACING_MEMBER).orElse(false); + if (disableClientInfoTracing) { return null; } String module = annotation.stringValue(TRACING_MODULE_MEMBER).orElse(null); diff --git a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java index d58a4ee7aab..0fdcc537e44 100644 --- a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java +++ b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java @@ -17,11 +17,10 @@ import io.micronaut.core.annotation.NonNull; import io.micronaut.data.annotation.Expandable; -import io.micronaut.data.annotation.Id; import io.micronaut.data.annotation.Query; import io.micronaut.data.annotation.TypeDef; import io.micronaut.data.annotation.sql.Procedure; -import io.micronaut.data.connection.annotation.Connectable; +import io.micronaut.data.connection.annotation.OracleConnectionClientInfo; import io.micronaut.data.jdbc.annotation.JdbcRepository; import io.micronaut.data.model.DataType; import io.micronaut.data.model.query.builder.sql.Dialect; @@ -33,7 +32,7 @@ import java.util.List; @JdbcRepository(dialect = Dialect.ORACLE) -@Connectable(traceClientInfo = true, tracingModule = "BOOKS") +@OracleConnectionClientInfo(tracingModule = "BOOKS") public abstract class OracleXEBookRepository extends BookRepository { public OracleXEBookRepository(OracleXEAuthorRepository authorRepository) { super(authorRepository); @@ -48,7 +47,7 @@ public OracleXEBookRepository(OracleXEAuthorRepository authorRepository) { @Override @Query(value = "select * from book b where b.title = ANY (:arg0)", nativeQuery = true) - @Connectable(traceClientInfo = false) + @OracleConnectionClientInfo(disableClientInfoTracing = true) public abstract List listNativeBooksWithTitleAnyArray(@Expandable @TypeDef(type = DataType.STRING) @Nullable String[] arg0); @Procedure @@ -58,7 +57,7 @@ public OracleXEBookRepository(OracleXEAuthorRepository authorRepository) { public abstract int add1Aliased(int input); @Override - @Connectable(traceClientInfo = true, tracingAction = "INSERT") + @OracleConnectionClientInfo(tracingAction = "INSERT") public abstract @NonNull Book save(@NonNull Book book); // public abstract Book updateReturning(Book book); diff --git a/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc b/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc index 103bd353c07..808f07dd384 100644 --- a/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc +++ b/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc @@ -64,7 +64,7 @@ IMPORTANT: The dialect setting in configuration does *not* replace the need to e === Connection client info tracing (Oracle) In order to trace SQL calls using `java.sql.Connection.setClientInfo(String, String)` method, you can -annotate repository with @api:data.connection.annotation.Connectable[] annotation. If boolean field `traceClientInfo` is not set to true, then Micronaut will not issue `setClientInfo` calls to the JDBC connection. +annotate repository with @api:data.connection.annotation.OracleConnectionClientInfo[] annotation. If boolean field `disableClientInfoTracing` is set to true, then Micronaut will not issue `setClientInfo` calls to the JDBC connection. Fields in given annotation `tracingModule` and `tracingAction` can set custom module and action. If omitted, then module value will default to the class name (usually Micronaut Data repository class) annotated with the given annotation and action to the name of the method being executed. Annotation can be used on class or method, customizing module or action. From 304d5776a6a372c13de36a6001678c98ce1ef71c Mon Sep 17 00:00:00 2001 From: radovanradic Date: Mon, 21 Oct 2024 15:25:50 +0200 Subject: [PATCH 11/35] Fixed javadoc --- .../data/connection/annotation/OracleConnectionClientInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/OracleConnectionClientInfo.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/OracleConnectionClientInfo.java index 65e006fbc3b..1f35c52b9e9 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/annotation/OracleConnectionClientInfo.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/annotation/OracleConnectionClientInfo.java @@ -52,7 +52,7 @@ * If not provided, then it will fall back to the name of the method currently being intercepted in {@link io.micronaut.data.connection.interceptor.ConnectableInterceptor}. * Currently supported only for Oracle database connections. * - * @return the custom module name for tracing + * @return the custom action name for tracing */ String tracingAction() default ""; } From b8060d38110b14d5a7985d9eaaadc08f529fb6f8 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Fri, 15 Nov 2024 12:00:37 +0100 Subject: [PATCH 12/35] Resolve module/class name using InvocationContext.getTarget() --- .../interceptor/ConnectableInterceptor.java | 44 +++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java index 26bee20230f..884f53c7b1d 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java @@ -18,6 +18,7 @@ import io.micronaut.aop.InterceptPhase; import io.micronaut.aop.InterceptedMethod; import io.micronaut.aop.InterceptorBean; +import io.micronaut.aop.InvocationContext; import io.micronaut.aop.MethodInterceptor; import io.micronaut.aop.MethodInvocationContext; import io.micronaut.core.annotation.AnnotationValue; @@ -60,6 +61,7 @@ public final class ConnectableInterceptor implements MethodInterceptor connectionInvocationMap = new ConcurrentHashMap<>(30); @@ -109,7 +111,7 @@ public Object intercept(MethodInvocationContext context) { final ConnectionInvocation connectionInvocation = connectionInvocationMap .computeIfAbsent(new TenantExecutableMethod(tenantDataSourceName, executableMethod), ignore -> { final String dataSource = tenantDataSourceName == null ? executableMethod.stringValue(Connectable.class).orElse(null) : tenantDataSourceName; - final ConnectionDefinition connectionDefinition = getConnectionDefinition(executableMethod, appName); + final ConnectionDefinition connectionDefinition = getConnectionDefinition(context, executableMethod, appName); switch (interceptedMethod.resultType()) { case PUBLISHER -> { @@ -161,14 +163,44 @@ public Object intercept(MethodInvocationContext context) { } } + /** + * Retrieves the connection definition based on the provided executable method and application name. + * + * This method is deprecated since version 4.10.4 and marked for removal in future versions. + * + * @param executableMethod the executable method to retrieve the connection definition for + * @param appName the application name + * @return the connection definition + * @deprecated Since 4.10.4, use {@link #getConnectionDefinition(InvocationContext, ExecutableMethod, String)} instead + */ @NonNull + @Deprecated(since = "4.10.4", forRemoval = true) public static ConnectionDefinition getConnectionDefinition(ExecutableMethod executableMethod, String appName) { + return getConnectionDefinition(null, executableMethod, appName); + } + + /** + * Retrieves the connection definition based on the provided executable method and application name. + * + * This method examines the annotations present on the executable method to determine the connection definition. + * It looks for the presence of the {@link Connectable} annotation and uses its attributes to construct the connection definition. + * Additionally, it checks for the presence of the {@link OracleConnectionClientInfo} annotation to obtain connection tracing information. + * + * @param context the invocation context, may be null + * @param executableMethod the executable method to retrieve the connection definition for + * @param appName the application name + * @return the connection definition + */ + @NonNull + public static ConnectionDefinition getConnectionDefinition(@Nullable InvocationContext context, + ExecutableMethod executableMethod, + String appName) { AnnotationValue annotation = executableMethod.getAnnotation(Connectable.class); if (annotation == null) { throw new IllegalStateException("No declared @Connectable annotation present"); } AnnotationValue oracleConnectionClientInfoAnnotationValue = executableMethod.getAnnotation(OracleConnectionClientInfo.class); - ConnectionTracingInfo connectionTracingInfo = oracleConnectionClientInfoAnnotationValue == null ? null : getConnectionClientTracingInfo(oracleConnectionClientInfoAnnotationValue, executableMethod, appName); + ConnectionTracingInfo connectionTracingInfo = oracleConnectionClientInfoAnnotationValue == null ? null : getConnectionClientTracingInfo(oracleConnectionClientInfoAnnotationValue, context, executableMethod, appName); return new DefaultConnectionDefinition( executableMethod.getDeclaringType().getSimpleName() + "." + executableMethod.getMethodName(), annotation.enumValue("propagation", ConnectionDefinition.Propagation.class).orElse(ConnectionDefinition.PROPAGATION_DEFAULT), @@ -187,6 +219,7 @@ public static ConnectionDefinition getConnectionDefinition(ExecutableMethod annotation, + @Nullable InvocationContext context, ExecutableMethod executableMethod, String appName) { boolean disableClientInfoTracing = annotation.booleanValue(DISABLE_CLIENT_INFO_TRACING_MEMBER).orElse(false); @@ -196,7 +229,12 @@ public static ConnectionDefinition getConnectionDefinition(ExecutableMethod clazz = context.getTarget().getClass(); + module = clazz.getName().replace(INTERCEPTED_SUFFIX, ""); + } else { + module = executableMethod.getDeclaringType().getName(); + } } if (action == null) { action = executableMethod.getMethodName(); From 3320b381b9d4b538766ee3144ecf74be16150089 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Fri, 15 Nov 2024 16:49:08 +0100 Subject: [PATCH 13/35] Introduce ConnectionCustomizer for more flexibility --- .../jdbc/operations/ConnectionCustomizer.java | 67 ++++++++++ ...DefaultDataSourceConnectionOperations.java | 72 +++------- .../OracleConnectionCustomizer.java | 123 ++++++++++++++++++ ...entInfo.java => ConnectionClientInfo.java} | 8 +- .../interceptor/ConnectableInterceptor.java | 18 +-- .../jdbc/oraclexe/OracleXEBookRepository.java | 8 +- .../guide/dbc/jdbc/jdbcConfiguration.adoc | 9 +- 7 files changed, 229 insertions(+), 76 deletions(-) create mode 100644 data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/ConnectionCustomizer.java create mode 100644 data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/OracleConnectionCustomizer.java rename data-connection/src/main/java/io/micronaut/data/connection/annotation/{OracleConnectionClientInfo.java => ConnectionClientInfo.java} (87%) diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/ConnectionCustomizer.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/ConnectionCustomizer.java new file mode 100644 index 00000000000..edea9ee216c --- /dev/null +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/ConnectionCustomizer.java @@ -0,0 +1,67 @@ +/* + * Copyright 2017-2024 original authors + * + * 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 + * + * https://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 io.micronaut.data.connection.jdbc.operations; + +import io.micronaut.core.order.Ordered; +import io.micronaut.data.connection.ConnectionDefinition; + +import java.sql.Connection; + +/** + * Customizes connections based on the provided {@link ConnectionDefinition}. + * + * Implementations of this interface can modify the behavior of connections created by Micronaut Data JDBC. + * + * @see ConnectionDefinition + */ +public interface ConnectionCustomizer extends Ordered { + + /** + * Checks whether this customizer supports the given connection and connection definition. + * + * @param connection the SQL connection to be customized + * @param connectionDefinition the connection definition used to create the connection + * @return true if this customizer supports the given connection and connection definition, false otherwise + */ + boolean supportsConnection(Connection connection, ConnectionDefinition connectionDefinition); + + /** + * Called after a connection is opened. + * + * This method allows implementations to perform additional setup or configuration on the connection. + * + * @param connection the newly opened SQL connection + * @param connectionDefinition the connection definition used to create the connection + */ + void afterOpen(Connection connection, ConnectionDefinition connectionDefinition); + + /** + * Called before a connection is closed. + * + * This method allows implementations to release any resources or perform cleanup tasks related to the connection. + * + * @param connection the SQL connection about to be closed + * @param connectionDefinition the connection definition used to create the connection + */ + void beforeClose(Connection connection, ConnectionDefinition connectionDefinition); + + /** + * Returns the name of this customizer. Used for logging purposes. + * + * @return the name of this customizer + */ + String getName(); +} diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java index 270ae431117..d88793a15b9 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java @@ -17,7 +17,6 @@ import io.micronaut.context.annotation.EachBean; import io.micronaut.core.annotation.Internal; -import io.micronaut.core.util.StringUtils; import io.micronaut.data.connection.exceptions.ConnectionException; import io.micronaut.data.connection.jdbc.advice.DelegatingDataSource; import io.micronaut.data.connection.jdbc.exceptions.CannotGetJdbcConnectionException; @@ -25,19 +24,15 @@ import io.micronaut.data.connection.ConnectionStatus; import io.micronaut.data.connection.ConnectionSynchronization; import io.micronaut.data.connection.support.AbstractConnectionOperations; -import io.micronaut.data.connection.support.ConnectionTracingInfo; import io.micronaut.data.connection.support.JdbcConnectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.sql.DataSource; import java.sql.Connection; -import java.sql.SQLClientInfoException; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; /** * The {@link DataSource} connection operations. @@ -49,17 +44,14 @@ @EachBean(DataSource.class) public final class DefaultDataSourceConnectionOperations extends AbstractConnectionOperations { - private static final String ORACLE_TRACE_CLIENTID = "OCSID.CLIENTID"; - private static final String ORACLE_TRACE_MODULE = "OCSID.MODULE"; - private static final String ORACLE_TRACE_ACTION = "OCSID.ACTION"; - private static final String ORACLE_CONNECTION_DATABASE_PRODUCT_NAME = "Oracle"; - private static final Logger LOG = LoggerFactory.getLogger(DefaultDataSourceConnectionOperations.class); private final DataSource dataSource; - private final Map connectionIsOracleMap = new ConcurrentHashMap<>(20); + private final List connectionCustomizers; - DefaultDataSourceConnectionOperations(DataSource dataSource) { + DefaultDataSourceConnectionOperations(DataSource dataSource, + List connectionCustomizers) { this.dataSource = DelegatingDataSource.unwrapDataSource(dataSource); + this.connectionCustomizers = connectionCustomizers; } @Override @@ -71,23 +63,13 @@ protected Connection openConnection(ConnectionDefinition definition) { throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", e); } - // Set client info for connection if Oracle connection after connection is opened - ConnectionTracingInfo connectionTracingInfo = definition.connectionTracingInfo(); - if (connectionTracingInfo != null) { - boolean oracleConnection = connectionIsOracleMap.computeIfAbsent(connection, this::isOracleConnection); - if (!oracleConnection) { - LOG.debug("Connection tracing info is supported only for Oracle database connections."); - } else { - LOG.trace("Setting connection tracing info to the Oracle connection"); - try { - if (connectionTracingInfo.appName() != null) { - connection.setClientInfo(ORACLE_TRACE_CLIENTID, connectionTracingInfo.appName()); - } - connection.setClientInfo(ORACLE_TRACE_MODULE, connectionTracingInfo.module()); - connection.setClientInfo(ORACLE_TRACE_ACTION, connectionTracingInfo.action()); - } catch (SQLClientInfoException e) { - LOG.debug("Failed to set connection tracing info", e); + for (ConnectionCustomizer connectionCustomizer : connectionCustomizers) { + try { + if (connectionCustomizer.supportsConnection(connection, definition)) { + connectionCustomizer.afterOpen(connection, definition); } + } catch (Exception e) { + LOG.debug("Customizer {} failed to customize connection after open.", connectionCustomizer.getName(), e); } } return connection; @@ -115,18 +97,14 @@ public void executionComplete() { @Override protected void closeConnection(ConnectionStatus connectionStatus) { Connection connection = connectionStatus.getConnection(); - // Clear client info for connection if it was Oracle connection and client info was set previously - ConnectionTracingInfo connectionTracingInfo = connectionStatus.getDefinition().connectionTracingInfo(); - if (connectionTracingInfo != null) { - boolean oracleConnection = connectionIsOracleMap.computeIfAbsent(connection, this::isOracleConnection); - if (oracleConnection) { - try { - connection.setClientInfo(ORACLE_TRACE_CLIENTID, null); - connection.setClientInfo(ORACLE_TRACE_MODULE, null); - connection.setClientInfo(ORACLE_TRACE_ACTION, null); - } catch (SQLClientInfoException e) { - LOG.debug("Failed to clear connection tracing info", e); + ConnectionDefinition definition = connectionStatus.getDefinition(); + for (ConnectionCustomizer connectionCustomizer : connectionCustomizers) { + try { + if (connectionCustomizer.supportsConnection(connection, definition)) { + connectionCustomizer.beforeClose(connection, definition); } + } catch (Exception e) { + LOG.debug("Customizer {} failed to customize connection before close.", connectionCustomizer.getName(), e); } } @@ -136,20 +114,4 @@ protected void closeConnection(ConnectionStatus connectionStatus) { throw new ConnectionException("Failed to close the connection: " + e.getMessage(), e); } } - - /** - * Checks whether current connection is Oracle database connection. - * - * @param connection The connection - * @return true if current connection is Oracle database connection - */ - private boolean isOracleConnection(Connection connection) { - try { - String databaseProductName = connection.getMetaData().getDatabaseProductName(); - return StringUtils.isNotEmpty(databaseProductName) && databaseProductName.equalsIgnoreCase(ORACLE_CONNECTION_DATABASE_PRODUCT_NAME); - } catch (SQLException e) { - LOG.debug("Failed to get database product name from the connection", e); - return false; - } - } } diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/OracleConnectionCustomizer.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/OracleConnectionCustomizer.java new file mode 100644 index 00000000000..a70f3c39da4 --- /dev/null +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/OracleConnectionCustomizer.java @@ -0,0 +1,123 @@ +/* + * Copyright 2017-2024 original authors + * + * 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 + * + * https://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 io.micronaut.data.connection.jdbc.operations; + +import io.micronaut.context.annotation.Requires; +import io.micronaut.core.util.StringUtils; +import io.micronaut.data.connection.ConnectionDefinition; +import io.micronaut.data.connection.support.ConnectionTracingInfo; +import jakarta.inject.Singleton; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Connection; +import java.sql.SQLClientInfoException; +import java.sql.SQLException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A customizer for Oracle database connections that sets and clears client information. + * + * This customizer checks if the connection is an Oracle database connection and then sets the client information + * (client ID, module, and action) after opening the connection. It also clears these properties before closing the connection. + * + * @since 4.10 + */ +@Singleton +@Requires(property = "connection.customizer.oracle.enabled", value = "true", defaultValue = "false") +final class OracleConnectionCustomizer implements ConnectionCustomizer { + + /** + * Constant for the Oracle trace client ID property name. + */ + private static final String ORACLE_TRACE_CLIENTID = "OCSID.CLIENTID"; + /** + * Constant for the Oracle trace module property name. + */ + private static final String ORACLE_TRACE_MODULE = "OCSID.MODULE"; + /** + * Constant for the Oracle trace action property name. + */ + private static final String ORACLE_TRACE_ACTION = "OCSID.ACTION"; + /** + * Constant for the Oracle connection database product name. + */ + private static final String ORACLE_CONNECTION_DATABASE_PRODUCT_NAME = "Oracle"; + + private static final Logger LOG = LoggerFactory.getLogger(DefaultDataSourceConnectionOperations.class); + + private final Map connectionSupportedMap = new ConcurrentHashMap<>(20); + + @Override + public boolean supportsConnection(Connection connection, ConnectionDefinition connectionDefinition) { + return connectionSupportedMap.computeIfAbsent(connection, this::isOracleConnection); + } + + @Override + public void afterOpen(Connection connection, ConnectionDefinition connectionDefinition) { + // Set client info for connection if Oracle connection after connection is opened + ConnectionTracingInfo connectionTracingInfo = connectionDefinition.connectionTracingInfo(); + if (connectionTracingInfo != null) { + LOG.trace("Setting connection tracing info to the Oracle connection"); + try { + if (connectionTracingInfo.appName() != null) { + connection.setClientInfo(ORACLE_TRACE_CLIENTID, connectionTracingInfo.appName()); + } + connection.setClientInfo(ORACLE_TRACE_MODULE, connectionTracingInfo.module()); + connection.setClientInfo(ORACLE_TRACE_ACTION, connectionTracingInfo.action()); + } catch (SQLClientInfoException e) { + LOG.debug("Failed to set connection tracing info", e); + } + } + } + + @Override + public void beforeClose(Connection connection, ConnectionDefinition connectionDefinition) { +// Clear client info for connection if it was Oracle connection and client info was set previously + ConnectionTracingInfo connectionTracingInfo = connectionDefinition.connectionTracingInfo(); + if (connectionTracingInfo != null) { + try { + connection.setClientInfo(ORACLE_TRACE_CLIENTID, null); + connection.setClientInfo(ORACLE_TRACE_MODULE, null); + connection.setClientInfo(ORACLE_TRACE_ACTION, null); + } catch (SQLClientInfoException e) { + LOG.debug("Failed to clear connection tracing info", e); + } + } + } + + @Override + public String getName() { + return "Oracle Connection Customizer"; + } + + /** + * Checks whether current connection is Oracle database connection. + * + * @param connection The connection + * @return true if current connection is Oracle database connection + */ + private boolean isOracleConnection(Connection connection) { + try { + String databaseProductName = connection.getMetaData().getDatabaseProductName(); + return StringUtils.isNotEmpty(databaseProductName) && databaseProductName.equalsIgnoreCase(ORACLE_CONNECTION_DATABASE_PRODUCT_NAME); + } catch (SQLException e) { + LOG.debug("Failed to get database product name from the connection", e); + return false; + } + } +} diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/OracleConnectionClientInfo.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java similarity index 87% rename from data-connection/src/main/java/io/micronaut/data/connection/annotation/OracleConnectionClientInfo.java rename to data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java index 1f35c52b9e9..b298229cc67 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/annotation/OracleConnectionClientInfo.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java @@ -28,7 +28,7 @@ @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Connectable -public @interface OracleConnectionClientInfo { +public @interface ConnectionClientInfo { /** * If this flag is not disabled then when connection is established {@link java.sql.Connection#setClientInfo(String, String)} will be called @@ -36,10 +36,10 @@ * * @return whether connection should trace/set client info */ - boolean disableClientInfoTracing() default false; + boolean enabled() default true; /** - * The module name for tracing if {@link #disableClientInfoTracing()} ()} is not set to true. + * The module name for tracing if {@link #enabled()} is set to true. * If not provided, then it will fall back to the name of the class currently being intercepted in {@link io.micronaut.data.connection.interceptor.ConnectableInterceptor}. * Currently supported only for Oracle database connections. * @@ -48,7 +48,7 @@ String tracingModule() default ""; /** - * The action name for tracing if {@link #disableClientInfoTracing()} is not set to true. + * The action name for tracing if {@link #enabled()} is set to true. * If not provided, then it will fall back to the name of the method currently being intercepted in {@link io.micronaut.data.connection.interceptor.ConnectableInterceptor}. * Currently supported only for Oracle database connections. * diff --git a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java index 884f53c7b1d..d64ea0e8e3c 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java @@ -31,7 +31,7 @@ import io.micronaut.data.connection.ConnectionOperationsRegistry; import io.micronaut.data.connection.DefaultConnectionDefinition; import io.micronaut.data.connection.annotation.Connectable; -import io.micronaut.data.connection.annotation.OracleConnectionClientInfo; +import io.micronaut.data.connection.annotation.ConnectionClientInfo; import io.micronaut.data.connection.async.AsyncConnectionOperations; import io.micronaut.data.connection.reactive.ReactiveStreamsConnectionOperations; import io.micronaut.data.connection.reactive.ReactorConnectionOperations; @@ -58,7 +58,7 @@ @InterceptorBean(Connectable.class) public final class ConnectableInterceptor implements MethodInterceptor { - private static final String DISABLE_CLIENT_INFO_TRACING_MEMBER = "disableClientInfoTracing"; + private static final String ENABLED = "enabled"; private static final String TRACING_MODULE_MEMBER = "tracingModule"; private static final String TRACING_ACTION_MEMBER = "tracingAction"; private static final String INTERCEPTED_SUFFIX = "$Intercepted"; @@ -184,7 +184,7 @@ public static ConnectionDefinition getConnectionDefinition(ExecutableMethod oracleConnectionClientInfoAnnotationValue = executableMethod.getAnnotation(OracleConnectionClientInfo.class); + AnnotationValue oracleConnectionClientInfoAnnotationValue = executableMethod.getAnnotation(ConnectionClientInfo.class); ConnectionTracingInfo connectionTracingInfo = oracleConnectionClientInfoAnnotationValue == null ? null : getConnectionClientTracingInfo(oracleConnectionClientInfoAnnotationValue, context, executableMethod, appName); return new DefaultConnectionDefinition( executableMethod.getDeclaringType().getSimpleName() + "." + executableMethod.getMethodName(), @@ -211,19 +211,19 @@ public static ConnectionDefinition getConnectionDefinition(@Nullable InvocationC } /** - * Gets Oracle connection tracing info from the {@link OracleConnectionClientInfo} annotation. + * Gets Oracle connection tracing info from the {@link ConnectionClientInfo} annotation. * - * @param annotation The {@link OracleConnectionClientInfo} annotation value + * @param annotation The {@link ConnectionClientInfo} annotation value * @param executableMethod The method being executed * @param appName The micronaut application name, null if not set * @return The connection tracing info or null if not configured to be used */ - private static @Nullable ConnectionTracingInfo getConnectionClientTracingInfo(AnnotationValue annotation, + private static @Nullable ConnectionTracingInfo getConnectionClientTracingInfo(AnnotationValue annotation, @Nullable InvocationContext context, ExecutableMethod executableMethod, String appName) { - boolean disableClientInfoTracing = annotation.booleanValue(DISABLE_CLIENT_INFO_TRACING_MEMBER).orElse(false); - if (disableClientInfoTracing) { + boolean connectionClientInfoEnabled = annotation.booleanValue(ENABLED).orElse(true); + if (!connectionClientInfoEnabled) { return null; } String module = annotation.stringValue(TRACING_MODULE_MEMBER).orElse(null); diff --git a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java index 0fdcc537e44..1dea0cc9aae 100644 --- a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java +++ b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java @@ -20,7 +20,7 @@ import io.micronaut.data.annotation.Query; import io.micronaut.data.annotation.TypeDef; import io.micronaut.data.annotation.sql.Procedure; -import io.micronaut.data.connection.annotation.OracleConnectionClientInfo; +import io.micronaut.data.connection.annotation.ConnectionClientInfo; import io.micronaut.data.jdbc.annotation.JdbcRepository; import io.micronaut.data.model.DataType; import io.micronaut.data.model.query.builder.sql.Dialect; @@ -32,7 +32,7 @@ import java.util.List; @JdbcRepository(dialect = Dialect.ORACLE) -@OracleConnectionClientInfo(tracingModule = "BOOKS") +@ConnectionClientInfo(tracingModule = "BOOKS") public abstract class OracleXEBookRepository extends BookRepository { public OracleXEBookRepository(OracleXEAuthorRepository authorRepository) { super(authorRepository); @@ -47,7 +47,7 @@ public OracleXEBookRepository(OracleXEAuthorRepository authorRepository) { @Override @Query(value = "select * from book b where b.title = ANY (:arg0)", nativeQuery = true) - @OracleConnectionClientInfo(disableClientInfoTracing = true) + @ConnectionClientInfo(enabled = false) public abstract List listNativeBooksWithTitleAnyArray(@Expandable @TypeDef(type = DataType.STRING) @Nullable String[] arg0); @Procedure @@ -57,7 +57,7 @@ public OracleXEBookRepository(OracleXEAuthorRepository authorRepository) { public abstract int add1Aliased(int input); @Override - @OracleConnectionClientInfo(tracingAction = "INSERT") + @ConnectionClientInfo(tracingAction = "INSERT") public abstract @NonNull Book save(@NonNull Book book); // public abstract Book updateReturning(Book book); diff --git a/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc b/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc index 808f07dd384..fd394350d06 100644 --- a/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc +++ b/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc @@ -61,17 +61,18 @@ As seen in the configuration above you should also configure the dialect. Althou IMPORTANT: The dialect setting in configuration does *not* replace the need to ensure the correct dialect is set at the repository. If the dialect is H2 in configuration, the repository should have `@JdbcRepository(dialect = Dialect.H2)` / `@R2dbcRepository(dialect = Dialect.H2)`. Because repositories are computed at compile time, the configuration value is not known at that time. -=== Connection client info tracing (Oracle) +=== Connection client info tracing In order to trace SQL calls using `java.sql.Connection.setClientInfo(String, String)` method, you can -annotate repository with @api:data.connection.annotation.OracleConnectionClientInfo[] annotation. If boolean field `disableClientInfoTracing` is set to true, then Micronaut will not issue `setClientInfo` calls to the JDBC connection. +annotate repository with @api:data.connection.annotation.ConnectionClientInfo[] annotation. If boolean field `enabled` is set to false, then Micronaut will not issue `setClientInfo` calls to the JDBC connection. Fields in given annotation `tracingModule` and `tracingAction` can set custom module and action. If omitted, then module value will default to the class name (usually Micronaut Data repository class) annotated with the given annotation and action to the name of the method being executed. Annotation can be used on class or method, customizing module or action. -These values will be set to the JDBC connection using `setClientInfo("OCSID.MODULE", module)` and `setClientInfo("OCSID.ACTION", action)` respectively. +These values will be set to the JDBC connection using `setClientInfo("OCSID.MODULE", module)` and `setClientInfo("OCSID.ACTION", action)` respectively for Oracle database connections. Additionally, if `${micronaut.application.name}` is set, then it will be used to call JDBC connection `setClientInfo("OCSID.CLIENTID", appName)`. -Please note this is currently supported only for Oracle database connections. +Please note this is currently supported only for Oracle database connections. In order to enable Oracle connection client info to be set, +need to add this property `connection.customizer.oracle.enabled=true`. TIP: See the guide for https://guides.micronaut.io/latest/micronaut-data-jdbc-repository.html[Access a Database with Micronaut Data JDBC] to learn more. From d84dad1e67da7325630ad3602b96289ddffb4d00 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Sun, 17 Nov 2024 20:09:30 +0100 Subject: [PATCH 14/35] More changes as suggested in pull request comments. --- .../jdbc/operations/ConnectionCustomizer.java | 2 + ...OracleConnectionClientInfoCustomizer.java} | 61 ++++++++++++------- .../data/connection/ConnectionDefinition.java | 10 +-- .../DefaultConnectionDefinition.java | 12 ++-- .../annotation/ConnectionClientInfo.java | 33 ++++++---- .../ConnectionClientInfoAttribute.java | 42 +++++++++++++ .../interceptor/ConnectableInterceptor.java | 48 +++++++++------ ....java => ConnectionClientInfoDetails.java} | 15 +++-- .../OracleRepositorySetClientInfoSpec.groovy | 30 +++++++++ .../oraclexe/OracleXEAuthorRepository.java | 3 + .../jdbc/oraclexe/OracleXEBookRepository.java | 5 +- .../guide/dbc/jdbc/jdbcConfiguration.adoc | 12 ++-- 12 files changed, 200 insertions(+), 73 deletions(-) rename data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/{OracleConnectionCustomizer.java => OracleConnectionClientInfoCustomizer.java} (55%) create mode 100644 data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfoAttribute.java rename data-connection/src/main/java/io/micronaut/data/connection/support/{ConnectionTracingInfo.java => ConnectionClientInfoDetails.java} (52%) create mode 100644 data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/ConnectionCustomizer.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/ConnectionCustomizer.java index edea9ee216c..b71edf1304d 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/ConnectionCustomizer.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/ConnectionCustomizer.java @@ -29,6 +29,8 @@ */ public interface ConnectionCustomizer extends Ordered { + String PREFIX = "connection.customizer"; + /** * Checks whether this customizer supports the given connection and connection definition. * diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/OracleConnectionCustomizer.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/OracleConnectionClientInfoCustomizer.java similarity index 55% rename from data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/OracleConnectionCustomizer.java rename to data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/OracleConnectionClientInfoCustomizer.java index a70f3c39da4..b0d2cf7355d 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/OracleConnectionCustomizer.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/OracleConnectionClientInfoCustomizer.java @@ -18,7 +18,7 @@ import io.micronaut.context.annotation.Requires; import io.micronaut.core.util.StringUtils; import io.micronaut.data.connection.ConnectionDefinition; -import io.micronaut.data.connection.support.ConnectionTracingInfo; +import io.micronaut.data.connection.support.ConnectionClientInfoDetails; import jakarta.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,6 +26,7 @@ import java.sql.Connection; import java.sql.SQLClientInfoException; import java.sql.SQLException; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -38,27 +39,28 @@ * @since 4.10 */ @Singleton -@Requires(property = "connection.customizer.oracle.enabled", value = "true", defaultValue = "false") -final class OracleConnectionCustomizer implements ConnectionCustomizer { +@Requires(property = ConnectionCustomizer.PREFIX + ".oracleclientinfo.enabled", value = "true", defaultValue = "false") +final class OracleConnectionClientInfoCustomizer implements ConnectionCustomizer { /** - * Constant for the Oracle trace client ID property name. + * Constant for the Oracle connection client info client ID property name. */ - private static final String ORACLE_TRACE_CLIENTID = "OCSID.CLIENTID"; + private static final String ORACLE_CLIENT_ID = "OCSID.CLIENTID"; /** - * Constant for the Oracle trace module property name. + * Constant for the Oracle connection client info module property name. */ - private static final String ORACLE_TRACE_MODULE = "OCSID.MODULE"; + private static final String ORACLE_MODULE = "OCSID.MODULE"; /** - * Constant for the Oracle trace action property name. + * Constant for the Oracle connection client info action property name. */ - private static final String ORACLE_TRACE_ACTION = "OCSID.ACTION"; + private static final String ORACLE_ACTION = "OCSID.ACTION"; /** * Constant for the Oracle connection database product name. */ private static final String ORACLE_CONNECTION_DATABASE_PRODUCT_NAME = "Oracle"; + private static final List RESERVED_CLIENT_INFO_NAMES = List.of(ORACLE_CLIENT_ID, ORACLE_MODULE, ORACLE_ACTION); - private static final Logger LOG = LoggerFactory.getLogger(DefaultDataSourceConnectionOperations.class); + private static final Logger LOG = LoggerFactory.getLogger(OracleConnectionClientInfoCustomizer.class); private final Map connectionSupportedMap = new ConcurrentHashMap<>(20); @@ -70,15 +72,24 @@ public boolean supportsConnection(Connection connection, ConnectionDefinition co @Override public void afterOpen(Connection connection, ConnectionDefinition connectionDefinition) { // Set client info for connection if Oracle connection after connection is opened - ConnectionTracingInfo connectionTracingInfo = connectionDefinition.connectionTracingInfo(); - if (connectionTracingInfo != null) { + ConnectionClientInfoDetails connectionClientInfo = connectionDefinition.connectionClientInfo(); + if (connectionClientInfo != null) { LOG.trace("Setting connection tracing info to the Oracle connection"); try { - if (connectionTracingInfo.appName() != null) { - connection.setClientInfo(ORACLE_TRACE_CLIENTID, connectionTracingInfo.appName()); + if (connectionClientInfo.appName() != null) { + connection.setClientInfo(ORACLE_CLIENT_ID, connectionClientInfo.appName()); + } + connection.setClientInfo(ORACLE_MODULE, connectionClientInfo.module()); + connection.setClientInfo(ORACLE_ACTION, connectionClientInfo.action()); + for (Map.Entry additionalInfo : connectionClientInfo.connectionClientInfoAttributes().entrySet()) { + String name = additionalInfo.getKey(); + if (RESERVED_CLIENT_INFO_NAMES.contains(name)) { + LOG.debug("Connection client info attribute {} already set. Skip setting value from the arbitrary attributes.", name); + } else { + String value = additionalInfo.getValue(); + connection.setClientInfo(name, value); + } } - connection.setClientInfo(ORACLE_TRACE_MODULE, connectionTracingInfo.module()); - connection.setClientInfo(ORACLE_TRACE_ACTION, connectionTracingInfo.action()); } catch (SQLClientInfoException e) { LOG.debug("Failed to set connection tracing info", e); } @@ -87,13 +98,19 @@ public void afterOpen(Connection connection, ConnectionDefinition connectionDefi @Override public void beforeClose(Connection connection, ConnectionDefinition connectionDefinition) { -// Clear client info for connection if it was Oracle connection and client info was set previously - ConnectionTracingInfo connectionTracingInfo = connectionDefinition.connectionTracingInfo(); - if (connectionTracingInfo != null) { + // Clear client info for connection if it was Oracle connection and client info was set previously + ConnectionClientInfoDetails connectionClientInfo = connectionDefinition.connectionClientInfo(); + if (connectionClientInfo != null) { try { - connection.setClientInfo(ORACLE_TRACE_CLIENTID, null); - connection.setClientInfo(ORACLE_TRACE_MODULE, null); - connection.setClientInfo(ORACLE_TRACE_ACTION, null); + connection.setClientInfo(ORACLE_CLIENT_ID, null); + connection.setClientInfo(ORACLE_MODULE, null); + connection.setClientInfo(ORACLE_ACTION, null); + for (Map.Entry additionalInfo : connectionClientInfo.connectionClientInfoAttributes().entrySet()) { + String name = additionalInfo.getKey(); + if (!RESERVED_CLIENT_INFO_NAMES.contains(name)) { + connection.setClientInfo(name, null); + } + } } catch (SQLClientInfoException e) { LOG.debug("Failed to clear connection tracing info", e); } diff --git a/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java b/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java index 00c58bc80be..1e6ddacf1cf 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java @@ -18,7 +18,7 @@ import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; -import io.micronaut.data.connection.support.ConnectionTracingInfo; +import io.micronaut.data.connection.support.ConnectionClientInfoDetails; import java.time.Duration; import java.util.Optional; @@ -103,12 +103,12 @@ enum Propagation { String getName(); /** - * Returns the connection tracing information associated with this connection definition. - * If no connection tracing information has been set, this method will return null. + * Returns the connection client information associated with this connection definition. + * If no connection client information has been set, this method will return null. * - * @return An instance of {@link ConnectionTracingInfo} representing the connection tracing information, or null if not set. + * @return An instance of {@link ConnectionClientInfoDetails} representing the connection client information, or null if not set. */ - @Nullable ConnectionTracingInfo connectionTracingInfo(); + @Nullable ConnectionClientInfoDetails connectionClientInfo(); /** * Connection definition with specific propagation. diff --git a/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java b/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java index d504cf6c716..106902b6a49 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java @@ -18,7 +18,7 @@ import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; -import io.micronaut.data.connection.support.ConnectionTracingInfo; +import io.micronaut.data.connection.support.ConnectionClientInfoDetails; import java.time.Duration; import java.util.Optional; @@ -26,11 +26,11 @@ /** * Default implementation of the {@link ConnectionDefinition} interface. * - * @param name The connection name + * @param name The connection name * @param propagationBehavior The propagation behaviour * @param timeout The timeout * @param readOnlyValue The read only - * @param connectionTracingInfo The connection client tracing info, can be null + * @param connectionClientInfo The connection client info, can be null * @author Denis Stepanov * @since 4.0.0 */ @@ -41,7 +41,7 @@ public record DefaultConnectionDefinition( @Nullable Duration timeout, Boolean readOnlyValue, - @Nullable ConnectionTracingInfo connectionTracingInfo + @Nullable ConnectionClientInfoDetails connectionClientInfo ) implements ConnectionDefinition { DefaultConnectionDefinition(String name) { @@ -80,12 +80,12 @@ public String getName() { @Override public ConnectionDefinition withPropagation(Propagation propagation) { - return new DefaultConnectionDefinition(name, propagation, timeout, readOnlyValue, connectionTracingInfo); + return new DefaultConnectionDefinition(name, propagation, timeout, readOnlyValue, connectionClientInfo); } @Override public ConnectionDefinition withName(String name) { - return new DefaultConnectionDefinition(name, propagationBehavior, timeout, readOnlyValue, connectionTracingInfo); + return new DefaultConnectionDefinition(name, propagationBehavior, timeout, readOnlyValue, connectionClientInfo); } } diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java index b298229cc67..985b6f07b4d 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java @@ -16,14 +16,15 @@ package io.micronaut.data.connection.annotation; +import io.micronaut.data.connection.ConnectionDefinition; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * An annotation used to set client info for the connection. Assumed it is applied only for Oracle - * database connections. + * An annotation used to set client info for the connection. */ @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @@ -31,28 +32,36 @@ public @interface ConnectionClientInfo { /** - * If this flag is not disabled then when connection is established {@link java.sql.Connection#setClientInfo(String, String)} will be called - * if it is connected to the Oracle database. It will issue calls to set MODULE, ACTION and CLIENT_IDENTIFIER. + * If this flag is not disabled then when connection is established {@link io.micronaut.data.connection.support.ConnectionClientInfoDetails} + * will be populated in {@link ConnectionDefinition#connectionClientInfo()} using values from this annotation + * or calculate default module and action from the class name and method name issuing the call. + * Then this information can be used for example to {@link java.sql.Connection#setClientInfo(String, String)}. * - * @return whether connection should trace/set client info + * @return whether connection should set client info */ boolean enabled() default true; /** - * The module name for tracing if {@link #enabled()} is set to true. + * The module name for connection client info if {@link #enabled()} is set to true. * If not provided, then it will fall back to the name of the class currently being intercepted in {@link io.micronaut.data.connection.interceptor.ConnectableInterceptor}. - * Currently supported only for Oracle database connections. * - * @return the custom module name for tracing + * @return the custom module name for connection client info */ - String tracingModule() default ""; + String module() default ""; /** - * The action name for tracing if {@link #enabled()} is set to true. + * The action name for connection client info if {@link #enabled()} is set to true. * If not provided, then it will fall back to the name of the method currently being intercepted in {@link io.micronaut.data.connection.interceptor.ConnectableInterceptor}. - * Currently supported only for Oracle database connections. * * @return the custom action name for tracing */ - String tracingAction() default ""; + String action() default ""; + + /** + * Returns an array of additional attributes that will be included in the connection client info. + * These attributes can provide extra context about the connection and its usage. + * + * @return an array of ConnectionClientInfoAttribute instances + */ + ConnectionClientInfoAttribute[] clientInfoAttributes() default {}; } diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfoAttribute.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfoAttribute.java new file mode 100644 index 00000000000..1cde76e8c4a --- /dev/null +++ b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfoAttribute.java @@ -0,0 +1,42 @@ +/* + * Copyright 2017-2024 original authors + * + * 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 + * + * https://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 io.micronaut.data.connection.annotation; + +/** + * Annotation used to specify client information attributes that can be set on a JDBC connection. + * + * This annotation allows developers to define custom attributes that provide additional context about the client, + * such as application name or version. These attributes can then be retrieved by the database server and used + * for auditing, logging, or other purposes. + * + * @since 4.10 + */ +public @interface ConnectionClientInfoAttribute { + + /** + * Returns the name of the client information attribute. + * + * @return the attribute name + */ + String name(); + + /** + * Returns the value of the client information attribute. + * + * @return the attribute value + */ + String value(); +} diff --git a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java index d64ea0e8e3c..74db728c964 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java @@ -31,11 +31,11 @@ import io.micronaut.data.connection.ConnectionOperationsRegistry; import io.micronaut.data.connection.DefaultConnectionDefinition; import io.micronaut.data.connection.annotation.Connectable; -import io.micronaut.data.connection.annotation.ConnectionClientInfo; +import io.micronaut.data.connection.annotation.ConnectionClientInfoAttribute; import io.micronaut.data.connection.async.AsyncConnectionOperations; import io.micronaut.data.connection.reactive.ReactiveStreamsConnectionOperations; import io.micronaut.data.connection.reactive.ReactorConnectionOperations; -import io.micronaut.data.connection.support.ConnectionTracingInfo; +import io.micronaut.data.connection.support.ConnectionClientInfoDetails; import io.micronaut.inject.ExecutableMethod; import io.micronaut.runtime.ApplicationConfiguration; import jakarta.inject.Singleton; @@ -43,6 +43,8 @@ import reactor.core.publisher.Mono; import java.time.Duration; +import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; @@ -59,8 +61,11 @@ public final class ConnectableInterceptor implements MethodInterceptor { private static final String ENABLED = "enabled"; - private static final String TRACING_MODULE_MEMBER = "tracingModule"; - private static final String TRACING_ACTION_MEMBER = "tracingAction"; + private static final String MODULE_MEMBER = "module"; + private static final String ACTION_MEMBER = "action"; + private static final String NAME_MEMBER = "name"; + private static final String VALUE_MEMBER = "value"; + private static final String CLIENT_INFO_ATTRIBUTES_MEMBER = "clientInfoAttributes"; private static final String INTERCEPTED_SUFFIX = "$Intercepted"; private final Map connectionInvocationMap = new ConcurrentHashMap<>(30); @@ -184,7 +189,7 @@ public static ConnectionDefinition getConnectionDefinition(ExecutableMethod oracleConnectionClientInfoAnnotationValue = executableMethod.getAnnotation(ConnectionClientInfo.class); - ConnectionTracingInfo connectionTracingInfo = oracleConnectionClientInfoAnnotationValue == null ? null : getConnectionClientTracingInfo(oracleConnectionClientInfoAnnotationValue, context, executableMethod, appName); + AnnotationValue connectionClientInfoAnnotationValue = executableMethod.getAnnotation(io.micronaut.data.connection.annotation.ConnectionClientInfo.class); + ConnectionClientInfoDetails connectionClientInfoDetails = connectionClientInfoAnnotationValue == null ? null : getConnectionClientInfo(connectionClientInfoAnnotationValue, context, executableMethod, appName); return new DefaultConnectionDefinition( executableMethod.getDeclaringType().getSimpleName() + "." + executableMethod.getMethodName(), annotation.enumValue("propagation", ConnectionDefinition.Propagation.class).orElse(ConnectionDefinition.PROPAGATION_DEFAULT), annotation.longValue("timeout").stream().mapToObj(Duration::ofSeconds).findFirst().orElse(null), annotation.booleanValue("readOnly").orElse(null), - connectionTracingInfo + connectionClientInfoDetails ); } /** - * Gets Oracle connection tracing info from the {@link ConnectionClientInfo} annotation. + * Gets connection client info from the {@link io.micronaut.data.connection.annotation.ConnectionClientInfo} annotation. * - * @param annotation The {@link ConnectionClientInfo} annotation value + * @param annotation The {@link io.micronaut.data.connection.annotation.ConnectionClientInfo} annotation value * @param executableMethod The method being executed * @param appName The micronaut application name, null if not set - * @return The connection tracing info or null if not configured to be used + * @return The connection client info or null if not configured to be used */ - private static @Nullable ConnectionTracingInfo getConnectionClientTracingInfo(AnnotationValue annotation, - @Nullable InvocationContext context, - ExecutableMethod executableMethod, - String appName) { + private static @Nullable ConnectionClientInfoDetails getConnectionClientInfo(AnnotationValue annotation, + @Nullable InvocationContext context, + ExecutableMethod executableMethod, + String appName) { boolean connectionClientInfoEnabled = annotation.booleanValue(ENABLED).orElse(true); if (!connectionClientInfoEnabled) { return null; } - String module = annotation.stringValue(TRACING_MODULE_MEMBER).orElse(null); - String action = annotation.stringValue(TRACING_ACTION_MEMBER).orElse(null); + String module = annotation.stringValue(MODULE_MEMBER).orElse(null); + String action = annotation.stringValue(ACTION_MEMBER).orElse(null); if (module == null) { if (context != null) { Class clazz = context.getTarget().getClass(); @@ -239,7 +244,14 @@ public static ConnectionDefinition getConnectionDefinition(@Nullable InvocationC if (action == null) { action = executableMethod.getMethodName(); } - return new ConnectionTracingInfo(appName, module, action); + List> clientInfoAttributes = annotation.getAnnotations(CLIENT_INFO_ATTRIBUTES_MEMBER, ConnectionClientInfoAttribute.class); + Map additionalClientInfoAttributes = new HashMap<>(clientInfoAttributes.size()); + for (AnnotationValue clientInfoAttribute : clientInfoAttributes) { + String name = clientInfoAttribute.getRequiredValue(NAME_MEMBER, String.class); + String value = clientInfoAttribute.getRequiredValue(VALUE_MEMBER, String.class); + additionalClientInfoAttributes.put(name, value); + } + return new ConnectionClientInfoDetails(appName, module, action, additionalClientInfoAttributes); } /** diff --git a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionTracingInfo.java b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientInfoDetails.java similarity index 52% rename from data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionTracingInfo.java rename to data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientInfoDetails.java index fc878bd06d3..84028518d12 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionTracingInfo.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientInfoDetails.java @@ -17,14 +17,21 @@ import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; +import io.micronaut.data.connection.annotation.ConnectionClientInfo; + +import java.util.Map; /** - * The connection tracing information that can be used to set to {@link java.sql.Connection#setClientInfo(String, String)}. + * The connection client info details that can be used to set to {@link java.sql.Connection#setClientInfo(String, String)}. * Currently used only for Oracle database connections. * * @param appName The app name corresponding to the micronaut.application.name config value and can be null - * @param module The module - * @param action The action + * @param module The module (if not supplied in {@link ConnectionClientInfo#module()} + * then by default the name of the class issuing database call) + * @param action The action (if not supplied in {@link ConnectionClientInfo#action()} + * then by default the name of the method issuing database call) + * @param connectionClientInfoAttributes The arbitrary connection client info attributes to be set to {@link java.sql.Connection#setClientInfo(String, String)}. */ -public record ConnectionTracingInfo(@Nullable String appName, @NonNull String module, @NonNull String action) { +public record ConnectionClientInfoDetails(@Nullable String appName, @NonNull String module, @NonNull String action, + @NonNull Map connectionClientInfoAttributes) { } diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy new file mode 100644 index 00000000000..e3ea99c7e39 --- /dev/null +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy @@ -0,0 +1,30 @@ +/* + * Copyright 2017-2020 original authors + * + * 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 + * + * https://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 io.micronaut.data.jdbc.oraclexe + +/** + * The test + */ +class OracleRepositorySetClientInfoSpec extends OracleXERepositorySpec { + + @Override + Map getProperties() { + return super.getProperties() + [ + 'micronaut.application.name': 'OracleRepositorySetClientInfoSpec', + 'connection.customizer.oracleclientinfo.enabled': 'true' + ] + } +} diff --git a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEAuthorRepository.java b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEAuthorRepository.java index 75c90ba308f..cb9bea218e2 100644 --- a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEAuthorRepository.java +++ b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEAuthorRepository.java @@ -16,14 +16,17 @@ package io.micronaut.data.jdbc.oraclexe; import io.micronaut.data.annotation.Join; +import io.micronaut.data.connection.annotation.ConnectionClientInfo; import io.micronaut.data.jdbc.annotation.JdbcRepository; import io.micronaut.data.model.query.builder.sql.Dialect; import io.micronaut.data.tck.entities.Author; import io.micronaut.data.tck.repositories.AuthorRepository; @JdbcRepository(dialect = Dialect.ORACLE) +@ConnectionClientInfo public interface OracleXEAuthorRepository extends AuthorRepository { @Override @Join(value = "books", type = Join.Type.LEFT_FETCH) + @ConnectionClientInfo(action = "QueryAuthorByName") Author queryByName(String name); } diff --git a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java index 1dea0cc9aae..3c4ad218ee5 100644 --- a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java +++ b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java @@ -21,6 +21,7 @@ import io.micronaut.data.annotation.TypeDef; import io.micronaut.data.annotation.sql.Procedure; import io.micronaut.data.connection.annotation.ConnectionClientInfo; +import io.micronaut.data.connection.annotation.ConnectionClientInfoAttribute; import io.micronaut.data.jdbc.annotation.JdbcRepository; import io.micronaut.data.model.DataType; import io.micronaut.data.model.query.builder.sql.Dialect; @@ -32,7 +33,7 @@ import java.util.List; @JdbcRepository(dialect = Dialect.ORACLE) -@ConnectionClientInfo(tracingModule = "BOOKS") +@ConnectionClientInfo(module = "BOOKS") public abstract class OracleXEBookRepository extends BookRepository { public OracleXEBookRepository(OracleXEAuthorRepository authorRepository) { super(authorRepository); @@ -57,7 +58,7 @@ public OracleXEBookRepository(OracleXEAuthorRepository authorRepository) { public abstract int add1Aliased(int input); @Override - @ConnectionClientInfo(tracingAction = "INSERT") + @ConnectionClientInfo(action = "INSERT", clientInfoAttributes = {@ConnectionClientInfoAttribute(name = "OCSID.MODULE", value = "CustomModule")}) public abstract @NonNull Book save(@NonNull Book book); // public abstract Book updateReturning(Book book); diff --git a/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc b/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc index fd394350d06..1c36ff082d8 100644 --- a/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc +++ b/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc @@ -65,14 +65,18 @@ IMPORTANT: The dialect setting in configuration does *not* replace the need to e In order to trace SQL calls using `java.sql.Connection.setClientInfo(String, String)` method, you can annotate repository with @api:data.connection.annotation.ConnectionClientInfo[] annotation. If boolean field `enabled` is set to false, then Micronaut will not issue `setClientInfo` calls to the JDBC connection. -Fields in given annotation `tracingModule` and `tracingAction` can set custom module and action. If omitted, then module value will default +Fields in given annotation `module` and `action` can set custom module and action. If omitted, then module value will default to the class name (usually Micronaut Data repository class) annotated with the given annotation and action to the name of the method -being executed. Annotation can be used on class or method, customizing module or action. -These values will be set to the JDBC connection using `setClientInfo("OCSID.MODULE", module)` and `setClientInfo("OCSID.ACTION", action)` respectively for Oracle database connections. +being executed. There is also property `clientInfoAttributes` allowing to set arbitrary client info attributes to be set to the connection client info. +Annotation can be used on class or method, customizing module or action. + +For Oracle database, these values will be set to the JDBC connection using `setClientInfo("OCSID.MODULE", module)` and `setClientInfo("OCSID.ACTION", action)` respectively. Additionally, if `${micronaut.application.name}` is set, then it will be used to call JDBC connection `setClientInfo("OCSID.CLIENTID", appName)`. +Please make sure that if you supply `OCSID.MODULE` or `OCSID.ACTION` in `ConnectionClientInfo` property `clientInfoAttributes` these values will be ignored because +`module` and `action` attributes are going to be mapped to `OCSID.MODULE` and `OCSID.ACTION`. Please note this is currently supported only for Oracle database connections. In order to enable Oracle connection client info to be set, -need to add this property `connection.customizer.oracle.enabled=true`. +need to add this property `connection.customizer.oracleclientinfo.enabled=true`. TIP: See the guide for https://guides.micronaut.io/latest/micronaut-data-jdbc-repository.html[Access a Database with Micronaut Data JDBC] to learn more. From 4e984129fb7f68e86dd903ced55a079c3463f424 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Sun, 17 Nov 2024 21:43:57 +0100 Subject: [PATCH 15/35] Add @Experimental to ConnectionClientInfoDetails --- .../data/connection/support/ConnectionClientInfoDetails.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientInfoDetails.java b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientInfoDetails.java index 84028518d12..93b9889b05f 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientInfoDetails.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientInfoDetails.java @@ -15,6 +15,7 @@ */ package io.micronaut.data.connection.support; +import io.micronaut.core.annotation.Experimental; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; import io.micronaut.data.connection.annotation.ConnectionClientInfo; @@ -32,6 +33,7 @@ * then by default the name of the method issuing database call) * @param connectionClientInfoAttributes The arbitrary connection client info attributes to be set to {@link java.sql.Connection#setClientInfo(String, String)}. */ +@Experimental public record ConnectionClientInfoDetails(@Nullable String appName, @NonNull String module, @NonNull String action, @NonNull Map connectionClientInfoAttributes) { } From 91b3cd68cc8139b3420d3b1e61d100f29d0abfdf Mon Sep 17 00:00:00 2001 From: radovanradic Date: Mon, 18 Nov 2024 15:38:05 +0100 Subject: [PATCH 16/35] Applied more suggestions from pull request comments. --- .../HibernateConnectionOperations.java | 14 ++-- .../jdbc/operations/ConnectionCustomizer.java | 69 ------------------ ...DefaultDataSourceConnectionOperations.java | 33 ++------- .../OracleClientInfoCustomizerCondition.java | 70 +++++++++++++++++++ ...ientInfoCustomizerConnectionListener.java} | 33 ++++++--- .../interceptor/ConnectableInterceptor.java | 19 ++--- .../support/AbstractConnectionOperations.java | 53 +++++++++++++- .../support/ConnectionListener.java | 67 ++++++++++++++++++ .../OracleRepositorySetClientInfoSpec.groovy | 2 +- .../MongoConnectionOperationsImpl.java | 14 ++-- .../guide/dbc/jdbc/jdbcConfiguration.adoc | 2 +- 11 files changed, 250 insertions(+), 126 deletions(-) delete mode 100644 data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/ConnectionCustomizer.java create mode 100644 data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCustomizerCondition.java rename data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/{operations/OracleConnectionClientInfoCustomizer.java => oracle/OracleClientInfoCustomizerConnectionListener.java} (80%) create mode 100644 data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java diff --git a/data-connection-hibernate/src/main/java/io/micronaut/data/hibernate/connection/HibernateConnectionOperations.java b/data-connection-hibernate/src/main/java/io/micronaut/data/hibernate/connection/HibernateConnectionOperations.java index 00d7b29a230..da088da4608 100644 --- a/data-connection-hibernate/src/main/java/io/micronaut/data/hibernate/connection/HibernateConnectionOperations.java +++ b/data-connection-hibernate/src/main/java/io/micronaut/data/hibernate/connection/HibernateConnectionOperations.java @@ -22,12 +22,16 @@ import io.micronaut.data.connection.ConnectionDefinition; import io.micronaut.data.connection.ConnectionStatus; import io.micronaut.data.connection.support.AbstractConnectionOperations; +import io.micronaut.data.connection.support.ConnectionListener; +import io.micronaut.data.connection.support.DefaultConnectionStatus; import io.micronaut.data.hibernate.conf.RequiresSyncHibernate; import org.hibernate.Interceptor; import org.hibernate.Session; import org.hibernate.SessionBuilder; import org.hibernate.SessionFactory; +import java.util.List; + /** * The Hibernate connection operations. * @@ -45,18 +49,20 @@ public final class HibernateConnectionOperations extends AbstractConnectionOpera private final Interceptor entityInterceptor; public HibernateConnectionOperations(SessionFactory sessionFactory, - @Nullable Interceptor entityInterceptor) { + @Nullable Interceptor entityInterceptor, + List> connectionListeners) { + super(connectionListeners); this.sessionFactory = sessionFactory; this.entityInterceptor = entityInterceptor; } @Override - protected Session openConnection(ConnectionDefinition definition) { + protected ConnectionStatus doOpenConnection(ConnectionDefinition definition) { SessionBuilder sessionBuilder = sessionFactory.withOptions(); if (entityInterceptor != null) { sessionBuilder = sessionBuilder.interceptor(entityInterceptor); } - return sessionBuilder.openSession(); + return new DefaultConnectionStatus<>(sessionBuilder.openSession(), definition, true); } @Override @@ -64,7 +70,7 @@ protected void setupConnection(ConnectionStatus connectionStatus) { } @Override - protected void closeConnection(ConnectionStatus connectionStatus) { + protected void doCloseConnection(ConnectionStatus connectionStatus) { connectionStatus.getConnection().close(); } diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/ConnectionCustomizer.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/ConnectionCustomizer.java deleted file mode 100644 index b71edf1304d..00000000000 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/ConnectionCustomizer.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2017-2024 original authors - * - * 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 - * - * https://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 io.micronaut.data.connection.jdbc.operations; - -import io.micronaut.core.order.Ordered; -import io.micronaut.data.connection.ConnectionDefinition; - -import java.sql.Connection; - -/** - * Customizes connections based on the provided {@link ConnectionDefinition}. - * - * Implementations of this interface can modify the behavior of connections created by Micronaut Data JDBC. - * - * @see ConnectionDefinition - */ -public interface ConnectionCustomizer extends Ordered { - - String PREFIX = "connection.customizer"; - - /** - * Checks whether this customizer supports the given connection and connection definition. - * - * @param connection the SQL connection to be customized - * @param connectionDefinition the connection definition used to create the connection - * @return true if this customizer supports the given connection and connection definition, false otherwise - */ - boolean supportsConnection(Connection connection, ConnectionDefinition connectionDefinition); - - /** - * Called after a connection is opened. - * - * This method allows implementations to perform additional setup or configuration on the connection. - * - * @param connection the newly opened SQL connection - * @param connectionDefinition the connection definition used to create the connection - */ - void afterOpen(Connection connection, ConnectionDefinition connectionDefinition); - - /** - * Called before a connection is closed. - * - * This method allows implementations to release any resources or perform cleanup tasks related to the connection. - * - * @param connection the SQL connection about to be closed - * @param connectionDefinition the connection definition used to create the connection - */ - void beforeClose(Connection connection, ConnectionDefinition connectionDefinition); - - /** - * Returns the name of this customizer. Used for logging purposes. - * - * @return the name of this customizer - */ - String getName(); -} diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java index d88793a15b9..6da1c6903d7 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java @@ -24,6 +24,8 @@ import io.micronaut.data.connection.ConnectionStatus; import io.micronaut.data.connection.ConnectionSynchronization; import io.micronaut.data.connection.support.AbstractConnectionOperations; +import io.micronaut.data.connection.support.ConnectionListener; +import io.micronaut.data.connection.support.DefaultConnectionStatus; import io.micronaut.data.connection.support.JdbcConnectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,16 +48,15 @@ public final class DefaultDataSourceConnectionOperations extends AbstractConnect private static final Logger LOG = LoggerFactory.getLogger(DefaultDataSourceConnectionOperations.class); private final DataSource dataSource; - private final List connectionCustomizers; DefaultDataSourceConnectionOperations(DataSource dataSource, - List connectionCustomizers) { + List> connectionListeners) { + super(connectionListeners); this.dataSource = DelegatingDataSource.unwrapDataSource(dataSource); - this.connectionCustomizers = connectionCustomizers; } @Override - protected Connection openConnection(ConnectionDefinition definition) { + protected ConnectionStatus doOpenConnection(ConnectionDefinition definition) { Connection connection; try { connection = dataSource.getConnection(); @@ -63,16 +64,7 @@ protected Connection openConnection(ConnectionDefinition definition) { throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", e); } - for (ConnectionCustomizer connectionCustomizer : connectionCustomizers) { - try { - if (connectionCustomizer.supportsConnection(connection, definition)) { - connectionCustomizer.afterOpen(connection, definition); - } - } catch (Exception e) { - LOG.debug("Customizer {} failed to customize connection after open.", connectionCustomizer.getName(), e); - } - } - return connection; + return new DefaultConnectionStatus<>(connection, definition, true); } @Override @@ -95,19 +87,8 @@ public void executionComplete() { } @Override - protected void closeConnection(ConnectionStatus connectionStatus) { + protected void doCloseConnection(ConnectionStatus connectionStatus) { Connection connection = connectionStatus.getConnection(); - ConnectionDefinition definition = connectionStatus.getDefinition(); - for (ConnectionCustomizer connectionCustomizer : connectionCustomizers) { - try { - if (connectionCustomizer.supportsConnection(connection, definition)) { - connectionCustomizer.beforeClose(connection, definition); - } - } catch (Exception e) { - LOG.debug("Customizer {} failed to customize connection before close.", connectionCustomizer.getName(), e); - } - } - try { connection.close(); } catch (SQLException e) { diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCustomizerCondition.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCustomizerCondition.java new file mode 100644 index 00000000000..b5c75d0a639 --- /dev/null +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCustomizerCondition.java @@ -0,0 +1,70 @@ +/* + * Copyright 2017-2024 original authors + * + * 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 + * + * https://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 io.micronaut.data.connection.jdbc.oracle; + +import io.micronaut.context.BeanResolutionContext; +import io.micronaut.context.Qualifier; +import io.micronaut.context.condition.Condition; +import io.micronaut.context.condition.ConditionContext; +import io.micronaut.core.annotation.Internal; +import io.micronaut.core.naming.Named; +import io.micronaut.inject.BeanDefinition; + +/** + * A condition that determines whether to customize Oracle client information based on configuration properties. + * + * This condition checks if the data source dialect is set to Oracle and if the 'customize-oracle-client-info' property is enabled. + * + * @author radovanradic + * @since 4.10 + */ +@Internal +final class OracleClientInfoCustomizerCondition implements Condition { + + static final String DATASOURCES = "datasources"; + private static final Character DOT = '.'; + private static final String DIALECT = "dialect"; + private static final String ORACLE_CLIENT_INFO_ENABLED = "customize-oracle-client-info"; + private static final String ORACLE_DIALECT = "ORACLE"; + + @Override + public boolean matches(ConditionContext context) { + BeanResolutionContext beanResolutionContext = context.getBeanResolutionContext(); + String dataSourceName; + if (beanResolutionContext == null) { + return true; + } else { + Qualifier currentQualifier = beanResolutionContext.getCurrentQualifier(); + if (currentQualifier == null && context.getComponent() instanceof BeanDefinition definition) { + currentQualifier = definition.getDeclaredQualifier(); + } + if (currentQualifier instanceof Named named) { + dataSourceName = named.getName(); + } else { + dataSourceName = "default"; + } + } + + String dialectProperty = DATASOURCES + DOT + dataSourceName + DOT + DIALECT; + String dialect = context.getProperty(dialectProperty, String.class).orElse(null); + if (!ORACLE_DIALECT.equalsIgnoreCase(dialect)) { + return false; + } + + String property = DATASOURCES + DOT + dataSourceName + DOT + ORACLE_CLIENT_INFO_ENABLED; + return context.getProperty(property, Boolean.class, false); + } +} diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/OracleConnectionClientInfoCustomizer.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCustomizerConnectionListener.java similarity index 80% rename from data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/OracleConnectionClientInfoCustomizer.java rename to data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCustomizerConnectionListener.java index b0d2cf7355d..8fa5cabbdb5 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/OracleConnectionClientInfoCustomizer.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCustomizerConnectionListener.java @@ -13,16 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.micronaut.data.connection.jdbc.operations; +package io.micronaut.data.connection.jdbc.oracle; +import io.micronaut.context.annotation.EachBean; import io.micronaut.context.annotation.Requires; +import io.micronaut.core.annotation.Internal; +import io.micronaut.core.annotation.NonNull; import io.micronaut.core.util.StringUtils; import io.micronaut.data.connection.ConnectionDefinition; +import io.micronaut.data.connection.ConnectionStatus; import io.micronaut.data.connection.support.ConnectionClientInfoDetails; -import jakarta.inject.Singleton; +import io.micronaut.data.connection.support.ConnectionListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLClientInfoException; import java.sql.SQLException; @@ -31,16 +36,18 @@ import java.util.concurrent.ConcurrentHashMap; /** - * A customizer for Oracle database connections that sets and clears client information. + * A customizer for Oracle database connections that sets client information after opening and clears before closing. * * This customizer checks if the connection is an Oracle database connection and then sets the client information * (client ID, module, and action) after opening the connection. It also clears these properties before closing the connection. * + * @author radovanradic * @since 4.10 */ -@Singleton -@Requires(property = ConnectionCustomizer.PREFIX + ".oracleclientinfo.enabled", value = "true", defaultValue = "false") -final class OracleConnectionClientInfoCustomizer implements ConnectionCustomizer { +@EachBean(DataSource.class) +@Requires(condition = OracleClientInfoCustomizerCondition.class) +@Internal +final class OracleClientInfoCustomizerConnectionListener implements ConnectionListener { /** * Constant for the Oracle connection client info client ID property name. @@ -60,20 +67,22 @@ final class OracleConnectionClientInfoCustomizer implements ConnectionCustomizer private static final String ORACLE_CONNECTION_DATABASE_PRODUCT_NAME = "Oracle"; private static final List RESERVED_CLIENT_INFO_NAMES = List.of(ORACLE_CLIENT_ID, ORACLE_MODULE, ORACLE_ACTION); - private static final Logger LOG = LoggerFactory.getLogger(OracleConnectionClientInfoCustomizer.class); + private static final Logger LOG = LoggerFactory.getLogger(OracleClientInfoCustomizerConnectionListener.class); private final Map connectionSupportedMap = new ConcurrentHashMap<>(20); @Override - public boolean supportsConnection(Connection connection, ConnectionDefinition connectionDefinition) { - return connectionSupportedMap.computeIfAbsent(connection, this::isOracleConnection); + public boolean supportsConnection(@NonNull ConnectionStatus connectionStatus) { + return connectionSupportedMap.computeIfAbsent(connectionStatus.getConnection(), this::isOracleConnection); } @Override - public void afterOpen(Connection connection, ConnectionDefinition connectionDefinition) { + public void afterOpen(@NonNull ConnectionStatus connectionStatus) { + ConnectionDefinition connectionDefinition = connectionStatus.getDefinition(); // Set client info for connection if Oracle connection after connection is opened ConnectionClientInfoDetails connectionClientInfo = connectionDefinition.connectionClientInfo(); if (connectionClientInfo != null) { + Connection connection = connectionStatus.getConnection(); LOG.trace("Setting connection tracing info to the Oracle connection"); try { if (connectionClientInfo.appName() != null) { @@ -97,11 +106,13 @@ public void afterOpen(Connection connection, ConnectionDefinition connectionDefi } @Override - public void beforeClose(Connection connection, ConnectionDefinition connectionDefinition) { + public void beforeClose(@NonNull ConnectionStatus connectionStatus) { // Clear client info for connection if it was Oracle connection and client info was set previously + ConnectionDefinition connectionDefinition = connectionStatus.getDefinition(); ConnectionClientInfoDetails connectionClientInfo = connectionDefinition.connectionClientInfo(); if (connectionClientInfo != null) { try { + Connection connection = connectionStatus.getConnection(); connection.setClientInfo(ORACLE_CLIENT_ID, null); connection.setClientInfo(ORACLE_MODULE, null); connection.setClientInfo(ORACLE_ACTION, null); diff --git a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java index 74db728c964..617c39f23eb 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java @@ -43,7 +43,7 @@ import reactor.core.publisher.Mono; import java.time.Duration; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -68,6 +68,7 @@ public final class ConnectableInterceptor implements MethodInterceptor EXECUTABLE_METHOD_STRING_MAP = new ConcurrentHashMap<>(100); private final Map connectionInvocationMap = new ConcurrentHashMap<>(30); @NonNull @@ -234,18 +235,20 @@ public static ConnectionDefinition getConnectionDefinition(@Nullable InvocationC String module = annotation.stringValue(MODULE_MEMBER).orElse(null); String action = annotation.stringValue(ACTION_MEMBER).orElse(null); if (module == null) { - if (context != null) { - Class clazz = context.getTarget().getClass(); - module = clazz.getName().replace(INTERCEPTED_SUFFIX, ""); - } else { - module = executableMethod.getDeclaringType().getName(); - } + module = EXECUTABLE_METHOD_STRING_MAP.computeIfAbsent(executableMethod, executableMethod1 -> { + if (context != null) { + Class clazz = context.getTarget().getClass(); + return clazz.getName().replace(INTERCEPTED_SUFFIX, ""); + } else { + return executableMethod.getDeclaringType().getName(); + } + }); } if (action == null) { action = executableMethod.getMethodName(); } List> clientInfoAttributes = annotation.getAnnotations(CLIENT_INFO_ATTRIBUTES_MEMBER, ConnectionClientInfoAttribute.class); - Map additionalClientInfoAttributes = new HashMap<>(clientInfoAttributes.size()); + Map additionalClientInfoAttributes = new LinkedHashMap<>(clientInfoAttributes.size()); for (AnnotationValue clientInfoAttribute : clientInfoAttributes) { String name = clientInfoAttribute.getRequiredValue(NAME_MEMBER, String.class); String value = clientInfoAttribute.getRequiredValue(VALUE_MEMBER, String.class); diff --git a/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java b/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java index 665ce79aac3..4631a2fbe48 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java @@ -29,6 +29,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; @@ -45,13 +46,41 @@ public abstract class AbstractConnectionOperations implements ConnectionOpera protected final Logger logger = LoggerFactory.getLogger(getClass()); + private final List> connectionListeners; + + protected AbstractConnectionOperations(List> connectionListeners) { + this.connectionListeners = connectionListeners; + } + /** * Opens a new connection. * * @param definition The connection definition * @return The connection */ - protected abstract C openConnection(ConnectionDefinition definition); + protected final C openConnection(ConnectionDefinition definition) { + ConnectionStatus connectionStatus = doOpenConnection(definition); + for (ConnectionListener connectionListener : connectionListeners) { + try { + if (connectionListener.supportsConnection(connectionStatus)) { + connectionListener.afterOpen(connectionStatus); + } + } catch (Exception e) { + logger.debug("Customizer {} failed to customize connection after open.", connectionListener.getName(), e); + } + } + return connectionStatus.getConnection(); + } + + /** + * Opens a new connection based on the provided connection definition. + * + * This method should be implemented by subclasses to provide the actual logic for opening a connection. + * + * @param definition the connection definition to use when opening the connection + * @return the status of the newly opened connection + */ + protected abstract ConnectionStatus doOpenConnection(ConnectionDefinition definition); /** * Setups the connection after it have been open. @@ -65,7 +94,27 @@ public abstract class AbstractConnectionOperations implements ConnectionOpera * * @param connectionStatus The connection status */ - protected abstract void closeConnection(ConnectionStatus connectionStatus); + protected final void closeConnection(ConnectionStatus connectionStatus) { + for (ConnectionListener connectionListener : connectionListeners) { + try { + if (connectionListener.supportsConnection(connectionStatus)) { + connectionListener.beforeClose(connectionStatus); + } + } catch (Exception e) { + logger.debug("Customizer {} failed to customize connection before close.", connectionListener.getName(), e); + } + } + doCloseConnection(connectionStatus); + } + + /** + * Closes the connection represented by the given connection status. + * + * This method should be implemented by subclasses to provide the actual logic for closing a connection. + * + * @param connectionStatus the status of the connection to be closed + */ + protected abstract void doCloseConnection(ConnectionStatus connectionStatus); @Override public final Optional> findConnectionStatus() { diff --git a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java new file mode 100644 index 00000000000..449a3df40a9 --- /dev/null +++ b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java @@ -0,0 +1,67 @@ +/* + * Copyright 2017-2024 original authors + * + * 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 + * + * https://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 io.micronaut.data.connection.support; + +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.order.Ordered; +import io.micronaut.data.connection.ConnectionStatus; + +/** + * Customizes connections based on the provided {@link ConnectionStatus}. + * + * Implementations of this interface can modify the behavior of connections created by Micronaut Data. + * + * @see ConnectionStatus + * @param The connection type + * + * @author radovanradic + * @since 4.10 + */ +public interface ConnectionListener extends Ordered { + + /** + * Checks whether this connection listener supports the given connection status. + * + * @param connectionStatus The connection status + * @return true if this listener supports the given connection status, false otherwise + */ + boolean supportsConnection(@NonNull ConnectionStatus connectionStatus); + + /** + * Called after a connection is opened. + * + * This method allows implementations to perform additional setup or configuration on the connection. + * + * @param connectionStatus The newly opened connection + */ + void afterOpen(@NonNull ConnectionStatus connectionStatus); + + /** + * Called before a connection is closed. + * + * This method allows implementations to release any resources or perform cleanup tasks related to the connection. + * + * @param connectionStatus The connection statucs about to be closed + */ + void beforeClose(@NonNull ConnectionStatus connectionStatus); + + /** + * Returns the name of this listener. Used for logging purposes. + * + * @return the name of this listener + */ + String getName(); +} diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy index e3ea99c7e39..a1512930e2a 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy @@ -24,7 +24,7 @@ class OracleRepositorySetClientInfoSpec extends OracleXERepositorySpec { Map getProperties() { return super.getProperties() + [ 'micronaut.application.name': 'OracleRepositorySetClientInfoSpec', - 'connection.customizer.oracleclientinfo.enabled': 'true' + 'datasources.default.customize-oracle-client-info': 'true' ] } } diff --git a/data-mongodb/src/main/java/io/micronaut/data/mongodb/session/MongoConnectionOperationsImpl.java b/data-mongodb/src/main/java/io/micronaut/data/mongodb/session/MongoConnectionOperationsImpl.java index 023af65799d..971be4bb4b7 100644 --- a/data-mongodb/src/main/java/io/micronaut/data/mongodb/session/MongoConnectionOperationsImpl.java +++ b/data-mongodb/src/main/java/io/micronaut/data/mongodb/session/MongoConnectionOperationsImpl.java @@ -22,6 +22,10 @@ import io.micronaut.data.connection.ConnectionDefinition; import io.micronaut.data.connection.ConnectionStatus; import io.micronaut.data.connection.support.AbstractConnectionOperations; +import io.micronaut.data.connection.support.ConnectionListener; +import io.micronaut.data.connection.support.DefaultConnectionStatus; + +import java.util.List; @Internal @EachBean(MongoClient.class) @@ -29,13 +33,15 @@ final class MongoConnectionOperationsImpl extends AbstractConnectionOperations> connectionListeners) { + super(connectionListeners); this.mongoClient = mongoClient; } @Override - protected ClientSession openConnection(ConnectionDefinition definition) { - return mongoClient.startSession(); + protected ConnectionStatus doOpenConnection(ConnectionDefinition definition) { + return new DefaultConnectionStatus<>(mongoClient.startSession(), definition, true); } @Override @@ -46,7 +52,7 @@ protected void setupConnection(ConnectionStatus connectionStatus) } @Override - protected void closeConnection(ConnectionStatus connectionStatus) { + protected void doCloseConnection(ConnectionStatus connectionStatus) { connectionStatus.getConnection().close(); } } diff --git a/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc b/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc index 1c36ff082d8..f4b141acb7b 100644 --- a/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc +++ b/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc @@ -76,7 +76,7 @@ Please make sure that if you supply `OCSID.MODULE` or `OCSID.ACTION` in `Connect `module` and `action` attributes are going to be mapped to `OCSID.MODULE` and `OCSID.ACTION`. Please note this is currently supported only for Oracle database connections. In order to enable Oracle connection client info to be set, -need to add this property `connection.customizer.oracleclientinfo.enabled=true`. +need to add this property `datasources.default.customize-oracle-client-info=true`. TIP: See the guide for https://guides.micronaut.io/latest/micronaut-data-jdbc-repository.html[Access a Database with Micronaut Data JDBC] to learn more. From fdff6222acf84c47632c1dd240b313cd9f690f06 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Mon, 18 Nov 2024 15:55:48 +0100 Subject: [PATCH 17/35] Updated comments --- .../oracle/OracleClientInfoCustomizerConnectionListener.java | 2 +- .../io/micronaut/data/connection/ConnectionDefinition.java | 1 + .../data/connection/annotation/ConnectionClientInfo.java | 3 +++ .../connection/support/AbstractConnectionOperations.java | 4 ++-- .../data/connection/support/ConnectionListener.java | 5 +++-- .../jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy | 2 +- 6 files changed, 11 insertions(+), 6 deletions(-) diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCustomizerConnectionListener.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCustomizerConnectionListener.java index 8fa5cabbdb5..31b07e26b40 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCustomizerConnectionListener.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCustomizerConnectionListener.java @@ -130,7 +130,7 @@ public void beforeClose(@NonNull ConnectionStatus connectionStatus) @Override public String getName() { - return "Oracle Connection Customizer"; + return "Oracle Connection Client Info Customizer"; } /** diff --git a/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java b/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java index 1e6ddacf1cf..858ef306631 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java @@ -107,6 +107,7 @@ enum Propagation { * If no connection client information has been set, this method will return null. * * @return An instance of {@link ConnectionClientInfoDetails} representing the connection client information, or null if not set. + * @since 4.10 */ @Nullable ConnectionClientInfoDetails connectionClientInfo(); diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java index 985b6f07b4d..867e08f7362 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java @@ -25,6 +25,9 @@ /** * An annotation used to set client info for the connection. + * + * @author radovanradic + * @since 4.10 */ @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) diff --git a/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java b/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java index 4631a2fbe48..f7936cfc842 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java @@ -66,7 +66,7 @@ protected final C openConnection(ConnectionDefinition definition) { connectionListener.afterOpen(connectionStatus); } } catch (Exception e) { - logger.debug("Customizer {} failed to customize connection after open.", connectionListener.getName(), e); + logger.debug("An error occurred when calling listener {} afterOpen.", connectionListener.getName(), e); } } return connectionStatus.getConnection(); @@ -101,7 +101,7 @@ protected final void closeConnection(ConnectionStatus connectionStatus) { connectionListener.beforeClose(connectionStatus); } } catch (Exception e) { - logger.debug("Customizer {} failed to customize connection before close.", connectionListener.getName(), e); + logger.debug("An error occurred when calling listener {} beforeClose.", connectionListener.getName(), e); } } doCloseConnection(connectionStatus); diff --git a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java index 449a3df40a9..3e1761c31e8 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java @@ -20,9 +20,10 @@ import io.micronaut.data.connection.ConnectionStatus; /** - * Customizes connections based on the provided {@link ConnectionStatus}. + * Handles connection after open or before close events based on the provided {@link ConnectionStatus}. * - * Implementations of this interface can modify the behavior of connections created by Micronaut Data. + * Implementations of this interface can modify the behavior of connections created by Micronaut Data + * or do what might be needed after connection open or before close. * * @see ConnectionStatus * @param The connection type diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy index a1512930e2a..33021d6553d 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy @@ -16,7 +16,7 @@ package io.micronaut.data.jdbc.oraclexe /** - * The test + * The test for setting oracle connection client info. */ class OracleRepositorySetClientInfoSpec extends OracleXERepositorySpec { From d56a78ba5993b2c873a56716b54d77994c15c048 Mon Sep 17 00:00:00 2001 From: Radovan Radic Date: Mon, 18 Nov 2024 16:56:34 +0100 Subject: [PATCH 18/35] Update src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc Co-authored-by: Graeme Rocher --- src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc b/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc index f4b141acb7b..b42de93888f 100644 --- a/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc +++ b/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc @@ -64,7 +64,7 @@ IMPORTANT: The dialect setting in configuration does *not* replace the need to e === Connection client info tracing In order to trace SQL calls using `java.sql.Connection.setClientInfo(String, String)` method, you can -annotate repository with @api:data.connection.annotation.ConnectionClientInfo[] annotation. If boolean field `enabled` is set to false, then Micronaut will not issue `setClientInfo` calls to the JDBC connection. +annotate a repository with the @api:data.connection.annotation.ConnectionClientInfo[] annotation. If the boolean member `enabled` is set to `false`, then Micronaut will not issue `setClientInfo` calls to the JDBC connection. Fields in given annotation `module` and `action` can set custom module and action. If omitted, then module value will default to the class name (usually Micronaut Data repository class) annotated with the given annotation and action to the name of the method being executed. There is also property `clientInfoAttributes` allowing to set arbitrary client info attributes to be set to the connection client info. From c25d74252f152a99b5f5546612d86a15f6751768 Mon Sep 17 00:00:00 2001 From: Radovan Radic Date: Mon, 18 Nov 2024 16:56:57 +0100 Subject: [PATCH 19/35] Update src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc Co-authored-by: Graeme Rocher --- src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc b/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc index b42de93888f..6e2aee38ba1 100644 --- a/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc +++ b/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc @@ -65,10 +65,11 @@ IMPORTANT: The dialect setting in configuration does *not* replace the need to e In order to trace SQL calls using `java.sql.Connection.setClientInfo(String, String)` method, you can annotate a repository with the @api:data.connection.annotation.ConnectionClientInfo[] annotation. If the boolean member `enabled` is set to `false`, then Micronaut will not issue `setClientInfo` calls to the JDBC connection. -Fields in given annotation `module` and `action` can set custom module and action. If omitted, then module value will default +The annotation members `module` and `action` can set a custom module and action. If omitted, then the name of the module will default to the class name (usually Micronaut Data repository class) annotated with the given annotation and action to the name of the method -being executed. There is also property `clientInfoAttributes` allowing to set arbitrary client info attributes to be set to the connection client info. -Annotation can be used on class or method, customizing module or action. +being executed. In addition, the member `clientInfoAttributes` can be used to set arbitrary client info attributes to be set passed to the connection client info. + +Note that the @api:data.connection.annotation.ConnectionClientInfo[] annotation can be used on either the class or the method, thus allowing customizaction of the module or action individually. For Oracle database, these values will be set to the JDBC connection using `setClientInfo("OCSID.MODULE", module)` and `setClientInfo("OCSID.ACTION", action)` respectively. Additionally, if `${micronaut.application.name}` is set, then it will be used to call JDBC connection `setClientInfo("OCSID.CLIENTID", appName)`. From f6b7f803a5b43e0f76f54d5738b049bd853ed81f Mon Sep 17 00:00:00 2001 From: Radovan Radic Date: Mon, 18 Nov 2024 16:57:08 +0100 Subject: [PATCH 20/35] Update src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc Co-authored-by: Graeme Rocher --- src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc b/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc index 6e2aee38ba1..e4ef26e6278 100644 --- a/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc +++ b/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc @@ -76,8 +76,8 @@ Additionally, if `${micronaut.application.name}` is set, then it will be used to Please make sure that if you supply `OCSID.MODULE` or `OCSID.ACTION` in `ConnectionClientInfo` property `clientInfoAttributes` these values will be ignored because `module` and `action` attributes are going to be mapped to `OCSID.MODULE` and `OCSID.ACTION`. -Please note this is currently supported only for Oracle database connections. In order to enable Oracle connection client info to be set, -need to add this property `datasources.default.customize-oracle-client-info=true`. +Please note this feature is currently supported only for Oracle database connections. In order to enable Oracle connection client info to be set, +you need to specify the configuration property `datasources.default.enable-oracle-client-info=true` on a per datasource basis. TIP: See the guide for https://guides.micronaut.io/latest/micronaut-data-jdbc-repository.html[Access a Database with Micronaut Data JDBC] to learn more. From 1f9e9e9e1951878686bb1fc5bcab77892ac919f6 Mon Sep 17 00:00:00 2001 From: Radovan Radic Date: Mon, 18 Nov 2024 16:57:18 +0100 Subject: [PATCH 21/35] Update src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc Co-authored-by: Graeme Rocher --- src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc b/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc index e4ef26e6278..de346ac974b 100644 --- a/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc +++ b/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc @@ -73,8 +73,8 @@ Note that the @api:data.connection.annotation.ConnectionClientInfo[] annotation For Oracle database, these values will be set to the JDBC connection using `setClientInfo("OCSID.MODULE", module)` and `setClientInfo("OCSID.ACTION", action)` respectively. Additionally, if `${micronaut.application.name}` is set, then it will be used to call JDBC connection `setClientInfo("OCSID.CLIENTID", appName)`. -Please make sure that if you supply `OCSID.MODULE` or `OCSID.ACTION` in `ConnectionClientInfo` property `clientInfoAttributes` these values will be ignored because -`module` and `action` attributes are going to be mapped to `OCSID.MODULE` and `OCSID.ACTION`. +Note that if you supply the attributes `OCSID.MODULE` or `OCSID.ACTION` in the `clientInfoAttributes` member of the `@ConnectionClientInfo` annotation these values will be ignored because +`module` and `action` attributes are already mapped to `OCSID.MODULE` and `OCSID.ACTION`. Please note this feature is currently supported only for Oracle database connections. In order to enable Oracle connection client info to be set, you need to specify the configuration property `datasources.default.enable-oracle-client-info=true` on a per datasource basis. From c68b657aae0eb2235fdde08b0f291639012cd5f3 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Mon, 18 Nov 2024 17:06:17 +0100 Subject: [PATCH 22/35] Renamed classes as suggested and also property to enable Oracle client info connection listener --- ...tomizerCondition.java => OracleClientInfoCondition.java} | 4 ++-- ...istener.java => OracleClientInfoConnectionListener.java} | 6 +++--- .../jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) rename data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/{OracleClientInfoCustomizerCondition.java => OracleClientInfoCondition.java} (94%) rename data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/{OracleClientInfoCustomizerConnectionListener.java => OracleClientInfoConnectionListener.java} (96%) diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCustomizerCondition.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCondition.java similarity index 94% rename from data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCustomizerCondition.java rename to data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCondition.java index b5c75d0a639..cca3070c1d8 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCustomizerCondition.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCondition.java @@ -32,12 +32,12 @@ * @since 4.10 */ @Internal -final class OracleClientInfoCustomizerCondition implements Condition { +final class OracleClientInfoCondition implements Condition { static final String DATASOURCES = "datasources"; private static final Character DOT = '.'; private static final String DIALECT = "dialect"; - private static final String ORACLE_CLIENT_INFO_ENABLED = "customize-oracle-client-info"; + private static final String ORACLE_CLIENT_INFO_ENABLED = "enable-oracle-client-info"; private static final String ORACLE_DIALECT = "ORACLE"; @Override diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCustomizerConnectionListener.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java similarity index 96% rename from data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCustomizerConnectionListener.java rename to data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java index 31b07e26b40..1a1081d06fb 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCustomizerConnectionListener.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java @@ -45,9 +45,9 @@ * @since 4.10 */ @EachBean(DataSource.class) -@Requires(condition = OracleClientInfoCustomizerCondition.class) +@Requires(condition = OracleClientInfoCondition.class) @Internal -final class OracleClientInfoCustomizerConnectionListener implements ConnectionListener { +final class OracleClientInfoConnectionListener implements ConnectionListener { /** * Constant for the Oracle connection client info client ID property name. @@ -67,7 +67,7 @@ final class OracleClientInfoCustomizerConnectionListener implements ConnectionLi private static final String ORACLE_CONNECTION_DATABASE_PRODUCT_NAME = "Oracle"; private static final List RESERVED_CLIENT_INFO_NAMES = List.of(ORACLE_CLIENT_ID, ORACLE_MODULE, ORACLE_ACTION); - private static final Logger LOG = LoggerFactory.getLogger(OracleClientInfoCustomizerConnectionListener.class); + private static final Logger LOG = LoggerFactory.getLogger(OracleClientInfoConnectionListener.class); private final Map connectionSupportedMap = new ConcurrentHashMap<>(20); diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy index 33021d6553d..853b1ff09db 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy @@ -24,7 +24,7 @@ class OracleRepositorySetClientInfoSpec extends OracleXERepositorySpec { Map getProperties() { return super.getProperties() + [ 'micronaut.application.name': 'OracleRepositorySetClientInfoSpec', - 'datasources.default.customize-oracle-client-info': 'true' + 'datasources.default.enable-oracle-client-info': 'true' ] } } From 06901f30628a9d793569b90f6cdf98ef1dadb6b7 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Tue, 19 Nov 2024 15:30:47 +0100 Subject: [PATCH 23/35] Refactoring according to suggestions in PR comments. --- .../HibernateConnectionOperations.java | 13 +- data-connection-jdbc/build.gradle | 1 + ...DefaultDataSourceConnectionOperations.java | 21 +--- .../OracleClientInfoConnectionListener.java | 112 +++++++++++++----- .../data/connection/ConnectionDefinition.java | 13 +- .../DefaultConnectionDefinition.java | 21 ++-- .../connection/annotation/Connectable.java | 1 + .../annotation/ConnectionClientInfo.java | 23 ---- .../interceptor/ConnectableInterceptor.java | 77 +----------- .../support/AbstractConnectionOperations.java | 93 +++++++-------- .../support/ConnectionClientInfoDetails.java | 39 ------ .../support/ConnectionListener.java | 19 ++- .../oraclexe/OracleXEAuthorRepository.java | 3 +- .../jdbc/oraclexe/OracleXEBookRepository.java | 5 +- .../MongoConnectionOperationsImpl.java | 14 +-- .../guide/dbc/jdbc/jdbcConfiguration.adoc | 19 +-- 16 files changed, 188 insertions(+), 286 deletions(-) delete mode 100644 data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientInfoDetails.java diff --git a/data-connection-hibernate/src/main/java/io/micronaut/data/hibernate/connection/HibernateConnectionOperations.java b/data-connection-hibernate/src/main/java/io/micronaut/data/hibernate/connection/HibernateConnectionOperations.java index da088da4608..c49d6f07e1a 100644 --- a/data-connection-hibernate/src/main/java/io/micronaut/data/hibernate/connection/HibernateConnectionOperations.java +++ b/data-connection-hibernate/src/main/java/io/micronaut/data/hibernate/connection/HibernateConnectionOperations.java @@ -22,15 +22,12 @@ import io.micronaut.data.connection.ConnectionDefinition; import io.micronaut.data.connection.ConnectionStatus; import io.micronaut.data.connection.support.AbstractConnectionOperations; -import io.micronaut.data.connection.support.ConnectionListener; -import io.micronaut.data.connection.support.DefaultConnectionStatus; import io.micronaut.data.hibernate.conf.RequiresSyncHibernate; import org.hibernate.Interceptor; import org.hibernate.Session; import org.hibernate.SessionBuilder; import org.hibernate.SessionFactory; -import java.util.List; /** * The Hibernate connection operations. @@ -49,20 +46,18 @@ public final class HibernateConnectionOperations extends AbstractConnectionOpera private final Interceptor entityInterceptor; public HibernateConnectionOperations(SessionFactory sessionFactory, - @Nullable Interceptor entityInterceptor, - List> connectionListeners) { - super(connectionListeners); + @Nullable Interceptor entityInterceptor) { this.sessionFactory = sessionFactory; this.entityInterceptor = entityInterceptor; } @Override - protected ConnectionStatus doOpenConnection(ConnectionDefinition definition) { + protected Session openConnection(ConnectionDefinition definition) { SessionBuilder sessionBuilder = sessionFactory.withOptions(); if (entityInterceptor != null) { sessionBuilder = sessionBuilder.interceptor(entityInterceptor); } - return new DefaultConnectionStatus<>(sessionBuilder.openSession(), definition, true); + return sessionBuilder.openSession(); } @Override @@ -70,7 +65,7 @@ protected void setupConnection(ConnectionStatus connectionStatus) { } @Override - protected void doCloseConnection(ConnectionStatus connectionStatus) { + protected void closeConnection(ConnectionStatus connectionStatus) { connectionStatus.getConnection().close(); } diff --git a/data-connection-jdbc/build.gradle b/data-connection-jdbc/build.gradle index 7df18343c9b..55ee535bc48 100644 --- a/data-connection-jdbc/build.gradle +++ b/data-connection-jdbc/build.gradle @@ -13,6 +13,7 @@ dependencies { api projects.micronautDataConnection implementation mnSql.micronaut.jdbc implementation mn.micronaut.aop + implementation mn.micronaut.runtime testAnnotationProcessor mn.micronaut.inject.java diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java index 6da1c6903d7..e2fc969c852 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java @@ -24,8 +24,6 @@ import io.micronaut.data.connection.ConnectionStatus; import io.micronaut.data.connection.ConnectionSynchronization; import io.micronaut.data.connection.support.AbstractConnectionOperations; -import io.micronaut.data.connection.support.ConnectionListener; -import io.micronaut.data.connection.support.DefaultConnectionStatus; import io.micronaut.data.connection.support.JdbcConnectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,28 +47,22 @@ public final class DefaultDataSourceConnectionOperations extends AbstractConnect private static final Logger LOG = LoggerFactory.getLogger(DefaultDataSourceConnectionOperations.class); private final DataSource dataSource; - DefaultDataSourceConnectionOperations(DataSource dataSource, - List> connectionListeners) { - super(connectionListeners); + DefaultDataSourceConnectionOperations(DataSource dataSource) { this.dataSource = DelegatingDataSource.unwrapDataSource(dataSource); } @Override - protected ConnectionStatus doOpenConnection(ConnectionDefinition definition) { - Connection connection; + protected Connection openConnection(ConnectionDefinition definition) { try { - connection = dataSource.getConnection(); + return dataSource.getConnection(); } catch (SQLException e) { throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", e); } - - return new DefaultConnectionStatus<>(connection, definition, true); } @Override protected void setupConnection(ConnectionStatus connectionStatus) { - ConnectionDefinition connectionDefinition = connectionStatus.getDefinition(); - connectionDefinition.isReadOnly().ifPresent(readOnly -> { + connectionStatus.getDefinition().isReadOnly().ifPresent(readOnly -> { List onCompleteCallbacks = new ArrayList<>(1); JdbcConnectionUtils.applyReadOnly(LOG, connectionStatus.getConnection(), readOnly, onCompleteCallbacks); if (!onCompleteCallbacks.isEmpty()) { @@ -87,10 +79,9 @@ public void executionComplete() { } @Override - protected void doCloseConnection(ConnectionStatus connectionStatus) { - Connection connection = connectionStatus.getConnection(); + protected void closeConnection(ConnectionStatus connectionStatus) { try { - connection.close(); + connectionStatus.getConnection().close(); } catch (SQLException e) { throw new ConnectionException("Failed to close the connection: " + e.getMessage(), e); } diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java index 1a1081d06fb..f297df66541 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java @@ -15,15 +15,25 @@ */ package io.micronaut.data.connection.jdbc.oracle; +import io.micronaut.aop.MethodInvocationContext; +import io.micronaut.context.annotation.Context; import io.micronaut.context.annotation.EachBean; +import io.micronaut.context.annotation.Parameter; import io.micronaut.context.annotation.Requires; +import io.micronaut.core.annotation.AnnotationMetadata; +import io.micronaut.core.annotation.AnnotationValue; import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.annotation.Nullable; import io.micronaut.core.util.StringUtils; import io.micronaut.data.connection.ConnectionDefinition; import io.micronaut.data.connection.ConnectionStatus; -import io.micronaut.data.connection.support.ConnectionClientInfoDetails; +import io.micronaut.data.connection.annotation.ConnectionClientInfo; +import io.micronaut.data.connection.annotation.ConnectionClientInfoAttribute; +import io.micronaut.data.connection.jdbc.advice.DelegatingDataSource; +import io.micronaut.data.connection.support.AbstractConnectionOperations; import io.micronaut.data.connection.support.ConnectionListener; +import io.micronaut.runtime.ApplicationConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,6 +41,7 @@ import java.sql.Connection; import java.sql.SQLClientInfoException; import java.sql.SQLException; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -46,9 +57,16 @@ */ @EachBean(DataSource.class) @Requires(condition = OracleClientInfoCondition.class) +@Context @Internal final class OracleClientInfoConnectionListener implements ConnectionListener { + private static final String ENABLED = "enabled"; + private static final String NAME_MEMBER = "name"; + private static final String VALUE_MEMBER = "value"; + private static final String CLIENT_INFO_ATTRIBUTES_MEMBER = "clientInfoAttributes"; + private static final String INTERCEPTED_SUFFIX = "$Intercepted"; + /** * Constant for the Oracle connection client info client ID property name. */ @@ -65,39 +83,40 @@ final class OracleClientInfoConnectionListener implements ConnectionListener RESERVED_CLIENT_INFO_NAMES = List.of(ORACLE_CLIENT_ID, ORACLE_MODULE, ORACLE_ACTION); private static final Logger LOG = LoggerFactory.getLogger(OracleClientInfoConnectionListener.class); + private static final Map METHOD_INVOCATION_CONTEXT_STRING_MAP = new ConcurrentHashMap<>(100); - private final Map connectionSupportedMap = new ConcurrentHashMap<>(20); + @Nullable + private final String applicationName; - @Override - public boolean supportsConnection(@NonNull ConnectionStatus connectionStatus) { - return connectionSupportedMap.computeIfAbsent(connectionStatus.getConnection(), this::isOracleConnection); + OracleClientInfoConnectionListener(@NonNull DataSource dataSource, + @NonNull @Parameter AbstractConnectionOperations connectionOperations, + @Nullable ApplicationConfiguration applicationConfiguration) { + this.applicationName = applicationConfiguration.getName().orElse(null); + try { + Connection connection = DelegatingDataSource.unwrapDataSource(dataSource).getConnection(); + if (isOracleConnection(connection)) { + connectionOperations.addConnectionListener(this); + } + } catch (SQLException e) { + LOG.error("Failed to get connection for oracle connection listener", e); + } } @Override public void afterOpen(@NonNull ConnectionStatus connectionStatus) { ConnectionDefinition connectionDefinition = connectionStatus.getDefinition(); // Set client info for connection if Oracle connection after connection is opened - ConnectionClientInfoDetails connectionClientInfo = connectionDefinition.connectionClientInfo(); + Map connectionClientInfo = getConnectionClientInfo(connectionDefinition); if (connectionClientInfo != null) { Connection connection = connectionStatus.getConnection(); LOG.trace("Setting connection tracing info to the Oracle connection"); try { - if (connectionClientInfo.appName() != null) { - connection.setClientInfo(ORACLE_CLIENT_ID, connectionClientInfo.appName()); - } - connection.setClientInfo(ORACLE_MODULE, connectionClientInfo.module()); - connection.setClientInfo(ORACLE_ACTION, connectionClientInfo.action()); - for (Map.Entry additionalInfo : connectionClientInfo.connectionClientInfoAttributes().entrySet()) { + for (Map.Entry additionalInfo : connectionClientInfo.entrySet()) { String name = additionalInfo.getKey(); - if (RESERVED_CLIENT_INFO_NAMES.contains(name)) { - LOG.debug("Connection client info attribute {} already set. Skip setting value from the arbitrary attributes.", name); - } else { - String value = additionalInfo.getValue(); - connection.setClientInfo(name, value); - } + String value = additionalInfo.getValue(); + connection.setClientInfo(name, value); } } catch (SQLClientInfoException e) { LOG.debug("Failed to set connection tracing info", e); @@ -109,18 +128,12 @@ public void afterOpen(@NonNull ConnectionStatus connectionStatus) { public void beforeClose(@NonNull ConnectionStatus connectionStatus) { // Clear client info for connection if it was Oracle connection and client info was set previously ConnectionDefinition connectionDefinition = connectionStatus.getDefinition(); - ConnectionClientInfoDetails connectionClientInfo = connectionDefinition.connectionClientInfo(); + Map connectionClientInfo = getConnectionClientInfo(connectionDefinition); if (connectionClientInfo != null) { try { Connection connection = connectionStatus.getConnection(); - connection.setClientInfo(ORACLE_CLIENT_ID, null); - connection.setClientInfo(ORACLE_MODULE, null); - connection.setClientInfo(ORACLE_ACTION, null); - for (Map.Entry additionalInfo : connectionClientInfo.connectionClientInfoAttributes().entrySet()) { - String name = additionalInfo.getKey(); - if (!RESERVED_CLIENT_INFO_NAMES.contains(name)) { - connection.setClientInfo(name, null); - } + for (String key : connectionClientInfo.keySet()) { + connection.setClientInfo(key, null); } } catch (SQLClientInfoException e) { LOG.debug("Failed to clear connection tracing info", e); @@ -148,4 +161,47 @@ private boolean isOracleConnection(Connection connection) { return false; } } + + /** + * Gets connection client info from the {@link ConnectionClientInfo} annotation. + * + * @param connectionDefinition The connection definition + * @return The connection client info or null if not configured to be used + */ + private @Nullable Map getConnectionClientInfo(@NonNull ConnectionDefinition connectionDefinition) { + AnnotationMetadata annotationMetadata = connectionDefinition.getAnnotationMetadata(); + AnnotationValue annotation = annotationMetadata.getAnnotation(io.micronaut.data.connection.annotation.ConnectionClientInfo.class); + if (annotation == null) { + return null; + } + boolean connectionClientInfoEnabled = annotation.booleanValue(ENABLED).orElse(true); + if (!connectionClientInfoEnabled) { + return null; + } + if (annotationMetadata instanceof MethodInvocationContext methodInvocationContext) { + List> clientInfoAttributes = annotation.getAnnotations(CLIENT_INFO_ATTRIBUTES_MEMBER, ConnectionClientInfoAttribute.class); + Map additionalClientInfoAttributes = new LinkedHashMap<>(clientInfoAttributes.size()); + for (AnnotationValue clientInfoAttribute : clientInfoAttributes) { + String name = clientInfoAttribute.getRequiredValue(NAME_MEMBER, String.class); + String value = clientInfoAttribute.getRequiredValue(VALUE_MEMBER, String.class); + additionalClientInfoAttributes.put(name, value); + } + if (StringUtils.isNotEmpty(applicationName) && !additionalClientInfoAttributes.containsKey(ORACLE_CLIENT_ID)) { + additionalClientInfoAttributes.put(ORACLE_CLIENT_ID, applicationName); + } + if (!additionalClientInfoAttributes.containsKey(ORACLE_MODULE)) { + String module = METHOD_INVOCATION_CONTEXT_STRING_MAP.computeIfAbsent(methodInvocationContext, ctx -> { + Class clazz = ctx.getTarget().getClass(); + return clazz.getName().replace(INTERCEPTED_SUFFIX, ""); + }); + additionalClientInfoAttributes.put(ORACLE_MODULE, module); + } + if (!additionalClientInfoAttributes.containsKey(ORACLE_ACTION)) { + String action = methodInvocationContext.getName(); + additionalClientInfoAttributes.put(ORACLE_ACTION, action); + } + return additionalClientInfoAttributes; + } + return null; + } } diff --git a/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java b/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java index 858ef306631..621d056ff31 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java @@ -16,9 +16,9 @@ package io.micronaut.data.connection; +import io.micronaut.core.annotation.AnnotationMetadataProvider; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; -import io.micronaut.data.connection.support.ConnectionClientInfoDetails; import java.time.Duration; import java.util.Optional; @@ -29,7 +29,7 @@ * @author Denis Stepanov * @since 4.0.0 */ -public interface ConnectionDefinition { +public interface ConnectionDefinition extends AnnotationMetadataProvider { /** * Use the default propagation value. @@ -102,15 +102,6 @@ enum Propagation { @Nullable String getName(); - /** - * Returns the connection client information associated with this connection definition. - * If no connection client information has been set, this method will return null. - * - * @return An instance of {@link ConnectionClientInfoDetails} representing the connection client information, or null if not set. - * @since 4.10 - */ - @Nullable ConnectionClientInfoDetails connectionClientInfo(); - /** * Connection definition with specific propagation. * @param propagation The new propagation diff --git a/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java b/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java index 106902b6a49..7803e97cecb 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java @@ -15,10 +15,10 @@ */ package io.micronaut.data.connection; +import io.micronaut.core.annotation.AnnotationMetadata; import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; -import io.micronaut.data.connection.support.ConnectionClientInfoDetails; import java.time.Duration; import java.util.Optional; @@ -30,7 +30,7 @@ * @param propagationBehavior The propagation behaviour * @param timeout The timeout * @param readOnlyValue The read only - * @param connectionClientInfo The connection client info, can be null + * @param annotationMetadata The annotation metadata * @author Denis Stepanov * @since 4.0.0 */ @@ -40,20 +40,19 @@ public record DefaultConnectionDefinition( Propagation propagationBehavior, @Nullable Duration timeout, Boolean readOnlyValue, - - @Nullable ConnectionClientInfoDetails connectionClientInfo + @NonNull AnnotationMetadata annotationMetadata ) implements ConnectionDefinition { DefaultConnectionDefinition(String name) { - this(name, PROPAGATION_DEFAULT, null, null, null); + this(name, PROPAGATION_DEFAULT, null, null, AnnotationMetadata.EMPTY_METADATA); } public DefaultConnectionDefinition(Propagation propagationBehaviour) { - this(null, propagationBehaviour, null, null, null); + this(null, propagationBehaviour, null, null, AnnotationMetadata.EMPTY_METADATA); } public DefaultConnectionDefinition(String name, boolean readOnly) { - this(name, PROPAGATION_DEFAULT, null, readOnly, null); + this(name, PROPAGATION_DEFAULT, null, readOnly, AnnotationMetadata.EMPTY_METADATA); } @Override @@ -80,13 +79,17 @@ public String getName() { @Override public ConnectionDefinition withPropagation(Propagation propagation) { - return new DefaultConnectionDefinition(name, propagation, timeout, readOnlyValue, connectionClientInfo); + return new DefaultConnectionDefinition(name, propagation, timeout, readOnlyValue, annotationMetadata); } @Override public ConnectionDefinition withName(String name) { - return new DefaultConnectionDefinition(name, propagationBehavior, timeout, readOnlyValue, connectionClientInfo); + return new DefaultConnectionDefinition(name, propagationBehavior, timeout, readOnlyValue, annotationMetadata); } + @Override + public @NonNull AnnotationMetadata getAnnotationMetadata() { + return annotationMetadata; + } } diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/Connectable.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/Connectable.java index 0f1c6a2d1aa..7c03b25474e 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/annotation/Connectable.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/annotation/Connectable.java @@ -82,4 +82,5 @@ * @return Whether is read-only connection */ boolean readOnly() default false; + } diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java index 867e08f7362..0bb18d7b17a 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java @@ -16,8 +16,6 @@ package io.micronaut.data.connection.annotation; -import io.micronaut.data.connection.ConnectionDefinition; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -35,31 +33,10 @@ public @interface ConnectionClientInfo { /** - * If this flag is not disabled then when connection is established {@link io.micronaut.data.connection.support.ConnectionClientInfoDetails} - * will be populated in {@link ConnectionDefinition#connectionClientInfo()} using values from this annotation - * or calculate default module and action from the class name and method name issuing the call. - * Then this information can be used for example to {@link java.sql.Connection#setClientInfo(String, String)}. - * * @return whether connection should set client info */ boolean enabled() default true; - /** - * The module name for connection client info if {@link #enabled()} is set to true. - * If not provided, then it will fall back to the name of the class currently being intercepted in {@link io.micronaut.data.connection.interceptor.ConnectableInterceptor}. - * - * @return the custom module name for connection client info - */ - String module() default ""; - - /** - * The action name for connection client info if {@link #enabled()} is set to true. - * If not provided, then it will fall back to the name of the method currently being intercepted in {@link io.micronaut.data.connection.interceptor.ConnectableInterceptor}. - * - * @return the custom action name for tracing - */ - String action() default ""; - /** * Returns an array of additional attributes that will be included in the connection client info. * These attributes can provide extra context about the connection and its usage. diff --git a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java index 617c39f23eb..9ba2e6c92ea 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java @@ -31,20 +31,15 @@ import io.micronaut.data.connection.ConnectionOperationsRegistry; import io.micronaut.data.connection.DefaultConnectionDefinition; import io.micronaut.data.connection.annotation.Connectable; -import io.micronaut.data.connection.annotation.ConnectionClientInfoAttribute; import io.micronaut.data.connection.async.AsyncConnectionOperations; import io.micronaut.data.connection.reactive.ReactiveStreamsConnectionOperations; import io.micronaut.data.connection.reactive.ReactorConnectionOperations; -import io.micronaut.data.connection.support.ConnectionClientInfoDetails; import io.micronaut.inject.ExecutableMethod; -import io.micronaut.runtime.ApplicationConfiguration; import jakarta.inject.Singleton; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.time.Duration; -import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; @@ -60,15 +55,6 @@ @InterceptorBean(Connectable.class) public final class ConnectableInterceptor implements MethodInterceptor { - private static final String ENABLED = "enabled"; - private static final String MODULE_MEMBER = "module"; - private static final String ACTION_MEMBER = "action"; - private static final String NAME_MEMBER = "name"; - private static final String VALUE_MEMBER = "value"; - private static final String CLIENT_INFO_ATTRIBUTES_MEMBER = "clientInfoAttributes"; - private static final String INTERCEPTED_SUFFIX = "$Intercepted"; - - private static final Map EXECUTABLE_METHOD_STRING_MAP = new ConcurrentHashMap<>(100); private final Map connectionInvocationMap = new ConcurrentHashMap<>(30); @NonNull @@ -78,9 +64,6 @@ public final class ConnectableInterceptor implements MethodInterceptor context) { final ConnectionInvocation connectionInvocation = connectionInvocationMap .computeIfAbsent(new TenantExecutableMethod(tenantDataSourceName, executableMethod), ignore -> { final String dataSource = tenantDataSourceName == null ? executableMethod.stringValue(Connectable.class).orElse(null) : tenantDataSourceName; - final ConnectionDefinition connectionDefinition = getConnectionDefinition(context, executableMethod, appName); + final ConnectionDefinition connectionDefinition = getConnectionDefinition(context, executableMethod); switch (interceptedMethod.resultType()) { case PUBLISHER -> { @@ -175,14 +156,13 @@ public Object intercept(MethodInvocationContext context) { * This method is deprecated since version 4.10.4 and marked for removal in future versions. * * @param executableMethod the executable method to retrieve the connection definition for - * @param appName the application name * @return the connection definition - * @deprecated Since 4.10.4, use {@link #getConnectionDefinition(InvocationContext, ExecutableMethod, String)} instead + * @deprecated Since 4.10.4, use {@link #getConnectionDefinition(InvocationContext, ExecutableMethod)} instead */ @NonNull @Deprecated(since = "4.10.4", forRemoval = true) - public static ConnectionDefinition getConnectionDefinition(ExecutableMethod executableMethod, String appName) { - return getConnectionDefinition(null, executableMethod, appName); + public static ConnectionDefinition getConnectionDefinition(ExecutableMethod executableMethod) { + return getConnectionDefinition(null, executableMethod); } /** @@ -194,69 +174,24 @@ public static ConnectionDefinition getConnectionDefinition(ExecutableMethod context, - ExecutableMethod executableMethod, - String appName) { + ExecutableMethod executableMethod) { AnnotationValue annotation = executableMethod.getAnnotation(Connectable.class); if (annotation == null) { throw new IllegalStateException("No declared @Connectable annotation present"); } - AnnotationValue connectionClientInfoAnnotationValue = executableMethod.getAnnotation(io.micronaut.data.connection.annotation.ConnectionClientInfo.class); - ConnectionClientInfoDetails connectionClientInfoDetails = connectionClientInfoAnnotationValue == null ? null : getConnectionClientInfo(connectionClientInfoAnnotationValue, context, executableMethod, appName); return new DefaultConnectionDefinition( executableMethod.getDeclaringType().getSimpleName() + "." + executableMethod.getMethodName(), annotation.enumValue("propagation", ConnectionDefinition.Propagation.class).orElse(ConnectionDefinition.PROPAGATION_DEFAULT), annotation.longValue("timeout").stream().mapToObj(Duration::ofSeconds).findFirst().orElse(null), annotation.booleanValue("readOnly").orElse(null), - connectionClientInfoDetails + context ); } - /** - * Gets connection client info from the {@link io.micronaut.data.connection.annotation.ConnectionClientInfo} annotation. - * - * @param annotation The {@link io.micronaut.data.connection.annotation.ConnectionClientInfo} annotation value - * @param executableMethod The method being executed - * @param appName The micronaut application name, null if not set - * @return The connection client info or null if not configured to be used - */ - private static @Nullable ConnectionClientInfoDetails getConnectionClientInfo(AnnotationValue annotation, - @Nullable InvocationContext context, - ExecutableMethod executableMethod, - String appName) { - boolean connectionClientInfoEnabled = annotation.booleanValue(ENABLED).orElse(true); - if (!connectionClientInfoEnabled) { - return null; - } - String module = annotation.stringValue(MODULE_MEMBER).orElse(null); - String action = annotation.stringValue(ACTION_MEMBER).orElse(null); - if (module == null) { - module = EXECUTABLE_METHOD_STRING_MAP.computeIfAbsent(executableMethod, executableMethod1 -> { - if (context != null) { - Class clazz = context.getTarget().getClass(); - return clazz.getName().replace(INTERCEPTED_SUFFIX, ""); - } else { - return executableMethod.getDeclaringType().getName(); - } - }); - } - if (action == null) { - action = executableMethod.getMethodName(); - } - List> clientInfoAttributes = annotation.getAnnotations(CLIENT_INFO_ATTRIBUTES_MEMBER, ConnectionClientInfoAttribute.class); - Map additionalClientInfoAttributes = new LinkedHashMap<>(clientInfoAttributes.size()); - for (AnnotationValue clientInfoAttribute : clientInfoAttributes) { - String name = clientInfoAttribute.getRequiredValue(NAME_MEMBER, String.class); - String value = clientInfoAttribute.getRequiredValue(VALUE_MEMBER, String.class); - additionalClientInfoAttributes.put(name, value); - } - return new ConnectionClientInfoDetails(appName, module, action, additionalClientInfoAttributes); - } - /** * Cached invocation associating a method with a definition a connection manager. * diff --git a/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java b/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java index f7936cfc842..227b38bc5db 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java @@ -17,6 +17,7 @@ import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.order.OrderUtil; import io.micronaut.core.propagation.PropagatedContext; import io.micronaut.core.propagation.PropagatedContextElement; import io.micronaut.data.connection.exceptions.ConnectionException; @@ -29,6 +30,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.function.Function; @@ -46,41 +48,27 @@ public abstract class AbstractConnectionOperations implements ConnectionOpera protected final Logger logger = LoggerFactory.getLogger(getClass()); - private final List> connectionListeners; - - protected AbstractConnectionOperations(List> connectionListeners) { - this.connectionListeners = connectionListeners; - } + private final List> connectionListeners = new ArrayList<>(10); /** - * Opens a new connection. + * Adds a connection listener to the list of listeners that will be notified when a connection is opened or closed. * - * @param definition The connection definition - * @return The connection + * The added listener will be sorted according to its order using the {@link OrderUtil#sort(List)} method. + * + * @param connectionListener the listener to add */ - protected final C openConnection(ConnectionDefinition definition) { - ConnectionStatus connectionStatus = doOpenConnection(definition); - for (ConnectionListener connectionListener : connectionListeners) { - try { - if (connectionListener.supportsConnection(connectionStatus)) { - connectionListener.afterOpen(connectionStatus); - } - } catch (Exception e) { - logger.debug("An error occurred when calling listener {} afterOpen.", connectionListener.getName(), e); - } - } - return connectionStatus.getConnection(); + public void addConnectionListener(@NonNull ConnectionListener connectionListener) { + connectionListeners.add(connectionListener); + OrderUtil.sort(connectionListeners); } /** - * Opens a new connection based on the provided connection definition. - * - * This method should be implemented by subclasses to provide the actual logic for opening a connection. + * Opens a new connection. * - * @param definition the connection definition to use when opening the connection - * @return the status of the newly opened connection + * @param definition The connection definition + * @return The connection */ - protected abstract ConnectionStatus doOpenConnection(ConnectionDefinition definition); + protected abstract C openConnection(ConnectionDefinition definition); /** * Setups the connection after it have been open. @@ -94,27 +82,7 @@ protected final C openConnection(ConnectionDefinition definition) { * * @param connectionStatus The connection status */ - protected final void closeConnection(ConnectionStatus connectionStatus) { - for (ConnectionListener connectionListener : connectionListeners) { - try { - if (connectionListener.supportsConnection(connectionStatus)) { - connectionListener.beforeClose(connectionStatus); - } - } catch (Exception e) { - logger.debug("An error occurred when calling listener {} beforeClose.", connectionListener.getName(), e); - } - } - doCloseConnection(connectionStatus); - } - - /** - * Closes the connection represented by the given connection status. - * - * This method should be implemented by subclasses to provide the actual logic for closing a connection. - * - * @param connectionStatus the status of the connection to be closed - */ - protected abstract void doCloseConnection(ConnectionStatus connectionStatus); + protected abstract void closeConnection(ConnectionStatus connectionStatus); @Override public final Optional> findConnectionStatus() { @@ -185,6 +153,9 @@ private R executeWithNewConnection(@NonNull ConnectionDefinition definition, @NonNull Function, R> callback) { C connection = openConnection(definition); DefaultConnectionStatus status = new DefaultConnectionStatus<>(connection, definition, true); + + afterOpen(status); + try (PropagatedContext.Scope ignore = PropagatedContext.getOrEmpty() .plus(new ConnectionPropagatedContextElement<>(this, status)) .propagate()) { @@ -233,7 +204,7 @@ public void complete(@NonNull ConnectionStatus status) { connectionStatus.beforeClosed(); } finally { if (connectionStatus.isNew()) { - closeConnection(status); + closeNewConnectionInternal(status); } connectionStatus.afterClosed(); } @@ -243,6 +214,9 @@ public void complete(@NonNull ConnectionStatus status) { private DefaultConnectionStatus openNewConnectionInternal(@NonNull ConnectionDefinition definition) { C connection = openConnection(definition); DefaultConnectionStatus status = new DefaultConnectionStatus<>(connection, definition, true); + + afterOpen(status); + PropagatedContext propagatedContext = PropagatedContext.getOrEmpty().plus(new ConnectionPropagatedContextElement<>(this, status)); PropagatedContext.Scope scope = propagatedContext.propagate(); status.registerSynchronization(new ConnectionSynchronization() { @@ -251,9 +225,31 @@ public void executionComplete() { scope.close(); } }); + return status; } + private void afterOpen(ConnectionStatus connectionStatus) { + for (ConnectionListener connectionListener : connectionListeners) { + try { + connectionListener.afterOpen(connectionStatus); + } catch (Exception e) { + logger.debug("An error occurred when calling listener {} afterOpen.", connectionListener.getName(), e); + } + } + } + + private void closeNewConnectionInternal(@NonNull ConnectionStatus connectionStatus) { + for (ConnectionListener connectionListener : connectionListeners) { + try { + connectionListener.beforeClose(connectionStatus); + } catch (Exception e) { + logger.debug("An error occurred when calling listener {} beforeClose.", connectionListener.getName(), e); + } + } + closeConnection(connectionStatus); + } + private DefaultConnectionStatus reuseExistingConnectionInternal(@NonNull ConnectionPropagatedContextElement existingContextElement) { DefaultConnectionStatus status = new DefaultConnectionStatus<>( existingContextElement.status.getConnection(), @@ -285,7 +281,6 @@ public void executionComplete() { return newStatus; } - private record ConnectionPropagatedContextElement( ConnectionOperations connectionOperations, ConnectionStatus status) implements PropagatedContextElement { diff --git a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientInfoDetails.java b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientInfoDetails.java deleted file mode 100644 index 93b9889b05f..00000000000 --- a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionClientInfoDetails.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2017-2024 original authors - * - * 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 - * - * https://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 io.micronaut.data.connection.support; - -import io.micronaut.core.annotation.Experimental; -import io.micronaut.core.annotation.NonNull; -import io.micronaut.core.annotation.Nullable; -import io.micronaut.data.connection.annotation.ConnectionClientInfo; - -import java.util.Map; - -/** - * The connection client info details that can be used to set to {@link java.sql.Connection#setClientInfo(String, String)}. - * Currently used only for Oracle database connections. - * - * @param appName The app name corresponding to the micronaut.application.name config value and can be null - * @param module The module (if not supplied in {@link ConnectionClientInfo#module()} - * then by default the name of the class issuing database call) - * @param action The action (if not supplied in {@link ConnectionClientInfo#action()} - * then by default the name of the method issuing database call) - * @param connectionClientInfoAttributes The arbitrary connection client info attributes to be set to {@link java.sql.Connection#setClientInfo(String, String)}. - */ -@Experimental -public record ConnectionClientInfoDetails(@Nullable String appName, @NonNull String module, @NonNull String action, - @NonNull Map connectionClientInfoAttributes) { -} diff --git a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java index 3e1761c31e8..4ea9f650700 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java @@ -16,6 +16,7 @@ package io.micronaut.data.connection.support; import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.naming.Named; import io.micronaut.core.order.Ordered; import io.micronaut.data.connection.ConnectionStatus; @@ -31,15 +32,7 @@ * @author radovanradic * @since 4.10 */ -public interface ConnectionListener extends Ordered { - - /** - * Checks whether this connection listener supports the given connection status. - * - * @param connectionStatus The connection status - * @return true if this listener supports the given connection status, false otherwise - */ - boolean supportsConnection(@NonNull ConnectionStatus connectionStatus); +public interface ConnectionListener extends Named, Ordered { /** * Called after a connection is opened. @@ -60,9 +53,13 @@ public interface ConnectionListener extends Ordered { void beforeClose(@NonNull ConnectionStatus connectionStatus); /** - * Returns the name of this listener. Used for logging purposes. + * Returns the name of this listener. Used for logging purposes. By default, returns class simple name. * * @return the name of this listener */ - String getName(); + @Override + @NonNull + default String getName() { + return getClass().getSimpleName(); + } } diff --git a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEAuthorRepository.java b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEAuthorRepository.java index cb9bea218e2..2a58711ed38 100644 --- a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEAuthorRepository.java +++ b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEAuthorRepository.java @@ -17,6 +17,7 @@ import io.micronaut.data.annotation.Join; import io.micronaut.data.connection.annotation.ConnectionClientInfo; +import io.micronaut.data.connection.annotation.ConnectionClientInfoAttribute; import io.micronaut.data.jdbc.annotation.JdbcRepository; import io.micronaut.data.model.query.builder.sql.Dialect; import io.micronaut.data.tck.entities.Author; @@ -27,6 +28,6 @@ public interface OracleXEAuthorRepository extends AuthorRepository { @Override @Join(value = "books", type = Join.Type.LEFT_FETCH) - @ConnectionClientInfo(action = "QueryAuthorByName") + @ConnectionClientInfo(clientInfoAttributes = {@ConnectionClientInfoAttribute(name = "OCSID.ACTION", value = "QueryAuthorByName")}) Author queryByName(String name); } diff --git a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java index 3c4ad218ee5..01e301eff65 100644 --- a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java +++ b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java @@ -33,7 +33,7 @@ import java.util.List; @JdbcRepository(dialect = Dialect.ORACLE) -@ConnectionClientInfo(module = "BOOKS") +@ConnectionClientInfo(clientInfoAttributes = {@ConnectionClientInfoAttribute(name = "OCSID.MODULE", value = "BOOKS")}) public abstract class OracleXEBookRepository extends BookRepository { public OracleXEBookRepository(OracleXEAuthorRepository authorRepository) { super(authorRepository); @@ -58,7 +58,8 @@ public OracleXEBookRepository(OracleXEAuthorRepository authorRepository) { public abstract int add1Aliased(int input); @Override - @ConnectionClientInfo(action = "INSERT", clientInfoAttributes = {@ConnectionClientInfoAttribute(name = "OCSID.MODULE", value = "CustomModule")}) + @ConnectionClientInfo(clientInfoAttributes = {@ConnectionClientInfoAttribute(name = "OCSID.MODULE", value = "CustomModule"), + @ConnectionClientInfoAttribute(name = "OCSID.ACTION", value = "INSERT")}) public abstract @NonNull Book save(@NonNull Book book); // public abstract Book updateReturning(Book book); diff --git a/data-mongodb/src/main/java/io/micronaut/data/mongodb/session/MongoConnectionOperationsImpl.java b/data-mongodb/src/main/java/io/micronaut/data/mongodb/session/MongoConnectionOperationsImpl.java index 971be4bb4b7..023af65799d 100644 --- a/data-mongodb/src/main/java/io/micronaut/data/mongodb/session/MongoConnectionOperationsImpl.java +++ b/data-mongodb/src/main/java/io/micronaut/data/mongodb/session/MongoConnectionOperationsImpl.java @@ -22,10 +22,6 @@ import io.micronaut.data.connection.ConnectionDefinition; import io.micronaut.data.connection.ConnectionStatus; import io.micronaut.data.connection.support.AbstractConnectionOperations; -import io.micronaut.data.connection.support.ConnectionListener; -import io.micronaut.data.connection.support.DefaultConnectionStatus; - -import java.util.List; @Internal @EachBean(MongoClient.class) @@ -33,15 +29,13 @@ final class MongoConnectionOperationsImpl extends AbstractConnectionOperations> connectionListeners) { - super(connectionListeners); + MongoConnectionOperationsImpl(MongoClient mongoClient) { this.mongoClient = mongoClient; } @Override - protected ConnectionStatus doOpenConnection(ConnectionDefinition definition) { - return new DefaultConnectionStatus<>(mongoClient.startSession(), definition, true); + protected ClientSession openConnection(ConnectionDefinition definition) { + return mongoClient.startSession(); } @Override @@ -52,7 +46,7 @@ protected void setupConnection(ConnectionStatus connectionStatus) } @Override - protected void doCloseConnection(ConnectionStatus connectionStatus) { + protected void closeConnection(ConnectionStatus connectionStatus) { connectionStatus.getConnection().close(); } } diff --git a/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc b/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc index de346ac974b..1ec5a484a12 100644 --- a/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc +++ b/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc @@ -65,19 +65,22 @@ IMPORTANT: The dialect setting in configuration does *not* replace the need to e In order to trace SQL calls using `java.sql.Connection.setClientInfo(String, String)` method, you can annotate a repository with the @api:data.connection.annotation.ConnectionClientInfo[] annotation. If the boolean member `enabled` is set to `false`, then Micronaut will not issue `setClientInfo` calls to the JDBC connection. -The annotation members `module` and `action` can set a custom module and action. If omitted, then the name of the module will default -to the class name (usually Micronaut Data repository class) annotated with the given annotation and action to the name of the method -being executed. In addition, the member `clientInfoAttributes` can be used to set arbitrary client info attributes to be set passed to the connection client info. +The member `clientInfoAttributes` can be used to set the connection client info. -Note that the @api:data.connection.annotation.ConnectionClientInfo[] annotation can be used on either the class or the method, thus allowing customizaction of the module or action individually. +Note that the @api:data.connection.annotation.ConnectionClientInfo[] annotation can be used on either the class or the method, thus allowing customization of the module or action individually. -For Oracle database, these values will be set to the JDBC connection using `setClientInfo("OCSID.MODULE", module)` and `setClientInfo("OCSID.ACTION", action)` respectively. -Additionally, if `${micronaut.application.name}` is set, then it will be used to call JDBC connection `setClientInfo("OCSID.CLIENTID", appName)`. -Note that if you supply the attributes `OCSID.MODULE` or `OCSID.ACTION` in the `clientInfoAttributes` member of the `@ConnectionClientInfo` annotation these values will be ignored because -`module` and `action` attributes are already mapped to `OCSID.MODULE` and `OCSID.ACTION`. +For Oracle database, following attributes can be set to the connection client info: `OCSID.MODULE`, `OCSID.ACTION` and `OCSID.CLIENTID` and provided in `clientInfoAttributes` field of the @api:data.connection.annotation.ConnectionClientInfo[]. +If some of these attributes are not provided then Micronaut Data Jdbc is going to populate values automatically for Oracle connections: + +*** `OCSID.MODULE` will get the value of the class name where annotation `@ConnectionClientInfo` is added (usually Micronaut Data repository class) +*** `OCSID.ACTION` will get the value of the method name which is annotated with `@ConnectionClientInfo` annotation +*** `OCSID.CLIENTID` will get the value of the Micronaut application name, if configured Please note this feature is currently supported only for Oracle database connections. In order to enable Oracle connection client info to be set, you need to specify the configuration property `datasources.default.enable-oracle-client-info=true` on a per datasource basis. +For other databases, new implementation of api:io.micronaut.data.connection.support.ConnectionListener[] can be added and +needs to be registered as a listener to the api:io.micronaut.data.connection.support.AbstractConnectionOperations[] by calling `addConnectionListener` method. + TIP: See the guide for https://guides.micronaut.io/latest/micronaut-data-jdbc-repository.html[Access a Database with Micronaut Data JDBC] to learn more. From 5adc61eab8f4dcea16ee27e2a651e4bb0b257de4 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Tue, 19 Nov 2024 15:45:12 +0100 Subject: [PATCH 24/35] Cleanup --- .../HibernateConnectionOperations.java | 1 - ...DefaultDataSourceConnectionOperations.java | 1 + .../OracleClientInfoConnectionListener.java | 46 +++++++++---------- .../annotation/ConnectionClientInfo.java | 26 +++++++++++ .../ConnectionClientInfoAttribute.java | 42 ----------------- .../oraclexe/OracleXEAuthorRepository.java | 3 +- .../jdbc/oraclexe/OracleXEBookRepository.java | 7 ++- 7 files changed, 52 insertions(+), 74 deletions(-) delete mode 100644 data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfoAttribute.java diff --git a/data-connection-hibernate/src/main/java/io/micronaut/data/hibernate/connection/HibernateConnectionOperations.java b/data-connection-hibernate/src/main/java/io/micronaut/data/hibernate/connection/HibernateConnectionOperations.java index c49d6f07e1a..00d7b29a230 100644 --- a/data-connection-hibernate/src/main/java/io/micronaut/data/hibernate/connection/HibernateConnectionOperations.java +++ b/data-connection-hibernate/src/main/java/io/micronaut/data/hibernate/connection/HibernateConnectionOperations.java @@ -28,7 +28,6 @@ import org.hibernate.SessionBuilder; import org.hibernate.SessionFactory; - /** * The Hibernate connection operations. * diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java index e2fc969c852..e54c24d7911 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/operations/DefaultDataSourceConnectionOperations.java @@ -86,4 +86,5 @@ protected void closeConnection(ConnectionStatus connectionStatus) { throw new ConnectionException("Failed to close the connection: " + e.getMessage(), e); } } + } diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java index f297df66541..8efd2c73139 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java @@ -25,11 +25,11 @@ import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; +import io.micronaut.core.util.CollectionUtils; import io.micronaut.core.util.StringUtils; import io.micronaut.data.connection.ConnectionDefinition; import io.micronaut.data.connection.ConnectionStatus; import io.micronaut.data.connection.annotation.ConnectionClientInfo; -import io.micronaut.data.connection.annotation.ConnectionClientInfoAttribute; import io.micronaut.data.connection.jdbc.advice.DelegatingDataSource; import io.micronaut.data.connection.support.AbstractConnectionOperations; import io.micronaut.data.connection.support.ConnectionListener; @@ -85,6 +85,7 @@ final class OracleClientInfoConnectionListener implements ConnectionListener METHOD_INVOCATION_CONTEXT_STRING_MAP = new ConcurrentHashMap<>(100); @Nullable @@ -93,7 +94,7 @@ final class OracleClientInfoConnectionListener implements ConnectionListener connectionOperations, @Nullable ApplicationConfiguration applicationConfiguration) { - this.applicationName = applicationConfiguration.getName().orElse(null); + this.applicationName = applicationConfiguration != null ? applicationConfiguration.getName().orElse(null) : null; try { Connection connection = DelegatingDataSource.unwrapDataSource(dataSource).getConnection(); if (isOracleConnection(connection)) { @@ -109,7 +110,7 @@ public void afterOpen(@NonNull ConnectionStatus connectionStatus) { ConnectionDefinition connectionDefinition = connectionStatus.getDefinition(); // Set client info for connection if Oracle connection after connection is opened Map connectionClientInfo = getConnectionClientInfo(connectionDefinition); - if (connectionClientInfo != null) { + if (CollectionUtils.isNotEmpty(connectionClientInfo)) { Connection connection = connectionStatus.getConnection(); LOG.trace("Setting connection tracing info to the Oracle connection"); try { @@ -129,7 +130,7 @@ public void beforeClose(@NonNull ConnectionStatus connectionStatus) // Clear client info for connection if it was Oracle connection and client info was set previously ConnectionDefinition connectionDefinition = connectionStatus.getDefinition(); Map connectionClientInfo = getConnectionClientInfo(connectionDefinition); - if (connectionClientInfo != null) { + if (CollectionUtils.isNotEmpty(connectionClientInfo)) { try { Connection connection = connectionStatus.getConnection(); for (String key : connectionClientInfo.keySet()) { @@ -178,30 +179,25 @@ private boolean isOracleConnection(Connection connection) { if (!connectionClientInfoEnabled) { return null; } + List> clientInfoAttributes = annotation.getAnnotations(CLIENT_INFO_ATTRIBUTES_MEMBER, ConnectionClientInfo.ConnectionClientInfoAttribute.class); + Map additionalClientInfoAttributes = new LinkedHashMap<>(clientInfoAttributes.size()); + for (AnnotationValue clientInfoAttribute : clientInfoAttributes) { + String name = clientInfoAttribute.getRequiredValue(NAME_MEMBER, String.class); + String value = clientInfoAttribute.getRequiredValue(VALUE_MEMBER, String.class); + additionalClientInfoAttributes.put(name, value); + } + if (StringUtils.isNotEmpty(applicationName)) { + additionalClientInfoAttributes.putIfAbsent(ORACLE_CLIENT_ID, applicationName); + } if (annotationMetadata instanceof MethodInvocationContext methodInvocationContext) { - List> clientInfoAttributes = annotation.getAnnotations(CLIENT_INFO_ATTRIBUTES_MEMBER, ConnectionClientInfoAttribute.class); - Map additionalClientInfoAttributes = new LinkedHashMap<>(clientInfoAttributes.size()); - for (AnnotationValue clientInfoAttribute : clientInfoAttributes) { - String name = clientInfoAttribute.getRequiredValue(NAME_MEMBER, String.class); - String value = clientInfoAttribute.getRequiredValue(VALUE_MEMBER, String.class); - additionalClientInfoAttributes.put(name, value); - } - if (StringUtils.isNotEmpty(applicationName) && !additionalClientInfoAttributes.containsKey(ORACLE_CLIENT_ID)) { - additionalClientInfoAttributes.put(ORACLE_CLIENT_ID, applicationName); - } - if (!additionalClientInfoAttributes.containsKey(ORACLE_MODULE)) { - String module = METHOD_INVOCATION_CONTEXT_STRING_MAP.computeIfAbsent(methodInvocationContext, ctx -> { + additionalClientInfoAttributes.putIfAbsent(ORACLE_MODULE, + METHOD_INVOCATION_CONTEXT_STRING_MAP.computeIfAbsent(methodInvocationContext, ctx -> { Class clazz = ctx.getTarget().getClass(); return clazz.getName().replace(INTERCEPTED_SUFFIX, ""); - }); - additionalClientInfoAttributes.put(ORACLE_MODULE, module); - } - if (!additionalClientInfoAttributes.containsKey(ORACLE_ACTION)) { - String action = methodInvocationContext.getName(); - additionalClientInfoAttributes.put(ORACLE_ACTION, action); - } - return additionalClientInfoAttributes; + }) + ); + additionalClientInfoAttributes.putIfAbsent(ORACLE_ACTION, methodInvocationContext.getName()); } - return null; + return additionalClientInfoAttributes; } } diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java index 0bb18d7b17a..2ff3ddf3443 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java @@ -44,4 +44,30 @@ * @return an array of ConnectionClientInfoAttribute instances */ ConnectionClientInfoAttribute[] clientInfoAttributes() default {}; + + /** + * Annotation used to specify client information attributes that can be set on a JDBC connection. + * + * This annotation allows developers to define custom attributes that provide additional context about the client, + * such as application name or version. These attributes can then be retrieved by the database server and used + * for auditing, logging, or other purposes. + * + * @since 4.10 + */ + @interface ConnectionClientInfoAttribute { + + /** + * Returns the name of the client information attribute. + * + * @return the attribute name + */ + String name(); + + /** + * Returns the value of the client information attribute. + * + * @return the attribute value + */ + String value(); + } } diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfoAttribute.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfoAttribute.java deleted file mode 100644 index 1cde76e8c4a..00000000000 --- a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfoAttribute.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2017-2024 original authors - * - * 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 - * - * https://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 io.micronaut.data.connection.annotation; - -/** - * Annotation used to specify client information attributes that can be set on a JDBC connection. - * - * This annotation allows developers to define custom attributes that provide additional context about the client, - * such as application name or version. These attributes can then be retrieved by the database server and used - * for auditing, logging, or other purposes. - * - * @since 4.10 - */ -public @interface ConnectionClientInfoAttribute { - - /** - * Returns the name of the client information attribute. - * - * @return the attribute name - */ - String name(); - - /** - * Returns the value of the client information attribute. - * - * @return the attribute value - */ - String value(); -} diff --git a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEAuthorRepository.java b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEAuthorRepository.java index 2a58711ed38..a18d1797186 100644 --- a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEAuthorRepository.java +++ b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEAuthorRepository.java @@ -17,7 +17,6 @@ import io.micronaut.data.annotation.Join; import io.micronaut.data.connection.annotation.ConnectionClientInfo; -import io.micronaut.data.connection.annotation.ConnectionClientInfoAttribute; import io.micronaut.data.jdbc.annotation.JdbcRepository; import io.micronaut.data.model.query.builder.sql.Dialect; import io.micronaut.data.tck.entities.Author; @@ -28,6 +27,6 @@ public interface OracleXEAuthorRepository extends AuthorRepository { @Override @Join(value = "books", type = Join.Type.LEFT_FETCH) - @ConnectionClientInfo(clientInfoAttributes = {@ConnectionClientInfoAttribute(name = "OCSID.ACTION", value = "QueryAuthorByName")}) + @ConnectionClientInfo(clientInfoAttributes = {@ConnectionClientInfo.ConnectionClientInfoAttribute(name = "OCSID.ACTION", value = "QueryAuthorByName")}) Author queryByName(String name); } diff --git a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java index 01e301eff65..e6f742f3058 100644 --- a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java +++ b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEBookRepository.java @@ -21,7 +21,6 @@ import io.micronaut.data.annotation.TypeDef; import io.micronaut.data.annotation.sql.Procedure; import io.micronaut.data.connection.annotation.ConnectionClientInfo; -import io.micronaut.data.connection.annotation.ConnectionClientInfoAttribute; import io.micronaut.data.jdbc.annotation.JdbcRepository; import io.micronaut.data.model.DataType; import io.micronaut.data.model.query.builder.sql.Dialect; @@ -33,7 +32,7 @@ import java.util.List; @JdbcRepository(dialect = Dialect.ORACLE) -@ConnectionClientInfo(clientInfoAttributes = {@ConnectionClientInfoAttribute(name = "OCSID.MODULE", value = "BOOKS")}) +@ConnectionClientInfo(clientInfoAttributes = {@ConnectionClientInfo.ConnectionClientInfoAttribute(name = "OCSID.MODULE", value = "BOOKS")}) public abstract class OracleXEBookRepository extends BookRepository { public OracleXEBookRepository(OracleXEAuthorRepository authorRepository) { super(authorRepository); @@ -58,8 +57,8 @@ public OracleXEBookRepository(OracleXEAuthorRepository authorRepository) { public abstract int add1Aliased(int input); @Override - @ConnectionClientInfo(clientInfoAttributes = {@ConnectionClientInfoAttribute(name = "OCSID.MODULE", value = "CustomModule"), - @ConnectionClientInfoAttribute(name = "OCSID.ACTION", value = "INSERT")}) + @ConnectionClientInfo(clientInfoAttributes = {@ConnectionClientInfo.ConnectionClientInfoAttribute(name = "OCSID.MODULE", value = "CustomModule"), + @ConnectionClientInfo.ConnectionClientInfoAttribute(name = "OCSID.ACTION", value = "INSERT")}) public abstract @NonNull Book save(@NonNull Book book); // public abstract Book updateReturning(Book book); From f66881f6fc5a3c929afbcbddcaf4db51d42f20bb Mon Sep 17 00:00:00 2001 From: radovanradic Date: Tue, 19 Nov 2024 15:53:30 +0100 Subject: [PATCH 25/35] Cache module name by the class name --- .../jdbc/oracle/OracleClientInfoConnectionListener.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java index 8efd2c73139..5904eab30ba 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java @@ -86,7 +86,7 @@ final class OracleClientInfoConnectionListener implements ConnectionListener METHOD_INVOCATION_CONTEXT_STRING_MAP = new ConcurrentHashMap<>(100); + private static final Map, String> MODULE_CLASS_MAP = new ConcurrentHashMap<>(100); @Nullable private final String applicationName; @@ -191,10 +191,8 @@ private boolean isOracleConnection(Connection connection) { } if (annotationMetadata instanceof MethodInvocationContext methodInvocationContext) { additionalClientInfoAttributes.putIfAbsent(ORACLE_MODULE, - METHOD_INVOCATION_CONTEXT_STRING_MAP.computeIfAbsent(methodInvocationContext, ctx -> { - Class clazz = ctx.getTarget().getClass(); - return clazz.getName().replace(INTERCEPTED_SUFFIX, ""); - }) + MODULE_CLASS_MAP.computeIfAbsent(methodInvocationContext.getTarget().getClass(), + clazz -> clazz.getName().replace(INTERCEPTED_SUFFIX, "")) ); additionalClientInfoAttributes.putIfAbsent(ORACLE_ACTION, methodInvocationContext.getName()); } From 5c97b1581856ca965ad9f243330855be17608013 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Tue, 19 Nov 2024 16:57:05 +0100 Subject: [PATCH 26/35] Remove enabled attribute and create repeatable annotation --- .../OracleClientInfoConnectionListener.java | 14 ++---- .../annotation/ConnectionClientInfo.java | 40 ++------------- .../ConnectionClientInfoAttribute.java | 50 +++++++++++++++++++ .../interceptor/ConnectableInterceptor.java | 3 +- .../oraclexe/OracleXEAuthorRepository.java | 3 +- .../jdbc/oraclexe/OracleXEBookRepository.java | 9 ++-- .../guide/dbc/jdbc/jdbcConfiguration.adoc | 9 ++-- 7 files changed, 71 insertions(+), 57 deletions(-) create mode 100644 data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfoAttribute.java diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java index 5904eab30ba..c7fd4e54521 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java @@ -29,6 +29,7 @@ import io.micronaut.core.util.StringUtils; import io.micronaut.data.connection.ConnectionDefinition; import io.micronaut.data.connection.ConnectionStatus; +import io.micronaut.data.connection.annotation.ConnectionClientInfoAttribute; import io.micronaut.data.connection.annotation.ConnectionClientInfo; import io.micronaut.data.connection.jdbc.advice.DelegatingDataSource; import io.micronaut.data.connection.support.AbstractConnectionOperations; @@ -61,7 +62,6 @@ @Internal final class OracleClientInfoConnectionListener implements ConnectionListener { - private static final String ENABLED = "enabled"; private static final String NAME_MEMBER = "name"; private static final String VALUE_MEMBER = "value"; private static final String CLIENT_INFO_ATTRIBUTES_MEMBER = "clientInfoAttributes"; @@ -164,24 +164,20 @@ private boolean isOracleConnection(Connection connection) { } /** - * Gets connection client info from the {@link ConnectionClientInfo} annotation. + * Gets connection client info from the {@link ConnectionClientInfoAttribute} annotation. * * @param connectionDefinition The connection definition * @return The connection client info or null if not configured to be used */ private @Nullable Map getConnectionClientInfo(@NonNull ConnectionDefinition connectionDefinition) { AnnotationMetadata annotationMetadata = connectionDefinition.getAnnotationMetadata(); - AnnotationValue annotation = annotationMetadata.getAnnotation(io.micronaut.data.connection.annotation.ConnectionClientInfo.class); + AnnotationValue annotation = annotationMetadata.getAnnotation(ConnectionClientInfo.class); if (annotation == null) { return null; } - boolean connectionClientInfoEnabled = annotation.booleanValue(ENABLED).orElse(true); - if (!connectionClientInfoEnabled) { - return null; - } - List> clientInfoAttributes = annotation.getAnnotations(CLIENT_INFO_ATTRIBUTES_MEMBER, ConnectionClientInfo.ConnectionClientInfoAttribute.class); + List> clientInfoAttributes = annotation.getAnnotations(VALUE_MEMBER); Map additionalClientInfoAttributes = new LinkedHashMap<>(clientInfoAttributes.size()); - for (AnnotationValue clientInfoAttribute : clientInfoAttributes) { + for (AnnotationValue clientInfoAttribute : clientInfoAttributes) { String name = clientInfoAttribute.getRequiredValue(NAME_MEMBER, String.class); String value = clientInfoAttribute.getRequiredValue(VALUE_MEMBER, String.class); additionalClientInfoAttributes.put(name, value); diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java index 2ff3ddf3443..5df68a0adce 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java @@ -22,7 +22,7 @@ import java.lang.annotation.Target; /** - * An annotation used to set client info for the connection. + * Repeatable annotation for {@link ConnectionClientInfoAttribute}. * * @author radovanradic * @since 4.10 @@ -33,41 +33,9 @@ public @interface ConnectionClientInfo { /** - * @return whether connection should set client info - */ - boolean enabled() default true; - - /** - * Returns an array of additional attributes that will be included in the connection client info. - * These attributes can provide extra context about the connection and its usage. - * - * @return an array of ConnectionClientInfoAttribute instances - */ - ConnectionClientInfoAttribute[] clientInfoAttributes() default {}; - - /** - * Annotation used to specify client information attributes that can be set on a JDBC connection. - * - * This annotation allows developers to define custom attributes that provide additional context about the client, - * such as application name or version. These attributes can then be retrieved by the database server and used - * for auditing, logging, or other purposes. + * Returns the list of the client information attributes. * - * @since 4.10 + * @return the attribute collection */ - @interface ConnectionClientInfoAttribute { - - /** - * Returns the name of the client information attribute. - * - * @return the attribute name - */ - String name(); - - /** - * Returns the value of the client information attribute. - * - * @return the attribute value - */ - String value(); - } + ConnectionClientInfoAttribute[] value() default {}; } diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfoAttribute.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfoAttribute.java new file mode 100644 index 00000000000..3e647d6892c --- /dev/null +++ b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfoAttribute.java @@ -0,0 +1,50 @@ +/* + * Copyright 2017-2024 original authors + * + * 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 + * + * https://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 io.micronaut.data.connection.annotation; + + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation used to set client info for the connection. + * + * @author radovanradic + * @since 4.10 + */ +@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(ConnectionClientInfo.class) +@Connectable +public @interface ConnectionClientInfoAttribute { + + /** + * Returns the name of the client information attribute. + * + * @return the attribute name + */ + String name(); + + /** + * Returns the value of the client information attribute. + * + * @return the attribute value + */ + String value(); +} diff --git a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java index 9ba2e6c92ea..04f0a9a225d 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java @@ -31,6 +31,7 @@ import io.micronaut.data.connection.ConnectionOperationsRegistry; import io.micronaut.data.connection.DefaultConnectionDefinition; import io.micronaut.data.connection.annotation.Connectable; +import io.micronaut.data.connection.annotation.ConnectionClientInfoAttribute; import io.micronaut.data.connection.async.AsyncConnectionOperations; import io.micronaut.data.connection.reactive.ReactiveStreamsConnectionOperations; import io.micronaut.data.connection.reactive.ReactorConnectionOperations; @@ -170,7 +171,7 @@ public static ConnectionDefinition getConnectionDefinition(ExecutableMethod listNativeBooksWithTitleAnyArray(@Expandable @TypeDef(type = DataType.STRING) @Nullable String[] arg0); @Procedure @@ -57,8 +56,8 @@ public OracleXEBookRepository(OracleXEAuthorRepository authorRepository) { public abstract int add1Aliased(int input); @Override - @ConnectionClientInfo(clientInfoAttributes = {@ConnectionClientInfo.ConnectionClientInfoAttribute(name = "OCSID.MODULE", value = "CustomModule"), - @ConnectionClientInfo.ConnectionClientInfoAttribute(name = "OCSID.ACTION", value = "INSERT")}) + @ConnectionClientInfoAttribute(name = "OCSID.MODULE", value = "CustomModule") + @ConnectionClientInfoAttribute(name = "OCSID.ACTION", value = "INSERT") public abstract @NonNull Book save(@NonNull Book book); // public abstract Book updateReturning(Book book); diff --git a/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc b/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc index 1ec5a484a12..5652be27fcd 100644 --- a/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc +++ b/src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc @@ -64,16 +64,15 @@ IMPORTANT: The dialect setting in configuration does *not* replace the need to e === Connection client info tracing In order to trace SQL calls using `java.sql.Connection.setClientInfo(String, String)` method, you can -annotate a repository with the @api:data.connection.annotation.ConnectionClientInfo[] annotation. If the boolean member `enabled` is set to `false`, then Micronaut will not issue `setClientInfo` calls to the JDBC connection. -The member `clientInfoAttributes` can be used to set the connection client info. +annotate a repository with the @api:data.connection.annotation.ConnectionClientInfo[] annotation or @api:data.connection.annotation.ConnectionClientInfoAttribute[] for individual client info. Note that the @api:data.connection.annotation.ConnectionClientInfo[] annotation can be used on either the class or the method, thus allowing customization of the module or action individually. -For Oracle database, following attributes can be set to the connection client info: `OCSID.MODULE`, `OCSID.ACTION` and `OCSID.CLIENTID` and provided in `clientInfoAttributes` field of the @api:data.connection.annotation.ConnectionClientInfo[]. +For Oracle database, following attributes can be set to the connection client info: `OCSID.MODULE`, `OCSID.ACTION` and `OCSID.CLIENTID` and provided in @api:data.connection.annotation.ConnectionClientInfoAttribute[]. If some of these attributes are not provided then Micronaut Data Jdbc is going to populate values automatically for Oracle connections: -*** `OCSID.MODULE` will get the value of the class name where annotation `@ConnectionClientInfo` is added (usually Micronaut Data repository class) -*** `OCSID.ACTION` will get the value of the method name which is annotated with `@ConnectionClientInfo` annotation +*** `OCSID.MODULE` will get the value of the class name where annotation `@ConnectionClientInfoAttribute` is added (usually Micronaut Data repository class) +*** `OCSID.ACTION` will get the value of the method name which is annotated with `@ConnectionClientInfoAttribute` annotation *** `OCSID.CLIENTID` will get the value of the Micronaut application name, if configured Please note this feature is currently supported only for Oracle database connections. In order to enable Oracle connection client info to be set, From da4e8a38584f8eb9d9b7735ea13619fa643a1820 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Tue, 19 Nov 2024 17:00:09 +0100 Subject: [PATCH 27/35] Fixes for Sonar --- .../jdbc/oracle/OracleClientInfoConnectionListener.java | 6 ++---- .../data/connection/interceptor/ConnectableInterceptor.java | 3 ++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java index c7fd4e54521..3fe41a51563 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java @@ -25,7 +25,6 @@ import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; -import io.micronaut.core.util.CollectionUtils; import io.micronaut.core.util.StringUtils; import io.micronaut.data.connection.ConnectionDefinition; import io.micronaut.data.connection.ConnectionStatus; @@ -64,7 +63,6 @@ final class OracleClientInfoConnectionListener implements ConnectionListener connectionStatus) { ConnectionDefinition connectionDefinition = connectionStatus.getDefinition(); // Set client info for connection if Oracle connection after connection is opened Map connectionClientInfo = getConnectionClientInfo(connectionDefinition); - if (CollectionUtils.isNotEmpty(connectionClientInfo)) { + if (connectionClientInfo != null && !connectionClientInfo.isEmpty()) { Connection connection = connectionStatus.getConnection(); LOG.trace("Setting connection tracing info to the Oracle connection"); try { @@ -130,7 +128,7 @@ public void beforeClose(@NonNull ConnectionStatus connectionStatus) // Clear client info for connection if it was Oracle connection and client info was set previously ConnectionDefinition connectionDefinition = connectionStatus.getDefinition(); Map connectionClientInfo = getConnectionClientInfo(connectionDefinition); - if (CollectionUtils.isNotEmpty(connectionClientInfo)) { + if (connectionClientInfo != null && !connectionClientInfo.isEmpty()) { try { Connection connection = connectionStatus.getConnection(); for (String key : connectionClientInfo.keySet()) { diff --git a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java index 04f0a9a225d..afa2b6f286d 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java @@ -21,6 +21,7 @@ import io.micronaut.aop.InvocationContext; import io.micronaut.aop.MethodInterceptor; import io.micronaut.aop.MethodInvocationContext; +import io.micronaut.core.annotation.AnnotationMetadata; import io.micronaut.core.annotation.AnnotationValue; import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; @@ -189,7 +190,7 @@ public static ConnectionDefinition getConnectionDefinition(@Nullable InvocationC annotation.enumValue("propagation", ConnectionDefinition.Propagation.class).orElse(ConnectionDefinition.PROPAGATION_DEFAULT), annotation.longValue("timeout").stream().mapToObj(Duration::ofSeconds).findFirst().orElse(null), annotation.booleanValue("readOnly").orElse(null), - context + context == null ? AnnotationMetadata.EMPTY_METADATA : context ); } From 886b10379a85f256927e2c05b4e3e67c3663648a Mon Sep 17 00:00:00 2001 From: radovanradic Date: Wed, 20 Nov 2024 12:11:50 +0100 Subject: [PATCH 28/35] Update @since attributes --- .../data/connection/jdbc/oracle/OracleClientInfoCondition.java | 2 +- .../jdbc/oracle/OracleClientInfoConnectionListener.java | 2 +- .../data/connection/annotation/ConnectionClientInfo.java | 2 +- .../connection/annotation/ConnectionClientInfoAttribute.java | 2 +- .../data/connection/interceptor/ConnectableInterceptor.java | 2 +- .../data/connection/support/AbstractConnectionOperations.java | 2 ++ .../micronaut/data/connection/support/ConnectionListener.java | 2 +- .../data/jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy | 2 +- 8 files changed, 9 insertions(+), 7 deletions(-) diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCondition.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCondition.java index cca3070c1d8..b3571dac082 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCondition.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCondition.java @@ -29,7 +29,7 @@ * This condition checks if the data source dialect is set to Oracle and if the 'customize-oracle-client-info' property is enabled. * * @author radovanradic - * @since 4.10 + * @since 4.11 */ @Internal final class OracleClientInfoCondition implements Condition { diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java index 3fe41a51563..669569f4df5 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java @@ -53,7 +53,7 @@ * (client ID, module, and action) after opening the connection. It also clears these properties before closing the connection. * * @author radovanradic - * @since 4.10 + * @since 4.11 */ @EachBean(DataSource.class) @Requires(condition = OracleClientInfoCondition.class) diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java index 5df68a0adce..8ad2efd7bce 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java @@ -25,7 +25,7 @@ * Repeatable annotation for {@link ConnectionClientInfoAttribute}. * * @author radovanradic - * @since 4.10 + * @since 4.11 */ @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfoAttribute.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfoAttribute.java index 3e647d6892c..6c35767fe8c 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfoAttribute.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfoAttribute.java @@ -26,7 +26,7 @@ * An annotation used to set client info for the connection. * * @author radovanradic - * @since 4.10 + * @since 4.11 */ @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) diff --git a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java index afa2b6f286d..9421aa38eb6 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java @@ -162,7 +162,7 @@ public Object intercept(MethodInvocationContext context) { * @deprecated Since 4.10.4, use {@link #getConnectionDefinition(InvocationContext, ExecutableMethod)} instead */ @NonNull - @Deprecated(since = "4.10.4", forRemoval = true) + @Deprecated(since = "4.11.0", forRemoval = true) public static ConnectionDefinition getConnectionDefinition(ExecutableMethod executableMethod) { return getConnectionDefinition(null, executableMethod); } diff --git a/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java b/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java index 227b38bc5db..10eaa1c9f29 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java @@ -56,6 +56,8 @@ public abstract class AbstractConnectionOperations implements ConnectionOpera * The added listener will be sorted according to its order using the {@link OrderUtil#sort(List)} method. * * @param connectionListener the listener to add + * + * @since 4.11 */ public void addConnectionListener(@NonNull ConnectionListener connectionListener) { connectionListeners.add(connectionListener); diff --git a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java index 4ea9f650700..48f5e622ab0 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java @@ -30,7 +30,7 @@ * @param The connection type * * @author radovanradic - * @since 4.10 + * @since 4.11 */ public interface ConnectionListener extends Named, Ordered { diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy index 853b1ff09db..741085f5c81 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleRepositorySetClientInfoSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 original authors + * Copyright 2017-2024 original authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 60458ad94bf493f3727f056634c5def366284bfe Mon Sep 17 00:00:00 2001 From: radovanradic Date: Sat, 23 Nov 2024 11:10:20 +0100 Subject: [PATCH 29/35] Rename classes to shorter names --- .../oracle/OracleClientInfoConnectionListener.java | 12 ++++++------ ...ConnectionClientInfo.java => ConnClientInfo.java} | 6 +++--- ...entInfoAttribute.java => ConnClientInfoAttr.java} | 4 ++-- .../interceptor/ConnectableInterceptor.java | 4 ++-- .../data/jdbc/oraclexe/OracleXEAuthorRepository.java | 8 ++++---- .../data/jdbc/oraclexe/OracleXEBookRepository.java | 8 ++++---- src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc | 4 ++-- 7 files changed, 23 insertions(+), 23 deletions(-) rename data-connection/src/main/java/io/micronaut/data/connection/annotation/{ConnectionClientInfo.java => ConnClientInfo.java} (87%) rename data-connection/src/main/java/io/micronaut/data/connection/annotation/{ConnectionClientInfoAttribute.java => ConnClientInfoAttr.java} (93%) diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java index 669569f4df5..f692dae936b 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java @@ -28,8 +28,8 @@ import io.micronaut.core.util.StringUtils; import io.micronaut.data.connection.ConnectionDefinition; import io.micronaut.data.connection.ConnectionStatus; -import io.micronaut.data.connection.annotation.ConnectionClientInfoAttribute; -import io.micronaut.data.connection.annotation.ConnectionClientInfo; +import io.micronaut.data.connection.annotation.ConnClientInfoAttr; +import io.micronaut.data.connection.annotation.ConnClientInfo; import io.micronaut.data.connection.jdbc.advice.DelegatingDataSource; import io.micronaut.data.connection.support.AbstractConnectionOperations; import io.micronaut.data.connection.support.ConnectionListener; @@ -162,20 +162,20 @@ private boolean isOracleConnection(Connection connection) { } /** - * Gets connection client info from the {@link ConnectionClientInfoAttribute} annotation. + * Gets connection client info from the {@link ConnClientInfoAttr} annotation. * * @param connectionDefinition The connection definition * @return The connection client info or null if not configured to be used */ private @Nullable Map getConnectionClientInfo(@NonNull ConnectionDefinition connectionDefinition) { AnnotationMetadata annotationMetadata = connectionDefinition.getAnnotationMetadata(); - AnnotationValue annotation = annotationMetadata.getAnnotation(ConnectionClientInfo.class); + AnnotationValue annotation = annotationMetadata.getAnnotation(ConnClientInfo.class); if (annotation == null) { return null; } - List> clientInfoAttributes = annotation.getAnnotations(VALUE_MEMBER); + List> clientInfoAttributes = annotation.getAnnotations(VALUE_MEMBER); Map additionalClientInfoAttributes = new LinkedHashMap<>(clientInfoAttributes.size()); - for (AnnotationValue clientInfoAttribute : clientInfoAttributes) { + for (AnnotationValue clientInfoAttribute : clientInfoAttributes) { String name = clientInfoAttribute.getRequiredValue(NAME_MEMBER, String.class); String value = clientInfoAttribute.getRequiredValue(VALUE_MEMBER, String.class); additionalClientInfoAttributes.put(name, value); diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnClientInfo.java similarity index 87% rename from data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java rename to data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnClientInfo.java index 8ad2efd7bce..ade78452916 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfo.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnClientInfo.java @@ -22,7 +22,7 @@ import java.lang.annotation.Target; /** - * Repeatable annotation for {@link ConnectionClientInfoAttribute}. + * Repeatable annotation for {@link ConnClientInfoAttr}. * * @author radovanradic * @since 4.11 @@ -30,12 +30,12 @@ @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Connectable -public @interface ConnectionClientInfo { +public @interface ConnClientInfo { /** * Returns the list of the client information attributes. * * @return the attribute collection */ - ConnectionClientInfoAttribute[] value() default {}; + ConnClientInfoAttr[] value() default {}; } diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfoAttribute.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnClientInfoAttr.java similarity index 93% rename from data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfoAttribute.java rename to data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnClientInfoAttr.java index 6c35767fe8c..f59bc8c1e99 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnectionClientInfoAttribute.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnClientInfoAttr.java @@ -30,9 +30,9 @@ */ @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) -@Repeatable(ConnectionClientInfo.class) +@Repeatable(ConnClientInfo.class) @Connectable -public @interface ConnectionClientInfoAttribute { +public @interface ConnClientInfoAttr { /** * Returns the name of the client information attribute. diff --git a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java index 9421aa38eb6..01a369ce473 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java @@ -32,7 +32,7 @@ import io.micronaut.data.connection.ConnectionOperationsRegistry; import io.micronaut.data.connection.DefaultConnectionDefinition; import io.micronaut.data.connection.annotation.Connectable; -import io.micronaut.data.connection.annotation.ConnectionClientInfoAttribute; +import io.micronaut.data.connection.annotation.ConnClientInfoAttr; import io.micronaut.data.connection.async.AsyncConnectionOperations; import io.micronaut.data.connection.reactive.ReactiveStreamsConnectionOperations; import io.micronaut.data.connection.reactive.ReactorConnectionOperations; @@ -172,7 +172,7 @@ public static ConnectionDefinition getConnectionDefinition(ExecutableMethod Date: Mon, 25 Nov 2024 18:17:20 +0100 Subject: [PATCH 30/35] Rename connection client info annotations --- .../OracleClientInfoConnectionListener.java | 11 ++-- .../{ConnClientInfo.java => ClientInfo.java} | 34 +++++++++++-- .../annotation/ConnClientInfoAttr.java | 50 ------------------- .../interceptor/ConnectableInterceptor.java | 4 +- .../oraclexe/OracleXEAuthorRepository.java | 7 ++- .../jdbc/oraclexe/OracleXEBookRepository.java | 8 +-- .../guide/dbc/jdbc/jdbcConfiguration.adoc | 10 ++-- 7 files changed, 50 insertions(+), 74 deletions(-) rename data-connection/src/main/java/io/micronaut/data/connection/annotation/{ConnClientInfo.java => ClientInfo.java} (57%) delete mode 100644 data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnClientInfoAttr.java diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java index f692dae936b..2da20361764 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java @@ -28,8 +28,7 @@ import io.micronaut.core.util.StringUtils; import io.micronaut.data.connection.ConnectionDefinition; import io.micronaut.data.connection.ConnectionStatus; -import io.micronaut.data.connection.annotation.ConnClientInfoAttr; -import io.micronaut.data.connection.annotation.ConnClientInfo; +import io.micronaut.data.connection.annotation.ClientInfo; import io.micronaut.data.connection.jdbc.advice.DelegatingDataSource; import io.micronaut.data.connection.support.AbstractConnectionOperations; import io.micronaut.data.connection.support.ConnectionListener; @@ -162,20 +161,20 @@ private boolean isOracleConnection(Connection connection) { } /** - * Gets connection client info from the {@link ConnClientInfoAttr} annotation. + * Gets connection client info from the {@link ClientInfo.Attribute} annotation. * * @param connectionDefinition The connection definition * @return The connection client info or null if not configured to be used */ private @Nullable Map getConnectionClientInfo(@NonNull ConnectionDefinition connectionDefinition) { AnnotationMetadata annotationMetadata = connectionDefinition.getAnnotationMetadata(); - AnnotationValue annotation = annotationMetadata.getAnnotation(ConnClientInfo.class); + AnnotationValue annotation = annotationMetadata.getAnnotation(ClientInfo.class); if (annotation == null) { return null; } - List> clientInfoAttributes = annotation.getAnnotations(VALUE_MEMBER); + List> clientInfoAttributes = annotation.getAnnotations(VALUE_MEMBER); Map additionalClientInfoAttributes = new LinkedHashMap<>(clientInfoAttributes.size()); - for (AnnotationValue clientInfoAttribute : clientInfoAttributes) { + for (AnnotationValue clientInfoAttribute : clientInfoAttributes) { String name = clientInfoAttribute.getRequiredValue(NAME_MEMBER, String.class); String value = clientInfoAttribute.getRequiredValue(VALUE_MEMBER, String.class); additionalClientInfoAttributes.put(name, value); diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnClientInfo.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ClientInfo.java similarity index 57% rename from data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnClientInfo.java rename to data-connection/src/main/java/io/micronaut/data/connection/annotation/ClientInfo.java index ade78452916..9d60845f5e5 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnClientInfo.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ClientInfo.java @@ -17,12 +17,13 @@ import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Repeatable annotation for {@link ConnClientInfoAttr}. + * Repeatable annotation for {@link ClientInfo.Attribute}. * * @author radovanradic * @since 4.11 @@ -30,12 +31,39 @@ @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Connectable -public @interface ConnClientInfo { +public @interface ClientInfo { /** * Returns the list of the client information attributes. * * @return the attribute collection */ - ConnClientInfoAttr[] value() default {}; + Attribute[] value() default {}; + + /** + * An annotation used to set client info for the connection. + * + * @author radovanradic + * @since 4.11 + */ + @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @Repeatable(ClientInfo.class) + @Connectable + @interface Attribute { + + /** + * Returns the name of the client information attribute. + * + * @return the attribute name + */ + String name(); + + /** + * Returns the value of the client information attribute. + * + * @return the attribute value + */ + String value(); + } } diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnClientInfoAttr.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnClientInfoAttr.java deleted file mode 100644 index f59bc8c1e99..00000000000 --- a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ConnClientInfoAttr.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2017-2024 original authors - * - * 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 - * - * https://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 io.micronaut.data.connection.annotation; - - -import java.lang.annotation.ElementType; -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * An annotation used to set client info for the connection. - * - * @author radovanradic - * @since 4.11 - */ -@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Repeatable(ConnClientInfo.class) -@Connectable -public @interface ConnClientInfoAttr { - - /** - * Returns the name of the client information attribute. - * - * @return the attribute name - */ - String name(); - - /** - * Returns the value of the client information attribute. - * - * @return the attribute value - */ - String value(); -} diff --git a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java index 01a369ce473..01fb9cbe9d3 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java @@ -31,8 +31,8 @@ import io.micronaut.data.connection.ConnectionOperations; import io.micronaut.data.connection.ConnectionOperationsRegistry; import io.micronaut.data.connection.DefaultConnectionDefinition; +import io.micronaut.data.connection.annotation.ClientInfo; import io.micronaut.data.connection.annotation.Connectable; -import io.micronaut.data.connection.annotation.ConnClientInfoAttr; import io.micronaut.data.connection.async.AsyncConnectionOperations; import io.micronaut.data.connection.reactive.ReactiveStreamsConnectionOperations; import io.micronaut.data.connection.reactive.ReactorConnectionOperations; @@ -172,7 +172,7 @@ public static ConnectionDefinition getConnectionDefinition(ExecutableMethod Date: Fri, 29 Nov 2024 10:59:55 +0200 Subject: [PATCH 31/35] Connection interceptor --- .../OracleClientInfoConnectionListener.java | 60 ++++++++++--------- .../support/AbstractConnectionOperations.java | 6 +- .../support/ConnectionListener.java | 22 +++++-- .../DefaultJdbcRepositoryOperations.java | 37 ++++++------ 4 files changed, 72 insertions(+), 53 deletions(-) diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java index 2da20361764..2e7fc271c73 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java @@ -44,6 +44,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; /** * A customizer for Oracle database connections that sets client information after opening and clears before closing. @@ -103,40 +104,41 @@ final class OracleClientInfoConnectionListener implements ConnectionListener connectionStatus) { - ConnectionDefinition connectionDefinition = connectionStatus.getDefinition(); - // Set client info for connection if Oracle connection after connection is opened - Map connectionClientInfo = getConnectionClientInfo(connectionDefinition); - if (connectionClientInfo != null && !connectionClientInfo.isEmpty()) { - Connection connection = connectionStatus.getConnection(); - LOG.trace("Setting connection tracing info to the Oracle connection"); - try { - for (Map.Entry additionalInfo : connectionClientInfo.entrySet()) { - String name = additionalInfo.getKey(); - String value = additionalInfo.getValue(); - connection.setClientInfo(name, value); + public Function, R> intercept(Function, R> operation) { + return connectionStatus -> { + ConnectionDefinition connectionDefinition = connectionStatus.getDefinition(); + // Set client info for connection if Oracle connection after connection is opened + Map connectionClientInfo = getConnectionClientInfo(connectionDefinition); + if (connectionClientInfo != null && !connectionClientInfo.isEmpty()) { + Connection connection = connectionStatus.getConnection(); + LOG.trace("Setting connection tracing info to the Oracle connection"); + try { + for (Map.Entry additionalInfo : connectionClientInfo.entrySet()) { + String name = additionalInfo.getKey(); + String value = additionalInfo.getValue(); + connection.setClientInfo(name, value); + } + } catch (SQLClientInfoException e) { + LOG.debug("Failed to set connection tracing info", e); } - } catch (SQLClientInfoException e) { - LOG.debug("Failed to set connection tracing info", e); } - } - } - - @Override - public void beforeClose(@NonNull ConnectionStatus connectionStatus) { - // Clear client info for connection if it was Oracle connection and client info was set previously - ConnectionDefinition connectionDefinition = connectionStatus.getDefinition(); - Map connectionClientInfo = getConnectionClientInfo(connectionDefinition); - if (connectionClientInfo != null && !connectionClientInfo.isEmpty()) { try { - Connection connection = connectionStatus.getConnection(); - for (String key : connectionClientInfo.keySet()) { - connection.setClientInfo(key, null); + return operation.apply(connectionStatus); + } finally { + // Clear client info for connection if it was Oracle connection and client info was set previously + if (connectionClientInfo != null && !connectionClientInfo.isEmpty()) { + try { + Connection connection = connectionStatus.getConnection(); + for (String key : connectionClientInfo.keySet()) { + connection.setClientInfo(key, null); + } + } catch (SQLClientInfoException e) { + LOG.debug("Failed to clear connection tracing info", e); + } } - } catch (SQLClientInfoException e) { - LOG.debug("Failed to clear connection tracing info", e); } - } + + }; } @Override diff --git a/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java b/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java index 10eaa1c9f29..22a9217715c 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java @@ -103,6 +103,10 @@ private Optional> findContextElement() { @Override public final R execute(@NonNull ConnectionDefinition definition, @NonNull Function, R> callback) { ConnectionPropagatedContextElement existingConnection = findContextElement().orElse(null); + for (ConnectionListener connectionListener : connectionListeners) { + callback = connectionListener.intercept(callback); + } + @NonNull Function, R> finalCallback = callback; return switch (definition.getPropagationBehavior()) { case REQUIRED -> { if (existingConnection == null) { @@ -120,7 +124,7 @@ public final R execute(@NonNull ConnectionDefinition definition, @NonNull Fu if (existingConnection == null) { yield executeWithNewConnection(definition, callback); } - yield suspend(existingConnection, () -> executeWithNewConnection(definition, callback)); + yield suspend(existingConnection, () -> executeWithNewConnection(definition, finalCallback)); } }; } diff --git a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java index 48f5e622ab0..408e29c03e4 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java @@ -15,11 +15,14 @@ */ package io.micronaut.data.connection.support; +import io.micronaut.core.annotation.Experimental; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.naming.Named; import io.micronaut.core.order.Ordered; import io.micronaut.data.connection.ConnectionStatus; +import java.util.function.Function; + /** * Handles connection after open or before close events based on the provided {@link ConnectionStatus}. * @@ -32,25 +35,34 @@ * @author radovanradic * @since 4.11 */ +@Experimental public interface ConnectionListener extends Named, Ordered { + /** + * Intercept the connection operation. + * @param operation The operation + * @param The result + * @return the operation callback + */ + Function, R> intercept(Function, R> operation); + /** * Called after a connection is opened. - * * This method allows implementations to perform additional setup or configuration on the connection. * * @param connectionStatus The newly opened connection */ - void afterOpen(@NonNull ConnectionStatus connectionStatus); + default void afterOpen(@NonNull ConnectionStatus connectionStatus) { + } /** * Called before a connection is closed. - * * This method allows implementations to release any resources or perform cleanup tasks related to the connection. * - * @param connectionStatus The connection statucs about to be closed + * @param connectionStatus The connection status about to be closed */ - void beforeClose(@NonNull ConnectionStatus connectionStatus); + default void beforeClose(@NonNull ConnectionStatus connectionStatus) { + } /** * Returns the name of this listener. Used for logging purposes. By default, returns class simple name. diff --git a/data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/DefaultJdbcRepositoryOperations.java b/data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/DefaultJdbcRepositoryOperations.java index 33e0f42d904..913a2d4283b 100644 --- a/data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/DefaultJdbcRepositoryOperations.java +++ b/data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/DefaultJdbcRepositoryOperations.java @@ -29,6 +29,7 @@ import io.micronaut.core.type.Argument; import io.micronaut.core.util.ArgumentUtils; import io.micronaut.core.util.CollectionUtils; +import io.micronaut.data.connection.ConnectionDefinition; import io.micronaut.data.connection.ConnectionOperations; import io.micronaut.data.connection.annotation.Connectable; import io.micronaut.data.exceptions.DataAccessException; @@ -343,7 +344,7 @@ public ReactiveRepositoryOperations reactive() { @Nullable @Override public R findOne(@NonNull PreparedQuery pq) { - return executeRead(connection -> findOne(connection, getSqlPreparedQuery(pq))); + return executeRead(connection -> findOne(connection, getSqlPreparedQuery(pq)), pq.getAnnotationMetadata()); } private R findOne(Connection connection, SqlPreparedQuery preparedQuery) { @@ -437,7 +438,7 @@ public boolean exists(@NonNull PreparedQuery pq) { } catch (SQLException e) { throw new DataAccessException("Error executing SQL query: " + e.getMessage(), e); } - }); + }, pq.getAnnotationMetadata()); } @NonNull @@ -534,7 +535,7 @@ private void closeResultSet(Connection connection, PreparedStatement ps, ResultS @NonNull @Override public Iterable findAll(@NonNull PreparedQuery preparedQuery) { - return executeRead(connection -> findAll(connection, getSqlPreparedQuery(preparedQuery), true)); + return executeRead(connection -> findAll(connection, getSqlPreparedQuery(preparedQuery), true), preparedQuery.getAnnotationMetadata()); } @NonNull @@ -555,7 +556,7 @@ public Optional executeUpdate(@NonNull PreparedQuery pq) { } catch (SQLException e) { throw sqlExceptionToDataAccessException(e, preparedQuery.getDialect(), sqlException -> new DataAccessException("Error executing SQL UPDATE: " + sqlException.getMessage(), sqlException)); } - }); + }, pq.getAnnotationMetadata()); } @Override @@ -571,7 +572,7 @@ public List execute(PreparedQuery pq) { } catch (SQLException e) { throw sqlExceptionToDataAccessException(e, preparedQuery.getDialect(), sqlException -> new DataAccessException("Error executing SQL UPDATE: " + sqlException.getMessage(), sqlException)); } - }); + }, pq.getAnnotationMetadata()); } private List callProcedure(Connection connection, SqlPreparedQuery preparedQuery) throws SQLException { @@ -623,7 +624,7 @@ public Optional deleteAll(@NonNull DeleteBatchOperation operation return op.rowsUpdated; }) ); - })); + }, operation.getAnnotationMetadata())); } @Override @@ -634,7 +635,7 @@ public int delete(@NonNull DeleteOperation operation) { JdbcEntityOperations op = new JdbcEntityOperations<>(ctx, storedQuery.getPersistentEntity(), operation.getEntity(), storedQuery); op.delete(); return op; - }).rowsUpdated; + }, operation.getAnnotationMetadata()).rowsUpdated; } @Override @@ -645,7 +646,7 @@ public R deleteReturning(DeleteReturningOperation operation) { JdbcEntityOperations op = new JdbcEntityOperations<>(ctx, storedQuery.getPersistentEntity(), operation.getEntity(), storedQuery); op.delete(); return (R) op.getEntity(); - }); + }, operation.getAnnotationMetadata()); } @Override @@ -665,7 +666,7 @@ public List deleteAllReturning(DeleteReturningBatchOperation ope op.delete(); return op.getEntity(); }).toList(); - }); + }, operation.getAnnotationMetadata()); } @NonNull @@ -677,7 +678,7 @@ public T update(@NonNull UpdateOperation operation) { JdbcEntityOperations op = new JdbcEntityOperations<>(ctx, storedQuery.getPersistentEntity(), operation.getEntity(), storedQuery); op.update(); return op.getEntity(); - }); + }, operation.getAnnotationMetadata()); } @NonNull @@ -700,7 +701,7 @@ public Iterable updateAll(@NonNull UpdateBatchOperation operation) { JdbcEntitiesOperations op = new JdbcEntitiesOperations<>(ctx, persistentEntity, operation, storedQuery); op.update(); return op.getEntities(); - }); + }, operation.getAnnotationMetadata()); } @NonNull @@ -712,7 +713,7 @@ public T persist(@NonNull InsertOperation operation) { JdbcEntityOperations op = new JdbcEntityOperations<>(ctx, storedQuery, storedQuery.getPersistentEntity(), operation.getEntity(), true); op.persist(); return op; - }).getEntity(); + }, operation.getAnnotationMetadata()).getEntity(); } @Nullable @@ -764,25 +765,25 @@ public Iterable persistAll(@NonNull InsertBatchOperation operation) { return op.getEntities(); } - }); + }, operation.getAnnotationMetadata()); } - private I executeRead(Function fn) { + private I executeRead(Function fn, AnnotationMetadata annotationMetadata) { if (!jdbcConfiguration.isAllowConnectionPerOperation() && connectionOperations.findConnectionStatus().isEmpty()) { throw connectionNotFoundAndNewNotAllowed(); } - return connectionOperations.executeRead(status -> { + return connectionOperations.execute(ConnectionDefinition.READ_ONLY.withAnnotationMetadata(annotationMetadata), status -> { Connection connection = status.getConnection(); applySchema(connection); return fn.apply(connection); }); } - private I executeWrite(Function fn) { + private I executeWrite(Function fn, AnnotationMetadata annotationMetadata) { if (!jdbcConfiguration.isAllowConnectionPerOperation() && connectionOperations.findConnectionStatus().isEmpty()) { throw connectionNotFoundAndNewNotAllowed(); } - return connectionOperations.executeWrite(status -> { + return connectionOperations.execute(ConnectionDefinition.DEFAULT.withAnnotationMetadata(annotationMetadata), status -> { Connection connection = status.getConnection(); applySchema(connection); return fn.apply(connection); @@ -846,7 +847,7 @@ public R execute(@NonNull ConnectionCallback callback) { } catch (SQLException e) { throw new DataAccessException("Error executing SQL Callback: " + e.getMessage(), e); } - }); + }, AnnotationMetadata.EMPTY_METADATA); } @NonNull From 7566f76434c261a4e0b60a0ff7f400cdf19b139d Mon Sep 17 00:00:00 2001 From: radovanradic Date: Fri, 29 Nov 2024 15:35:12 +0100 Subject: [PATCH 32/35] Adjust code per Denis's suggestion for callbacks --- ...OracleClientInfoConnectionCustomizer.java} | 52 ++++++++++--------- .../data/connection/ConnectionDefinition.java | 10 ++++ .../DefaultConnectionDefinition.java | 5 ++ .../connection/annotation/ClientInfo.java | 2 - .../support/AbstractConnectionOperations.java | 48 ++++------------- ...istener.java => ConnectionCustomizer.java} | 24 ++------- .../DefaultJdbcRepositoryOperations.java | 34 ++++++------ .../oraclexe/OracleXEAuthorRepository.java | 1 - .../internal/sql/DefaultSqlPreparedQuery.java | 6 +++ .../internal/sql/SqlPreparedQuery.java | 10 ++++ 10 files changed, 90 insertions(+), 102 deletions(-) rename data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/{OracleClientInfoConnectionListener.java => OracleClientInfoConnectionCustomizer.java} (78%) rename data-connection/src/main/java/io/micronaut/data/connection/support/{ConnectionListener.java => ConnectionCustomizer.java} (64%) diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionCustomizer.java similarity index 78% rename from data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java rename to data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionCustomizer.java index 2e7fc271c73..f9a85236935 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionCustomizer.java @@ -25,13 +25,14 @@ import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; +import io.micronaut.core.util.CollectionUtils; import io.micronaut.core.util.StringUtils; import io.micronaut.data.connection.ConnectionDefinition; import io.micronaut.data.connection.ConnectionStatus; import io.micronaut.data.connection.annotation.ClientInfo; import io.micronaut.data.connection.jdbc.advice.DelegatingDataSource; import io.micronaut.data.connection.support.AbstractConnectionOperations; -import io.micronaut.data.connection.support.ConnectionListener; +import io.micronaut.data.connection.support.ConnectionCustomizer; import io.micronaut.runtime.ApplicationConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,6 +41,7 @@ import java.sql.Connection; import java.sql.SQLClientInfoException; import java.sql.SQLException; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -59,7 +61,7 @@ @Requires(condition = OracleClientInfoCondition.class) @Context @Internal -final class OracleClientInfoConnectionListener implements ConnectionListener { +final class OracleClientInfoConnectionCustomizer implements ConnectionCustomizer { private static final String NAME_MEMBER = "name"; private static final String VALUE_MEMBER = "value"; @@ -82,21 +84,21 @@ final class OracleClientInfoConnectionListener implements ConnectionListener, String> MODULE_CLASS_MAP = new ConcurrentHashMap<>(100); @Nullable private final String applicationName; - OracleClientInfoConnectionListener(@NonNull DataSource dataSource, - @NonNull @Parameter AbstractConnectionOperations connectionOperations, - @Nullable ApplicationConfiguration applicationConfiguration) { + OracleClientInfoConnectionCustomizer(@NonNull DataSource dataSource, + @NonNull @Parameter AbstractConnectionOperations connectionOperations, + @Nullable ApplicationConfiguration applicationConfiguration) { this.applicationName = applicationConfiguration != null ? applicationConfiguration.getName().orElse(null) : null; try { Connection connection = DelegatingDataSource.unwrapDataSource(dataSource).getConnection(); if (isOracleConnection(connection)) { - connectionOperations.addConnectionListener(this); + connectionOperations.addConnectionCustomizer(this); } } catch (SQLException e) { LOG.error("Failed to get connection for oracle connection listener", e); @@ -109,7 +111,7 @@ public Function, R> intercept(Function connectionClientInfo = getConnectionClientInfo(connectionDefinition); - if (connectionClientInfo != null && !connectionClientInfo.isEmpty()) { + if (CollectionUtils.isNotEmpty(connectionClientInfo)) { Connection connection = connectionStatus.getConnection(); LOG.trace("Setting connection tracing info to the Oracle connection"); try { @@ -126,7 +128,7 @@ public Function, R> intercept(Function getConnectionClientInfo(@NonNull ConnectionDefinition connectionDefinition) { + private @NonNull Map getConnectionClientInfo(@NonNull ConnectionDefinition connectionDefinition) { AnnotationMetadata annotationMetadata = connectionDefinition.getAnnotationMetadata(); AnnotationValue annotation = annotationMetadata.getAnnotation(ClientInfo.class); - if (annotation == null) { - return null; - } - List> clientInfoAttributes = annotation.getAnnotations(VALUE_MEMBER); - Map additionalClientInfoAttributes = new LinkedHashMap<>(clientInfoAttributes.size()); - for (AnnotationValue clientInfoAttribute : clientInfoAttributes) { - String name = clientInfoAttribute.getRequiredValue(NAME_MEMBER, String.class); - String value = clientInfoAttribute.getRequiredValue(VALUE_MEMBER, String.class); - additionalClientInfoAttributes.put(name, value); + List> clientInfoValues = annotation != null ? annotation.getAnnotations(VALUE_MEMBER) : Collections.emptyList(); + Map clientInfoAttributes = new LinkedHashMap<>(clientInfoValues.size()); + if (CollectionUtils.isNotEmpty(clientInfoValues)) { + for (AnnotationValue clientInfoValue : clientInfoValues) { + String name = clientInfoValue.getRequiredValue(NAME_MEMBER, String.class); + String value = clientInfoValue.getRequiredValue(VALUE_MEMBER, String.class); + clientInfoAttributes.put(name, value); + } } + // Fallback defaults if not provided in the annotation if (StringUtils.isNotEmpty(applicationName)) { - additionalClientInfoAttributes.putIfAbsent(ORACLE_CLIENT_ID, applicationName); + clientInfoAttributes.putIfAbsent(ORACLE_CLIENT_ID, applicationName); } if (annotationMetadata instanceof MethodInvocationContext methodInvocationContext) { - additionalClientInfoAttributes.putIfAbsent(ORACLE_MODULE, + clientInfoAttributes.putIfAbsent(ORACLE_MODULE, MODULE_CLASS_MAP.computeIfAbsent(methodInvocationContext.getTarget().getClass(), clazz -> clazz.getName().replace(INTERCEPTED_SUFFIX, "")) ); - additionalClientInfoAttributes.putIfAbsent(ORACLE_ACTION, methodInvocationContext.getName()); + clientInfoAttributes.putIfAbsent(ORACLE_ACTION, methodInvocationContext.getName()); } - return additionalClientInfoAttributes; + return clientInfoAttributes; } } diff --git a/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java b/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java index 621d056ff31..b085463754a 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/ConnectionDefinition.java @@ -16,6 +16,7 @@ package io.micronaut.data.connection; +import io.micronaut.core.annotation.AnnotationMetadata; import io.micronaut.core.annotation.AnnotationMetadataProvider; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; @@ -118,6 +119,15 @@ enum Propagation { @NonNull ConnectionDefinition withName(String name); + /** + * Connection definition with new annotation metadata. + * + * @param annotationMetadata The new annotation metadata + * @return A new connection definition with specified annotation metadata + */ + @NonNull + ConnectionDefinition withAnnotationMetadata(AnnotationMetadata annotationMetadata); + /** * Create a new {@link ConnectionDefinition} for the given behaviour. * diff --git a/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java b/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java index 7803e97cecb..6720695f38e 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java @@ -87,6 +87,11 @@ public ConnectionDefinition withName(String name) { return new DefaultConnectionDefinition(name, propagationBehavior, timeout, readOnlyValue, annotationMetadata); } + @Override + public ConnectionDefinition withAnnotationMetadata(AnnotationMetadata newAnnotationMetadata) { + return new DefaultConnectionDefinition(name, propagationBehavior, timeout, readOnlyValue, newAnnotationMetadata); + } + @Override public @NonNull AnnotationMetadata getAnnotationMetadata() { return annotationMetadata; diff --git a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ClientInfo.java b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ClientInfo.java index 9d60845f5e5..bb9b87c8eaf 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/annotation/ClientInfo.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/annotation/ClientInfo.java @@ -30,7 +30,6 @@ */ @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) -@Connectable public @interface ClientInfo { /** @@ -49,7 +48,6 @@ @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Repeatable(ClientInfo.class) - @Connectable @interface Attribute { /** diff --git a/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java b/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java index 22a9217715c..f81e647a092 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java @@ -48,20 +48,21 @@ public abstract class AbstractConnectionOperations implements ConnectionOpera protected final Logger logger = LoggerFactory.getLogger(getClass()); - private final List> connectionListeners = new ArrayList<>(10); + private final List> connectionCustomizers = new ArrayList<>(10); /** - * Adds a connection listener to the list of listeners that will be notified when a connection is opened or closed. + * Adds a connection customizer to the list of customizers that will be notified before or after a call to the underlying data repository + * is issues. * - * The added listener will be sorted according to its order using the {@link OrderUtil#sort(List)} method. + * The added customizer will be sorted according to its order using the {@link OrderUtil#sort(List)} method. * - * @param connectionListener the listener to add + * @param connectionCustomizer the connection customizer to add * * @since 4.11 */ - public void addConnectionListener(@NonNull ConnectionListener connectionListener) { - connectionListeners.add(connectionListener); - OrderUtil.sort(connectionListeners); + public void addConnectionCustomizer(@NonNull ConnectionCustomizer connectionCustomizer) { + connectionCustomizers.add(connectionCustomizer); + OrderUtil.sort(connectionCustomizers); } /** @@ -103,8 +104,8 @@ private Optional> findContextElement() { @Override public final R execute(@NonNull ConnectionDefinition definition, @NonNull Function, R> callback) { ConnectionPropagatedContextElement existingConnection = findContextElement().orElse(null); - for (ConnectionListener connectionListener : connectionListeners) { - callback = connectionListener.intercept(callback); + for (ConnectionCustomizer connectionCustomizer : connectionCustomizers) { + callback = connectionCustomizer.intercept(callback); } @NonNull Function, R> finalCallback = callback; return switch (definition.getPropagationBehavior()) { @@ -160,8 +161,6 @@ private R executeWithNewConnection(@NonNull ConnectionDefinition definition, C connection = openConnection(definition); DefaultConnectionStatus status = new DefaultConnectionStatus<>(connection, definition, true); - afterOpen(status); - try (PropagatedContext.Scope ignore = PropagatedContext.getOrEmpty() .plus(new ConnectionPropagatedContextElement<>(this, status)) .propagate()) { @@ -210,7 +209,7 @@ public void complete(@NonNull ConnectionStatus status) { connectionStatus.beforeClosed(); } finally { if (connectionStatus.isNew()) { - closeNewConnectionInternal(status); + closeConnection(status); } connectionStatus.afterClosed(); } @@ -220,9 +219,6 @@ public void complete(@NonNull ConnectionStatus status) { private DefaultConnectionStatus openNewConnectionInternal(@NonNull ConnectionDefinition definition) { C connection = openConnection(definition); DefaultConnectionStatus status = new DefaultConnectionStatus<>(connection, definition, true); - - afterOpen(status); - PropagatedContext propagatedContext = PropagatedContext.getOrEmpty().plus(new ConnectionPropagatedContextElement<>(this, status)); PropagatedContext.Scope scope = propagatedContext.propagate(); status.registerSynchronization(new ConnectionSynchronization() { @@ -231,31 +227,9 @@ public void executionComplete() { scope.close(); } }); - return status; } - private void afterOpen(ConnectionStatus connectionStatus) { - for (ConnectionListener connectionListener : connectionListeners) { - try { - connectionListener.afterOpen(connectionStatus); - } catch (Exception e) { - logger.debug("An error occurred when calling listener {} afterOpen.", connectionListener.getName(), e); - } - } - } - - private void closeNewConnectionInternal(@NonNull ConnectionStatus connectionStatus) { - for (ConnectionListener connectionListener : connectionListeners) { - try { - connectionListener.beforeClose(connectionStatus); - } catch (Exception e) { - logger.debug("An error occurred when calling listener {} beforeClose.", connectionListener.getName(), e); - } - } - closeConnection(connectionStatus); - } - private DefaultConnectionStatus reuseExistingConnectionInternal(@NonNull ConnectionPropagatedContextElement existingContextElement) { DefaultConnectionStatus status = new DefaultConnectionStatus<>( existingContextElement.status.getConnection(), diff --git a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionCustomizer.java similarity index 64% rename from data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java rename to data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionCustomizer.java index 408e29c03e4..55776331d59 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionListener.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionCustomizer.java @@ -24,10 +24,10 @@ import java.util.function.Function; /** - * Handles connection after open or before close events based on the provided {@link ConnectionStatus}. + * Customizes connection before or after data repository call based on the provided {@link ConnectionStatus}. * * Implementations of this interface can modify the behavior of connections created by Micronaut Data - * or do what might be needed after connection open or before close. + * or do what might be needed before or after call to the data repository (for example JDBC statement call). * * @see ConnectionStatus * @param The connection type @@ -36,7 +36,7 @@ * @since 4.11 */ @Experimental -public interface ConnectionListener extends Named, Ordered { +public interface ConnectionCustomizer extends Named, Ordered { /** * Intercept the connection operation. @@ -46,24 +46,6 @@ public interface ConnectionListener extends Named, Ordered { */ Function, R> intercept(Function, R> operation); - /** - * Called after a connection is opened. - * This method allows implementations to perform additional setup or configuration on the connection. - * - * @param connectionStatus The newly opened connection - */ - default void afterOpen(@NonNull ConnectionStatus connectionStatus) { - } - - /** - * Called before a connection is closed. - * This method allows implementations to release any resources or perform cleanup tasks related to the connection. - * - * @param connectionStatus The connection status about to be closed - */ - default void beforeClose(@NonNull ConnectionStatus connectionStatus) { - } - /** * Returns the name of this listener. Used for logging purposes. By default, returns class simple name. * diff --git a/data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/DefaultJdbcRepositoryOperations.java b/data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/DefaultJdbcRepositoryOperations.java index 913a2d4283b..906b014fd5e 100644 --- a/data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/DefaultJdbcRepositoryOperations.java +++ b/data-jdbc/src/main/java/io/micronaut/data/jdbc/operations/DefaultJdbcRepositoryOperations.java @@ -344,7 +344,8 @@ public ReactiveRepositoryOperations reactive() { @Nullable @Override public R findOne(@NonNull PreparedQuery pq) { - return executeRead(connection -> findOne(connection, getSqlPreparedQuery(pq)), pq.getAnnotationMetadata()); + SqlPreparedQuery sqlPreparedQuery = getSqlPreparedQuery(pq); + return executeRead(connection -> findOne(connection, getSqlPreparedQuery(pq)), sqlPreparedQuery.getInvocationContext()); } private R findOne(Connection connection, SqlPreparedQuery preparedQuery) { @@ -426,9 +427,9 @@ private List findAll(SqlStoredQuery sqlStoredQuery, ResultSet rs @Override public boolean exists(@NonNull PreparedQuery pq) { + SqlPreparedQuery preparedQuery = getSqlPreparedQuery(pq); return executeRead(connection -> { try { - SqlPreparedQuery preparedQuery = getSqlPreparedQuery(pq); try (PreparedStatement ps = prepareStatement(connection::prepareStatement, preparedQuery, false, true)) { preparedQuery.bindParameters(new JdbcParameterBinder(connection, ps, preparedQuery)); try (ResultSet rs = ps.executeQuery()) { @@ -438,7 +439,7 @@ public boolean exists(@NonNull PreparedQuery pq) { } catch (SQLException e) { throw new DataAccessException("Error executing SQL query: " + e.getMessage(), e); } - }, pq.getAnnotationMetadata()); + }, preparedQuery.getInvocationContext()); } @NonNull @@ -535,14 +536,15 @@ private void closeResultSet(Connection connection, PreparedStatement ps, ResultS @NonNull @Override public Iterable findAll(@NonNull PreparedQuery preparedQuery) { - return executeRead(connection -> findAll(connection, getSqlPreparedQuery(preparedQuery), true), preparedQuery.getAnnotationMetadata()); + SqlPreparedQuery sqlPreparedQuery = getSqlPreparedQuery(preparedQuery); + return executeRead(connection -> findAll(connection, sqlPreparedQuery, true), sqlPreparedQuery.getInvocationContext()); } @NonNull @Override public Optional executeUpdate(@NonNull PreparedQuery pq) { + SqlPreparedQuery preparedQuery = getSqlPreparedQuery(pq); return executeWrite(connection -> { - SqlPreparedQuery preparedQuery = getSqlPreparedQuery(pq); try (PreparedStatement ps = prepareStatement(connection::prepareStatement, preparedQuery, true, false)) { preparedQuery.bindParameters(new JdbcParameterBinder(connection, ps, preparedQuery)); int result = ps.executeUpdate(); @@ -556,13 +558,13 @@ public Optional executeUpdate(@NonNull PreparedQuery pq) { } catch (SQLException e) { throw sqlExceptionToDataAccessException(e, preparedQuery.getDialect(), sqlException -> new DataAccessException("Error executing SQL UPDATE: " + sqlException.getMessage(), sqlException)); } - }, pq.getAnnotationMetadata()); + }, preparedQuery.getInvocationContext()); } @Override public List execute(PreparedQuery pq) { + SqlPreparedQuery preparedQuery = getSqlPreparedQuery(pq); return executeWrite(connection -> { - SqlPreparedQuery preparedQuery = getSqlPreparedQuery(pq); try { if (preparedQuery.isProcedure()) { return callProcedure(connection, preparedQuery); @@ -572,7 +574,7 @@ public List execute(PreparedQuery pq) { } catch (SQLException e) { throw sqlExceptionToDataAccessException(e, preparedQuery.getDialect(), sqlException -> new DataAccessException("Error executing SQL UPDATE: " + sqlException.getMessage(), sqlException)); } - }, pq.getAnnotationMetadata()); + }, preparedQuery.getInvocationContext()); } private List callProcedure(Connection connection, SqlPreparedQuery preparedQuery) throws SQLException { @@ -624,7 +626,7 @@ public Optional deleteAll(@NonNull DeleteBatchOperation operation return op.rowsUpdated; }) ); - }, operation.getAnnotationMetadata())); + }, operation.getInvocationContext())); } @Override @@ -635,7 +637,7 @@ public int delete(@NonNull DeleteOperation operation) { JdbcEntityOperations op = new JdbcEntityOperations<>(ctx, storedQuery.getPersistentEntity(), operation.getEntity(), storedQuery); op.delete(); return op; - }, operation.getAnnotationMetadata()).rowsUpdated; + }, operation.getInvocationContext()).rowsUpdated; } @Override @@ -646,7 +648,7 @@ public R deleteReturning(DeleteReturningOperation operation) { JdbcEntityOperations op = new JdbcEntityOperations<>(ctx, storedQuery.getPersistentEntity(), operation.getEntity(), storedQuery); op.delete(); return (R) op.getEntity(); - }, operation.getAnnotationMetadata()); + }, operation.getInvocationContext()); } @Override @@ -666,7 +668,7 @@ public List deleteAllReturning(DeleteReturningBatchOperation ope op.delete(); return op.getEntity(); }).toList(); - }, operation.getAnnotationMetadata()); + }, operation.getInvocationContext()); } @NonNull @@ -678,7 +680,7 @@ public T update(@NonNull UpdateOperation operation) { JdbcEntityOperations op = new JdbcEntityOperations<>(ctx, storedQuery.getPersistentEntity(), operation.getEntity(), storedQuery); op.update(); return op.getEntity(); - }, operation.getAnnotationMetadata()); + }, operation.getInvocationContext()); } @NonNull @@ -701,7 +703,7 @@ public Iterable updateAll(@NonNull UpdateBatchOperation operation) { JdbcEntitiesOperations op = new JdbcEntitiesOperations<>(ctx, persistentEntity, operation, storedQuery); op.update(); return op.getEntities(); - }, operation.getAnnotationMetadata()); + }, operation.getInvocationContext()); } @NonNull @@ -713,7 +715,7 @@ public T persist(@NonNull InsertOperation operation) { JdbcEntityOperations op = new JdbcEntityOperations<>(ctx, storedQuery, storedQuery.getPersistentEntity(), operation.getEntity(), true); op.persist(); return op; - }, operation.getAnnotationMetadata()).getEntity(); + }, operation.getInvocationContext()).getEntity(); } @Nullable @@ -765,7 +767,7 @@ public Iterable persistAll(@NonNull InsertBatchOperation operation) { return op.getEntities(); } - }, operation.getAnnotationMetadata()); + }, operation.getInvocationContext()); } private I executeRead(Function fn, AnnotationMetadata annotationMetadata) { diff --git a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEAuthorRepository.java b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEAuthorRepository.java index 88035632ca3..63d9327f4f2 100644 --- a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEAuthorRepository.java +++ b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEAuthorRepository.java @@ -23,7 +23,6 @@ import io.micronaut.data.tck.repositories.AuthorRepository; @JdbcRepository(dialect = Dialect.ORACLE) -@ClientInfo public interface OracleXEAuthorRepository extends AuthorRepository { @Override @Join(value = "books", type = Join.Type.LEFT_FETCH) diff --git a/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/DefaultSqlPreparedQuery.java b/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/DefaultSqlPreparedQuery.java index f8a4cf65485..bb7eeda349b 100644 --- a/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/DefaultSqlPreparedQuery.java +++ b/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/DefaultSqlPreparedQuery.java @@ -15,6 +15,7 @@ */ package io.micronaut.data.runtime.operations.internal.sql; +import io.micronaut.aop.InvocationContext; import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; @@ -445,6 +446,11 @@ public QueryResultInfo getQueryResultInfo() { return sqlStoredQuery.getQueryResultInfo(); } + @Override + public InvocationContext getInvocationContext() { + return invocationContext; + } + /** * Build a sort for ID for the given entity. * diff --git a/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/SqlPreparedQuery.java b/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/SqlPreparedQuery.java index 574cdb62669..9f9c6cc1aba 100644 --- a/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/SqlPreparedQuery.java +++ b/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/SqlPreparedQuery.java @@ -15,6 +15,7 @@ */ package io.micronaut.data.runtime.operations.internal.sql; +import io.micronaut.aop.InvocationContext; import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.Nullable; import io.micronaut.data.model.Pageable; @@ -54,4 +55,13 @@ public interface SqlPreparedQuery extends BindableParametersPreparedQuery< */ @Override QueryResultInfo getQueryResultInfo(); + + /** + * Returns the invocation context associated with this prepared query. + * + * @return the invocation context + */ + @Nullable + @SuppressWarnings("java:S1452") + InvocationContext getInvocationContext(); } From 9130104f391cc3910b4ea10d9a659b2276cc33b0 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Fri, 29 Nov 2024 15:47:38 +0100 Subject: [PATCH 33/35] Revert changes in ConnectableInterceptor --- .../DefaultConnectionDefinition.java | 5 +++ .../interceptor/ConnectableInterceptor.java | 35 ++----------------- 2 files changed, 7 insertions(+), 33 deletions(-) diff --git a/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java b/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java index 6720695f38e..f67259bd975 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/DefaultConnectionDefinition.java @@ -55,6 +55,11 @@ public DefaultConnectionDefinition(String name, boolean readOnly) { this(name, PROPAGATION_DEFAULT, null, readOnly, AnnotationMetadata.EMPTY_METADATA); } + public DefaultConnectionDefinition(String name, Propagation propagationBehavior, Duration timeout, + Boolean readOnlyValue) { + this(name, propagationBehavior, timeout, readOnlyValue, AnnotationMetadata.EMPTY_METADATA); + } + @Override public Optional isReadOnly() { return Optional.ofNullable(readOnlyValue); diff --git a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java index 01fb9cbe9d3..aac82d33a7e 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java @@ -18,10 +18,8 @@ import io.micronaut.aop.InterceptPhase; import io.micronaut.aop.InterceptedMethod; import io.micronaut.aop.InterceptorBean; -import io.micronaut.aop.InvocationContext; import io.micronaut.aop.MethodInterceptor; import io.micronaut.aop.MethodInvocationContext; -import io.micronaut.core.annotation.AnnotationMetadata; import io.micronaut.core.annotation.AnnotationValue; import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; @@ -31,7 +29,6 @@ import io.micronaut.data.connection.ConnectionOperations; import io.micronaut.data.connection.ConnectionOperationsRegistry; import io.micronaut.data.connection.DefaultConnectionDefinition; -import io.micronaut.data.connection.annotation.ClientInfo; import io.micronaut.data.connection.annotation.Connectable; import io.micronaut.data.connection.async.AsyncConnectionOperations; import io.micronaut.data.connection.reactive.ReactiveStreamsConnectionOperations; @@ -100,7 +97,7 @@ public Object intercept(MethodInvocationContext context) { final ConnectionInvocation connectionInvocation = connectionInvocationMap .computeIfAbsent(new TenantExecutableMethod(tenantDataSourceName, executableMethod), ignore -> { final String dataSource = tenantDataSourceName == null ? executableMethod.stringValue(Connectable.class).orElse(null) : tenantDataSourceName; - final ConnectionDefinition connectionDefinition = getConnectionDefinition(context, executableMethod); + final ConnectionDefinition connectionDefinition = getConnectionDefinition(executableMethod); switch (interceptedMethod.resultType()) { case PUBLISHER -> { @@ -152,35 +149,8 @@ public Object intercept(MethodInvocationContext context) { } } - /** - * Retrieves the connection definition based on the provided executable method and application name. - * - * This method is deprecated since version 4.10.4 and marked for removal in future versions. - * - * @param executableMethod the executable method to retrieve the connection definition for - * @return the connection definition - * @deprecated Since 4.10.4, use {@link #getConnectionDefinition(InvocationContext, ExecutableMethod)} instead - */ @NonNull - @Deprecated(since = "4.11.0", forRemoval = true) public static ConnectionDefinition getConnectionDefinition(ExecutableMethod executableMethod) { - return getConnectionDefinition(null, executableMethod); - } - - /** - * Retrieves the connection definition based on the provided executable method and application name. - * - * This method examines the annotations present on the executable method to determine the connection definition. - * It looks for the presence of the {@link Connectable} annotation and uses its attributes to construct the connection definition. - * Additionally, it checks for the presence of the {@link ClientInfo.Attribute} annotation to obtain connection tracing information. - * - * @param context the invocation context, may be null - * @param executableMethod the executable method to retrieve the connection definition for - * @return the connection definition - */ - @NonNull - public static ConnectionDefinition getConnectionDefinition(@Nullable InvocationContext context, - ExecutableMethod executableMethod) { AnnotationValue annotation = executableMethod.getAnnotation(Connectable.class); if (annotation == null) { throw new IllegalStateException("No declared @Connectable annotation present"); @@ -189,8 +159,7 @@ public static ConnectionDefinition getConnectionDefinition(@Nullable InvocationC executableMethod.getDeclaringType().getSimpleName() + "." + executableMethod.getMethodName(), annotation.enumValue("propagation", ConnectionDefinition.Propagation.class).orElse(ConnectionDefinition.PROPAGATION_DEFAULT), annotation.longValue("timeout").stream().mapToObj(Duration::ofSeconds).findFirst().orElse(null), - annotation.booleanValue("readOnly").orElse(null), - context == null ? AnnotationMetadata.EMPTY_METADATA : context + annotation.booleanValue("readOnly").orElse(null) ); } From 8528515fc833f5e04166b6633064f4a2a9181d68 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Fri, 29 Nov 2024 15:49:19 +0100 Subject: [PATCH 34/35] Revert new line --- .../data/connection/interceptor/ConnectableInterceptor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java index aac82d33a7e..a8b1b102a1b 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/interceptor/ConnectableInterceptor.java @@ -155,6 +155,7 @@ public static ConnectionDefinition getConnectionDefinition(ExecutableMethod Date: Fri, 29 Nov 2024 16:03:25 +0100 Subject: [PATCH 35/35] Reverted some changes and refactored code to resolve Sonar warning --- .../OracleClientInfoConnectionCustomizer.java | 59 ++++++++++--------- .../support/AbstractConnectionOperations.java | 1 - .../support/ConnectionCustomizer.java | 15 +---- 3 files changed, 32 insertions(+), 43 deletions(-) diff --git a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionCustomizer.java b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionCustomizer.java index f9a85236935..3f542e13cd7 100644 --- a/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionCustomizer.java +++ b/data-connection-jdbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionCustomizer.java @@ -109,43 +109,46 @@ final class OracleClientInfoConnectionCustomizer implements ConnectionCustomizer public Function, R> intercept(Function, R> operation) { return connectionStatus -> { ConnectionDefinition connectionDefinition = connectionStatus.getDefinition(); - // Set client info for connection if Oracle connection after connection is opened + // Set client info for connection if Oracle before issue JDBC call Map connectionClientInfo = getConnectionClientInfo(connectionDefinition); - if (CollectionUtils.isNotEmpty(connectionClientInfo)) { - Connection connection = connectionStatus.getConnection(); - LOG.trace("Setting connection tracing info to the Oracle connection"); - try { - for (Map.Entry additionalInfo : connectionClientInfo.entrySet()) { - String name = additionalInfo.getKey(); - String value = additionalInfo.getValue(); - connection.setClientInfo(name, value); - } - } catch (SQLClientInfoException e) { - LOG.debug("Failed to set connection tracing info", e); - } - } + applyClientInfo(connectionStatus, connectionClientInfo); try { return operation.apply(connectionStatus); } finally { // Clear client info for connection if it was Oracle connection and client info was set previously - if (CollectionUtils.isNotEmpty(connectionClientInfo)) { - try { - Connection connection = connectionStatus.getConnection(); - for (String key : connectionClientInfo.keySet()) { - connection.setClientInfo(key, null); - } - } catch (SQLClientInfoException e) { - LOG.debug("Failed to clear connection tracing info", e); - } - } + clearClientInfo(connectionStatus, connectionClientInfo); } }; } - @Override - public String getName() { - return "Oracle Connection Client Info Customizer"; + private void applyClientInfo(@NonNull ConnectionStatus connectionStatus, @NonNull Map connectionClientInfo) { + if (CollectionUtils.isNotEmpty(connectionClientInfo)) { + Connection connection = connectionStatus.getConnection(); + LOG.trace("Setting connection tracing info to the Oracle connection"); + try { + for (Map.Entry additionalInfo : connectionClientInfo.entrySet()) { + String name = additionalInfo.getKey(); + String value = additionalInfo.getValue(); + connection.setClientInfo(name, value); + } + } catch (SQLClientInfoException e) { + LOG.debug("Failed to set connection tracing info", e); + } + } + } + + private void clearClientInfo(@NonNull ConnectionStatus connectionStatus, @NonNull Map connectionClientInfo) { + if (CollectionUtils.isNotEmpty(connectionClientInfo)) { + try { + Connection connection = connectionStatus.getConnection(); + for (String key : connectionClientInfo.keySet()) { + connection.setClientInfo(key, null); + } + } catch (SQLClientInfoException e) { + LOG.debug("Failed to clear connection tracing info", e); + } + } } /** @@ -186,7 +189,7 @@ private boolean isOracleConnection(Connection connection) { if (StringUtils.isNotEmpty(applicationName)) { clientInfoAttributes.putIfAbsent(ORACLE_CLIENT_ID, applicationName); } - if (annotationMetadata instanceof MethodInvocationContext methodInvocationContext) { + if (annotationMetadata instanceof MethodInvocationContext methodInvocationContext) { clientInfoAttributes.putIfAbsent(ORACLE_MODULE, MODULE_CLASS_MAP.computeIfAbsent(methodInvocationContext.getTarget().getClass(), clazz -> clazz.getName().replace(INTERCEPTED_SUFFIX, "")) diff --git a/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java b/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java index f81e647a092..f9dfe18894c 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/support/AbstractConnectionOperations.java @@ -160,7 +160,6 @@ private R executeWithNewConnection(@NonNull ConnectionDefinition definition, @NonNull Function, R> callback) { C connection = openConnection(definition); DefaultConnectionStatus status = new DefaultConnectionStatus<>(connection, definition, true); - try (PropagatedContext.Scope ignore = PropagatedContext.getOrEmpty() .plus(new ConnectionPropagatedContextElement<>(this, status)) .propagate()) { diff --git a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionCustomizer.java b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionCustomizer.java index 55776331d59..1a79841ae1a 100644 --- a/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionCustomizer.java +++ b/data-connection/src/main/java/io/micronaut/data/connection/support/ConnectionCustomizer.java @@ -16,8 +16,6 @@ package io.micronaut.data.connection.support; import io.micronaut.core.annotation.Experimental; -import io.micronaut.core.annotation.NonNull; -import io.micronaut.core.naming.Named; import io.micronaut.core.order.Ordered; import io.micronaut.data.connection.ConnectionStatus; @@ -36,7 +34,7 @@ * @since 4.11 */ @Experimental -public interface ConnectionCustomizer extends Named, Ordered { +public interface ConnectionCustomizer extends Ordered { /** * Intercept the connection operation. @@ -45,15 +43,4 @@ public interface ConnectionCustomizer extends Named, Ordered { * @return the operation callback */ Function, R> intercept(Function, R> operation); - - /** - * Returns the name of this listener. Used for logging purposes. By default, returns class simple name. - * - * @return the name of this listener - */ - @Override - @NonNull - default String getName() { - return getClass().getSimpleName(); - } }