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
214 _dispatcher.close();
215 if (Thread.currentThread() != _dispatcher) {
216 try {
217 _dispatcher.join();
218 } catch (InterruptedException ignore) {
219
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 }