Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
/.metadata
/.idea/
.factorypath
/.claude/
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.seasar.doma.gradle.codegen.dialect.CodeGenDialectRegistry;
import org.seasar.doma.gradle.codegen.exception.CodeGenException;
import org.seasar.doma.gradle.codegen.generator.Generator;
import org.seasar.doma.gradle.codegen.jdbc.DriverWrapper;
import org.seasar.doma.gradle.codegen.message.Message;
import org.seasar.doma.gradle.codegen.util.ClassUtil;
import org.seasar.doma.gradle.codegen.util.JdbcUtil;
Expand Down Expand Up @@ -159,9 +160,10 @@ private Provider<DataSource> dataSourceProvider() {
ClassLoader classLoader = createClassLoader();
Driver driver =
ClassUtil.newInstance(Driver.class, driverClassName, "driverClassName", classLoader);
DriverWrapper driverWrapper = new DriverWrapper(driver);
return globalFactory
.get()
.createDataSource(driver, user.getOrNull(), password.getOrNull(), url.get());
.createDataSource(driverWrapper, user.getOrNull(), password.getOrNull(), url.get());
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package org.seasar.doma.gradle.codegen.jdbc;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Objects;
import java.util.Properties;
import java.util.logging.Logger;

/**
* A wrapper for JDBC Driver that manages class loader context switching.
*
* <p>This wrapper ensures that all driver operations are executed with the correct class loader
* context, preventing class loading issues when the driver and calling code are loaded by different
* class loaders.
*
* @see java.sql.Driver
*/
public class DriverWrapper implements Driver {

private final Driver driver;

/**
* Constructs a new DriverWrapper with the specified driver.
*
* @param driver the driver to wrap, must not be null
* @throws NullPointerException if driver is null
*/
public DriverWrapper(Driver driver) {
this.driver = Objects.requireNonNull(driver);
}

/**
* {@inheritDoc}
*
* <p>Executes with the driver's class loader context.
*/
@Override
public Connection connect(String url, Properties info) throws SQLException {
return execute(() -> driver.connect(url, info));
}

/**
* {@inheritDoc}
*
* <p>Executes with the driver's class loader context.
*/
@Override
public boolean acceptsURL(String url) throws SQLException {
return execute(() -> driver.acceptsURL(url));
}

/**
* {@inheritDoc}
*
* <p>Executes with the driver's class loader context.
*/
@Override
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
return execute(() -> driver.getPropertyInfo(url, info));
}

/**
* {@inheritDoc}
*
* <p>Executes with the driver's class loader context.
*/
@Override
public int getMajorVersion() {
return execute(driver::getMajorVersion);
}

/**
* {@inheritDoc}
*
* <p>Executes with the driver's class loader context.
*/
@Override
public int getMinorVersion() {
return execute(driver::getMinorVersion);
}

/**
* {@inheritDoc}
*
* <p>Executes with the driver's class loader context.
*/
@Override
public boolean jdbcCompliant() {
return execute(driver::jdbcCompliant);
}

/**
* {@inheritDoc}
*
* <p>Executes with the driver's class loader context.
*/
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return execute(driver::getParentLogger);
}

/**
* Executes an operation with the driver's class loader as the context class loader.
*
* <p>This method temporarily switches the thread's context class loader to the driver's class
* loader, executes the operation, and then restores the original class loader. This ensures that
* the driver can properly load its internal classes and resources.
*
* @param <R> the return type of the executable
* @param <TH> the type of exception that may be thrown
* @param executable the operation to execute
* @return the result of the executable operation
* @throws TH if the executable operation throws an exception
*/
private <R, TH extends Exception> R execute(Executable<R, TH> executable) throws TH {
var classLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(driver.getClass().getClassLoader());
try {
return executable.execute();
} finally {
Thread.currentThread().setContextClassLoader(classLoader);
}
}

