Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support including Module and Action in each JDBC session with Oracle JDBC #3183

Open
wants to merge 40 commits into
base: 4.10.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
b45b532
Investigation for setting connection client info
radovanradic Oct 15, 2024
e0b6f4a
Merge branch '4.10.x' into jdbc-clientinfo
radovanradic Oct 16, 2024
19e58f0
Use Connectable interface to pass connection client tracing info
radovanradic Oct 16, 2024
b8ed459
Merge branch '4.10.x' into jdbc-clientinfo
radovanradic Oct 16, 2024
92902bd
Remove unneeded changes
radovanradic Oct 16, 2024
afb396d
Use connectable for Oracle Book repo
radovanradic Oct 16, 2024
a481771
Merge branch '4.10.x' into jdbc-clientinfo
radovanradic Oct 16, 2024
27bdc07
Documentation, comments and code cleanup.
radovanradic Oct 17, 2024
8bb94e8
Merge branch '4.10.x' into jdbc-clientinfo
radovanradic Oct 17, 2024
7874bdf
Rename ConnectionClientTracingInfo to ConnectionTracingInfo
radovanradic Oct 17, 2024
41fd257
Changes per CR comments, still not there.
radovanradic Oct 21, 2024
8e70480
Merge branch '4.10.x' into jdbc-clientinfo
radovanradic Oct 21, 2024
71e66de
Change as suggested in PR comment
radovanradic Oct 21, 2024
7a16299
Properly clear connection client info
radovanradic Oct 21, 2024
2785d8d
Introduce new OracleConnectionClientInfo annotation for connection cl…
radovanradic Oct 21, 2024
304d577
Fixed javadoc
radovanradic Oct 21, 2024
7e90fa5
Merge branch '4.10.x' into jdbc-clientinfo
radovanradic Nov 1, 2024
b3b0468
Merge branch '4.10.x' into jdbc-clientinfo
radovanradic Nov 15, 2024
b8060d3
Resolve module/class name using InvocationContext.getTarget()
radovanradic Nov 15, 2024
3320b38
Introduce ConnectionCustomizer for more flexibility
radovanradic Nov 15, 2024
d84dad1
More changes as suggested in pull request comments.
radovanradic Nov 17, 2024
1bb490a
Merge branch '4.10.x' into jdbc-clientinfo
radovanradic Nov 17, 2024
4e98412
Add @Experimental to ConnectionClientInfoDetails
radovanradic Nov 17, 2024
91b3cd6
Applied more suggestions from pull request comments.
radovanradic Nov 18, 2024
fdff622
Updated comments
radovanradic Nov 18, 2024
d56a78b
Update src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc
radovanradic Nov 18, 2024
c25d742
Update src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc
radovanradic Nov 18, 2024
f6b7f80
Update src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc
radovanradic Nov 18, 2024
1f9e9e9
Update src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc
radovanradic Nov 18, 2024
c68b657
Renamed classes as suggested and also property to enable Oracle clien…
radovanradic Nov 18, 2024
06901f3
Refactoring according to suggestions in PR comments.
radovanradic Nov 19, 2024
5adc61e
Cleanup
radovanradic Nov 19, 2024
f66881f
Cache module name by the class name
radovanradic Nov 19, 2024
5c97b15
Remove enabled attribute and create repeatable annotation
radovanradic Nov 19, 2024
da4e8a3
Fixes for Sonar
radovanradic Nov 19, 2024
913d9a8
Merge branch '4.10.x' into jdbc-clientinfo
radovanradic Nov 20, 2024
886b103
Update @since attributes
radovanradic Nov 20, 2024
60458ad
Rename classes to shorter names
radovanradic Nov 23, 2024
e0099b8
Rename connection client info annotations
radovanradic Nov 25, 2024
e5e0c1b
Connection interceptor
dstepanov Nov 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -45,26 +49,28 @@ public final class HibernateConnectionOperations extends AbstractConnectionOpera
private final Interceptor entityInterceptor;

public HibernateConnectionOperations(SessionFactory sessionFactory,
@Nullable Interceptor entityInterceptor) {
@Nullable Interceptor entityInterceptor,
List<ConnectionListener<Session>> connectionListeners) {
super(connectionListeners);
this.sessionFactory = sessionFactory;
this.entityInterceptor = entityInterceptor;
}

