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-2005 (C) Exoffice Technologies Inc. All Rights Reserved.
42   *
43   * $Id: SocketManagedConnectionAcceptor.java,v 1.8 2006/12/16 12:37:17 tanderson Exp $
44   */
45  package org.exolab.jms.net.socket;
46  
47  import java.io.IOException;
48  import java.net.InetAddress;
49  import java.net.ServerSocket;
50  import java.net.Socket;
51  
52  import org.apache.commons.logging.Log;
53  import org.apache.commons.logging.LogFactory;
54  
55  import org.exolab.jms.net.connector.Authenticator;
56  import org.exolab.jms.net.connector.ManagedConnection;
57  import org.exolab.jms.net.connector.ManagedConnectionAcceptor;
58  import org.exolab.jms.net.connector.ManagedConnectionAcceptorListener;
59  import org.exolab.jms.net.connector.ResourceException;
60  import org.exolab.jms.net.connector.URIRequestInfo;
61  import org.exolab.jms.net.uri.URI;
62  
63  
64  /***
65   * A {@link ManagedConnectionAcceptor} for accepting socket connections.
66   *
67   * @author <a href="mailto:tma@netspace.net.au">Tim Anderson</a>
68   * @version $Revision: 1.8 $ $Date: 2006/12/16 12:37:17 $
69   */
70  public abstract class SocketManagedConnectionAcceptor
71          implements ManagedConnectionAcceptor {
72  
73      /***
74       * The connection authenticator.
75       */
76      private Authenticator _authenticator;
77  
78      /***
79       * The underlying socket.
80       */
81      private ServerSocket _socket;
82  
83      /***
84       * The URI denoting this acceptor.
85       */
86      private final URI _uri;
87  
88      /***
89       * The thread group for all threads associated with this.
90       */
91      private final ThreadGroup _group;
92  
93      /***
94       * The connection dispatcher.
95       */
96      private Dispatcher _dispatcher;
97  
98      /***
99       * The connection request info used to match acceptors.
100      */
101     private final SocketRequestInfo _info;
102 
103 
104     /***
105      * The logger.
106      */
107     private static final Log _log =
108             LogFactory.getLog(SocketManagedConnectionAcceptor.class);
109 
110 
111     /***
112      * Construct a new <code>SocketManagedConnectionAcceptor</code>.
113      * <p/>
114      * This creates a server socket with the specified port and listen backlog.
115      * <p/>
116      * If {@link SocketRequestInfo#getBindAll()} flag can be used on multi-homed
117      * hosts to limit the addresses on which connections are accepted.
118      * If <code>false</code>, the socket will only accept connections on the
119      * address specified by {@link SocketRequestInfo#getHostAddress}.
120      * If <code>true</code> it will accept connections on all local addresses.
121      * <p/>
122      * The port returned by {@link URIRequestInfo#getPort} must be between 0 and
123      * 65535, inclusive
124      *
125      * @param authenticator the connection authenticator
126      * @param info          the connection request info
127      * @throws ResourceException if a server socket cannot be created
128      */
129     public SocketManagedConnectionAcceptor(Authenticator authenticator,
130                                            SocketRequestInfo info)
131             throws ResourceException {
132 
133         if (authenticator == null) {
134             throw new IllegalArgumentException(
135                     "Argument 'authenticator' is null");
136         }
137         if (info == null) {
138             throw new IllegalArgumentException("Argument 'info' is null");
139         }
140 
141         _authenticator = authenticator;
142         _uri = info.getURI();
143         _info = info;
144         int port = info.getPort();
145         try {
146             InetAddress host = null;
147             if (!info.getBindAll()) {
148                 host = info.getHostAddress();
149             }
150             int backlog = info.getConnectionRequestQueueSize();
151             _socket = createServerSocket(port, backlog, host);
152         } catch (IOException exception) {
153             throw new ResourceException(
154                     "Failed to create server socket for URI=" + info.getURI(),
155                     exception);
156         }
157 
158         _group = new ThreadGroup(_uri.toString());
159         StringBuffer name = new StringBuffer();
160         name.append(_uri.toString());
161         name.append("[server]");
162     }
163 
164     /***
165      * Start accepting connections.
166      *
167      * @param listener the listener to delegate accepted connections to
168      * @throws ResourceException if connections cannot be accepted
169      */
170     public synchronized void accept(ManagedConnectionAcceptorListener listener)
171             throws ResourceException {
172         if (_dispatcher != null) {
173             throw new ResourceException(
174                     "Acceptor is already accepting connections on URI=" + _uri);
175         }
176 
177         _dispatcher = new Dispatcher(listener);
178         _dispatcher.start();
179         if (_log.isDebugEnabled()) {
180             _log.debug("Acceptor accepting requests at URI=" + _uri);
181         }
182     }
183 
184     /***
185      * Returns the connection request info used to construct this.
186      *
187      * @return the connection request info
188      */
189     public SocketRequestInfo getRequestInfo() {
190         return _info;
191     }
192 
193     /***
194      * Returns the URI that this acceptor is accepting connections on.
195      *
196      * @return the URI that this acceptor is accepting connections on
197      */
198     public URI getURI() {
199         return _uri;
200     }
201 
202     /***
203      * Stop accepting connection requests, and clean up any allocated
204      * resources.
205      *
206      * @throws ResourceException generic exception if the operation fails
207      */
208     public synchronized void close() throws ResourceException {
209         if (_log.isDebugEnabled()) {
210             _log.debug("Acceptor shutting down at URI=" + _uri);
211         }
212         if (_dispatcher != null) {
213             // dispatcher responsible for closing socket
214             _dispatcher.close();
215             if (Thread.currentThread() != _dispatcher) {
216                 try {
217                     _dispatcher.join();  // wait for the dispatcher to terminate
218                 } catch (InterruptedException ignore) {
219                     // don't care.
220                 }
221             }
222             _dispatcher = null;
223             _socket = null;
224         } else if (_socket != null) {
225             try {
226                 _socket.close();
227                 _socket = null;
228             } catch (IOException exception) {
229                 throw new ResourceException("Failed to close socket",
230                                             exception);
231             }
232         }
233     }
234 
235     /***
236      * Create a new server socket.
237      *
238      * @param port    the port to listen on
239      * @param backlog the listen backlog
240      * @param host    if non-null, specifies to only accept connections to the
241      *                specified address. If null, accept connections on any/all
242      *                local addresses.
243      * @return a new server socket, listening on <code>port</code>
244      * @throws IOException if the socket can't be created
245      */
246     protected ServerSocket createServerSocket(int port, int backlog,
247                                               InetAddress host)
248             throws IOException {
249         return new ServerSocket(port, backlog, host);
250     }
251 
252     /***
253      * Create a new server-side <code>ManagedConnection</code> for an accepted
254      * socket connection.
255      *
256      * @param uri           the URI denoting this acceptor
257      * @param socket        the accepted socket connection
258      * @param authenticator the connection authenticator
259      * @return a new server-side managed connection
260      * @throws ResourceException if the managed connection can't be created
261      */
262     protected abstract ManagedConnection createManagedConnection(
263             URI uri, Socket socket, Authenticator authenticator)
264             throws ResourceException;
265 
266     /***
267      * Accepts connections.
268      */
269     private class Dispatcher extends Thread {
270 
271         /***
272          * The listener to delegate accepted connections to.
273          */
274         private final ManagedConnectionAcceptorListener _listener;
275 
276         /***
277          * Determines if the dispatcher is closed.
278          */
279         private volatile boolean _closed = false;
280 
281         /***
282          * Construct a new <code>Dispatcher</code>.
283          *
284          * @param listener the listener to delegate accepted connections to
285          */
286         public Dispatcher(ManagedConnectionAcceptorListener listener) {
287             super(_group, getURI() + "[acceptor]");
288             _listener = listener;
289         }
290 
291         /***
292          * Close the dispatcher.
293          */
294         public void close() {
295             _closed = true;
296             try {
297                 _socket.close();
298             } catch (IOException exception) {
299                 _log.debug(exception);
300             }
301         }
302 
303         /***
304          * Accept connections.
305          */
306         public void run() {
307             while (!_closed) {
308                 try {
309                     Socket socket = _socket.accept();
310                     socket.setTcpNoDelay(true);
311                     ManagedConnection connection = createManagedConnection(
312                             _uri, socket, _authenticator);
313                     _listener.accepted(SocketManagedConnectionAcceptor.this,
314                                        connection);
315                 } catch (Exception exception) {
316                     if (!_closed) {
317                         _listener.error(SocketManagedConnectionAcceptor.this,
318                                         exception);
319                     }
320                     break;
321                 }
322             }
323         }
324     }
325 }