KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > jdbc > datasource > SingleConnectionDataSource


1 /*
2  * Copyright 2002-2006 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.jdbc.datasource;
18
19 import java.lang.reflect.InvocationHandler JavaDoc;
20 import java.lang.reflect.InvocationTargetException JavaDoc;
21 import java.lang.reflect.Method JavaDoc;
22 import java.lang.reflect.Proxy JavaDoc;
23 import java.sql.Connection JavaDoc;
24 import java.sql.SQLException JavaDoc;
25
26 import org.springframework.beans.factory.DisposableBean;
27 import org.springframework.jdbc.CannotGetJdbcConnectionException;
28 import org.springframework.util.Assert;
29 import org.springframework.util.ObjectUtils;
30
31 /**
32  * Implementation of SmartDataSource that wraps a single Connection which is not
33  * closed after use. Obviously, this is not multi-threading capable.
34  *
35  * <p>Note that at shutdown, someone should close the underlying Connection via the
36  * <code>close()</code> method. Client code will never call close on the Connection
37  * handle if it is SmartDataSource-aware (e.g. uses
38  * <code>DataSourceUtils.releaseConnection</code>).
39  *
40  * <p>If client code will call <code>close()</code> in the assumption of a pooled
41  * Connection, like when using persistence tools, set "suppressClose" to "true".
42  * This will return a close-suppressing proxy instead of the physical Connection.
43  * Be aware that you will not be able to cast this to a native OracleConnection
44  * or the like anymore (you need to use a NativeJdbcExtractor for this then).
45  *
46  * <p>This is primarily intended for testing. For example, it enables easy testing
47  * outside an application server, for code that expects to work on a DataSource.
48  * In contrast to DriverManagerDataSource, it reuses the same Connection all the
49  * time, avoiding excessive creation of physical Connections.
50  *
51  * @author Rod Johnson
52  * @author Juergen Hoeller
53  * @see #getConnection()
54  * @see java.sql.Connection#close()
55  * @see DataSourceUtils#releaseConnection
56  * @see org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor
57  */

