001: /*
002: * Copyright 2002-2006 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.InvocationHandler;
020: import java.lang.reflect.InvocationTargetException;
021: import java.lang.reflect.Method;
022: import java.lang.reflect.Proxy;
023: import java.sql.Connection;
024: import java.sql.SQLException;
025:
026: import org.springframework.beans.factory.DisposableBean;
027: import org.springframework.jdbc.CannotGetJdbcConnectionException;
028: import org.springframework.util.Assert;
029: import org.springframework.util.ObjectUtils;
030:
031: /**
032: * Implementation of SmartDataSource that wraps a single Connection which is not
033: * closed after use. Obviously, this is not multi-threading capable.
034: *
035: * <p>Note that at shutdown, someone should close the underlying Connection via the
036: * <code>close()</code> method. Client code will never call close on the Connection
037: * handle if it is SmartDataSource-aware (e.g. uses
038: * <code>DataSourceUtils.releaseConnection</code>).
039: *
040: * <p>If client code will call <code>close()</code> in the assumption of a pooled
041: * Connection, like when using persistence tools, set "suppressClose" to "true".
042: * This will return a close-suppressing proxy instead of the physical Connection.
043: * Be aware that you will not be able to cast this to a native OracleConnection
044: * or the like anymore (you need to use a NativeJdbcExtractor for this then).
045: *
046: * <p>This is primarily intended for testing. For example, it enables easy testing
047: * outside an application server, for code that expects to work on a DataSource.
048: * In contrast to DriverManagerDataSource, it reuses the same Connection all the
049: * time, avoiding excessive creation of physical Connections.
050: *
051: * @author Rod Johnson
052: * @author Juergen Hoeller
053: * @see #getConnection()
054: * @see java.sql.Connection#close()
055: * @see DataSourceUtils#releaseConnection
056: * @see org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor
057: */
058: public class SingleConnectionDataSource extends DriverManagerDataSource
059: implements SmartDataSource, DisposableBean {
060:
061: /** Create a close-suppressing proxy? */
062: private boolean suppressClose;
063:
064: /** Override AutoCommit? */
065: private Boolean autoCommit;
066:
067: /** Wrapped Connection */
068: private Connection target;
069:
070: /** Proxy Connection */
071: private Connection connection;
072:
073: /** Synchronization monitor for the shared Connection */
074: private final Object connectionMonitor = new Object();
075:
076: /**
077: * Constructor for bean-style configuration.
078: */
079: public SingleConnectionDataSource() {
080: }
081:
082: /**
083: * Create a new SingleConnectionDataSource with the given standard
084: * DriverManager parameters.
085: * @param driverClassName the JDBC driver class name
086: * @param url the JDBC URL to use for accessing the DriverManager
087: * @param username the JDBC username to use for accessing the DriverManager
088: * @param password the JDBC password to use for accessing the DriverManager
089: * @param suppressClose if the returned Connection should be a
090: * close-suppressing proxy or the physical Connection
091: * @see java.sql.DriverManager#getConnection(String, String, String)
092: */
093: public SingleConnectionDataSource(String driverClassName,
094: String url, String username, String password,
095: boolean suppressClose)
096: throws CannotGetJdbcConnectionException {
097:
098: super (driverClassName, url, username, password);
099: this .suppressClose = suppressClose;
100: }
101:
102: /**
103: * Create a new SingleConnectionDataSource with the given standard
104: * DriverManager parameters.
105: * @param url the JDBC URL to use for accessing the DriverManager
106: * @param username the JDBC username to use for accessing the DriverManager
107: * @param password the JDBC password to use for accessing the DriverManager
108: * @param suppressClose if the returned Connection should be a
109: * close-suppressing proxy or the physical Connection
110: * @see java.sql.DriverManager#getConnection(String, String, String)
111: */
112: public SingleConnectionDataSource(String url, String username,
113: String password, boolean suppressClose)
114: throws CannotGetJdbcConnectionException {
115:
116: super (url, username, password);
117: this .suppressClose = suppressClose;
118: }
119:
120: /**
121: * Create a new SingleConnectionDataSource with the given standard
122: * DriverManager parameters.
123: * @param url the JDBC URL to use for accessing the DriverManager
124: * @param suppressClose if the returned Connection should be a
125: * close-suppressing proxy or the physical Connection
126: * @see java.sql.DriverManager#getConnection(String, String, String)
127: */
128: public SingleConnectionDataSource(String url, boolean suppressClose)
129: throws CannotGetJdbcConnectionException {
130:
131: super (url);
132: this .suppressClose = suppressClose;
133: }
134:
135: /**
136: * Create a new SingleConnectionDataSource with a given Connection.
137: * @param target underlying target Connection
138: * @param suppressClose if the Connection should be wrapped with a Connection that
139: * suppresses <code>close()</code> calls (to allow for normal <code>close()</code>
140: * usage in applications that expect a pooled Connection but do not know our
141: * SmartDataSource interface)
142: */
143: public SingleConnectionDataSource(Connection target,
144: boolean suppressClose) {
145: Assert.notNull(target, "Connection must not be null");
146: this .target = target;
147: this .suppressClose = suppressClose;
148: this .connection = (suppressClose ? getCloseSuppressingConnectionProxy(target)
149: : target);
150: }
151:
152: /**
153: * Set whether the returned Connection should be a close-suppressing proxy
154: * or the physical Connection.
155: */
156: public void setSuppressClose(boolean suppressClose) {
157: this .suppressClose = suppressClose;
158: }
159:
160: /**
161: * Return whether the returned Connection will be a close-suppressing proxy
162: * or the physical Connection.
163: */
164: protected boolean isSuppressClose() {
165: return suppressClose;
166: }
167:
168: /**
169: * Set whether the returned Connection's "autoCommit" setting should be overridden.
170: */
171: public void setAutoCommit(boolean autoCommit) {
172: this .autoCommit = (autoCommit ? Boolean.TRUE : Boolean.FALSE);
173: }
174:
175: /**
176: * Return whether the returned Connection's "autoCommit" setting should be overridden.
177: * @return the "autoCommit" value, or <code>null</code> if none to be applied
178: */
179: protected Boolean getAutoCommitValue() {
180: return autoCommit;
181: }
182:
183: public Connection getConnection() throws SQLException {
184: synchronized (this .connectionMonitor) {
185: if (this .connection == null) {
186: // No underlying Connection -> lazy init via DriverManager.
187: initConnection();
188: }
189: if (this .connection.isClosed()) {
190: throw new SQLException(
191: "Connection was closed in SingleConnectionDataSource. Check that user code checks "
192: + "shouldClose() before closing Connections, or set 'suppressClose' to 'true'");
193: }
194: return this .connection;
195: }
196: }
197:
198: /**
199: * Specifying a custom username and password doesn't make sense
200: * with a single Connection. Returns the single Connection if given
201: * the same username and password; throws a SQLException else.
202: */
203: public Connection getConnection(String username, String password)
204: throws SQLException {
205: if (ObjectUtils.nullSafeEquals(username, getUsername())
206: && ObjectUtils.nullSafeEquals(password, getPassword())) {
207: return getConnection();
208: } else {
209: throw new SQLException(
210: "SingleConnectionDataSource does not support custom username and password");
211: }
212: }
213:
214: /**
215: * This is a single Connection: Do not close it when returning to the "pool".
216: */
217: public boolean shouldClose(Connection con) {
218: synchronized (this .connectionMonitor) {
219: return (con != this .connection && con != this .target);
220: }
221: }
222:
223: /**
224: * Close the underlying Connection.
225: * The provider of this DataSource needs to care for proper shutdown.
226: * <p>As this bean implements DisposableBean, a bean factory will
227: * automatically invoke this on destruction of its cached singletons.
228: */
229: public void destroy() {
230: synchronized (this .connectionMonitor) {
231: closeConnection();
232: }
233: }
234:
235: /**
236: * Initialize the underlying Connection via the DriverManager.
237: */
238: public void initConnection() throws SQLException {
239: if (getUrl() == null) {
240: throw new IllegalStateException(
241: "'url' property is required for lazily initializing a Connection");
242: }
243: synchronized (this .connectionMonitor) {
244: closeConnection();
245: this .target = getConnectionFromDriverManager();
246: prepareConnection(this .target);
247: if (logger.isInfoEnabled()) {
248: logger.info("Established shared JDBC Connection: "
249: + this .target);
250: }
251: this .connection = (isSuppressClose() ? getCloseSuppressingConnectionProxy(this .target)
252: : this .target);
253: }
254: }
255:
256: /**
257: * Reset the underlying shared Connection, to be reinitialized on next access.
258: */
259: public void resetConnection() {
260: synchronized (this .connectionMonitor) {
261: closeConnection();
262: this .target = null;
263: this .connection = null;
264: }
265: }
266:
267: /**
268: * Prepare the given Connection before it is exposed.
269: * <p>The default implementation applies the auto-commit flag, if necessary.
270: * Can be overridden in subclasses.
271: * @param con the Connection to prepare
272: * @see #setAutoCommit
273: */
274: protected void prepareConnection(Connection con)
275: throws SQLException {
276: Boolean autoCommit = getAutoCommitValue();
277: if (autoCommit != null
278: && con.getAutoCommit() != autoCommit.booleanValue()) {
279: con.setAutoCommit(autoCommit.booleanValue());
280: }
281: }
282:
283: /**
284: * Close the underlying shared Connection.
285: */
286: private void closeConnection() {
287: if (this .target != null) {
288: try {
289: this .target.close();
290: } catch (Throwable ex) {
291: logger.warn("Could not close shared JDBC Connection",
292: ex);
293: }
294: }
295: }
296:
297: /**
298: * Wrap the given Connection with a proxy that delegates every method call to it
299: * but suppresses close calls.
300: * @param target the original Connection to wrap
301: * @return the wrapped Connection
302: */
303: protected Connection getCloseSuppressingConnectionProxy(
304: Connection target) {
305: return (Connection) Proxy.newProxyInstance(
306: ConnectionProxy.class.getClassLoader(),
307: new Class[] { ConnectionProxy.class },
308: new CloseSuppressingInvocationHandler(target));
309: }
310:
311: /**
312: * Invocation handler that suppresses close calls on JDBC Connections.
313: */
314: private static class CloseSuppressingInvocationHandler implements
315: InvocationHandler {
316:
317: private final Connection target;
318:
319: public CloseSuppressingInvocationHandler(Connection target) {
320: this .target = target;
321: }
322:
323: public Object invoke(Object proxy, Method method, Object[] args)
324: throws Throwable {
325: // Invocation on ConnectionProxy interface coming in...
326:
327: if (method.getName().equals("getTargetConnection")) {
328: // Handle getTargetConnection method: return underlying Connection.
329: return this .target;
330: } else if (method.getName().equals("equals")) {
331: // Only consider equal when proxies are identical.
332: return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
333: } else if (method.getName().equals("hashCode")) {
334: // Use hashCode of Connection proxy.
335: return new Integer(hashCode());
336: } else if (method.getName().equals("close")) {
337: // Handle close method: don't pass the call on.
338: return null;
339: }
340:
341: // Invoke method on target Connection.
342: try {
343: return method.invoke(this .target, args);
344: } catch (InvocationTargetException ex) {
345: throw ex.getTargetException();
346: }
347: }
348: }
349:
350: }
|