package org.springframework.jdbc.datasource; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.jdbc.datasource.DelegatingDataSource; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.StringUtils; /** *
* {@link DataSource}implementation that delegates all calls to a WebSphere * JNDI {@link DataSource}. *
* ** Uses IBM extended API to get a database connection with a specific isolation * level from a WebSphere JNDI datasource ( IBM * code example ). Supports dynamic transaction specific isolation level * which is fetched with * {@link TransactionSynchronizationManager#getCurrentTransactionIsolationLevel()} * It's also possible to use a specific isolation level and ignore the * transaction setting. *
* * * Typical usage example: * ** * <bean id="myDataSource" class="org.springframework.jdbc.datasource.WebSphereDataSourceAdapter"> * <property name="targetDataSource"> * <bean class="org.springframework.jndi.JndiObjectFactoryBean"> * <property name="jndiName" value="jdbc/myds"/> * </bean> * </property> * <property name="useCurrentIsolationLevel" value="true"/> * <property name="defaultIsolationLevel"> * <bean * class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"> * <property name="staticField"> * <value>java.sql.Connection.TRANSACTION_READ_COMMITTED</value> * </property> * </bean> * </property> * </bean> * * ** *
* License for this file: Apache 2.0 *
* * @author Lari Hotari * @author Ricardo N. Olivieri * */ public class WebSphereDataSourceAdapter extends DelegatingDataSource implements InitializingBean { private static final Log logger = LogFactory .getLog(WebSphereDataSourceAdapter.class); /** * The default isolation level (is used if current isolation level is null * or disabled with useCurrentIsolationLevel=false). */ protected int defaultIsolationLevel = Connection.TRANSACTION_READ_COMMITTED; /** * If this is false, the defaultIsolationLevel will be always used * regardless of the transaction's current isolation level. */ protected boolean useCurrentIsolationLevel = true; private String username; private String password; private Class wsDataSourceClazz; private Class jdbcConnSpecClazz; private Method newJdbcConnSpecMethod; private Method wsDataSourceGetConnectionMethod; private Method setTransIsolationLevelMethod; private Method setReadOnlyMethod; private Method setUserMethod; private Method setPassMethod; /** * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() */ public void afterPropertiesSet() { super.afterPropertiesSet(); // Resolve classes and methods with reflection and cache them try { wsDataSourceClazz = Class .forName("com.ibm.websphere.rsadapter.WSDataSource"); jdbcConnSpecClazz = Class .forName("com.ibm.websphere.rsadapter.JDBCConnectionSpec"); Class wsrraFactoryClazz = Class .forName("com.ibm.websphere.rsadapter.WSRRAFactory"); newJdbcConnSpecMethod = wsrraFactoryClazz.getMethod( "createJDBCConnectionSpec", null); wsDataSourceGetConnectionMethod = wsDataSourceClazz.getMethod( "getConnection", new Class[] { jdbcConnSpecClazz }); setTransIsolationLevelMethod = jdbcConnSpecClazz.getMethod( "setTransactionIsolation", new Class[] { int.class }); setReadOnlyMethod = jdbcConnSpecClazz.getMethod("setReadOnly", new Class[] { Boolean.class }); setUserMethod = jdbcConnSpecClazz.getMethod("setUserName", new Class[] { String.class }); setPassMethod = jdbcConnSpecClazz.getMethod("setPassword", new Class[] { String.class }); } catch (Exception e) { logger.error("Could not resolve WebSphere data source classes.", e); throw new IllegalStateException( "This adapter can be only used in WebSphere Application Server. " + e.getMessage()); } // This test should occcur after initializing wsDataSourceClazz if (!wsDataSourceClazz.isInstance(this.getTargetDataSource())) { throw new IllegalStateException( "This data source adapter must delegate to a WSDataSource instance."); } } /** * @see org.springframework.jdbc.datasource.DelegatingDataSource#getConnection() */ public Connection getConnection() throws SQLException { return this.getConnection(username, password); } /** * @see org.springframework.jdbc.datasource.DelegatingDataSource#getConnection(java.lang.String, * java.lang.String) */ public Connection getConnection(String username, String password) throws SQLException { Integer isolationLevel = null; if (useCurrentIsolationLevel) { // Since 2.0.1 in Spring Framework isolationLevel = TransactionSynchronizationManager .getCurrentTransactionIsolationLevel(); } if (isolationLevel == null) { // Use default if none is found from // TransactionSynchronizationManager or // useCurrentIsolationLevel is false isolationLevel = new Integer(defaultIsolationLevel); } if (logger.isDebugEnabled()) { logger.debug("Creating connection. isolation level = " + isolationLevel); } try { // Create JDBCConnectionSpec using isolationLevel value and // current readOnly flag Object connSpec = createJDBCConnectionSpec(isolationLevel .intValue(), TransactionSynchronizationManager .isCurrentTransactionReadOnly(), username, password); // Create Connection by invoking // WSDataSource.getConnection(JDBCConnectionSpec) using // reflection Connection connection = (Connection) wsDataSourceGetConnectionMethod .invoke(this.getTargetDataSource(), new Object[] { connSpec }); if (logger.isDebugEnabled()) { logger.debug("Connection created with isolation level = " + connection.getTransactionIsolation()); } return connection; } catch (IllegalArgumentException e) { // Not probable logger.error("IllegalArgumentException", e); throw new DataAccessResourceFailureException( "IllegalArgumentException when getting connection", e); } catch (IllegalAccessException e) { // Not probable logger.error("IllegalAccessException", e); throw new DataAccessResourceFailureException( "IllegalAccessException when getting connection", e); } catch (InvocationTargetException e) { logger.error("InvocationTargetException", e); throw new DataAccessResourceFailureException( "InvocationTargetException when getting connection", e .getTargetException()); } } /** * Create a WebSphere JDBCConnectionSpec (jca cci connection spec?) object * with reflection you can make a subclass and overload this method if you * want to set other options on the connection spec JDBCConnectionSpec * Javadoc developwerWorks * article . */ protected Object createJDBCConnectionSpec(int isolationLevel, boolean readOnly, String user, String pass) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { Object connSpec = newJdbcConnSpecMethod.invoke(null, null); setTransIsolationLevelMethod.invoke(connSpec, new Object[] { new Integer(isolationLevel) }); setReadOnlyMethod.invoke(connSpec, new Object[] { new Boolean(readOnly) }); // If the "user" is empty, this proxy will simply delegate to the // standard getConnection() method of the target DataSource if (StringUtils.hasLength(user)) { setUserMethod.invoke(connSpec, new Object[] { user }); setPassMethod.invoke(connSpec, new Object[] { pass }); } return connSpec; } /** * The default isolation level (is used if current isolation level is null * or disabled with useCurrentIsolationLevel=false). * * @param defaultIsolationLevel */ public void setDefaultIsolationLevel(int defaultIsolationLevel) { this.defaultIsolationLevel = defaultIsolationLevel; } /** * If this is false, the defaultIsolationLevel will be always used * regardless of the transaction's current isolation level. * * @param useCurrentIsolationLevel */ public void setUseCurrentIsolationLevel(boolean useCurrentIsolationLevel) { this.useCurrentIsolationLevel = useCurrentIsolationLevel; } /** * Username and password are used only when the application resource * reference is using res-auth = Application. If the username is "empty", * then this class will simply delegate to the standard getConnection() * method (similar to the UserCredentialsDataSourceAdapter class). * * @param password */ public void setPassword(String password) { this.password = password; } /** * Username and password are used only when the application resource * reference is using res-auth = Application. If the username is "empty", * then this class will simply delegate to the standard getConnection() * method (similar to the UserCredentialsDataSourceAdapter class). * * @param username */ public void setUsername(String username) { this.username = username; } }