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: 550546 $ $Date: 2007-06-25 12:52:11 -0400 (Mon, 25 Jun 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 currentInstanceContexts = new ThreadLocal();
063 private final ConcurrentMap proxiesByConnectionInfo = new ConcurrentHashMap();
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 = (ConnectorInstanceContext) currentInstanceContexts.get();
079 currentInstanceContexts.set(newContext);
080 associateConnections(newContext);
081 return oldContext;
082 }
083
084 private void associateConnections(ConnectorInstanceContext context) throws ResourceException {
085 Map connectionManagerToManagedConnectionInfoMap = context.getConnectionManagerMap();
086 for (Iterator i = connectionManagerToManagedConnectionInfoMap.entrySet().iterator(); i.hasNext();) {
087 Map.Entry entry = (Map.Entry) i.next();
088 ConnectionTrackingInterceptor mcci =
089 (ConnectionTrackingInterceptor) entry.getKey();
090 Set connections = (Set) entry.getValue();
091 mcci.enter(connections);
092 }
093 }
094
095 public void newTransaction() throws ResourceException {
096 ConnectorInstanceContext currentContext = (ConnectorInstanceContext) currentInstanceContexts.get();
097 if (currentContext == null) {
098 return;
099 }
100 associateConnections(currentContext);
101 }
102
103 public void exit(ConnectorInstanceContext oldContext) throws ResourceException {
104 ConnectorInstanceContext currentContext = (ConnectorInstanceContext) currentInstanceContexts.get();
105 try {
106 // for each connection type opened in this componet
107 Map resources = currentContext.getConnectionManagerMap();
108 for (Iterator i = resources.entrySet().iterator(); i.hasNext();) {
109 Map.Entry entry = (Map.Entry) i.next();
110 ConnectionTrackingInterceptor mcci =
111 (ConnectionTrackingInterceptor) entry.getKey();
112 Set connections = (Set) entry.getValue();
113
114 // release proxy connections
115 if (lazyConnect) {
116 for (Iterator infoIterator = connections.iterator(); infoIterator.hasNext();) {
117 ConnectionInfo connectionInfo = (ConnectionInfo) infoIterator.next();
118 releaseProxyConnection(connectionInfo);
119 }
120 }
121
122 // use connection interceptor to dissociate connections that support disassociation
123 mcci.exit(connections);
124
125 // if no connection remain clear context... we could support automatic commit, rollback or exception here
126 if (connections.isEmpty()) {
127 i.remove();
128 }
129 }
130 } finally {
131 // when lazy we do not need or want to track open connections... they will automatically reconnect
132 if (lazyConnect) {
133 currentContext.getConnectionManagerMap().clear();
134 }
135 currentInstanceContexts.set(oldContext);
136 }
137 }
138
139 /**
140 * A new connection (handle) has been obtained. If we are within a component context, store the connection handle
141 * so we can disassociate connections that support disassociation on exit.
142 * @param connectionTrackingInterceptor our interceptor in the connection manager which is used to disassociate the connections
143 * @param connectionInfo the connection that was obtained
144 * @param reassociate
145 */
146 public void handleObtained(ConnectionTrackingInterceptor connectionTrackingInterceptor,
147 ConnectionInfo connectionInfo,
148 boolean reassociate) throws ResourceException {
149
150 ConnectorInstanceContext currentContext = (ConnectorInstanceContext) currentInstanceContexts.get();
151 if (currentContext == null) {
152 return;
153 }
154
155 Map resources = currentContext.getConnectionManagerMap();
156 Set infos = (Set) resources.get(connectionTrackingInterceptor);
157 if (infos == null) {
158 infos = new HashSet();
159 resources.put(connectionTrackingInterceptor, infos);
160 }
161
162 infos.add(connectionInfo);
163
164 // if lazyConnect, we must proxy so we know when to connect the proxy
165 if (!reassociate && lazyConnect) {
166 proxyConnection(connectionTrackingInterceptor, connectionInfo);
167 }
168 }
169
170 /**
171 * A connection (handle) has been released or destroyed. If we are within a component context, remove the connection
172 * handle from the context.
173 * @param connectionTrackingInterceptor our interceptor in the connection manager
174 * @param connectionInfo the connection that was released
175 * @param connectionReturnAction
176 */
177 public void handleReleased(ConnectionTrackingInterceptor connectionTrackingInterceptor,
178 ConnectionInfo connectionInfo,
179 ConnectionReturnAction connectionReturnAction) {
180
181 ConnectorInstanceContext currentContext = (ConnectorInstanceContext) currentInstanceContexts.get();
182 if (currentContext == null) {
183 return;
184 }
185
186 Map resources = currentContext.getConnectionManagerMap();
187 Set infos = (Set) resources.get(connectionTrackingInterceptor);
188 if (infos != null) {
189 if (connectionInfo.getConnectionHandle() == null) {
190 //destroy was called as a result of an error
191 ManagedConnectionInfo mci = connectionInfo.getManagedConnectionInfo();
192 Collection toRemove = mci.getConnectionInfos();
193 infos.removeAll(toRemove);
194 } else {
195 infos.remove(connectionInfo);
196 }
197 } else {
198 if ( log.isTraceEnabled()) {
199 log.trace("No infos found for handle " + connectionInfo.getConnectionHandle() +
200 " for MCI: " + connectionInfo.getManagedConnectionInfo() +
201 " for MC: " + connectionInfo.getManagedConnectionInfo().getManagedConnection() +
202 " for CTI: " + connectionTrackingInterceptor, new Exception("Stack Trace"));
203 }
204 }
205
206 // NOTE: This method is also called by DissociatableManagedConnection when a connection has been
207 // dissociated in addition to the normal connection closed notification, but this is not a problem
208 // because DissociatableManagedConnection are not proied so this method will have no effect
209 closeProxyConnection(connectionInfo);
210 }
211
212 /**
213 * If we are within a component context, before a connection is obtained, set the connection unshareable and
214 * applicationManagedSecurity properties so the correct connection type is obtained.
215 * @param connectionInfo the connection to be obtained
216 * @param key the unique id of the connection manager
217 */
218 public void setEnvironment(ConnectionInfo connectionInfo, String key) {
219 ConnectorInstanceContext currentContext = (ConnectorInstanceContext) currentInstanceContexts.get();
220 if (currentContext != null) {
221 // is this resource unshareable in this component context
222 Set unshareableResources = currentContext.getUnshareableResources();
223 boolean unshareable = unshareableResources.contains(key);
224 connectionInfo.setUnshareable(unshareable);
225
226 // does this resource use application managed security in this component context
227 Set applicationManagedSecurityResources = currentContext.getApplicationManagedSecurityResources();
228 boolean applicationManagedSecurity = applicationManagedSecurityResources.contains(key);
229 connectionInfo.setApplicationManagedSecurity(applicationManagedSecurity);
230 }
231 }
232
233 private void proxyConnection(ConnectionTrackingInterceptor connectionTrackingInterceptor, ConnectionInfo connectionInfo) throws ResourceException {
234 // if this connection already has a proxy no need to create another
235 if (connectionInfo.getConnectionProxy() != null) return;
236
237 // DissociatableManagedConnection do not need to be proxied
238 if (connectionInfo.getManagedConnectionInfo().getManagedConnection() instanceof DissociatableManagedConnection) {
239 return;
240 }
241
242 try {
243 Object handle = connectionInfo.getConnectionHandle();
244 ConnectionInvocationHandler invocationHandler = new ConnectionInvocationHandler(connectionTrackingInterceptor, connectionInfo, handle);
245 Object proxy = Proxy.newProxyInstance(getClassLoader(handle), handle.getClass().getInterfaces(), invocationHandler);
246
247 // add it to our map... if the map already has a proxy for this connection, use the existing one
248 Object existingProxy = proxiesByConnectionInfo.putIfAbsent(connectionInfo, proxy);
249 if (existingProxy != null) proxy = existingProxy;
250
251 connectionInfo.setConnectionProxy(proxy);
252 } catch (Throwable e) {
253 throw new ResourceException("Unable to construct connection proxy", e);
254 }
255 }
256
257 private void releaseProxyConnection(ConnectionInfo connectionInfo) {
258 ConnectionInvocationHandler invocationHandler = getConnectionInvocationHandler(connectionInfo);
259 if (invocationHandler != null) {
260 invocationHandler.releaseHandle();
261 }
262 }
263
264 private void closeProxyConnection(ConnectionInfo connectionInfo) {
265 ConnectionInvocationHandler invocationHandler = getConnectionInvocationHandler(connectionInfo);
266 if (invocationHandler != null) {
267 invocationHandler.close();
268 proxiesByConnectionInfo.remove(connectionInfo);
269 connectionInfo.setConnectionProxy(null);
270 }
271 }
272
273 // Favor the thread context class loader for proxy construction
274 private ClassLoader getClassLoader(Object handle) {
275 ClassLoader threadClassLoader = Thread.currentThread().getContextClassLoader();
276 if (threadClassLoader != null) {
277 return threadClassLoader;
278 }
279 return handle.getClass().getClassLoader();
280 }
281
282 private ConnectionInvocationHandler getConnectionInvocationHandler(ConnectionInfo connectionInfo) {
283 Object proxy = connectionInfo.getConnectionProxy();
284 if (proxy == null) {
285 proxy = proxiesByConnectionInfo.get(connectionInfo);
286 }
287
288 // no proxy or proxy already destroyed
289 if (proxy == null) return null;
290
291 if (Proxy.isProxyClass(proxy.getClass())) {
292 InvocationHandler invocationHandler = Proxy.getInvocationHandler(proxy);
293 if (invocationHandler instanceof ConnectionInvocationHandler) {
294 return (ConnectionInvocationHandler) invocationHandler;
295 }
296 }
297 return null;
298 }
299
300 public static class ConnectionInvocationHandler implements InvocationHandler {
301 private ConnectionTrackingInterceptor connectionTrackingInterceptor;
302 private ConnectionInfo connectionInfo;
303 private final Object handle;
304 private boolean released = false;
305
306 public ConnectionInvocationHandler(ConnectionTrackingInterceptor connectionTrackingInterceptor, ConnectionInfo connectionInfo, Object handle) {
307 this.connectionTrackingInterceptor = connectionTrackingInterceptor;
308 this.connectionInfo = connectionInfo;
309 this.handle = handle;
310 }
311
312 public Object invoke(Object object, Method method, Object[] args) throws Throwable {
313 Object handle;
314 if (method.getDeclaringClass() == Object.class) {
315 if (method.getName().equals("finalize")) {
316 // ignore the handle will get called if it implemented the method
317 return null;
318 }
319 if (method.getName().equals("clone")) {
320 throw new CloneNotSupportedException();
321 }
322 // for equals, hashCode and toString don't activate handle
323 synchronized (this) {
324 handle = this.handle;
325 }
326 } else {
327 handle = getHandle();
328 }
329
330 try {
331 Object value = method.invoke(handle, args);
332 return value;
333 } catch (InvocationTargetException ite) {
334 // catch InvocationTargetExceptions and turn them into the target exception (if there is one)
335 Throwable t = ite.getTargetException();
336 if (t != null) {
337 throw t;
338 }
339 throw ite;
340 }
341
342 }
343
344 public synchronized boolean isReleased() {
345 return released;
346 }
347
348 public synchronized void releaseHandle() {
349 released = true;
350 }
351
352 public synchronized void close() {
353 connectionTrackingInterceptor = null;
354 connectionInfo = null;
355 released = true;
356 }
357
358 public synchronized Object getHandle() {
359 if (connectionTrackingInterceptor == null) {
360 // connection has been closed... send invocations directly to the handle
361 // which will throw an exception or in some clases like JDBC connection.close()
362 // ignore the invocation
363 return handle;
364 }
365
366 if (released) {
367 try {
368 connectionTrackingInterceptor.reassociateConnection(connectionInfo);
369 } catch (ResourceException e) {
370 throw (IllegalStateException) new IllegalStateException("Could not obtain a physical connection").initCause(e);
371 }
372 released = false;
373 }
374 return handle;
375 }
376 }
377 }