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