001package gu.dtalk.redis;
002
003import static com.google.common.base.Preconditions.*;
004
005import java.net.URI;
006import java.util.Iterator;
007import java.util.Map;
008import java.util.ServiceLoader;
009
010import com.google.common.base.Optional;
011import com.google.common.base.Predicate;
012import com.google.common.base.Strings;
013import com.google.common.collect.ImmutableMap;
014import com.google.common.collect.Iterators;
015import com.google.common.collect.ImmutableMap.Builder;
016import com.google.common.net.HostAndPort;
017
018import gu.dtalk.exception.DtalkException;
019import gu.simplemq.redis.JedisPoolLazy.PropName;
020import gu.simplemq.redis.JedisUtils;
021
022/**
023 * redis连接配置参数
024 * @author guyadong
025 *
026 */
027public enum RedisConfigType{
028        /** 自定义配置 */CUSTOM(new DefaultCustomRedisConfigProvider())
029        /** 局域网配置 */,LAN(new DefaultLocalRedisConfigProvider())
030        /** 公有云配置 */,CLOUD(new DefaultCloudRedisConfigProvider())
031        /** 本机配置(仅用于测试) */,LOCALHOST(new DefaultLocalhostRedisConfigProvider());
032        /**
033         * 接口实例的默认实现
034         */
035        private final RedisConfigProvider defImpl;
036        /**
037         * 接口实例
038         */
039        private volatile RedisConfigProvider instance;
040        /**
041         * redis连接配置参数
042         */
043        private volatile Map<PropName, Object> parameters;
044        /**
045         * 当前配置是否可连接
046         */
047        private boolean connectable = false;
048        private RedisConfigType(){
049                this(null);
050        }
051        private RedisConfigType(RedisConfigProvider defImpl) {
052                this.defImpl = defImpl;
053        }
054        /**
055         * SPI(Server Load Interface)方式加载当前类型的{@link RedisConfigProvider}实例,
056         * 没找到则用默认{@link #defImpl}实例代替
057         * @return
058         */
059        private RedisConfigProvider findRedisConfigProvider(){
060                // double checking
061                if(instance == null){
062                        synchronized (this) {
063                                if(instance == null){
064                                        ServiceLoader<RedisConfigProvider> providers = ServiceLoader.load(RedisConfigProvider.class);
065                                        Iterator<RedisConfigProvider> itor = providers.iterator();
066                                        Optional<RedisConfigProvider> find = Iterators.tryFind(itor, new Predicate<RedisConfigProvider>() {
067
068                                                @Override
069                                                public boolean apply(RedisConfigProvider input) {
070                                                        return input.type() == RedisConfigType.this;
071                                                }
072                                        });
073                                        instance = find.isPresent() ? find.get() : this.defImpl;
074                                        // host uri都为null则 instance无效设置为null
075                                        if(null != instance && instance.getHost()==null && instance.getURI() == null){
076                                                instance = null;
077                                        }
078                                }
079                        }
080                }
081                return instance;
082        }
083        public HostAndPort getHostAndPort(){
084                String host;
085                int port;
086                checkState(null != parameters,"redis parameters not initialized,must call readRedisParam() firstly");
087                if(parameters.get(PropName.uri) != null){
088                        URI uri = (URI)parameters.get(PropName.uri);
089                        host = uri.getHost();
090                        port = uri.getPort();
091                }else{
092                        host = (String) parameters.get(PropName.host);
093                        port  = (int) parameters.get(PropName.port);    
094                }
095                return HostAndPort.fromParts(host, port);
096        }
097        /**
098         * 根据SPI加载的{@link RedisConfigProvider}实例提供的参数创建Redis连接参数<b>
099         * 如果{@link #findRedisConfigProvider()}返回{@code null}则返回{@code null}
100         * @return
101         */
102        public Map<PropName, Object> readRedisParam(){
103                // double checking
104                if(parameters == null){
105                        synchronized (this) {
106                                if(parameters == null){
107                                        RedisConfigProvider config = findRedisConfigProvider();         
108                                        
109                                        if(config != null){
110                                                Builder<PropName, Object> builder = ImmutableMap.builder();
111                                                URI uri = config.getURI();
112                                                if(uri != null){
113                                                        builder.put(PropName.uri, uri);
114                                                }else{
115                                                        String host = config.getHost();
116                                                        int port =config.getPort();
117                                                        String password = config.getPassword();
118                                                        int database = config.getDatabase();
119                                                        checkArgument(!Strings.isNullOrEmpty(host),"INVALID REDIS HOST");
120
121                                                        builder.put(PropName.host, host);
122                                                        if(port >0){
123                                                                builder.put(PropName.port, port);
124                                                        }
125                                                        if(!Strings.isNullOrEmpty(password)){
126                                                                builder.put(PropName.password, password);
127                                                        }
128                                                        if(database > 0){
129                                                                builder.put(PropName.database, database);
130                                                        }
131                                                }
132                                                int timeout = config.getTimeout();
133                                                if(timeout >0){
134                                                        builder.put(PropName.timeout, timeout);
135                                                }
136                                                parameters = builder.build();
137                                        }                                       
138                                }
139                        }
140                }
141                return parameters;
142        }
143        /**
144         * 保存redis参数到当前类型对应的{@link RedisConfigProvider}实例
145         * @param param
146         */
147        public synchronized void saveRedisParam(Map<PropName, Object> param){
148                RedisConfigProvider config = findRedisConfigProvider();
149                Object host = param.get(PropName.host);
150                if(host instanceof String){
151                        config.setHost((String) host);
152                }
153                Object port = param.get(PropName.port);
154                if(port instanceof Number){
155                        config.setPort(((Number) port).intValue());
156                }
157                Object password = param.get(PropName.password);
158                if(password instanceof String){
159                        config.setPassword((String) password);
160                }
161                Object database = param.get(PropName.database);
162                if(database instanceof Number){
163                        config.setDatabase(((Number) database).intValue());
164                }
165                Object timeout = param.get(PropName.timeout);
166                if(timeout instanceof Number){
167                        config.setTimeout(((Number) timeout).intValue());
168                }
169                Object uri = param.get(PropName.uri);
170                if(uri instanceof URI){
171                        config.setURI( (URI) uri);
172                }
173        }
174        /**
175         * 当前可用的的配置类型
176         */
177        private static volatile RedisConfigType activeConfigType = null;
178        /**
179         * 复位{@link #activeConfigType}为{@code null}<br>
180         * 为避免{@link #lookupRedisConnect()}方法被多次执行,
181         * 当{@link #activeConfigType}不为{@code null}时直接返回{@link #activeConfigType}的值,
182         * 如果希望再次执行{@link #lookupRedisConnect()}方法,可先调用此方法设置{@link #activeConfigType}为{@code null}
183         */
184        public static void resetActiveConfigType(){
185                activeConfigType = null;
186        }
187        /**
188         * 测试redis连接
189         * @return 连接成功返回{@code true},否则返回{@code false}
190         */
191        public synchronized boolean testConnect(){
192                Map<PropName, Object> props = readRedisParam();
193                connectable = false;
194                if(props != null){
195//                      System.out.printf("try to connect %s...\n", this);
196                        try{
197                                connectable = JedisUtils.testConnect(props);
198                        }catch (Exception e) {
199                        }
200//                      if(connectable){
201//                              System.out.println(toString() + " connect OK\n");
202//                      }
203//                      System.out.printf("%s connect %s\n",this.toString(),connectable?"OK":"FAIL");
204                }
205                return connectable;
206        }
207
208        /**
209         * 按照如下优先顺序测试配置的redis连接,返回第一个能建立有效连接的配置,否则抛出异常<br>
210         * <li>{@link RedisConfigType#CUSTOM}</li>
211         * <li>{@link RedisConfigType#LAN}</li>
212         * <li>{@link RedisConfigType#CLOUD}</li>
213         * <li>{@link RedisConfigType#LOCALHOST}</li>
214         * @return {@link #activeConfigType}不为{@code null}时直接返回{@link #activeConfigType}的值
215         * @throws DtalkException 没有找到有效redis连接
216         */
217        public static RedisConfigType lookupRedisConnect() throws DtalkException{
218                if(activeConfigType == null){
219                        synchronized(RedisConfigType.class){
220                                if(activeConfigType == null){
221                                        // 并发执行连接测试,以减少等待时间
222                                        Thread[] threads = new Thread[values().length];
223                                        int index = 0;
224                                        for (final RedisConfigType type : values()) {
225                                                threads[index] = new Thread(){
226
227                                                        @Override
228                                                        public void run() {
229                                                                type.testConnect();
230                                                        }
231
232                                                };
233                                                threads[index].start();
234                                                index++;
235                                        }
236                                        // 等待所有子线程结束
237                                        // 以枚举变量定义的顺序为优先级查找第一个connectable为true的对象返回
238                                        // 都为false则抛出异常
239                                        try {
240                                                for(int i =0;i<threads.length;++i){
241                                                        threads[i].join();
242                                                        RedisConfigType type = values()[i];
243                                                        if(type.connectable){
244                                                                return type;
245                                                        }
246                                                }
247                                        } catch (InterruptedException e) {
248                                        }
249                                        throw new DtalkException("NOT FOUND VALID REDIS SERVER");
250                                }
251                        }
252                }
253                return activeConfigType;
254        }
255
256        /**
257         * 与{@link #lookupRedisConnect()}功能相似,不同的时当没有找到有效redis连接时,不抛出异常,返回{@code null}
258         * @return 返回第一个能建立有效连接的配置,否则返回{@code null}
259         */
260        public static RedisConfigType lookupRedisConnectUnchecked() {
261                try {
262                        return lookupRedisConnect();
263                } catch (DtalkException e) {
264                        return null;
265                }
266        }
267        @Override
268        public  String toString(){
269                StringBuffer buffer = new StringBuffer();
270                buffer.append(name());
271                Map<PropName, Object> param = readRedisParam();
272                if(param==null){
273                        buffer.append("(UNDEFINED)");
274                }else{
275                        buffer.append("(").append(JedisUtils.getCanonicalURI(param).toString()).append(")");
276                }
277                return buffer.toString();
278        }
279}