KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > james > util > mordred > JdbcDataSource


1 /***********************************************************************
2  * Copyright (c) 2000-2004 The Apache Software Foundation. *
3  * All rights reserved. *
4  * ------------------------------------------------------------------- *
5  * Licensed under the Apache License, Version 2.0 (the "License"); you *
6  * may not use this file except in compliance with the License. You *
7  * may obtain a copy of the License at: *
8  * *
9  * http://www.apache.org/licenses/LICENSE-2.0 *
10  * *
11  * Unless required by applicable law or agreed to in writing, software *
12  * distributed under the License is distributed on an "AS IS" BASIS, *
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or *
14  * implied. See the License for the specific language governing *
15  * permissions and limitations under the License. *
16  ***********************************************************************/

17
18 package org.apache.james.util.mordred;
19
20 import java.io.PrintWriter JavaDoc;
21 import java.io.StringWriter JavaDoc;
22
23 import java.sql.Connection JavaDoc;
24 import java.sql.SQLException JavaDoc;
25
26 import java.util.ArrayList JavaDoc;
27
28 import org.apache.avalon.excalibur.datasource.DataSourceComponent;
29 import org.apache.avalon.framework.activity.Disposable;
30 import org.apache.avalon.framework.configuration.Configurable;
31 import org.apache.avalon.framework.configuration.Configuration;
32 import org.apache.avalon.framework.configuration.ConfigurationException;
33 import org.apache.avalon.framework.logger.AbstractLogEnabled;
34
35
36 /**
37  * <p>
38  * This is a <b>reliable</b> DataSource implementation, based on the pooling logic written for <a
39  * HREF="http://share.whichever.com/">Town</a> and the configuration found in Avalon's excalibur
40  * code.
41  * </p>
42  *
43  * <p>
44  * This uses the normal <code>java.sql.Connection</code> object and
45  * <code>java.sql.DriverManager</code>. The Configuration is like this:
46  * <pre>
47  * &lt;jdbc&gt;
48  * &lt;pool-controller min="<i>5</i>" max="<i>10</i>" connection-class="<i>my.overrided.ConnectionClass</i>"&gt;
49  * &lt;keep-alive&gt;select 1&lt;/keep-alive&gt;
50  * &lt;/pool-controller&gt;
51  * &lt;driver&gt;<i>com.database.jdbc.JdbcDriver</i>&lt;/driver&gt;
52  * &lt;dburl&gt;<i>jdbc:driver://host/mydb</i>&lt;/dburl&gt;
53  * &lt;user&gt;<i>username</i>&lt;/user&gt;
54  * &lt;password&gt;<i>password</i>&lt;/password&gt;
55  * &lt;/jdbc&gt;
56  * </pre>
57  * </p>
58  *
59  * @version CVS $Revision: 1.18.4.8 $
60  * @since 4.0
61  */

