package voldemort.server.protocol.vold;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.apache.log4j.Logger;
import voldemort.VoldemortException;
import voldemort.common.VoldemortOpCode;
import voldemort.common.nio.ByteBufferBackedInputStream;
import voldemort.common.nio.ByteBufferContainer;
import voldemort.server.RequestRoutingType;
import voldemort.server.StoreRepository;
import voldemort.server.protocol.AbstractRequestHandler;
import voldemort.server.protocol.RequestHandler;
import voldemort.server.protocol.StreamRequestHandler;
import voldemort.store.ErrorCodeMapper;
import voldemort.store.Store;
import voldemort.utils.ByteArray;
import voldemort.versioning.ObsoleteVersionException;
/**
* Server-side request handler for voldemort native client protocol
*
*
*/
public class VoldemortNativeRequestHandler extends AbstractRequestHandler implements RequestHandler {
private static final Logger logger = Logger.getLogger(VoldemortNativeRequestHandler.class);
private final int protocolVersion;
public VoldemortNativeRequestHandler(ErrorCodeMapper errorMapper,
StoreRepository repository,
int protocolVersion) {
super(errorMapper, repository);
if(protocolVersion < 0 || protocolVersion > 3)
throw new IllegalArgumentException("Unknown protocol version: " + protocolVersion);
this.protocolVersion = protocolVersion;
}
private ClientRequestHandler getClientRequestHandler(byte opCode,
Store<ByteArray, byte[], byte[]> store)
throws IOException {
switch(opCode) {
case VoldemortOpCode.GET_OP_CODE:
return new GetRequestHandler(store, protocolVersion);
case VoldemortOpCode.GET_ALL_OP_CODE:
return new GetAllRequestHandler(store, protocolVersion);
case VoldemortOpCode.PUT_OP_CODE:
return new PutRequestHandler(store, protocolVersion);
case VoldemortOpCode.DELETE_OP_CODE:
return new DeleteRequestHandler(store, protocolVersion);
case VoldemortOpCode.GET_VERSION_OP_CODE:
return new GetVersionRequestHandler(store, protocolVersion);
default:
throw new IOException("Unknown op code: " + opCode);
}
}
@Override
public StreamRequestHandler handleRequest(final DataInputStream inputStream,
final DataOutputStream outputStream)
throws IOException {
return handleRequest(inputStream, outputStream, null);
}
private void clearBuffer(ByteBufferContainer outputContainer) {
if(outputContainer != null) {
outputContainer.getBuffer().clear();
}
}
@Override
public StreamRequestHandler handleRequest(final DataInputStream inputStream,
final DataOutputStream outputStream,
final ByteBufferContainer outputContainer)
throws IOException {
long startTimeMs = -1;
long startTimeNs = -1;
if(logger.isDebugEnabled()) {
startTimeMs = System.currentTimeMillis();
startTimeNs = System.nanoTime();
}
byte opCode = inputStream.readByte();
String storeName = inputStream.readUTF();
RequestRoutingType routingType = getRoutingType(inputStream);
Store<ByteArray, byte[], byte[]> store = getStore(storeName, routingType);
if(store == null) {
clearBuffer(outputContainer);
writeException(outputStream, new VoldemortException("No store named: '" + storeName
+ "'."));
return null;
}
ClientRequestHandler requestHandler = getClientRequestHandler(opCode, store);
try {
requestHandler.parseRequest(inputStream);
requestHandler.processRequest();
} catch (VoldemortException e) {
// Put generates lot of ObsoleteVersionExceptions, suppress them
// they are harmless and indicates normal mode of operation.
if(!(e instanceof ObsoleteVersionException)) {
logger.error("Store: " + storeName + ". Error: " + e.getMessage());
}
clearBuffer(outputContainer);
writeException(outputStream, e);
return null;
}
// We are done with Input, clear the buffers
clearBuffer(outputContainer);
int size = requestHandler.getResponseSize();
if(outputContainer != null) {
outputContainer.growBuffer(size);
}
requestHandler.writeResponse(outputStream);
outputStream.flush();
if(logger.isDebugEnabled()) {
String debugPrefix = "OpCode: " + opCode + ", started at: " + startTimeMs
+ ", handlerRef: " + System.identityHashCode(inputStream)
+ ", Elapsed : " + (System.nanoTime() - startTimeNs) + " ns, ";
logger.debug(debugPrefix + requestHandler.getDebugMessage() );
}
return null;
}
private RequestRoutingType getRoutingType(DataInputStream inputStream) throws IOException {
RequestRoutingType routingType = RequestRoutingType.NORMAL;
if(protocolVersion > 0) {
boolean isRouted = inputStream.readBoolean();
routingType = RequestRoutingType.getRequestRoutingType(isRouted, false);
}
if(protocolVersion > 1) {
int routingTypeCode = inputStream.readByte();
routingType = RequestRoutingType.getRequestRoutingType(routingTypeCode);
}
return routingType;
}
/**
* This is pretty ugly. We end up mimicking the request logic here, so this
* needs to stay in sync with handleRequest.
*/
@Override
public boolean isCompleteRequest(final ByteBuffer buffer) throws VoldemortException {
DataInputStream inputStream = new DataInputStream(new ByteBufferBackedInputStream(buffer));
try {
byte opCode = inputStream.readByte();
// Store Name
inputStream.readUTF();
// Store routing type
getRoutingType(inputStream);
switch(opCode) {
case VoldemortOpCode.GET_VERSION_OP_CODE:
if(!GetVersionRequestHandler.isCompleteRequest(inputStream, buffer))
return false;
break;
case VoldemortOpCode.GET_OP_CODE:
if(!GetRequestHandler.isCompleteRequest(inputStream, buffer, protocolVersion))
return false;
break;
case VoldemortOpCode.GET_ALL_OP_CODE:
if(!GetAllRequestHandler.isCompleteRequest(inputStream, buffer, protocolVersion))
return false;
break;
case VoldemortOpCode.PUT_OP_CODE: {
if(!PutRequestHandler.isCompleteRequest(inputStream, buffer, protocolVersion))
return false;
break;
}
case VoldemortOpCode.DELETE_OP_CODE: {
if(!DeleteRequestHandler.isCompleteRequest(inputStream, buffer))
return false;
break;
}
default:
throw new VoldemortException(" Unrecognized Voldemort OpCode " + opCode);
}
// This should not happen, if we reach here and if buffer has more
// data, there is something wrong.
if(buffer.hasRemaining()) {
logger.info("Probably a client bug, Discarding additional bytes in isCompleteRequest. Opcode: "
+ opCode + ", remaining bytes: " + buffer.remaining());
}
return true;
} catch(IOException e) {
// This could also occur if the various methods we call into
// re-throw a corrupted value error as some other type of exception.
// For example, updating the position on a buffer past its limit
// throws an InvalidArgumentException.
if(logger.isDebugEnabled())
logger.debug("Probable partial read occurred causing exception", e);
return false;
}
}
private void writeException(DataOutputStream stream, VoldemortException e) throws IOException {
short code = getErrorMapper().getCode(e);
stream.writeShort(code);
stream.writeUTF(e.getMessage());
stream.flush();
}
}