/* * @(#) $Id: SocketAcceptorDelegate.java 379346 2006-02-21 05:10:30Z trustin $ * * Copyright 2004 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.openamq.nio; import org.apache.mina.common.*; import org.apache.mina.common.support.BaseIoAcceptor; import org.apache.mina.transport.socket.nio.SocketAcceptorConfig; import org.apache.mina.transport.socket.nio.SocketSessionConfig; import org.apache.mina.util.IdentityHashSet; import org.apache.mina.util.Queue; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.*; /** * {@link IoAcceptor} for socket transport (TCP/IP). * * @author The Apache Directory Project (dev@directory.apache.org) * @version $Rev: 379346 $, $Date: 2006-02-21 05:10:30 +0000 (Tue, 21 Feb 2006) $ */ public class SocketAcceptorDelegate extends BaseIoAcceptor { private static volatile int nextId = 0; private final IoAcceptor wrapper; private final int id = nextId ++; private final String threadName = "SocketAcceptor-" + id; private final IoServiceConfig defaultConfig = new SocketAcceptorConfig(); private Selector selector; private final Map channels = new HashMap(); private final Hashtable sessions = new Hashtable(); private final Queue registerQueue = new Queue(); private final Queue cancelQueue = new Queue(); private Worker worker; /** * Creates a new instance. */ public SocketAcceptorDelegate(IoAcceptor wrapper) { this.wrapper = wrapper; } /** * Binds to the specified <code>address</code> and handles incoming * connections with the specified <code>handler</code>. Backlog value * is configured to the value of <code>backlog</code> property. * * @throws IOException if failed to bind */ public void bind(SocketAddress address, IoHandler handler, IoServiceConfig config) throws IOException { if (address == null) { throw new NullPointerException("address"); } if (handler == null) { throw new NullPointerException("handler"); } if (!(address instanceof InetSocketAddress)) { throw new IllegalArgumentException("Unexpected address type: " + address.getClass()); } if (((InetSocketAddress) address).getPort() == 0) { throw new IllegalArgumentException("Unsupported port number: 0"); } if (config == null) { config = getDefaultConfig(); } RegistrationRequest request = new RegistrationRequest(address, handler, config); synchronized (this) { synchronized (registerQueue) { registerQueue.push(request); } startupWorker(); } selector.wakeup(); synchronized (request) { while (!request.done) { try { request.wait(); } catch (InterruptedException e) { } } } if (request.exception != null) { throw request.exception; } } private synchronized void startupWorker() throws IOException { if (worker == null) { selector = Selector.open(); worker = new Worker(); worker.start(); } } public Set getManagedSessions(SocketAddress address) { if (address == null) { throw new NullPointerException("address"); } Set managedSessions = (Set) sessions.get(address); if (managedSessions == null) { throw new IllegalArgumentException("Address not bound: " + address); } return Collections.unmodifiableSet( new IdentityHashSet(Arrays.asList(managedSessions.toArray()))); } public void unbind(SocketAddress address) { if (address == null) { throw new NullPointerException("address"); } final Set managedSessions = (Set) sessions.get(address); CancellationRequest request = new CancellationRequest(address); synchronized (this) { try { startupWorker(); } catch (IOException e) { // IOException is thrown only when Worker thread is not // running and failed to open a selector. We simply throw // IllegalArgumentException here because we can simply // conclude that nothing is bound to the selector. throw new IllegalArgumentException("Address not bound: " + address); } synchronized (cancelQueue) { cancelQueue.push(request); } } selector.wakeup(); synchronized (request) { while (!request.done) { try { request.wait(); } catch (InterruptedException e) { } } } if (request.exception != null) { request.exception.fillInStackTrace(); throw request.exception; } // Disconnect all clients IoServiceConfig cfg = request.registrationRequest.config; boolean disconnectOnUnbind; if (cfg instanceof IoAcceptorConfig) { disconnectOnUnbind = ((IoAcceptorConfig) cfg).isDisconnectOnUnbind(); } else { disconnectOnUnbind = ((IoAcceptorConfig) getDefaultConfig()).isDisconnectOnUnbind(); } if (disconnectOnUnbind && managedSessions != null) { IoSession[] tempSessions = (IoSession[]) managedSessions.toArray(new IoSession[ 0 ]); final Object lock = new Object(); for (int i = 0; i < tempSessions.length; i++) { if (!managedSessions.contains(tempSessions[i])) { // The session has already been closed and have been // removed from managedSessions by the SocketIoProcessor. continue; } tempSessions[i].close().setCallback(new IoFuture.Callback() { public void operationComplete(IoFuture future) { synchronized (lock) { lock.notify(); } } }); } try { synchronized (lock) { while (!managedSessions.isEmpty()) { lock.wait(1000); } } } catch (InterruptedException ie) { // Ignored } } } public void unbindAll() { List addresses; synchronized (channels) { addresses = new ArrayList(channels.keySet()); } for (Iterator i = addresses.iterator(); i.hasNext();) { unbind((SocketAddress) i.next()); } } public boolean isBound(SocketAddress address) { synchronized (channels) { return channels.containsKey(address); } } private class Worker extends Thread { public Worker() { super(SocketAcceptorDelegate.this.threadName); } public void run() { for (; ;) { try { int nKeys = selector.select(); registerNew(); cancelKeys(); if (nKeys > 0) { processSessions(selector.selectedKeys()); } if (selector.keys().isEmpty()) { synchronized (SocketAcceptorDelegate.this) { if (selector.keys().isEmpty() && registerQueue.isEmpty() && cancelQueue.isEmpty()) { worker = null; try { selector.close(); } catch (IOException e) { ExceptionMonitor.getInstance().exceptionCaught(e); } finally { selector = null; } break; } } } } catch (IOException e) { ExceptionMonitor.getInstance().exceptionCaught(e); try { Thread.sleep(1000); } catch (InterruptedException e1) { } } } } private void processSessions(Set keys) throws IOException { Iterator it = keys.iterator(); while (it.hasNext()) { SelectionKey key = (SelectionKey) it.next(); it.remove(); if (!key.isAcceptable()) { continue; } ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); SocketChannel ch = ssc.accept(); if (ch == null) { continue; } boolean success = false; SocketSessionImpl session = null; try { RegistrationRequest req = (RegistrationRequest) key.attachment(); session = new SocketSessionImpl( SocketAcceptorDelegate.this.wrapper, (Set) sessions.get(req.address), (SocketSessionConfig) req.config.getSessionConfig(), ch, req.handler, req.address); getFilterChainBuilder().buildFilterChain(session.getFilterChain()); req.config.getFilterChainBuilder().buildFilterChain(session.getFilterChain()); ((SocketFilterChain) session.getFilterChain()).sessionCreated(session); session.getManagedSessions().add(session); session.getIoProcessor().addNew(session); success = true; } catch (Throwable t) { ExceptionMonitor.getInstance().exceptionCaught(t); } finally { if (!success) { if (session != null) { session.getManagedSessions().remove(session); } ch.close(); } } } } } public IoServiceConfig getDefaultConfig() { return defaultConfig; } private void registerNew() { if (registerQueue.isEmpty()) { return; } for (; ;) { RegistrationRequest req; synchronized (registerQueue) { req = (RegistrationRequest) registerQueue.pop(); } if (req == null) { break; } ServerSocketChannel ssc = null; try { ssc = ServerSocketChannel.open(); ssc.configureBlocking(false); // Configure the server socket, SocketAcceptorConfig cfg; if (req.config instanceof SocketAcceptorConfig) { cfg = (SocketAcceptorConfig) req.config; } else { cfg = (SocketAcceptorConfig) getDefaultConfig(); } ssc.socket().setReuseAddress(cfg.isReuseAddress()); ssc.socket().setReceiveBufferSize( ((SocketSessionConfig) cfg.getSessionConfig()).getReceiveBufferSize()); // and bind. ssc.socket().bind(req.address, cfg.getBacklog()); ssc.register(selector, SelectionKey.OP_ACCEPT, req); synchronized (channels) { channels.put(req.address, ssc); } sessions.put(req.address, Collections.synchronizedSet(new HashSet())); } catch (IOException e) { req.exception = e; } finally { synchronized (req) { req.done = true; req.notify(); } if (ssc != null && req.exception != null) { try { ssc.close(); } catch (IOException e) { ExceptionMonitor.getInstance().exceptionCaught(e); } } } } } private void cancelKeys() { if (cancelQueue.isEmpty()) { return; } for (; ;) { CancellationRequest request; synchronized (cancelQueue) { request = (CancellationRequest) cancelQueue.pop(); } if (request == null) { break; } sessions.remove(request.address); ServerSocketChannel ssc; synchronized (channels) { ssc = (ServerSocketChannel) channels.remove(request.address); } // close the channel try { if (ssc == null) { request.exception = new IllegalArgumentException("Address not bound: " + request.address); } else { SelectionKey key = ssc.keyFor(selector); request.registrationRequest = (RegistrationRequest) key.attachment(); key.cancel(); selector.wakeup(); // wake up again to trigger thread death ssc.close(); } } catch (IOException e) { ExceptionMonitor.getInstance().exceptionCaught(e); } finally { synchronized (request) { request.done = true; request.notify(); } } } } private static class RegistrationRequest { private final SocketAddress address; private final IoHandler handler; private final IoServiceConfig config; private IOException exception; private boolean done; private RegistrationRequest(SocketAddress address, IoHandler handler, IoServiceConfig config) { this.address = address; this.handler = handler; this.config = config; } } private static class CancellationRequest { private final SocketAddress address; private boolean done; private RegistrationRequest registrationRequest; private RuntimeException exception; private CancellationRequest(SocketAddress address) { this.address = address; } } }