-
Notifications
You must be signed in to change notification settings - Fork 198
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
Merged
Merged
Changes from 37 commits
Commits
Show all changes
47 commits
Select commit
Hold shift + click to select a range
b45b532
Investigation for setting connection client info
radovanradic e0b6f4a
Merge branch '4.10.x' into jdbc-clientinfo
radovanradic 19e58f0
Use Connectable interface to pass connection client tracing info
radovanradic b8ed459
Merge branch '4.10.x' into jdbc-clientinfo
radovanradic 92902bd
Remove unneeded changes
radovanradic afb396d
Use connectable for Oracle Book repo
radovanradic a481771
Merge branch '4.10.x' into jdbc-clientinfo
radovanradic 27bdc07
Documentation, comments and code cleanup.
radovanradic 8bb94e8
Merge branch '4.10.x' into jdbc-clientinfo
radovanradic 7874bdf
Rename ConnectionClientTracingInfo to ConnectionTracingInfo
radovanradic 41fd257
Changes per CR comments, still not there.
radovanradic 8e70480
Merge branch '4.10.x' into jdbc-clientinfo
radovanradic 71e66de
Change as suggested in PR comment
radovanradic 7a16299
Properly clear connection client info
radovanradic 2785d8d
Introduce new OracleConnectionClientInfo annotation for connection cl…
radovanradic 304d577
Fixed javadoc
radovanradic 7e90fa5
Merge branch '4.10.x' into jdbc-clientinfo
radovanradic b3b0468
Merge branch '4.10.x' into jdbc-clientinfo
radovanradic b8060d3
Resolve module/class name using InvocationContext.getTarget()
radovanradic 3320b38
Introduce ConnectionCustomizer for more flexibility
radovanradic d84dad1
More changes as suggested in pull request comments.
radovanradic 1bb490a
Merge branch '4.10.x' into jdbc-clientinfo
radovanradic 4e98412
Add @Experimental to ConnectionClientInfoDetails
radovanradic 91b3cd6
Applied more suggestions from pull request comments.
radovanradic fdff622
Updated comments
radovanradic d56a78b
Update src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc
radovanradic c25d742
Update src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc
radovanradic f6b7f80
Update src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc
radovanradic 1f9e9e9
Update src/main/docs/guide/dbc/jdbc/jdbcConfiguration.adoc
radovanradic c68b657
Renamed classes as suggested and also property to enable Oracle clien…
radovanradic 06901f3
Refactoring according to suggestions in PR comments.
radovanradic 5adc61e
Cleanup
radovanradic f66881f
Cache module name by the class name
radovanradic 5c97b15
Remove enabled attribute and create repeatable annotation
radovanradic da4e8a3
Fixes for Sonar
radovanradic 913d9a8
Merge branch '4.10.x' into jdbc-clientinfo
radovanradic 886b103
Update @since attributes
radovanradic 60458ad
Rename classes to shorter names
radovanradic e0099b8
Rename connection client info annotations
radovanradic e5e0c1b
Connection interceptor
dstepanov 7566f76
Adjust code per Denis's suggestion for callbacks
radovanradic a5d01a8
Merge branch '4.10.x' into jdbc-clientinfo
radovanradic 9130104
Revert changes in ConnectableInterceptor
radovanradic 8528515
Revert new line
radovanradic c325e1b
Reverted some changes and refactored code to resolve Sonar warning
radovanradic dcfbcfa
Updated docs
radovanradic 0b9dc3f
Merge branch '4.10.x' into jdbc-clientinfo
radovanradic File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 70 additions & 0 deletions
70
...dbc/src/main/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoCondition.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.11 | ||
*/ | ||
@Internal | ||
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 = "enable-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); | ||
} | ||
} |
195 changes: 195 additions & 0 deletions
195
...ain/java/io/micronaut/data/connection/jdbc/oracle/OracleClientInfoConnectionListener.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
/* | ||
* 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.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.annotation.ConnectionClientInfoAttribute; | ||
import io.micronaut.data.connection.annotation.ConnectionClientInfo; | ||
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; | ||
|
||
import javax.sql.DataSource; | ||
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; | ||
|
||
/** | ||
* 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.11 | ||
*/ | ||
@EachBean(DataSource.class) | ||
@Requires(condition = OracleClientInfoCondition.class) | ||
@Context | ||
@Internal | ||
final class OracleClientInfoConnectionListener implements ConnectionListener<Connection> { | ||
|
||
private static final String NAME_MEMBER = "name"; | ||
private static final String VALUE_MEMBER = "value"; | ||
private static final String INTERCEPTED_SUFFIX = "$Intercepted"; | ||
|
||
/** | ||
* 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 Logger LOG = LoggerFactory.getLogger(OracleClientInfoConnectionListener.class); | ||
|
||
private static final Map<Class<?>, String> MODULE_CLASS_MAP = new ConcurrentHashMap<>(100); | ||
|
||
@Nullable | ||
private final String applicationName; | ||
|
||
OracleClientInfoConnectionListener(@NonNull DataSource dataSource, | ||
@NonNull @Parameter AbstractConnectionOperations<Connection> 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); | ||
} | ||
} catch (SQLException e) { | ||
LOG.error("Failed to get connection for oracle connection listener", e); | ||
} | ||
} | ||
|
||
@Override | ||
public void afterOpen(@NonNull ConnectionStatus<Connection> connectionStatus) { | ||
ConnectionDefinition connectionDefinition = connectionStatus.getDefinition(); | ||
// Set client info for connection if Oracle connection after connection is opened | ||
Map<String, String> 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<String, String> 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); | ||
} | ||
} | ||
} | ||
|
||
@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(); | ||
Map<String, String> connectionClientInfo = getConnectionClientInfo(connectionDefinition); | ||
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); | ||
} | ||
} | ||
} | ||
|
||
@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; | ||
} | ||
} | ||
|
||
/** | ||
* 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<String, String> getConnectionClientInfo(@NonNull ConnectionDefinition connectionDefinition) { | ||
AnnotationMetadata annotationMetadata = connectionDefinition.getAnnotationMetadata(); | ||
AnnotationValue<ConnectionClientInfo> annotation = annotationMetadata.getAnnotation(ConnectionClientInfo.class); | ||
if (annotation == null) { | ||
return null; | ||
} | ||
List<AnnotationValue<ConnectionClientInfoAttribute>> clientInfoAttributes = annotation.getAnnotations(VALUE_MEMBER); | ||
Map<String, String> additionalClientInfoAttributes = new LinkedHashMap<>(clientInfoAttributes.size()); | ||
for (AnnotationValue<ConnectionClientInfoAttribute> 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) { | ||
additionalClientInfoAttributes.putIfAbsent(ORACLE_MODULE, | ||
MODULE_CLASS_MAP.computeIfAbsent(methodInvocationContext.getTarget().getClass(), | ||
clazz -> clazz.getName().replace(INTERCEPTED_SUFFIX, "")) | ||
); | ||
additionalClientInfoAttributes.putIfAbsent(ORACLE_ACTION, methodInvocationContext.getName()); | ||
} | ||
return additionalClientInfoAttributes; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@EachBean(DataSource.class)
was not creating this bean until added@Context
. Not sure if this can cause some issues, I would prefer if it could work without@Context
but not sure what is missing.