All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.javaclub.toolbox.conf.CompositeAppConfigProperties Maven / Gradle / Ivy

/*
 * @(#)CompositeAppConfigProperties.java	2021-8-31
 *
 * Copyright (c) 2021. All Rights Reserved.
 *
 */

package com.github.javaclub.toolbox.conf;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.javaclub.toolbox.ToolBox.Collections;
import com.github.javaclub.toolbox.ToolBox.Environments;
import com.github.javaclub.toolbox.ToolBox.Files;
import com.github.javaclub.toolbox.ToolBox.IO;
import com.github.javaclub.toolbox.ToolBox.Maps;
import com.github.javaclub.toolbox.ToolBox.Numbers;
import com.github.javaclub.toolbox.ToolBox.Objects;
import com.github.javaclub.toolbox.ToolBox.Strings;
import com.github.javaclub.toolbox.conf.yml.YamlParseException;
import com.github.javaclub.toolbox.conf.yml.YmlConfigFile;
import com.github.javaclub.toolbox.file.FileChangeListener;
import com.github.javaclub.toolbox.file.FileMonitor;
import com.github.javaclub.toolbox.thread.ExecutorServiceInstance;
import com.google.common.collect.Lists;

/**
 * CompositeAppConfigProperties
 *
 * @author Gerald Chen
 * @version $Id: CompositeAppConfigProperties.java 2021-8-31 15:38:17 Exp $
 */
public class CompositeAppConfigProperties {
	
	private static final Logger log = LoggerFactory.getLogger(CompositeAppConfigProperties.class);
	
	private volatile Properties cachedProperties;
	private final FileChangeListener changeListener = new ConfChangedListener(this);
	
	private static class SingletonHolder {
        private static final CompositeAppConfigProperties INSTANCE = new CompositeAppConfigProperties();  
    }

	private CompositeAppConfigProperties() {
		String appJarPath = ConfUtils.getAppJarPath();
		log.info("Current Application Jar path: {}", appJarPath);
		this.cachedProperties = new Properties();
		if (Strings.isBlank(appJarPath)) {
			Set pathUrls = ConfUtils.getClasspathArray();
			log.info("Loading config from classpath");
			parseClasspathConfig(pathUrls);
			loadAndSetExtraConfigs();
			loadExtraConfigsDelayed();
			return;
		}
		FileMonitor.getInstance().addFileChangeListener(changeListener, appJarPath, 6000L);
		log.info("Current Application Jar is Added to FileMonitor");
		loadFromCurrentJar(appJarPath); // 1. 先从当前应用jar包中获取配置
		overrideByOuterConfig(appJarPath); // 2. 当前应用jar包同级目录及子目录有配置项,则加载并覆盖jar包内的配置
		loadAndSetExtraConfigs(); // 3. 加载其他配置并监听变更
		loadExtraConfigsDelayed(); // 4. 延迟加载一些额外的配置
	}

	public static final CompositeAppConfigProperties getInstance() {
		return SingletonHolder.INSTANCE;
    }
	
	public Properties getAppConfigProperties() {
		return cachedProperties;
	}
	
	public Properties selectConfigProperties(String ... keys) {
		Properties properties = new Properties();
		if(null == keys) {
			return properties;
		}
		for (String key : keys) {
			if ( hasKey(key) ) {
				properties.put(key, getValue(key));
			}
		}
		return properties;
	}
	
	public Properties selectPrefixKeyProperties(String ... prefixKeys) {
		Properties properties = new Properties();
		if(null == prefixKeys) {
			return properties;
		}
		for (String prefix : prefixKeys) {
			Properties group = this.selectPrefixKeyGroup(prefix);
			if ( null !=  group) {
				properties.putAll(group);
			}
		}
		return properties;
	}
	
	public Properties selectPrefixKeyGroup(String prefix) {
		Properties properties = new Properties();
		if(null == cachedProperties || Strings.isBlank(prefix)) {
			return properties;
		}
		for (Object key : cachedProperties.keySet()) {
			String keyVal = Objects.toString(key, "");
			if (Strings.isBlank(keyVal) || !keyVal.startsWith(prefix)) {
				continue;
			}
			properties.put(keyVal, getValue(keyVal));
		}
		
		return properties;
	}

	public boolean hasKey(String configKey) {
		if(null == cachedProperties) {
			return false;
		}
		return cachedProperties.containsKey(configKey);
	}
	
