package org.fastcatsearch.transport.common; import java.io.IOException; import org.fastcatsearch.common.io.Streamable; import org.fastcatsearch.control.JobExecutor; import org.fastcatsearch.control.ResultFuture; import org.fastcatsearch.env.Environment; import org.fastcatsearch.ir.io.DataInput; import org.fastcatsearch.job.Job; import org.fastcatsearch.transport.ChannelBufferStreamInput; import org.fastcatsearch.transport.TransportChannel; import org.fastcatsearch.transport.TransportException; import org.fastcatsearch.transport.TransportModule; import org.fastcatsearch.transport.TransportOption; import org.fastcatsearch.transport.vo.StreamableThrowable; import org.fastcatsearch.util.DynamicClassLoader; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SimpleChannelUpstreamHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MessageChannelHandler extends SimpleChannelUpstreamHandler { private static Logger logger = LoggerFactory.getLogger(MessageChannelHandler.class); private Environment environment; private TransportModule transport; private JobExecutor jobExecutor; private String id; public MessageChannelHandler(String id, Environment environment, TransportModule transport, JobExecutor jobExecutor) { this.id = id; this.environment = environment; this.transport = transport; this.jobExecutor = jobExecutor; } @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { Object m = e.getMessage(); if (!(m instanceof ChannelBuffer)) { ctx.sendUpstream(e); return; } ChannelBuffer buffer = (ChannelBuffer) m; int readerIndex = buffer.readerIndex(); byte type = buffer.getByte(readerIndex); // 타입이 메시지가 아니면 올려보낸다. if (!TransportOption.isTypeMessage(type)) { ctx.sendUpstream(e); return; } // logger.debug("message received[{}]>> {}", type, e); buffer.readByte();// type을 읽어서 버린다. int dataLength = buffer.readInt(); int markedReaderIndex = buffer.readerIndex(); int expectedIndexReader = markedReaderIndex + dataLength; DataInput wrappedStream = new ChannelBufferStreamInput(buffer, dataLength); long requestId = wrappedStream.readLong(); byte status = wrappedStream.readByte(); // logger.debug("message status[{}]", status); // logger.debug("## readIndex={}, writerIndex={}", buffer.readerIndex(), buffer.writerIndex()); // logger.debug("## readString={}", wrappedStream.readString()); if (TransportOption.isRequest(status)) { // int readTo = wrappedStream.available(); // for (int i = 0; i < readTo; i++) { // wrappedStream.read(); // } handleRequest(ctx.getChannel(), wrappedStream, requestId); // logger.debug("buffer.readerIndex()={}, expectedIndexReader={}", buffer.readerIndex(), expectedIndexReader); if (buffer.readerIndex() != expectedIndexReader) { if (buffer.readerIndex() < expectedIndexReader) { // logger.warn("Message not fully read (request) for [{}] and action [{}], resetting", requestId); } else { // logger.warn("Message read past expected size (request) for [{}] and action [{}], resetting", requestId); } buffer.readerIndex(expectedIndexReader); } } else { // logger.debug("# status = {}", status); if (TransportOption.isError(status)) { // logger.debug("# status isError"); handlerErrorResponse(wrappedStream, requestId); } else if (TransportOption.isResponseObject(status)) { // logger.debug("# status isResponseObject"); handleObjectResponse(wrappedStream, requestId); } else { // logger.debug("# status isResponse streamable"); handleStreamableResponse(wrappedStream, requestId); } if (buffer.readerIndex() != expectedIndexReader) { if (buffer.readerIndex() < expectedIndexReader) { logger.warn("Message not fully read (response) for [{}] , error [{}], resetting", requestId, TransportOption.isError(status)); } else { logger.warn("Message read past expected size (response) for [{}] , error [{}], resetting", requestId, TransportOption.isError(status)); } buffer.readerIndex(expectedIndexReader); } } wrappedStream.close(); } @Override public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { logger.debug("CLOSED! {} {}", id, ctx.getChannel(), ctx.getChannel()); super.channelClosed(ctx, e); } @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { logger.error("에러발생 >> {}", e.getCause().getMessage()); } private void handleRequest(Channel channel, DataInput input, long requestId) throws IOException { // logger.debug("handleRequest "); final TransportChannel transportChannel = new TransportChannel(channel, requestId); try { String jobName = input.readString(); boolean isNoResult = input.readBoolean(); boolean isScheduled = input.readBoolean(); logger.debug("#### READ job = {}", jobName); Job requestJob = DynamicClassLoader.loadObject(jobName, Job.class); requestJob.setEnvironment(environment); if (isNoResult) { requestJob.setNoResult(); } requestJob.setScheduled(isScheduled); if (requestJob instanceof Streamable) { ((Streamable) requestJob).readFrom(input); } transport.execute(new RequestHandler(requestJob, transportChannel)); } catch (Exception e) { logger.error("", e); try { transportChannel.sendResponse(e); } catch (IOException e1) { logger.warn("Failed to send error message back to client", e); logger.warn("Actual Exception", e1); } } } private void handleStreamableResponse(DataInput input, long requestId) { try { String className = input.readString(); Streamable streamableResult = DynamicClassLoader.loadObject(className, Streamable.class); streamableResult.readFrom(input); // logger.debug("## Response-{} >> {}", requestId, streamableResult.toString()); transport.resultReceived(requestId, streamableResult); } catch (Exception e) { StreamableThrowable streamableThrowable = new StreamableThrowable(e); transport.exceptionReceived(requestId, streamableThrowable); } } private void handleObjectResponse(DataInput input, long requestId) { try { Object response = input.readGenericValue(); // logger.debug("## Response-{} >> {}", requestId, response); transport.resultReceived(requestId, response); } catch (Exception e) { StreamableThrowable streamableThrowable = new StreamableThrowable(e); transport.exceptionReceived(requestId, streamableThrowable); } } private void handlerErrorResponse(DataInput buffer, long requestId) { StreamableThrowable streamableThrowable = null; try { streamableThrowable = new StreamableThrowable(); streamableThrowable.readFrom(buffer); logger.debug("에러도착 Response-{} >> {}", requestId, streamableThrowable.getThrowable()); } catch (Exception e) { streamableThrowable = new StreamableThrowable(new TransportException("Failed to deserialize exception response from stream", e)); } transport.exceptionReceived(requestId, streamableThrowable); } class RequestHandler implements Runnable { private final Job job; private final TransportChannel transportChannel; public RequestHandler(Job job, TransportChannel transportChannel) { // logger.debug("Request Job >> {}", job.getClass().getName()); this.job = job; this.transportChannel = transportChannel; } @Override public void run() { try { if (job.isNoResult()) { jobExecutor.offer(job); } else { ResultFuture resultFuture = jobExecutor.offer(job); Object obj = resultFuture.take(); // logger.debug("## RequestHandler {} result >> {}", job.getClass().getSimpleName(), obj); if (obj instanceof Streamable) { Streamable result = (Streamable) obj; transportChannel.sendResponse(result); } else if (obj instanceof Throwable) { throw (Throwable) obj; } else { // 전송된 job의 결과가 streamable이 아니라면 어떻게 할까? transportChannel.sendResponse(obj); } } // logger.debug("Request Job Result >> {}", obj); } catch (Throwable e) { logger.error("Fail to write response message", e); // we can only send a response transport is started.... try { transportChannel.sendResponse(e); } catch (IOException e1) { logger.warn("Failed to send error message back to client", e1); logger.warn("Actual Exception", e); } } } } }