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 }