001package net.gdface.facelog.client;
002
003import static com.google.common.base.Preconditions.*;
004
005import java.io.Closeable;
006import java.lang.reflect.Proxy;
007import java.net.MalformedURLException;
008import java.net.URI;
009import java.net.URISyntaxException;
010import java.net.URL;
011import java.util.List;
012import java.util.Map;
013import java.util.Map.Entry;
014import java.util.Set;
015import java.util.concurrent.ExecutionException;
016
017import com.google.common.base.Function;
018import com.google.common.base.MoreObjects;
019import com.google.common.base.Predicate;
020import com.google.common.base.Strings;
021import com.google.common.base.Supplier;
022import com.google.common.base.Suppliers;
023import com.google.common.base.Throwables;
024import com.google.common.collect.Maps;
025import com.google.common.collect.Sets;
026import com.google.common.net.HostAndPort;
027
028import gu.dtalk.MenuItem;
029import gu.dtalk.cmd.CmdManager;
030import gu.dtalk.cmd.TaskManager;
031import gu.dtalk.engine.BaseDispatcher;
032import gu.dtalk.engine.CmdDispatcher;
033import gu.dtalk.engine.TaskDispatcher;
034import gu.simplemq.IMessageAdapter;
035import gu.simplemq.IMessageQueueFactory;
036import gu.simplemq.ISubscriber;
037import gu.simplemq.MessageQueueFactorys;
038import gu.simplemq.json.BaseJsonEncoder;
039import gu.sql2java.SimpleLog;
040import net.gdface.facelog.IFaceLog;
041import net.gdface.facelog.MQParam;
042import net.gdface.facelog.ServiceSecurityException;
043import net.gdface.facelog.Token;
044import net.gdface.facelog.Token.TokenType;
045import net.gdface.facelog.client.dtalk.DtalkEngineForFacelog;
046import net.gdface.facelog.db.DeviceBean;
047import net.gdface.facelog.db.PersonBean;
048import net.gdface.facelog.hb.DynamicChannelListener;
049import net.gdface.facelog.hb.DeviceHeartbeat;
050import net.gdface.facelog.mq.BaseServiceHeartbeatListener;
051import net.gdface.facelog.mq.ServiceHeartbeatAdapter;
052import net.gdface.facelog.mq.ServiceHeartbeatListener;
053import net.gdface.facelog.mq.ServiceHeartbeatPackage;
054import net.gdface.facelog.thrift.IFaceLogThriftClient;
055import net.gdface.facelog.thrift.IFaceLogThriftClientAsync;
056import net.gdface.thrift.ClientFactory;
057import net.gdface.utils.Delegator;
058import net.gdface.utils.NetworkUtil;
059
060/**
061 * client端便利性扩展工具类
062 * @author guyadong
063 *
064 */
065public class ClientExtendTools{
066        private final IFaceLog syncInstance;
067        private final IFaceLogThriftClientAsync asyncInstance;
068        private final ClientFactory factory;
069        private TokenHelper tokenHelper;
070        private volatile Map<MQParam, String> redisParameters = null;
071        private volatile Map<MQParam, String> mqParameters = null;
072        private Map<String, String> faceapiParameters = null;
073
074        private final ServiceHeartbeatListener tokenRefreshListener = new BaseServiceHeartbeatListener(){
075                @Override
076                public boolean doServiceOnline(ServiceHeartbeatPackage heartbeatPackage){
077                        // 执行令牌刷新,更新redis参数
078                        return tokenRefresh !=null && tokenRefresh.refresh();
079                }};
080
081        private final DispatcherListener dispatcherListener = new DispatcherListener();
082        private TokenRefresh tokenRefresh;
083        private class TokenRefresh implements Supplier<Token>{
084                private Token token;
085                private final Object lock = new Object();
086                private volatile boolean relead = true;
087                private final TokenHelper helper;
088                private TokenRefresh(TokenHelper helper,Token token) {
089                        this.helper = checkNotNull(helper,"helper is null");
090                        this.token = checkNotNull(token,"token is null");
091                }
092                @Override
093                public Token get() {
094                        // double check
095                        if(relead){
096                                synchronized (lock) {
097                                        if(relead){
098                                                Token t = refreshToken(helper, token);
099                                                if(null != t){
100                                                        relead = false;
101                                                        token.assignFrom(t);
102                                                }
103                                                return t;
104                                        }                                       
105                                }                               
106                        }
107                        return token;
108                }
109                /**
110                 * 刷新令牌和redis参数
111                 * @return 刷新成功返回{@code true},否则返回{@code false}
112                 */
113                private boolean refresh(){
114                        relead = true;
115                        Token token = get();
116                        if(null != token){
117                                try {
118                                        redisParameters = null;
119                                        getRedisParametersLazy(token);
120                                        mqParameters = null;
121                                        getMessageQueueParametersLazy(token);
122                                        faceapiParameters = null;
123                                        getFaceApiParametersLazy(token);
124                                        return  true;
125                                } catch (Exception e) {
126                                        // DO NOTHING
127                                        SimpleLog.log(MoreObjects.firstNonNull(e.getMessage(),e.getClass().getName()));
128                                }
129                        }
130                        return false;
131                }
132        }
133        private static class DispatcherListener extends BaseServiceHeartbeatListener{
134                private final Set<BaseDispatcher> dispatchers = Sets.newLinkedHashSet();
135                @Override
136                protected boolean doServiceOnline(ServiceHeartbeatPackage heartbeatPackage) {
137                        synchronized (dispatchers) {
138                                for(BaseDispatcher dispatcher: dispatchers){
139                                        if(dispatcher.isEnable()){
140                                                dispatcher.unregister();
141                                                dispatcher.register();
142                                        }
143                                }
144                        }
145                        return true;
146                }
147                boolean addDispatcher(BaseDispatcher dispatcher){
148                        synchronized (dispatchers) {
149                                return dispatchers.add(dispatcher);
150                        }
151                }
152/*              boolean removeDispatcher(BaseDispatcher dispatcher){
153                        synchronized (dispatchers) {
154                                return dispatchers.remove(dispatcher);
155                        }
156                }*/
157        }
158        public static interface ParameterSupplier<T> extends Supplier<T>,Closeable{
159                public Object key();
160        }
161        private class MessageQueueParameterSupplier implements ParameterSupplier<String>{
162                private final MQParam mqParam;
163                private final Token token;
164                private MessageQueueParameterSupplier(MQParam mqParam,Token token) {
165                        this.mqParam = checkNotNull(mqParam,"mqParam is null");
166                        this.token = checkNotNull(token,"token is null");
167                }
168                @Override
169                public String get() {
170                        return getMessageQueueParametersLazy(token).get(mqParam);
171                }
172                @Override
173                public Object key(){
174                        return mqParam;
175                }
176                @Override
177                public void close() {
178                }
179        }
180        private class TaskQueueSupplier extends BaseServiceHeartbeatListener implements ParameterSupplier<String>{
181                private final String task;
182                private final Token token;
183                protected String taskQueue = null;
184                private TaskQueueSupplier(String task,Token token) {
185                        this.task = checkNotNull(task,"task is null");
186                        this.token = checkNotNull(token,"token is null");
187                        addServiceEventListener(this);
188                }
189                @Override
190                public String get() {
191                        if(taskQueue == null){
192                                doServiceOnline(null);
193                        }
194                        return taskQueue;
195                }
196                @Override
197                public Object key(){
198                        return task;
199                }
200
201                @Override
202                protected boolean doServiceOnline(ServiceHeartbeatPackage heartbeatPackage) {
203                        taskQueue = taskQueueOf(task,token);
204                        return true;
205                }
206                /**
207                 * 当对象不再被需要时,执行此方法将其从服务心跳侦听器列表中删除
208                 * @see java.io.Closeable#close()
209                 */
210                @Override
211                public void close() {
212                        removeServiceEventListener(this);                       
213                }
214        }
215        private static <T>T unwrap(Object value,Class<T> clazz){
216                if(Proxy.isProxyClass(value.getClass())){
217                        return unwrap(Proxy.getInvocationHandler(value),clazz);
218                }else if(value instanceof Delegator){
219                        return unwrap(((Delegator<?>)value).delegate(),clazz);
220                }
221                return clazz.cast(value);
222        }
223        ClientExtendTools(IFaceLog syncInstance) {
224                super();
225                this.syncInstance = checkNotNull(syncInstance,"syncInstance is null");
226                this.asyncInstance = null;
227                this.factory = unwrap(syncInstance,IFaceLogThriftClient.class).getFactory();
228        }
229        ClientExtendTools(IFaceLogThriftClientAsync asyncInstance) {
230                super();
231                this.syncInstance = null;
232                this.asyncInstance = checkNotNull(asyncInstance,"asyncInstance is null");
233                this.factory = asyncInstance.getFactory();
234        }
235        /**
236         * 如果{@code host}是本机地址则用facelog服务主机名替换
237         * @param host
238         * @return {@code host} or host in {@link #factory}
239         */
240        public String insteadHostIfLocalhost(String host){
241                if(NetworkUtil.isLoopbackAddress(host)){
242                        return factory.getHostAndPort().getHost();
243                }
244                return host;
245        }
246        /**
247         * 如果{@code host}是本机地址则用facelog服务主机名替换
248         * @param host
249         * @return {@code host} or host in {@link #factory}
250         */
251        public HostAndPort insteadHostIfLocalhost(HostAndPort host){
252                return HostAndPort.fromParts(insteadHostIfLocalhost(host.getHost()), host.getPort());
253        }
254        /**
255         * 如果{@code uri}的主机名是本机地址则用facelog服务主机名替换
256         * @param uri
257         * @return {@code uri} or new URI instead with host of facelog
258         */
259        public URI insteadHostIfLocalhost(URI uri){
260                if(null != uri && NetworkUtil.isLoopbackAddress(uri.getHost())){
261                        try {
262                                return new URI(uri.getScheme(),
263                                           uri.getUserInfo(), 
264                                           factory.getHostAndPort().getHost(), 
265                                           uri.getPort(),
266                                           uri.getPath(), 
267                                           uri.getQuery(), 
268                                           uri.getFragment());
269                        } catch (URISyntaxException e) {
270                                // DO NOTHING
271                        }
272                }
273                return uri;
274        }
275        /**
276         * 如果{@code url}的主机名是本机地址则用facelog服务主机名替换
277         * @param url
278         * @return {@code url} or new URI instead with host of facelog
279         */
280        public URL insteadHostIfLocalhost(URL url){
281                if(null != url && NetworkUtil.isLoopbackAddress(url.getHost())){
282                        try {
283                                return new URL(url.getProtocol(),
284                                           factory.getHostAndPort().getHost(), 
285                                           url.getPort(),
286                                           url.getFile());
287                        } catch (MalformedURLException e) {
288                                // DO NOTHING
289                        }
290                }
291                return url;
292        }
293        /**
294         * 如果{@code json}中的值为URI,且主机名是本机地址则用facelog服务主机名替换
295         * @param json
296         * @return {@code json} or new json string instead with host of facelog
297         */
298        public String insteadHostJsonIfLocalhost(String json){
299                if(!Strings.isNullOrEmpty(json)){
300                        Map<String, String> map = MessageQueueFactorys.asMQConnParam2(json);
301                        for(Entry<String, String> entry : map.entrySet()){
302                                try {
303                                        URI uri = new URI (entry.getValue());
304                                        URI insteaded = insteadHostIfLocalhost(uri);
305                                        entry.setValue(insteaded.toString());
306                                } catch (URISyntaxException e) {
307                                        // 不是URI则跳过
308                                }
309                        }
310                        return BaseJsonEncoder.getEncoder().toJsonString(map);
311                }
312                return json;
313        }
314        /**
315         *  如果{@code uri}的主机名是本机地址则用facelog服务主机名替换
316         * @param uri
317         * @return {@code uri} or new URI string instead with host of facelog
318         */
319        public String insteadHostOfURIIfLocalhost(String uri){
320                try {
321                        return Strings.isNullOrEmpty(uri) ? uri : insteadHostIfLocalhost(new URI(uri)).toString();
322                } catch (URISyntaxException e) {
323                        // DO NOTHING
324                }
325                return uri;
326        }
327        /**
328         *  如果{@code url}的主机名是本机地址则用facelog服务主机名替换
329         * @param url 
330         * @return {@code url} or new URL string instead with host of facelog
331         */
332        public String insteadHostOfURLIfLocalhost(String url){
333                try {
334                        return Strings.isNullOrEmpty(url) ? url : insteadHostIfLocalhost(new URL(url)).toString();
335                } catch (MalformedURLException e) {
336                        // DO NOTHING
337                }
338                return url;
339        }
340
341        /**
342         * 如果{@code parameters}的参数({@link MQParam#REDIS_URI},{@link MQParam#WEBREDIS_URL},
343         * 或{@link MQParam#MQ_CONNECT} (JSON)中的值)是本机地址则用facelog服务主机名替换
344         * @param parameters
345         * @return 替换主机名参数后的新对象 {@link java.util.HashMap}
346         */
347        public Map<MQParam, String> insteadHostOfMQParamIfLocalhost(Map<MQParam, String> parameters) {
348                if(parameters != null){
349                        Map<MQParam, String> out = Maps.newHashMap();
350                        for (Entry<MQParam, String> entry : parameters.entrySet()) {
351                                MQParam key = entry.getKey();
352                                String value = entry.getValue();
353                                if(MQParam.REDIS_URI.equals(key)){
354                                        String insteaded = insteadHostOfURIIfLocalhost(value);
355                                        out.put(key, insteaded);
356                                }else   if(MQParam.WEBREDIS_URL.equals(key)){
357                                        String insteaded = insteadHostOfURLIfLocalhost(value);
358                                        out.put(key, insteaded);
359                                }else if(MQParam.MQ_CONNECT.equals(key)){
360                                        String insteaded = insteadHostJsonIfLocalhost(value);
361                                        out.put(key, insteaded);
362                                }else{
363                                        out.put(key,value);
364                                }
365                        }
366                        return out;
367                }
368                return parameters;
369        }
370        /**
371         * 如果{@code parameters}的值(host:port格式)是本机地址则用facelog服务主机名替换
372         * @param parameters
373         * @return 替换主机名参数后的新对象 {@link java.util.HashMap}
374         * @see #insteadHostIfLocalhost(HostAndPort)
375         */
376        public Map<String, String> insteadHostOfValueIfLocalhost(Map<String, String>parameters){
377                if(parameters != null){
378                        Map<String, String> out = Maps.newHashMap();
379                        for( Entry<String, String> entry:parameters.entrySet()){
380                                String value = entry.getValue();
381                                HostAndPort insteaded = insteadHostIfLocalhost(HostAndPort.fromString(value));
382                                out.put(entry.getKey(), insteaded.toString());
383                                return out;
384                        }
385                }
386                return parameters;
387        }
388    /**
389     * 根据设备ID返回设备所属的设备组ID的{@code Function}实例,
390     * 设备ID无效则返回{@code null}
391     */
392    public final Function<Integer,Integer> deviceGroupIdGetter = 
393        new Function<Integer,Integer>(){
394        @Override
395        public Integer apply(Integer input) {
396            try{
397                DeviceBean device = syncInstance != null 
398                                ? syncInstance.getDevice(input) 
399                                : asyncInstance.getDevice(input).get();
400                return null == device ? null : device.getGroupId();
401            } catch (ExecutionException e) {
402                Throwables.throwIfUnchecked(e.getCause());
403                throw new RuntimeException(e.getCause());
404                } catch(Exception e){
405                Throwables.throwIfUnchecked(e);
406                throw new RuntimeException(e);
407            }
408        }};
409    /**
410     * 根据设备ID返回一个获取设备组ID的{@code Supplier}实例
411     * @param deviceId
412     * @return 对应的groupId,如果{@code deviceId}无效则返回{@code null}
413     * @see #deviceGroupIdGetter
414     */
415    public Supplier<Integer> getDeviceGroupIdSupplier(final int deviceId){
416        return new Supplier<Integer>(){
417            @Override
418            public Integer get() {
419                return deviceGroupIdGetter.apply(deviceId);
420            }        
421        };
422    }
423    /**
424     * 根据人员ID返回人员所属的所有组ID的{@code Function}实例
425     * 如果人员ID无效则返回空表
426     */
427    public final Function<Integer,List<Integer>> personGroupBelonsGetter = 
428        new Function<Integer,List<Integer>>(){
429        @Override
430        public List<Integer> apply(Integer personId) {
431            try{
432                return syncInstance != null 
433                                ? syncInstance.getPersonGroupsBelongs(personId) 
434                                : asyncInstance.getPersonGroupsBelongs(personId).get();
435            } catch (ExecutionException e) {
436                Throwables.throwIfUnchecked(e.getCause());
437                throw new RuntimeException(e.getCause());
438                } catch(Exception e){
439                Throwables.throwIfUnchecked(e);
440                throw new RuntimeException(e);
441            }
442        }};
443    /**
444     * 根据人员ID返回一个获取所属组ID列表的{@code Supplier}实例
445     * @param personId
446     * @return 人员组ID列表,如果{@code personId}无效则返回空表
447     * @see #personGroupBelonsGetter
448     */
449    public Supplier<List<Integer>> getPersonGroupBelonsSupplier(final int personId){
450        return new Supplier<List<Integer>>(){
451            @Override
452            public List<Integer> get() {
453                return personGroupBelonsGetter.apply(personId);
454            }        
455        };
456    }
457    /**
458     * (管理端)创建{@link CmdManager}实例<br>
459     * @param token 访问令牌(person Token or root Token)
460     * @return {@link CmdManager}实例
461     */
462    public CmdManager makeCmdManager(Token token){
463        try{
464            checkArgument(checkNotNull(token,"token is null").getType() == TokenType.PERSON 
465                || token.getType() == TokenType.ROOT,"person or root token required");
466            checkArgument(tokenRank.apply(token)>=2,"person or root token required");
467                IMessageQueueFactory mqFactory = MessageQueueFactorys.getDefaultFactory();
468            return new CmdManager(
469                        mqFactory.getPublisher(),
470                        mqFactory.getSubscriber(),
471                        getCmdChannelSupplier(token))
472                        /** 设置命令序列号 */
473                        .setCmdSn(getCmdSnSupplier(token))
474                        /** 设置命令响应通道 */
475                        .setAckChannel(getAckChannelSupplier(token))
476                        .self();
477        } catch(Exception e){
478            Throwables.throwIfUnchecked(e);
479            throw new RuntimeException(e);
480        }
481    }
482    /**
483     * (管理端)创建{@link TaskManager}实例<br>
484     * @param token 访问令牌(person Token or root Token)
485     * @param cmdpath 设备(菜单)命令路径
486     * @param taskQueueSupplier 任务队列
487     * @return {@link TaskManager}实例
488     */
489    public TaskManager makeTaskManager(Token token, String cmdpath, Supplier<String> taskQueueSupplier){
490        try{
491            checkArgument(checkNotNull(token,"token is null").getType() == TokenType.PERSON 
492                || token.getType() == TokenType.ROOT,"person or root token required");
493            checkArgument(tokenRank.apply(token)>=2,"person or root token required");
494                IMessageQueueFactory mqFactory = MessageQueueFactorys.getDefaultFactory();
495            return new TaskManager(
496                        mqFactory.getPublisher(),
497                        mqFactory.getSubscriber(),
498                        mqFactory.getProducer(),
499                        cmdpath, taskQueueSupplier)
500                        /** 设置命令序列号 */
501                        .setCmdSn(getCmdSnSupplier(token))
502                        /** 设置命令响应通道 */
503                        .setAckChannel(getAckChannelSupplier(token))
504                        .self();
505        } catch(Exception e){
506            Throwables.throwIfUnchecked(e);
507            throw new RuntimeException(e);
508        }
509    }
510    /**
511     * (设备端)创建设备多目标命令分发器<br>
512     * @param token 设备令牌
513     * @return {@link CmdDispatcher}实例
514     */
515    public CmdDispatcher makeCmdDispatcher(Token token){
516        try{
517                checkArgument(checkNotNull(token,"token is null").getType() == TokenType.DEVICE,"device token required");
518                int deviceId = token.getId();
519                IMessageQueueFactory mqFactory = MessageQueueFactorys.getDefaultFactory();
520                CmdDispatcher dispatcher = new CmdDispatcher(deviceId, mqFactory.getPublisher(), mqFactory.getSubscriber())
521                                        .setGroupIdSupplier(this.getDeviceGroupIdSupplier(deviceId))
522                                        .setCmdSnValidator(cmdSnValidator)
523                                        .setChannelSupplier(new MessageQueueParameterSupplier(MQParam.CMD_CHANNEL,token))
524                                        .register()
525                                        .self();
526                dispatcherListener.addDispatcher(dispatcher);
527                        return dispatcher;
528        } catch(Exception e){
529                Throwables.throwIfUnchecked(e);
530                throw new RuntimeException(e);
531        }
532    }
533    /**
534     * (设备端)创建设备任务分发器<br>
535     * @param token 设备令牌
536     * @param taskQueueSupplier 任务队列名,创建的分发器对象注册到任务队列,可为{@code null}
537     * @return {@link TaskDispatcher}实例
538     */
539    public TaskDispatcher makeTaskDispatcher(Token token,Supplier<String> taskQueueSupplier){
540        try{
541                checkArgument(checkNotNull(token,"token is null").getType() == TokenType.DEVICE,"device token required");
542                int deviceId = token.getId();
543                IMessageQueueFactory mqFactory = MessageQueueFactorys.getDefaultFactory();
544                TaskDispatcher dispatcher = new TaskDispatcher(deviceId, 
545                                mqFactory.getPublisher(), mqFactory.getConsumer())
546                                .setCmdSnValidator(cmdSnValidator)
547                                        .setChannelSupplier(taskQueueSupplier)
548                                        .register()
549                                        .self();
550                dispatcherListener.addDispatcher(dispatcher);
551                return dispatcher;
552        } catch(Exception e){
553                Throwables.throwIfUnchecked(e);
554                throw new RuntimeException(e);
555        }
556    }
557    /**
558     * 返回一个申请命令响应通道的{@link Supplier}实例
559     * @param duration 命令通道有效时间(秒) 大于0有效,否则使用默认的有效期
560     * @param token 访问令牌
561     * @return {@link Supplier}实例
562     */
563    public Supplier<String> 
564    getAckChannelSupplier(final int duration,final Token token){
565        return new Supplier<String>(){
566            @Override
567            public String get() {
568                try{
569                    return syncInstance != null 
570                                ? syncInstance.applyAckChannel(duration,token) 
571                                : asyncInstance.applyAckChannel(duration,token).get();
572                } catch (ExecutionException e) {
573                        Throwables.throwIfUnchecked(e.getCause());
574                        throw new RuntimeException(e.getCause());
575                        } catch(Exception e){
576                    Throwables.throwIfUnchecked(e);
577                    throw new RuntimeException(e);
578                }
579            }
580        }; 
581    }
582    /**
583     * 返回一个申请命令响应通道的{@link Supplier}实例
584     * @param token 访问令牌
585     * @return {@link Supplier}实例
586     */
587    public Supplier<String> 
588    getAckChannelSupplier(final Token token){
589        return getAckChannelSupplier(0,token);
590    }
591    /**
592     * 返回一个申请命令序号的{@code Supplier}实例
593     * @param token
594     * @return {@code Supplier}实例
595     */
596    public Supplier<Integer> 
597    getCmdSnSupplier(final Token token){
598        return new Supplier<Integer>(){
599            @Override
600            public Integer get() {
601                try{
602                    return syncInstance != null 
603                                ? syncInstance.applyCmdSn(token) 
604                                : asyncInstance.applyCmdSn(token).get();
605                } catch (ExecutionException e) {
606                        Throwables.throwIfUnchecked(e.getCause());
607                        throw new RuntimeException(e.getCause());
608                        } catch(Exception e){
609                    Throwables.throwIfUnchecked(e);
610                    throw new RuntimeException(e);
611                }
612            }
613        }; 
614    }
615    /**
616     * 返回一个获取设备命令通道名的{@code Supplier}实例
617     * @param token
618     * @return {@code Supplier}实例
619     */
620    public Supplier<String> 
621    getCmdChannelSupplier(final Token token){
622        return new Supplier<String>(){
623            @Override
624            public String get() {
625                try{
626                    return getRedisParametersLazy(token).get(MQParam.CMD_CHANNEL);
627                } catch(Exception e){
628                    Throwables.throwIfUnchecked(e);
629                    throw new RuntimeException(e);
630                }
631            }
632        }; 
633    }
634    /** 设备命令序列号验证器 */
635    public final Predicate<Integer> cmdSnValidator = 
636        new Predicate<Integer>(){
637            @Override
638            public boolean apply(Integer input) {
639                try{
640                    return null == input ? false 
641                                : (syncInstance != null 
642                                                ? syncInstance.isValidCmdSn(input) 
643                                                : asyncInstance.isValidCmdSn(input).get());
644                } catch (ExecutionException e) {
645                        Throwables.throwIfUnchecked(e.getCause());
646                        throw new RuntimeException(e.getCause());
647                        } catch(Exception e){
648                    Throwables.throwIfUnchecked(e);
649                    throw new RuntimeException(e);
650                }
651            }};
652    /** 设备命令响应通道验证器 */
653    public final Predicate<String> ackChannelValidator =
654        new Predicate<String>(){
655            @Override
656            public boolean apply(String input) {
657                try{
658                    return null == input || input.isEmpty() ? false 
659                                : (syncInstance != null 
660                                                ? syncInstance.isValidAckChannel(input) 
661                                                : asyncInstance.isValidAckChannel(input).get());
662                } catch (ExecutionException e) {
663                        Throwables.throwIfUnchecked(e.getCause());
664                        throw new RuntimeException(e.getCause());
665                        } catch(Exception e){
666                    Throwables.throwIfUnchecked(e);
667                    throw new RuntimeException(e);
668                }
669            }};
670    /** 设备令牌验证器 */
671    public final Predicate<Token> deviceTokenValidator =
672        new Predicate<Token>(){
673            @Override
674            public boolean apply(Token input) {
675                try{
676                    return null == input ? false 
677                                : (syncInstance != null 
678                                                ? syncInstance.isValidDeviceToken(input) 
679                                                : asyncInstance.isValidDeviceToken(input).get());
680                } catch (ExecutionException e) {
681                        Throwables.throwIfUnchecked(e.getCause());
682                        throw new RuntimeException(e.getCause());
683                        } catch(Exception e){
684                    Throwables.throwIfUnchecked(e);
685                    throw new RuntimeException(e);
686                }
687            }};
688    /** 人员令牌验证器 */
689        public final Predicate<Token> personTokenValidator =
690                new Predicate<Token>(){
691                    @Override
692                    public boolean apply(Token input) {
693                        try{
694                            return null == input ? false 
695                                        : (syncInstance != null 
696                                                        ? syncInstance.isValidPersonToken(input) 
697                                                        : asyncInstance.isValidPersonToken(input).get());
698                        } catch (ExecutionException e) {
699                                Throwables.throwIfUnchecked(e.getCause());
700                                throw new RuntimeException(e.getCause());
701                                } catch(Exception e){
702                            Throwables.throwIfUnchecked(e);
703                            throw new RuntimeException(e);
704                        }
705                    }};
706    /** root令牌验证器 */
707        public final Predicate<Token> rootTokenValidator =
708                new Predicate<Token>(){
709                    @Override
710                    public boolean apply(Token input) {
711                        try{
712                            return null == input ? false 
713                                        : (syncInstance != null 
714                                                        ? syncInstance.isValidRootToken(input) 
715                                                        : asyncInstance.isValidRootToken(input).get());
716                        } catch (ExecutionException e) {
717                                Throwables.throwIfUnchecked(e.getCause());
718                                throw new RuntimeException(e.getCause());
719                                } catch(Exception e){
720                            Throwables.throwIfUnchecked(e);
721                            throw new RuntimeException(e);
722                        }
723                    }};
724        /** 
725         * 返回令牌的管理等级,输入为{@code null}或设备令牌返回-1
726         * <li> 4---root</li>
727         * <li> 3---管理员</li>
728         * <li> 2---操作员</li>
729         * <li> 0---普通用户</li>
730         * <li>-1---未定义</li>
731         */
732        public final Function<Token,Integer> tokenRank =
733                new Function<Token,Integer>(){
734                    @Override
735                    public Integer apply(Token input) {
736                        try{
737                                if(null != input){
738                                        switch (input.getType()) {
739                                        case ROOT:
740                                                if(rootTokenValidator.apply(input)){
741                                                        return 4;
742                                                }
743                                                break;
744                                        case PERSON:{
745                                                if(personTokenValidator.apply(input)){ 
746                                                        PersonBean bean = (syncInstance != null 
747                                                                        ? syncInstance.getPerson(input.getId()) 
748                                                                                        : asyncInstance.getPerson(input.getId()).get());
749                                                        Integer rank = bean.getRank();
750                                                        return rank != null ? rank : 0;
751                                                }
752                                                break;
753                                        }                                       
754                                        default:
755                                                break;
756                                        }
757                                }
758                                                return -1;
759                        } catch (ExecutionException e) {
760                                Throwables.throwIfUnchecked(e.getCause());
761                                throw new RuntimeException(e.getCause());
762                                } catch(Exception e){
763                            Throwables.throwIfUnchecked(e);
764                            throw new RuntimeException(e);
765                        }
766                    }};
767    /**
768     * 申请用户令牌
769     * @param userid 用户id,为-1代表root
770     * @param password 用户密码
771     * @param isMd5 密码是否为md5校验码
772     * @return
773     * @throws ServiceSecurityException
774     */
775    private Token applyUserToken(int userid,String password,boolean isMd5) throws ServiceSecurityException{
776        Token token;
777        try {
778                        token = syncInstance != null 
779                                        ? syncInstance.applyUserToken(userid, password, isMd5)
780                                        : asyncInstance.applyUserToken(userid, password, isMd5).get();
781                return token;
782        } catch (ExecutionException e) {
783                Throwables.propagateIfPossible(e.getCause(), ServiceSecurityException.class);
784                throw new RuntimeException(e.getCause());
785                } catch(Exception e){
786                Throwables.propagateIfPossible(e, ServiceSecurityException.class);
787            throw new RuntimeException(e);
788        }
789    }
790        /**
791         *  获取redis连接参数
792         * @param token
793         * @return redis连接参数名-参数值映射
794         */
795        private Map<MQParam, String> getRedisParametersLazy(Token token){
796                // double check
797                if(redisParameters == null){
798                        synchronized (this) {
799                                if(redisParameters == null){
800                                        checkArgument(token != null,"token is null");
801                                        // 获取redis连接参数
802                                        try {
803                                                Map<MQParam, String> param = syncInstance != null 
804                                                                ? syncInstance.getRedisParameters(token)
805                                                                                : asyncInstance.getRedisParameters(token).get();
806                                                                redisParameters =  insteadHostOfMQParamIfLocalhost(param);
807                                        } catch (ExecutionException e) {
808                                                Throwables.throwIfUnchecked(e.getCause());
809                                                throw new RuntimeException(e.getCause());
810                                        } catch(Exception e){
811                                                Throwables.throwIfUnchecked(e);
812                                                throw new RuntimeException(e);
813                                        }
814                                }
815                        }
816                }
817                return redisParameters;
818        }
819        /**
820         * @param token 调用 {@link #getRedisParametersLazy(Token)}所需要的令牌
821         * @return 返回一个获取redis参数的{@link Supplier}实例
822         */
823        public Supplier<Map<MQParam, String>> getRedisParametersSupplier(final Token token){
824                checkArgument(token != null,"token is null");
825                return new Supplier<Map<MQParam, String>>(){
826
827                        @Override
828                        public Map<MQParam, String> get() {
829                                return getRedisParametersLazy(token);
830                        }};
831        }
832        /**
833
834         *  获取消息系统连接参数
835         * @param token
836         * @return 消息系统连接参数名-参数值映射
837         */
838        private Map<MQParam, String> getMessageQueueParametersLazy(Token token){
839                // double check
840                if(mqParameters == null){
841                        synchronized (this) {
842                                if(mqParameters == null){
843                                        checkArgument(token != null,"token is null");
844                                        // 获取redis连接参数
845                                        try {
846                                                Map<MQParam, String> param = syncInstance != null 
847                                                                ? syncInstance.getMessageQueueParameters(token)
848                                                                : asyncInstance.getMessageQueueParameters(token).get();
849                                                mqParameters =  insteadHostOfMQParamIfLocalhost(param);
850                                        } catch (ExecutionException e) {
851                                                Throwables.throwIfUnchecked(e.getCause());
852                                                throw new RuntimeException(e.getCause());
853                                        } catch(Exception e){
854                                                Throwables.throwIfUnchecked(e);
855                                                throw new RuntimeException(e);
856                                        }
857                                }
858                        }
859                }
860                return mqParameters;
861        }
862        /**
863         * @param token 调用 {@link #getMessageQueueParametersLazy(Token)}所需要的令牌
864         * @return 返回一个获取消息系统参数的{@link Supplier}实例
865         */
866        public Supplier<Map<MQParam, String>> getMessageQueueParametersSupplier(final Token token){
867                checkArgument(token != null,"token is null");
868                return new Supplier<Map<MQParam, String>>(){
869
870                        @Override
871                        public Map<MQParam, String> get() {
872                                return getMessageQueueParametersLazy(token);
873                        }};
874        }
875        /**
876         *  获取faceapi连接参数
877         * @param token
878         * @return
879         */
880        private Map<String, String> getFaceApiParametersLazy(Token token){
881                if(faceapiParameters == null){
882                        checkArgument(token != null,"token is null");
883                        // 获取faceapi连接参数
884                        try {
885                                Map<String, String> param = syncInstance != null 
886                                                ? syncInstance.getFaceApiParameters(token)
887                                                : asyncInstance.getFaceApiParameters(token).get();
888                                                faceapiParameters =  insteadHostOfValueIfLocalhost(param);
889                        } catch (ExecutionException e) {
890                                Throwables.throwIfUnchecked(e.getCause());
891                                throw new RuntimeException(e.getCause());
892                        } catch(Exception e){
893                                Throwables.throwIfUnchecked(e);
894                                throw new RuntimeException(e);
895                        }
896                }
897                return faceapiParameters;
898        }
899        /**
900         * @param token 调用 {@link #getFaceApiParametersLazy(Token)}所需要的令牌
901         * @return 返回一个获取redis参数的{@link Supplier}实例
902         */
903        public Supplier<Map<String, String>> getFaceApiParametersSupplier(final Token token){
904                checkArgument(token != null,"token is null");
905                return new Supplier<Map<String, String>>(){
906
907                        @Override
908                        public Map<String, String> get() {
909                                return getFaceApiParametersLazy(token);
910                        }};
911        }
912        /**
913         * @param mqParam
914         * @param token 调用 {@link #getRedisParametersLazy(Token)}所需要的令牌
915         * @return 返回一个动态获取指定消息参数的{@link Supplier}实例
916         */
917        public Supplier<String> getDynamicParamSupplier(final MQParam mqParam,Token token){
918                checkNotNull(mqParam,"mqParam is null");
919                return Suppliers.compose(new Function<Map<MQParam, String>,String>(){
920                        @Override
921                        public String apply(Map<MQParam, String> input) {
922                                return input.get(mqParam);
923                        }
924                }, getRedisParametersSupplier(token));
925        }
926        /**
927         * 创建dtalk引擎
928         * @param deviceToken 设备令牌,不可为{@code null}
929         * @param rootMenu 包括所有菜单的根菜单对象,不可为{@code null}
930         * @return {@link DtalkEngineForFacelog}实例
931         */
932        public DtalkEngineForFacelog initDtalkEngine(Token deviceToken, MenuItem rootMenu){
933                // 设备端才能调用此方法
934                checkArgument(deviceTokenValidator.apply(deviceToken),"device token REQUIRED");
935                initMQDefaultFactory(deviceToken);
936                IMessageQueueFactory mqFactory = MessageQueueFactorys.getDefaultFactory();
937                return new DtalkEngineForFacelog(checkNotNull(rootMenu,"rootMenu is null"), tokenRank,mqFactory);               
938        }
939
940        /**
941         * 从facelog获取消息系统参数,初始化消息系统的默认实例<br>
942         * 该方法只能在应用启动时调用一次
943         * @param token
944         */
945        public void initMQDefaultFactory(Token token){
946                Map<MQParam, String> mqParam = getMessageQueueParametersLazy(token);
947                MessageQueueFactorys.getFactory(mqParam.get(MQParam.MQ_TYPE))
948                        .init(mqParam.get(MQParam.MQ_CONNECT))
949                        .asDefaultFactory();
950        }
951        private Token online(DeviceBean deviceBean) throws ServiceSecurityException{
952                try{
953                        return syncInstance != null 
954                                        ? syncInstance.online(deviceBean) 
955                                        : asyncInstance.online(deviceBean).get();
956        } catch (ExecutionException e) {
957                Throwables.propagateIfPossible(e.getCause(), ServiceSecurityException.class);
958                throw new RuntimeException(e.getCause());
959                } catch(Exception e){
960                Throwables.propagateIfPossible(e, ServiceSecurityException.class);
961            throw new RuntimeException(e);
962        }
963        }
964
965        /**
966         * 令牌刷新
967         * @param helper
968         * @param token
969         * @return 刷新的令牌,刷新失败返回{@code null}
970         */
971        public Token refreshToken(TokenHelper helper,Token token){
972                // 最后一个参数为token
973                Token freshToken = null;
974                try{
975                        // 重新申请令牌
976                        switch(token.getType()){
977                        case DEVICE:{
978                                if(helper.deviceBean() != null){
979                                        freshToken = online(helper.deviceBean());
980                                }
981                                break;
982                        }
983                        case ROOT:
984                        case PERSON:{
985                                String pwd = helper.passwordOf(token.getId());
986                                if(pwd != null){
987                                        freshToken = applyUserToken(token.getId(),pwd,helper.isHashedPwd());
988                                }
989                                break;
990                        }
991                        default:
992                                break;
993                        }
994                        // 如果申请令牌成功则更新令牌参数,重新执行方法调用
995                        if(freshToken != null){
996                                // 用申请的新令牌更新参数
997                                helper.saveFreshedToken(freshToken);
998                        }
999                }catch (Exception er) {
1000                        // DO NOTHING
1001                }
1002                return freshToken;
1003        }
1004
1005        /**
1006         * 返回有效令牌的{@link Supplier}实例<br>
1007         * @return {@link Supplier}实例
1008         */
1009        public Supplier<Token> getTokenSupplier(){
1010                return checkNotNull(this.tokenRefresh,"tokenRefresh field must be initialized by startServiceHeartbeatListener firstly");
1011        }
1012        public ClientExtendTools addServiceEventListener(ServiceHeartbeatListener listener){
1013                ServiceHeartbeatAdapter.INSTANCE.addServiceEventListener(listener);     
1014                return this;
1015        }
1016        public ClientExtendTools removeServiceEventListener(ServiceHeartbeatListener listener){
1017                ServiceHeartbeatAdapter.INSTANCE.removeServiceEventListener(listener);
1018                return this;
1019        }
1020
1021        /**
1022         * 创建动态频道名侦听对象
1023         * 动态频道是指定当服务重启后,频道名会动态改变的频道,
1024         * 对于这种频道,通过侦听服务心跳判断服务是否重启,如果重启则重新获取频道名继续保持侦听
1025         * @param listener
1026         * @param channelType 频道消息数据类型
1027         * @param mqParam 参数名
1028         * @param token
1029         * @param factory 消息系统工厂类实例
1030         * @return 返回{@link DynamicChannelListener}实例
1031         */
1032        public <T>DynamicChannelListener<T> makeDynamicChannelListener(IMessageAdapter<T> listener,Class<T> channelType, MQParam mqParam, Token token, IMessageQueueFactory factory) {
1033                DynamicChannelListener<T> monitor = new DynamicChannelListener<T>(
1034                                listener,
1035                                channelType,
1036                                getDynamicParamSupplier(mqParam, token), factory);
1037                addServiceEventListener(monitor);
1038                return monitor;
1039        }
1040        /**
1041         * 创建设备心跳包发送对象<br>
1042         * {@link DeviceHeartbeat}为单实例,该方法只能调用一次
1043         * @param deviceID 设备ID
1044         * @param token 设备令牌
1045         * @return {@link DeviceHeartbeat}实例
1046         */
1047        public DeviceHeartbeat makeHeartbeat(int deviceID,Token token){
1048                DeviceHeartbeat heartbeat = DeviceHeartbeat.makeHeartbeat(deviceID)
1049                                /** 将设备心跳包数据发送到指定的设备心跳监控通道名,否则监控端无法收到设备心跳包 */
1050                                .setMonitorChannelSupplier(getDynamicParamSupplier(MQParam.HB_MONITOR_CHANNEL,token));
1051                addServiceEventListener(heartbeat);
1052                return heartbeat;
1053        }
1054        /**
1055         * @param tokenHelper 要设置的 tokenHelper
1056         * @return 当前{@link ClientExtendTools}实例
1057         */
1058        public ClientExtendTools setTokenHelper(TokenHelper tokenHelper) {
1059                this.tokenHelper = tokenHelper;
1060                return this;
1061        }
1062        /**
1063         * 启动服务心跳侦听器<br>
1064         * 启动侦听器后CLIENT端才能感知服务端断线,并执行相应动作。
1065         * 调用前必须先执行{@link #setTokenHelper(TokenHelper)}初始化
1066         * @param token 令牌
1067         * @param initMQDefaultFactoryInstance 是否初始化 {@link IMessageQueueFactory}默认实例
1068         * @return 返回当前{@link ClientExtendTools}实例
1069         */
1070        public ClientExtendTools startServiceHeartbeatListener(Token token,boolean initMQDefaultFactoryInstance){
1071                checkState(tokenHelper != null,"tokenHelper field must be initialized by setTokenHelper firstly");
1072                this.tokenRefresh = new TokenRefresh(tokenHelper, token);
1073                ISubscriber subscriber;
1074                if(initMQDefaultFactoryInstance){
1075                        initMQDefaultFactory(token);
1076                        subscriber = MessageQueueFactorys.getDefaultFactory().getSubscriber();
1077                }else{
1078                        Map<MQParam, String> mqParam = getMessageQueueParametersLazy(token);
1079                        IMessageQueueFactory mqFactory = MessageQueueFactorys.getFactory(mqParam.get(MQParam.MQ_TYPE)).init(mqParam.get(MQParam.MQ_CONNECT));
1080                        subscriber = mqFactory.getSubscriber();
1081                }
1082                subscriber.register(ServiceHeartbeatAdapter.SERVICE_HB_CHANNEL);
1083                addServiceEventListener(tokenRefreshListener);
1084                addServiceEventListener(dispatcherListener);
1085                return this;
1086        }
1087        private String taskQueueOf(String task,Token token) {   
1088                checkArgument(token != null,"token is null");
1089                try {
1090                        return syncInstance != null 
1091                                        ? syncInstance.taskQueueOf(task, token)
1092                                        : asyncInstance.taskQueueOf(task,token).get();
1093            } catch (ExecutionException e) {
1094                Throwables.throwIfUnchecked(e.getCause());
1095                throw new RuntimeException(e.getCause());
1096                } catch(Exception e){
1097                Throwables.throwIfUnchecked(e);
1098                throw new RuntimeException(e);
1099            }
1100        }
1101        public ParameterSupplier<String> getTaskQueueSupplier(String task,Token token){
1102                return new TaskQueueSupplier(task,token);
1103        }
1104        public ParameterSupplier<String> getSdkTaskQueueSupplier(String task,String sdkVersion,Token token){
1105                return new TaskQueueSupplier(checkNotNull(task,"task is null") + checkNotNull(sdkVersion,"sdkVersion is null"),token);
1106        }
1107        public ClientFactory getFactory() {
1108                return factory;
1109        }
1110}