1 /***
2 * Redistribution and use of this software and associated documentation
3 * ("Software"), with or without modification, are permitted provided
4 * that the following conditions are met:
5 *
6 * 1. Redistributions of source code must retain copyright
7 * statements and notices. Redistributions must also contain a
8 * copy of this document.
9 *
10 * 2. Redistributions in binary form must reproduce the
11 * above copyright notice, this list of conditions and the
12 * following disclaimer in the documentation and/or other
13 * materials provided with the distribution.
14 *
15 * 3. The name "Exolab" must not be used to endorse or promote
16 * products derived from this Software without prior written
17 * permission of Exoffice Technologies. For written permission,
18 * please contact info@exolab.org.
19 *
20 * 4. Products derived from this Software may not be called "Exolab"
21 * nor may "Exolab" appear in their names without prior written
22 * permission of Exoffice Technologies. Exolab is a registered
23 * trademark of Exoffice Technologies.
24 *
25 * 5. Due credit should be given to the Exolab Project
26 * (http://www.exolab.org/).
27 *
28 * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS
29 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
32 * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39 * OF THE POSSIBILITY OF SUCH DAMAGE.
40 *
41 * Copyright 2004-2006 (C) Exoffice Technologies Inc. All Rights Reserved.
42 *
43 * $Id: SocketManager.java,v 1.1 2006/01/11 13:24:05 tanderson Exp $
44 */
45 package org.exolab.jms.net.tunnel;
46
47 import java.io.IOException;
48 import java.net.Socket;
49 import java.rmi.server.ObjID;
50 import java.util.HashMap;
51 import java.util.Map;
52
53 import EDU.oswego.cs.dl.util.concurrent.ClockDaemon;
54 import EDU.oswego.cs.dl.util.concurrent.ThreadFactory;
55 import org.apache.log4j.Logger;
56
57
58 /***
59 * Manages connections for {@link TunnelServlet}.
60 *
61 * @author <a href="mailto:tma@netspace.net.au">Tim Anderson</a>
62 * @version $Revision: 1.1 $ $Date: 2006/01/11 13:24:05 $
63 */
64 class SocketManager {
65
66 /***
67 * Clock daemon for periodically running the reaper.
68 */
69 private ClockDaemon _daemon;
70
71 /***
72 * A map of <code>SocketInfo</code>, keyed on identifier.
73 */
74 private Map _sockets = new HashMap();
75
76 /***
77 * The logger.
78 */
79 private static final Logger _log = Logger.getLogger(SocketManager.class);
80
81 /***
82 * Reap thread synchronization helper.
83 */
84 private final Object _reapLock = new Object();
85
86 /***
87 * The maximum period that a connection may be idle before it is reaped, in
88 * milliseconds. If <code>0</code> indicates not to reap connections.
89 */
90 private long _idlePeriod = 30 * 1000;
91
92
93 /***
94 * Create a new socket.
95 *
96 * @param host the host to connect to
97 * @param port the port to connect to
98 * @return the identifier of the new endpoint
99 * @throws IOException for any I/O error
100 */
101 public synchronized String open(String host, int port) throws IOException {
102 Socket socket = new Socket(host, port);
103 String id = new ObjID().toString();
104 SocketInfo result = new SocketInfo(id, socket);
105 _sockets.put(id, result);
106 startReaper();
107 return id;
108 }
109
110 /***
111 * Returns a socket given its identifier.
112 *
113 * @param id the endpoint identifier
114 * @return the endpoint corresponding to <code>id</code> or
115 * <code>null</code>
116 */
117 public synchronized Socket getSocket(String id) {
118 Socket result = null;
119 synchronized (_reapLock) {
120 SocketInfo info = getSocketInfo(id);
121 if (info != null) {
122 info.setUsed();
123 result = info.getSocket();
124 }
125 }
126 return result;
127 }
128
129 /***
130 * Close a connection given its identifier.
131 *
132 * @param id the connection identifier
133 * @throws IOException for any I/O error
134 */
135 public synchronized void close(String id) throws IOException {
136 Socket socket = getSocket(id);
137 if (socket != null) {
138 try {
139 socket.close();
140 } finally {
141 _sockets.remove(id);
142 if (_sockets.isEmpty()) {
143 stopReaper();
144 }
145 }
146 }
147 }
148
149 /***
150 * Sets the the maximum period that a connection may be idle before it is
151 * reaped, in seconds.
152 *
153 * @param period the idle period, in seconds
154 */
155 public void setIdlePeriod(int period) {
156 _idlePeriod = period * 1000;
157 }
158
159 /***
160 * Returns a {@link SocketInfo} given its identifier.
161 *
162 * @param id the endpoint identifier
163 * @return the connection corresponding to <code>id</code> or
164 * <code>null</code> if none exists
165 */
166 protected SocketInfo getSocketInfo(String id) {
167 return (SocketInfo) _sockets.get(id);
168 }
169
170
171 /***
172 * Reap idle connections.
173 */
174 private void reapIdleConnections() {
175 Map.Entry[] entries;
176 synchronized (this) {
177 entries = (Map.Entry[]) _sockets.entrySet().toArray(new Map.Entry[0]);
178 }
179 synchronized (_reapLock) {
180 for (int i = 0; i < entries.length && !stopReaping(); ++i) {
181 Map.Entry entry = entries[i];
182 SocketInfo info = (SocketInfo) entry.getValue();
183 long current = System.currentTimeMillis();
184 long unused = current - info.getUsed();
185 if (unused > _idlePeriod) {
186 if (_log.isDebugEnabled()) {
187 _log.debug("Reaping idle connection=" + info.getId());
188 }
189 try {
190 close(info.getId());
191 } catch (IOException ignore) {
192 }
193 }
194 }
195 }
196 }
197
198 /***
199 * Starts the reaper for dead/idle connections, if needed.
200 */
201 private synchronized void startReaper() {
202 if (_daemon == null) {
203 _daemon = new ClockDaemon();
204 if (_idlePeriod > 0) {
205 _daemon.setThreadFactory(new ThreadFactory() {
206 public Thread newThread(Runnable command) {
207 Thread thread = new Thread(command, "Reaper");
208 thread.setDaemon(true);
209 return thread;
210 }
211 });
212
213 _daemon.executePeriodically(_idlePeriod, new Reaper(), false);
214 }
215 }
216 }
217
218 /***
219 * Stops the reaper for dead/idle connections, if needed.
220 */
221 private synchronized void stopReaper() {
222 if (_daemon != null) {
223 _daemon.shutDown();
224 _daemon = null;
225 }
226 }
227
228 /***
229 * Helper to determines if a reaper should terminate, by checking the
230 * interrupt status of the current thread.
231 *
232 * @return <code>true</code> if the reaper should terminate
233 */
234 private boolean stopReaping() {
235 return Thread.currentThread().isInterrupted();
236 }
237
238 /***
239 * Helper class for reaping idle connections.
240 */
241 private class Reaper implements Runnable {
242
243 /***
244 * Run the reaper.
245 */
246 public void run() {
247 try {
248 reapIdleConnections();
249 } catch (Throwable exception) {
250 _log.error(exception, exception);
251 }
252 }
253 }
254
255 }
256