Source code: edu/emory/mathcs/util/net/ConnectionPool.java
1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 *
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
8 *
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
13 *
14 * The Original Code is the Emory Utilities.
15 *
16 * The Initial Developer of the Original Code is
17 * The Distributed Computing Laboratory, Emory University.
18 * Portions created by the Initial Developer are Copyright (C) 2002
19 * the Initial Developer. All Rights Reserved.
20 *
21 * Alternatively, the contents of this file may be used under the terms of
22 * either the GNU General Public License Version 2 or later (the "GPL"), or
23 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
24 * in which case the provisions of the GPL or the LGPL are applicable instead
25 * of those above. If you wish to allow use of your version of this file only
26 * under the terms of either the GPL or the LGPL, and not to allow others to
27 * use your version of this file under the terms of the MPL, indicate your
28 * decision by deleting the provisions above and replace them with the notice
29 * and other provisions required by the GPL or the LGPL. If you do not delete
30 * the provisions above, a recipient may use your version of this file under
31 * the terms of any one of the MPL, the GPL or the LGPL.
32 *
33 * ***** END LICENSE BLOCK ***** */
34
35 package edu.emory.mathcs.util.net;
36
37 import java.io.IOException;
38 import java.net.Socket;
39 import java.rmi.server.RMIClientSocketFactory;
40 import java.util.HashSet;
41 import java.util.Iterator;
42
43 /**
44 * Manages a pool of socket connections to a single network endpoint.
45 * Pooling enables reusing connections for multiple, unrelated data transfers,
46 * and it can be used to implement certain connection-based protocols like
47 * HTTP 1.1. Additionally, pooling can aid in controlling network load -
48 * limiting the maximum pool size causes excessive connection requests to
49 * be enqueued at the client side.
50 * <p>
51 * The endpoint is represented by a host name and a port number, as well as
52 * by an optional client socket factory, specified at
53 * the construction time. Client requests connections, use them, then return
54 * them to the pool. Clients should not close the socket associated with
55 * the connection or use the socket after returning connection to the pool.
56 * Upon a request for connection, the pool first tries to
57 * return a pre-existing idle one, creating a new connection only if none
58 * is available.
59 * Request may block if pool size limit is reached and all connections are in
60 * use. After being returned to the pool, if connection idles for longer than
61 * its expiration timeout, it is closed.
62 * <p>
63 * Example:
64 *
65 * <pre>
66 * ConnectionPool pool = new ConnectionPool(host, port);
67 * ...
68 * Connection conn = pool.getConnection();
69 * Socket socket = conn.getSocket();
70 * try {
71 * socket.getOutputStream().write(0x00);
72 * ...
73 * conn.returnToPool();
74 * }
75 * catch (IOException e) {
76 * conn.close();
77 * }
78 * </pre>
79 *
80 * @author Dawid Kurzyniec
81 * @author Tomasz Janiak
82 * @version 1.0
83 */
84 public class ConnectionPool {
85 static final long DEFAULT_EXPIRATION_TIMEOUT = 15000;
86 static final int DEFAULT_CAPACITY = 10;
87 private final HashSet connections = new HashSet();
88 private final String hostName;
89 private final int port;
90 private final RMIClientSocketFactory socketFactory;
91 final long expirationTimeout;
92 private final int capacity;
93
94 /**
95 * Creates a connection pool for a specified endpoint, using a default
96 * TCP/IP socket factory, a default expiration timeout of 15 s, and a
97 * default capacity of 10 connections.
98 *
99 * @param hostName remote host name
100 * @param port remote port
101 * @param socketFactory socket factory to use when creating new connections
102 */
103 public ConnectionPool(String hostName, int port) {
104 this(hostName, port, null);
105 }
106
107 /**
108 * Creates a connection pool for a specified endpoint, using specified
109 * socket factory and a default expiration timeout of 15 s and a
110 * default capacity of 10 connections.
111 *
112 * @param hostName remote host name
113 * @param port remote port
114 * @param socketFactory socket factory to use when creating new connections
115 */
116 public ConnectionPool(String hostName, int port,
117 RMIClientSocketFactory socketFactory)
118 {
119 this(hostName, port, socketFactory,
120 DEFAULT_EXPIRATION_TIMEOUT, DEFAULT_CAPACITY);
121 }
122
123 /**
124 * Creates a connection pool for a specified endpoint, using specified
125 * expiration timeout and capacity and a default TCP/IP socket factory.
126 *
127 * @param hostName remote host name
128 * @param port remote port
129 * @param expirationTimeout maximum connection idle time
130 * @param capacity maximum number of active connections
131 */
132 public ConnectionPool(String hostName, int port,
133 long expirationTimeout, int capacity)
134 {
135 this(hostName, port, null, expirationTimeout, capacity);
136 }
137
138 /**
139 * Creates a connection pool for a specified endpoint, using specified
140 * socket factory, expiration timeout, and capacity.
141 *
142 * @param hostName remote host name
143 * @param port remote port
144 * @param socketFactory socket factory to use when creating new connections
145 * @param expirationTimeout maximum connection idle time
146 * @param capacity maximum number of active connections
147 */
148 public ConnectionPool(
149 String hostName,
150 int port,
151 RMIClientSocketFactory socketFactory,
152 long expirationTimeout,
153 int capacity)
154 {
155 if (capacity <= 0 || expirationTimeout < 0) {
156 throw new IllegalArgumentException();
157 }
158 this.hostName = hostName;
159 this.port = port;
160 this.socketFactory = socketFactory;
161 this.expirationTimeout = expirationTimeout;
162 this.capacity = capacity;
163 }
164
165 private Connection findConnection() {
166 Connection result = null;
167 for (Iterator iter = connections.iterator(); iter.hasNext();) {
168 Connection conn = (Connection)iter.next();
169 byte connStatus = conn.acquire();
170 if (connStatus == Connection.READY) {
171 result = conn;
172 break;
173 } else if (connStatus == Connection.CLOSED) {
174 iter.remove();
175 }
176 }
177 return result;
178 }
179
180 synchronized void notifyConnectionStateChanged() {
181 notify();
182 }
183
184 /**
185 * Requests a connection from the pool. If an existing idle connection is
186 * found, it is returned. Otherwise, if pool capacity has not been reached,
187 * new connection is created. Otherwise, the operation blocks until
188 * a connection is available.
189 *
190 * @return a connection
191 * @throws IOException if I/O error occurs
192 * @throws InterruptedException if interrupted while waiting for
193 * a connection
194 */
195 public synchronized Connection getConnection()
196 throws IOException, InterruptedException
197 {
198 // make sure that connection pooling does not circumvent security
199 // policy by allowing unauthorized clients to use network sockets
200 checkConnectPermission();
201
202 Connection conn = findConnection();
203 while (conn == null) {
204 if (connections.size() == capacity) {
205 wait();
206 conn = findConnection();
207 } else {
208 Socket socket = socketFactory != null
209 ? socketFactory.createSocket(hostName, port)
210 : new Socket(hostName, port);
211 conn = new Connection(socket, this);
212 connections.add(conn);
213 }
214 }
215 return conn;
216 }
217
218 private void checkConnectPermission() {
219 SecurityManager security = System.getSecurityManager();
220 if (security != null) {
221 security.checkConnect(hostName, port);
222 }
223 }
224 }