@Override
protected Session openConnection(ConnectionDefinition definition) {
protected ConnectionStatus<Session> 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
protected void setupConnection(ConnectionStatus<Session> connectionStatus) {
}

@Override
protected void closeConnection(ConnectionStatus<Session> connectionStatus) {
protected void doCloseConnection(ConnectionStatus<Session> connectionStatus) {
connectionStatus.getConnection().close();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -47,22 +49,28 @@ public final class DefaultDataSourceConnectionOperations extends AbstractConnect
private static final Logger LOG = LoggerFactory.getLogger(DefaultDataSourceConnectionOperations.class);
private final DataSource dataSource;

DefaultDataSourceConnectionOperations(DataSource dataSource) {
DefaultDataSourceConnectionOperations(DataSource dataSource,
List<ConnectionListener<Connection>> connectionListeners) {
super(connectionListeners);
this.dataSource = DelegatingDataSource.unwrapDataSource(dataSource);
}

@Override
protected Connection openConnection(ConnectionDefinition definition) {
protected ConnectionStatus<Connection> doOpenConnection(ConnectionDefinition definition) {
Connection connection;
try {
return dataSource.getConnection();
connection = dataSource.getConnection();
} catch (SQLException e) {
throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", e);
}

return new DefaultConnectionStatus<>(connection, definition, true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this change?

Copy link
Contributor Author

@radovanradic radovanradic Nov 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because you suggested interface methods accept ConnectionStatus and then creating it here and passing back to openConnection
Will revert this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it can be done without this change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, using existing internal method for open and introducing new for close.

}

@Override
protected void setupConnection(ConnectionStatus<Connection> connectionStatus) {
connectionStatus.getDefinition().isReadOnly().ifPresent(readOnly -> {
ConnectionDefinition connectionDefinition = connectionStatus.getDefinition();
connectionDefinition.isReadOnly().ifPresent(readOnly -> {
List<Runnable> onCompleteCallbacks = new ArrayList<>(1);
JdbcConnectionUtils.applyReadOnly(LOG, connectionStatus.getConnection(), readOnly, onCompleteCallbacks);
if (!onCompleteCallbacks.isEmpty()) {
Expand All @@ -79,12 +87,12 @@ public void executionComplete() {
}

@Override
protected void closeConnection(ConnectionStatus<Connection> connectionStatus) {
protected void doCloseConnection(ConnectionStatus<Connection> connectionStatus) {
Connection connection = connectionStatus.getConnection();
try {
connectionStatus.getConnection().close();
connection.close();
} catch (SQLException e) {
throw new ConnectionException("Failed to close the connection: " + e.getMessage(), e);
}
}

}
Original file line number Diff line number Diff line change
@@ -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 {
radovanradic marked this conversation as resolved.
Show resolved Hide resolved

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";
radovanradic marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* 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.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 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;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* 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
*/
@EachBean(DataSource.class)
@Requires(condition = OracleClientInfoCustomizerCondition.class)
@Internal
final class OracleClientInfoCustomizerConnectionListener implements ConnectionListener<Connection> {
radovanradic marked this conversation as resolved.
Show resolved Hide resolved

/**
* Constant for the Oracle connection client info client ID property name.
*/
private static final String ORACLE_CLIENT_ID = "OCSID.CLIENTID";
/**
* Constant for the Oracle connection client info module property name.
*/
private static final String ORACLE_MODULE = "OCSID.MODULE";
/**
* Constant for the Oracle connection client info action property name.
*/
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<String> RESERVED_CLIENT_INFO_NAMES = List.of(ORACLE_CLIENT_ID, ORACLE_MODULE, ORACLE_ACTION);

private static final Logger LOG = LoggerFactory.getLogger(OracleClientInfoCustomizerConnectionListener.class);

private final Map<Connection, Boolean> connectionSupportedMap = new ConcurrentHashMap<>(20);

@Override
public boolean supportsConnection(@NonNull ConnectionStatus<Connection> connectionStatus) {
return connectionSupportedMap.computeIfAbsent(connectionStatus.getConnection(), this::isOracleConnection);
}

@Override
public void afterOpen(@NonNull ConnectionStatus<Connection> 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) {
connection.setClientInfo(ORACLE_CLIENT_ID, connectionClientInfo.appName());
}
connection.setClientInfo(ORACLE_MODULE, connectionClientInfo.module());
connection.setClientInfo(ORACLE_ACTION, connectionClientInfo.action());
for (Map.Entry<String, String> 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);
}
}
} catch (SQLClientInfoException e) {
LOG.debug("Failed to set connection tracing info", e);
}
}
}

@Override
public void beforeClose(@NonNull ConnectionStatus<Connection> 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);
for (Map.Entry<String, String> 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);
}
}
}

@Override
public String getName() {
return "Oracle Connection Client Info 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

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;
Expand Down Expand Up @@ -101,6 +102,15 @@ 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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this; let it be fetched by the Oracle listener. Make ConnectionDefinition AnnotationMetadataProvider


/**
* Connection definition with specific propagation.
* @param propagation The new propagation
Expand Down
Loading
Loading