segunda-feira, 17 de julho de 2006

JBoss WrappedConnection

No JBoss, quando obtemos uma conexão de banco de dados de um Datasource, obtemos um objeto da classe org.jboss.resource.adapter.jdbc.WrappedConnection, que é uma classe que implementa a interface java.sql.Connection.

No entanto, ao utilizar algumas operações específicas da implementação do JDBC da Oracle, me deparei com a seguinte exceção:

java.lang.ClassCastException: org.jboss.resource.adapter.jdbc.WrappedConnection

Pelo que pude entender isto ocorre porque estas operações específicas esperam um objeto conexão do tipo oracle.jdbc.driver.OracleConnection. No entanto, a conexão devolvida pelo Datasource é deste tipo, uma vez que estou usando um banco de dados Oracle (pois é leitor, não iria tentar usar o JDBC da Oracle com outro banco). O que o JBoss faz é encapsular este objeto dentro do objeto WrappedConnection. Desta forma, é possível obter o objeto OracleConnection e utilizar as funcionalidades desejadas do JDBC da Oracle. Para tanto, escrevi o seguinte código:

public class ConnectionUtilities {
   
    /**
     *
     */
    private static final String WRAPPED_CONNECTION_NAME =
        "org.jboss.resource.adapter.jdbc.WrappedConnection";

    /**
     *
     */
    private static final String GET_UNDERLYING_CONNECTION_METHOD =
        "getUnderlyingConnection";

    /**
     * Se a aplicação estiver rodando dentro do JBoss retorna a conexão encapsulada dentro
     * da conexão obtida pelo JBoss. Caso contrário, retorna a própria conexão dada
     *
     * @param conn A conexão com o banco de dados
     * 
     * @return
     */
    public static Connection getUnderlyingConnection(Connection conn) {
        // Variaveis auxiliares
        Connection underlyingConn = conn;
        ClassLoader cl = null;
        Class wrappedConnectionClass = null;
        Method getUnderlyingConnectionMethod = null;
       
        try {
 
            cl = Thread.currentThread().getContextClassLoader();
            wrappedConnectionClass =  cl.loadClass( WRAPPED_CONNECTION_NAME );
            getUnderlyingConnectionMethod = wrappedConnectionClass.getMethod(
                    GET_UNDERLYING_CONNECTION_METHOD, (Class[]) null );
           
            if( wrappedConnectionClass.isAssignableFrom( conn.getClass() ) ) {
                underlyingConn =
                    (Connection) getUnderlyingConnectionMethod.invoke( conn,
                            (Object[]) null );
            }
        } catch (Exception e) {           
        }
       
        return underlyingConn;
    }

}

Note-se que se este código for utilizado fora do ambiente JBoss ele retorna a própria conexão dada como parâmetro. Desta forma consigo utilizar o mesmo código no ambiente de testes que configurei no Eclipse, e no ambiente de produção com o JBoss.

Um comentário:

Bart disse...

Eu conversei agora pouco com voce sobre esse codigo e fiquei de escrever minhas observacoes aqui. Entao aih vai.

Eu achei desnecessario todo esse codigo pra fazer tao pouco. O seguinte codigo faria a mesma coisa (nao testei o codigo, escrevi direto aqui).

public class ConnectionUtilities {
public static Connection getUnderlyingConnection(Connection conn) {
if (conn instanceof WrappedConnection) {
return ((WrappedConnection) conn).getUnderlyingConnection();
}
return conn;
}

Esse codigo teria as seguintes vantagens:
1) eh mais eficiente, pois tudo pode ser verificado estaticamente (ok, tem o cast, mas eh bem mais eficiente do que usar reflexao)
2) eh mais facil de entender, qualquer programador entende o que estah acontecendo. Pede pra um estagiario entender o seu codigo ;-)
3) nao gera checked excecoes (sem contar que no seu codigo voce nao trata a excecao que voce gerou)
4) nao eh bem uma vantagem, mas o seu codigo usa muitas variaveis locais. Isso eh ruim, pois dificulta entendimento e refactoring automatico.

Agora, se eh pra fazer um negocio mais independente, e pra brincar com reflexao, voce poderia fazer o utilities configuravel, algo como:

public class ConnectionUtilities {
Map<Class, String> map; // inicia isso aqui de algum jeito
public static Connection getUnderlyingConnection(Connection conn) {
String methodName = map.get(conn.getClass());
if (methodName == null) return conn;
try {
return (Connection) conn.getMethod(methodName, null).invoke(conn, null);
} ... // trata excecoes direito aqui
}

Abracos,
Bart