001package gu.dtalk; 002 003import java.util.Collections; 004import java.util.Map; 005import java.util.concurrent.ExecutorService; 006import java.util.concurrent.LinkedBlockingQueue; 007import java.util.concurrent.ThreadPoolExecutor; 008import java.util.concurrent.TimeUnit; 009 010import com.google.common.base.MoreObjects; 011import com.google.common.base.Strings; 012import com.google.common.util.concurrent.MoreExecutors; 013import com.google.common.util.concurrent.ThreadFactoryBuilder; 014 015import gu.dtalk.Ack.Status; 016import gu.dtalk.CmdItem.ICmdAdapter; 017import gu.dtalk.exception.CmdExecutionException; 018import gu.dtalk.exception.UnsupportCmdException; 019import gu.simplemq.Channel; 020import gu.simplemq.IMessageAdapter; 021import gu.simplemq.exceptions.SmqUnsubscribeException; 022import gu.simplemq.redis.RedisFactory; 023 024/** 025 * 任务执行对象<br> 026 * 将{@link ICmdAdapter}实例封装为执行队列任务的{@link IMessageAdapter}<br> 027 * 调用{@link #register()}将当前对象注册到队列<br> 028 * 调用{@link #unregister()}从队列注册将当前对象<br> 029 * @author guyadong 030 * 031 */ 032public class TaskAdapter implements IMessageAdapter<Map<String, Object>>{ 033 /** 任务响应频道名 */ 034 public static final String P_TASK_ACK = "taskAck"; 035 /** 任务序列号 */ 036 public static final String P_TASK_ID = "taskId"; 037 private final Channel<Map<String, Object>> channel; 038 private ICmdAdapter cmdAdapter; 039 /** 执行publish的线程池对象 */ 040 protected static final ExecutorService publishExecutor = MoreExecutors.getExitingExecutorService( 041 new ThreadPoolExecutor(1, 1, 042 0L, TimeUnit.MILLISECONDS, 043 new LinkedBlockingQueue<Runnable>(), 044 new ThreadFactoryBuilder().setNameFormat("task-ack-publish-%d").build())); 045 /** 046 * @param queue 队列名称 047 */ 048 public TaskAdapter(String queue) { 049 channel = new Channel<Map<String, Object>>(queue,this ){}; 050 } 051 /** 052 * 处理收到的任务包 053 * @see gu.simplemq.IMessageAdapter#onSubscribe(java.lang.Object) 054 */ 055 @Override 056 public final void onSubscribe(Map<String, Object> parameter) throws SmqUnsubscribeException { 057 parameter = MoreObjects.firstNonNull(parameter, Collections.<String, Object>emptyMap()); 058 // 返回值 059 Object res = null; 060 // 抛出异常 061 Exception err = null; 062 try { 063 if(cmdAdapter == null){ 064 throw new UnsupportCmdException("UNSUPPORTED TASK"); 065 } 066 res = cmdAdapter.apply(parameter); 067 } catch (CmdExecutionException e) { 068 err = e; 069 } catch (UnsupportCmdException e) { 070 err = e; 071 } 072 073 Object tack = parameter.get(P_TASK_ACK); 074 Object tid = parameter.get(P_TASK_ID); 075 // 如果命令参数有提供响应频道名则将命令执行情况通过Ack对象发送到指定的频道 076 if(tack instanceof String){ 077 final String ackChannel = (String) tack; 078 if(!Strings.isNullOrEmpty(ackChannel)){ 079 Object ack = makeAck(res,err,ackChannel, (tid instanceof Number)? (Number)tid:-1); 080 publish(ackChannel,ack); 081 } 082 } 083 } 084 /** 085 * 创建响应消息对象 086 * @param res 设备命令执行结果,对于没有返回值的命令为{@code null} 087 * @param err 设备命令执行异常 088 * @param ackChannel 设备命令响应频道 089 * @param taskid 任务序列号 090 * @return 返回响应消息对象 091 */ 092 @SuppressWarnings("unchecked") 093 protected <T, ACK>ACK makeAck(T res,Exception err,String ackChannel, Number taskid) { 094 final Ack<T> ack = new Ack<T>() 095 .setStatus(Status.OK) 096 .setValue(res) 097 .setCmdSn(taskid.longValue()); 098 099 if(err != null){ 100 ack.setStatus(Status.ERROR).setErrorMessage(err.getMessage()); 101 } 102 return (ACK) ack; 103 } 104 /** 105 * 返回队列名称 106 * @return 107 */ 108 public String getQueue(){ 109 return channel.name; 110 } 111 /** 112 * 注册当前对象到{@link #channel}指定的队列 113 * @return 当前对象 114 */ 115 public final TaskAdapter register(){ 116 RedisFactory.getConsumer().register(channel); 117 return this; 118 } 119 /** 120 * 将当前对象从{@link #channel}指定的队列注销 121 * @return 当前对象 122 */ 123 public final TaskAdapter unregister(){ 124 RedisFactory.getConsumer().unregister(channel); 125 return this; 126 } 127 /** 128 * @return cmdAdapter 129 */ 130 public ICmdAdapter getCmdAdapter() { 131 return cmdAdapter; 132 } 133 /** 134 * @param cmdAdapter 要设置的 cmdAdapter 135 * @return 当前对象 136 */ 137 public TaskAdapter setCmdAdapter(ICmdAdapter cmdAdapter) { 138 this.cmdAdapter = cmdAdapter; 139 return this; 140 } 141 /** 142 * 向{@code ackChannel}指定的频道发送消息响应对象{@code ack} 143 * @param ackChannel 144 * @param ack 145 */ 146 private <T>void publish(final String ackChannel,final T ack){ 147 if(ack != null){ 148 publishExecutor.execute(new Runnable() { 149 @Override 150 public void run() { 151 Channel<T> ch = new Channel<T>(ackChannel,ack.getClass()); 152 RedisFactory.getPublisher().publish(ch, ack); 153 } 154 }); 155 } 156 } 157}