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