IBM / JTOpen

IBM Toolbox for Java, an IBM i communications library
https://ibm.github.io/JTOpen/
Other
56 stars 26 forks source link

How to unWrap a Connection to AS400JDBCConnection #177

Open YuLimin opened 2 months ago

YuLimin commented 2 months ago

How to unWrap a Connection to AS400JDBCConnection

com.ibm.as400.access.AS400JDBCConnection class is a abstract class Not an Interface

AS400JDBCConnection not like oracle.jdbc.OracleConnection which is an Interface https://www.ibm.com/support/pages/comibmwsrsadapterjdbcwsjdbcconnection-incompatible-oraclejdbcoracleconnection

Here are customer requirement and run on Tomcat smoothly :

Connection connection = as400DataSource.getConnection();
DatabaseMetaData metaDataConnection = connection.getMetaData();
if (as400DataSource.isWrapperFor(com.ibm.as400.access.AS400JDBCDataSource.class)) {
 AS400JDBCConnection as400JDBCConnection = (com.ibm.as400.access.AS400JDBCConnection) metaDataConnection.getConnection();
AS400 as400System = as400JDBCConnection.getSystem();
}

Connection connection = as400DataSource.getConnection();
DelegatingConnection delegatingConnection = new DelegatingConnection(connection);
AS400JDBCConnection as400JDBCConnection = (AS400JDBCConnection) delegatingConnection.getInnermostDelegate();
AS400 as400System = as400JDBCConnection.getSystem();

How to get AS400JDBCConnection from Liberty datasource? 1.server.xml

    <!-- DB2 on iSeries (Toolbox) -->
    <library id="400JDBCLib">
        <fileset dir="C:\OpenSource\JTOpen" includes="jt400_11.2.jar" />
    </library>
    <dataSource id="AS400" jndiName="jdbc/AS400" enableConnectionCasting="true">
        <jdbcDriver libraryRef="400JDBCLib" />
        <properties.db2.i.toolbox serverName="9.200.104.193" user="yulimin" password="yulimin" databaseName="test"/>
    </dataSource>

2.Java Code

Context ctx = new InitialContext();
        DataSource ds = (DataSource) ctx.lookup("jdbc/AS400");
        Connection connection = ds.getConnection();

        // [com.ibm.ws](http://com.ibm.ws/).rsadapter.jdbc.WSJdbcConnection
        System.out.println("Connection : " + connection);
        // [com.ibm.ws](http://com.ibm.ws/).rsadapter.jdbc.v43.WSJdbc43Connection
        System.out.println("getClass : " + connection.getClass());
  1. get AS400JDBCConnection directly but I can get via JDBC directly as below:

        DriverManager.registerDriver(new com.ibm.as400.access.AS400JDBCDriver());
        String dbUrl = "jdbc:as400://<IP>/yulimin";
        Connection connection = DriverManager.getConnection(dbUrl, "yulimin", "yulimin");
        System.out.println("Connection : " + connection);
        System.out.println("getClass : " + connection.getClass());
    
        //
        try
        {
            if (connection instanceof com.ibm.as400.access.AS400JDBCConnection) 
                System.out.println("AS400 Connection is running under the IBM Toolbox for Java JDBC driver.");
            else
                System.out.println("There is something wrong with AS400 Connection.");
        }
        catch(Exception ex)
        {
            ex.printStackTrace();
        }
        AS400JDBCConnection as400JDBCConn = (AS400JDBCConnection) connection;
        System.out.println("AS400JDBCConnectionImpl getClass : " + as400JDBCConn.getClass());

I use the same code and driver version, I have try call DB on AS400 via Tomat 9.0.60 and Liberty 24.0.0.4, then I found on Tomcat will get: DataSource is org.apache.tomcat.dbcp.dbcp2.BasicDataSource Connection is org.apache.tomcat.dbcp.dbcp2.PoolingDataSource$PoolGuardConnectionWrapper

But on Liberty DataSource : com.ibm.ws.rsadapter.jdbc.WSJdbcDataSource Connection : com.ibm.ws.rsadapter.jdbc.WSJdbcConnection

The driver is JTOpen v20.0.7, jt400-20.0.7-java8.jar download from https://github.com/IBM/JTOpen/releases

jeber-ibm commented 2 months ago

Why does the connection need to be unwrapped to something other than a java.sql.Connection?

Would the following code work?

Connection connection = as400DataSource.getConnection(); DatabaseMetaData metaDataConnection = connection.getMetaData(); Connection connection 2 = metaDataConnection.getConnection(); if (connection2 instanceof com.ibm.as400.access.AS400JDBCConnection) { AS400JDBCConnection as400JDBCConnection = (com.ibm.as400.access.AS400JDBCConnection) connection2; AS400 as400System = as400JDBCConnection.getSystem(); }

YuLimin commented 2 months ago

