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 *
48 * @see org.apache.myfaces.orchestra.connectionManager.DisconnectableConnection
49 */
50 public class ConnectionManagerDataSource implements DataSource
51 {
52 private DataSource dataSource;
53 private String jndiName;
54 private ConnectionManagerListener[] listeners;
55
56 // List of connections that have been borrowed by this thread but not returned.
57 // When using a threadpool, it is required that the releaseAllBorrowedConnections
58 // method be called before the thread is returned to the pool; that ensures this
59 // threadlocal is reset to null.
60 private static ThreadLocal borrowedConnections = new ThreadLocal()
61 {
62 protected Object initialValue()
63 {
64 return new HashSet();
65 }
66 };
67
68 public ConnectionManagerDataSource()
69 {
70 }
71
72 void onAfterBorrowConnection(Connection con)
73 {
74 ((Set) borrowedConnections.get()).add(con);
75
76 if (listeners != null)
77 {
78 for (int i = 0; i<listeners.length; i++)
79 {
80 listeners[i].borrowConnection(con);
81 }
82 }
83 }
84
85 public void onBeforeReleaseConnection(Connection con)
86 {
87 if (listeners != null)
88 {
89 for (int i = 0; i<listeners.length; i++)
90 {
91 listeners[i].releaseConnection(con);
92 }
93 }
94 }
95
96 void onAfterReleaseConnection(Connection con)
97 {
98 ((Set) borrowedConnections.get()).remove(con);
99 }
100
101 public static void releaseAllBorrowedConnections()
102 {
103 DisconnectableConnection[] connections = new DisconnectableConnection[((Set) borrowedConnections.get()).size()];
104 ((Set) borrowedConnections.get()).toArray(connections);
105
106 for (int i = 0; i<connections.length; i++)
107 {
108 DisconnectableConnection connection = connections[i];
109 connection.disconnect();
110 }
111
112 ((Set) borrowedConnections.get()).clear();
113 }
114
115 public void setListeners(ConnectionManagerListener[] listeners)
116 {
117 this.listeners = listeners;
118 }
119
120 public void setDataSource(DataSource dataSource)
121 {
122 this.dataSource = dataSource;
123 }
124
125 public DataSource getDataSource()
126 {
127 if (dataSource != null)
128 {
129 return dataSource;
130 }
131
132 try
133 {
134 Context ctx = new InitialContext();
135 dataSource = (DataSource) ctx.lookup(jndiName);
136 }
137 catch (NamingException e)
138 {
139 throw (IllegalArgumentException) new IllegalArgumentException(jndiName).initCause(e);
140 }
141
142 return dataSource;
143 }
144
145 public void setJndiName(String jndiName)
146 {
147 this.jndiName = jndiName;
148 }
149
150 public Connection getConnection() throws SQLException
151 {
152 return DisconnectableConnectionFactory.create(this);
153 }
154
155 public Connection getConnection(String username, String password) throws SQLException
156 {
157 throw new UnsupportedOperationException();
158 }
159
160 public PrintWriter getLogWriter() throws SQLException
161 {
162 return getDataSource().getLogWriter();
163 }
164
165 public void setLogWriter(PrintWriter out) throws SQLException
166 {
167 getDataSource().setLogWriter(out);
168 }
169
170 public void setLoginTimeout(int seconds) throws SQLException
171 {
172 getDataSource().setLoginTimeout(seconds);
173 }
174
175 public int getLoginTimeout() throws SQLException
176 {
177 return getDataSource().getLoginTimeout();
178 }
179
180 public Object unwrap(Class iface) throws SQLException
181 {
182 throw new UnsupportedOperationException();
183 /*
184 try
185 {
186 if (iface.isAssignableFrom(dataSource.getClass()))
187 {
188 return dataSource;
189 }
190
191 Method method = dataSource.getClass().getMethod("unwrap", new Class[]{Class.class});
192 return method.invoke(dataSource, new Object[] { iface });
193 }
194 catch (NoSuchMethodException e)
195 {
196 throw new UnsupportedOperationException();
197 }
198 catch (IllegalAccessException e)
199 {
200 throw new SQLException(e);
201 }
202 catch (InvocationTargetException e)
203 {
204 throw new SQLException(e);
205 }
206 */
207 }
208
209 public boolean isWrapperFor(Class iface) throws SQLException
210 {
211 throw new UnsupportedOperationException();
212
213 /*
214 try
215 {
216 if (iface.isAssignableFrom(dataSource.getClass()))
217 {
218 return true;
219 }
220 Method method = dataSource.getClass().getMethod("isWrapperFor", new Class[]{Class.class});
221 return Boolean.TRUE.equals(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 }