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.InvocationTargetException; 020import java.lang.reflect.Method; 021import java.sql.Connection; 022import java.sql.SQLException; 023 024import javax.sql.DataSource; 025 026import org.apache.commons.logging.Log; 027import org.apache.commons.logging.LogFactory; 028 029import org.springframework.lang.Nullable; 030import org.springframework.util.Assert; 031import org.springframework.util.ReflectionUtils; 032import org.springframework.util.StringUtils; 033 034/** 035 * {@link DataSource} implementation that delegates all calls to a WebSphere 036 * target {@link DataSource}, typically obtained from JNDI, applying a current 037 * isolation level and/or current user credentials to every Connection obtained 038 * from it. 039 * 040 * <p>Uses IBM-specific API to get a JDBC Connection with a specific isolation 041 * level (and read-only flag) from a WebSphere DataSource 042 * (<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>). 043 * Supports the transaction-specific isolation level exposed by 044 * {@link org.springframework.transaction.support.TransactionSynchronizationManager#getCurrentTransactionIsolationLevel()}. 045 * It's also possible to specify a default isolation level, to be applied when the 046 * current Spring-managed transaction does not define a specific isolation level. 047 * 048 * <p>Usage example, defining the target DataSource as an inner-bean JNDI lookup 049 * (of course, you can link to any WebSphere DataSource through a bean reference): 050 * 051 * <pre class="code"> 052 * <bean id="myDataSource" class="org.springframework.jdbc.datasource.WebSphereDataSourceAdapter"> 053 * <property name="targetDataSource"> 054 * <bean class="org.springframework.jndi.JndiObjectFactoryBean"> 055 * <property name="jndiName" value="jdbc/myds"/> 056 * </bean> 057 * </property> 058 * </bean></pre> 059 * 060 * Thanks to Ricardo Olivieri for submitting the original implementation 061 * of this approach! 062 * 063 * @author Juergen Hoeller 064 * @author <a href="mailto:lari.hotari@sagire.fi">Lari Hotari</a> 065 * @author <a href="mailto:roliv@us.ibm.com">Ricardo N. Olivieri</a> 066 * @since 2.0.3 067 * @see com.ibm.websphere.rsadapter.JDBCConnectionSpec 068 * @see com.ibm.websphere.rsadapter.WSDataSource#getConnection(com.ibm.websphere.rsadapter.JDBCConnectionSpec) 069 * @see org.springframework.transaction.support.TransactionSynchronizationManager#getCurrentTransactionIsolationLevel() 070 * @see org.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly() 071 */ 072public class WebSphereDataSourceAdapter extends IsolationLevelDataSourceAdapter { 073 074 protected final Log logger = LogFactory.getLog(getClass()); 075 076 private Class<?> wsDataSourceClass; 077 078 private Method newJdbcConnSpecMethod; 079 080 private Method wsDataSourceGetConnectionMethod; 081 082 private Method setTransactionIsolationMethod; 083 084 private Method setReadOnlyMethod; 085 086 private Method setUserNameMethod; 087 088 private Method setPasswordMethod; 089 090 091 /** 092 * This constructor retrieves the WebSphere JDBC connection spec API, 093 * so we can get obtain specific WebSphere Connections using reflection. 094 */ 095 public WebSphereDataSourceAdapter() { 096 try { 097 this.wsDataSourceClass = getClass().getClassLoader().loadClass("com.ibm.websphere.rsadapter.WSDataSource"); 098 Class<?> jdbcConnSpecClass = getClass().getClassLoader().loadClass("com.ibm.websphere.rsadapter.JDBCConnectionSpec"); 099 Class<?> wsrraFactoryClass = getClass().getClassLoader().loadClass("com.ibm.websphere.rsadapter.WSRRAFactory"); 100 this.newJdbcConnSpecMethod = wsrraFactoryClass.getMethod("createJDBCConnectionSpec"); 101 this.wsDataSourceGetConnectionMethod = 102 this.wsDataSourceClass.getMethod("getConnection", jdbcConnSpecClass); 103 this.setTransactionIsolationMethod = 104 jdbcConnSpecClass.getMethod("setTransactionIsolation", int.class); 105 this.setReadOnlyMethod = jdbcConnSpecClass.getMethod("setReadOnly", Boolean.class); 106 this.setUserNameMethod = jdbcConnSpecClass.getMethod("setUserName", String.class); 107 this.setPasswordMethod = jdbcConnSpecClass.getMethod("setPassword", String.class); 108 } 109 catch (Exception ex) { 110 throw new IllegalStateException( 111 "Could not initialize WebSphereDataSourceAdapter because WebSphere API classes are not available: " + ex); 112 } 113 } 114 115 /** 116 * Checks that the specified 'targetDataSource' actually is 117 * a WebSphere WSDataSource. 118 */ 119 @Override 120 public void afterPropertiesSet() { 121 super.afterPropertiesSet(); 122 123 if (!this.wsDataSourceClass.isInstance(getTargetDataSource())) { 124 throw new IllegalStateException( 125 "Specified 'targetDataSource' is not a WebSphere WSDataSource: " + getTargetDataSource()); 126 } 127 } 128 129 130 /** 131 * Builds a WebSphere JDBCConnectionSpec object for the current settings 132 * and calls {@code WSDataSource.getConnection(JDBCConnectionSpec)}. 133 * @see #createConnectionSpec 134 * @see com.ibm.websphere.rsadapter.WSDataSource#getConnection(com.ibm.websphere.rsadapter.JDBCConnectionSpec) 135 */ 136 @Override 137 protected Connection doGetConnection(@Nullable String username, @Nullable String password) throws SQLException { 138 // Create JDBCConnectionSpec using current isolation level value and read-only flag. 139 Object connSpec = createConnectionSpec( 140 getCurrentIsolationLevel(), getCurrentReadOnlyFlag(), username, password); 141 if (logger.isDebugEnabled()) { 142 logger.debug("Obtaining JDBC Connection from WebSphere DataSource [" + 143 getTargetDataSource() + "], using ConnectionSpec [" + connSpec + "]"); 144 } 145 // Create Connection through invoking WSDataSource.getConnection(JDBCConnectionSpec) 146 Connection con = (Connection) invokeJdbcMethod( 147 this.wsDataSourceGetConnectionMethod, obtainTargetDataSource(), connSpec); 148 Assert.state(con != null, "No Connection"); 149 return con; 150 } 151 152 /** 153 * Create a WebSphere {@code JDBCConnectionSpec} object for the given characteristics. 154 * <p>The default implementation uses reflection to apply the given settings. 155 * Can be overridden in subclasses to customize the JDBCConnectionSpec object 156 * (<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>; 157 * <a href="https://www.ibm.com/developerworks/websphere/library/techarticles/0404_tang/0404_tang.html">IBM developerWorks article</a>). 158 * @param isolationLevel the isolation level to apply (or {@code null} if none) 159 * @param readOnlyFlag the read-only flag to apply (or {@code null} if none) 160 * @param username the username to apply ({@code null} or empty indicates the default) 161 * @param password the password to apply (may be {@code null} or empty) 162 * @throws SQLException if thrown by JDBCConnectionSpec API methods 163 * @see com.ibm.websphere.rsadapter.JDBCConnectionSpec 164 */ 165 protected Object createConnectionSpec(@Nullable Integer isolationLevel, @Nullable Boolean readOnlyFlag, 166 @Nullable String username, @Nullable String password) throws SQLException { 167 168 Object connSpec = invokeJdbcMethod(this.newJdbcConnSpecMethod, null); 169 Assert.state(connSpec != null, "No JDBCConnectionSpec"); 170 if (isolationLevel != null) { 171 invokeJdbcMethod(this.setTransactionIsolationMethod, connSpec, isolationLevel); 172 } 173 if (readOnlyFlag != null) { 174 invokeJdbcMethod(this.setReadOnlyMethod, connSpec, readOnlyFlag); 175 } 176 // If the username is empty, we'll simply let the target DataSource 177 // use its default credentials. 178 if (StringUtils.hasLength(username)) { 179 invokeJdbcMethod(this.setUserNameMethod, connSpec, username); 180 invokeJdbcMethod(this.setPasswordMethod, connSpec, password); 181 } 182 return connSpec; 183 } 184 185 186 @Nullable 187 private static Object invokeJdbcMethod(Method method, @Nullable Object target, @Nullable Object... args) 188 throws SQLException { 189 try { 190 return method.invoke(target, args); 191 } 192 catch (IllegalAccessException ex) { 193 ReflectionUtils.handleReflectionException(ex); 194 } 195 catch (InvocationTargetException ex) { 196 if (ex.getTargetException() instanceof SQLException) { 197 throw (SQLException) ex.getTargetException(); 198 } 199 ReflectionUtils.handleInvocationTargetException(ex); 200 } 201 throw new IllegalStateException("Should never get here"); 202 } 203 204}