This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit ce8b28fc15ac21fc9102eb9e796a56e5c454d760 Author: Andrus Adamchik <[email protected]> AuthorDate: Sun May 24 15:10:43 2026 -0400 CAY-2949 CayenneModeler MCP: dbimport_run tool 1. fixing issues 2. Don't duplicate connection opening --- .../mcp/tools/dbimport/DbImportRunTool.java | 85 +++++++++++----------- 1 file changed, 41 insertions(+), 44 deletions(-) diff --git a/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/dbimport/DbImportRunTool.java b/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/dbimport/DbImportRunTool.java index a73b10b40..0da88ca22 100644 --- a/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/dbimport/DbImportRunTool.java +++ b/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/dbimport/DbImportRunTool.java @@ -22,7 +22,6 @@ import io.modelcontextprotocol.json.McpJsonMapper; import io.modelcontextprotocol.server.McpServerFeatures; import io.modelcontextprotocol.spec.McpSchema; import org.apache.cayenne.configuration.DataChannelDescriptor; -import org.apache.cayenne.datasource.DriverDataSource; import org.apache.cayenne.dbsync.DbSyncModule; import org.apache.cayenne.dbsync.reverse.configuration.ToolsModule; import org.apache.cayenne.dbsync.reverse.dbimport.DbImportConfiguration; @@ -55,8 +54,6 @@ import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; -import java.sql.Connection; -import java.sql.Driver; import java.sql.SQLException; import java.util.List; import java.util.Map; @@ -211,44 +208,12 @@ public class DbImportRunTool { connector.getDbAdapter() ); - // Step 5 — JDBC driver loadable from Preferences → Classpath? + // Set the thread context classloader so DefaultClassLoaderManager (used by + // DriverDataSourceFactory → AdhocObjectFactory) picks up the driver JARs from preferences. + // Driver loading and connection failures surface as exceptions from action.execute() below. ClassLoader driverCl = buildDriverClassLoader(); - Driver driver; - try { - driver = (Driver) driverCl.loadClass(connector.getJdbcDriver()).getDeclaredConstructor().newInstance(); - } catch (ClassNotFoundException e) { - return validationFailed(DbImportErrorCode.jdbc_driver_not_loadable, - "JDBC driver class '%s' could not be loaded. In CayenneModeler, open Preferences → Classpath, add the driver jar, then re-run — the MCP server reads the classpath fresh on each call." - .formatted(connector.getJdbcDriver()), - new DbImportValidation(true, true, true, false, null), - resolved); - } catch (Exception e) { - return validationFailed(DbImportErrorCode.jdbc_driver_not_loadable, - "JDBC driver class '%s' could not be instantiated: %s" - .formatted(connector.getJdbcDriver(), e.getMessage()), - new DbImportValidation(true, true, true, false, null), - resolved); - } - - // Step 6 — JDBC connection opens? - Connection conn; - try { - conn = new DriverDataSource( - driver, - connector.getUrl(), - connector.getUserName(), - connector.getPassword()).getConnection(); - - } catch (SQLException e) { - String sqlState = e.getSQLState() != null ? " [SQLState: %s]".formatted(e.getSQLState()) : ""; - return validationFailed(DbImportErrorCode.jdbc_connection_failed, - "Could not open JDBC connection to '%s' as '%s': %s%s." - .formatted(connector.getUrl(), connector.getUserName(), e.getMessage(), sqlState), - new DbImportValidation(true, true, true, true, false), - resolved); - } - - // All validation passed — run dbimport + ClassLoader previousCl = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(driverCl); Supplier<List<String>> stopCapture = McpLoggingHandler.startCapture("org.apache.cayenne.dbsync"); List<String> warnings; try { @@ -263,16 +228,29 @@ public class DbImportRunTool { return new DbImportRunResult(status, summary, resolved, warnings, DbImportValidation.ALL_PASSED, null); } catch (Exception e) { warnings = stopCapture.get(); + if (hasCauseOfType(e, ClassNotFoundException.class)) { + return validationFailed(DbImportErrorCode.jdbc_driver_not_loadable, + "JDBC driver class '%s' could not be loaded. In CayenneModeler, open Preferences → Classpath, add the driver jar, then re-run." + .formatted(connector.getJdbcDriver()), + new DbImportValidation(true, true, true, false, null), + resolved); + } + if (hasCauseOfType(e, SQLException.class)) { + SQLException sqle = causeOfType(e, SQLException.class); + String sqlState = sqle.getSQLState() != null ? " [SQLState: %s]".formatted(sqle.getSQLState()) : ""; + return validationFailed(DbImportErrorCode.jdbc_connection_failed, + "Could not open JDBC connection to '%s' as '%s': %s%s." + .formatted(connector.getUrl(), connector.getUserName(), sqle.getMessage(), sqlState), + new DbImportValidation(true, true, true, true, false), + resolved); + } InstrumentedDbImportAction action = injector.getInstance(InstrumentedDbImportAction.class); DbImportSummary partial = action.buildSummary(0); String msg = e.getMessage() != null ? e.getMessage() : e.getClass().getName(); return new DbImportRunResult("error", partial, resolved, warnings, DbImportValidation.ALL_PASSED, new DbImportError(DbImportErrorCode.dbimport_runtime_error, msg)); } finally { - try { - conn.close(); - } catch (SQLException ignored) { - } + Thread.currentThread().setContextClassLoader(previousCl); } } @@ -337,6 +315,25 @@ public class DbImportRunTool { ); } + private static boolean hasCauseOfType(Throwable t, Class<? extends Throwable> type) { + for (Throwable c = t; c != null; c = c.getCause()) { + if (type.isInstance(c)) { + return true; + } + } + return false; + } + + @SuppressWarnings("unchecked") + private static <T extends Throwable> T causeOfType(Throwable t, Class<T> type) { + for (Throwable c = t; c != null; c = c.getCause()) { + if (type.isInstance(c)) { + return (T) c; + } + } + return null; + } + private record CachedClassLoader(List<String> entries, ClassLoader loader) { } }
