package com.linkedin.databus2.core.container.request;
/*
*
* Copyright 2013 LinkedIn Corp. All rights reserved
*
* 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.
*
*/
import java.nio.ByteOrder;
import org.apache.log4j.Logger;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.handler.codec.frame.FrameDecoder;
import com.linkedin.databus2.core.DatabusException;
import com.linkedin.databus2.core.container.ExtendedReadTimeoutHandler;
/** Decodes a binary stream with databus commands */
public class SimpleBinaryDatabusRequestDecoder extends FrameDecoder
//extends SimpleChannelUpstreamHandler
{
public static final String MODULE = SimpleBinaryDatabusRequestDecoder.class.getName();
public static final Logger LOG = Logger.getLogger(MODULE);
public static final String REQUEST_EXEC_HANDLER_NAME = "request execute hander";
private enum State
{
EXPECT_COMMAND,
INCOMPLETE_DATA,
EXPECT_MORE_DATA,
}
private final CommandsRegistry _commandsRegistry;
private final ExtendedReadTimeoutHandler _readTimeoutHandler;
private final ByteOrder _byteOrder;
private State _state;
private int _curOpcode = -1;
private BinaryCommandParser _currentParser;
public SimpleBinaryDatabusRequestDecoder(CommandsRegistry commandsRegistry,
ExtendedReadTimeoutHandler readTimeoutHandler,
ByteOrder byteOrder)
{
_state = State.EXPECT_COMMAND;
_commandsRegistry = commandsRegistry;
_readTimeoutHandler = readTimeoutHandler;
_byteOrder = byteOrder;
}
private void returnError(Channel responseChannel, ErrorResponse errResponse, ChannelBuffer buf,
State newState,
boolean logError,
boolean logBuffer)
{
returnError(responseChannel, errResponse, buf, logError, logBuffer);
_state = newState;
if (LOG.isTraceEnabled())
{
LOG.trace("new state after error: " + _state);
}
}
public static void returnError(Channel responseChannel, ErrorResponse errResponse,
ChannelBuffer buf, boolean logError, boolean logBuffer)
{
if (logError || LOG.isDebugEnabled())
{
if (errResponse.isExpected())
{
LOG.info("Returning expected error response:" + errResponse.getErrorCode());
}
else
{
LOG.error("error " + errResponse.getErrorCode(), errResponse.getCause());
if (null != buf && logBuffer)
{
buf.readerIndex(0);
LOG.error("buffer contents:" + ChannelBuffers.hexDump(buf));
}
}
}
responseChannel.write(errResponse);
if (null != buf)
{
//flush remaining bytes in the current buffer
buf.readerIndex(buf.readerIndex() + buf.readableBytes());
}
}
@Override
protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer)
throws Exception
{
int resetIndex = buffer.readerIndex();
Object result = null;
while (State.INCOMPLETE_DATA != _state && null == result && buffer.readable())
{
switch (_state)
{
case EXPECT_COMMAND:
{
byte opcode = buffer.readByte();
// if (debugEnabled) LOG.debug("received command: " +
// Integer.toHexString((opcode) & 0xFF));
if ( opcode != _curOpcode || -1 == _curOpcode)
{
_currentParser = _commandsRegistry.createParser(opcode, ctx.getChannel(), _byteOrder);
if (null == _currentParser)
{
_curOpcode = -1;
returnError(ctx.getChannel(),
ErrorResponse.createUnknownCommandResponse(opcode),
buffer, State.EXPECT_COMMAND,
true,
true);
}
else
{
_curOpcode = opcode;
_state = State.EXPECT_MORE_DATA;
ChannelPipeline pipe = ctx.getPipeline();
pipe.replace(REQUEST_EXEC_HANDLER_NAME, REQUEST_EXEC_HANDLER_NAME,
_commandsRegistry.createExecHandler(opcode, ctx.getChannel()));
_currentParser.startNew();
resetIndex = buffer.readerIndex();
}
}
else
{
_state = State.EXPECT_MORE_DATA;
_currentParser.startNew();
resetIndex = buffer.readerIndex();
}
break;
}
case EXPECT_MORE_DATA:
{
if (null == _currentParser)
{
returnError(ctx.getChannel(),
ErrorResponse.createInternalServerErrorResponse(
new DatabusException("expecting more data but no parser")),
buffer, State.EXPECT_COMMAND,
true,
true);
}
else
{
try
{
BinaryCommandParser.ParseResult parseResult = _currentParser.parseBinary(buffer);
switch (parseResult)
{
case DISCARD: { /* do nothing */ break; }
case INCOMPLETE_DATA: {_state = State.INCOMPLETE_DATA; break;}
case EXPECT_MORE: { _state = State.EXPECT_MORE_DATA; break; }
case PASS_THROUGH: { result = buffer; break; }
case DONE:
{
if (null == _currentParser.getError())
{
result = _currentParser.getCommand();
_state = State.EXPECT_COMMAND;
}
else
{
returnError(ctx.getChannel(),
_currentParser.getError(),
buffer, State.EXPECT_COMMAND,
true,
true);
}
break;
}
default:
{
returnError(ctx.getChannel(),
ErrorResponse.createInternalServerErrorResponse(
new DatabusException("unknown parser return code" + parseResult)),
buffer, State.EXPECT_COMMAND,
true,
true);
}
}
}
catch (UnsupportedProtocolVersionException upve)
{
returnError(ctx.getChannel(),
ErrorResponse.createUnsupportedProtocolVersionResponse(upve.getProtocolVerson()),
buffer, State.EXPECT_COMMAND,
true,
true);
}
catch (Exception ex)
{
returnError(ctx.getChannel(),
ErrorResponse.createInternalServerErrorResponse(ex),
buffer, State.EXPECT_COMMAND,
true,
true);
}
}
break;
}
default:
{
returnError(ctx.getChannel(),
ErrorResponse.createInternalServerErrorResponse(
new DatabusException("unknown state: " + _state)),
buffer, State.EXPECT_COMMAND,
true,
true);
}
}
}
if (State.INCOMPLETE_DATA == _state)
{
buffer.readerIndex(resetIndex);
result = null;
_state = State.EXPECT_MORE_DATA;
}
if (State.EXPECT_COMMAND == _state)
{
if (null != _readTimeoutHandler && _readTimeoutHandler.isStarted()) _readTimeoutHandler.stop();
}
else
{
if (null != _readTimeoutHandler && !_readTimeoutHandler.isStarted()) _readTimeoutHandler.start(ctx);
}
return result;
}
}