Source code: org/roller/util/ConnectionPool.java
1
2 package org.roller.util;
3
4 import java.sql.*;
5 import java.util.*;
6
7 /** A class for preallocating, recycling, and managing
8 * JDBC connections.
9 * <P>
10 * Taken from Core Servlets and JavaServer Pages
11 * from Prentice Hall and Sun Microsystems Press,
12 * http://www.coreservlets.com/.
13 * © 2000 Marty Hall; may be freely used or adapted.
14 */
15
16 public class ConnectionPool implements Runnable {
17 private String driver, url, username, password;
18 private int maxConnections;
19 private boolean waitIfBusy;
20 private Vector availableConnections, busyConnections;
21 private boolean connectionPending = false;
22
23 public ConnectionPool(String driver, String url,
24 String username, String password,
25 int initialConnections,
26 int maxConnections,
27 boolean waitIfBusy)
28 throws SQLException {
29 this.driver = driver;
30 this.url = url;
31 this.username = username;
32 this.password = password;
33 this.maxConnections = maxConnections;
34 this.waitIfBusy = waitIfBusy;
35 if (initialConnections > maxConnections) {
36 initialConnections = maxConnections;
37 }
38 availableConnections = new Vector(initialConnections);
39 busyConnections = new Vector();
40 for(int i=0; i<initialConnections; i++) {
41 availableConnections.addElement(makeNewConnection());
42 }
43 }
44
45 public synchronized Connection getConnection()
46 throws SQLException {
47 if (!availableConnections.isEmpty()) {
48 Connection existingConnection =
49 (Connection)availableConnections.lastElement();
50 int lastIndex = availableConnections.size() - 1;
51 availableConnections.removeElementAt(lastIndex);
52 // If connection on available list is closed (e.g.,
53 // it timed out), then remove it from available list
54 // and repeat the process of obtaining a connection.
55 // Also wake up threads that were waiting for a
56 // connection because maxConnection limit was reached.
57 if (existingConnection.isClosed()) {
58 notifyAll(); // Freed up a spot for anybody waiting
59 return(getConnection());
60 } else {
61 busyConnections.addElement(existingConnection);
62 return(existingConnection);
63 }
64 } else {
65
66 // Three possible cases:
67 // 1) You haven't reached maxConnections limit. So
68 // establish one in the background if there isn't
69 // already one pending, then wait for
70 // the next available connection (whether or not
71 // it was the newly established one).
72 // 2) You reached maxConnections limit and waitIfBusy
73 // flag is false. Throw SQLException in such a case.
74 // 3) You reached maxConnections limit and waitIfBusy
75 // flag is true. Then do the same thing as in second
76 // part of step 1: wait for next available connection.
77
78 if ((totalConnections() < maxConnections) &&
79 !connectionPending) {
80 makeBackgroundConnection();
81 } else if (!waitIfBusy) {
82 throw new SQLException("Connection limit reached");
83 }
84 // Wait for either a new connection to be established
85 // (if you called makeBackgroundConnection) or for
86 // an existing connection to be freed up.
87 try {
88 wait();
89 } catch(InterruptedException ie) {}
90 // Someone freed up a connection, so try again.
91 return(getConnection());
92 }
93 }
94
95 // You can't just make a new connection in the foreground
96 // when none are available, since this can take several
97 // seconds with a slow network connection. Instead,
98 // start a thread that establishes a new connection,
99 // then wait. You get woken up either when the new connection
100 // is established or if someone finishes with an existing
101 // connection.
102
103 private void makeBackgroundConnection() {
104 connectionPending = true;
105 try {
106 Thread connectThread = new Thread(this);
107 connectThread.start();
108 } catch(OutOfMemoryError oome) {
109 // Give up on new connection
110 }
111 }
112
113 public void run() {
114 try {
115 Connection connection = makeNewConnection();
116 synchronized(this) {
117 availableConnections.addElement(connection);
118 connectionPending = false;
119 notifyAll();
120 }
121 } catch(Exception e) { // SQLException or OutOfMemory
122 // Give up on new connection and wait for existing one
123 // to free up.
124 }
125 }
126
127 // This explicitly makes a new connection. Called in
128 // the foreground when initializing the ConnectionPool,
129 // and called in the background when running.
130
131 private Connection makeNewConnection()
132 throws SQLException {
133 try {
134 // Load database driver if not already loaded
135 Class.forName(driver);
136 // Establish network connection to database
137 Connection connection =
138 DriverManager.getConnection(url, username, password);
139 return(connection);
140 } catch(ClassNotFoundException cnfe) {
141 // Simplify try/catch blocks of people using this by
142 // throwing only one exception type.
143 throw new SQLException("Can't find class for driver: " +
144 driver);
145 }
146 }
147
148 public synchronized void free(Connection connection) {
149 busyConnections.removeElement(connection);
150 availableConnections.addElement(connection);
151 // Wake up threads that are waiting for a connection
152 notifyAll();
153 }
154
155 public synchronized int totalConnections() {
156 return(availableConnections.size() +
157 busyConnections.size());
158 }
159
160 /** Close all the connections. Use with caution:
161 * be sure no connections are in use before
162 * calling. Note that you are not <I>required</I> to
163 * call this when done with a ConnectionPool, since
164 * connections are guaranteed to be closed when
165 * garbage collected. But this method gives more control
166 * regarding when the connections are closed.
167 */
168
169 public synchronized void closeAllConnections() {
170 closeConnections(availableConnections);
171 availableConnections = new Vector();
172 closeConnections(busyConnections);
173 busyConnections = new Vector();
174 }
175
176 private void closeConnections(Vector connections) {
177 try {
178 for(int i=0; i<connections.size(); i++) {
179 Connection connection =
180 (Connection)connections.elementAt(i);
181 if (!connection.isClosed()) {
182 connection.close();
183 }
184 }
185 } catch(SQLException sqle) {
186 // Ignore errors; garbage collect anyhow
187 }
188 }
189
190 public synchronized String toString() {
191 String info =
192 "ConnectionPool(" + url + "," + username + ")" +
193 ", available=" + availableConnections.size() +
194 ", busy=" + busyConnections.size() +
195 ", max=" + maxConnections;
196 return(info);
197 }
198 }
199