package cz.cuni.mff.d3s.been.socketworks.twoway; import java.util.Iterator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import cz.cuni.mff.d3s.been.mq.MessagingException; import cz.cuni.mff.d3s.been.socketworks.SocketHandlerException; final class ReplyingWorker implements Runnable { private static final Logger log = LoggerFactory.getLogger(ReplyingWorker.class); private final FrameSink replySink; private ReadReplyHandler handler; private Frames work; private Object runLock; private ReplyingWorker(FrameSink replySink) { this.replySink = replySink; this.runLock = new Object(); } /** * Create a worker that dumps the responses to a specific {@link FrameSink}. * * @param replySink * Sink to dump responses to * * @return A ready worker */ public static ReplyingWorker create(FrameSink replySink) { return new ReplyingWorker(replySink); } /** * Set up input and processor for this thread. * * @param work * Set the work that this handler should process * @param handler * Set the handler which will perform this worker's logic */ void setUp(ReadReplyHandler handler, Frames work) { // we don't want people to tamper with our data while processing synchronized (runLock) { this.work = work; this.handler = handler; } } @Override public void run() { try { doRun(); } catch (Throwable t) { log.error( "Worker using handler '{}' died unexpectedly. Requesting side will be left hanging.", (handler == null) ? "null" : handler.getClass().getSimpleName(), t); } } private void doRun() throws Throwable { // lock here to prevent input data from being changed under our hands synchronized (runLock) { if (work == null) { log.error("No data to process"); return; } if (handler == null) { log.error("No handler set"); return; } final Frames response = Frames.create(); final Iterator<byte[]> wi = work.iterator(); // copy header with client ID if (!wi.hasNext()) { log.error("No header in supplied message ({})", work.toString()); return; } response.add(wi.next()); // copy separator if (!wi.hasNext()) { log.error("No separator in supplied message ({})", work.toString()); return; } response.add(wi.next()); // handle content if (!wi.hasNext()) { log.error("No content in supplied message ({})", work.toString()); } String handlerResponse = ""; try { handlerResponse = handleFrame(wi.next()); } catch (MessagingException e) { log.error("Transport logic error on message {}", work.toString(), e); } catch (SocketHandlerException e) { log.error("Handler error on message {}", work.toString(), e); } catch (InterruptedException e) { log.error("Handler {} interrupted", handler.toString(), e); } catch (Throwable t) { log.error("Unknown error on message {}", work.toString(), t); } finally { response.add(handlerResponse.getBytes()); try { replySink.receiveFromBuddy(response); } catch (MessagingException e) { log.error("Failed to send reply, thread pair will block", e); } if (wi.hasNext()) { log.warn("Trailing content (from frame 3 onward) truncated for message {}", work.toString()); } } } // give the user a clue that he can recycle his handler handler.markAsRecyclable(); } /** * Crunch the (presumably) last frame of work with {@link #handler} and return * the response. * * @param frame * Frame to crunch * * @return Response to this frame * * @throws MessagingException * In case the underlying message channels go bad * @throws SocketHandlerException * If the user signals an error or supplies no response * @throws InterruptedException * If the user handler gets interrupted */ private final String handleFrame(byte[] frame) throws MessagingException, SocketHandlerException, InterruptedException { final String frameContent = new String(frame); final String responseContent = handler.handle(frameContent); if (responseContent == null) { throw new SocketHandlerException(String.format( "Handler %s provided no response for message (%s)", handler.toString(), work.toString())); } return responseContent; } }