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    }