001    package org.apache.ojb.broker.accesslayer;
002    
003    /* Copyright 2002-2005 The Apache Software Foundation
004     *
005     * Licensed under the Apache License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * 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    import org.apache.commons.pool.BasePoolableObjectFactory;
019    import org.apache.commons.pool.ObjectPool;
020    import org.apache.commons.pool.PoolableObjectFactory;
021    import org.apache.commons.pool.impl.GenericObjectPool;
022    import org.apache.ojb.broker.metadata.JdbcConnectionDescriptor;
023    import org.apache.ojb.broker.util.logging.Logger;
024    import org.apache.ojb.broker.util.logging.LoggerFactory;
025    import org.apache.ojb.broker.OJBRuntimeException;
026    
027    import java.sql.Connection;
028    import java.sql.ResultSet;
029    import java.sql.SQLException;
030    import java.sql.PreparedStatement;
031    import java.util.Collection;
032    import java.util.HashMap;
033    import java.util.Iterator;
034    import java.util.Map;
035    import java.util.NoSuchElementException;
036    
037    /**
038     * Connection factory which pools the requested
039     * connections for different JdbcConnectionDescriptors
040     * using Commons Pool API.
041     *
042     * @version $Id: ConnectionFactoryPooledImpl.java,v 1.1 2007-08-24 22:17:30 ewestfal Exp $
043     * @see <a href="http://jakarta.apache.org/commons/pool/">Commons Pool Website</a>
044     */
045    public class ConnectionFactoryPooledImpl extends ConnectionFactoryAbstractImpl
046    {
047    
048        private Logger log = LoggerFactory.getLogger(ConnectionFactoryPooledImpl.class);
049        /** Key=PBKey, value=ObjectPool. */
050        private Map poolMap = new HashMap();
051        /** Synchronize object for operations not synchronized on Map only. */
052        private final Object poolSynch = new Object();
053    
054        public void releaseJdbcConnection(JdbcConnectionDescriptor jcd, Connection con)
055                throws LookupException
056        {
057            final ObjectPool op = (ObjectPool) poolMap.get(jcd.getPBKey());
058            try
059            {
060                /* mkalen: NB - according to the Commons Pool API we should _not_ perform
061                 * any additional checks here since we will then break testOnX semantics
062                 *
063                 * To enable Connection validation on releaseJdbcConnection,
064                 * set a validation query and specify testOnRelease=true
065                 *
066                 * Destruction of pooled objects is performed by the actual Commons Pool
067                 * ObjectPool implementation when the object factory's validateObject method
068                 * returns false. See ConPoolFactory#validateObject.
069                 */
070                op.returnObject(con);
071            }
072            catch (Exception e)
073            {
074                throw new LookupException(e);
075            }
076        }
077    
078        public Connection checkOutJdbcConnection(JdbcConnectionDescriptor jcd) throws LookupException
079        {
080            ObjectPool op = (ObjectPool) poolMap.get(jcd.getPBKey());
081            if (op == null)
082            {
083                synchronized (poolSynch)
084                {
085                    log.info("Create new connection pool:" + jcd);
086                    op = createConnectionPool(jcd);
087                    poolMap.put(jcd.getPBKey(), op);
088                }
089            }
090            final Connection conn;
091            try
092            {
093                conn = (Connection) op.borrowObject();
094            }
095            catch (NoSuchElementException e)
096            {
097                int active = 0;
098                int idle = 0;
099                try
100                {
101                    active = op.getNumActive();
102                    idle = op.getNumIdle();
103                }
104                catch(Exception ignore){}
105                throw new LookupException("Could not borrow connection from pool, seems ObjectPool is exhausted." +
106                        " Active/Idle instances in pool=" + active + "/" +  idle
107                        + ". "+ JdbcConnectionDescriptor.class.getName() + ":  " + jcd, e);
108            }
109            catch (Exception e)
110            {
111                int active = 0;
112                int idle = 0;
113                try
114                {
115                    active = op.getNumActive();
116                    idle = op.getNumIdle();
117                }
118                catch(Exception ignore){}
119                throw new LookupException("Could not borrow connection from pool." +
120                        " Active/Idle instances in pool=" + active + "/" +  idle
121                        + ". "+ JdbcConnectionDescriptor.class.getName() + ":  " + jcd, e);
122            }
123            return conn;
124        }
125    
126        /**
127         * Create the pool for pooling the connections of the given connection descriptor.
128         * Override this method to implement your on {@link org.apache.commons.pool.ObjectPool}.
129         */
130        public ObjectPool createConnectionPool(JdbcConnectionDescriptor jcd)
131        {
132            if (log.isDebugEnabled()) log.debug("createPool was called");
133            PoolableObjectFactory pof = new ConPoolFactory(this, jcd);
134            GenericObjectPool.Config conf = jcd.getConnectionPoolDescriptor().getObjectPoolConfig();
135            return (ObjectPool)new GenericObjectPool(pof, conf);
136        }
137    
138        /**
139         * Closes all managed pools.
140         */
141        public void releaseAllResources()
142        {
143            synchronized (poolSynch)
144            {
145                Collection pools = poolMap.values();
146                poolMap = new HashMap(poolMap.size());
147                ObjectPool op = null;
148                for (Iterator iterator = pools.iterator(); iterator.hasNext();)
149                {
150                    try
151                    {
152                        op = ((ObjectPool) iterator.next());
153                        op.close();
154                    }
155                    catch (Exception e)
156                    {
157                        log.error("Exception occured while closing pool " + op, e);
158                    }
159                }
160            }
161            super.releaseAllResources();
162        }
163    
164        //**************************************************************************************
165        // Inner classes
166        //************************************************************************************
167    
168        /**
169         * Inner class - {@link org.apache.commons.pool.PoolableObjectFactory}
170         * used as factory for connection pooling.
171         */
172        class ConPoolFactory extends BasePoolableObjectFactory
173        {
174            final private JdbcConnectionDescriptor jcd;
175            final private ConnectionFactoryPooledImpl cf;
176            private int failedValidationQuery;
177    
178            public ConPoolFactory(ConnectionFactoryPooledImpl cf, JdbcConnectionDescriptor jcd)
179            {
180                this.cf = cf;
181                this.jcd = jcd;
182            }
183    
184            public boolean validateObject(Object obj)
185            {
186                boolean isValid = false;
187                if (obj != null)
188                {
189                    final Connection con = (Connection) obj;
190                    try
191                    {
192                        isValid = !con.isClosed();
193                    }
194                    catch (SQLException e)
195                    {
196                        log.warn("Connection validation failed: " + e.getMessage());
197                        if (log.isDebugEnabled()) log.debug(e);
198                        isValid = false;
199                    }
200                    if (isValid)
201                    {
202                        final String validationQuery;
203                        validationQuery = jcd.getConnectionPoolDescriptor().getValidationQuery();
204                        if (validationQuery != null)
205                        {
206                            isValid = validateConnection(con, validationQuery);
207                        }
208                    }
209                }
210                return isValid;
211            }
212    
213            private boolean validateConnection(Connection conn, String query)
214            {
215                PreparedStatement stmt = null;
216                ResultSet rset = null;
217                boolean isValid = false;
218                if (failedValidationQuery > 100)
219                {
220                    --failedValidationQuery;
221                    throw new OJBRuntimeException("Validation of connection "+conn+" using validation query '"+
222                            query + "' failed more than 100 times.");
223                }
224                try
225                {
226                    stmt = conn.prepareStatement(query);
227                    stmt.setMaxRows(1);
228                    stmt.setFetchSize(1);
229                    rset = stmt.executeQuery();
230                    if (rset.next())
231                    {
232                        failedValidationQuery = 0;
233                        isValid = true;
234                    }
235                    else
236                    {
237                        ++failedValidationQuery;
238                        log.warn("Validation query '" + query +
239                                "' result set does not match, discard connection");
240                        isValid = false;
241                    }
242                }
243                catch (SQLException e)
244                {
245                    ++failedValidationQuery;
246                    log.warn("Validation query for connection failed, discard connection. Query was '" +
247                            query + "', Message was " + e.getMessage());
248                    if (log.isDebugEnabled()) log.debug(e);
249                }
250                finally
251                {
252                    try
253                    {
254                        if(rset != null) rset.close();
255                    }
256                    catch (SQLException t)
257                    {
258                        if (log.isDebugEnabled()) log.debug("ResultSet already closed.", t);
259                    }
260                    try
261                    {
262                        if(stmt != null) stmt.close();
263                    }
264                    catch (SQLException t)
265                    {
266                        if (log.isDebugEnabled()) log.debug("Statement already closed.", t);
267                    }
268                }
269                return isValid;
270            }
271    
272            public Object makeObject() throws Exception
273            {
274                if (log.isDebugEnabled()) log.debug("makeObject called");
275                return cf.newConnectionFromDriverManager(jcd);
276            }
277    
278            public void destroyObject(Object obj)
279                    throws Exception
280            {
281                log.info("Destroy object was called, try to close connection: " + obj);
282                try
283                {
284                    ((Connection) obj).close();
285                }
286                catch (SQLException ignore)
287                {
288                    //ignore it
289                }
290            }
291        }
292    
293    }