/* ************************************************************************ # # DivConq # # http://divconq.com/ # # Copyright: # Copyright 2014 eTimeline, LLC. All rights reserved. # # License: # See the license.txt file in the project's top-level directory for details. # # Authors: # * Andy White # ************************************************************************ */ package divconq.ctp; import divconq.ctp.cmd.ResponseCommand; import divconq.hub.Hub; import divconq.lang.op.FuncCallback; import divconq.lang.op.OperationContext; import divconq.log.Logger; import divconq.struct.RecordStruct; import divconq.work.IWork; import divconq.work.Task; import divconq.work.TaskRun; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelFutureListener; public class CtpAdapter { protected boolean switchingProtocols = false; protected CtpCommandMapper mapper = CtpCommandMapper.instance; // for basic operations protected ICommandHandler handler = null; protected ICtpChannel channel = null; protected OperationContext context = null; protected CtpCommand current = null; protected FuncCallback<RecordStruct> currCallback = null; public void setChannel(ICtpChannel v) { this.channel = v; } public ICtpChannel getChannel() { return this.channel; } protected void setSwitchingProtocols(boolean v) { this.switchingProtocols = v; } public boolean isSwitchingProtocols() { return this.switchingProtocols; } public void setMapper(CtpCommandMapper v) { this.mapper = v; } public void setHandler(ICommandHandler v) { this.handler = v; } public OperationContext getContext() { return this.context; } public CtpAdapter(OperationContext ctx) { this.context = ctx; } public CtpAdapter() { this.context = OperationContext.get(); } // used internally // return false if need more public boolean decode(ByteBuf buf) throws Exception { OperationContext.set(this.context); // get the command to continue to decode itself if (this.current != null) return this.current.decode(buf); if (buf.readableBytes() < 1) return false; int cmdtype = buf.readUnsignedByte(); this.current = this.mapper.map(cmdtype); // TODO handle error/close? if (this.current == null) return false; // get the command to decode itself return this.current.decode(buf); } // used internally public void handleCommand() { OperationContext.set(this.context); CtpCommand cmd = this.current; this.current = null; //System.out.println("Got Command ------ " + cmd.getClass().getName()); //if (!this.readRequested) // System.out.println("-------- Got Command when no read was requested ------ " + cmd.getClass().getName()); //this.readRequested = false; if (cmd == CtpCommand.ALIVE) { //System.out.println("Ctp Adapter Touched"); // TODO touch session... this.read(); // indicate we want another message } else if (cmd == CtpCommand.EXIT_NO_SIGN_OUT) { // TODO exit this.close(); } else if (cmd == CtpCommand.EXIT_SIGN_OUT) { // TODO sign out and exit... this.close(); } else if (cmd instanceof ResponseCommand) { FuncCallback<RecordStruct> cb = this.currCallback; if (cb != null) { this.currCallback = null; // TODO - rather than create a new task all the time, possibly make this ctpfclient class be an always running // never timing out task with state that can be resumed intermedentately to do different tasks... // then use with Progress and Read, etc too. // maybe set timeout to 30 minutes and close connection if idle that long? or just have the keep alive scheduler // also touch this? // put the call back into the work pool, don't tie up the IO thread Task t = new Task() .withContext(cb.getContext().subContext()) .withWork(new IWork() { @Override public void run(TaskRun trun) { RecordStruct res = ((ResponseCommand)cmd).getResult(); if (res != null) { trun.getContext().logResult(res); cb.setResult(res); } cb.complete(); // no, let caller decide when to read //CtpAdapter.this.read(); trun.complete(); } }); Hub.instance.getWorkPool().submit(t); } else { // if we get a response but no callback is in queue - TODO log? // be sure to read (or close) this.read(); } } else { // make sure handle runs in WorkPool so IO threads are free try { this.handler.handle(cmd, this); } catch (Exception x) { // TODO exit/abort System.out.println("Error with command handler: " + x); } } } // when using this be sure to issue "adapter.read()" in callback public void sendCommand(CtpCommand cmd, FuncCallback<RecordStruct> cb) throws Exception { this.currCallback = cb; this.sendCommandNotify(cmd, null); } public void sendCommand(CtpCommand cmd) throws Exception { this.sendCommandNotify(cmd, null); } public void sendCommandNotify(CtpCommand cmd, ChannelFutureListener listener) throws Exception { ByteBuf buf = cmd.encode(); //System.out.println("writing buffer: " + buf.readableBytes()); // TODO write to channel, not release //buf.release(); try { if (this.channel != null) this.channel.send(buf, listener); } catch (Exception x) { Logger.error("Error writing Ctp message: " + cmd); Logger.error("Error writing Ctp message: " + x); this.close(); } } //protected boolean init = false; public void read() { if (this.switchingProtocols) { // TODO send CTP_S_CMD_RESPONSE_SUCCESS when read called, if } if (this.channel != null) this.channel.read(); /* if (!this.init) { this.init = true; this.current = new InitCommand(); this.handleCommand(); } */ } public void close() { // TODO more if (this.channel != null) { this.channel.close(); this.channel = null; } this.handler.close(); } }