001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.jdbc.datasource; 018 019import java.lang.reflect.Method; 020import java.sql.Connection; 021import java.sql.SQLException; 022import javax.sql.DataSource; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026 027import org.springframework.util.ReflectionUtils; 028import org.springframework.util.StringUtils; 029 030/** 031 * {@link DataSource} implementation that delegates all calls to a WebSphere 032 * target {@link DataSource}, typically obtained from JNDI, applying a current 033 * isolation level and/or current user credentials to every Connection obtained 034 * from it. 035 * 036 * <p>Uses IBM-specific API to get a JDBC Connection with a specific isolation 037 * level (and read-only flag) from a WebSphere DataSource 038 * (<a href="https://publib.boulder.ibm.com/infocenter/wasinfo/v5r1//topic/com.ibm.websphere.base.doc/info/aes/ae/rdat_extiapi.html">IBM code example</a>). 039 * Supports the transaction-specific isolation level exposed by 040 * {@link org.springframework.transaction.support.TransactionSynchronizationManager#getCurrentTransactionIsolationLevel()}. 041 * It's also possible to specify a default isolation level, to be applied when the 042 * current Spring-managed transaction does not define a specific isolation level. 043 * 044 * <p>Usage example, defining the target DataSource as an inner-bean JNDI lookup 045 * (of course, you can link to any WebSphere DataSource through a bean reference): 046 * 047 * <pre class="code"> 048 * <bean id="myDataSource" class="org.springframework.jdbc.datasource.WebSphereDataSourceAdapter"> 049 * <property name="targetDataSource"> 050 * <bean class="org.springframework.jndi.JndiObjectFactoryBean"> 051 * <property name="jndiName" value="jdbc/myds"/> 052 * </bean> 053 * </property> 054 * </bean></pre> 055 * 056 * Thanks to Ricardo Olivieri for submitting the original implementation 057 * of this approach! 058 * 059 * @author Juergen Hoeller 060 * @author <a href="mailto:lari.hotari@sagire.fi">Lari Hotari</a> 061 * @author <a href="mailto:roliv@us.ibm.com">Ricardo N. Olivieri</a> 062 * @since 2.0.3 063 * @see com.ibm.websphere.rsadapter.JDBCConnectionSpec 064 * @see com.ibm.websphere.rsadapter.WSDataSource#getConnection(com.ibm.websphere.rsadapter.JDBCConnectionSpec) 065 * @see org.springframework.transaction.support.TransactionSynchronizationManager#getCurrentTransactionIsolationLevel() 066 * @see org.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly() 067 */ 068public class WebSphereDataSourceAdapter extends IsolationLevelDataSourceAdapter { 069 070 protected final Log logger = LogFactory.getLog(getClass()); 071 072 private Class<?> wsDataSourceClass; 073 074 private Method newJdbcConnSpecMethod; 075 076 private Method wsDataSourceGetConnectionMethod; 077 078 private Method setTransactionIsolationMethod; 079 080 private Method setReadOnlyMethod; 081 082 private Method setUserNameMethod; 083 084 private Method setPasswordMethod; 085 086 087 /** 088 * This constructor retrieves the WebSphere JDBC connection spec API, 089 * so we can get obtain specific WebSphere Connections using reflection. 090 */ 091 public WebSphereDataSourceAdapter() { 092 try { 093 this.wsDataSourceClass = getClass().getClassLoader().loadClass("com.ibm.websphere.rsadapter.WSDataSource"); 094 Class<?> jdbcConnSpecClass = getClass().getClassLoader().loadClass("com.ibm.websphere.rsadapter.JDBCConnectionSpec"); 095 Class<?> wsrraFactoryClass = getClass().getClassLoader().loadClass("com.ibm.websphere.rsadapter.WSRRAFactory"); 096 this.newJdbcConnSpecMethod = wsrraFactoryClass.getMethod("createJDBCConnectionSpec"); 097 this.wsDataSourceGetConnectionMethod = 098 this.wsDataSourceClass.getMethod("getConnection", jdbcConnSpecClass); 099 this.setTransactionIsolationMethod = 100 jdbcConnSpecClass.getMethod("setTransactionIsolation", int.class); 101 this.setReadOnlyMethod = jdbcConnSpecClass.getMethod("setReadOnly", Boolean.class); 102 this.setUserNameMethod = jdbcConnSpecClass.getMethod("setUserName", String.class); 103 this.setPasswordMethod = jdbcConnSpecClass.getMethod("setPassword", String.class); 104 } 105 catch (Exception ex) { 106 throw new IllegalStateException( 107 "Could not initialize WebSphereDataSourceAdapter because WebSphere API classes are not available: " + ex); 108 } 109 } 110 111 /** 112 * Checks that the specified 'targetDataSource' actually is 113 * a WebSphere WSDataSource. 114 */ 115 @Override 116 public void afterPropertiesSet() { 117 super.afterPropertiesSet(); 118 119 if (!this.wsDataSourceClass.isInstance(getTargetDataSource())) { 120 throw new IllegalStateException( 121 "Specified 'targetDataSource' is not a WebSphere WSDataSource: " + getTargetDataSource()); 122 } 123 } 124 125 126 /** 127 * Builds a WebSphere JDBCConnectionSpec object for the current settings 128 * and calls {@code WSDataSource.getConnection(JDBCConnectionSpec)}. 129 * @see #createConnectionSpec 130 * @see com.ibm.websphere.rsadapter.WSDataSource#getConnection(com.ibm.websphere.rsadapter.JDBCConnectionSpec) 131 */ 132 @Override 133 protected Connection doGetConnection(String username, String password) throws SQLException { 134 // Create JDBCConnectionSpec using current isolation level value and read-only flag. 135 Object connSpec = createConnectionSpec( 136 getCurrentIsolationLevel(), getCurrentReadOnlyFlag(), username, password); 137 if (logger.isDebugEnabled()) { 138 logger.debug("Obtaining JDBC Connection from WebSphere DataSource [" + 139 getTargetDataSource() + "], using ConnectionSpec [" + connSpec + "]"); 140 } 141 // Create Connection through invoking WSDataSource.getConnection(JDBCConnectionSpec) 142 return (Connection) ReflectionUtils.invokeJdbcMethod( 143 this.wsDataSourceGetConnectionMethod, getTargetDataSource(), connSpec); 144 } 145 146 /** 147 * Create a WebSphere {@code JDBCConnectionSpec} object for the given characteristics. 148 * <p>The default implementation uses reflection to apply the given settings. 149 * Can be overridden in subclasses to customize the JDBCConnectionSpec object 150 * (<a href="https://publib.boulder.ibm.com/infocenter/wasinfo/v6r0/topic/com.ibm.websphere.javadoc.doc/public_html/api/com/ibm/websphere/rsadapter/JDBCConnectionSpec.html">JDBCConnectionSpec javadoc</a>; 151 * <a href="https://www.ibm.com/developerworks/websphere/library/techarticles/0404_tang/0404_tang.html">IBM developerWorks article</a>). 152 * @param isolationLevel the isolation level to apply (or {@code null} if none) 153 * @param readOnlyFlag the read-only flag to apply (or {@code null} if none) 154 * @param username the username to apply ({@code null} or empty indicates the default) 155 * @param password the password to apply (may be {@code null} or empty) 156 * @throws SQLException if thrown by JDBCConnectionSpec API methods 157 * @see com.ibm.websphere.rsadapter.JDBCConnectionSpec 158 */ 159 protected Object createConnectionSpec( 160 Integer isolationLevel, Boolean readOnlyFlag, String username, String password) throws SQLException { 161 162 Object connSpec = ReflectionUtils.invokeJdbcMethod(this.newJdbcConnSpecMethod, null); 163 if (isolationLevel != null) { 164 ReflectionUtils.invokeJdbcMethod(this.setTransactionIsolationMethod, connSpec, isolationLevel); 165 } 166 if (readOnlyFlag != null) { 167 ReflectionUtils.invokeJdbcMethod(this.setReadOnlyMethod, connSpec, readOnlyFlag); 168 } 169 // If the username is empty, we'll simply let the target DataSource 170 // use its default credentials. 171 if (StringUtils.hasLength(username)) { 172 ReflectionUtils.invokeJdbcMethod(this.setUserNameMethod, connSpec, username); 173 ReflectionUtils.invokeJdbcMethod(this.setPasswordMethod, connSpec, password); 174 } 175 return connSpec; 176 } 177 178}