001: /*
002: * Copyright 2004-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.compass.gps.device.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.compass.gps.device.jdbc.CannotGetJdbcConnectionException;
027:
028: /**
029: * Implementation of SmartDataSource that wraps a single Connection which is not
030: * closed after use. Obviously, this is not multi-threading capable.
031: *
032: * <p>
033: * Note that at shutdown, someone should close the underlying connection via the
034: * <code>close()</code> method. Client code will never call close on the
035: * Connection handle if it is SmartDataSource-aware (e.g. uses
036: * <code>DataSourceUtils.releaseConnection</code>).
037: *
038: * <p>
039: * If client code will call <code>close()</code> in the assumption of a pooled
040: * Connection, like when using persistence tools, set "suppressClose" to "true".
041: * This will return a close-suppressing proxy instead of the physical
042: * Connection. Be aware that you will not be able to cast this to a native
043: * OracleConnection or the like anymore (you need to use a NativeJdbcExtractor
044: * for this then).
045: *
046: * <p>
047: * This is primarily intended for testing. For example, it enables easy testing
048: * outside an application server, for code that expects to work on a DataSource.
049: * In contrast to DriverManagerDataSource, it reuses the same Connection all the
050: * time, avoiding excessive creation of physical Connections.
051: *
052: * <p>
053: * Taken from Spring
054: *
055: * @author kimchy
056: *
057: * @see java.sql.Connection#close()
058: */
059: public class SingleConnectionDataSource extends DriverManagerDataSource {
060:
061: private boolean suppressClose;
062:
063: /** Wrapped connection */
064: private Connection target;
065:
066: /** Proxy connection */
067: private Connection connection;
068:
069: /**
070: * Constructor for bean-style configuration.
071: */
072: public SingleConnectionDataSource() {
073: }
074:
075: /**
076: * Create a new SingleConnectionDataSource with the given standard
077: * DriverManager parameters.
078: *
079: * @param driverClassName
080: * the JDBC driver class name
081: * @param url
082: * the JDBC URL to use for accessing the DriverManager
083: * @param username
084: * the JDBC username to use for accessing the DriverManager
085: * @param password
086: * the JDBC password to use for accessing the DriverManager
087: * @param suppressClose
088: * if the returned connection should be a close-suppressing proxy
089: * or the physical connection.
090: * @see java.sql.DriverManager#getConnection(String, String, String)
091: */
092: public SingleConnectionDataSource(String driverClassName,
093: String url, String username, String password,
094: boolean suppressClose)
095: throws CannotGetJdbcConnectionException {
096: super (driverClassName, url, username, password);
097: this .suppressClose = suppressClose;
098: }
099:
100: /**
101: * Create a new SingleConnectionDataSource with the given standard
102: * DriverManager parameters.
103: *
104: * @param url
105: * the JDBC URL to use for accessing the DriverManager
106: * @param username
107: * the JDBC username to use for accessing the DriverManager
108: * @param password
109: * the JDBC password to use for accessing the DriverManager
110: * @param suppressClose
111: * if the returned connection should be a close-suppressing proxy
112: * or the physical connection.
113: * @see java.sql.DriverManager#getConnection(String, String, String)
114: */
115: public SingleConnectionDataSource(String url, String username,
116: String password, boolean suppressClose)
117: throws CannotGetJdbcConnectionException {
118: super (url, username, password);
119: this .suppressClose = suppressClose;
120: }
121:
122: /**
123: * Create a new SingleConnectionDataSource with the given standard
124: * DriverManager parameters.
125: *
126: * @param url
127: * the JDBC URL to use for accessing the DriverManager
128: * @param suppressClose
129: * if the returned connection should be a close-suppressing proxy
130: * or the physical connection.
131: * @see java.sql.DriverManager#getConnection(String, String, String)
132: */
133: public SingleConnectionDataSource(String url, boolean suppressClose)
134: throws CannotGetJdbcConnectionException {
135: super (url);
136: this .suppressClose = suppressClose;
137: }
138:
139: /**
140: * Create a new SingleConnectionDataSource with a given connection.
141: *
142: * @param target
143: * underlying target connection
144: * @param suppressClose
145: * if the connection should be wrapped with a* connection that
146: * suppresses close() calls (to allow for normal close() usage in
147: * applications that expect a pooled connection but do not know
148: * our SmartDataSource interface).
149: */
150: public SingleConnectionDataSource(Connection target,
151: boolean suppressClose) {
152: if (target == null) {
153: throw new IllegalArgumentException(
154: "Connection is null in SingleConnectionDataSource");
155: }
156: this .suppressClose = suppressClose;
157: init(target);
158: }
159:
160: /**
161: * Set if the returned connection should be a close-suppressing proxy or the
162: * physical connection.
163: */
164: public void setSuppressClose(boolean suppressClose) {
165: this .suppressClose = suppressClose;
166: }
167:
168: /**
169: * Return if the returned connection will be a close-suppressing proxy or
170: * the physical connection.
171: */
172: public boolean isSuppressClose() {
173: return suppressClose;
174: }
175:
176: /**
177: * This is a single connection: Do not close it when returning to the
178: * "pool".
179: */
180: public boolean shouldClose(Connection con) {
181: return (con != this .connection && con != this .target);
182: }
183:
184: /**
185: * Initialize the underlying connection via DriverManager.
186: */
187: protected void init() throws SQLException {
188: init(getConnectionFromDriverManager());
189: }
190:
191: /**
192: * Initialize the underlying connection. Wraps the connection with a
193: * close-suppressing proxy if necessary.
194: *
195: * @param target
196: * the JDBC Connection to use
197: */
198: protected void init(Connection target) {
199: this .target = target;
200: this .connection = this .suppressClose ? getCloseSuppressingConnectionProxy(target)
201: : target;
202: }
203:
204: /**
205: * Close the underlying connection. The provider of this DataSource needs to
206: * care for proper shutdown.
207: * <p>
208: * As this bean implements DisposableBean, a bean factory will automatically
209: * invoke this on destruction of its cached singletons.
210: */
211: public void destroy() throws SQLException {
212: if (this .target != null) {
213: this .target.close();
214: }
215: }
216:
217: public Connection getConnection() throws SQLException {
218: synchronized (this ) {
219: if (this .connection == null) {
220: // no underlying connection -> lazy init via DriverManager
221: init();
222: }
223: }
224: if (this .connection.isClosed()) {
225: throw new SQLException(
226: "Connection was closed in SingleConnectionDataSource. Check that user code checks "
227: + "shouldClose() before closing connections, or set suppressClose to true");
228: }
229: if (log.isDebugEnabled()) {
230: log.debug("Returning single connection [" + this .connection
231: + "]");
232: }
233: return this .connection;
234: }
235:
236: /**
237: * Specifying a custom username and password doesn't make sense with a
238: * single connection. Returns the single connection if given the same
239: * username and password, though.
240: */
241: public Connection getConnection(String username, String password)
242: throws SQLException {
243: if (getUsername() != null && getUsername().equals(username)
244: && getPassword() != null
245: && getPassword().equals(password)) {
246: return getConnection();
247: } else {
248: throw new SQLException(
249: "SingleConnectionDataSource does not support custom username and password");
250: }
251: }
252:
253: /**
254: * Wrap the given Connection with a proxy that delegates every method call
255: * to it but suppresses close calls.
256: *
257: * @param target
258: * the original Connection to wrap
259: * @return the wrapped Connection
260: */
261: protected Connection getCloseSuppressingConnectionProxy(
262: Connection target) {
263: return (Connection) Proxy.newProxyInstance(
264: ConnectionProxy.class.getClassLoader(),
265: new Class[] { ConnectionProxy.class },
266: new CloseSuppressingInvocationHandler(target));
267: }
268:
269: /**
270: * Invocation handler that suppresses close calls on JDBC Connections.
271: */
272: private static class CloseSuppressingInvocationHandler implements
273: InvocationHandler {
274:
275: private static final String GET_TARGET_CONNECTION_METHOD_NAME = "getTargetConnection";
276:
277: private static final String CONNECTION_CLOSE_METHOD_NAME = "close";
278:
279: private final Connection target;
280:
281: public CloseSuppressingInvocationHandler(Connection target) {
282: this .target = target;
283: }
284:
285: public Object invoke(Object proxy, Method method, Object[] args)
286: throws Throwable {
287: // Invocation on ConnectionProxy interface coming in...
288:
289: // Handle getTargetConnection method: return underlying connection.
290: if (method.getName().equals(
291: GET_TARGET_CONNECTION_METHOD_NAME)) {
292: return this .target;
293: }
294:
295: // Handle close method: don't pass the call on.
296: if (method.getName().equals(CONNECTION_CLOSE_METHOD_NAME)) {
297: return null;
298: }
299:
300: // Invoke method on target connection.
301: try {
302: return method.invoke(this .target, args);
303: } catch (InvocationTargetException ex) {
304: throw ex.getTargetException();
305: }
306: }
307: }
308:
309: }
|