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