content: package com.alicp.jetcache.autoconfigure;
  
  import com.alicp.jetcache.CacheBuilder;
  import com.alicp.jetcache.CacheConfigException;
  import com.alicp.jetcache.redis.RedisCacheBuilder;
  import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
  import org.springframework.beans.PropertyValues;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.context.annotation.Bean;
  import org.springframework.context.annotation.Conditional;
  import org.springframework.context.annotation.Configuration;
  import org.springframework.core.env.Environment;
  import org.springframework.util.ClassUtils;
  import redis.clients.jedis.HostAndPort;
  import redis.clients.jedis.Jedis;
  import redis.clients.jedis.JedisCluster;
  import redis.clients.jedis.JedisPool;
  import redis.clients.jedis.JedisSentinelPool;
  import redis.clients.jedis.Protocol;
  import redis.clients.jedis.UnifiedJedis;
  import redis.clients.jedis.util.Pool;
  
  import java.lang.reflect.Constructor;
  import java.lang.reflect.Method;
  import java.util.ArrayList;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Objects;
  import java.util.Set;
  import java.util.stream.Collectors;
  
  /**
   * Created on 2016/11/25.
   *
   * @author huangli
   */
  @Configuration
  @Conditional(RedisAutoConfiguration.RedisCondition.class)
  public class RedisAutoConfiguration {
  
      public static final String AUTO_INIT_BEAN_NAME = "redisAutoInit";
  
      @Bean(name = AUTO_INIT_BEAN_NAME)
      public RedisAutoInit redisAutoInit() {
          return new RedisAutoInit();
      }
  
      public static class RedisCondition extends JetCacheCondition {
          public RedisCondition() {
              super("redis");
          }
      }
  
      public static class RedisAutoInit extends ExternalCacheAutoInit {
          public RedisAutoInit() {
              super("redis");
          }
  
          @Autowired
          private AutoConfigureBeans autoConfigureBeans;
  
          @Override
          protected CacheBuilder initCache(ConfigTree ct, String cacheAreaWithPrefix) {
              Object jedisObj = parsePool(ct);
              boolean readFromSlave = Boolean.parseBoolean(ct.getProperty("readFromSlave", "False"));
  
              RedisCacheBuilder.RedisCacheBuilderImpl builder = RedisCacheBuilder.createRedisCacheBuilder()
                      .readFromSlave(readFromSlave);
              if (jedisObj instanceof Pool) {
                  builder.jedisPool((Pool<Jedis>) jedisObj);
              } else {
                  builder.jedis((UnifiedJedis) jedisObj);
              }
  
              ConfigTree slaves = ct.subTree("slaves.");
              Set<String> slaveNames = slaves.directChildrenKeys();
              if (slaveNames.size() > 0) {
                  List<Object> slavesObjects = new ArrayList<>();
                  int[] slavesWeights = new int[slaveNames.size()];
                  int i = 0;
                  for (String slaveName: slaveNames) {
                      ConfigTree slaveConfig = slaves.subTree(slaveName + ".");
                      slavesObjects.add(parsePool(slaveConfig));
                      slavesWeights[i] = Integer.parseInt(slaveConfig.getProperty("weight","100"));
                      i++;
                  }
  
                  builder.slaveReadWeights(slavesWeights);
                  if (slavesObjects.get(0) instanceof Pool) {
                      builder.jedisSlavePools(slavesObjects.toArray(new Pool[0]));
                  } else {
                      builder.slaves(slavesObjects.toArray(new UnifiedJedis[0]));
                  }
              }
  
              parseGeneralConfig(builder, ct);
  
              // eg: "jedisPool.remote.default"
              autoConfigureBeans.getCustomContainer().put("jedisPool." + cacheAreaWithPrefix, jedisObj);
              if (jedisObj instanceof UnifiedJedis) {
                  autoConfigureBeans.getCustomContainer().put("jedis." + cacheAreaWithPrefix, jedisObj);
              }
  
              return builder;
          }
  
          private Object parsePool(ConfigTree ct) {
              GenericObjectPoolConfig poolConfig = parsePoolConfig(ct);
  
              Map<String, Object> cluster = ct.subTree("cluster"/*there is no dot*/).getProperties();
              String host = ct.getProperty("host", (String) null);
              int port = Integer.parseInt(ct.getProperty("port", "0"));
              int timeout = Integer.parseInt(ct.getProperty("timeout", String.valueOf(Protocol.DEFAULT_TIMEOUT)));
              int connectionTimeout = Integer.parseInt(ct.getProperty("connectionTimeout", String.valueOf(timeout)));
              int soTimeout = Integer.parseInt(ct.getProperty("soTimeout", String.valueOf(timeout)));
              String user = ct.getProperty("user", (String) null);
              String password = ct.getProperty("password", (String) null);
              int database = Integer.parseInt(ct.getProperty("database", String.valueOf(Protocol.DEFAULT_DATABASE)));
              String clientName = ct.getProperty("clientName", (String) null);
              boolean ssl = Boolean.parseBoolean(ct.getProperty("ssl", "false"));
  
  
              String sentinels = ct.getProperty("sentinels", (String) null);//ip1:port,ip2:port
  
              if (sentinels == null) {
                  if (cluster == null || cluster.size() == 0) {
                      Objects.requireNonNull(host, "host is required");
                      if (port == 0) {
                          throw new IllegalStateException("port is required");
                      }
                      return new JedisPool(poolConfig, host, port, connectionTimeout, soTimeout, user, password,
                              database, clientName, ssl);
                  } else {
                      int maxAttempt = Integer.parseInt(ct.getProperty("maxAttempt", "5"));
                      Set<HostAndPort> hostAndPortSet = cluster.values().stream()
                              .map(uri -> uri.toString().split(":"))
                              .map(hostAndPort -> new HostAndPort(hostAndPort[0], Integer.parseInt(hostAndPort[1])))
                              .collect(Collectors.toSet());
                      return new JedisCluster(hostAndPortSet, connectionTimeout, soTimeout, maxAttempt, user, password,
                              clientName, poolConfig, ssl);
                  }
              } else {
                  String masterName = ct.getProperty("masterName", (String) null);
                  Objects.requireNonNull(masterName, "masterName is required");
                  int sentinelConnectionTimeout = Integer.parseInt(ct.getProperty("sentinelConnectionTimeout", String.valueOf(2000)));
                  int sentinelSoTimeout = Integer.parseInt(ct.getProperty("sentinelSoTimeout", String.valueOf(2000)));
                  String sentinelUser = ct.getProperty("sentinelUser", null);
                  String sentinelPassword = ct.getProperty("sentinelPassword", null);
                  String sentinelClientName = ct.getProperty("sentinelClientName", null);
                  String[] strings = sentinels.split(",");
                  HashSet<String> sentinelsSet = new HashSet<>();
                  for (String s : strings) {
                      if (s != null && !s.trim().equals("")) {
                          sentinelsSet.add(s.trim());
                      }
                  }
                  return new JedisSentinelPool(masterName, sentinelsSet, poolConfig, connectionTimeout, soTimeout,
                          user, password, database, clientName, sentinelConnectionTimeout, sentinelSoTimeout,
                          sentinelUser, sentinelPassword, sentinelClientName);
              }
          }
  
          private GenericObjectPoolConfig parsePoolConfig(ConfigTree ct) {
              try {
                  // Spring Boot 2.0 removed RelaxedDataBinder class. Binder class not exists in 1.X
                  if (ClassUtils.isPresent("org.springframework.boot.context.properties.bind.Binder",
                          this.getClass().getClassLoader())) {
                      // Spring Boot 2.0+
                      String prefix = ct.subTree("poolConfig").getPrefix().toLowerCase();
  
                      // invoke following code by reflect
                      // Binder binder = Binder.get(environment);
                      // return binder.bind(name, Bindable.of(GenericObjectPoolConfig.class)).get();
  
                      Class<?> binderClass = Class.forName("org.springframework.boot.context.properties.bind.Binder");
                      Class<?> bindableClass = Class.forName("org.springframework.boot.context.properties.bind.Bindable");
                      Class<?> bindResultClass = Class.forName("org.springframework.boot.context.properties.bind.BindResult");
                      Method getMethodOnBinder = binderClass.getMethod("get", Environment.class);
                      Method getMethodOnBindResult = bindResultClass.getMethod("get");
                      Method bindMethod = binderClass.getMethod("bind", String.class, bindableClass);
                      Method ofMethod = bindableClass.getMethod("of", Class.class);
                      Object binder = getMethodOnBinder.invoke(null, environment);
                      Object bindable = ofMethod.invoke(null, GenericObjectPoolConfig.class);
                      Object bindResult = bindMethod.invoke(binder, prefix, bindable);
                      return (GenericObjectPoolConfig) getMethodOnBindResult.invoke(bindResult);
                  } else {
                      // Spring Boot 1.X
                      GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
                      Map<String, Object> props = ct.subTree("poolConfig.").getProperties();
  
                      // invoke following code by reflect
                      //RelaxedDataBinder binder = new RelaxedDataBinder(poolConfig);
                      //binder.bind(new MutablePropertyValues(props));
  
                      Class<?> relaxedDataBinderClass = Class.forName("org.springframework.boot.bind.RelaxedDataBinder");
                      Class<?> mutablePropertyValuesClass = Class.forName("org.springframework.beans.MutablePropertyValues");
                      Constructor<?> c1 = relaxedDataBinderClass.getConstructor(Object.class);
                      Constructor<?> c2 = mutablePropertyValuesClass.getConstructor(Map.class);
                      Method bindMethod = relaxedDataBinderClass.getMethod("bind", PropertyValues.class);
                      Object binder = c1.newInstance(poolConfig);
                      bindMethod.invoke(binder, c2.newInstance(props));
                      return poolConfig;
                  }
              } catch (Throwable ex) {
                  throw new CacheConfigException("parse poolConfig fail", ex);
              }
  
          }
      }
  
  
  }
  