/**
* Represents an executable operation that can return a result and throw an exception.
*
* @param <R> the return type
* @param <TH> the type of exception that may be thrown
*/
private interface Executable<R, TH extends Exception> {
/**
* Executes the operation.
*
* @return the result of the operation
* @throws TH if an error occurs during execution
*/
R execute() throws TH;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public final class JdbcUtil {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(JdbcUtil.class);

protected static final Pattern jdbcUrlPattern =
Pattern.compile("jdbc:(?:tc:)?([^:]+):(([^:]+)?:)?");
Pattern.compile("jdbc:(tc:)?([^:]+):(([^:]+)?:)?");

public static Connection getConnection(DataSource dataSource) {
try {
Expand Down Expand Up @@ -79,7 +79,7 @@ public static String inferDialectName(String url) {
return match(
url,
names -> {
switch (names.getFirst()) {
switch (names.second()) {
case "h2":
return "h2";
case "hsqldb":
Expand Down Expand Up @@ -107,15 +107,18 @@ public static String inferDriverClassName(String url) {
return match(
url,
names -> {
switch (names.getFirst()) {
if ("tc:".equals(names.first())) {
return "org.testcontainers.jdbc.ContainerDatabaseDriver";
}
switch (names.second()) {
case "h2":
return "org.h2.Driver";
case "hsqldb":
return "org.hsqldb.jdbc.JDBCDriver";
case "sqlite":
return "org.sqlite.JDBC";
case "mysql":
if ("aws".equals(names.getSecond())) {
if ("aws".equals(names.third())) {
return "software.aws.rds.jdbc.mysql.Driver";
}
return "com.mysql.cj.jdbc.Driver";
Expand All @@ -135,16 +138,19 @@ public static String inferDriverClassName(String url) {
});
}

protected static <R> R match(String url, Function<Pair<String, String>, R> mapper) {
protected static <R> R match(String url, Function<Triple, R> mapper) {
if (url == null) {
throw new CodeGenNullPointerException("url");
}
Matcher matcher = jdbcUrlPattern.matcher(url);
if (matcher.lookingAt()) {
String first = matcher.group(1);
String second = matcher.group(3);
return mapper.apply(new Pair<>(first, second));
String second = matcher.group(2);
String third = matcher.group(4);
return mapper.apply(new Triple(first, second, third));
}
return null;
}

protected record Triple(String first, String second, String third) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,36 +116,36 @@ public void testInferDialectName_testcontainers_db2() throws Exception {
@Test
public void testInferDriverClassName_testcontainers_postgresql() throws Exception {
String driverClassName = JdbcUtil.inferDriverClassName("jdbc:tc:postgresql:13:///test");
assertEquals("org.postgresql.Driver", driverClassName);
assertEquals("org.testcontainers.jdbc.ContainerDatabaseDriver", driverClassName);
}

@Test
public void testInferDriverClassName_testcontainers_mysql() throws Exception {
String driverClassName = JdbcUtil.inferDriverClassName("jdbc:tc:mysql:8:///test");
assertEquals("com.mysql.cj.jdbc.Driver", driverClassName);
assertEquals("org.testcontainers.jdbc.ContainerDatabaseDriver", driverClassName);
}

@Test
public void testInferDriverClassName_testcontainers_mariadb() throws Exception {
String driverClassName = JdbcUtil.inferDriverClassName("jdbc:tc:mariadb:10.5:///test");
assertEquals("org.mariadb.jdbc.Driver", driverClassName);
assertEquals("org.testcontainers.jdbc.ContainerDatabaseDriver", driverClassName);
}

@Test
public void testInferDriverClassName_testcontainers_oracle() throws Exception {
String driverClassName = JdbcUtil.inferDriverClassName("jdbc:tc:oracle:21c:///test");
assertEquals("oracle.jdbc.driver.OracleDriver", driverClassName);
assertEquals("org.testcontainers.jdbc.ContainerDatabaseDriver", driverClassName);
}

@Test
public void testInferDriverClassName_testcontainers_sqlserver() throws Exception {
String driverClassName = JdbcUtil.inferDriverClassName("jdbc:tc:sqlserver:2019:///test");
assertEquals("com.microsoft.sqlserver.jdbc.SQLServerDriver", driverClassName);
assertEquals("org.testcontainers.jdbc.ContainerDatabaseDriver", driverClassName);
}

@Test
public void testInferDriverClassName_testcontainers_db2() throws Exception {
String driverClassName = JdbcUtil.inferDriverClassName("jdbc:tc:db2:11.5:///test");
assertEquals("com.ibm.db2.jcc.DB2Driver", driverClassName);
assertEquals("org.testcontainers.jdbc.ContainerDatabaseDriver", driverClassName);
}
}