diff --git a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/connectors/ActiveResourceAdapterImpl.java b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/connectors/ActiveResourceAdapterImpl.java index ee8c13c4f29..b7454dd14e8 100755 --- a/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/connectors/ActiveResourceAdapterImpl.java +++ b/appserver/connectors/connectors-runtime/src/main/java/com/sun/enterprise/connectors/ActiveResourceAdapterImpl.java @@ -33,6 +33,7 @@ import com.sun.enterprise.util.i18n.StringManager; import com.sun.logging.LogDomains; +import jakarta.inject.Inject; import jakarta.resource.spi.ManagedConnectionFactory; import java.util.Set; @@ -41,6 +42,7 @@ import org.glassfish.api.naming.SimpleJndiName; import org.glassfish.hk2.api.PerLookup; +import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.resourcebase.resources.api.PoolInfo; import org.glassfish.resourcebase.resources.api.ResourceInfo; import org.jvnet.hk2.annotations.Service; @@ -64,13 +66,15 @@ public class ActiveResourceAdapterImpl implements ActiveResourceAdapter { private static Logger _logger = LogDomains.getLogger(ActiveResourceAdapterImpl.class, LogDomains.RSR_LOGGER); private final StringManager localStrings = StringManager.getManager(ActiveResourceAdapterImpl.class); + @Inject + protected ServiceLocator locator; + protected ConnectorDescriptor desc_; protected String moduleName_; protected ClassLoader jcl_; protected ConnectionDefDescriptor[] connectionDefs_; protected ConnectorRuntime connectorRuntime_; - /** * Constructor. * @@ -385,17 +389,18 @@ private void setLogWriter(ManagedConnectionFactory mcf) { } - protected ManagedConnectionFactory instantiateMCF(String mcfClass, ClassLoader loader) throws Exception { + protected ManagedConnectionFactory instantiateMCF(String mcfClassName, ClassLoader loader) throws Exception { ManagedConnectionFactory mcf = null; + Class mcfClass; if (jcl_ != null) { - mcf = (ManagedConnectionFactory) jcl_.loadClass(mcfClass).getDeclaredConstructor().newInstance(); + mcfClass = jcl_.loadClass(mcfClassName); } else if (loader != null) { - mcf = (ManagedConnectionFactory) loader.loadClass(mcfClass).getDeclaredConstructor().newInstance(); + mcfClass = loader.loadClass(mcfClassName); + } else { - // mcf = (ManagedConnectionFactory) Class.forName(mcfClass).newInstance(); - mcf = (ManagedConnectionFactory) Thread.currentThread().getContextClassLoader().loadClass(mcfClass) - .getDeclaredConstructor().newInstance(); + mcfClass = Thread.currentThread().getContextClassLoader().loadClass(mcfClassName); } + mcf = locator.createAndInitialize((Class)mcfClass); setLogWriter(mcf); return mcf; } diff --git a/appserver/itest-tools/src/main/java/org/glassfish/main/itest/tools/asadmin/DomainSettings.java b/appserver/itest-tools/src/main/java/org/glassfish/main/itest/tools/asadmin/DomainSettings.java new file mode 100644 index 00000000000..04c00d60eb9 --- /dev/null +++ b/appserver/itest-tools/src/main/java/org/glassfish/main/itest/tools/asadmin/DomainSettings.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024 Eclipse Foundation and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.main.itest.tools.asadmin; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import static org.glassfish.main.itest.tools.asadmin.AsadminResultMatcher.asadminOK; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * + * @author Ondro Mihalyi + */ +public class DomainSettings { + + private final Asadmin asadmin; + List settingsBackup = new ArrayList<>(); + + public DomainSettings(Asadmin asadmin) { + this.asadmin = asadmin; + } + + public void backupSettings(String getKey) { + final AsadminResult result = asadmin.exec(5_000, "get", getKey); + Stream.of(result.getStdOut().split("\n")) + // Exclude "command successful + .filter(line -> line.contains("=")) + // Exclude .name for connection pools which cannot be changed + .filter(line -> !line.startsWith("resources.jdbc-connection-pool") || !line.contains(".name=")) + .forEach(settingsBackup::add); + } + + public void restoreSettings() { + String[] args = new String[settingsBackup.size() + 1]; + args[0] = "set"; + for (int i = 1; i < args.length; i++) { + args[i] = settingsBackup.get(i - 1); + } + settingsBackup.clear(); + final AsadminResult result = asadmin.exec(5_000, args); + assertThat(result, asadminOK()); + } + + public void backupDerbyPoolSettings() { + backupSettings("resources.jdbc-connection-pool.DerbyPool.*"); + } + + /** Default is org.apache.derby.jdbc.ClientDataSource */ + public void setDerbyPoolEmbededded() { + final AsadminResult result = asadmin.exec(5_000, "set", + "resources.jdbc-connection-pool.DerbyPool.datasource-classname=org.apache.derby.jdbc.EmbeddedDataSource", + "resources.jdbc-connection-pool.DerbyPool.property.PortNumber=", + "resources.jdbc-connection-pool.DerbyPool.property.serverName=", + "resources.jdbc-connection-pool.DerbyPool.property.URL="); + assertThat(result, asadminOK()); + asadmin.exec(5_000, "get", "resources.jdbc-connection-pool.DerbyPool.*"); + } + + +} diff --git a/appserver/jdbc/jdbc-ra/jdbc-core/src/main/java/com/sun/gjc/spi/ManagedConnectionFactoryImpl.java b/appserver/jdbc/jdbc-ra/jdbc-core/src/main/java/com/sun/gjc/spi/ManagedConnectionFactoryImpl.java index 082c8f6d513..70d71ae10cf 100644 --- a/appserver/jdbc/jdbc-ra/jdbc-core/src/main/java/com/sun/gjc/spi/ManagedConnectionFactoryImpl.java +++ b/appserver/jdbc/jdbc-ra/jdbc-core/src/main/java/com/sun/gjc/spi/ManagedConnectionFactoryImpl.java @@ -25,6 +25,7 @@ import com.sun.gjc.util.SQLTraceDelegator; import com.sun.logging.LogDomains; +import jakarta.inject.Inject; import jakarta.resource.ResourceException; import jakarta.resource.spi.ConfigProperty; import jakarta.resource.spi.ConnectionManager; @@ -65,6 +66,7 @@ import javax.sql.DataSource; import javax.sql.PooledConnection; +import org.glassfish.api.invocation.InvocationManager; import org.glassfish.api.jdbc.ConnectionValidation; import org.glassfish.api.jdbc.SQLTraceListener; import org.glassfish.api.jdbc.objects.TxIsolationLevel; @@ -101,6 +103,9 @@ public abstract class ManagedConnectionFactoryImpl private static Logger _logger = LogDomains.getLogger(ManagedConnectionFactoryImpl.class, LogDomains.RSR_LOGGER); protected static final StringManager localStrings = StringManager.getManager(DataSourceObjectBuilder.class); + @Inject + protected InvocationManager invocationManager; + protected DataSourceSpec spec = new DataSourceSpec(); protected transient DataSourceObjectBuilder dataSourceObjectBuilder; protected PrintWriter logWriter; @@ -553,7 +558,7 @@ private void detectSqlTraceListeners() { String delimiter = ","; if (sqlTraceListeners != null && !sqlTraceListeners.equals("null")) { - sqlTraceDelegator = new SQLTraceDelegator(getPoolName(), getApplicationName(), getModuleName()); + sqlTraceDelegator = new SQLTraceDelegator(getPoolName(), invocationManager); StringTokenizer st = new StringTokenizer(sqlTraceListeners, delimiter); while (st.hasMoreTokens()) { diff --git a/appserver/jdbc/jdbc-ra/jdbc-core/src/main/java/com/sun/gjc/util/SQLTraceDelegator.java b/appserver/jdbc/jdbc-ra/jdbc-core/src/main/java/com/sun/gjc/util/SQLTraceDelegator.java index 3946bfcf017..7da4bd7318a 100644 --- a/appserver/jdbc/jdbc-ra/jdbc-core/src/main/java/com/sun/gjc/util/SQLTraceDelegator.java +++ b/appserver/jdbc/jdbc-ra/jdbc-core/src/main/java/com/sun/gjc/util/SQLTraceDelegator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2022,2024 Contributors to the Eclipse Foundation * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the @@ -14,7 +14,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ - package com.sun.gjc.util; import com.sun.gjc.monitoring.JdbcRAConstants; @@ -22,9 +21,14 @@ import com.sun.logging.LogDomains; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Optional; +import java.util.Set; import java.util.logging.Logger; +import org.glassfish.api.invocation.ComponentInvocation; +import org.glassfish.api.invocation.InvocationManager; import org.glassfish.api.jdbc.SQLTraceListener; import org.glassfish.api.jdbc.SQLTraceRecord; import org.glassfish.api.naming.SimpleJndiName; @@ -45,24 +49,21 @@ public class SQLTraceDelegator implements SQLTraceListener { // List of listeners protected List sqlTraceListenersList; private final SimpleJndiName poolName; - private final String appName; - private final String moduleName; + private InvocationManager invocationManager; private SQLTraceProbeProvider probeProvider = null; public SQLTraceProbeProvider getProbeProvider() { return probeProvider; } - public SQLTraceDelegator(SimpleJndiName poolName, String appName, String moduleName) { + public SQLTraceDelegator(SimpleJndiName poolName, InvocationManager invocationManager) { this.poolName = poolName; - this.appName = appName; - this.moduleName = moduleName; + this.invocationManager = invocationManager; probeProvider = new SQLTraceProbeProvider(); } /** - * Add a listener to the list of sql trace listeners maintained by this - * registry. + * Add a listener to the list of sql trace listeners maintained by this registry. * * @param listener */ @@ -75,50 +76,102 @@ public void registerSQLTraceListener(SQLTraceListener listener) { @Override public void sqlTrace(SQLTraceRecord record) { - if (sqlTraceListenersList != null) { - for (SQLTraceListener listener : sqlTraceListenersList) { - try { - listener.sqlTrace(record); - } catch (Exception e) { - // it is possible that any of the implementations may fail processing a trace - // record. - // do not propagate such failures. Log them as FINEST. - if (_logger.isLoggable(FINEST)) { - _logger.log(FINEST, - "exception from one of the SQL trace listeners [" + listener.getClass().getName() + "]", - e); + if (record != null) { + record.setPoolName(poolName.toString()); + + String sqlQuery = findSqlQuery(record); + record.setSqlQuery(sqlQuery); + record.setApplicationName(getAppName()); + record.setModuleName(getModuleName()); + + if (sqlQuery != null) { + probeProvider.traceSQLEvent(poolName.toString(), record.getApplicationName(), + record.getModuleName(), sqlQuery); + } + + if (sqlTraceListenersList != null && !sqlTraceListenersList.isEmpty()) { + getCallingApplicationStackFrame().ifPresent(record::setCallingApplicationMethod); + for (SQLTraceListener listener : sqlTraceListenersList) { + try { + listener.sqlTrace(record); + } catch (Exception e) { + // it is possible that any of the implementations may fail processing a trace + // record. + // do not propagate such failures. Log them as FINEST. + if (_logger.isLoggable(FINEST)) { + _logger.log(FINEST, + "exception from one of the SQL trace listeners [" + listener.getClass().getName() + "]", + e); + } } } } } + } - if (record != null) { - record.setPoolName(poolName.toString()); - String methodName = record.getMethodName(); - // Check if the method name is one in which sql query is used - if (isMethodValidForCaching(methodName)) { - Object[] params = record.getParams(); - if (params != null && params.length > 0) { - String sqlQuery = null; - for (Object param : params) { - if (param instanceof String) { - sqlQuery = param.toString(); - } - break; - } - if (sqlQuery != null) { - probeProvider.traceSQLEvent(poolName.toString(), appName, moduleName, sqlQuery); + private Optional getCallingApplicationStackFrame() { + Set> checkedClasses = new HashSet<>(); + checkedClasses.add(this.getClass()); + return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) + .walk(traces -> { + return traces + .filter(trace -> { + Class declaringClass = trace.getDeclaringClass(); + final ClassLoader appClassLoader = Thread.currentThread().getContextClassLoader(); + boolean result = !checkedClasses.contains(declaringClass) && isClassFromApplication(declaringClass, appClassLoader); + if (!result) { + checkedClasses.add(declaringClass); + } + return result; + }) + .findFirst(); + }); + } + + private boolean isClassFromApplication(Class cls, ClassLoader appClassLoader) { + ClassLoader clsClassLoader = cls.getClassLoader(); + while (clsClassLoader != null) { + if (clsClassLoader.equals(appClassLoader)) { + return true; + } + clsClassLoader = clsClassLoader.getParent(); + } + return false; + } + + private String findSqlQuery(SQLTraceRecord record) { + // Check if the method name is one in which sql query is used + String methodName = record.getMethodName(); + String sqlQuery = null; + + if (isMethodValidForCaching(methodName)) { + Object[] params = record.getParams(); + if (params != null && params.length > 0) { + for (Object param : params) { + if (param instanceof String) { + sqlQuery = param.toString(); } + break; } } } + return sqlQuery; + } + + private String getAppName() { + ComponentInvocation currentInvocation = invocationManager.getCurrentInvocation(); + return currentInvocation != null ? currentInvocation.getAppName() : null; + } + + private String getModuleName() { + ComponentInvocation currentInvocation = invocationManager.getCurrentInvocation(); + return currentInvocation != null ? currentInvocation.getModuleName(): null; } /** - * Check if the method name from the sql trace record can be used to retrieve a - * sql string for caching purpose. Most of the method names do not contain a sql - * string and hence are unusable for caching the sql strings. These method names - * are filtered in this method. + * Check if the method name from the sql trace record can be used to retrieve a sql string for caching purpose. Most + * of the method names do not contain a sql string and hence are unusable for caching the sql strings. These method + * names are filtered in this method. * * @param methodName * @return true if method name can be used to get a sql string for caching. @@ -126,4 +179,5 @@ public void sqlTrace(SQLTraceRecord record) { private boolean isMethodValidForCaching(String methodName) { return JdbcRAConstants.validSqlTracingMethodNames.contains(methodName); } + } diff --git a/appserver/tests/application/pom.xml b/appserver/tests/application/pom.xml index b27195f3437..b0b77e70bc8 100755 --- a/appserver/tests/application/pom.xml +++ b/appserver/tests/application/pom.xml @@ -1,7 +1,7 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/appserver/tests/application/src/test/java/org/glassfish/main/test/app/connpool/SQLTraceListenerTest.java b/appserver/tests/application/src/test/java/org/glassfish/main/test/app/connpool/SQLTraceListenerTest.java new file mode 100644 index 00000000000..c41c24fdb39 --- /dev/null +++ b/appserver/tests/application/src/test/java/org/glassfish/main/test/app/connpool/SQLTraceListenerTest.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.main.test.app.connpool; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; + +import org.glassfish.main.itest.tools.GlassFishTestEnvironment; +import org.glassfish.main.itest.tools.asadmin.Asadmin; +import org.glassfish.main.itest.tools.asadmin.AsadminResult; +import org.glassfish.main.itest.tools.asadmin.DomainSettings; +import org.glassfish.main.test.app.connpool.lib.LastTraceSQLTraceListener; +import org.glassfish.main.test.app.connpool.webapp.Employee; +import org.glassfish.main.test.app.connpool.webapp.SqlListenerApplication; +import org.glassfish.main.test.app.connpool.webapp.SqlListenerEndpoint; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.exporter.ZipExporter; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static java.lang.System.Logger.Level.INFO; +import static org.glassfish.main.itest.tools.GlassFishTestEnvironment.openConnection; +import static org.glassfish.main.itest.tools.asadmin.AsadminResultMatcher.asadminOK; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +public class SQLTraceListenerTest { + + private static final System.Logger LOG = System.getLogger(SQLTraceListenerTest.class.getName()); + + private static final String RESOURCE_ROOT = "src/main/resources/" + SqlListenerApplication.class.getPackageName().replace(".", "/"); + + private static final String LIB_FILE_NAME = "lib.jar"; + + private static final String WEBAPP_FILE_NAME = "webapp.war"; + + private static final String WEBAPP_NAME = "webapp"; + + private static final String POOL_NAME = "DerbyPool"; + + private static final Asadmin ASADMIN = GlassFishTestEnvironment.getAsadmin(); + + private static final DomainSettings DOMAIN_SETTINGS = new DomainSettings(ASADMIN); + + @TempDir + private static File appLibDir; + + @TempDir + private static File webAppDir; + + @BeforeAll + public static void deployAll() throws IOException { + AsadminResult result; + + File webApp = createWebApp(); + + File lib = createSqlTraceListenerLib(); + + result = ASADMIN.exec("add-library", lib.getAbsolutePath()); + assertThat(result, asadminOK()); + + // add-library requires restart + result = ASADMIN.exec("restart-domain"); + assertThat(result, asadminOK()); + + DOMAIN_SETTINGS.backupDerbyPoolSettings(); + DOMAIN_SETTINGS.setDerbyPoolEmbededded(); + + result = ASADMIN.exec("set", "resources.jdbc-connection-pool." + POOL_NAME + + ".sql-trace-listeners=" + LastTraceSQLTraceListener.class.getName()); + assertThat(result, asadminOK()); + + result = ASADMIN.exec("deploy", + "--contextroot", "/" + WEBAPP_NAME, + "--name", WEBAPP_NAME, + webApp.getAbsolutePath()); + assertThat(result, asadminOK()); + } + + @AfterAll + public static void undeployAll() { + DOMAIN_SETTINGS.restoreSettings(); + assertAll( + () -> assertThat(ASADMIN.exec("undeploy", WEBAPP_NAME), asadminOK()), + () -> assertThat(ASADMIN.exec("set", "resources.jdbc-connection-pool." + POOL_NAME + + ".sql-trace-listeners="), asadminOK()), + () -> assertThat(ASADMIN.exec("remove-library", LIB_FILE_NAME), asadminOK()) + ); + } + + @Test + public void testSQLQueryWithListener() throws IOException { + createWebApp(); + assertValidTraceRecordReceived(WEBAPP_NAME, "validate-trace-listener"); + } + + private void assertValidTraceRecordReceived(String contextRoot, String endpoint) throws IOException { + HttpURLConnection connection = openConnection(8080, "/" + contextRoot + "/" + endpoint); + connection.setRequestMethod("GET"); + try { + try { + assertThat(connection.getResponseCode(), equalTo(200)); + } catch (AssertionError e) { + throw new AssertionError(readErrorResponse(connection), e); + } + } finally { + connection.disconnect(); + } + } + + private String readErrorResponse(HttpURLConnection connection) throws IOException { + try (InputStream inputStream = connection.getErrorStream()) { + return new String(inputStream.readAllBytes()).trim(); + } + } + + private static File createSqlTraceListenerLib() throws IOException { + JavaArchive javaArchive = ShrinkWrap.create(JavaArchive.class) + .addClasses(LastTraceSQLTraceListener.class); + + LOG.log(INFO, javaArchive.toString(true)); + + File appLib = new File(appLibDir, LIB_FILE_NAME); + javaArchive.as(ZipExporter.class).exportTo(appLib, true); + return appLib; + } + + private static File createWebApp() throws IOException { + WebArchive webArchive = ShrinkWrap.create(WebArchive.class) + .addClass(SqlListenerApplication.class) + .addClass(SqlListenerEndpoint.class) + .addClass(Employee.class) + .addAsResource(new File(RESOURCE_ROOT, "/META-INF/persistence.xml"), "/META-INF/persistence.xml") + .addAsResource(new File(RESOURCE_ROOT, "/META-INF/load.sql"), "/META-INF/load.sql"); + + LOG.log(INFO, webArchive.toString(true)); + + File webApp = new File(webAppDir, WEBAPP_FILE_NAME); + webArchive.as(ZipExporter.class).exportTo(webApp, true); + return webApp; + } +} diff --git a/appserver/tests/application/src/test/java/org/glassfish/main/test/app/persistence/resourceref/JtaDataSourceResourceRefTest.java b/appserver/tests/application/src/test/java/org/glassfish/main/test/app/persistence/resourceref/JtaDataSourceResourceRefTest.java index cd8e1dea7d6..a2c4e54f6ee 100644 --- a/appserver/tests/application/src/test/java/org/glassfish/main/test/app/persistence/resourceref/JtaDataSourceResourceRefTest.java +++ b/appserver/tests/application/src/test/java/org/glassfish/main/test/app/persistence/resourceref/JtaDataSourceResourceRefTest.java @@ -18,11 +18,11 @@ import java.io.File; import java.io.IOException; import java.net.HttpURLConnection; -import java.util.stream.Stream; import org.glassfish.main.itest.tools.TestUtilities; import org.glassfish.main.itest.tools.asadmin.Asadmin; import org.glassfish.main.itest.tools.asadmin.AsadminResult; +import org.glassfish.main.itest.tools.asadmin.DomainSettings; import org.glassfish.main.test.app.persistence.resourceref.webapp.ResourceRefApplication; import org.glassfish.main.test.app.persistence.resourceref.webapp.ResourceRefResource; import org.jboss.shrinkwrap.api.ShrinkWrap; @@ -51,12 +51,12 @@ public class JtaDataSourceResourceRefTest { private static final String CONTEXT_ROOT = "/"; private static final Asadmin ASADMIN = getAsadmin(); - private static String[] derbyPoolSettingsBackup; + private static final DomainSettings DOMAIN_SETTINGS = new DomainSettings(ASADMIN); @BeforeAll public static void deploy() throws Exception { - backupDerbyPoolSettings(); - setDerbyPoolEmbededded(); + DOMAIN_SETTINGS.backupDerbyPoolSettings(); + DOMAIN_SETTINGS.setDerbyPoolEmbededded(); final File warFile = createDeployment(); try { AsadminResult result = ASADMIN.exec("deploy", "--contextroot", CONTEXT_ROOT, "--name", APP_NAME, @@ -72,7 +72,7 @@ public static void deploy() throws Exception { static void undeploy() { AsadminResult result = ASADMIN.exec("undeploy", APP_NAME); assertThat(result, asadminOK()); - restoreDerbyPoolSettings(); + DOMAIN_SETTINGS.restoreSettings(); } @@ -87,34 +87,6 @@ public void test() throws IOException { } } - private static void backupDerbyPoolSettings() { - final AsadminResult result = ASADMIN.exec(5_000, "get", "resources.jdbc-connection-pool.DerbyPool.*"); - // Exclude "command successful and .name which cannot be changed - derbyPoolSettingsBackup = Stream.of(result.getStdOut().split("\n")) - .filter(line -> line.contains("=") && !line.contains(".name=")).toArray(String[]::new); - } - - private static void restoreDerbyPoolSettings() { - String[] args = new String[derbyPoolSettingsBackup.length + 1]; - args[0] = "set"; - for (int i = 1; i < args.length; i++) { - args[i] = derbyPoolSettingsBackup[i - 1]; - } - final AsadminResult result = ASADMIN.exec(5_000, args); - assertThat(result, asadminOK()); - } - - /** Default is org.apache.derby.jdbc.ClientDataSource */ - private static void setDerbyPoolEmbededded() { - final AsadminResult result = ASADMIN.exec(5_000, "set", - "resources.jdbc-connection-pool.DerbyPool.datasource-classname=org.apache.derby.jdbc.EmbeddedDataSource", - "resources.jdbc-connection-pool.DerbyPool.property.PortNumber=", - "resources.jdbc-connection-pool.DerbyPool.property.serverName=", - "resources.jdbc-connection-pool.DerbyPool.property.URL="); - assertThat(result, asadminOK()); - ASADMIN.exec(5_000, "get", "resources.jdbc-connection-pool.DerbyPool.*"); - } - private static File createDeployment() throws IOException { final WebArchive webArchive = ShrinkWrap.create(WebArchive.class) .addClass(ResourceRefResource.class) diff --git a/nucleus/common/glassfish-api/src/main/java/org/glassfish/api/jdbc/SQLTraceRecord.java b/nucleus/common/glassfish-api/src/main/java/org/glassfish/api/jdbc/SQLTraceRecord.java index 240b5d8b330..3c8d6f30d55 100644 --- a/nucleus/common/glassfish-api/src/main/java/org/glassfish/api/jdbc/SQLTraceRecord.java +++ b/nucleus/common/glassfish-api/src/main/java/org/glassfish/api/jdbc/SQLTraceRecord.java @@ -1,4 +1,5 @@ /* + * Copyright (c) 2024 Contributors to the Eclipse Foundation * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the @@ -13,10 +14,10 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ - package org.glassfish.api.jdbc; import java.io.Serializable; +import java.util.Optional; /** * Information related to SQL operations executed by the applications are stored in this object. @@ -26,6 +27,7 @@ * @author Shalini M */ public class SQLTraceRecord implements Serializable { + /** * */ @@ -46,6 +48,16 @@ public class SQLTraceRecord implements Serializable { */ private String poolName; + /** + * Application Name which executed the SQL statement + */ + private String applicationName; + + /** + * Module Name which executed the SQL statement + */ + private String moduleName; + /** * Type of SQL query. Could be PreparedStatement, CallableStatement or other object types. */ @@ -67,7 +79,18 @@ public class SQLTraceRecord implements Serializable { private Object[] params; /** - * Gets the class name of the SQL query expressed as a String. + * The SQL query + */ + private String sqlQuery; + + /** + * Info about the method in the application that triggered the SQL query + */ + private StackWalker.StackFrame callingApplicationMethod; + + /** + * Gets the class name in the application which executed the SQL query, expressed as a String. + * If it's not possible to detect the application, it will be the class name which directly invoked the connection. * * @return The class name of the SQL query expressed as a String. */ @@ -76,7 +99,7 @@ public String getClassName() { } /** - * Sets the class name of the SQL query expressed as a String. + * Set the class name which executed the SQL query. * * @param className class name of the SQL query. */ @@ -85,7 +108,8 @@ public void setClassName(String className) { } /** - * Gets the method name that executed the SQL query. + * Gets the method name in the application which executed the SQL query, expressed as a String. + * If it's not possible to detect the application, it will be the method name which directly invoked the connection. * * @return methodName that executed the SQL query. */ @@ -194,21 +218,107 @@ public void setParams(Object[] params) { this.params = params; } + /** + * SQL query related to the database operation if applicable. + * + * @return SQL query + */ + public Optional getSqlQuery() { + return Optional.ofNullable(sqlQuery); + } + + /** + * Set the SQL query related to the database operation + * + * @param sqlQuery + */ + public void setSqlQuery(String sqlQuery) { + this.sqlQuery = sqlQuery; + } + + /** + * Application Name which executed the SQL statement + * + * @return Application name + */ + public String getApplicationName() { + return applicationName; + } + + /** + * Set the application name which executed the SQL statement + * + * @param applicationName + */ + public void setApplicationName(String applicationName) { + this.applicationName = applicationName; + } + + /** + * Module name of an application which executed the SQL statement. + * If the application doesn't have modules it will be equal to the application name + * + * @return Module name + */ + public String getModuleName() { + return moduleName; + } + + /** + * Set the module name which executed the SQL statement + * + * @param moduleName + */ + public void setModuleName(String moduleName) { + this.moduleName = moduleName; + } + + /** + * Get the stack frame for the method call in the application that triggered SQL execution, if the call comes from a deployed application. + * + * This returns a frame which returns a class from the {@link StackWalker.StackFrame#getDeclaringClass()} method + * ({@link StackWalker.Option.RETAIN_CLASS_REFERENCE} is enabled). + * + * @return Stack frame that represents a call to a server component that triggered SQL execution + */ + public Optional getCallingApplicationMethod() { + return Optional.ofNullable(callingApplicationMethod); + } + + /** + * Set the stack frame for the method call in the application that triggered SQL execution. + * The {@link StackWalker.StackFrame#getDeclaringClass()} should return a class and never throw {@link UnsupportedOperationException}. + */ + public void setCallingApplicationMethod(StackWalker.StackFrame callingApplicationMethod) { + this.callingApplicationMethod = callingApplicationMethod; + } + + + @Override public String toString() { - StringBuffer sb = new StringBuffer(); - sb.append("ThreadID=" + getThreadID() + " | "); - sb.append("ThreadName=" + getThreadName() + " | "); - sb.append("TimeStamp=" + getTimeStamp() + " | "); - sb.append("ClassName=" + getClassName() + " | "); - sb.append("MethodName=" + getMethodName() + " | "); + StringBuilder sb = new StringBuilder(); + sb.append("PoolName=").append(getPoolName()).append(" | "); + sb.append("ThreadID=").append(getThreadID()).append(" | "); + sb.append("ThreadName=").append(getThreadName()).append(" | "); + sb.append("TimeStamp=").append(getTimeStamp()).append(" | "); + sb.append("SQL=").append(getSqlQuery()).append(" | "); + sb.append("AppName=").append(getApplicationName()).append(" | "); + sb.append("ModuleName=").append(getModuleName()).append(" | "); + sb.append("ClassName=").append(getClassName()).append(" | "); + sb.append("MethodName=").append(getMethodName()).append(" | "); if (params != null && params.length > 0) { int index = 0; for (Object param : params) { - sb.append("arg[" + index++ + "]=" + (param != null ? param.toString() : "null") + " | "); + sb.append("arg[").append(index++).append("]=") + .append(param != null ? param.toString() : "null").append(" | "); } } - // TODO add poolNames and other fields of this record. + if (callingApplicationMethod == null) { + sb.append("CallingMethod=(null)").append(" | "); + } else { + sb.append("CallingMethod=").append(callingApplicationMethod).append(" | "); + } return sb.toString(); } }