001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package org.apache.geronimo.connector.outbound.connectiontracking; 019 020 import java.lang.reflect.InvocationHandler; 021 import java.lang.reflect.InvocationTargetException; 022 import java.lang.reflect.Method; 023 import java.lang.reflect.Proxy; 024 import java.util.Collection; 025 import java.util.HashSet; 026 import java.util.Iterator; 027 import java.util.Map; 028 import java.util.Set; 029 import java.util.concurrent.ConcurrentHashMap; 030 import java.util.concurrent.ConcurrentMap; 031 032 import javax.resource.ResourceException; 033 import javax.resource.spi.DissociatableManagedConnection; 034 035 import org.apache.commons.logging.Log; 036 import org.apache.commons.logging.LogFactory; 037 import org.apache.geronimo.connector.outbound.ConnectionInfo; 038 import org.apache.geronimo.connector.outbound.ConnectionReturnAction; 039 import org.apache.geronimo.connector.outbound.ConnectionTrackingInterceptor; 040 import org.apache.geronimo.connector.outbound.ManagedConnectionInfo; 041 042 /** 043 * ConnectionTrackingCoordinator tracks connections that are in use by 044 * components such as EJB's. The component must notify the ccm 045 * when a method enters and exits. On entrance, the ccm will 046 * notify ConnectionManager stacks so the stack can make sure all 047 * connection handles left open from previous method calls are 048 * attached to ManagedConnections of the correct security context, and 049 * the ManagedConnections are enrolled in any current transaction. 050 * On exit, the ccm will notify ConnectionManager stacks of the handles 051 * left open, so they may be disassociated if appropriate. 052 * In addition, when a UserTransaction is started the ccm will notify 053 * ConnectionManager stacks so the existing ManagedConnections can be 054 * enrolled properly. 055 * 056 * @version $Rev: 585608 $ $Date: 2007-10-17 19:56:54 +0200 (Wed, 17 Oct 2007) $ 057 */ 058 public class ConnectionTrackingCoordinator implements TrackedConnectionAssociator, ConnectionTracker { 059 private static final Log log = LogFactory.getLog(ConnectionTrackingCoordinator.class.getName()); 060 061 private final boolean lazyConnect; 062 private final ThreadLocal<ConnectorInstanceContext> currentInstanceContexts = new ThreadLocal<ConnectorInstanceContext>(); 063 private final ConcurrentMap<ConnectionInfo,Object> proxiesByConnectionInfo = new ConcurrentHashMap<ConnectionInfo,Object>(); 064 065 public ConnectionTrackingCoordinator() { 066 this(false); 067 } 068 069 public ConnectionTrackingCoordinator(boolean lazyConnect) { 070 this.lazyConnect = lazyConnect; 071 } 072 073 public boolean isLazyConnect() { 074 return lazyConnect; 075 } 076 077 public ConnectorInstanceContext enter(ConnectorInstanceContext newContext) throws ResourceException { 078 ConnectorInstanceContext oldContext = currentInstanceContexts.get(); 079 currentInstanceContexts.set(newContext); 080 associateConnections(newContext); 081 return oldContext; 082 } 083 084 private void associateConnections(ConnectorInstanceContext context) throws ResourceException { 085 Map<ConnectionTrackingInterceptor, Set<ConnectionInfo>> connectionManagerToManagedConnectionInfoMap = context.getConnectionManagerMap(); 086 for (Map.Entry<ConnectionTrackingInterceptor, Set<ConnectionInfo>> entry : connectionManagerToManagedConnectionInfoMap.entrySet()) { 087 ConnectionTrackingInterceptor mcci = entry.getKey(); 088 Set<ConnectionInfo> connections = entry.getValue(); 089 mcci.enter(connections); 090 } 091 } 092 093 public void newTransaction() throws ResourceException { 094 ConnectorInstanceContext currentContext = currentInstanceContexts.get(); 095 if (currentContext == null) { 096 return; 097 } 098 associateConnections(currentContext); 099 } 100 101 public void exit(ConnectorInstanceContext oldContext) throws ResourceException { 102 ConnectorInstanceContext currentContext = currentInstanceContexts.get(); 103 try { 104 // for each connection type opened in this componet 105 Map<ConnectionTrackingInterceptor, Set<ConnectionInfo>> resources = currentContext.getConnectionManagerMap(); 106 for (Iterator<Map.Entry<ConnectionTrackingInterceptor, Set<ConnectionInfo>>> iterator = resources.entrySet().iterator(); iterator.hasNext();) { 107 Map.Entry<ConnectionTrackingInterceptor, Set<ConnectionInfo>> entry = iterator.next(); 108 ConnectionTrackingInterceptor mcci = entry.getKey(); 109 Set<ConnectionInfo> connections = entry.getValue(); 110 111 // release proxy connections 112 if (lazyConnect) { 113 for (ConnectionInfo connectionInfo : connections) { 114 releaseProxyConnection(connectionInfo); 115 } 116 } 117 118 // use connection interceptor to dissociate connections that support disassociation 119 mcci.exit(connections); 120 121 // if no connection remain clear context... we could support automatic commit, rollback or exception here 122 if (connections.isEmpty()) { 123 iterator.remove(); 124 } 125 } 126 } finally { 127 // when lazy we do not need or want to track open connections... they will automatically reconnect 128 if (lazyConnect) { 129 currentContext.getConnectionManagerMap().clear(); 130 } 131 currentInstanceContexts.set(oldContext); 132 } 133 } 134 135 /** 136 * A new connection (handle) has been obtained. If we are within a component context, store the connection handle 137 * so we can disassociate connections that support disassociation on exit. 138 * @param connectionTrackingInterceptor our interceptor in the connection manager which is used to disassociate the connections 139 * @param connectionInfo the connection that was obtained 140 * @param reassociate 141 */ 142 public void handleObtained(ConnectionTrackingInterceptor connectionTrackingInterceptor, 143 ConnectionInfo connectionInfo, 144 boolean reassociate) throws ResourceException { 145 146 ConnectorInstanceContext currentContext = currentInstanceContexts.get(); 147 if (currentContext == null) { 148 return; 149 } 150 151 Map<ConnectionTrackingInterceptor, Set<ConnectionInfo>> resources = currentContext.getConnectionManagerMap(); 152 Set<ConnectionInfo> infos = resources.get(connectionTrackingInterceptor); 153 if (infos == null) { 154 infos = new HashSet<ConnectionInfo>(); 155 resources.put(connectionTrackingInterceptor, infos); 156 } 157 158 infos.add(connectionInfo); 159 160 // if lazyConnect, we must proxy so we know when to connect the proxy 161 if (!reassociate && lazyConnect) { 162 proxyConnection(connectionTrackingInterceptor, connectionInfo); 163 } 164 } 165 166 /** 167 * A connection (handle) has been released or destroyed. If we are within a component context, remove the connection 168 * handle from the context. 169 * @param connectionTrackingInterceptor our interceptor in the connection manager 170 * @param connectionInfo the connection that was released 171 * @param connectionReturnAction 172 */ 173 public void handleReleased(ConnectionTrackingInterceptor connectionTrackingInterceptor, 174 ConnectionInfo connectionInfo, 175 ConnectionReturnAction connectionReturnAction) { 176 177 ConnectorInstanceContext currentContext = currentInstanceContexts.get(); 178 if (currentContext == null) { 179 return; 180 } 181 182 Map<ConnectionTrackingInterceptor, Set<ConnectionInfo>> resources = currentContext.getConnectionManagerMap(); 183 Set<ConnectionInfo> infos = resources.get(connectionTrackingInterceptor); 184 if (infos != null) { 185 if (connectionInfo.getConnectionHandle() == null) { 186 //destroy was called as a result of an error 187 ManagedConnectionInfo mci = connectionInfo.getManagedConnectionInfo(); 188 Collection<ConnectionInfo> toRemove = mci.getConnectionInfos(); 189 infos.removeAll(toRemove); 190 } else { 191 infos.remove(connectionInfo); 192 } 193 } else { 194 if ( log.isTraceEnabled()) { 195 log.trace("No infos found for handle " + connectionInfo.getConnectionHandle() + 196 " for MCI: " + connectionInfo.getManagedConnectionInfo() + 197 " for MC: " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + 198 " for CTI: " + connectionTrackingInterceptor, new Exception("Stack Trace")); 199 } 200 } 201 202 // NOTE: This method is also called by DissociatableManagedConnection when a connection has been 203 // dissociated in addition to the normal connection closed notification, but this is not a problem 204 // because DissociatableManagedConnection are not proied so this method will have no effect 205 closeProxyConnection(connectionInfo); 206 } 207 208 /** 209 * If we are within a component context, before a connection is obtained, set the connection unshareable and 210 * applicationManagedSecurity properties so the correct connection type is obtained. 211 * @param connectionInfo the connection to be obtained 212 * @param key the unique id of the connection manager 213 */ 214 public void setEnvironment(ConnectionInfo connectionInfo, String key) { 215 ConnectorInstanceContext currentContext = currentInstanceContexts.get(); 216 if (currentContext != null) { 217 // is this resource unshareable in this component context 218 Set<String> unshareableResources = currentContext.getUnshareableResources(); 219 boolean unshareable = unshareableResources.contains(key); 220 connectionInfo.setUnshareable(unshareable); 221 222 // does this resource use application managed security in this component context 223 Set<String> applicationManagedSecurityResources = currentContext.getApplicationManagedSecurityResources(); 224 boolean applicationManagedSecurity = applicationManagedSecurityResources.contains(key); 225 connectionInfo.setApplicationManagedSecurity(applicationManagedSecurity); 226 } 227 } 228 229 private void proxyConnection(ConnectionTrackingInterceptor connectionTrackingInterceptor, ConnectionInfo connectionInfo) throws ResourceException { 230 // if this connection already has a proxy no need to create another 231 if (connectionInfo.getConnectionProxy() != null) return; 232 233 // DissociatableManagedConnection do not need to be proxied 234 if (connectionInfo.getManagedConnectionInfo().getManagedConnection() instanceof DissociatableManagedConnection) { 235 return; 236 } 237 238 try { 239 Object handle = connectionInfo.getConnectionHandle(); 240 ConnectionInvocationHandler invocationHandler = new ConnectionInvocationHandler(connectionTrackingInterceptor, connectionInfo, handle); 241 Object proxy = Proxy.newProxyInstance(getClassLoader(handle), handle.getClass().getInterfaces(), invocationHandler); 242 243 // add it to our map... if the map already has a proxy for this connection, use the existing one 244 Object existingProxy = proxiesByConnectionInfo.putIfAbsent(connectionInfo, proxy); 245 if (existingProxy != null) proxy = existingProxy; 246 247 connectionInfo.setConnectionProxy(proxy); 248 } catch (Throwable e) { 249 throw new ResourceException("Unable to construct connection proxy", e); 250 } 251 } 252 253 private void releaseProxyConnection(ConnectionInfo connectionInfo) { 254 ConnectionInvocationHandler invocationHandler = getConnectionInvocationHandler(connectionInfo); 255 if (invocationHandler != null) { 256 invocationHandler.releaseHandle(); 257 } 258 } 259 260 private void closeProxyConnection(ConnectionInfo connectionInfo) { 261 ConnectionInvocationHandler invocationHandler = getConnectionInvocationHandler(connectionInfo); 262 if (invocationHandler != null) { 263 invocationHandler.close(); 264 proxiesByConnectionInfo.remove(connectionInfo); 265 connectionInfo.setConnectionProxy(null); 266 } 267 } 268 269 // Favor the thread context class loader for proxy construction 270 private ClassLoader getClassLoader(Object handle) { 271 ClassLoader threadClassLoader = Thread.currentThread().getContextClassLoader(); 272 if (threadClassLoader != null) { 273 return threadClassLoader; 274 } 275 return handle.getClass().getClassLoader(); 276 } 277 278 private ConnectionInvocationHandler getConnectionInvocationHandler(ConnectionInfo connectionInfo) { 279 Object proxy = connectionInfo.getConnectionProxy(); 280 if (proxy == null) { 281 proxy = proxiesByConnectionInfo.get(connectionInfo); 282 } 283 284 // no proxy or proxy already destroyed 285 if (proxy == null) return null; 286 287 if (Proxy.isProxyClass(proxy.getClass())) { 288 InvocationHandler invocationHandler = Proxy.getInvocationHandler(proxy); 289 if (invocationHandler instanceof ConnectionInvocationHandler) { 290 return (ConnectionInvocationHandler) invocationHandler; 291 } 292 } 293 return null; 294 } 295 296 public static class ConnectionInvocationHandler implements InvocationHandler { 297 private ConnectionTrackingInterceptor connectionTrackingInterceptor; 298 private ConnectionInfo connectionInfo; 299 private final Object handle; 300 private boolean released = false; 301 302 public ConnectionInvocationHandler(ConnectionTrackingInterceptor connectionTrackingInterceptor, ConnectionInfo connectionInfo, Object handle) { 303 this.connectionTrackingInterceptor = connectionTrackingInterceptor; 304 this.connectionInfo = connectionInfo; 305 this.handle = handle; 306 } 307 308 public Object invoke(Object object, Method method, Object[] args) throws Throwable { 309 Object handle; 310 if (method.getDeclaringClass() == Object.class) { 311 if (method.getName().equals("finalize")) { 312 // ignore the handle will get called if it implemented the method 313 return null; 314 } 315 if (method.getName().equals("clone")) { 316 throw new CloneNotSupportedException(); 317 } 318 // for equals, hashCode and toString don't activate handle 319 synchronized (this) { 320 handle = this.handle; 321 } 322 } else { 323 handle = getHandle(); 324 } 325 326 try { 327 Object value = method.invoke(handle, args); 328 return value; 329 } catch (InvocationTargetException ite) { 330 // catch InvocationTargetExceptions and turn them into the target exception (if there is one) 331 Throwable t = ite.getTargetException(); 332 if (t != null) { 333 throw t; 334 } 335 throw ite; 336 } 337 338 } 339 340 public synchronized boolean isReleased() { 341 return released; 342 } 343 344 public synchronized void releaseHandle() { 345 released = true; 346 } 347 348 public synchronized void close() { 349 connectionTrackingInterceptor = null; 350 connectionInfo = null; 351 released = true; 352 } 353 354 public synchronized Object getHandle() { 355 if (connectionTrackingInterceptor == null) { 356 // connection has been closed... send invocations directly to the handle 357 // which will throw an exception or in some clases like JDBC connection.close() 358 // ignore the invocation 359 return handle; 360 } 361 362 if (released) { 363 try { 364 connectionTrackingInterceptor.reassociateConnection(connectionInfo); 365 } catch (ResourceException e) { 366 throw (IllegalStateException) new IllegalStateException("Could not obtain a physical connection").initCause(e); 367 } 368 released = false; 369 } 370 return handle; 371 } 372 } 373 }