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 }