Why does the connection need to be unwrapped to something other than a java.sql.Connection?

Would the following code work?

Connection connection = as400DataSource.getConnection(); DatabaseMetaData metaDataConnection = connection.getMetaData(); Connection connection 2 = metaDataConnection.getConnection(); if (connection2 instanceof com.ibm.as400.access.AS400JDBCConnection) { AS400JDBCConnection as400JDBCConnection = (com.ibm.as400.access.AS400JDBCConnection) connection2; AS400 as400System = as400JDBCConnection.getSystem(); }

Yes, These code work on Tomcat smoothly.

jeber-ibm commented 2 months ago

I'm still not sure what change needs to be made. I don't want to change the class hierarchy structure if a sufficient workaround is available.

YuLimin commented 2 months ago

I'm still not sure what change needs to be made. I don't want to change the class hierarchy structure if a sufficient workaround is available.

I don't find any sufficient workaround until now.

jeber-ibm commented 2 months ago

So, what is the code that isn't working? Why doesn't the following code work?

Connection connection = as400DataSource.getConnection(); DatabaseMetaData metaDataConnection = connection.getMetaData(); Connection connection 2 = metaDataConnection.getConnection(); if (connection2 instanceof com.ibm.as400.access.AS400JDBCConnection) { AS400JDBCConnection as400JDBCConnection = (com.ibm.as400.access.AS400JDBCConnection) connection2; AS400 as400System = as400JDBCConnection.getSystem(); }

YuLimin commented 2 months ago

Here are Exception from Liberty 24.0.0.4

        <feature>jndi-1.0</feature>
    <feature>jdbc-4.3</feature>

J2CA8050I: An authentication alias should be used instead of defining a user name and password on dataSource[AS400]. CWRLS0010I: Performing recovery processing for local WebSphere server (AS400). CWRLS0012I: All persistent services have been directed to perform recovery processing for this WebSphere server (AS400). WTRN0135I: Transaction service recovering no transactions. YuLimin : DataSource : com.ibm.ws.rsadapter.jdbc.v43.WSJdbc43DataSource@847e2893

DSRA8203I: Database product name : DB2 UDB for AS/400 DSRA8204I: Database product version : 07.04.0000 V7R4m0 DSRA8205I: JDBC driver name : AS/400 Toolbox for Java JDBC Driver DSRA8206I: JDBC driver version : 13.3 Connection : com.ibm.ws.rsadapter.jdbc.v43.WSJdbc43Connection@4ae92124 Connection getClass : class com.ibm.ws.rsadapter.jdbc.v43.WSJdbc43Connection FFDC1015I: An FFDC Incident has been created: "java.sql.SQLException: DSRA9124E: Cannot unwrap object to com.ibm.as400.access.AS400JDBCConnection because com.ibm.as400.access.AS400JDBCConnection is not an interface class. com.ibm.ws.rsadapter.jdbc.v43.WSJdbc43Connection.unwrap 441" at ffdc_24.05.18_12.41.47.0.log java.sql.SQLException: DSRA9124E: Cannot unwrap object to com.ibm.as400.access.AS400JDBCConnection because com.ibm.as400.access.AS400JDBCConnection is not an interface class. at com.ibm.ws.rsadapter.jdbc.WSJdbcWrapper.unwrap(WSJdbcWrapper.java:472) at AS400JDBCDirect.getAS400JNDI(AS400JDBCDirect.java:66) at AS400JndiServlet.doGet(AS400JndiServlet.java:38) at javax.servlet.http.HttpServlet.service(HttpServlet.java:687)

jeber-ibm commented 2 months ago

What error do you see if you use this code?

Connection connection = as400DataSource.getConnection(); DatabaseMetaData metaDataConnection = connection.getMetaData(); Connection connection 2 = metaDataConnection.getConnection(); if (connection2 instanceof com.ibm.as400.access.AS400JDBCConnection) { AS400JDBCConnection as400JDBCConnection = (com.ibm.as400.access.AS400JDBCConnection) connection2; AS400 as400System = as400JDBCConnection.getSystem(); }

What does the AS400JDBCDirect.java code look like?

YuLimin commented 2 months ago

I add some log to these code as below

        DataSource ds = (DataSource) ctx.lookup("jdbc/AS400");
        System.out.println("YuLimin : DataSource : " + ds);

        Connection connection1 = ds.getConnection();
        DatabaseMetaData metaDataConnection1 = connection1.getMetaData();
        System.out.println("YuLimin : metaDataConnection1 : " + metaDataConnection1);
        Connection connection2 = metaDataConnection1.getConnection();
        **System.out.println("YuLimin : Connection : " + connection2);
        if (connection2 instanceof com.ibm.as400.access.AS400JDBCConnection)** {
            AS400JDBCConnection as400JDBCConnection = (com.ibm.as400.access.AS400JDBCConnection) connection2;
            AS400 as400System = as400JDBCConnection.getSystem();
            System.out.println("YuLimin : as400System : " + as400System);
        }
        else
        {
            System.out.println("YuLimin : Connection Not instanceof com.ibm.as400.access.AS400JDBCConnection");
        }

