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;
019    
020    import java.util.Collections;
021    import java.util.HashSet;
022    import java.util.Iterator;
023    import java.util.Set;
024    
025    import javax.resource.ResourceException;
026    import javax.transaction.SystemException;
027    import javax.transaction.Transaction;
028    import javax.transaction.TransactionManager;
029    
030    import org.apache.commons.logging.Log;
031    import org.apache.commons.logging.LogFactory;
032    import org.apache.geronimo.connector.ConnectionReleaser;
033    import org.apache.geronimo.connector.ConnectorTransactionContext;
034    
035    /**
036     * TransactionCachingInterceptor.java
037     * TODO: This implementation does not take account of unshareable resources
038     * TODO: This implementation does not take account of application security
039     * where several connections with different security info are obtained.
040     * TODO: This implementation does not take account of container managed security where,
041     * within one transaction, a security domain boundary is crossed
042     * and connections are obtained with two (or more) different subjects.
043     * <p/>
044     * I suggest a state pattern, with the state set in a threadlocal upon entering a component,
045     * will be a usable implementation.
046     * <p/>
047     * The afterCompletion method will need to move to an interface,  and that interface include the
048     * security info to distinguish connections.
049     * <p/>
050     * <p/>
051     * Created: Mon Sep 29 15:07:07 2003
052     *
053     * @version 1.0
054     */
055    public class TransactionCachingInterceptor implements ConnectionInterceptor, ConnectionReleaser {
056        protected static Log log = LogFactory.getLog(TransactionCachingInterceptor.class.getName());
057    
058        private final ConnectionInterceptor next;
059        private final TransactionManager transactionManager;
060    
061        public TransactionCachingInterceptor(ConnectionInterceptor next, TransactionManager transactionManager) {
062            this.next = next;
063            this.transactionManager = transactionManager;
064        }
065    
066        public void getConnection(ConnectionInfo connectionInfo) throws ResourceException {
067            //There can be an inactive transaction context when a connection is requested in
068            //Synchronization.afterCompletion().
069    
070            // get the current transation and status... if there is a problem just assume there is no transaction present
071            Transaction transaction = TxUtil.getTransactionIfActive(transactionManager);
072            if (transaction != null) {
073                ManagedConnectionInfos managedConnectionInfos = ConnectorTransactionContext.get(transaction, this);
074                if (connectionInfo.isUnshareable()) {
075                    if (!managedConnectionInfos.containsUnshared(connectionInfo.getManagedConnectionInfo())) {
076                        next.getConnection(connectionInfo);
077                        managedConnectionInfos.addUnshared(connectionInfo.getManagedConnectionInfo());
078                    }
079                } else {
080                    ManagedConnectionInfo managedConnectionInfo = managedConnectionInfos.getShared();
081                    if (managedConnectionInfo != null) {
082                        connectionInfo.setManagedConnectionInfo(managedConnectionInfo);
083                        //return;
084                        if (log.isTraceEnabled()) {
085                            log.trace("supplying connection from tx cache " + connectionInfo.getConnectionHandle() + " for managed connection " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " to tx caching interceptor " + this);
086                        }
087                    } else {
088                        next.getConnection(connectionInfo);
089                        managedConnectionInfos.setShared(connectionInfo.getManagedConnectionInfo());
090                        if (log.isTraceEnabled()) {
091                            log.trace("supplying connection from pool " + connectionInfo.getConnectionHandle() + " for managed connection " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " to tx caching interceptor " + this);
092                        }
093                    }
094                }
095            } else {
096                next.getConnection(connectionInfo);
097            }
098        }
099    
100        public void returnConnection(ConnectionInfo connectionInfo, ConnectionReturnAction connectionReturnAction) {
101    
102            if (connectionReturnAction == ConnectionReturnAction.DESTROY) {
103                if (log.isTraceEnabled()) {
104                    log.trace("destroying connection" + connectionInfo.getConnectionHandle() + " for managed connection " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " to tx caching interceptor " + this);
105                }
106                next.returnConnection(connectionInfo, connectionReturnAction);
107                return;
108            }
109            Transaction transaction;
110            try {
111                transaction = transactionManager.getTransaction();
112                if (transaction != null) {
113                    if (TxUtil.isActive(transaction)) {
114                        if (log.isTraceEnabled()) {
115                            log.trace("tx active, not returning connection" + connectionInfo.getConnectionHandle() + " for managed connection " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " to tx caching interceptor " + this);
116                        }
117                        return;
118                    }
119                    //We are called from an afterCompletion synchronization.  Remove the MCI from the ManagedConnectionInfos
120                    //so we don't close it twice
121                    ManagedConnectionInfos managedConnectionInfos = ConnectorTransactionContext.get(transaction, this);
122                    managedConnectionInfos.remove(connectionInfo.getManagedConnectionInfo());
123                    if (log.isTraceEnabled()) {
124                        log.trace("tx ended, but not removed");
125                    }
126                }
127            } catch (SystemException e) {
128                //ignore
129            }
130            if (log.isTraceEnabled()) {
131                log.trace("tx ended, returning connection" + connectionInfo.getConnectionHandle() + " for managed connection " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " to tx caching interceptor " + this);
132            }
133            internalReturn(connectionInfo, connectionReturnAction);
134        }
135    
136        private void internalReturn(ConnectionInfo connectionInfo, ConnectionReturnAction connectionReturnAction) {
137            if (connectionInfo.getManagedConnectionInfo().hasConnectionHandles()) {
138                if (log.isTraceEnabled()) {
139                    log.trace("not returning connection from tx cache (has handles) " + connectionInfo.getConnectionHandle() + " for managed connection " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " to tx caching interceptor " + this);
140                }
141                return;
142            }
143            //No transaction, no handles, we return it.
144            next.returnConnection(connectionInfo, connectionReturnAction);
145            if (log.isTraceEnabled()) {
146                log.trace("completed return of connection through tx cache " + connectionInfo.getConnectionHandle() + " for MCI: " + connectionInfo.getManagedConnectionInfo() + " and MC " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " to tx caching interceptor " + this);
147            }
148        }
149    
150        public void destroy() {
151            next.destroy();
152        }
153    
154        public void afterCompletion(Object stuff) {
155            ManagedConnectionInfos managedConnectionInfos = (ManagedConnectionInfos) stuff;
156            ManagedConnectionInfo sharedMCI = managedConnectionInfos.getShared();
157            if (sharedMCI != null) {
158                if (log.isTraceEnabled()) {
159                    log.trace("Transaction completed, attempting to return shared connection MCI: " + sharedMCI + " for managed connection " + sharedMCI.getManagedConnection() + " to tx caching interceptor " + this);
160                }
161                returnHandle(sharedMCI);
162            }
163            for (Iterator iterator = managedConnectionInfos.getUnshared().iterator(); iterator.hasNext();) {
164                ManagedConnectionInfo managedConnectionInfo = (ManagedConnectionInfo) iterator.next();
165                if (log.isTraceEnabled()) {
166                    log.trace("Transaction completed, attempting to return unshared connection MCI: " + managedConnectionInfo + " for managed connection " + managedConnectionInfo.getManagedConnection() + " to tx caching interceptor " + this);
167                }
168                returnHandle(managedConnectionInfo);
169            }
170        }
171    
172        private void returnHandle(ManagedConnectionInfo managedConnectionInfo) {
173            ConnectionInfo connectionInfo = new ConnectionInfo();
174            connectionInfo.setManagedConnectionInfo(managedConnectionInfo);
175            internalReturn(connectionInfo, ConnectionReturnAction.RETURN_HANDLE);
176        }
177    
178        public static class ManagedConnectionInfos {
179            private ManagedConnectionInfo shared;
180            private Set unshared = Collections.EMPTY_SET;
181    
182            public ManagedConnectionInfo getShared() {
183                return shared;
184            }
185    
186            public void setShared(ManagedConnectionInfo shared) {
187                this.shared = shared;
188            }
189    
190            public Set getUnshared() {
191                return unshared;
192            }
193    
194            public void addUnshared(ManagedConnectionInfo unsharedMCI) {
195                if (this.unshared == Collections.EMPTY_SET) {
196                    this.unshared = new HashSet();
197                }
198                this.unshared.add(unsharedMCI);
199            }
200    
201            public boolean containsUnshared(ManagedConnectionInfo unsharedMCI) {
202                return this.unshared.contains(unsharedMCI);
203            }
204    
205            public void remove(ManagedConnectionInfo managedConnectionInfo) {
206                if (shared == managedConnectionInfo) {
207                    shared = null;
208                } else {
209                    unshared.remove(managedConnectionInfo);
210                }
211            }
212        }
213    
214    }