content: /*
   * Copyright 2013-2023 the original author or authors.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      https://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.alibaba.cloud.nacos;
  
  import java.net.Inet4Address;
  import java.net.Inet6Address;
  import java.net.InetAddress;
  import java.net.NetworkInterface;
  import java.util.Enumeration;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.Objects;
  import java.util.Properties;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  import com.alibaba.cloud.commons.context.support.PropertySourcesUtils;
  import com.alibaba.cloud.commons.lang.StringUtils;
  import com.alibaba.cloud.nacos.event.NacosDiscoveryInfoChangedEvent;
  import com.alibaba.cloud.nacos.util.InetIPv6Utils;
  import com.alibaba.nacos.api.naming.NamingService;
  import com.alibaba.nacos.api.naming.PreservedMetadataKeys;
  import com.alibaba.nacos.client.naming.utils.UtilAndComs;
  import jakarta.annotation.PostConstruct;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.beans.factory.annotation.Value;
  import org.springframework.boot.context.properties.ConfigurationProperties;
  import org.springframework.cloud.commons.util.InetUtils;
  import org.springframework.context.ApplicationEventPublisher;
  import org.springframework.core.env.ConfigurableEnvironment;
  import org.springframework.core.env.Environment;
  
  import static com.alibaba.nacos.api.PropertyKeyConst.ACCESS_KEY;
  import static com.alibaba.nacos.api.PropertyKeyConst.ENDPOINT;
  import static com.alibaba.nacos.api.PropertyKeyConst.ENDPOINT_PORT;
  import static com.alibaba.nacos.api.PropertyKeyConst.NAMESPACE;
  import static com.alibaba.nacos.api.PropertyKeyConst.NAMING_LOAD_CACHE_AT_START;
  import static com.alibaba.nacos.api.PropertyKeyConst.PASSWORD;
  import static com.alibaba.nacos.api.PropertyKeyConst.SECRET_KEY;
  import static com.alibaba.nacos.api.PropertyKeyConst.SERVER_ADDR;
  import static com.alibaba.nacos.api.PropertyKeyConst.USERNAME;
  
  /**
   * @author dungu.zpf
   * @author xiaojing
   * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
   * @author <a href="mailto:lyuzb@lyuzb.com">lyuzb</a>
   * @author <a href="mailto:78552423@qq.com">eshun</a>
   * @author freeman
   */
  @ConfigurationProperties("spring.cloud.nacos.discovery")
  public class NacosDiscoveryProperties {
  
  	/**
  	 * Prefix of {@link NacosDiscoveryProperties}.
  	 */
  	public static final String PREFIX = "spring.cloud.nacos.discovery";
  	private static final Logger log = LoggerFactory
  			.getLogger(NacosDiscoveryProperties.class);
  	private static final Pattern PATTERN = Pattern.compile("-(\\w)");
  
  	private static final String IPV4 = "IPv4";
  
  	private static final String IPV6 = "IPv6";
  
  	/**
  	 * nacos discovery server address.
  	 */
  	private String serverAddr;
  
  	/**
  	 * the nacos authentication username.
  	 */
  	private String username;
  
  	/**
  	 * the nacos authentication password.
  	 */
  	private String password;
  
  	/**
  	 * the domain name of a service, through which the server address can be dynamically
  	 * obtained.
  	 */
  	private String endpoint;
  
  	/**
  	 * namespace, separation registry of different environments.
  	 */
  	private String namespace;
  
  	/**
  	 * watch delay,duration to pull new service from nacos server.
  	 */
  	private long watchDelay = 30000;
  
  	/**
  	 * nacos naming log file name.
  	 */
  	private String logName;
  
  	/**
  	 * service name to registry.
  	 */
  	@Value("${spring.cloud.nacos.discovery.service:${spring.application.name:}}")
  	private String service;
  
  	/**
  	 * weight for service instance, the larger the value, the larger the weight.
  	 */
  	private float weight = 1;
  
  	/**
  	 * cluster name for nacos .
  	 */
  	private String clusterName;
  
  	/**
  	 * group name for nacos.
  	 */
  	private String group = "DEFAULT_GROUP";
  
  	/**
  	 * naming load from local cache at application start. true is load.
  	 */
  	private String namingLoadCacheAtStart = "false";
  
  	/**
  	 * extra metadata to register.
  	 */
  	private Map<String, String> metadata = new HashMap<>();
  
  	/**
  	 * if you just want to subscribe, but don't want to register your service, set it to
  	 * false.
  	 */
  	private boolean registerEnabled = true;
  
  	/**
  	 * The ip address your want to register for your service instance, needn't to set it
  	 * if the auto detect ip works well.
  	 */
  	private String ip;
  
  	/**
  	 * which network interface's ip you want to register.
  	 */
  	private String networkInterface = "";
  
  	/**
  	 * choose IPv4 or IPv6,if you don't set it will choose IPv4.
  	 * When IPv6 is chosen but no IPv6 can be found, system will automatically find IPv4 to ensure there is an
  	 * available service address.
  	 */
  	private String ipType;
  
  	/**
  	 * The port your want to register for your service instance, needn't to set it if the
  	 * auto detect port works well.
  	 */
  	private int port = -1;
  
  	/**
  	 * whether your service is a https service.
  	 */
  	private boolean secure = false;
  
  	/**
  	 * access key for namespace.
  	 */
  	private String accessKey;
  
  	/**
  	 * secret key for namespace.
  	 */
  	private String secretKey;
  
  	/**
  	 * Heart beat interval. Time unit: millisecond.
  	 */
  	private Integer heartBeatInterval;
  
  	/**
  	 * Heart beat timeout. Time unit: millisecond.
  	 */
  	private Integer heartBeatTimeout;
  
  	/**
  	 * Ip delete timeout. Time unit: millisecond.
  	 */
  	private Integer ipDeleteTimeout;
  
  	/**
  	 * If instance is enabled to accept request. The default value is true.
  	 */
  	private boolean instanceEnabled = true;
  
  	/**
  	 * If instance is ephemeral.The default value is true.
  	 */
  	private boolean ephemeral = true;
  
  	/**
  	 * Whether to enable nacos failure tolerance. If enabled, nacos will return cached
  	 * values when exceptions occur.
  	 */
  	private boolean failureToleranceEnabled;
  
  	/**
  	 * Throw exceptions during service registration if true, otherwise, log error
  	 * (defaults to true).
  	 */
  	private boolean failFast = true;
  
  	/**
  	 * graceful shutdown wait time. Time unit: millisecond.
  	 * default is 10s
  	 * When the Springboot shutdown hook is executed, remove the Nacos service, wait for 10 seconds, and then Tomcat rejects the request
  	 */
  	private Integer gracefulShutdownWaitTime = 10 * 1000;
  
  	@Autowired
  	private InetIPv6Utils inetIPv6Utils;
  
  	@Autowired
  	private InetUtils inetUtils;
  
  	@Autowired
  	private Environment environment;
  
  	@Autowired
  	private NacosServiceManager nacosServiceManager;
  
  	@Autowired
  	private ApplicationEventPublisher applicationEventPublisher;
  
  	@PostConstruct
  	public void init() throws Exception {
  
  		metadata.put(PreservedMetadataKeys.REGISTER_SOURCE, "SPRING_CLOUD");
  		if (secure) {
  			metadata.put("secure", "true");
  		}
  
  		serverAddr = Objects.toString(serverAddr, "");
  		if (serverAddr.endsWith("/")) {
  			serverAddr = serverAddr.substring(0, serverAddr.length() - 1);
  		}
  		endpoint = Objects.toString(endpoint, "");
  		namespace = Objects.toString(namespace, "");
  		logName = Objects.toString(logName, "");
  
  		if (StringUtils.isEmpty(ip)) {
  			// traversing network interfaces if didn't specify an interface
  			if (StringUtils.isEmpty(networkInterface)) {
  				if (ipType == null) {
  					ip = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
  					String ipv6Addr = inetIPv6Utils.findIPv6Address();
  					metadata.put(IPV6, ipv6Addr);
  					if (ipv6Addr != null) {
  						metadata.put(IPV6, ipv6Addr);
  					}
  				}
  				else if (IPV4.equalsIgnoreCase(ipType)) {
  					ip = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
  				}
  				else if (IPV6.equalsIgnoreCase(ipType)) {
  					ip = inetIPv6Utils.findIPv6Address();
  					if (StringUtils.isEmpty(ip)) {
  						log.warn("There is no available IPv6 found. Spring Cloud Alibaba will automatically find IPv4.");
  						ip = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
  					}
  				}
  				else {
  					throw new IllegalArgumentException(
  							"please checking the type of IP " + ipType);
  				}
  			}
  			else {
  				NetworkInterface netInterface = NetworkInterface
  						.getByName(networkInterface);
  				if (null == netInterface) {
  					throw new IllegalArgumentException(
  							"no such interface " + networkInterface);
  				}
  
  				Enumeration<InetAddress> inetAddress = netInterface.getInetAddresses();
  				while (inetAddress.hasMoreElements()) {
  					InetAddress currentAddress = inetAddress.nextElement();
  					if (currentAddress instanceof Inet4Address
  							|| currentAddress instanceof Inet6Address
  							&& !currentAddress.isLoopbackAddress()) {
  						ip = currentAddress.getHostAddress();
  						break;
  					}
  				}
  
  				if (StringUtils.isEmpty(ip)) {
  					throw new RuntimeException("cannot find available ip from"
  							+ " network interface " + networkInterface);
  				}
  
  			}
  		}
  
  		this.overrideFromEnv(environment);
  		if (nacosServiceManager.isNacosDiscoveryInfoChanged(this)) {
  			applicationEventPublisher
  					.publishEvent(new NacosDiscoveryInfoChangedEvent(this));
  		}
  		nacosServiceManager.setNacosDiscoveryProperties(this);
  	}
  
  	/**
  	 * recommend to use {@link NacosServiceManager#getNamingService()}.
  	 * @return NamingService
  	 */
  	@Deprecated
  	public NamingService namingServiceInstance() {
  		return nacosServiceManager.getNamingService();
  	}
  
  	public String getEndpoint() {
  		return endpoint;
  	}
  
  	public void setEndpoint(String endpoint) {
  		this.endpoint = endpoint;
  	}
  
  	public String getNamespace() {
  		return namespace;
  	}
  
  	public void setNamespace(String namespace) {
  		this.namespace = namespace;
  	}
  
  	public String getLogName() {
  		return logName;
  	}
  
  	public void setLogName(String logName) {
  		this.logName = logName;
  	}
  
  	public void setInetUtils(InetUtils inetUtils) {
  		this.inetUtils = inetUtils;
  	}
  
  	public float getWeight() {
  		return weight;
  	}
  
  	public void setWeight(float weight) {
  		this.weight = weight;
  	}
  
  	public String getClusterName() {
  		return clusterName;
  	}
  
  	public void setClusterName(String clusterName) {
  		this.clusterName = clusterName;
  	}
  
  	public String getService() {
  		return service;
  	}
  
  	public void setService(String service) {
  		this.service = service;
  	}
  
  	public boolean isRegisterEnabled() {
  		return registerEnabled;
  	}
  
  	public void setRegisterEnabled(boolean registerEnabled) {
  		this.registerEnabled = registerEnabled;
  	}
  
  	public String getIp() {
  		return ip;
  	}
  
  	public void setIp(String ip) {
  		this.ip = ip;
  	}
  
  	public String getIpType() {
  		return ipType;
  	}
  
  	public void setIpType(String ipType) {
  		this.ipType = ipType;
  	}
  
  	public String getNetworkInterface() {
  		return networkInterface;
  	}
  
  	public void setNetworkInterface(String networkInterface) {
  		this.networkInterface = networkInterface;
  	}
  
  	public int getPort() {
  		return port;
  	}
  
  	public void setPort(int port) {
  		this.port = port;
  	}
  
  	public boolean isSecure() {
  		return secure;
  	}
  
  	public void setSecure(boolean secure) {
  		this.secure = secure;
  	}
  
  	public Map<String, String> getMetadata() {
  		return metadata;
  	}
  
  	public void setMetadata(Map<String, String> metadata) {
  		this.metadata = metadata;
  	}
  
  	public String getServerAddr() {
  		return serverAddr;
  	}
  
  	public void setServerAddr(String serverAddr) {
  		this.serverAddr = serverAddr;
  	}
  
  	public String getAccessKey() {
  		return accessKey;
  	}
  
  	public void setAccessKey(String accessKey) {
  		this.accessKey = accessKey;
  	}
  
  	public String getSecretKey() {
  		return secretKey;
  	}
  
  	public void setSecretKey(String secretKey) {
  		this.secretKey = secretKey;
  	}
  
  	public Integer getHeartBeatInterval() {
  		return heartBeatInterval;
  	}
  
  	public void setHeartBeatInterval(Integer heartBeatInterval) {
  		this.heartBeatInterval = heartBeatInterval;
  	}
  
  	public Integer getHeartBeatTimeout() {
  		return heartBeatTimeout;
  	}
  
  	public void setHeartBeatTimeout(Integer heartBeatTimeout) {
  		this.heartBeatTimeout = heartBeatTimeout;
  	}
  
  	public Integer getIpDeleteTimeout() {
  		return ipDeleteTimeout;
  	}
  
  	public void setIpDeleteTimeout(Integer ipDeleteTimeout) {
  		this.ipDeleteTimeout = ipDeleteTimeout;
  	}
  
  	public String getNamingLoadCacheAtStart() {
  		return namingLoadCacheAtStart;
  	}
  
  	public void setNamingLoadCacheAtStart(String namingLoadCacheAtStart) {
  		this.namingLoadCacheAtStart = namingLoadCacheAtStart;
  	}
  
  	public long getWatchDelay() {
  		return watchDelay;
  	}
  
  	public void setWatchDelay(long watchDelay) {
  		this.watchDelay = watchDelay;
  	}
  
  	public String getGroup() {
  		return group;
  	}
  
  	public void setGroup(String group) {
  		this.group = group;
  	}
  
  	public String getUsername() {
  		return username;
  	}
  
  	public void setUsername(String username) {
  		this.username = username;
  	}
  
  	public String getPassword() {
  		return password;
  	}
  
  	public void setPassword(String password) {
  		this.password = password;
  	}
  
  	public boolean isInstanceEnabled() {
  		return instanceEnabled;
  	}
  
  	public void setInstanceEnabled(boolean instanceEnabled) {
  		this.instanceEnabled = instanceEnabled;
  	}
  
  	public boolean isEphemeral() {
  		return ephemeral;
  	}
  
  	public void setEphemeral(boolean ephemeral) {
  		this.ephemeral = ephemeral;
  	}
  
  	public boolean isFailureToleranceEnabled() {
  		return failureToleranceEnabled;
  	}
  
  	public void setFailureToleranceEnabled(boolean failureToleranceEnabled) {
  		this.failureToleranceEnabled = failureToleranceEnabled;
  	}
  
  	public boolean isFailFast() {
  		return failFast;
  	}
  
  	public void setFailFast(boolean failFast) {
  		this.failFast = failFast;
  	}
  
  	public Integer getGracefulShutdownWaitTime() {
  		return gracefulShutdownWaitTime;
  	}
  
  	public void setGracefulShutdownWaitTime(Integer gracefulShutdownWaitTime) {
  		this.gracefulShutdownWaitTime = gracefulShutdownWaitTime;
  	}
  
  	@Override
  	public boolean equals(Object o) {
  		if (this == o) {
  			return true;
  		}
  		if (o == null || getClass() != o.getClass()) {
  			return false;
  		}
  		NacosDiscoveryProperties that = (NacosDiscoveryProperties) o;
  		return watchDelay == that.watchDelay && Float.compare(that.weight, weight) == 0
  				&& registerEnabled == that.registerEnabled && port == that.port
  				&& secure == that.secure && instanceEnabled == that.instanceEnabled
  				&& ephemeral == that.ephemeral
  				&& failureToleranceEnabled == that.failureToleranceEnabled
  				&& Objects.equals(serverAddr, that.serverAddr)
  				&& Objects.equals(username, that.username)
  				&& Objects.equals(password, that.password)
  				&& Objects.equals(endpoint, that.endpoint)
  				&& Objects.equals(namespace, that.namespace)
  				&& Objects.equals(logName, that.logName)
  				&& Objects.equals(service, that.service)
  				&& Objects.equals(clusterName, that.clusterName)
  				&& Objects.equals(group, that.group)
  				&& Objects.equals(namingLoadCacheAtStart, that.namingLoadCacheAtStart)
  				&& Objects.equals(metadata, that.metadata) && Objects.equals(ip, that.ip)
  				&& Objects.equals(networkInterface, that.networkInterface)
  				&& Objects.equals(accessKey, that.accessKey)
  				&& Objects.equals(secretKey, that.secretKey)
  				&& Objects.equals(heartBeatInterval, that.heartBeatInterval)
  				&& Objects.equals(heartBeatTimeout, that.heartBeatTimeout)
  				&& Objects.equals(failFast, that.failFast)
  				&& Objects.equals(ipDeleteTimeout, that.ipDeleteTimeout);
  	}
  
  	@Override
  	public int hashCode() {
  		return Objects.hash(serverAddr, username, password, endpoint, namespace,
  				watchDelay, logName, service, weight, clusterName, group,
  				namingLoadCacheAtStart, metadata, registerEnabled, ip, networkInterface,
  				port, secure, accessKey, secretKey, heartBeatInterval, heartBeatTimeout,
  				ipDeleteTimeout, instanceEnabled, ephemeral, failureToleranceEnabled,
  				failFast);
  	}
  
  	@Override
  	public String toString() {
  		return "NacosDiscoveryProperties{" + "serverAddr='" + serverAddr + '\''
  				+ ", username='" + username + '\'' + ", password='" + password + '\''
  				+ ", endpoint='" + endpoint + '\'' + ", namespace='" + namespace + '\''
  				+ ", watchDelay=" + watchDelay + ", logName='" + logName + '\''
  				+ ", service='" + service + '\'' + ", weight=" + weight
  				+ ", clusterName='" + clusterName + '\'' + ", group='" + group + '\''
  				+ ", namingLoadCacheAtStart='" + namingLoadCacheAtStart + '\''
  				+ ", metadata=" + metadata + ", registerEnabled=" + registerEnabled
  				+ ", ip='" + ip + '\'' + ", networkInterface='" + networkInterface + '\''
  				+ ", port=" + port + ", secure=" + secure + ", accessKey='" + accessKey
  				+ '\'' + ", secretKey='" + secretKey + '\'' + ", heartBeatInterval="
  				+ heartBeatInterval + ", heartBeatTimeout=" + heartBeatTimeout
  				+ ", ipDeleteTimeout=" + ipDeleteTimeout + ", instanceEnabled="
  				+ instanceEnabled + ", ephemeral=" + ephemeral
  				+ ", failureToleranceEnabled=" + failureToleranceEnabled + '}'
  				+ ", ipDeleteTimeout=" + ipDeleteTimeout + ", failFast=" + failFast + '}';
  	}
  
  	public void overrideFromEnv(Environment env) {
  
  		if (StringUtils.isEmpty(this.getServerAddr())) {
  			String serverAddr = env
  					.resolvePlaceholders("${spring.cloud.nacos.discovery.server-addr:}");
  			if (StringUtils.isEmpty(serverAddr)) {
  				serverAddr = env.resolvePlaceholders(
  						"${spring.cloud.nacos.server-addr:127.0.0.1:8848}");
  			}
  			this.setServerAddr(serverAddr);
  		}
  		if (StringUtils.isEmpty(this.getNamespace())) {
  			this.setNamespace(env
  					.resolvePlaceholders("${spring.cloud.nacos.discovery.namespace:}"));
  		}
  		if (StringUtils.isEmpty(this.getAccessKey())) {
  			this.setAccessKey(env
  					.resolvePlaceholders("${spring.cloud.nacos.discovery.access-key:}"));
  		}
  		if (StringUtils.isEmpty(this.getSecretKey())) {
  			this.setSecretKey(env
  					.resolvePlaceholders("${spring.cloud.nacos.discovery.secret-key:}"));
  		}
  		if (StringUtils.isEmpty(this.getLogName())) {
  			this.setLogName(
  					env.resolvePlaceholders("${spring.cloud.nacos.discovery.log-name:}"));
  		}
  		if (StringUtils.isEmpty(this.getClusterName())) {
  			this.setClusterName(env.resolvePlaceholders(
  					"${spring.cloud.nacos.discovery.cluster-name:}"));
  		}
  		if (StringUtils.isEmpty(this.getEndpoint())) {
  			this.setEndpoint(
  					env.resolvePlaceholders("${spring.cloud.nacos.discovery.endpoint:}"));
  		}
  		if (StringUtils.isEmpty(this.getGroup())) {
  			this.setGroup(
  					env.resolvePlaceholders("${spring.cloud.nacos.discovery.group:}"));
  		}
  		if (StringUtils.isEmpty(this.getUsername())) {
  			this.setUsername(env.resolvePlaceholders("${spring.cloud.nacos.username:}"));
  		}
  		if (StringUtils.isEmpty(this.getPassword())) {
  			this.setPassword(env.resolvePlaceholders("${spring.cloud.nacos.password:}"));
  		}
  	}
  
  	public Properties getNacosProperties() {
  		Properties properties = new Properties();
  		properties.put(SERVER_ADDR, serverAddr);
  		properties.put(USERNAME, Objects.toString(username, ""));
  		properties.put(PASSWORD, Objects.toString(password, ""));
  		properties.put(NAMESPACE, namespace);
  		properties.put(UtilAndComs.NACOS_NAMING_LOG_NAME, logName);
  
  		if (endpoint.contains(":")) {
  			int index = endpoint.indexOf(":");
  			properties.put(ENDPOINT, endpoint.substring(0, index));
  			properties.put(ENDPOINT_PORT, endpoint.substring(index + 1));
  		}
  		else {
  			properties.put(ENDPOINT, endpoint);
  		}
  
  		properties.put(ACCESS_KEY, accessKey);
  		properties.put(SECRET_KEY, secretKey);
  		// only used for instance.setClusterName()
  //		properties.put(CLUSTER_NAME, clusterName);
  		properties.put(NAMING_LOAD_CACHE_AT_START, namingLoadCacheAtStart);
  
  		enrichNacosDiscoveryProperties(properties);
  		return properties;
  	}
  
  	private void enrichNacosDiscoveryProperties(Properties nacosDiscoveryProperties) {
  		Map<String, Object> properties = PropertySourcesUtils
  				.getSubProperties((ConfigurableEnvironment) environment, PREFIX);
  		properties.forEach((k, v) -> nacosDiscoveryProperties.putIfAbsent(resolveKey(k),
  				String.valueOf(v)));
  	}
  
  	private String resolveKey(String key) {
  		Matcher matcher = PATTERN.matcher(key);
  		StringBuffer sb = new StringBuffer();
  		while (matcher.find()) {
  			matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
  		}
  		matcher.appendTail(sb);
  		return sb.toString();
  	}
  
  }
  