package com.github.ompc.greys.core.server; import com.github.ompc.greys.core.advisor.AdviceListener; import com.github.ompc.greys.core.advisor.AdviceWeaver; import com.github.ompc.greys.core.advisor.Enhancer; import com.github.ompc.greys.core.advisor.InvokeTraceable; import com.github.ompc.greys.core.command.Command; import com.github.ompc.greys.core.command.Command.Action; import com.github.ompc.greys.core.command.Command.GetEnhancerAction; import com.github.ompc.greys.core.command.Command.Printer; import com.github.ompc.greys.core.command.Commands; import com.github.ompc.greys.core.command.QuitCommand; import com.github.ompc.greys.core.command.ShutdownCommand; import com.github.ompc.greys.core.exception.CommandException; import com.github.ompc.greys.core.exception.CommandInitializationException; import com.github.ompc.greys.core.exception.CommandNotFoundException; import com.github.ompc.greys.core.exception.GaExecuteException; import com.github.ompc.greys.core.util.GaStringUtils; import com.github.ompc.greys.core.util.LogUtil; import com.github.ompc.greys.core.util.affect.Affect; import com.github.ompc.greys.core.util.affect.EnhancerAffect; import com.github.ompc.greys.core.util.affect.RowAffect; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import java.io.IOException; import java.lang.instrument.Instrumentation; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.SocketChannel; import java.nio.charset.Charset; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import static com.github.ompc.greys.core.util.GaCheckUtils.$; import static com.github.ompc.greys.core.util.GaCheckUtils.$$; import static com.github.ompc.greys.core.util.GaStringUtils.ABORT_MSG; import static com.github.ompc.greys.core.util.GaStringUtils.getCauseMessage; import static java.lang.String.format; import static java.nio.ByteBuffer.wrap; import static org.apache.commons.lang3.StringUtils.isBlank; /** * 命令处理器 * Created by oldmanpushcart@gmail.com on 15/5/2. */ public class DefaultCommandHandler implements CommandHandler { private final Logger logger = LogUtil.getLogger(); private final GaServer gaServer; private final Instrumentation inst; public DefaultCommandHandler(GaServer gaServer, Instrumentation inst) { this.gaServer = gaServer; this.inst = inst; } @Override public void executeCommand(final String line, final Session session) throws IOException { final SocketChannel socketChannel = session.getSocketChannel(); // 只有输入了有效字符才进行命令解析 // 否则仅仅重绘提示符 if (isBlank(line)) { // 这里因为控制不好,造成了输出两次提示符的问题 // 第一次是因为这里,第二次则是下边(命令结束重绘提示符) // 这里做了一次取巧,虽然依旧是重绘了两次提示符,但在提示符之间增加了\r // 这样两次重绘都是在同一个位置,这样就没有人能发现,其实他们是被绘制了两次 logger.debug("reDrawPrompt for blank line."); reDrawPrompt(session, socketChannel, session.getCharset(), session.prompt()); return; } // don't ask why if ($(line)) { write(socketChannel, wrap($$())); reDrawPrompt(session, socketChannel, session.getCharset(), session.prompt()); return; } try { final Command command = Commands.getInstance().newCommand(line); execute(session, command); // 退出命令,需要关闭会话 if (command instanceof QuitCommand) { session.destroy(); } // 关闭命令,需要关闭整个服务端 else if (command instanceof ShutdownCommand) { DefaultCommandHandler.this.gaServer.destroy(); } // 其他命令需要重新绘制提示符 else { logger.debug("reDrawPrompt for command execute finished."); reDrawPrompt(session, socketChannel, session.getCharset(), session.prompt()); } } // 命令准备错误(参数校验等) catch (CommandException t) { final String message; if (t instanceof CommandNotFoundException) { message = format("Command \"%s\" not found.", t.getCommand()); } else if (t instanceof CommandInitializationException) { message = format("Command \"%s\" failed to initiate.", t.getCommand()); } else { message = format("Command \"%s\" preprocessor failed : %s.", t.getCommand(), getCauseMessage(t)); } write(socketChannel, message + "\n", session.getCharset()); reDrawPrompt(session, socketChannel, session.getCharset(), session.prompt()); logger.info(message, t); } // 命令执行错误 catch (GaExecuteException e) { logger.warn("command execute failed.", e); final String cause = GaStringUtils.getCauseMessage(e); if (StringUtils.isNotBlank(cause)) { write(socketChannel, format("Command execution failed. cause : %s\n", cause), session.getCharset()); } else { write(socketChannel, "Command execution failed.\n", session.getCharset()); } reDrawPrompt(session, socketChannel, session.getCharset(), session.prompt()); } } /* * 执行命令 */ private void execute(final Session session, final Command command) throws GaExecuteException, IOException { // 是否结束输入引用 final AtomicBoolean isFinishRef = new AtomicBoolean(false); // 消息发送者 final Printer printer = new Printer() { @Override public Printer print(boolean isF, String message) { if(isFinishRef.get()) { return this; } final BlockingQueue<String> writeQueue = session.getWriteQueue(); if (null != message) { if (!writeQueue.offer(message)) { logger.warn("offer message failed. write-queue.size() was {}", writeQueue.size()); } } if (isF) { finish(); } return this; } @Override public Printer println(boolean isF, String message) { return print(isF, message + "\n"); } @Override public Printer print(String message) { return print(false, message); } @Override public Printer println(String message) { return println(false, message); } @Override public void finish() { isFinishRef.set(true); } }; try { // 影响反馈 final Affect affect; final Action action = command.getAction(); // 无任何后续动作的动作 if (action instanceof Command.SilentAction) { affect = new Affect(); ((Command.SilentAction) action).action(session, inst, printer); } // 需要反馈行影响范围的动作 else if (action instanceof Command.RowAction) { affect = new RowAffect(); final RowAffect rowAffect = ((Command.RowAction) action).action(session, inst, printer); ((RowAffect) affect).rCnt(rowAffect.rCnt()); } // 需要做类增强的动作 else if (action instanceof GetEnhancerAction) { affect = new EnhancerAffect(); // 执行命令动作 & 获取增强器 final Command.GetEnhancer getEnhancer = ((GetEnhancerAction) action).action(session, inst, printer); final int lock = session.getLock(); final AdviceListener listener = getEnhancer.getAdviceListener(); final EnhancerAffect enhancerAffect = Enhancer.enhance( inst, lock, listener instanceof InvokeTraceable, getEnhancer.getPointCut() ); // 这里做个补偿,如果在enhance期间,unLock被调用了,则补偿性放弃 if (session.getLock() == lock) { // 注册通知监听器 AdviceWeaver.reg(lock, listener); if (!session.isSilent()) { printer.println(ABORT_MSG); } ((EnhancerAffect) affect).cCnt(enhancerAffect.cCnt()); ((EnhancerAffect) affect).mCnt(enhancerAffect.mCnt()); ((EnhancerAffect) affect).getClassDumpFiles().addAll(enhancerAffect.getClassDumpFiles()); } } // 其他自定义动作 else { // do nothing... affect = new Affect(); } if (!session.isSilent()) { // 记录下命令执行的执行信息 printer.print(false, affect.toString() + "\n"); } } // 命令执行错误必须纪录 catch (Throwable t) { throw new GaExecuteException(format("execute failed. sessionId=%s", session.getSessionId()), t); } // 跑任务 jobRunning(session, isFinishRef); } private void jobRunning(Session session, AtomicBoolean isFinishRef) throws IOException, GaExecuteException { final Thread currentThread = Thread.currentThread(); final BlockingQueue<String> writeQueue = session.getWriteQueue(); try { while (!session.isDestroy() && !currentThread.isInterrupted() && session.isLocked()) { // touch the session session.touch(); try { final String segment = writeQueue.poll(200, TimeUnit.MILLISECONDS); // 如果返回的片段为null,说明当前没有东西可写 if (null == segment) { // 当读到EOF的时候,同时Sender标记为isFinished // 说明整个命令结束了,标记整个会话为不可写,结束命令循环 if (isFinishRef.get()) { session.unLock(); break; } } // 读出了点东西 else { write(session.getSocketChannel(), segment, session.getCharset()); } } catch (InterruptedException e) { currentThread.interrupt(); } }//while command running } // 遇到关闭的链接可以忽略 catch (ClosedChannelException e) { logger.debug("session[{}] write failed, because socket broken.", session.getSessionId(), e); } } /* * 绘制提示符 */ private void reDrawPrompt(Session session, SocketChannel socketChannel, Charset charset, String prompt) throws IOException { if (!session.isSilent()) { write(socketChannel, prompt, charset); } } /* * 输出到网络 */ private void write(SocketChannel socketChannel, String message, Charset charset) throws IOException { write(socketChannel, charset.encode(message)); } private void write(SocketChannel socketChannel, ByteBuffer buffer) throws IOException { while (buffer.hasRemaining() && socketChannel.isConnected()) { if (-1 == socketChannel.write(buffer)) { // socket broken throw new IOException("write EOF"); } } } }