KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > winstone > jndi > resourceFactories > WinstoneDataSource


1 /*
2  * Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net>
3  * Distributed under the terms of either:
4  * - the common development and distribution license (CDDL), v1.0; or
5  * - the GNU Lesser General Public License, v2.1 or later
6  */

7 package winstone.jndi.resourceFactories;
8
9 import java.io.PrintWriter JavaDoc;
10 import java.sql.Connection JavaDoc;
11 import java.sql.Driver JavaDoc;
12 import java.sql.PreparedStatement JavaDoc;
13 import java.sql.SQLException JavaDoc;
14 import java.util.ArrayList JavaDoc;
15 import java.util.Iterator JavaDoc;
16 import java.util.List JavaDoc;
17 import java.util.Map JavaDoc;
18 import java.util.Properties JavaDoc;
19
20 import javax.sql.DataSource JavaDoc;
21
22 import winstone.Logger;
23 import winstone.WebAppConfiguration;
24 import winstone.WinstoneResourceBundle;
25
26 /**
27  * Implements a JDBC 2.0 pooling datasource. This is meant to act as a wrapper
28  * around a JDBC 1.0 driver, just providing the pool management functions.
29  *
30  * Supports keep alives, and check-connection-before-get options, as well
31  * as normal reclaimable pool management options like maxIdle, maxConnections and
32  * startConnections. Additionally it supports poll-retry on full, which means the
33  * getConnection call will block and retry after a certain period when the pool
34  * is maxed out (good for high load conditions).
35  *
36  * This class was originally drawn from the generator-runtime servlet framework and
37  * modified to make it more JDBC-API only compliant.
38  *
39  * @author <a HREF="mailto:rick_knowles@hotmail.com">Rick Knowles</a>
40  * @version $Id: WinstoneDataSource.java,v 1.7 2006/03/04 05:47:15 rickknowles Exp $
41  */

