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.commons.dbcp;
019    
020    import java.util.ArrayList;
021    import java.util.Iterator;
022    import java.util.List;
023    
024    import org.apache.commons.pool.PoolableObjectFactory;
025    import org.apache.commons.pool.impl.GenericObjectPool;
026    
027    /**
028     * <p>An implementation of a Jakarta-Commons ObjectPool which
029     * tracks JDBC connections and can recover abandoned db connections.
030     * If logAbandoned=true, a stack trace will be printed for any
031     * abandoned db connections recovered.
032     *                                                                        
033     * @author Glenn L. Nielsen
034     * @version $Revision: 899987 $ $Date: 2010-01-16 11:51:16 -0500 (Sat, 16 Jan 2010) $
035     */
036    public class AbandonedObjectPool extends GenericObjectPool {
037    
038        /** 
039         * DBCP AbandonedConfig 
040         */
041        private final AbandonedConfig config;
042        
043        /**
044         * A list of connections in use
045         */
046        private final List trace = new ArrayList();
047    
048        /**
049         * Create an ObjectPool which tracks db connections.
050         *
051         * @param factory PoolableObjectFactory used to create this
052         * @param config configuration for abandoned db connections
053         */
054        public AbandonedObjectPool(PoolableObjectFactory factory,
055                                   AbandonedConfig config) {
056            super(factory);
057            this.config = config;
058        }
059    
060        /**
061         * Get a db connection from the pool.
062         *
063         * If removeAbandoned=true, recovers db connections which
064         * have been idle > removeAbandonedTimeout and
065         * getNumActive() > getMaxActive() - 3 and
066         * getNumIdle() < 2
067         * 
068         * @return Object jdbc Connection
069         * @throws Exception if an exception occurs retrieving a 
070         * connection from the pool
071         */
072        public Object borrowObject() throws Exception {
073            if (config != null
074                    && config.getRemoveAbandoned()
075                    && (getNumIdle() < 2)
076                    && (getNumActive() > getMaxActive() - 3) ) {
077                removeAbandoned();
078            }
079            Object obj = super.borrowObject();
080            if (obj instanceof AbandonedTrace) {
081                ((AbandonedTrace) obj).setStackTrace();
082            }
083            if (obj != null && config != null && config.getRemoveAbandoned()) {
084                synchronized (trace) {
085                    trace.add(obj);
086                }
087            }
088            return obj;
089        }
090    
091        /**
092         * Return a db connection to the pool.
093         *
094         * @param obj db Connection to return
095         * @throws Exception if an exception occurs returning the connection
096         * to the pool
097         */
098        public void returnObject(Object obj) throws Exception {
099            if (config != null && config.getRemoveAbandoned()) {
100                synchronized (trace) {
101                    boolean foundObject = trace.remove(obj);
102                    if (!foundObject) {
103                        return; // This connection has already been invalidated.  Stop now.
104                    }
105                }
106            }
107            super.returnObject(obj);
108        }
109    
110        /**
111         * Invalidates an object from the pool.
112         *
113         * @param obj object to be returned
114         * @throws Exception if an exception occurs invalidating the object
115         */
116        public void invalidateObject(Object obj) throws Exception {
117            if (config != null && config.getRemoveAbandoned()) {
118                synchronized (trace) {
119                    boolean foundObject = trace.remove(obj);
120                    if (!foundObject) {
121                        return; // This connection has already been invalidated.  Stop now.
122                    }
123                }
124            }
125            super.invalidateObject(obj);        
126        }
127    
128        /**
129         * Recover abandoned db connections which have been idle
130         * greater than the removeAbandonedTimeout.
131         */
132        private void removeAbandoned() {
133            // Generate a list of abandoned connections to remove
134            long now = System.currentTimeMillis();
135            long timeout = now - (config.getRemoveAbandonedTimeout() * 1000);
136            ArrayList remove = new ArrayList();
137            synchronized (trace) {
138                Iterator it = trace.iterator();
139                while (it.hasNext()) {
140                    AbandonedTrace pc = (AbandonedTrace) it.next();
141                    if (pc.getLastUsed() > timeout) {
142                        continue;
143                    }
144                    if (pc.getLastUsed() > 0) {
145                        remove.add(pc);
146                    }
147                }
148            }
149    
150            // Now remove the abandoned connections
151            Iterator it = remove.iterator();
152            while (it.hasNext()) {
153                AbandonedTrace pc = (AbandonedTrace) it.next();
154                if (config.getLogAbandoned()) {
155                    pc.printStackTrace();
156                }             
157                try {
158                    invalidateObject(pc);
159                } catch (Exception e) {
160                    e.printStackTrace();
161                }
162                
163            }
164        }
165    }
166