View Javadoc

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