/* * Copyright 2007-2010 Sun Microsystems, Inc. * * This file is part of Project Darkstar Server. * * Project Darkstar Server is free software: you can redistribute it * and/or modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation and * distributed hereunder to you. * * Project Darkstar Server is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * -- */ package com.sun.sgs.impl.nio; import static java.nio.channels.SelectionKey.OP_CONNECT; import static java.nio.channels.SelectionKey.OP_READ; import static java.nio.channels.SelectionKey.OP_WRITE; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousCloseException; import java.nio.channels.ClosedChannelException; import java.nio.channels.NotYetConnectedException; import java.nio.channels.SocketChannel; import java.nio.channels.UnresolvedAddressException; import java.nio.channels.UnsupportedAddressTypeException; import java.util.Collections; import java.util.EnumSet; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import com.sun.sgs.nio.channels.AlreadyBoundException; import com.sun.sgs.nio.channels.AsynchronousSocketChannel; import com.sun.sgs.nio.channels.ClosedAsynchronousChannelException; import com.sun.sgs.nio.channels.CompletionHandler; import com.sun.sgs.nio.channels.IoFuture; import com.sun.sgs.nio.channels.ShutdownType; import com.sun.sgs.nio.channels.SocketOption; import com.sun.sgs.nio.channels.StandardSocketOption; /** * An implementation of {@link AsynchronousSocketChannel}. * Most interesting methods are delegated to the {@link AsyncKey} * returned by this channel's channel group. */ class AsyncSocketChannelImpl extends AsynchronousSocketChannel { /** The valid socket options for this channel. */ private static final Set<SocketOption> socketOptions; static { Set<? extends SocketOption> es = EnumSet.of( StandardSocketOption.SO_SNDBUF, StandardSocketOption.SO_RCVBUF, StandardSocketOption.SO_KEEPALIVE, StandardSocketOption.SO_REUSEADDR, StandardSocketOption.TCP_NODELAY); socketOptions = Collections.unmodifiableSet(es); } /** The underlying {@code SocketChannel}. */ final SocketChannel channel; /** The {@code AsyncKey} for the underlying channel. */ final AsyncKey key; /** * Creates a new instance registered with the given channel group. * * @param group the channel group * @throws IOException if an I/O error occurs */ AsyncSocketChannelImpl(AsyncGroupImpl group) throws IOException { this(group, group.selectorProvider().openSocketChannel()); } /** * Creates a new instance registered with the given channel group, with * the given underlying {@link SocketChannel}. Used by an * {@link AsyncServerSocketChannelImpl} when a new connection is * accepted. * * @param group the channel group * @param channel the {@link SocketChannel} for this async channel * @throws IOException if an I/O error occurs */ AsyncSocketChannelImpl(AsyncGroupImpl group, SocketChannel channel) throws IOException { super(group.provider()); this.channel = channel; key = group.register(channel); } /** * {@inheritDoc} */ @Override public String toString() { return super.toString() + ":" + key; } /** * {@inheritDoc} */ public boolean isOpen() { return channel.isOpen(); } /** * {@inheritDoc} */ @Override public void close() throws IOException { key.close(); } /** * {@inheritDoc} */ @Override public AsyncSocketChannelImpl bind(SocketAddress local) throws IOException { if ((local != null) && (!(local instanceof InetSocketAddress))) { throw new UnsupportedAddressTypeException(); } InetSocketAddress inetLocal = (InetSocketAddress) local; if ((inetLocal != null) && inetLocal.isUnresolved()) { throw new UnresolvedAddressException(); } final Socket socket = channel.socket(); try { socket.bind(inetLocal); } catch (SocketException e) { if (socket.isBound()) { throw Util.initCause(new AlreadyBoundException(), e); } if (socket.isClosed()) { throw Util.initCause(new ClosedChannelException(), e); } throw e; } return this; } /** * {@inheritDoc} */ public SocketAddress getLocalAddress() throws IOException { return channel.socket().getLocalSocketAddress(); } /** * {@inheritDoc} */ @Override public AsyncSocketChannelImpl setOption(SocketOption name, Object value) throws IOException { if (!(name instanceof StandardSocketOption)) { throw new IllegalArgumentException("Unsupported option " + name); } if (value == null || !name.type().isAssignableFrom(value.getClass())) { throw new IllegalArgumentException("Bad parameter for " + name); } StandardSocketOption stdOpt = (StandardSocketOption) name; final Socket socket = channel.socket(); try { switch (stdOpt) { case SO_SNDBUF: socket.setSendBufferSize(((Integer) value).intValue()); break; case SO_RCVBUF: socket.setReceiveBufferSize(((Integer) value).intValue()); break; case SO_KEEPALIVE: socket.setKeepAlive(((Boolean) value).booleanValue()); break; case SO_REUSEADDR: socket.setReuseAddress(((Boolean) value).booleanValue()); break; case TCP_NODELAY: socket.setTcpNoDelay(((Boolean) value).booleanValue()); break; default: throw new IllegalArgumentException( "Unsupported option " + name); } } catch (SocketException e) { if (socket.isClosed()) { throw Util.initCause(new ClosedChannelException(), e); } throw e; } return this; } /** * {@inheritDoc} */ public Object getOption(SocketOption name) throws IOException { if (!(name instanceof StandardSocketOption)) { throw new IllegalArgumentException("Unsupported option " + name); } StandardSocketOption stdOpt = (StandardSocketOption) name; final Socket socket = channel.socket(); try { switch (stdOpt) { case SO_SNDBUF: return socket.getSendBufferSize(); case SO_RCVBUF: return socket.getReceiveBufferSize(); case SO_KEEPALIVE: return socket.getKeepAlive(); case SO_REUSEADDR: return socket.getReuseAddress(); case TCP_NODELAY: return socket.getTcpNoDelay(); default: throw new IllegalArgumentException("Unsupported option " + name); } } catch (SocketException e) { if (socket.isClosed()) { throw Util.initCause(new ClosedChannelException(), e); } throw e; } } /** * {@inheritDoc} */ public Set<SocketOption> options() { return socketOptions; } /** * {@inheritDoc} */ @Override public AsyncSocketChannelImpl shutdown(ShutdownType how) throws IOException { final Socket socket = channel.socket(); try { if (how == ShutdownType.READ || how == ShutdownType.BOTH) { if (!socket.isInputShutdown()) { socket.shutdownInput(); key.selected(OP_READ); } } if (how == ShutdownType.WRITE || how == ShutdownType.BOTH) { if (!socket.isOutputShutdown()) { socket.shutdownOutput(); key.selected(OP_WRITE); } } } catch (SocketException e) { if (!socket.isConnected()) { throw Util.initCause(new NotYetConnectedException(), e); } if (socket.isClosed()) { throw Util.initCause(new ClosedChannelException(), e); } throw e; } return this; } /** * {@inheritDoc} */ @Override public SocketAddress getConnectedAddress() throws IOException { return channel.socket().getRemoteSocketAddress(); } /** * {@inheritDoc} */ @Override public boolean isConnectionPending() { return channel.isConnectionPending(); } /** * {@inheritDoc} */ @Override public boolean isReadPending() { return key.isOpPending(OP_READ); } /** * {@inheritDoc} */ @Override public boolean isWritePending() { return key.isOpPending(OP_WRITE); } /** * {@inheritDoc} */ @Override public <A> IoFuture<Void, A> connect( SocketAddress remote, final A attachment, final CompletionHandler<Void, ? super A> handler) { try { if (channel.connect(remote)) { Future<Void> result = Util.finishedFuture(null); key.runCompletion(handler, attachment, result); return AttachedFuture.wrap(result, attachment); } } catch (ClosedChannelException e) { throw Util.initCause(new ClosedAsynchronousChannelException(), e); } catch (IOException e) { Future<Void> result = Util.failedFuture(e); key.runCompletion(handler, attachment, result); return AttachedFuture.wrap(result, attachment); } return key.execute( OP_CONNECT, attachment, handler, 0, TimeUnit.MILLISECONDS, new Callable<Void>() { public Void call() throws IOException { try { channel.finishConnect(); return null; } catch (ClosedChannelException e) { throw Util.initCause( new AsynchronousCloseException(), e); } } }); } /** * {@inheritDoc} */ @Override public <A> IoFuture<Integer, A> read( final ByteBuffer dst, long timeout, TimeUnit unit, A attachment, CompletionHandler<Integer, ? super A> handler) { return key.execute(OP_READ, attachment, handler, timeout, unit, new Callable<Integer>() { public Integer call() throws IOException { try { return channel.read(dst); } catch (ClosedChannelException e) { throw Util.initCause( new AsynchronousCloseException(), e); } } }); } /** * {@inheritDoc} */ @Override public <A> IoFuture<Long, A> read( final ByteBuffer[] dsts, final int offset, final int length, long timeout, TimeUnit unit, A attachment, CompletionHandler<Long, ? super A> handler) { if ((offset < 0) || (offset >= dsts.length)) { throw new IllegalArgumentException("offset out of range"); } if ((length < 0) || (length > (dsts.length - offset))) { throw new IllegalArgumentException("length out of range"); } return key.execute(OP_READ, attachment, handler, timeout, unit, new Callable<Long>() { public Long call() throws IOException { try { return channel.read(dsts, offset, length); } catch (ClosedChannelException e) { throw Util.initCause( new AsynchronousCloseException(), e); } } }); } /** * {@inheritDoc} */ @Override public <A> IoFuture<Integer, A> write( final ByteBuffer src, long timeout, TimeUnit unit, A attachment, CompletionHandler<Integer, ? super A> handler) { return key.execute(OP_WRITE, attachment, handler, timeout, unit, new Callable<Integer>() { public Integer call() throws IOException { try { return channel.write(src); } catch (ClosedChannelException e) { throw Util.initCause( new AsynchronousCloseException(), e); } } }); } /** * {@inheritDoc} */ @Override public <A> IoFuture<Long, A> write( final ByteBuffer[] srcs, final int offset, final int length, long timeout, TimeUnit unit, A attachment, CompletionHandler<Long, ? super A> handler) { if ((offset < 0) || (offset >= srcs.length)) { throw new IllegalArgumentException("offset out of range"); } if ((length < 0) || (length > (srcs.length - offset))) { throw new IllegalArgumentException("length out of range"); } return key.execute(OP_WRITE, attachment, handler, timeout, unit, new Callable<Long>() { public Long call() throws IOException { try { return channel.write(srcs, offset, length); } catch (ClosedChannelException e) { throw Util.initCause( new AsynchronousCloseException(), e); } } }); } }