View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.orchestra.connectionManager;
20  
21  import javax.naming.Context;
22  import javax.naming.InitialContext;
23  import javax.naming.NamingException;
24  import javax.sql.DataSource;
25  import java.io.PrintWriter;
26  import java.sql.Connection;
27  import java.sql.SQLException;
28  import java.util.HashSet;
29  import java.util.Set;
30  
31  /***
32   * Manage all borrowed connections and hand out
33   * {@link org.apache.myfaces.orchestra.connectionManager.DisconnectableConnection}
34   * objects so that we can close them again after the HTTP request has been finished.
35   * <p>
36   * This datasource can be configured as a "wrapper" for a real datasource. When a connection is
37   * requested from this object, a proxy is returned that simply forwards all calls transparently
38   * to the real connection. This manager keeps track of all the Connections borrowed by each
39   * thread. At some point (eg from a servlet filter) this object can be asked to check for 
40   * unreturned connections held by the current thread. If any exist then the real connection
41   * is returned to the underlying datasource and the proxy's connection reference is set to null.
42   * This ensures that a thread cannot leak real database connections.
43   * <p>
44   * Of course all code should return its connections; this is only a workaround/hack useful when the
45   * real problem cannot be fixed. This is particularly useful for JPA implementations that do not free
46   * their connection again after a lazy-init.
47   * <p>
48   * If a proxy's underlying connection has been returned to the database (either via the
49   * leak-detection, or by explicitly calling close) then invoking any method on the proxy
50   * will transparently cause a new connection to be retrieved from the underlying datasource.
51   * This means that a Connection returned by this datasource works somewhat differently than
52   * a normal one: for a normal connection, close() followed by prepareStatement() would cause
53   * an exception to be thrown, but works when this datasource is used.
54   *
55   * @see org.apache.myfaces.orchestra.connectionManager.DisconnectableConnection
56   */
57  public class ConnectionManagerDataSource implements DataSource
58  {
59  	private DataSource dataSource;
60  	private String jndiName;
61  
62  	// List of connections that have been borrowed by this thread but not returned.
63  	// When using a threadpool, it is required that the releaseAllBorrowedConnections
64  	// method be called before the thread is returned to the pool; that ensures this
65  	// threadlocal is reset to null.
66  	private static ThreadLocal borrowedConnections = new ThreadLocal()
67  	{
68  		protected Object initialValue()
69  		{
70  			return new HashSet();
71  		}
72  	};
73  
74  	public ConnectionManagerDataSource()
75  	{
76  	}
77  
78  	void onAfterBorrowConnection(Connection con)
79  	{
80  		((Set) borrowedConnections.get()).add(con);
81  	}
82  
83  	void onAfterReleaseConnection(Connection con)
84  	{
85  		((Set) borrowedConnections.get()).remove(con);
86  	}
87  
88  	/***
89  	 * If the calling thread has allocated connections via this datasource, then return the
90  	 * underlying real connections to the underlying datasource.
91  	 * <p>
92  	 * To code that holds references to the proxy connection returned by this datasource,
93  	 * this operation is generally transparent. They continue to hold a reference to the
94  	 * proxy, and if a method is ever called on that proxy in the future then the proxy
95  	 * will transparently allocate a new underlying Connection at that time. 
96  	 * <p>
97  	 * This is expected to be called just before a thread is returned to a threadpool,
98  	 * eg via a ServletFilter just before returning from processing a request.
99  	 */
100 	public static void releaseAllBorrowedConnections()
101 	{
102 		DisconnectableConnection[] connections = new DisconnectableConnection[((Set) borrowedConnections.get()).size()];
103 		((Set) borrowedConnections.get()).toArray(connections);
104 
105 		for (int i = 0; i<connections.length; i++)
106 		{
107 			DisconnectableConnection connection = connections[i];
108 			connection.disconnect();
109 		}
110 
111 		((Set) borrowedConnections.get()).clear();
112 	}
113 
114 	/***
115 	 * Set the underlying datasource via an explicit call.
116 	 * See also method setJndiName.
117 	 */
118 	public void setDataSource(DataSource dataSource)
119 	{
120 		this.dataSource = dataSource;
121 	}
122 
123 	/***
124 	 * Return the underlying datasource for this wrapper.
125 	 * <p>
126 	 * If method setJndiName was used to specify the datasource, then it is retrieved
127 	 * from JNDI if necessary.
128 	 * 
129 	 * @throw IllegalArgumentException if neither setDataSource nor setJndiName was called. 
130 	 * @throw IllegalArgumentException if an invalid jndi name was specified.
131 	 */
132 	public DataSource getDataSource()
133 	{
134 		if (dataSource != null)
135 		{
136 			return dataSource;
137 		}
138 
139 		try
140 		{
141 			Context ctx = new InitialContext();
142 			dataSource = (DataSource) ctx.lookup(jndiName);
143 		}
144 		catch (NamingException e)
145 		{
146 			throw (IllegalArgumentException) new IllegalArgumentException(jndiName).initCause(e);
147 		}
148 
149 		return dataSource;
150 	}
151 
152 	/***
153 	 * Specify that the underlying datasource should be retrieved via JNDI.
154 	 */
155 	public void setJndiName(String jndiName)
156 	{
157 		this.jndiName = jndiName;
158 	}
159 
160 	/***
161 	 * Return a proxy that wraps a connection of the underlying datasource.
162 	 */
163 	public Connection getConnection() throws SQLException
164 	{
165 		return DisconnectableConnectionFactory.create(this);
166 	}
167 
168 	/***
169 	 * Not supported. Always throws UnsupportedOperationException.
170 	 */
171 	public Connection getConnection(String username, String password) throws SQLException
172 	{
173 		throw new UnsupportedOperationException();
174 	}
175 
176 	/*** @inherited */
177 	public PrintWriter getLogWriter() throws SQLException
178 	{
179 		return getDataSource().getLogWriter();
180 	}
181 
182 	/*** @inherited */
183 	public void setLogWriter(PrintWriter out) throws SQLException
184 	{
185 		getDataSource().setLogWriter(out);
186 	}
187 
188 	/*** @inherited */
189 	public void setLoginTimeout(int seconds) throws SQLException
190 	{
191 		getDataSource().setLoginTimeout(seconds);
192 	}
193 
194 	/*** @inherited */
195 	public int getLoginTimeout() throws SQLException
196 	{
197 		return getDataSource().getLoginTimeout();
198 	}
199 
200 	/***
201 	 * Always throws UnsupportedOperationException.
202 	 * <p>
203 	 * Note that this method was only introduced in java 1.6, and therefore
204 	 * cannot be implemented on platforms earlier than this without using
205 	 * reflection. Orchestra supports pre-1.6 JVMs, and this is a very
206 	 * rarely used method so currently no support is offered for this
207 	 * method.
208 	 */
209 	public Object unwrap(Class iface) throws SQLException
210 	{
211 		throw new UnsupportedOperationException();
212 		/*
213 		try
214 		{
215 			if (iface.isAssignableFrom(dataSource.getClass()))
216 			{
217 				return dataSource;
218 			}
219 
220 			Method method = dataSource.getClass().getMethod("unwrap", new Class[]{Class.class});
221 			return method.invoke(dataSource, new Object[] { iface });
222 		}
223 		catch (NoSuchMethodException e)
224 		{
225 			throw new UnsupportedOperationException();
226 		}
227 		catch (IllegalAccessException e)
228 		{
229 			throw new SQLException(e);
230 		}
231 		catch (InvocationTargetException e)
232 		{
233 			throw new SQLException(e);
234 		}
235 		*/
236 	}
237 
238 	/***
239 	 * Always throws UnsupportedOperationException.
240 	 * See method unwrap.
241 	 */
242 	public boolean isWrapperFor(Class iface) throws SQLException
243 	{
244 		throw new UnsupportedOperationException();
245 
246 		/*
247 		try
248 		{
249 			if (iface.isAssignableFrom(dataSource.getClass()))
250 			{
251 				return true;
252 			}
253 			Method method = dataSource.getClass().getMethod("isWrapperFor", new Class[]{Class.class});
254 			return Boolean.TRUE.equals(method.invoke(dataSource, new Object[] { iface }));
255 		}
256 		catch (NoSuchMethodException e)
257 		{
258 			throw new UnsupportedOperationException();
259 		}
260 		catch (IllegalAccessException e)
261 		{
262 			throw new SQLException(e);
263 		}
264 		catch (InvocationTargetException e)
265 		{
266 			throw new SQLException(e);
267 		}
268 		*/
269 	}
270 }