oracle / graaljs

A ECMAScript 2024 compliant JavaScript implementation built on GraalVM. With polyglot language interoperability support. Running Node.js applications!
Universal Permissive License v1.0
1.78k stars 189 forks source link

Cannot use Oracle JDBC thin driver in JavaScript code. Error: java.sql.SQLException: No suitable driver found for #225

Open denis-komarov opened 4 years ago

denis-komarov commented 4 years ago

Java launcher for JavaScript (js.java):

import org.graalvm.polyglot.*;
public class js 
{
  public static void main(String[] args) 
  {
    try
    {
      (
        Context.newBuilder("js")
        .allowAllAccess(true)
        .build()
      )
      .eval
      (
        Source.newBuilder
        (
          "js", 
          new String(java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(args[0]))),
          args[0]
        )
        .build()
      );
    }
    catch (Exception e)
    {
      e.printStackTrace();
    };
  }
}

JavaScript code with JDBC call (test.js):

var Driver = Java.type("oracle.jdbc.OracleDriver");
var DriverManager = Java.type('java.sql.DriverManager');

print("Are there any registered drivers? " + DriverManager.getDrivers().hasMoreElements());

DriverManager.registerDriver(new Driver());

print("Are there any registered drivers? " + DriverManager.getDrivers().hasMoreElements());

try
{ 
  var c = DriverManager.getConnection("jdbc:oracle:thin:s/s@localhost:1521/xepdb1"); 
}
catch( e )
{ 
  print("Error: " + e);
  print("Message: " + e.message);
  print("Stack: " + e.stack);
}

run it (GraalVM Community Edition 19.3.0 based on JDK8 on Windows 10 x64) like this:

set JAVA_HOME=D:\d\run\graalvm
set CLASSPATH=.\;..\..\..\run\oracle\jdbc_thin_19.3\ojdbc8-full\ojdbc8.jar
D:\d\run\graalvm\bin\javac js.java
D:\d\run\graalvm\bin\java js test.js 

output:

Are there any registered drivers? false
Are there any registered drivers? false
Error: java.sql.SQLException: No suitable driver found for jdbc:oracle:thin:s/s@localhost:1521/xepdb1
Message: undefined
Stack: undefined

This is due to When the method getConnection is called, the DriverManager will attempt to locate a suitable driver from amongst those loaded at initialization and those loaded explicitly using the same classloader as the current applet or application. java.sql.DriverManager when executing getConnection tries to load the class of the JDBC driver by the class loader of the calling class something like this:

Class.forName
( 
  registeredDrivers.driver.getClass().getName(), 
  true, 
  Reflection.getCallerClass().getClassLoader()
)

If the loading fails, this driver is considered unavailable for use.

When JavaScript code is executed through context, the calling class for DriverManager is org.graalvm.polyglot.Context.

The org.graalvm.polyglot.Context class is loaded using the Bootstrap class loader (getClassLoader () == null) - this is the main problem!

The JDBC driver class is loaded using the Application class loader (getClassLoader () == sun.misc.Launcher$AppClassLoader).

Therefore, the JDBC driver class, in principle, cannot be loaded through the Bootstrap class loader, because Bootstrap is the parent of Application, which violates the Visibility principle.

So, due to the use of the Bootstrap class loader for org.graalvm.polyglot.Context, any JavaScript application executed through the context cannot create a JDBC connection.

denis-komarov commented 4 years ago

The problem remains relevant for the new version GraalVM 19.3.0 Community Edition based on JDK 8.

But in version GraalVM 19.3.0 Community Edition based on JDK 11 the problem is fixed, since the same class loader (jdk.internal.loader.ClassLoaders$AppClassLoader) is used for loading org.graalvm.polyglot.Context class as for JDBC driver class.