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.geronimo.connector.outbound.ConnectionInfo;
036 import org.apache.geronimo.connector.outbound.ConnectionReturnAction;
037 import org.apache.geronimo.connector.outbound.ConnectionTrackingInterceptor;
038 import org.apache.geronimo.connector.outbound.ManagedConnectionInfo;
039 import org.slf4j.Logger;
040 import org.slf4j.LoggerFactory;
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: 723385 $ $Date: 2008-12-04 12:55:02 -0500 (Thu, 04 Dec 2008) $
057 */
058 public class ConnectionTrackingCoordinator implements TrackedConnectionAssociator, ConnectionTracker {
059 private static final Logger log = LoggerFactory.getLogger(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 }