1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
63
64
65
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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
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
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269 }
270 }