	public String getEnv() {
		return getValue("env", "application.env", "spring.application.env", "system.configs.env",
				"spring.profiles.active", "app.env", "project.env", "configserver.env");
	}
	
	public String getAppName() {
		return getValue("spring.application.name", "dubbo.application.name", "application.name", 
				"app.name", "project.name", "system.configs.appName");
	}
	
	public String getValue(String key) {
		return cachedProperties.getProperty(key);
	}
	
	public String getValueOrDefault(String key, String defaultValueIfNull) {
		String value = cachedProperties.getProperty(key);
		return value == null ? defaultValueIfNull : value;
	}
	
	public String getValueOrDefault(String[] keys, String defaultValueIfNull) {
		String value = getValue(keys);
		return value == null ? defaultValueIfNull : value;
	}
	
	public String getValue(String key, String anotherKey) {
		String val = cachedProperties.getProperty(key);
		return Objects.toString(val, cachedProperties.getProperty(anotherKey));
	}
	
	public String getValue(String... keys) {
		if (Objects.isEmpty(keys)) {
			return null;
		}
		for (String key : keys) {
			String val = cachedProperties.getProperty(key);
			if (null != val) {
				return Strings.cleanQuotes(val);
			}
		}
		return null;
	}
	
	public String getValue(String[] keys, String defaultValueIfNull) {
		return getValueOrDefault(keys, defaultValueIfNull);
	}
	
	public int intValue(String[] keys, int defaultValueIfNull) {
		String val = getValue(keys);
		if (null == val) {
			return defaultValueIfNull;
		}
		Integer integer = Numbers.parseInt(val);
		return null == integer ? defaultValueIfNull : integer.intValue();
	}
	
	public long longValue(String[] keys, long defaultValueIfNull) {
		String val = getValue(keys);
		if (null == val) {
			return defaultValueIfNull;
		}
		Long longVal = Numbers.parseLong(val);
		return null == longVal ? defaultValueIfNull : longVal.longValue();
	}
	
	public boolean boolValue(String[] keys, boolean defaultValueIfNull) {
		for (String key : keys) {
			String val = cachedProperties.getProperty(key);
			if (null != val) {
				Boolean flag = Objects.parseBoolean(val);
				if (null != flag) {
					return flag.booleanValue();
				}
			}
		}
		return defaultValueIfNull;
	}
	
	public byte byteValue(String key) {
		try {
			return Byte.parseByte(getValue(key));
		} catch (NumberFormatException e) {
		}
		return 0;
	}
	
	public byte byteValue(String key, byte defaultVal) {
		try {
			return Byte.parseByte(getValue(key));
		} catch (NumberFormatException e) {
		}
		return defaultVal;
	}
	
	public short shortValue(String key) {
		try {
			return Short.parseShort(getValue(key));
		} catch (NumberFormatException e) {
		}
		return 0;
	}
	
	public short shortValue(String key, short defaultVal) {
		try {
			return Short.parseShort(getValue(key));
		} catch (NumberFormatException e) {
		}
		return defaultVal;
	}
	
	public int intValue(String key) {
		try {
			return Integer.parseInt(getValue(key));
		} catch (NumberFormatException e) {
		}
		return 0;
	}
	
	public int intValue(String key, int defaultVal) {
		try {
			return Integer.parseInt(getValue(key));
		} catch (NumberFormatException e) {
		}
		return defaultVal;
	}
	
	public long longValue(String key) {
		try {
			return Long.parseLong(getValue(key));
		} catch (NumberFormatException e) {
		}
		return 0L;
	}
	
	public long longValue(String key, long defaultVal) {
		try {
			return Long.parseLong(getValue(key));
		} catch (NumberFormatException e) {
		}
		return defaultVal;
	}
	
	public float floatValue(String key) {
		try {
			return Float.parseFloat(getValue(key));
		} catch (NumberFormatException e) {
		}
		return 0.0f;
	}
	
	public float floatValue(String key, float defaultVal) {
		try {
			return Float.parseFloat(getValue(key));
		} catch (NumberFormatException e) {
		}
		return defaultVal;
	}
	
	public double doubleValue(String key) {
		try {
			return Double.parseDouble(getValue(key));
		} catch (NumberFormatException e) {
		}
		return 0D;
	}
	
	public double doubleValue(String key, double defaultVal) {
		try {
			return Double.parseDouble(getValue(key));
		} catch (NumberFormatException e) {
		}
		return defaultVal;
	}
	
