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.geronimo.connector.ConnectionReleaser;
029    import org.apache.geronimo.connector.ConnectorTransactionContext;
030    import org.slf4j.Logger;
031    import org.slf4j.LoggerFactory;
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 Logger log = LoggerFactory.getLogger(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(
084                                    "supplying connection from tx cache " + connectionInfo.getConnectionHandle() + " for managed connection " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " to tx caching interceptor " + this);
085                        }
086                    } else {
087                        next.getConnection(connectionInfo);
088                        managedConnectionInfos.setShared(connectionInfo.getManagedConnectionInfo());
089                        if (log.isTraceEnabled()) {
090                            log.trace(
091                                    "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(
116                                    "tx active, not returning connection" + connectionInfo.getConnectionHandle() + " for managed connection " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " to tx caching interceptor " + this);
117                        }
118                        return;
119                    }
120                    //We are called from an afterCompletion synchronization.  Remove the MCI from the ManagedConnectionInfos
121                    //so we don't close it twice
122                    ManagedConnectionInfos managedConnectionInfos = ConnectorTransactionContext.get(transaction, this);
123                    managedConnectionInfos.remove(connectionInfo.getManagedConnectionInfo());
124                    if (log.isTraceEnabled()) {
125                        log.trace("tx ended, but not removed");
126                    }
127                }
128            } catch (SystemException e) {
129                //ignore
130            }
131            if (log.isTraceEnabled()) {
132                log.trace("tx ended, returning connection" + connectionInfo.getConnectionHandle() + " for managed connection " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " to tx caching interceptor " + this);
133            }
134            internalReturn(connectionInfo, connectionReturnAction);
135        }
136    
137        private void internalReturn(ConnectionInfo connectionInfo, ConnectionReturnAction connectionReturnAction) {
138            if (connectionInfo.getManagedConnectionInfo().hasConnectionHandles()) {
139                if (log.isTraceEnabled()) {
140                    log.trace(
141                            "not returning connection from tx cache (has handles) " + connectionInfo.getConnectionHandle() + " for managed connection " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " to tx caching interceptor " + this);
142                }
143                return;
144            }
145            //No transaction, no handles, we return it.
146            next.returnConnection(connectionInfo, connectionReturnAction);
147            if (log.isTraceEnabled()) {
148                log.trace(
149                        "completed return of connection through tx cache " + connectionInfo.getConnectionHandle() + " for MCI: " + connectionInfo.getManagedConnectionInfo() + " and MC " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " to tx caching interceptor " + this);
150            }
151        }
152    
153        public void destroy() {
154            next.destroy();
155        }
156    
157        public void afterCompletion(Object stuff) {
158            ManagedConnectionInfos managedConnectionInfos = (ManagedConnectionInfos) stuff;
159            ManagedConnectionInfo sharedMCI = managedConnectionInfos.getShared();
160            if (sharedMCI != null) {
161                if (log.isTraceEnabled()) {
162                    log.trace("Transaction completed, attempting to return shared connection MCI: " + sharedMCI + " for managed connection " + sharedMCI.getManagedConnection() + " to tx caching interceptor " + this);
163                }
164                returnHandle(sharedMCI);
165            }
166            for (ManagedConnectionInfo managedConnectionInfo : managedConnectionInfos.getUnshared()) {
167                if (log.isTraceEnabled()) {
168                    log.trace("Transaction completed, attempting to return unshared connection MCI: " + managedConnectionInfo + " for managed connection " + managedConnectionInfo.getManagedConnection() + " to tx caching interceptor " + this);
169                }
170                returnHandle(managedConnectionInfo);
171            }
172        }
173    
174        private void returnHandle(ManagedConnectionInfo managedConnectionInfo) {
175            ConnectionInfo connectionInfo = new ConnectionInfo();
176            connectionInfo.setManagedConnectionInfo(managedConnectionInfo);
177            internalReturn(connectionInfo, ConnectionReturnAction.RETURN_HANDLE);
178        }
179    
180        public static class ManagedConnectionInfos {
181            private ManagedConnectionInfo shared;
182            private Set<ManagedConnectionInfo> unshared = new HashSet<ManagedConnectionInfo>(1);
183    
184            public ManagedConnectionInfo getShared() {
185                return shared;
186            }
187    
188            public void setShared(ManagedConnectionInfo shared) {
189                this.shared = shared;
190            }
191    
192            public Set<ManagedConnectionInfo> getUnshared() {
193                return unshared;
194            }
195    
196            public void addUnshared(ManagedConnectionInfo unsharedMCI) {
197                unshared.add(unsharedMCI);
198            }
199    
200            public boolean containsUnshared(ManagedConnectionInfo unsharedMCI) {
201                return unshared.contains(unsharedMCI);
202            }
203    
204            public void remove(ManagedConnectionInfo managedConnectionInfo) {
205                if (shared == managedConnectionInfo) {
206                    shared = null;
207                } else {
208                    unshared.remove(managedConnectionInfo);
209                }
210            }
211        }
212    }