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    }