/*
* 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.protocol.simple;
import com.sun.sgs.app.Delivery;
import com.sun.sgs.auth.Identity;
import com.sun.sgs.impl.sharedutil.HexDumper;
import com.sun.sgs.impl.sharedutil.LoggerWrapper;
import com.sun.sgs.impl.sharedutil.MessageBuffer;
import com.sun.sgs.impl.util.AbstractKernelRunnable;
import com.sun.sgs.nio.channels.AsynchronousByteChannel;
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.ReadPendingException;
import com.sun.sgs.protocol.LoginFailureException;
import com.sun.sgs.protocol.LoginRedirectException;
import com.sun.sgs.protocol.ProtocolDescriptor;
import com.sun.sgs.protocol.ProtocolListener;
import com.sun.sgs.protocol.RequestFailureException;
import com.sun.sgs.protocol.RequestFailureException.FailureReason;
import com.sun.sgs.protocol.RequestCompletionHandler;
import com.sun.sgs.protocol.SessionProtocol;
import com.sun.sgs.protocol.SessionProtocolHandler;
import com.sun.sgs.protocol.simple.SimpleSgsProtocol;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Implements the protocol specified in {@code SimpleSgsProtocol}. The
* implementation uses a wrapper channel, {@link AsynchronousMessageChannel},
* that reads and writes complete messages by framing messages with a 2-byte
* message length, and masking (and re-issuing) partial I/O operations. Also
* enforces a fixed buffer size when reading.
*/
public class SimpleSgsProtocolImpl implements SessionProtocol {
/** The protocol version for this implementation. */
private static final byte PROTOCOL4 = 0x04;
/** The number of bytes used to represent the message length. */
private static final int PREFIX_LENGTH = 2;
/** The logger for this class. */
private static final LoggerWrapper staticLogger = new LoggerWrapper(
Logger.getLogger(SimpleSgsProtocolImpl.class.getName()));
/** The default reason string returned for login failure. */
private static final String DEFAULT_LOGIN_FAILED_REASON = "login refused";
/** The default length of the reconnect key, in bytes.
* TBD: the reconnection key length should be configurable.
*/
private static final int DEFAULT_RECONNECT_KEY_LENGTH = 16;
/** A random number generator for reconnect keys. */
private static final SecureRandom random = new SecureRandom();
/**
* The underlying channel (possibly another layer of abstraction,
* e.g. compression, retransmission...).
*/
private final AsynchronousMessageChannel asyncMsgChannel;
/** The protocol handler. */
protected volatile SessionProtocolHandler protocolHandler;
/** This protocol's acceptor. */
protected final SimpleSgsProtocolAcceptor acceptor;
/** The logger for this instance. */
protected final LoggerWrapper logger;
/** The protocol listener. */
protected final ProtocolListener listener;
/** The identity. */
private volatile Identity identity;
/** The reconnect key. */
protected final byte[] reconnectKey;
/** The completion handler for reading from the I/O channel. */
private volatile ReadHandler readHandler = new ConnectedReadHandler();
/** The completion handler for writing to the I/O channel. */
private volatile WriteHandler writeHandler = new ConnectedWriteHandler();
/** A lock for {@code loginHandled} and {@code messageQueue} fields. */
private final Object lock = new Object();
/** Indicates whether the client's login ack has been sent. */
private boolean loginHandled = false;
/** Messages enqueued to be sent after a login ack is sent. */
private List<ByteBuffer> messageQueue = new ArrayList<ByteBuffer>();
/** The set of supported delivery requirements. */
protected final Set<Delivery> deliverySet = new HashSet<Delivery>();
/**
* Creates a new instance of this class.
*
* @param listener a protocol listener
* @param acceptor the {@code SimpleSgsProtocol} acceptor
* @param byteChannel a byte channel for the underlying connection
* @param readBufferSize the read buffer size
*/
SimpleSgsProtocolImpl(ProtocolListener listener,
SimpleSgsProtocolAcceptor acceptor,
AsynchronousByteChannel byteChannel,
int readBufferSize)
{
this(listener, acceptor, byteChannel, readBufferSize, staticLogger);
/*
* TBD: It might be a good idea to implement high- and low-water marks
* for the buffers, so they don't go into hysteresis when they get
* full. -JM
*/
scheduleRead();
}
/**
* Constructs a new instance of this class. The subclass should invoke
* {@code scheduleRead} after constructing the instance to commence
* reading.
*
* @param listener a protocol listener
* @param acceptor the {@code SimpleSgsProtocol} acceptor
* @param byteChannel a byte channel for the underlying connection
* @param readBufferSize the read buffer size
* @param logger a logger for this instance
*/
protected SimpleSgsProtocolImpl(ProtocolListener listener,
SimpleSgsProtocolAcceptor acceptor,
AsynchronousByteChannel byteChannel,
int readBufferSize,
LoggerWrapper logger)
{
// The read buffer size lower bound is enforced by the protocol acceptor
assert readBufferSize >= PREFIX_LENGTH;
this.asyncMsgChannel =
new AsynchronousMessageChannel(byteChannel, readBufferSize);
this.listener = listener;
this.acceptor = acceptor;
this.logger = logger;
this.reconnectKey = getNextReconnectKey();
deliverySet.add(Delivery.RELIABLE);
}
/**
* Returns the {@code SimpleSgsProtocol} version supported by this
* implementation.
*
* @return the {@code SimpleSgsProtocol} version supported by this
* implementation
*/
protected byte getProtocolVersion() {
return PROTOCOL4;
}
/**
* Returns the associated identity, or {@code null} if the client has
* not yet authenticated.
*
* @return the associated identity, or {@code null}
*/
protected Identity getIdentity() {
return identity;
}
/* -- Implement SessionProtocol -- */
/** {@inheritDoc} */
public Set<Delivery> getDeliveries() {
return Collections.unmodifiableSet(deliverySet);
}
/** {@inheritDoc} */
public int getMaxMessageLength() {
// largest message size is max for channel messages
return
SimpleSgsProtocol.MAX_MESSAGE_LENGTH -
1 - // Opcode
2 - // channel ID size
8; // (max) channel ID bytes
}
/** {@inheritDoc} */
public void sessionMessage(ByteBuffer message, Delivery delivery) {
int messageLength = 1 + message.remaining();
assert messageLength <= SimpleSgsProtocol.MAX_MESSAGE_LENGTH;
ByteBuffer buf = ByteBuffer.wrap(new byte[messageLength]);
buf.put(SimpleSgsProtocol.SESSION_MESSAGE).
put(message).
flip();
writeBuffer(buf, delivery);
}
/** {@inheritDoc} */
public void channelJoin(
String name, BigInteger channelId, Delivery delivery) {
byte[] channelIdBytes = channelId.toByteArray();
MessageBuffer buf =
new MessageBuffer(1 + MessageBuffer.getSize(name) +
channelIdBytes.length);
buf.putByte(SimpleSgsProtocol.CHANNEL_JOIN).
putString(name).
putBytes(channelIdBytes);
write(ByteBuffer.wrap(buf.getBuffer()));
}
/** {@inheritDoc} */
public void channelLeave(BigInteger channelId) {
byte[] channelIdBytes = channelId.toByteArray();
ByteBuffer buf =
ByteBuffer.allocate(1 + channelIdBytes.length);
buf.put(SimpleSgsProtocol.CHANNEL_LEAVE).
put(channelIdBytes).
flip();
write(buf);
}
/***
* {@inheritDoc}
*
* <p>This implementation invokes the protected method {@link
* #writeBuffer writeBuffer} with the channel protocol message (a
* {@code ByteBuffer}) and the specified delivery requirement. A
* subclass can override the {@code writeBuffer} method if it supports
* other delivery guarantees and can make use of alternate transports
* for those other delivery requirements.
*/
public void channelMessage(BigInteger channelId,
ByteBuffer message,
Delivery delivery)
{
byte[] channelIdBytes = channelId.toByteArray();
int messageLength = 3 + channelIdBytes.length + message.remaining();
assert messageLength <= SimpleSgsProtocol.MAX_MESSAGE_LENGTH;
ByteBuffer buf =
ByteBuffer.allocate(messageLength);
buf.put(SimpleSgsProtocol.CHANNEL_MESSAGE).
putShort((short) channelIdBytes.length).
put(channelIdBytes).
put(message).
flip();
writeBuffer(buf, delivery);
}
/** {@inheritDoc} */
public void disconnect(DisconnectReason reason) throws IOException {
// TBD: The SimpleSgsProtocol does not yet support sending a
// message to the client in the case of session termination or
// preemption, so just close the connection for now.
close();
}
/* -- Private methods for sending protocol messages -- */
/**
* Notifies the associated client that the previous login attempt was
* successful.
*/
protected void loginSuccess() {
MessageBuffer buf = new MessageBuffer(1 + reconnectKey.length);
buf.putByte(SimpleSgsProtocol.LOGIN_SUCCESS).
putBytes(reconnectKey);
writeNow(ByteBuffer.wrap(buf.getBuffer()), true);
}
/**
* Notifies the associated client that it should redirect its login to
* the specified {@code node} with the specified protocol {@code
* descriptors}.
*
* @param nodeId the ID of the node to redirect the login
* @param descriptors a set of protocol descriptors supported
* by {@code node}
*/
private void loginRedirect(
long nodeId, Set<ProtocolDescriptor> descriptors)
{
for (ProtocolDescriptor descriptor : descriptors) {
if (acceptor.getDescriptor().supportsProtocol(descriptor)) {
byte[] redirectionData =
((SimpleSgsProtocolDescriptor) descriptor).
getConnectionData();
MessageBuffer buf =
new MessageBuffer(1 + redirectionData.length);
buf.putByte(SimpleSgsProtocol.LOGIN_REDIRECT).
putBytes(redirectionData);
writeNow(ByteBuffer.wrap(buf.getBuffer()), true);
monitorDisconnection();
return;
}
}
loginFailure("redirect failed", null);
logger.log(Level.SEVERE,
"redirect node {0} does not support a compatable protocol",
nodeId);
}
/**
* Notifies the associated client that the previous login attempt was
* unsuccessful for the specified {@code reason}. The specified {@code
* throwable}, if non-{@code null} is an exception that occurred while
* processing the login request. The message channel should be careful
* not to reveal to the associated client sensitive data that may be
* present in the specified {@code throwable}.
*
* @param reason a reason why the login was unsuccessful
* @param throwable an exception that occurred while processing the
* login request, or {@code null}
*/
private void loginFailure(String reason, Throwable ignore) {
// for now, override specified reason.
reason = DEFAULT_LOGIN_FAILED_REASON;
MessageBuffer buf =
new MessageBuffer(1 + MessageBuffer.getSize(reason));
buf.putByte(SimpleSgsProtocol.LOGIN_FAILURE).
putString(reason);
writeNow(ByteBuffer.wrap(buf.getBuffer()), true);
monitorDisconnection();
}
/**
* Notifies the associated client that it has successfully logged out.
*/
private void logoutSuccess() {
ByteBuffer buf = ByteBuffer.allocate(1);
buf.put(SimpleSgsProtocol.LOGOUT_SUCCESS).
flip();
writeNow(buf, false);
monitorDisconnection();
}
/* -- Implement Channel -- */
/** {@inheritDoc} */
public boolean isOpen() {
return asyncMsgChannel.isOpen();
}
/** {@inheritDoc} */
public void close() {
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "closing channel, protocol:{0}", this);
}
if (isOpen()) {
try {
asyncMsgChannel.close();
} catch (IOException e) {
}
}
readHandler = new ClosedReadHandler();
writeHandler = new ClosedWriteHandler();
if (protocolHandler != null) {
SessionProtocolHandler handler = protocolHandler;
protocolHandler = null;
handler.disconnect(new RequestHandler());
}
}
/* -- Object method overrides -- */
/** {@inheritDoc} */
@Override
public String toString() {
return getClass().getName() + "[" +
(identity != null ? identity : "<unknown>") + "]";
}
/* -- Methods for reading and writing -- */
/**
* Schedules an asynchronous task to resume reading.
*/
protected final void scheduleRead() {
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "scheduling read, protocol:{0}", this);
}
acceptor.scheduleNonTransactionalTask(
new AbstractKernelRunnable("ResumeReadOnReadHandler") {
public void run() {
logger.log(
Level.FINER, "resuming reads protocol:{0}", this);
readNow();
} });
}
/**
* Resumes reading from the underlying connection.
*/
protected final void readNow() {
if (isOpen()) {
readHandler.read();
} else {
close();
}
}
/**
* Writes a message to the underlying connection if login has been handled,
* otherwise enqueues the message to be sent when the login has not yet been
* handled.
*
* @param buf a buffer containing a complete protocol message
*/
protected final void write(ByteBuffer buf) {
synchronized (lock) {
if (!loginHandled) {
messageQueue.add(buf);
} else {
writeNow(buf, false);
}
}
}
/**
* Writes a message to the underlying connection.
*
* @param message a buffer containing a complete protocol message
* @param flush if {@code true}, then set the {@code loginHandled}
* flag to {@code true} and flush the message queue
*/
protected final void writeNow(ByteBuffer message, boolean flush) {
try {
writeHandler.write(message);
} catch (RuntimeException e) {
if (logger.isLoggable(Level.WARNING)) {
logger.logThrow(
Level.WARNING, e,
"writeNow protocol:{0} throws", this);
}
}
if (flush) {
synchronized (lock) {
loginHandled = true;
for (ByteBuffer nextMessage : messageQueue) {
try {
writeHandler.write(nextMessage);
} catch (RuntimeException e) {
if (logger.isLoggable(Level.WARNING)) {
logger.logThrow(
Level.WARNING, e,
"writeNow protocol:{0} throws", this);
}
}
}
messageQueue.clear();
}
}
}
/**
* Writes the specified buffer, satisfying the specified delivery
* requirement.
*
* <p>This implementation writes the buffer reliably, because this
* protocol only supports reliable delivery.
*
* <p>A subclass can override the {@code writeBuffer} method if it
* supports other delivery guarantees and can make use of alternate
* transports for those other delivery requirements.
*
* @param buf a byte buffer containing a protocol message
* @param delivery a delivery requirement
*/
protected void writeBuffer(ByteBuffer buf, Delivery delivery) {
write(buf);
}
/**
* Returns the next reconnect key.
*
* @return the next reconnect key
*/
private static byte[] getNextReconnectKey() {
byte[] key = new byte[DEFAULT_RECONNECT_KEY_LENGTH];
random.nextBytes(key);
return key;
}
/* -- I/O completion handlers -- */
/** A completion handler for writing to a connection. */
private abstract class WriteHandler
implements CompletionHandler<Void, Void>
{
/** Writes the specified message. */
abstract void write(ByteBuffer message);
}
/** A completion handler for writing that always fails. */
private class ClosedWriteHandler extends WriteHandler {
ClosedWriteHandler() { }
@Override
void write(ByteBuffer message) {
throw new ClosedAsynchronousChannelException();
}
public void completed(IoFuture<Void, Void> result) {
throw new AssertionError("should be unreachable");
}
}
/** A completion handler for writing to the session's channel. */
private class ConnectedWriteHandler extends WriteHandler {
/** The lock for accessing the fields {@code pendingWrites} and
* {@code isWriting}. The locks {@code lock} and {@code writeLock}
* should only be acquired in that specified order.
*/
private final Object writeLock = new Object();
/** An unbounded queue of messages waiting to be written. */
private final LinkedList<ByteBuffer> pendingWrites =
new LinkedList<ByteBuffer>();
/** Whether a write is underway. */
private boolean isWriting = false;
/** Creates an instance of this class. */
ConnectedWriteHandler() { }
/**
* Adds the message to the queue, and starts processing the queue if
* needed.
*/
@Override
void write(ByteBuffer message) {
if (message.remaining() > SimpleSgsProtocol.MAX_PAYLOAD_LENGTH) {
throw new IllegalArgumentException(
"message too long: " + message.remaining() + " > " +
SimpleSgsProtocol.MAX_PAYLOAD_LENGTH);
}
boolean first;
synchronized (writeLock) {
first = pendingWrites.isEmpty();
pendingWrites.add(message);
}
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST,
"write protocol:{0} message:{1} first:{2}",
SimpleSgsProtocolImpl.this,
HexDumper.format(message, 0x50), first);
}
if (first) {
processQueue();
}
}
/** Start processing the first element of the queue, if present. */
private void processQueue() {
ByteBuffer message;
synchronized (writeLock) {
if (isWriting) {
return;
}
message = pendingWrites.peek();
if (message == null) {
return;
}
isWriting = true;
}
if (logger.isLoggable(Level.FINEST)) {
logger.log(
Level.FINEST,
"processQueue protocol:{0} size:{1,number,#} head={2}",
SimpleSgsProtocolImpl.this, pendingWrites.size(),
HexDumper.format(message, 0x50));
message.mark();
}
try {
asyncMsgChannel.write(message, this);
} catch (RuntimeException e) {
logger.logThrow(Level.SEVERE, e,
"{0} processing message {1}",
SimpleSgsProtocolImpl.this,
HexDumper.format(message, 0x50));
throw e;
}
}
/** Done writing the first request in the queue. */
public void completed(IoFuture<Void, Void> result) {
ByteBuffer message;
synchronized (writeLock) {
message = pendingWrites.remove();
isWriting = false;
}
if (logger.isLoggable(Level.FINEST)) {
ByteBuffer resetMessage = message.duplicate();
resetMessage.reset();
logger.log(Level.FINEST,
"completed write protocol:{0} message:{1}",
SimpleSgsProtocolImpl.this,
HexDumper.format(resetMessage, 0x50));
}
try {
result.getNow();
/* Keep writing */
processQueue();
} catch (ExecutionException e) {
/*
* TBD: If we're expecting the session to close, don't
* complain.
*/
if (logger.isLoggable(Level.FINE)) {
logger.logThrow(Level.FINE, e,
"write protocol:{0} message:{1} throws",
SimpleSgsProtocolImpl.this,
HexDumper.format(message, 0x50));
}
synchronized (writeLock) {
pendingWrites.clear();
}
close();
}
}
}
/** A completion handler for reading from a connection. */
private abstract class ReadHandler
implements CompletionHandler<ByteBuffer, Void>
{
/** Initiates the read request. */
abstract void read();
}
/** A completion handler for reading that always fails. */
private class ClosedReadHandler extends ReadHandler {
ClosedReadHandler() { }
@Override
void read() {
throw new ClosedAsynchronousChannelException();
}
public void completed(IoFuture<ByteBuffer, Void> result) {
throw new AssertionError("should be unreachable");
}
}
/** A completion handler for reading from the session's channel. */
private class ConnectedReadHandler extends ReadHandler {
/** The lock for accessing the {@code isReading} field. The locks
* {@code lock} and {@code readLock} should only be acquired in
* that specified order.
*/
private final Object readLock = new Object();
/** Whether a read is underway. */
private boolean isReading = false;
/** Creates an instance of this class. */
ConnectedReadHandler() { }
/** Reads a message from the connection. */
@Override
void read() {
synchronized (readLock) {
if (isReading) {
throw new ReadPendingException();
}
isReading = true;
}
asyncMsgChannel.read(this);
}
/** Handles the completed read operation. */
public void completed(IoFuture<ByteBuffer, Void> result) {
synchronized (readLock) {
isReading = false;
}
try {
ByteBuffer message = result.getNow();
if (message == null) {
close();
return;
}
if (logger.isLoggable(Level.FINEST)) {
logger.log(
Level.FINEST,
"completed read protocol:{0} message:{1}",
SimpleSgsProtocolImpl.this,
HexDumper.format(message, 0x50));
}
byte[] payload = new byte[message.remaining()];
message.get(payload);
// Dispatch
MessageBuffer msg = new MessageBuffer(payload);
byte opcode = msg.getByte();
if (logger.isLoggable(Level.FINEST)) {
logger.log(
Level.FINEST,
"processing opcode 0x{0}",
Integer.toHexString(opcode));
}
handleMessageReceived(opcode, msg);
} catch (Exception e) {
/*
* TBD: If we're expecting the channel to close, don't
* complain.
*/
if (logger.isLoggable(Level.FINE)) {
logger.logThrow(
Level.FINE, e,
"Read completion exception {0}", asyncMsgChannel);
}
close();
}
}
}
/**
* Processes the received message. This implementation processes
* opcodes for {@code SimpleSgsProtocol} version {@code 0x04}. A
* subclass can override this implementation to process additional
* opcodes, and then delegate to this implementation to process the
* version {@code 0x04} opcodes.
*
* @param opcode the message opcode
* @param msg a message buffer containing the entire message, but
* with the position advanced to the payload (just after the
* opcode)
*/
protected void handleMessageReceived(byte opcode, MessageBuffer msg) {
switch (opcode) {
case SimpleSgsProtocol.LOGIN_REQUEST:
byte version = msg.getByte();
if (version != getProtocolVersion()) {
if (logger.isLoggable(Level.SEVERE)) {
logger.log(Level.SEVERE,
"got protocol version:{0}, " +
"expected {1}", version, getProtocolVersion());
}
close();
break;
}
String name = msg.getString();
String password = msg.getString();
try {
identity = acceptor.authenticate(name, password);
} catch (Exception e) {
logger.logThrow(
Level.FINEST, e,
"login authentication failed for name:{0}", name);
loginFailure("login failed", e);
break;
}
listener.newLogin(
identity, SimpleSgsProtocolImpl.this, new LoginHandler());
// Resume reading immediately
readNow();
break;
case SimpleSgsProtocol.SESSION_MESSAGE:
ByteBuffer clientMessage =
ByteBuffer.wrap(msg.getBytes(msg.limit() - msg.position()));
if (protocolHandler == null) {
// ignore message before authentication
if (logger.isLoggable(Level.FINE)) {
logger.log(
Level.FINE,
"Dropping early session message:{0} " +
"for protocol:{1}",
HexDumper.format(clientMessage, 0x50),
SimpleSgsProtocolImpl.this);
}
return;
}
// TBD: schedule a task to process this message?
protocolHandler.sessionMessage(clientMessage,
new RequestHandler());
break;
case SimpleSgsProtocol.CHANNEL_MESSAGE:
BigInteger channelRefId =
new BigInteger(1, msg.getBytes(msg.getShort()));
ByteBuffer channelMessage =
ByteBuffer.wrap(msg.getBytes(msg.limit() - msg.position()));
if (protocolHandler == null) {
// ignore message before authentication
if (logger.isLoggable(Level.FINE)) {
logger.log(
Level.FINE,
"Dropping early channel message:{0} " +
"for protocol:{1}",
HexDumper.format(channelMessage, 0x50),
SimpleSgsProtocolImpl.this);
}
return;
}
// TBD: schedule a task to process this message?
protocolHandler.channelMessage(
channelRefId, channelMessage, new RequestHandler());
break;
case SimpleSgsProtocol.LOGOUT_REQUEST:
if (protocolHandler == null) {
close();
return;
}
protocolHandler.logoutRequest(new LogoutHandler());
// Resume reading immediately
readNow();
break;
default:
if (logger.isLoggable(Level.SEVERE)) {
logger.log(
Level.SEVERE,
"unknown opcode 0x{0}",
Integer.toHexString(opcode));
}
close();
break;
}
}
/**
* Monitors the client's disconnection and closes this instance's
* underlying connection if the client hasn't closed the connection in
* a timely fashion.
*/
protected void monitorDisconnection() {
acceptor.monitorDisconnection(this);
}
/**
* A completion handler that is notified when the associated login
* request has completed processing.
*/
private class LoginHandler
implements RequestCompletionHandler<SessionProtocolHandler>
{
/** {@inheritDoc}
*
* <p>This implementation invokes the {@code get} method on the
* specified {@code future} to obtain the session's protocol
* handler.
*
* <p>If the login request completed successfully (without throwing an
* exception), it sends a logout success message to the client.
*
* <p>Otherwise, if the {@code get} invocation throws an {@code
* ExecutionException} and the exception's cause is a {@link
* LoginRedirectException}, it sends a login redirect message to
* the client with the redirection information obtained from the
* exception. If the {@code ExecutionException}'s cause is a
* {@link LoginFailureException}, it sends a login failure message
* to the client.
*
* <p>If the {@code get} method throws an exception other than
* {@code ExecutionException}, or the {@code ExecutionException}'s
* cause is not either a {@code LoginFailureException} or a {@code
* LoginRedirectException}, then a login failed message is sent to
* the client.
*/
public void completed(Future<SessionProtocolHandler> future) {
try {
protocolHandler = future.get();
loginSuccess();
} catch (ExecutionException e) {
// login failed
Throwable cause = e.getCause();
if (cause instanceof LoginRedirectException) {
// redirect
LoginRedirectException redirectException =
(LoginRedirectException) cause;
loginRedirect(redirectException.getNodeId(),
redirectException.getProtocolDescriptors());
} else if (cause instanceof LoginFailureException) {
loginFailure(cause.getMessage(), cause.getCause());
} else {
loginFailure(e.getMessage(), e.getCause());
}
} catch (Exception e) {
loginFailure(e.getMessage(), e.getCause());
}
}
}
/**
* A completion handler that is notified when its associated request has
* completed processing.
*/
private class RequestHandler implements RequestCompletionHandler<Void> {
/**
* {@inheritDoc}
*
* <p>This implementation schedules a task to resume reading.
*/
public void completed(Future<Void> future) {
try {
future.get();
} catch (ExecutionException e) {
if (logger.isLoggable(Level.FINE)) {
logger.logThrow(
Level.FINE, e, "Obtaining request result throws ");
}
Throwable cause = e.getCause();
if (cause instanceof RequestFailureException) {
FailureReason reason =
((RequestFailureException) cause).getReason();
if (reason.equals(FailureReason.DISCONNECT_PENDING)) {
// Don't read any more from client because session
// is disconnecting.
return;
}
// Assume other failures are transient.
}
} catch (Exception e) {
// TBD: Unknown exception: disconnect?
if (logger.isLoggable(Level.WARNING)) {
logger.logThrow(
Level.WARNING, e, "Obtaining request result throws ");
}
}
scheduleRead();
}
}
/**
* A completion handler that is notified when the associated logout
* request has completed processing.
*/
private class LogoutHandler implements RequestCompletionHandler<Void> {
/** {@inheritDoc}
*
* <p>This implementation sends a logout success message to the
* client .
*/
public void completed(Future<Void> future) {
try {
future.get();
} catch (Exception e) {
if (logger.isLoggable(Level.WARNING)) {
logger.logThrow(
Level.WARNING, e, "Obtaining logout result throws ");
}
}
logoutSuccess();
}
}
}