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 * @throws IllegalArgumentException if neither setDataSource nor setJndiName was called.
130 * @throws 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 /** @inheritDoc */
177 public PrintWriter getLogWriter() throws SQLException
178 {
179 return getDataSource().getLogWriter();
180 }
181
182 /** @inheritDoc */
183 public void setLogWriter(PrintWriter out) throws SQLException
184 {
185 getDataSource().setLogWriter(out);
186 }
187
188 /** @inheritDoc */
189 public void setLoginTimeout(int seconds) throws SQLException
190 {
191 getDataSource().setLoginTimeout(seconds);
192 }
193
194 /** @inheritDoc */
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 }