package lsr.paxos.replica;
import static lsr.common.ProcessDescriptor.processDescriptor;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import lsr.common.ClientCommand;
import lsr.common.ClientReply;
import lsr.common.nio.PacketHandler;
import lsr.common.nio.ReaderAndWriter;
import lsr.common.nio.SelectorThread;
import lsr.paxos.idgen.IdGenerator;
import lsr.paxos.idgen.IdGeneratorType;
import lsr.paxos.idgen.SimpleIdGenerator;
import lsr.paxos.idgen.TimeBasedIdGenerator;
import lsr.paxos.idgen.ViewEpochIdGenerator;
import lsr.paxos.storage.Storage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is used to handle one client connection. It uses
* <code>ReaderAndWriter</code> for writing and reading packets from clients.
* <p>
* First it initializes client connection reading client id (or granting him a
* new one). After successful initialization commands will be received, and
* reply can be send to clients.
*
* @see ReaderAndWriter
*/
public class NioClientProxy implements ClientProxy {
private final ClientRequestManager requestManager;
private long clientId;
private final ByteBuffer readBuffer = ByteBuffer.allocate(processDescriptor.clientRequestBufferSize);
private final ReaderAndWriter readerAndWriter;
/** Generator for client IDs */
public static IdGenerator idGenerator;
/**
* Creates new client proxy.
*
* @param readerAndWriter - used to send and receive data from clients
* @param requestManager - callback for executing command from clients
* @param idGenerator - generator used to generate id's for clients
*/
public NioClientProxy(ReaderAndWriter readerAndWriter, ClientRequestManager requestManager) {
this.readerAndWriter = readerAndWriter;
this.requestManager = requestManager;
if (logger.isInfoEnabled())
logger.info("New client connection: {}", readerAndWriter.socketChannel.socket());
this.readerAndWriter.setPacketHandler(new InitializePacketHandler());
}
/**
* Sends the reply to client held by this proxy. This method has to be
* called after client is initialized.
*
* @param clientReply - reply send to underlying client
*/
public void send(final ClientReply clientReply) {
getSelectorThread().beginInvoke(new Runnable() {
public void run() {
try {
sendInternal(clientReply);
} catch (IOException e) {
// cannot send message to the client; Client should send
// request again
logger.error(
"Could not send reply to client. Discarding reply {} (reason: {})",
clientReply, e);
}
}
});
}
private void sendInternal(ClientReply clientReply) throws IOException {
readerAndWriter.send(clientReply.toByteArray());
}
/**
* executes command from byte buffer
*
* @throws InterruptedException
*/
private void execute(ByteBuffer buffer) throws InterruptedException {
ClientCommand command = new ClientCommand(buffer);
requestManager.onClientRequest(command, this);
}
/**
* Waits for first byte, 'T' or 'F' which specifies whether we should grant
* new id for this client, or it has one already.
*/
private class InitializePacketHandler implements PacketHandler {
public InitializePacketHandler() {
readBuffer.clear();
readBuffer.limit(1);
}
public void finished() {
readBuffer.rewind();
byte b = readBuffer.get();
if (b == lsr.paxos.client.Client.REQUEST_NEW_ID) {
// grant new id for client
clientId = idGenerator.next();
byte[] bytesClientId = new byte[8];
ByteBuffer.wrap(bytesClientId).putLong(clientId);
readerAndWriter.send(bytesClientId);
readerAndWriter.setPacketHandler(new ClientCommandPacketHandler());
} else if (b == lsr.paxos.client.Client.HAVE_CLIENT_ID) {
// wait for receiving id from client
readerAndWriter.setPacketHandler(new ClientIdPacketHandler());
} else {
// command client is incorrect; close the underlying connection
logger.error(
"Incorrect initialization header. Expected '{}' or '{}' but received {}",
lsr.paxos.client.Client.REQUEST_NEW_ID,
lsr.paxos.client.Client.HAVE_CLIENT_ID, b);
readerAndWriter.scheduleClose();
}
}
public ByteBuffer getByteBuffer() {
return readBuffer;
}
}
/**
* Waits for the id from client. After receiving it starts receiving client
* commands packets.
*/
private class ClientIdPacketHandler implements PacketHandler {
public ClientIdPacketHandler() {
readBuffer.clear();
readBuffer.limit(8);
}
public void finished() {
readBuffer.rewind();
clientId = readBuffer.getLong();
readerAndWriter.setPacketHandler(new ClientCommandPacketHandler());
}
public ByteBuffer getByteBuffer() {
return readBuffer;
}
}
/**
* Waits for the header and then for the message from the client.
*/
private class ClientCommandPacketHandler implements PacketHandler {
// TODO: (JK) consider keeping the once allocated bigger buffer
/**
* Client request may be bigger than default buffer size; in such case,
* new buffer will be created and used for the request
*/
private ByteBuffer currentBuffer;
private boolean header = true;
public ClientCommandPacketHandler() {
currentBuffer = readBuffer;
currentBuffer.clear();
currentBuffer.limit(ClientCommand.HEADERS_SIZE);
}
public void finished() throws InterruptedException {
if (header) {
assert currentBuffer == readBuffer : "Default buffer should be used for reading header";
readBuffer.position(ClientCommand.HEADER_VALUE_SIZE_OFFSET);
int sizeOfValue = readBuffer.getInt();
if (ClientCommand.HEADERS_SIZE + sizeOfValue <= readBuffer.capacity()) {
readBuffer.limit(ClientCommand.HEADERS_SIZE + sizeOfValue);
} else {
currentBuffer = ByteBuffer.allocate(ClientCommand.HEADERS_SIZE + sizeOfValue);
currentBuffer.put(readBuffer.array(), 0, ClientCommand.HEADERS_SIZE);
}
} else {
currentBuffer.flip();
execute(currentBuffer);
// for reading header we can use default buffer
currentBuffer = readBuffer;
readBuffer.clear();
readBuffer.limit(ClientCommand.HEADERS_SIZE);
}
header = !header;
readerAndWriter.setPacketHandler(this);
}
public ByteBuffer getByteBuffer() {
return currentBuffer;
}
}
public String toString() {
return "client: " + clientId + " - " + readerAndWriter.socketChannel.socket().getPort();
}
public SelectorThread getSelectorThread() {
return readerAndWriter.getSelectorThread();
}
public void closeConnection() {
readerAndWriter.close();
}
public static void createIdGenerator(Storage storage) {
IdGeneratorType igt;
try {
igt = IdGeneratorType.valueOf(processDescriptor.clientIDGenerator);
} catch (IllegalArgumentException e) {
throw new RuntimeException("Unknown id generator: " +
processDescriptor.clientIDGenerator + ". Valid options: " +
Arrays.toString(IdGeneratorType.values()), e);
}
switch (igt) {
case Simple:
idGenerator = new SimpleIdGenerator();
break;
case TimeBased:
idGenerator = new TimeBasedIdGenerator();
break;
case ViewEpoch:
idGenerator = new ViewEpochIdGenerator(storage.getRunUniqueId());
break;
default:
throw new RuntimeException("Unknown id generator: " +
processDescriptor.clientIDGenerator +
". Valid options: " +
Arrays.toString(IdGeneratorType.values()));
}
}
private final static Logger logger = LoggerFactory.getLogger(NioClientProxy.class);
}