	public boolean boolValue(String key, boolean defaultVal) {
		String val = getValue(key);
		if (null == val) {
			return defaultVal;
		}
		Boolean parseVal = Objects.parseBoolean(val);
		boolean boolVal = (null == parseVal ? defaultVal : parseVal.booleanValue());
		return boolVal;
	}
	
	public boolean boolValue(String key) {
		String[] focusArray = new String[] {
			"true", "yes", "enabled", "opened",  
			"ok", "on", "enable", "open", 
			"T", "Y", "O", "1"
		};
		String val = null == getValue(key) ? null : getValue(key).trim();
		return Strings.equalsAnyIgnoreCase(val, focusArray);
	}
	
	public String[] arrayValue(String key) {
		String configValue = getValue(key);
		if (null == configValue) {
			return new String[0];
		}
		return Strings.splitArrayString(configValue);
	}
	
	public String[] arrayValue(String... keys) {
		if (null == keys || 0 >= keys.length) {
			return new String[0];
		}
		for (String key : keys) {
			if (Strings.isBlank(key)) {
				continue;
			}
			String configValue = getValue(key);
			if (Strings.isBlank(configValue)) {
				continue;
			}
			String[] array = Strings.splitArrayString(configValue);
			if (null != array && 0 < array.length) {
				return array;
			}
		}
		return new String[0];
	}
	
	public long bytesAmount(String key, long defaultVal) {
		String configValue = getValue(key);
		if (Strings.isBlank(configValue)) {
			return defaultVal;
		}
		String val = configValue.trim();
		try {
			return Strings.parseDataSize(val);
		} catch (Exception e) {
		}
		return defaultVal;
	}
	
	public synchronized void destroy() {
		if (null == cachedProperties) {
			return;
		}
		cachedProperties.clear();
		log.warn("AppConfigProperties was destroying!");
	}
	
	public synchronized boolean merge(Map map) {
		try {
			if (null == cachedProperties) {
				cachedProperties = new Properties();
			}
			if (Maps.isEmpty(map)) {
				return true;
			}
			cachedProperties.putAll(map);
			this.initAlarmsConfig(false);
		} catch (Exception e) {
			return false;
		}
		return true;
	}
	
	public synchronized boolean remove(String key) {
		try {
			if (null == cachedProperties) {
				cachedProperties = new Properties();
				return true;
			}
			if (!cachedProperties.containsKey(key)) {
				return true;
			}
			cachedProperties.remove(key);
		} catch (Exception e) {
			return false;
		}
		return true;
	}
	
	protected synchronized boolean merge(Properties properties) {
		try {
			if (null == cachedProperties) {
				cachedProperties = new Properties();
			}
			if(null == properties || properties.isEmpty()) {
				return true;
			}
			cachedProperties.putAll(properties);
			this.initAlarmsConfig(false);
		} catch (Exception e) {
			return false;
		}
		return true;
	}
	
	protected synchronized boolean change(Properties properties, Properties removeProperties) {
		try {
			if (null == cachedProperties) {
				cachedProperties = new Properties();
			}
			if(null != properties && !properties.isEmpty()) {
				cachedProperties.putAll(properties);
			}
			if (null != removeProperties && !removeProperties.isEmpty()) {
				for (Object key : removeProperties.keySet()) {
					String keyVal = Objects.toString(key, "");
					if (Strings.isBlank(keyVal)) {
						cachedProperties.remove(keyVal);
					}
				}
			}
			this.initAlarmsConfig(false);
		} catch (Exception e) {
			return false;
		}
		return true;
	}
	
	protected synchronized boolean reset(Properties properties) {
		try {
			if (null == cachedProperties) {
				cachedProperties = new Properties();
			} else {
				cachedProperties.clear();
			}
			if(null != properties && !properties.isEmpty()) {
				cachedProperties.putAll(properties);
			}
			this.loadAndSetExtraConfigs();
		} catch (Exception e) {
			return false;
		}
		return true;
	}
	
	protected void loadExtraConfigsDelayed() {
		ExecutorServiceInstance.get().scheduleAtFixedRate(() -> {
			Properties properties = ExtraConfigsDelayLoader.getInstance().loadConfigs();
			if (null != properties && !properties.isEmpty()) {
				this.merge(properties);
			}
		}, 5L, 90L, TimeUnit.SECONDS);
	}
	