will get the result as below

YuLimin : DataSource : com.ibm.ws.rsadapter.jdbc.v43.WSJdbc43DataSource@d425249e YuLimin : metaDataConnection1 : com.ibm.ws.rsadapter.jdbc.v43.WSJdbc43DatabaseMet

YuLimin : Connection : com.ibm.ws.rsadapter.jdbc.v43.WSJdbc43Connection@550c23e8 YuLimin : Connection Not instanceof com.ibm.as400.access.AS400JDBCConnection

jeber-ibm commented 2 months ago

What kind of object do you get if you unwrap the com.ibm.ws.rsadapter.jdbc.v43.WSJdbc43Connection to a java.sql.Connection?

YuLimin commented 2 months ago

Can not unwrap to AS400JDBCConnection, only java.sql.Connection can get.

jeber-ibm commented 2 months ago

But what object implements the java.sql.Connection that is returned?

jeber-ibm commented 2 months ago

There is nothing but the stated class restriction (I'm not sure why it's there in the first place) that prevents unwrapping to a class. A connection object can unwrap itself as seen in the following example.

$ java -jar jt400.jar jdbc:as400:localhost

!setvar CLASS = java.lang.Class.forName('com.ibm.as400.access.AS400JDBCConnection') CLASS=class com.ibm.as400.access.AS400JDBCConnection

!setvar C2=CON.unwrap(CLASS) C2=UT24P87

!callmethod C2.getAS400() Call returned com.ibm.as400.access.AS400ImplRemote@32ac87bf

Perhaps the com.ibm.ws.rsadapter.jdbc.v43.WSJdbc43Connection.unwrap method at com.ibm.ws.rsadapter.jdbc.WSJdbcWrapper.unwrap(WSJdbcWrapper.java:472) shouldn't verify that the class isn't an interface, but try calling the unwrap method on the underlying class.

If not, which methods need to be accessed. We can probably define an interface for just those method.

YuLimin commented 1 month ago

There is nothing but the stated class restriction (I'm not sure why it's there in the first place) that prevents unwrapping to a class. A connection object can unwrap itself as seen in the following example.

$ java -jar jt400.jar jdbc:as400:localhost

!setvar CLASS = java.lang.Class.forName('com.ibm.as400.access.AS400JDBCConnection') CLASS=class com.ibm.as400.access.AS400JDBCConnection

!setvar C2=CON.unwrap(CLASS) C2=UT24P87

!callmethod C2.getAS400() Call returned com.ibm.as400.access.AS400ImplRemote@32ac87bf

Perhaps the com.ibm.ws.rsadapter.jdbc.v43.WSJdbc43Connection.unwrap method at com.ibm.ws.rsadapter.jdbc.WSJdbcWrapper.unwrap(WSJdbcWrapper.java:472) shouldn't verify that the class isn't an interface, but try calling the unwrap method on the underlying class.

If not, which methods need to be accessed. We can probably define an interface for just those method.

Yes, as code sample in my question, I can unwrap Connection to AS400JDBCConnection via JDBC directly and on Tomcat.

but on Liberty I can't unwrap from WSJdbc43Connection, Maybe Liberty connection pool implementation WSJdbcWrapper do NOT implement for wrap AS400JDBCConnection to WSJdbc43Connection. then we can't unwrap from WSJdbc43Connection to AS400JDBCConnection even AS400JDBCConnection is an interface or NOT.

jeber-ibm commented 1 month ago

As I said before..

Perhaps the com.ibm.ws.rsadapter.jdbc.v43.WSJdbc43Connection.unwrap method at com.ibm.ws.rsadapter.jdbc.WSJdbcWrapper.unwrap(WSJdbcWrapper.java:472) shouldn't verify that the class isn't an interface, but try calling the unwrap method on the underlying class.

You should work with the provider of the "com.ibm.ws.rsadapter.jdbc.WSJdbcWrapper" class to see if they can change to not throw an exception when the wrapped target isn't an interface.

java.sql.SQLException: DSRA9124E: Cannot unwrap object to com.ibm.as400.access.AS400JDBCConnection because com.ibm.as400.access.AS400JDBCConnection is not an interface class. at com.ibm.ws.rsadapter.jdbc.WSJdbcWrapper.unwrap(WSJdbcWrapper.java:472) at AS400JDBCDirect.getAS400JNDI(AS400JDBCDirect.java:66) at AS400JndiServlet.doGet(AS400JndiServlet.java:38) at javax.servlet.http.HttpServlet.service(HttpServlet.java:687)

Otherwise, what methods of AS400JDBCConnection need to be called. We can probably create an interface for just those methods.