2007-03-28

P6Spy + Data source + WebSphere

Kilka dni temu musiałem zabawić się w detektywa i przeprowadzić małe śledztwo. Podejrzanym miał być proces (ukrywający się w serwerze aplikacyjnym WebSphere), a celem znalezienie listy jego kontaktów - zapytań sql wykonywanych na bazie danych db2.

Proces - session bean, używał do komunikacji z bazą danych pewnej liczby entity bean-ów (CMP/CMR), które z kolei wykorzystywały zdefiniowane w serwerze aplikacji źródło danych.

Jak zwykle w takich sytuacjach pomocny okazał się TW - google.pl i już po "kilku sekundach" ;) wiedziałem, że powinienem użyć P6Spy ("kilka sekund", bo sprawdzałem i odrzucałem inne alternatywne narzędzia: Craftsman, Elvyx, JDBMonitor i kilka innych - powody były różne: niezerowa cena, brak wsparcia dla WebSphere-a, lub nie obsługiwanie źródeł danych).

Mimo tego, że P6Spy nie jest już od kilku lat rozwijany, wydawał się idealny. Przechwytuje on (działa jako proxy) komunikację JDBC (wykonywaną przez właściwy sterownik) z bazą danych oraz pozwala na logowanie tej komunikacji wykorzystując np. plik csv albo log4j-a. Sam P6Spy jest widziany przez aplikację jako zwykły sterownik JDBC.

Zmiana konfiguracji WebSphere-a była banalna i wymagała:

  • zmiany nazwy istniejącego źródła danych (zapamiętania jej)
  • zdefiniowania nowego dostawcy JDBC wykorzystującego com.p6spy.engine.spy.P6ConnectionPoolDataSource
  • stworzenia nowego źródła danych (o nazwie zapamiętanej dwa kroki wcześniej)
  • wprowadzenia w pliku konfiguracyjnym spy.properties - parametrów "podglądanego" źródła danych (własności - realdatasource, realdatasourceclass, realdatasourceproperties)
  • umieszczeniu tego pliku w miejscu w którym znajdzie go serwer aplikacji (np. WebSphere/AppServer/properties)
Uradowany uruchamiłem aplikację i .... niespodzianka, dziesięcio-ekranowy stack trace tylko śmignął mi przed oczami. Nie będę przytaczał tu całości, ale jego najbardziej interesująca część wglądała tak:
createManagedConnctionWithMCWrapper caught an exception during creation of the ManagedConnection for resource jdbc/mydb, throwing ResourceAllocationException. Original exception: com.ibm.ws.exception.WsException: DSRA8101E: DataSource class cannot be used as one-phase: ClassCastException: com.ibm.ws.rsadapter.jdbc.WSJdbcDataSource

W pierwszej chwili podejrzewałem błąd w konfiguracji, ale chwilę później znałem już prawdziwą przyczynę problemu.

Wyjątek wstępował w metodzie
getPooledConnection, implementacji ConnectionPoolDataSource dostarczanej przez P6Spy. Metoda ta wygląda tak:

public PooledConnection getPooledConnection() throws SQLException {
if (rds == null) {
bindDataSource();
}

PooledConnection pc = ((ConnectionPoolDataSource) rds).getPooledConnection();
P6PooledConnection pooledConnection = new P6PooledConnection(pc);
return pooledConnection;
}

Problemem było rzutowanie "podglądanego" źródła danych na
interfejs ConnectionPoolDataSource. Jak się okazało klasa WSJdbcDataSource tego interfejsu nie implementuje. Pomocny okazał się artykuł opisujący metody wywoływania "niestandardowych" metod JDBC na obiektach (wrapperach) dostarczanych przez WebSphere.
Na jego podstawie powstała działająca na serwerze WebSphere implementacja
ConnectionPoolDataSource zawierająca mechanizm logowanie komunikacji JDBC, wykorzystująca klasę pomocniczą WSCallHelper.

package com.p6spy.engine.spy.websphere;

import java.sql.*;
import javax.sql.*;
import com.p6spy.engine.spy.*;
import com.ibm.websphere.rsadapter.WSCallHelper;

public class WSP6ConnectionPoolDataSource extends P6DataSource implements ConnectionPoolDataSource {

public WSP6ConnectionPoolDataSource() {
super();
}

public WSP6ConnectionPoolDataSource(DataSource ds) {
super(ds);
}

public PooledConnection getPooledConnection() throws SQLException {
if (rds == null) {
bindDataSource();
}

PooledConnection pc = (PooledConnection)WSCallHelper.jdbcCall(
javax.sql.ConnectionPoolDataSource.class, rds, "getPooledConnection", new Object[]{},
new Class[]{});
P6PooledConnection pooledConnection =
new P6PooledConnection(pc);
return pooledConnection;
}

public PooledConnection getPooledConnection(String s, String s1) throws SQLException {
if (rds == null) {
bindDataSource();
}

PooledConnection pc = (PooledConnection)WSCallHelper.jdbcCall(
javax.sql.ConnectionPoolDataSource.class, rds, "getPooledConnection",
new Object[]{s,s1},
new Class[]{String.class,String.class});
P6PooledConnection pooledConnection = new P6PooledConnection(pc);
return pooledConnection;
}
}

Po dorzuceniu nowej klasy do p6spy.jar-a, zmianie
com.p6spy.engine.spy.P6ConnectionPoolDataSource na com.p6spy.engine.spy.websphere.WSP6ConnectionPoolDataSource w konfiguracji dostawcy JDBC i uruchomieniu aplikacji czekała na mnie już tylko miła niespodzianka w postaci pliku zawierającego wszystkie poszukiwane zapytania sql.

0 komentarze: