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   *
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 }