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