62 public class JdbcDataSource extends AbstractLogEnabled
63     implements Configurable,
64                Runnable JavaDoc,
65                Disposable,
66                DataSourceComponent {
67     // The limit that an active connection can be running
68
public static final long ACTIVE_CONN_TIME_LIMIT = 60000; // (one minute)
69
public static final long ACTIVE_CONN_HARD_TIME_LIMIT = 2*ACTIVE_CONN_TIME_LIMIT;
70     // How long before you kill off a connection due to inactivity
71
public static final long CONN_IDLE_LIMIT = 600000; // (10 minutes)
72
private static final boolean DEEP_DEBUG = false;
73     private static int total_served = 0;
74     // This is a temporary variable used to track how many active threads
75
// are in createConnection(). This is to prevent to many connections
76
// from being opened at once.
77
private int connCreationsInProgress = 0;
78     // The error message is the conn pooler cannot serve connections for whatever reason
79
private String JavaDoc connErrorMessage = null;
80     // the last time a connection was created...
81
private long connLastCreated = 0;
82     // connection number for like of this broker
83
private int connectionCount;
84     // Driver class
85
private String JavaDoc jdbcDriver;
86     // Password to login to database
87
private String JavaDoc jdbcPassword;
88     // Server to connect to database (this really is the jdbc URL)
89
private String JavaDoc jdbcURL;
90     // Username to login to database
91
private String JavaDoc jdbcUsername;
92     // Maximum number of connections to have open at any point
93
private int maxConn = 0;
94     // collection of connection objects
95
private ArrayList JavaDoc pool;
96     // Thread that checks for dead/aged connections and removes them from pool
97
private Thread JavaDoc reaper;
98     // Flag to indicate whether reaper thread should run
99
private boolean reaperActive = false;
100     // a SQL command to execute to see if the connection is still ok
101
private String JavaDoc verifyConnSQL;
102
103     /**
104      * Implements the ConnDefinition behavior when a connection is needed. Checks the pool of
105      * connections to see if there is one available. If there is not and we are below the max
106      * number of connections limit, it tries to create another connection. It retries this 10
107      * times until a connection is available or can be created
108      *
109      * @return java.sql.Connection
110      * @throws SQLException Document throws!
111      */

112     public Connection JavaDoc getConnection() throws SQLException JavaDoc {
113         //If the conn definition has a fatal connection problem, need to return that error
114
if(connErrorMessage != null) {
115             throw new SQLException JavaDoc(connErrorMessage);
116         }
117         //Look through our list of open connections right now, starting from beginning.
118
//If we find one, book it.
119
int count = total_served++;
120         if(DEEP_DEBUG) {
121             StringBuffer JavaDoc deepDebugBuffer =
122                 new StringBuffer JavaDoc(128)
123                         .append((new java.util.Date JavaDoc()).toString())
124                         .append(" trying to get a connection (")
125                         .append(count)
126                         .append(")");
127             System.out.println(deepDebugBuffer.toString());
128         }
129         for(int attempts = 1; attempts <= 100; attempts++) {
130             synchronized(pool) {
131                 for(int i = 0; i < pool.size(); i++) {
132                     PoolConnEntry entry = (PoolConnEntry)pool.get(i);
133                     //Set the appropriate flags to make this connection
134
//marked as in use
135
try {
136                         if(entry.lock()) {
137                             if(DEEP_DEBUG) {
138                                 StringBuffer JavaDoc deepDebugBuffer =
139                                     new StringBuffer JavaDoc(128)
140                                             .append((new java.util.Date JavaDoc()).toString())
141                                             .append(" return a connection (")
142                                             .append(count)
143                                             .append(")");
144                                 System.out.println(deepDebugBuffer.toString());
145                             }
146                             return entry;
147                         }
148                     } catch(SQLException JavaDoc se) {
149                         //Somehow a closed connection appeared in our pool.
150
//Remove it immediately.
151
finalizeEntry(entry);
152                         continue;
153                     }
154                     //we were unable to get a lock on this entry.. so continue through list
155
} //loop through existing connections
156
//If we have 0, create another
157
if(DEEP_DEBUG) {
158                     System.out.println(pool.size());
159                 }
160                 try {
161                     if(pool.size() == 0) {
162                         //create a connection
163
PoolConnEntry entry = createConn();
164                         if(entry != null) {
165                             if(DEEP_DEBUG) {
166                                 StringBuffer JavaDoc deepDebugBuffer =
167                                     new StringBuffer JavaDoc(128)
168                                             .append((new java.util.Date JavaDoc()).toString())
169                                             .append(" returning new connection (")
170                                             .append(count)
171                                             .append(")");
172                                 System.out.println(deepDebugBuffer.toString());
173                             }
174                             return entry;
175                         }
176                         //looks like a connection was already created
177
} else {
178                         //Since we didn't find one, and we have < max connections, then consider whether
179
// we create another
180
//if we've hit the 3rd attempt without getting a connection,
181
// let's create another to anticipate a slow down
182
if((attempts == 2) && (pool.size() < maxConn || maxConn == 0)) {
183                             PoolConnEntry entry = createConn();
184                             if(entry != null) {
185                                 if(DEEP_DEBUG) {
186                                     StringBuffer JavaDoc deepDebugBuffer =
187                                         new StringBuffer JavaDoc(32)
188                                                 .append(" returning new connection (")
189                                                 .append(count)
190                                                 .append(")");
191                                     System.out.println(deepDebugBuffer.toString());
192                                 }
193                                 return entry;
194                             } else {
195                                 attempts = 1;
196                             }
197                         }
198                     }
199                 } catch(SQLException JavaDoc sqle) {
200                     //Ignore... error creating the connection
201
StringWriter JavaDoc sout = new StringWriter JavaDoc();
202                     PrintWriter JavaDoc pout = new PrintWriter JavaDoc(sout, true);
203                     pout.println("Error creating connection: ");
204                     sqle.printStackTrace(pout);
205                     if (getLogger().isErrorEnabled()) {
206                         getLogger().error(sout.toString());
207                     }
208                 }
209             }
210             //otherwise sleep 50ms 10 times, then create a connection
211
try {
212                 Thread.currentThread().sleep(50);
213             } catch(InterruptedException JavaDoc ie) {
214             }
215         }
216         // Give up... no connections available
217
throw new SQLException JavaDoc("Giving up... no connections available.");
218     }
219
220     /**
221      * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
222      */

223     public void configure(final Configuration configuration)
224                    throws ConfigurationException {
225         try {
226             jdbcDriver = configuration.getChild("driver").getValue(null);
227             jdbcURL = configuration.getChild("dburl").getValue(null);
228             jdbcUsername = configuration.getChild("user").getValue(null);
229             jdbcPassword = configuration.getChild("password").getValue(null);
230             maxConn = configuration.getChild("max").getValueAsInteger(2);
231             //logfilename?
232
verifyConnSQL = configuration.getChild("keep-alive").getValue(null);
233             //Not support from Town: logfilename
234
//Not supporting from Excalibur: pool-controller, min, auto-commit, oradb, connection-class
235
if(jdbcDriver == null) {
236                 throw new ConfigurationException("You need to specify a valid driver, e.g., <driver>my.class</driver>");
237             }
238             try {
239                 if (getLogger().isDebugEnabled()) {
240                     getLogger().debug("Loading new driver: " + jdbcDriver);
241                 }
242                 // TODO: Figure out why this breaks when we change the Class.forName to
243
// a loadClass method call on the class loader.
244
// DO NOT MESS WITH THIS UNLESS YOU ARE WILLING TO TEST
245
// AND FIX THE PROBLEMS!
246
Class.forName(jdbcDriver, true, Thread.currentThread().getContextClassLoader());
247                 // These variations do NOT work:
248
// getClass().getClassLoader().loadClass(jdbcDriver); -- DON'T USE -- BROKEN!!
249
// Thread.currentThread().getContextClassLoader().loadClass(jdbcDriver); -- DON'T USE -- BROKEN!!
250
} catch(ClassNotFoundException JavaDoc cnfe) {
251                 StringBuffer JavaDoc exceptionBuffer =
252                     new StringBuffer JavaDoc(128)
253                             .append("'")
254                             .append(jdbcDriver)
255                             .append("' could not be found in classloader. Please specify a valid JDBC driver");
256                 String JavaDoc exceptionMessage = exceptionBuffer.toString();
257                 getLogger().error(exceptionMessage);
258                 throw new ConfigurationException(exceptionMessage);
259             }
260             if(jdbcURL == null) {
261                 throw new ConfigurationException("You need to specify a valid JDBC connection string, e.g., <dburl>jdbc:driver:database</dburl>");
262             }
263             if(maxConn < 0) {
264                 throw new ConfigurationException("Maximum number of connections specified must be at least 1 (0 means no limit).");
265             }
266             if (getLogger().isDebugEnabled()) {
267                 getLogger().debug("Starting connection pooler");
268                 getLogger().debug("driver = " + jdbcDriver);
269                 getLogger().debug("dburl = " + jdbcURL);
270                 getLogger().debug("username = " + jdbcUsername);
271                 //We don't show the password
272
getLogger().debug("max connections = " + maxConn);
273             }
274             pool = new ArrayList JavaDoc();
275             reaperActive = true;
276             reaper = new Thread JavaDoc(this);
277             reaper.setDaemon(true);
278             reaper.start();
279         } catch(ConfigurationException ce) {
280             //Let this pass through...
281
throw ce;
282         }
283          catch(Exception JavaDoc e) {
284             throw new ConfigurationException("Error configuring JdbcDataSource", e);
285         }
286     }
287
288     /**
289      * The dispose operation is called at the end of a components lifecycle.
290      * Cleans up all JDBC connections.
291      *
292      * @throws Exception if an error is encountered during shutdown
293      */

294     public void dispose() {
295         // Stop the background monitoring thread
296
if(reaper != null) {
297             reaperActive = false;
298             //In case it's sleeping, help it quit faster
299
reaper.interrupt();
300             reaper = null;
301         }
302         // The various entries will finalize themselves once the reference
303
// is removed, so no need to do it here
304
}
305
306     /**
307      * Close all connections. The connection pooler will recreate these connections if something
308      * starts requesting them again.
309      *
310      * @deprecated This was left over code from Town... but not exposed in Avalon.
311      */

312     public void killAllConnections() {
313         //Just remove the references to all the connections... this will cause them to get
314
// finalized before very long. (not an instant shutdown, but that's ok).
315
synchronized (pool) { pool.clear(); }
316     }
317
318     /**
319      * Implements the ConnDefinition behavior when something bad has happened to a connection. If a
320      * sql command was provided in the properties file, it will run this and attempt to determine
321      * whether the connection is still valid. If it is, it recycles this connection back into the
322      * pool. If it is not, it closes the connection.
323      *
324      * @param entry the connection that had problems
325      * @deprecated - No longer used in the new approach.
326      */

327     public void killConnection(PoolConnEntry entry) {
328         if(entry != null) {
329             // if we were provided SQL to test the connection with, we will use
330
// this and possibly just release the connection after clearing warnings
331
if(verifyConnSQL != null) {
332                 try {
333                     // Test this connection
334
java.sql.Statement JavaDoc stmt = null;
335                     try {
336                         stmt = entry.createStatement();
337                         stmt.execute(verifyConnSQL);
338                     } finally {
339                         try {
340                             if (stmt != null) {
341                                 stmt.close();
342                             }
343                         } catch (SQLException JavaDoc sqle) {
344                             // Failure to close ignored on test connection
345
}
346                     }
347                     // Passed test... recycle the entry
348
entry.unlock();
349                 } catch(SQLException JavaDoc e1) {
350                     // Failed test... close the entry
351
finalizeEntry(entry);
352                 }
353             } else {
354                 // No SQL was provided... we have to kill this entry to be sure
355
finalizeEntry(entry);
356             }
357             return;
358         } else {
359             if (getLogger().isWarnEnabled()) {
360                 getLogger().warn("----> Could not find connection to kill!!!");
361             }
362             return;
363         }
364     }
365
366     /**
367      * Implements the ConnDefinition behavior when a connection is no longer needed. This resets
368      * flags on the wrapper of the connection to allow others to use this connection.
369      *
370      * @param entry
371      */

372     public void releaseConnection(PoolConnEntry entry) {
373         //PoolConnEntry entry = findEntry(conn);
374
if(entry != null) {
375             entry.unlock();
376             return;
377         } else {
378             if (getLogger().isWarnEnabled()) {
379                 getLogger().warn("----> Could not find the connection to free!!!");
380             }
381             return;
382         }
383     }
384
385     /**
386      * Background thread that checks if there are fewer connections open than minConn specifies,
387      * and checks whether connections have been checked out for too long, killing them.
388      */

389     public void run() {
390         try {
391             while(reaperActive) {
392                 synchronized(pool) {
393                     for(int i = 0; i < pool.size(); i++) try {
394                         PoolConnEntry entry = (PoolConnEntry)pool.get(i);
395                         long age = System.currentTimeMillis() - entry.getLastActivity();
396                         synchronized(entry) {
397                             if((entry.getStatus() == PoolConnEntry.ACTIVE) &&
398                                (age > ACTIVE_CONN_HARD_TIME_LIMIT)) {
399                                 StringBuffer JavaDoc logBuffer =
400                                     new StringBuffer JavaDoc(128)
401                                             .append(" ***** connection ")
402                                             .append(entry.getId())
403                                             .append(" is way too old: ")
404                                             .append(age)
405                                             .append(" > ")
406                                             .append(ACTIVE_CONN_HARD_TIME_LIMIT)
407                                             .append(" and will be closed.");
408                                 getLogger().info(logBuffer.toString());
409                                 // This connection is way too old...
410
// kill it no matter what
411
finalizeEntry(entry);
412                                 continue;
413                             }
414                             if((entry.getStatus() == PoolConnEntry.ACTIVE) &&
415                                (age > ACTIVE_CONN_TIME_LIMIT)) {
416                                 StringBuffer JavaDoc logBuffer =
417                                     new StringBuffer JavaDoc(128)
418                                             .append(" ***** connection ")
419                                             .append(entry.getId())
420                                             .append(" is way too old: ")
421                                             .append(age)
422                                             .append(" > ")
423                                             .append(ACTIVE_CONN_TIME_LIMIT);
424                                 getLogger().info(logBuffer.toString());
425                                 // This connection is way too old...
426
// just log it for now.
427
continue;
428                             }
429                             if((entry.getStatus() == PoolConnEntry.AVAILABLE) &&
430                                (age > CONN_IDLE_LIMIT)) {
431                                 //We've got a connection that's too old... kill it
432
finalizeEntry(entry);
433                                 continue;
434                             }
435                         }
436                     }
437                     catch (Throwable JavaDoc ex)
438                     {
439                         StringWriter JavaDoc sout = new StringWriter JavaDoc();
440                         PrintWriter JavaDoc pout = new PrintWriter JavaDoc(sout, true);
441                         pout.println("Reaper Error: ");
442                         ex.printStackTrace(pout);
443                         if (getLogger().isErrorEnabled()) {
444                             getLogger().error(sout.toString());
445                         }
446                     }
447                 }
448                 try {
449                     // Check for activity every 5 seconds
450
Thread.sleep(5000L);
451                 } catch(InterruptedException JavaDoc ex) {
452                 }
453             }
454         } finally {
455             Thread.currentThread().interrupted();
456         }
457     }
458
459     protected void debug(String JavaDoc message) {
460         getLogger().debug(message);
461     }
462
463     protected void info(String JavaDoc message) {
464         getLogger().info(message);
465     }
466
467     /*
468      * This is a real hack, but oh well for now
469      */

470     protected void warn(String JavaDoc message) {
471         getLogger().warn(message);
472     }
473
474     /**
475      * Creates a new connection as per these properties, adds it to the pool, and logs the creation.
476      *
477      * @return PoolConnEntry the new connection wrapped as an entry
478      * @throws SQLException
479      */

480     private PoolConnEntry createConn() throws SQLException JavaDoc {
481         PoolConnEntry entry = null;
482         synchronized(pool) {
483             if(connCreationsInProgress > 0) {
484                 //We are already creating one in another place
485
return null;
486             }
487             long now = System.currentTimeMillis();
488             if((now - connLastCreated) < (1000 * pool.size())) {
489                 //We don't want to scale up too quickly...
490
if(DEEP_DEBUG) {
491                     System.err.println("We don't want to scale up too quickly");
492                 }
493                 return null;
494             }
495             if((maxConn == 0) || (pool.size() < maxConn)) {
496                 connCreationsInProgress++;
497                 connLastCreated = now;
498             } else {
499                 // We've already hit a limit... fail silently
500
if (getLogger().isDebugEnabled())
501                 {
502                     StringBuffer JavaDoc logBuffer =
503                         new StringBuffer JavaDoc(128)
504                                 .append("Connection limit hit... ")
505                                 .append(pool.size())
506                                 .append(" in pool and ")
507                                 .append(connCreationsInProgress)
508                                 .append(" + on the way.");
509                     getLogger().debug(logBuffer.toString());
510                 }
511                 return null;
512             }
513             try {
514                 entry = new PoolConnEntry(this,
515                                           java.sql.DriverManager.getConnection(jdbcURL, jdbcUsername,
516                                                                                jdbcPassword),
517                                           ++connectionCount);
518                 if (getLogger().isDebugEnabled())
519                 {
520                     getLogger().debug("Opening connection " + entry);
521                 }
522                 entry.lock();
523                 pool.add(entry);
524                 return entry;
525             } catch(SQLException JavaDoc sqle) {
526                 //Shouldn't ever happen, but it did, just return null.
527
// Exception from DriverManager.getConnection() - log it, and return null
528
StringWriter JavaDoc sout = new StringWriter JavaDoc();
529                 PrintWriter JavaDoc pout = new PrintWriter JavaDoc(sout, true);
530                 pout.println("Error creating connection: ");
531                 sqle.printStackTrace(pout);
532                 if (getLogger().isErrorEnabled()) {
533                     getLogger().error(sout.toString());
534                 }
535                 return null;
536             } finally {
537                     connCreationsInProgress--;
538             }
539         }
540     }
541
542     /**
543      * Closes a connection and removes it from the pool.
544      *
545      * @param entry entry
546      */

547     private void finalizeEntry(PoolConnEntry entry) {
548         synchronized(pool) {
549             try {
550                 entry.finalize();
551             } catch(Exception JavaDoc fe) {
552             }
553             pool.remove(entry);
554         }
555     }
556 }
557
Popular Tags