42 public class WinstoneDataSource implements DataSource JavaDoc, Runnable JavaDoc {
43     
44     public static final WinstoneResourceBundle DS_RESOURCES =
45         new WinstoneResourceBundle("winstone.jndi.resourceFactories.LocalStrings");
46     
47     private String JavaDoc name;
48     
49     private String JavaDoc url;
50     private Driver JavaDoc driver;
51     private Properties JavaDoc connectProps;
52     
53     private int maxIdleCount;
54     private int maxHeldCount;
55     private int retryCount;
56     private int retryPeriod;
57     
58     private String JavaDoc keepAliveSQL;
59     private int keepAlivePeriod;
60     private boolean checkBeforeGet;
61     private int killInactivePeriod;
62     
63     private List JavaDoc usedWrappers;
64     private List JavaDoc unusedRealConnections; // sempahore
65
private List JavaDoc usedRealConnections;
66     
67     private Thread JavaDoc managementThread;
68
69     private int loginTimeout;
70     private PrintWriter JavaDoc logWriter;
71     
72     /**
73      * Main constructor. Basically just calls the init method
74      */

75     public WinstoneDataSource(String JavaDoc name, Map JavaDoc args, ClassLoader JavaDoc loader) {
76         this.name = name;
77         
78         this.usedWrappers = new ArrayList JavaDoc();
79         this.usedRealConnections = new ArrayList JavaDoc();
80         this.unusedRealConnections = new ArrayList JavaDoc();
81         this.connectProps = new Properties JavaDoc();
82
83         // Extract pool management properties
84
this.keepAliveSQL = WebAppConfiguration.stringArg(args, "keepAliveSQL", "");
85         this.keepAlivePeriod = WebAppConfiguration.intArg(args, "keepAlivePeriod", -1);
86         this.checkBeforeGet = WebAppConfiguration.booleanArg(args, "checkBeforeGet",
87                 !this.keepAliveSQL.equals(""));
88         this.killInactivePeriod = WebAppConfiguration.intArg(args, "killInactivePeriod", -1);
89
90         this.url = WebAppConfiguration.stringArg(args, "url", null);
91         String JavaDoc driverClassName = WebAppConfiguration.stringArg(args, "driverClassName", "");
92         if (args.get("username") != null)
93             this.connectProps.put("user", args.get("username"));
94         if (args.get("password") != null)
95             this.connectProps.put("password", args.get("password"));
96
97         this.maxHeldCount = WebAppConfiguration.intArg(args, "maxConnections", 20);
98         this.maxIdleCount = WebAppConfiguration.intArg(args, "maxIdle", 10);
99         int startCount = WebAppConfiguration.intArg(args, "startConnections", 1);
100         
101         this.retryCount = WebAppConfiguration.intArg(args, "retryCount", 1);
102         this.retryPeriod = WebAppConfiguration.intArg(args, "retryPeriod", 1000);
103
104         log(Logger.FULL_DEBUG, "DBConnectionPool.Init", this.url, null);
105
106         try {
107             synchronized (this.unusedRealConnections) {
108                 if (!driverClassName.equals("")) {
109                     Class JavaDoc driverClass = Class.forName(driverClassName.trim(), true, loader);
110                     this.driver = (Driver JavaDoc) driverClass.newInstance();
111
112                     for (int n = 0; n < startCount; n++) {
113                         makeNewRealConnection(this.unusedRealConnections);
114                     }
115                 }
116             }
117         } catch (Throwable JavaDoc err) {
118             log(Logger.ERROR, "DBConnectionPool.ErrorInCreate", this.name, err);
119         }
120
121         // Start management thread
122
this.managementThread = new Thread JavaDoc(this, "DBConnectionPool management thread");
123         this.managementThread.start();
124     }
125
126     /**
127      * Close this pool - probably because we will want to re-init the pool
128      */

129     public void destroy() {
130         if (this.managementThread != null) {
131             this.managementThread.interrupt();
132             this.managementThread = null;
133         }
134
135         synchronized (this.unusedRealConnections) {
136             killPooledConnections(this.unusedRealConnections, 0);
137             killPooledConnections(this.usedRealConnections, 0);
138         }
139         
140         this.usedRealConnections.clear();
141         this.unusedRealConnections.clear();
142         this.usedWrappers.clear();
143     }
144
145     /**
146      * Gets a connection with a specific username/password. These are not pooled.
147      */

148     public Connection JavaDoc getConnection(String JavaDoc username, String JavaDoc password)
149             throws SQLException JavaDoc {
150         Properties JavaDoc newProps = new Properties JavaDoc();
151         newProps.put("user", username);
152         newProps.put("password", password);
153         Connection JavaDoc conn = this.driver.connect(this.url, newProps);
154         WinstoneConnection wrapper = null;
155         synchronized (this.unusedRealConnections) {
156             wrapper = new WinstoneConnection(conn, this);
157             this.usedWrappers.add(wrapper);
158         }
159         return wrapper;
160     }
161
162     public Connection JavaDoc getConnection() throws SQLException JavaDoc {
163         return getConnection(this.retryCount);
164     }
165     
166     /**
167      * Get a read-write connection - preferably from the pool, but fresh if needed
168      */

169     protected Connection JavaDoc getConnection(int retriesAllowed) throws SQLException JavaDoc {
170         Connection JavaDoc realConnection = null;
171         
172         synchronized (this.unusedRealConnections) {
173             // If we have any spare, get it from the unused pool
174
if (this.unusedRealConnections.size() > 0) {
175                 realConnection = (Connection JavaDoc) this.unusedRealConnections.get(0);
176                 this.unusedRealConnections.remove(realConnection);
177                 this.usedRealConnections.add(realConnection);
178                 log(Logger.FULL_DEBUG, "DBConnectionPool.UsingPooled",
179                         new String JavaDoc[] {"" + this.usedRealConnections.size(),
180                            "" + this.unusedRealConnections.size()}, null);
181                 try {
182                     return prepareWrapper(realConnection);
183                 } catch (SQLException JavaDoc err) {
184                     // Leave the realConnection as non-null, so we know prepareWrapper failed
185
}
186             }
187
188             // If we are out (and not over our limit), allocate a new one
189
else if (this.usedRealConnections.size() < maxHeldCount) {
190                 realConnection = makeNewRealConnection(this.usedRealConnections);
191                 log(Logger.FULL_DEBUG, "DBConnectionPool.UsingNew",
192                         new String JavaDoc[] {"" + this.usedRealConnections.size(),
193                            "" + this.unusedRealConnections.size()}, null);
194                 try {
195                     return prepareWrapper(realConnection);
196                 } catch (SQLException JavaDoc err) {
197                     // Leave the realConnection as non-null, so we know prepareWrapper failed
198
}
199             }
200         }
201         
202         if (realConnection != null) {
203             // prepareWrapper() must have failed, so call this method again
204
realConnection = null;
205             return getConnection(retriesAllowed);
206         } else if (retriesAllowed <= 0) {
207             // otherwise throw fail message - we've blown our limit
208
throw new SQLException JavaDoc(DS_RESOURCES.getString("DBConnectionPool.Exceeded", "" + maxHeldCount));
209         } else {
210             log(Logger.FULL_DEBUG, "DBConnectionPool.Retrying", new String JavaDoc[] {
211                     "" + maxHeldCount, "" + retriesAllowed, "" + retryPeriod}, null);
212             
213             // If we are here, it's because we need to retry for a connection
214
try {
215                 Thread.sleep(retryPeriod);
216             } catch (InterruptedException JavaDoc err) {}
217             return getConnection(retriesAllowed - 1);
218         }
219     }
220
221     private Connection JavaDoc prepareWrapper(Connection JavaDoc realConnection) throws SQLException JavaDoc {
222         // Check before get()
223
if (this.checkBeforeGet) {
224             try {
225                 executeKeepAlive(realConnection);
226             } catch (SQLException JavaDoc err) {
227                 // Dead connection, kill it and try again
228
killConnection(this.usedRealConnections, realConnection);
229                 throw err;
230             }
231         }
232         realConnection.setAutoCommit(false);
233         WinstoneConnection wrapper = new WinstoneConnection(realConnection, this);
234         this.usedWrappers.add(wrapper);
235         return wrapper;
236     }
237     
238     /**
239      * Releases connections back to the pool
240      */

241     void releaseConnection(WinstoneConnection wrapper, Connection JavaDoc realConnection) throws SQLException JavaDoc {
242         synchronized (this.unusedRealConnections) {
243             if (wrapper != null) {
244                 this.usedWrappers.remove(wrapper);
245             }
246             if (realConnection != null) {
247                 if (this.usedRealConnections.contains(realConnection)) {
248                     this.usedRealConnections.remove(realConnection);
249                     this.unusedRealConnections.add(realConnection);
250                     log(Logger.FULL_DEBUG, "DBConnectionPool.Releasing",
251                             new String JavaDoc[] {"" + this.usedRealConnections.size(),
252                                "" + this.unusedRealConnections.size()}, null);
253                 } else {
254                     log(Logger.WARNING, "DBConnectionPool.ReleasingUnknown", null);
255                     realConnection.close();
256                 }
257             }
258         }
259     }
260
261     public int getLoginTimeout() {
262         return this.loginTimeout;
263     }
264
265     public PrintWriter JavaDoc getLogWriter() {
266         return this.logWriter;
267     }
268
269     public void setLoginTimeout(int timeout) {
270         this.loginTimeout = timeout;
271     }
272
273     public void setLogWriter(PrintWriter JavaDoc writer) {
274         this.logWriter = writer;
275     }
276
277     /**
278      * Clean up and keep-alive thread.
279      * Note - this needs a lot more attention to the semaphore use during keepAlive etc
280      */

281     public void run() {
282         log(Logger.DEBUG, "DBConnectionPool.MaintenanceStart", null);
283
284         int keepAliveCounter = -1;
285         int killInactiveCounter = -1;
286         boolean threadRunning = true;
287
288         while (threadRunning) {
289             try {
290                 long startTime = System.currentTimeMillis();
291
292                 // Keep alive if the time is right
293
if ((this.keepAlivePeriod != -1) && threadRunning) {
294                     keepAliveCounter++;
295
296                     if (this.keepAlivePeriod <= keepAliveCounter) {
297                         synchronized (this.unusedRealConnections) {
298                             executeKeepAliveOnUnused();
299                         }
300                         keepAliveCounter = 0;
301                     }
302                 }
303                 
304                 if (Thread.interrupted()) {
305                     threadRunning = false;
306                 }
307
308                 // Kill inactive connections if the time is right
309
if ((this.killInactivePeriod != -1) && threadRunning) {
310                     killInactiveCounter++;
311
312                     if (this.killInactivePeriod <= killInactiveCounter) {
313                         synchronized (this.unusedRealConnections) {
314                             killPooledConnections(this.unusedRealConnections, this.maxIdleCount);
315                         }
316
317                         killInactiveCounter = 0;
318                     }
319                 }
320
321                 if ((killInactiveCounter == 0) || (keepAliveCounter == 0)) {
322                     log(Logger.FULL_DEBUG, "DBConnectionPool.MaintenanceTime",
323                             "" + (System.currentTimeMillis() - startTime), null);
324                 }
325
326                 if (Thread.interrupted()) {
327                     threadRunning = false;
328                 } else {
329                     Thread.sleep(60000); // sleep 1 minute
330
}
331                 
332                 if (Thread.interrupted()) {
333                     threadRunning = false;
334                 }
335             } catch (InterruptedException JavaDoc err) {
336                 threadRunning = false;
337                 continue;
338             }
339         }
340
341         log(Logger.DEBUG, "DBConnectionPool.MaintenanceFinish", null);
342     }
343
344     /**
345      * Executes keep alive for each of the connections in the supplied pool
346      */

347     protected void executeKeepAliveOnUnused() {
348         // keep alive all unused roConns now
349
List JavaDoc dead = new ArrayList JavaDoc();
350         
351         for (Iterator JavaDoc i = this.unusedRealConnections.iterator(); i.hasNext();) {
352             Connection JavaDoc conn = (Connection JavaDoc) i.next();
353
354             try {
355                 executeKeepAlive(conn);
356             } catch (SQLException JavaDoc errSQL) {
357                 dead.add(conn);
358             }
359         }
360         
361         for (Iterator JavaDoc i = dead.iterator(); i.hasNext(); ) {
362             killConnection(this.unusedRealConnections, (Connection JavaDoc) i.next());
363         }
364         
365         log(Logger.FULL_DEBUG, "DBConnectionPool.KeepAliveFinished", "" +
366                 this.unusedRealConnections.size(), null);
367     }
368
369     protected void executeKeepAlive(Connection JavaDoc connection) throws SQLException JavaDoc {
370         if (!this.keepAliveSQL.equals("")) {
371             PreparedStatement JavaDoc qryKeepAlive = null;
372             try {
373                 qryKeepAlive = connection.prepareStatement(keepAliveSQL);
374                 qryKeepAlive.execute();
375             } catch (SQLException JavaDoc err) {
376                 log(Logger.WARNING, "DBConnectionPool.KeepAliveFailed", err);
377                 throw err;
378             } finally {
379                 if (qryKeepAlive != null) {
380                     qryKeepAlive.close();
381                 }
382             }
383         }
384     }
385
386     /**
387      * This makes a new rw connection. It assumes that the synchronization has taken
388      * place in the calling code, so is unsafe for use outside this class.
389      */

390     protected Connection JavaDoc makeNewRealConnection(List JavaDoc pool) throws SQLException JavaDoc {
391         if (this.url == null) {
392             throw new SQLException JavaDoc("No JDBC URL supplied");
393         }
394
395         Connection JavaDoc realConnection = this.driver.connect(this.url, this.connectProps);
396         pool.add(realConnection);
397         log(Logger.FULL_DEBUG, "DBConnectionPool.AddingNew",
398                 new String JavaDoc[] {"" + this.usedRealConnections.size(),
399                 "" + this.unusedRealConnections.size()}, null);
400
401         return realConnection;
402     }
403
404     /**
405      * Iterates through a list and kills off unused connections until we reach the
406      * minimum idle count for that pool.
407      */

408     protected void killPooledConnections(List JavaDoc pool, int maxIdleCount) {
409         // kill inactive unused roConns now
410
int killed = 0;
411
412         while (pool.size() > maxIdleCount) {
413             killed++;
414             Connection JavaDoc conn = (Connection JavaDoc) pool.get(0);
415             killConnection(pool, conn);
416         }
417
418         if (killed > 0) {
419             log(Logger.FULL_DEBUG, "DBConnectionPool.Killed", "" + killed, null);
420         }
421     }
422     
423     private static void killConnection(List JavaDoc pool, Connection JavaDoc conn) {
424         pool.remove(conn);
425         try {
426             conn.close();
427         } catch (SQLException JavaDoc err) {
428         }
429     }
430     
431     private void log(int level, String JavaDoc msgKey, Throwable JavaDoc err) {
432         if (getLogWriter() != null) {
433             getLogWriter().println(DS_RESOURCES.getString(msgKey));
434             if (err != null) {
435                 err.printStackTrace(getLogWriter());
436             }
437         } else {
438             Logger.log(level, DS_RESOURCES, msgKey, err);
439         }
440     }
441     
442     private void log(int level, String JavaDoc msgKey, String JavaDoc arg, Throwable JavaDoc err) {
443         if (getLogWriter() != null) {
444             getLogWriter().println(DS_RESOURCES.getString(msgKey, arg));
445             if (err != null) {
446                 err.printStackTrace(getLogWriter());
447             }
448         } else {
449             Logger.log(level, DS_RESOURCES, msgKey, arg, err);
450         }
451     }
452     
453     private void log(int level, String JavaDoc msgKey, String JavaDoc arg[], Throwable JavaDoc err) {
454         if (getLogWriter() != null) {
455             getLogWriter().println(DS_RESOURCES.getString(msgKey, arg));
456             if (err != null) {
457                 err.printStackTrace(getLogWriter());
458             }
459         } else {
460             Logger.log(level, DS_RESOURCES, msgKey, arg, err);
461         }
462     }
463 }
464
Popular Tags