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}