	protected void loadAndSetExtraConfigs() {
		// Apollo配置(如果有的话)
		if (Environments.support(ConfUtils.APOLLO_CONFIG_SERVICE_CLASS)) {
			String configNamespaces = getValue(ConfUtils.APP_APOLLO_BOOTSTRAP_NAMESPACES);
			String[] namespaces = Strings.splitArrayString(configNamespaces);
			log.info("Apollo bootstrap namespaces = {}", configNamespaces);
			Properties properties = ApolloConfigLoader.getInstance().loadApolloConfigs(namespaces);
			if (null != properties && !properties.isEmpty()) {
				this.merge(properties);
			}
		}
		// Configcenter配置 (延迟加载)
		if (Environments.support(ConfUtils.CONFIGCENTER_SERVICE_CLASS)) {
			ExecutorServiceInstance.get().schedule(() -> {
				Properties properties = ConfigcenterLoader.getInstance().loadConfigs();
				if (null != properties && !properties.isEmpty()) {
					this.merge(properties);
					log.info("Configcenter loaded configKeyCount={}", properties.size());
				}
			}, Numbers.random(10L, 200L), TimeUnit.MILLISECONDS);
		}
		// Nacos 配置
		if (Environments.support(ConfUtils.NACOS_SERVICE_CLASS)) {
			log.info("support => {}", ConfUtils.NACOS_SERVICE_CLASS);
			ExecutorServiceInstance.get().schedule(() -> {
				Properties properties = NacosConfigLoader.getInstance().loadConfigs();
				if (null != properties && !properties.isEmpty()) {
					this.merge(properties);
					log.info("NacosConfig loaded configKeyCount={}", properties.size());
				}
			}, Numbers.random(10L, 200L), TimeUnit.MILLISECONDS);
		}
		// 设置一些额外的配置
		if (Environments.support(ConfUtils.FIRE_MONITORS_CLASS)) {
			initAlarmsConfig(true);
		}
	}
	
	private void initAlarmsConfig(boolean stdoutLog) {
		if (hasKey(ConfUtils.CONFIG_KEY_MASTER_AKS) || hasKey(ConfUtils.CONFIG_KEY_SLAVE_AKS)) {
			String masterAks = getValue(ConfUtils.CONFIG_KEY_MASTER_AKS);
			String slaveAks = getValue(ConfUtils.CONFIG_KEY_SLAVE_AKS);
			String[] aks1 = Strings.splitAndTrim(masterAks, ",");
			String[] aks2 = Strings.splitAndTrim(slaveAks, ",");
			List list = new ArrayList();
			if(null != aks1) {
				list.addAll(Arrays.asList(aks1));
			}
			if(null != aks2) {
				list.addAll(Arrays.asList(aks2));
			}
			String[] tokens = list.toArray(new String[0]);
			boolean setDingTalkAks = Objects.invoke(ConfUtils.FIRE_MONITORS_CLASS, "setDingTalkAks", 
					new Class[] { String[].class }, new Object[] { tokens });
			// configcenter => DingTalkUtils
			boolean setWebAks = Objects.invoke(ConfUtils.CONFIGCNTER_DINGTALK_CLASS, "setWebAks", 
					new Class[] { String[].class }, new Object[] { tokens }); 
			if (stdoutLog) {
				log.info("AlarmsConfig init setDingTalkAks = {}, setWebAks = {}", setDingTalkAks, setWebAks);
			}
		}
		if (hasKey(ConfUtils.MC_SMS_API_URL)) {
			boolean setSmsInvokeURL = Objects.invoke(ConfUtils.FIRE_MONITORS_CLASS, "setSmsInvokeURL", 
					new Class[] { String.class }, new Object[] { getValue(ConfUtils.MC_SMS_API_URL) });
			if (stdoutLog) {
				log.info("AlarmsConfig init setSmsInvokeURL = {}", setSmsInvokeURL);
			}
		}
	}
	
	private void parseClasspathConfig(Collection urls) {
		final FileFilter configFileFilter = new FileFilter() {
			public boolean accept(File file) {
				String filename = file.getAbsolutePath().toLowerCase();
				boolean matched = Strings.endsWith(filename, new String[] { ".properties", ".yml", ".yaml" });
				return file.isFile() && matched;
			}
		};
		List filelist = Collections.newArrayList();
		for (String url : urls) {
			if (url.endsWith(".jar")) {
				continue;
			}
			File file = new File(url);
			if (file.exists() && file.isDirectory()) {
				File[] configFiles = Files.listTree(file, configFileFilter);
				if (null != configFiles) {
					filelist.addAll(Arrays.asList(configFiles));
				}
			}
		}
		loadFromDirectory(filelist);
	}
	