58 public class SingleConnectionDataSource extends DriverManagerDataSource
59         implements SmartDataSource, DisposableBean {
60
61     /** Create a close-suppressing proxy? */
62     private boolean suppressClose;
63
64     /** Override AutoCommit? */
65     private Boolean JavaDoc autoCommit;
66
67     /** Wrapped Connection */
68     private Connection JavaDoc target;
69
70     /** Proxy Connection */
71     private Connection JavaDoc connection;
72
73     /** Synchronization monitor for the shared Connection */
74     private final Object JavaDoc connectionMonitor = new Object JavaDoc();
75
76
77     /**
78      * Constructor for bean-style configuration.
79      */

80     public SingleConnectionDataSource() {
81     }
82
83     /**
84      * Create a new SingleConnectionDataSource with the given standard
85      * DriverManager parameters.
86      * @param driverClassName the JDBC driver class name
87      * @param url the JDBC URL to use for accessing the DriverManager
88      * @param username the JDBC username to use for accessing the DriverManager
89      * @param password the JDBC password to use for accessing the DriverManager
90      * @param suppressClose if the returned Connection should be a
91      * close-suppressing proxy or the physical Connection
92      * @see java.sql.DriverManager#getConnection(String, String, String)
93      */

94     public SingleConnectionDataSource(
95             String JavaDoc driverClassName, String JavaDoc url, String JavaDoc username, String JavaDoc password, boolean suppressClose)
96             throws CannotGetJdbcConnectionException {
97
98         super(driverClassName, url, username, password);
99         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 JavaDoc url, String JavaDoc username, String JavaDoc password, boolean suppressClose)
113             throws CannotGetJdbcConnectionException {
114
115         super(url, username, password);
116         this.suppressClose = suppressClose;
117     }
118
119     /**
120      * Create a new SingleConnectionDataSource with the given standard
121      * DriverManager parameters.
122      * @param url the JDBC URL to use for accessing the DriverManager
123      * @param suppressClose if the returned Connection should be a
124      * close-suppressing proxy or the physical Connection
125      * @see java.sql.DriverManager#getConnection(String, String, String)
126      */

127     public SingleConnectionDataSource(String JavaDoc url, boolean suppressClose)
128             throws CannotGetJdbcConnectionException {
129
130         super(url);
131         this.suppressClose = suppressClose;
132     }
133
134     /**
135      * Create a new SingleConnectionDataSource with a given Connection.
136      * @param target underlying target Connection
137      * @param suppressClose if the Connection should be wrapped with a Connection that
138      * suppresses <code>close()</code> calls (to allow for normal <code>close()</code>
139      * usage in applications that expect a pooled Connection but do not know our
140      * SmartDataSource interface)
141      */

142     public SingleConnectionDataSource(Connection JavaDoc target, boolean suppressClose) {
143         Assert.notNull(target, "Connection must not be null");
144         this.target = target;
145         this.suppressClose = suppressClose;
146         this.connection = (suppressClose ? getCloseSuppressingConnectionProxy(target) : target);
147     }
148
149
150     /**
151      * Set whether the returned Connection should be a close-suppressing proxy
152      * or the physical Connection.
153      */

154     public void setSuppressClose(boolean suppressClose) {
155         this.suppressClose = suppressClose;
156     }
157
158     /**
159      * Return whether the returned Connection will be a close-suppressing proxy
160      * or the physical Connection.
161      */

162     protected boolean isSuppressClose() {
163         return suppressClose;
164     }
165
166     /**
167      * Set whether the returned Connection's "autoCommit" setting should be overridden.
168      */

169     public void setAutoCommit(boolean autoCommit) {
170         this.autoCommit = (autoCommit ? Boolean.TRUE : Boolean.FALSE);
171     }
172
173     /**
174      * Return whether the returned Connection's "autoCommit" setting should be overridden.
175      * @return the "autoCommit" value, or <code>null</code> if none to be applied
176      */

177     protected Boolean JavaDoc getAutoCommitValue() {
178         return autoCommit;
179     }
180
181
182     public Connection JavaDoc getConnection() throws SQLException JavaDoc {
183         synchronized (this.connectionMonitor) {
184             if (this.connection == null) {
185                 // No underlying Connection -> lazy init via DriverManager.
186
initConnection();
187             }
188             if (this.connection.isClosed()) {
189                 throw new SQLException JavaDoc(
190                         "Connection was closed in SingleConnectionDataSource. Check that user code checks " +
191                         "shouldClose() before closing Connections, or set 'suppressClose' to 'true'");
192             }
193             return this.connection;
194         }
195     }
196
197     /**
198      * Specifying a custom username and password doesn't make sense
199      * with a single Connection. Returns the single Connection if given
200      * the same username and password; throws a SQLException else.
201      */

202     public Connection JavaDoc getConnection(String JavaDoc username, String JavaDoc password) throws SQLException JavaDoc {
203         if (ObjectUtils.nullSafeEquals(username, getUsername()) &&
204                 ObjectUtils.nullSafeEquals(password, getPassword())) {
205             return getConnection();
206         }
207         else {
208             throw new SQLException JavaDoc("SingleConnectionDataSource does not support custom username and password");
209         }
210     }
211
212     /**
213      * This is a single Connection: Do not close it when returning to the "pool".
214      */

215     public boolean shouldClose(Connection JavaDoc con) {
216         synchronized (this.connectionMonitor) {
217             return (con != this.connection && con != this.target);
218         }
219     }
220
221     /**
222      * Close the underlying Connection.
223      * The provider of this DataSource needs to care for proper shutdown.
224      * <p>As this bean implements DisposableBean, a bean factory will
225      * automatically invoke this on destruction of its cached singletons.
226      */

227     public void destroy() {
228         synchronized (this.connectionMonitor) {
229             closeConnection();
230         }
231     }
232
233
234     /**
235      * Initialize the underlying Connection via the DriverManager.
236      */

237     public void initConnection() throws SQLException JavaDoc {
238         if (getUrl() == null) {
239             throw new IllegalStateException JavaDoc("'url' property is required for lazily initializing a Connection");
240         }
241         synchronized (this.connectionMonitor) {
242             closeConnection();
243             this.target = getConnectionFromDriverManager();
244             prepareConnection(this.target);
245             if (logger.isInfoEnabled()) {
246                 logger.info("Established shared JDBC Connection: " + this.target);
247             }
248             this.connection = (isSuppressClose() ? getCloseSuppressingConnectionProxy(this.target) : this.target);
249         }
250     }
251
252     /**
253      * Reset the underlying shared Connection, to be reinitialized on next access.
254      */

255     public void resetConnection() {
256         synchronized (this.connectionMonitor) {
257             closeConnection();
258             this.target = null;
259             this.connection = null;
260         }
261     }
262
263     /**
264      * Prepare the given Connection before it is exposed.
265      * <p>The default implementation applies the auto-commit flag, if necessary.
266      * Can be overridden in subclasses.
267      * @param con the Connection to prepare
268      * @see #setAutoCommit
269      */

270     protected void prepareConnection(Connection JavaDoc con) throws SQLException JavaDoc {
271         Boolean JavaDoc autoCommit = getAutoCommitValue();
272         if (autoCommit != null && con.getAutoCommit() != autoCommit.booleanValue()) {
273             con.setAutoCommit(autoCommit.booleanValue());
274         }
275     }
276
277     /**
278      * Close the underlying shared Connection.
279      */

280     private void closeConnection() {
281         if (this.target != null) {
282             try {
283                 this.target.close();
284             }
285             catch (Throwable JavaDoc ex) {
286                 logger.warn("Could not close shared JDBC Connection", ex);
287             }
288         }
289     }
290
291     /**
292      * Wrap the given Connection with a proxy that delegates every method call to it
293      * but suppresses close calls.
294      * @param target the original Connection to wrap
295      * @return the wrapped Connection
296      */

297     protected Connection JavaDoc getCloseSuppressingConnectionProxy(Connection JavaDoc target) {
298         return (Connection JavaDoc) Proxy.newProxyInstance(
299                 ConnectionProxy.class.getClassLoader(),
300                 new Class JavaDoc[] {ConnectionProxy.class},
301                 new CloseSuppressingInvocationHandler(target));
302     }
303
304
305     /**
306      * Invocation handler that suppresses close calls on JDBC Connections.
307      */

308     private static class CloseSuppressingInvocationHandler implements InvocationHandler JavaDoc {
309
310         private final Connection JavaDoc target;
311
312         public CloseSuppressingInvocationHandler(Connection JavaDoc target) {
313             this.target = target;
314         }
315
316         public Object JavaDoc invoke(Object JavaDoc proxy, Method JavaDoc method, Object JavaDoc[] args) throws Throwable JavaDoc {
317             // Invocation on ConnectionProxy interface coming in...
318

319             if (method.getName().equals("getTargetConnection")) {
320                 // Handle getTargetConnection method: return underlying Connection.
321
return this.target;
322             }
323             else if (method.getName().equals("equals")) {
324                 // Only consider equal when proxies are identical.
325
return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
326             }
327             else if (method.getName().equals("hashCode")) {
328                 // Use hashCode of Connection proxy.
329
return new Integer JavaDoc(hashCode());
330             }
331             else if (method.getName().equals("close")) {
332                 // Handle close method: don't pass the call on.
333
return null;
334             }
335
336             // Invoke method on target Connection.
337
try {
338                 return method.invoke(this.target, args);
339             }
340             catch (InvocationTargetException JavaDoc ex) {
341                 throw ex.getTargetException();
342             }
343         }
344     }
345
346 }
347
Popular Tags