	private void overrideByOuterConfig(String appJarPath) {
		File appJarFile = new File(appJarPath);
		File appJarDir = appJarFile.getParentFile();
		
		final FileFilter configFileFilter = new FileFilter() {
			public boolean accept(File file) {
				String filename = file.getAbsolutePath().toLowerCase();
				boolean matched = Strings.endsWith(filename, new String[] { ".properties", ".yml", ".yaml" });
				return file.isFile() && matched;
			}
		};
		// 防止遍历根目录,导致内存溢出
		File[] configFiles = (null == appJarDir.getParentFile()) ? 
				appJarDir.listFiles(configFileFilter) : Files.listTree(appJarDir, configFileFilter);
		loadFromDirectory(null == configFiles ? Lists.newArrayList() : Arrays.asList(configFiles));
	}
	
	private void loadFromDirectory(Collection configFiles) {
		if (null == configFiles || 0 >= configFiles.size()) {
			return;
		}
		try {
			for (File file : configFiles) {
				String filename = file.getAbsolutePath().toLowerCase();
				if (Strings.endsWith(filename, new String[] {
					"target/maven-archiver/pom.properties", "target\\maven-archiver\\pom.properties"
				})) {
					continue;
				}
				if (Strings.containsAny(filename, new String[] {
					"/meta-inf/maven/", "\\meta-inf\\maven", "/META-INF/maven/", "\\META-INF\\maven"
				}) && Strings.endsWithIgnoreCase(filename, "pom.properties")) {
					continue;
				}
				
				if (Strings.containsAny(filename, new String[] {
					"/eclipse/configuration/org.eclipse.osgi/", "\\eclipse\\configuration\\org.eclipse.osgi"
				})) {
					continue;
				}
				InputStream input = new FileInputStream(file);
				if (Strings.endsWithIgnoreCase(filename, ".properties")) {
					this.processPropertiesFile(input);
				} else if (Strings.endsWith(filename, new String[] { ".yml", ".yaml" })) {
					this.processYmlFile(input);
				}
				log.info("Parse config file: {}", filename);
				FileMonitor.getInstance().addFileChangeListener(changeListener, filename, 6000L);
			}
		} catch (Exception e) {
			log.error("overrideByOuterConfig error", e);
			throw new YamlParseException("load Properties from Jar outerConfigFile failed ", e);
		}
	}

	private void loadFromCurrentJar(String appJarPath) {
		JarFile jarFile = null;
    	try {
			jarFile = new JarFile(appJarPath);
			Enumeration enums = jarFile.entries();
			while (enums.hasMoreElements()) {
				JarEntry jarEntry = (JarEntry) enums.nextElement();
				String name = jarEntry.getName().toLowerCase();
				if (jarEntry.isDirectory() || name.indexOf("classes/") <= -1) {
					continue;
				}
				if (!Strings.endsWith(name, new String[] { ".properties", ".yml", ".yaml" })) {
					continue;
				}
				InputStream input = jarFile.getInputStream(jarEntry);
				if (Strings.endsWithIgnoreCase(name, ".properties")) {
					this.processPropertiesFile(input);
				} else if (Strings.endsWith(name, new String[] { ".yml", ".yaml" })) {
					this.processYmlFile(input);
				}
				log.info("Parse JarEntry config: {}", jarEntry.getName());
			}
		} catch (IOException e) {
			log.error("loadFromCurrentJar error", e);
			throw new YamlParseException("load Properties from current Jar failed ", e);
		} finally {
			if(null != jarFile) {
				try {
					jarFile.close();
				} catch (IOException e) {
				}
			}
		}
	}

	private void processYmlFile(InputStream input) throws IOException {
		try {
			String content = Files.readAsString(input, "UTF-8");
			YmlConfigFile ymlConfigFile = new YmlConfigFile(content);
			this.merge(ymlConfigFile.asProperties());
		} finally {
			IO.closeQuietly(input);
		}
	}

	private void processPropertiesFile(InputStream input) throws IOException {
		try {
			Properties p = new Properties();
			p.load(input);
			this.merge(p);
		} finally {
			IO.closeQuietly(input);
		}
	}

	


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy