chore(project): 添加项目配置文件和忽略规则
- 添加 Babel 配置文件支持 ES6+ 语法转换 - 添加 ESLint 忽略规则和配置文件 - 添加 Git 忽略规则文件 - 添加 Travis CI 配置文件 - 添加 1.4.2 版本变更日志文件 - 添加 Helm 图表辅助模板文件 - 添加 Helm 忽略规则文件
This commit is contained in:
40
config/pom.xml
Normal file
40
config/pom.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 1999-2019 Seata.io Group.
|
||||
~
|
||||
~ 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
|
||||
~
|
||||
~ http://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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-parent</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>seata-config</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>seata-config ${project.version}</name>
|
||||
<modules>
|
||||
<module>seata-config-core</module>
|
||||
<module>seata-config-custom</module>
|
||||
<module>seata-config-apollo</module>
|
||||
<module>seata-config-nacos</module>
|
||||
<module>seata-config-zk</module>
|
||||
<module>seata-config-all</module>
|
||||
<module>seata-config-etcd3</module>
|
||||
<module>seata-config-consul</module>
|
||||
<module>seata-config-spring-cloud</module>
|
||||
</modules>
|
||||
</project>
|
||||
56
config/seata-config-all/pom.xml
Normal file
56
config/seata-config-all/pom.xml
Normal file
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 1999-2019 Seata.io Group.
|
||||
~
|
||||
~ 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
|
||||
~
|
||||
~ http://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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-config</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>seata-config-all</artifactId>
|
||||
<name>seata-config-all ${project.version}</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-config-apollo</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-config-zk</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-config-nacos</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-config-etcd3</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-config-consul</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
40
config/seata-config-apollo/pom.xml
Normal file
40
config/seata-config-apollo/pom.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 1999-2019 Seata.io Group.
|
||||
~
|
||||
~ 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
|
||||
~
|
||||
~ http://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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-config</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>seata-config-apollo</artifactId>
|
||||
<name>seata-config-apollo ${project.version}</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-config-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ctrip.framework.apollo</groupId>
|
||||
<artifactId>apollo-client</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config.apollo;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.ctrip.framework.apollo.Config;
|
||||
import com.ctrip.framework.apollo.ConfigService;
|
||||
import com.ctrip.framework.apollo.enums.PropertyChangeType;
|
||||
import com.ctrip.framework.apollo.model.ConfigChange;
|
||||
import io.netty.util.internal.ConcurrentSet;
|
||||
import io.seata.common.exception.NotSupportYetException;
|
||||
import io.seata.common.thread.NamedThreadFactory;
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.config.AbstractConfiguration;
|
||||
import io.seata.config.ConfigFuture;
|
||||
import io.seata.config.Configuration;
|
||||
import io.seata.config.ConfigurationChangeEvent;
|
||||
import io.seata.config.ConfigurationChangeListener;
|
||||
import io.seata.config.ConfigurationChangeType;
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
|
||||
import static io.seata.config.ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR;
|
||||
import static io.seata.config.ConfigurationKeys.FILE_ROOT_CONFIG;
|
||||
|
||||
/**
|
||||
* The type Apollo configuration.
|
||||
*
|
||||
* @author: kl @kailing.pub
|
||||
*/
|
||||
public class ApolloConfiguration extends AbstractConfiguration {
|
||||
|
||||
private static final String REGISTRY_TYPE = "apollo";
|
||||
private static final String APP_ID = "appId";
|
||||
private static final String APOLLO_META = "apolloMeta";
|
||||
private static final String APOLLO_SECRET = "apolloAccesskeySecret";
|
||||
private static final String APOLLO_CLUSTER = "seata";
|
||||
private static final String APOLLO_CONFIG_SERVICE = "apolloConfigService";
|
||||
private static final String PROP_APP_ID = "app.id";
|
||||
private static final String PROP_APOLLO_META = "apollo.meta";
|
||||
private static final String PROP_APOLLO_CONFIG_SERVICE = "apollo.configService";
|
||||
private static final String PROP_APOLLO_SECRET = "apollo.accesskey.secret";
|
||||
private static final String PROP_APOLLO_CLUSTER = "apollo.cluster";
|
||||
private static final String NAMESPACE = "namespace";
|
||||
private static final String DEFAULT_NAMESPACE = "application";
|
||||
private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE;
|
||||
private static volatile Config config;
|
||||
private ExecutorService configOperateExecutor;
|
||||
private static final int CORE_CONFIG_OPERATE_THREAD = 1;
|
||||
private static final ConcurrentMap<String, Set<ConfigurationChangeListener>> LISTENER_SERVICE_MAP
|
||||
= new ConcurrentHashMap<>();
|
||||
private static final int MAX_CONFIG_OPERATE_THREAD = 2;
|
||||
private static volatile ApolloConfiguration instance;
|
||||
|
||||
private ApolloConfiguration() {
|
||||
readyApolloConfig();
|
||||
if (config == null) {
|
||||
synchronized (ApolloConfiguration.class) {
|
||||
if (config == null) {
|
||||
config = ConfigService.getConfig(FILE_CONFIG.getConfig(getApolloNamespaceKey(), DEFAULT_NAMESPACE));
|
||||
configOperateExecutor = new ThreadPoolExecutor(CORE_CONFIG_OPERATE_THREAD,
|
||||
MAX_CONFIG_OPERATE_THREAD, Integer.MAX_VALUE, TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
new NamedThreadFactory("apolloConfigOperate", MAX_CONFIG_OPERATE_THREAD));
|
||||
config.addChangeListener(changeEvent -> {
|
||||
for (String key : changeEvent.changedKeys()) {
|
||||
if (!LISTENER_SERVICE_MAP.containsKey(key)) {
|
||||
continue;
|
||||
}
|
||||
ConfigChange change = changeEvent.getChange(key);
|
||||
ConfigurationChangeEvent event = new ConfigurationChangeEvent(key, change.getNamespace(),
|
||||
change.getOldValue(), change.getNewValue(), getChangeType(change.getChangeType()));
|
||||
LISTENER_SERVICE_MAP.get(key).forEach(listener -> listener.onProcessEvent(event));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets instance.
|
||||
*
|
||||
* @return the instance
|
||||
*/
|
||||
public static ApolloConfiguration getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (ApolloConfiguration.class) {
|
||||
if (instance == null) {
|
||||
instance = new ApolloConfiguration();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) {
|
||||
String value = getConfigFromSysPro(dataId);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
ConfigFuture configFuture = new ConfigFuture(dataId, defaultValue, ConfigFuture.ConfigOperation.GET,
|
||||
timeoutMills);
|
||||
configOperateExecutor.submit(() -> {
|
||||
String result = config.getProperty(dataId, defaultValue);
|
||||
configFuture.setResult(result);
|
||||
});
|
||||
return (String) configFuture.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putConfig(String dataId, String content, long timeoutMills) {
|
||||
throw new NotSupportYetException("not support putConfig");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) {
|
||||
throw new NotSupportYetException("not support atomic operation putConfigIfAbsent");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeConfig(String dataId, long timeoutMills) {
|
||||
throw new NotSupportYetException("not support removeConfig");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addConfigListener(String dataId, ConfigurationChangeListener listener) {
|
||||
if (StringUtils.isBlank(dataId) || listener == null) {
|
||||
return;
|
||||
}
|
||||
LISTENER_SERVICE_MAP.computeIfAbsent(dataId, key -> new ConcurrentSet<>())
|
||||
.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeConfigListener(String dataId, ConfigurationChangeListener listener) {
|
||||
if (StringUtils.isBlank(dataId) || listener == null) {
|
||||
return;
|
||||
}
|
||||
Set<ConfigurationChangeListener> configListeners = getConfigListeners(dataId);
|
||||
if (CollectionUtils.isNotEmpty(configListeners)) {
|
||||
configListeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ConfigurationChangeListener> getConfigListeners(String dataId) {
|
||||
return LISTENER_SERVICE_MAP.get(dataId);
|
||||
}
|
||||
|
||||
private void readyApolloConfig() {
|
||||
Properties properties = System.getProperties();
|
||||
if (!properties.containsKey(PROP_APP_ID)) {
|
||||
System.setProperty(PROP_APP_ID, FILE_CONFIG.getConfig(getApolloAppIdFileKey()));
|
||||
}
|
||||
if (!properties.containsKey(PROP_APOLLO_META)) {
|
||||
System.setProperty(PROP_APOLLO_META, FILE_CONFIG.getConfig(getApolloMetaFileKey()));
|
||||
}
|
||||
if (!properties.containsKey(PROP_APOLLO_SECRET)) {
|
||||
String secretKey = FILE_CONFIG.getConfig(getApolloSecretFileKey());
|
||||
if (!StringUtils.isBlank(secretKey)) {
|
||||
System.setProperty(PROP_APOLLO_SECRET, secretKey);
|
||||
}
|
||||
}
|
||||
if (!properties.containsKey(APOLLO_CLUSTER)) {
|
||||
System.setProperty(PROP_APOLLO_CLUSTER, FILE_CONFIG.getConfig(getApolloCluster()));
|
||||
}
|
||||
if (!properties.containsKey(APOLLO_CONFIG_SERVICE)) {
|
||||
System.setProperty(PROP_APOLLO_CONFIG_SERVICE, FILE_CONFIG.getConfig(getApolloConfigService()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeName() {
|
||||
return REGISTRY_TYPE;
|
||||
}
|
||||
|
||||
private static String getApolloMetaFileKey() {
|
||||
return String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_CONFIG, REGISTRY_TYPE, APOLLO_META);
|
||||
}
|
||||
|
||||
private static String getApolloSecretFileKey() {
|
||||
return String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_CONFIG, REGISTRY_TYPE, APOLLO_SECRET);
|
||||
}
|
||||
|
||||
private static String getApolloAppIdFileKey() {
|
||||
return String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_CONFIG, REGISTRY_TYPE, APP_ID);
|
||||
}
|
||||
|
||||
private static String getApolloNamespaceKey() {
|
||||
return String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_CONFIG, REGISTRY_TYPE, NAMESPACE);
|
||||
}
|
||||
|
||||
private static String getApolloCluster() {
|
||||
return String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_CONFIG, REGISTRY_TYPE, APOLLO_CLUSTER);
|
||||
}
|
||||
|
||||
private static String getApolloConfigService() {
|
||||
return String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_CONFIG, REGISTRY_TYPE, APOLLO_CONFIG_SERVICE);
|
||||
}
|
||||
|
||||
|
||||
private ConfigurationChangeType getChangeType(PropertyChangeType changeType) {
|
||||
switch (changeType) {
|
||||
case ADDED:
|
||||
return ConfigurationChangeType.ADD;
|
||||
case DELETED:
|
||||
return ConfigurationChangeType.DELETE;
|
||||
default:
|
||||
return ConfigurationChangeType.MODIFY;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config.apollo;
|
||||
|
||||
import io.seata.common.loader.LoadLevel;
|
||||
import io.seata.config.Configuration;
|
||||
import io.seata.config.ConfigurationProvider;
|
||||
|
||||
/**
|
||||
* @author xingfudeshi@gmail.com
|
||||
*/
|
||||
@LoadLevel(name = "Apollo", order = 1)
|
||||
public class ApolloConfigurationProvider implements ConfigurationProvider {
|
||||
@Override
|
||||
public Configuration provide() {
|
||||
return ApolloConfiguration.getInstance();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
io.seata.config.apollo.ApolloConfigurationProvider
|
||||
40
config/seata-config-consul/pom.xml
Normal file
40
config/seata-config-consul/pom.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 1999-2019 Seata.io Group.
|
||||
~
|
||||
~ 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
|
||||
~
|
||||
~ http://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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-config</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>seata-config-consul</artifactId>
|
||||
<name>seata-config-consul ${project.version}</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-config-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ecwid.consul</groupId>
|
||||
<artifactId>consul-api</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config.consul;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.ecwid.consul.v1.ConsulClient;
|
||||
import com.ecwid.consul.v1.QueryParams;
|
||||
import com.ecwid.consul.v1.Response;
|
||||
import com.ecwid.consul.v1.kv.model.GetValue;
|
||||
import com.ecwid.consul.v1.kv.model.PutParams;
|
||||
import io.netty.util.internal.ConcurrentSet;
|
||||
import io.seata.common.thread.NamedThreadFactory;
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.common.util.NetUtil;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.config.AbstractConfiguration;
|
||||
import io.seata.config.ConfigFuture;
|
||||
import io.seata.config.Configuration;
|
||||
import io.seata.config.ConfigurationChangeEvent;
|
||||
import io.seata.config.ConfigurationChangeListener;
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
|
||||
import static io.seata.config.ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR;
|
||||
import static io.seata.config.ConfigurationKeys.FILE_ROOT_CONFIG;
|
||||
|
||||
/**
|
||||
* The type Consul configuration.
|
||||
*
|
||||
* @author xingfudeshi @gmail.com
|
||||
*/
|
||||
public class ConsulConfiguration extends AbstractConfiguration {
|
||||
private volatile static ConsulConfiguration instance;
|
||||
private volatile static ConsulClient client;
|
||||
|
||||
private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE;
|
||||
private static final String SERVER_ADDR_KEY = "serverAddr";
|
||||
private static final String CONFIG_TYPE = "consul";
|
||||
private static final String ACL_TOKEN = "aclToken";
|
||||
private static final String FILE_CONFIG_KEY_PREFIX = FILE_ROOT_CONFIG + FILE_CONFIG_SPLIT_CHAR + CONFIG_TYPE
|
||||
+ FILE_CONFIG_SPLIT_CHAR;
|
||||
private static final int THREAD_POOL_NUM = 1;
|
||||
private static final int MAP_INITIAL_CAPACITY = 8;
|
||||
private ExecutorService consulNotifierExecutor;
|
||||
private ConcurrentMap<String, Set<ConfigurationChangeListener>> configListenersMap = new ConcurrentHashMap<>(
|
||||
MAP_INITIAL_CAPACITY);
|
||||
|
||||
/**
|
||||
* default watch timeout in second
|
||||
*/
|
||||
private static final int DEFAULT_WATCH_TIMEOUT = 60;
|
||||
private static final long CAS = 0L;
|
||||
|
||||
private ConsulConfiguration() {
|
||||
consulNotifierExecutor = new ThreadPoolExecutor(THREAD_POOL_NUM, THREAD_POOL_NUM, Integer.MAX_VALUE,
|
||||
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(),
|
||||
new NamedThreadFactory("consul-config-executor", THREAD_POOL_NUM));
|
||||
}
|
||||
|
||||
/**
|
||||
* get instance
|
||||
*
|
||||
* @return instance
|
||||
*/
|
||||
public static ConsulConfiguration getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (ConsulConfiguration.class) {
|
||||
if (instance == null) {
|
||||
instance = new ConsulConfiguration();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) {
|
||||
String value = getConfigFromSysPro(dataId);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
ConfigFuture configFuture = new ConfigFuture(dataId, defaultValue, ConfigFuture.ConfigOperation.GET,
|
||||
timeoutMills);
|
||||
consulNotifierExecutor.execute(() -> complete(getConsulClient().getKVValue(dataId, getAclToken()), configFuture));
|
||||
return (String) configFuture.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putConfig(String dataId, String content, long timeoutMills) {
|
||||
ConfigFuture configFuture = new ConfigFuture(dataId, content, ConfigFuture.ConfigOperation.PUT, timeoutMills);
|
||||
consulNotifierExecutor.execute(() -> complete(getConsulClient().setKVValue(dataId, content, getAclToken(), null), configFuture));
|
||||
return (Boolean) configFuture.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) {
|
||||
ConfigFuture configFuture = new ConfigFuture(dataId, content, ConfigFuture.ConfigOperation.PUTIFABSENT,
|
||||
timeoutMills);
|
||||
consulNotifierExecutor.execute(() -> {
|
||||
PutParams putParams = new PutParams();
|
||||
//Setting CAS to 0 means that this is an atomic operation, created when key does not exist.
|
||||
putParams.setCas(CAS);
|
||||
complete(getConsulClient().setKVValue(dataId, content, getAclToken(), putParams), configFuture);
|
||||
});
|
||||
return (Boolean) configFuture.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeConfig(String dataId, long timeoutMills) {
|
||||
ConfigFuture configFuture = new ConfigFuture(dataId, null, ConfigFuture.ConfigOperation.REMOVE, timeoutMills);
|
||||
consulNotifierExecutor.execute(() -> complete(getConsulClient().deleteKVValue(dataId, getAclToken()), configFuture));
|
||||
return (Boolean) configFuture.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addConfigListener(String dataId, ConfigurationChangeListener listener) {
|
||||
if (StringUtils.isBlank(dataId) || listener == null) {
|
||||
return;
|
||||
}
|
||||
ConsulListener consulListener = new ConsulListener(dataId, listener);
|
||||
configListenersMap.computeIfAbsent(dataId, key -> new ConcurrentSet<>())
|
||||
.add(consulListener);
|
||||
|
||||
// Start config change listener for the dataId.
|
||||
consulListener.onProcessEvent(new ConfigurationChangeEvent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeConfigListener(String dataId, ConfigurationChangeListener listener) {
|
||||
if (StringUtils.isBlank(dataId) || listener == null) {
|
||||
return;
|
||||
}
|
||||
Set<ConfigurationChangeListener> configListeners = getConfigListeners(dataId);
|
||||
if (CollectionUtils.isNotEmpty(configListeners)) {
|
||||
ConfigurationChangeListener target;
|
||||
for (ConfigurationChangeListener entry : configListeners) {
|
||||
target = ((ConsulListener) entry).getTargetListener();
|
||||
if (listener.equals(target)) {
|
||||
entry.onShutDown();
|
||||
configListeners.remove(entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ConfigurationChangeListener> getConfigListeners(String dataId) {
|
||||
return configListenersMap.get(dataId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeName() {
|
||||
return CONFIG_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* get consul client
|
||||
*
|
||||
* @return client
|
||||
*/
|
||||
private static ConsulClient getConsulClient() {
|
||||
if (client == null) {
|
||||
synchronized (ConsulConfiguration.class) {
|
||||
if (client == null) {
|
||||
String serverAddr = FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + SERVER_ADDR_KEY);
|
||||
InetSocketAddress inetSocketAddress = NetUtil.toInetSocketAddress(serverAddr);
|
||||
client = new ConsulClient(inetSocketAddress.getHostName(), inetSocketAddress.getPort());
|
||||
}
|
||||
}
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* get consul acl-token
|
||||
*
|
||||
* @return acl-token
|
||||
*/
|
||||
private static String getAclToken() {
|
||||
String aclToken = StringUtils.isNotBlank(System.getProperty(ACL_TOKEN)) ? System.getProperty(ACL_TOKEN)
|
||||
: FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + ACL_TOKEN);
|
||||
return StringUtils.isNotBlank(aclToken) ? aclToken : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* complete the future
|
||||
*
|
||||
* @param response
|
||||
* @param configFuture
|
||||
*/
|
||||
private void complete(Response response, ConfigFuture configFuture) {
|
||||
if (response != null && response.getValue() != null) {
|
||||
Object value = response.getValue();
|
||||
if (value instanceof GetValue) {
|
||||
configFuture.setResult(((GetValue) value).getDecodedValue());
|
||||
} else {
|
||||
configFuture.setResult(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The type Consul listener.
|
||||
*/
|
||||
public static class ConsulListener implements ConfigurationChangeListener {
|
||||
|
||||
private final ConfigurationChangeListener listener;
|
||||
private final String dataId;
|
||||
private long consulIndex;
|
||||
private final ExecutorService executor = new ThreadPoolExecutor(CORE_LISTENER_THREAD, MAX_LISTENER_THREAD, 0L,
|
||||
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(),
|
||||
new NamedThreadFactory("consulListener", MAX_LISTENER_THREAD));
|
||||
|
||||
/**
|
||||
* Instantiates a new Consul listener.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param listener the listener
|
||||
*/
|
||||
public ConsulListener(String dataId, ConfigurationChangeListener listener) {
|
||||
this.dataId = dataId;
|
||||
this.listener = listener;
|
||||
this.consulIndex = getConsulClient().getKVValue(dataId, getAclToken()).getConsulIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChangeEvent(ConfigurationChangeEvent event) {
|
||||
if (listener != null) {
|
||||
while (true) {
|
||||
QueryParams queryParams = new QueryParams(DEFAULT_WATCH_TIMEOUT, consulIndex);
|
||||
Response<GetValue> response = getConsulClient().getKVValue(this.dataId, getAclToken(), queryParams);
|
||||
Long currentIndex = response.getConsulIndex();
|
||||
if (currentIndex != null && currentIndex > consulIndex) {
|
||||
GetValue getValue = response.getValue();
|
||||
consulIndex = currentIndex;
|
||||
event.setDataId(dataId).setNewValue(getValue.getDecodedValue());
|
||||
listener.onChangeEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutorService getExecutorService() {
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets target listener.
|
||||
*
|
||||
* @return the target listener
|
||||
*/
|
||||
public ConfigurationChangeListener getTargetListener() {
|
||||
return this.listener;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config.consul;
|
||||
|
||||
import io.seata.common.loader.LoadLevel;
|
||||
import io.seata.config.Configuration;
|
||||
import io.seata.config.ConfigurationProvider;
|
||||
|
||||
/**
|
||||
* @author xingfudeshi@gmail.com
|
||||
*/
|
||||
@LoadLevel(name = "Consul", order = 1)
|
||||
public class ConsulConfigurationProvider implements ConfigurationProvider {
|
||||
@Override
|
||||
public Configuration provide() {
|
||||
return ConsulConfiguration.getInstance();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
io.seata.config.consul.ConsulConfigurationProvider
|
||||
48
config/seata-config-core/pom.xml
Normal file
48
config/seata-config-core/pom.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 1999-2019 Seata.io Group.
|
||||
~
|
||||
~ 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
|
||||
~
|
||||
~ http://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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-config</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>seata-config-core</artifactId>
|
||||
<name>seata-config-core ${project.version}</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.typesafe</groupId>
|
||||
<artifactId>config</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cglib</groupId>
|
||||
<artifactId>cglib</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
import java.time.Duration;
|
||||
import io.seata.common.util.DurationUtil;
|
||||
|
||||
/**
|
||||
* The type Abstract configuration.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public abstract class AbstractConfiguration implements Configuration {
|
||||
|
||||
/**
|
||||
* The constant DEFAULT_CONFIG_TIMEOUT.
|
||||
*/
|
||||
protected static final long DEFAULT_CONFIG_TIMEOUT = 5 * 1000;
|
||||
|
||||
@Override
|
||||
public short getShort(String dataId, int defaultValue, long timeoutMills) {
|
||||
String result = getConfig(dataId, String.valueOf(defaultValue), timeoutMills);
|
||||
return Short.parseShort(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShort(String dataId, short defaultValue) {
|
||||
return getShort(dataId, defaultValue, DEFAULT_CONFIG_TIMEOUT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShort(String dataId) {
|
||||
return getShort(dataId, (short) 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(String dataId, int defaultValue, long timeoutMills) {
|
||||
String result = getConfig(dataId, String.valueOf(defaultValue), timeoutMills);
|
||||
return Integer.parseInt(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(String dataId, int defaultValue) {
|
||||
return getInt(dataId, defaultValue, DEFAULT_CONFIG_TIMEOUT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(String dataId) {
|
||||
return getInt(dataId, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(String dataId, long defaultValue, long timeoutMills) {
|
||||
String result = getConfig(dataId, String.valueOf(defaultValue), timeoutMills);
|
||||
return Long.parseLong(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(String dataId, long defaultValue) {
|
||||
return getLong(dataId, defaultValue, DEFAULT_CONFIG_TIMEOUT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(String dataId) {
|
||||
return getLong(dataId, 0L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getDuration(String dataId) {
|
||||
return getDuration(dataId, Duration.ZERO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getDuration(String dataId, Duration defaultValue) {
|
||||
return getDuration(dataId, defaultValue, DEFAULT_CONFIG_TIMEOUT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getDuration(String dataId, Duration defaultValue, long timeoutMills) {
|
||||
String result = getConfig(dataId, defaultValue.toMillis() + "ms", timeoutMills);
|
||||
return DurationUtil.parse(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(String dataId, boolean defaultValue, long timeoutMills) {
|
||||
String result = getConfig(dataId, String.valueOf(defaultValue), timeoutMills);
|
||||
return Boolean.parseBoolean(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(String dataId, boolean defaultValue) {
|
||||
return getBoolean(dataId, defaultValue, DEFAULT_CONFIG_TIMEOUT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(String dataId) {
|
||||
return getBoolean(dataId, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConfig(String dataId, String defaultValue) {
|
||||
return getConfig(dataId, defaultValue, DEFAULT_CONFIG_TIMEOUT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConfig(String dataId, long timeoutMills) {
|
||||
return getConfig(dataId, null, timeoutMills);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConfig(String dataId, String content, long timeoutMills) {
|
||||
return getLatestConfig(dataId, content, timeoutMills);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConfig(String dataId) {
|
||||
return getConfig(dataId, DEFAULT_CONFIG_TIMEOUT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putConfig(String dataId, String content) {
|
||||
return putConfig(dataId, content, DEFAULT_CONFIG_TIMEOUT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putConfigIfAbsent(String dataId, String content) {
|
||||
return putConfigIfAbsent(dataId, content, DEFAULT_CONFIG_TIMEOUT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeConfig(String dataId) {
|
||||
return removeConfig(dataId, DEFAULT_CONFIG_TIMEOUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets type name.
|
||||
*
|
||||
* @return the type name
|
||||
*/
|
||||
public abstract String getTypeName();
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
/**
|
||||
* The interface Config change listener.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public interface ConfigChangeListener {
|
||||
|
||||
/**
|
||||
* Gets executor.
|
||||
*
|
||||
* @return the executor
|
||||
*/
|
||||
ExecutorService getExecutor();
|
||||
|
||||
/**
|
||||
* Receive config info.
|
||||
*
|
||||
* @param configInfo the config info
|
||||
*/
|
||||
void receiveConfigInfo(final String configInfo);
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
|
||||
import io.seata.common.exception.ShouldNeverHappenException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* The type Config future.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public class ConfigFuture {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigFuture.class);
|
||||
private static final long DEFAULT_CONFIG_TIMEOUT = 5 * 1000;
|
||||
private long timeoutMills;
|
||||
private long start = System.currentTimeMillis();
|
||||
private String dataId;
|
||||
private String content;
|
||||
private ConfigOperation operation;
|
||||
private transient CompletableFuture<Object> origin = new CompletableFuture<>();
|
||||
|
||||
/**
|
||||
* Instantiates a new Config future.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param content the content
|
||||
* @param operation the operation
|
||||
*/
|
||||
public ConfigFuture(String dataId, String content, ConfigOperation operation) {
|
||||
this(dataId, content, operation, DEFAULT_CONFIG_TIMEOUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Config future.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param content the content
|
||||
* @param operation the operation
|
||||
* @param timeoutMills the timeout mills
|
||||
*/
|
||||
public ConfigFuture(String dataId, String content, ConfigOperation operation, long timeoutMills) {
|
||||
this.dataId = dataId;
|
||||
this.content = content;
|
||||
this.operation = operation;
|
||||
this.timeoutMills = timeoutMills;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets timeout mills.
|
||||
*
|
||||
* @return the timeout mills
|
||||
*/
|
||||
public boolean isTimeout() {
|
||||
return System.currentTimeMillis() - start > timeoutMills;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object.
|
||||
*
|
||||
* @return the object
|
||||
*/
|
||||
public Object get() {
|
||||
return get(this.timeoutMills, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object.
|
||||
*
|
||||
* @param timeout the timeout
|
||||
* @param unit the unit
|
||||
* @return the object
|
||||
*/
|
||||
public Object get(long timeout, TimeUnit unit) {
|
||||
this.timeoutMills = unit.toMillis(timeout);
|
||||
Object result;
|
||||
try {
|
||||
result = origin.get(timeout, unit);
|
||||
} catch (ExecutionException e) {
|
||||
throw new ShouldNeverHappenException("Should not get results in a multi-threaded environment", e);
|
||||
} catch (TimeoutException e) {
|
||||
LOGGER.error("config operation timeout,cost:{} ms,op:{},dataId:{}", System.currentTimeMillis() - start, operation.name(), dataId);
|
||||
return getFailResult();
|
||||
} catch (InterruptedException exx) {
|
||||
LOGGER.error("config operate interrupted,error:{}", exx.getMessage(), exx);
|
||||
return getFailResult();
|
||||
}
|
||||
if (operation == ConfigOperation.GET) {
|
||||
return result == null ? content : result;
|
||||
} else {
|
||||
return result == null ? Boolean.FALSE : result;
|
||||
}
|
||||
}
|
||||
|
||||
private Object getFailResult() {
|
||||
if (operation == ConfigOperation.GET) {
|
||||
return content;
|
||||
} else {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets result.
|
||||
*
|
||||
* @param result the result
|
||||
*/
|
||||
public void setResult(Object result) {
|
||||
origin.complete(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets data id.
|
||||
*
|
||||
* @return the data id
|
||||
*/
|
||||
public String getDataId() {
|
||||
return dataId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets data id.
|
||||
*
|
||||
* @param dataId the data id
|
||||
*/
|
||||
public void setDataId(String dataId) {
|
||||
this.dataId = dataId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets content.
|
||||
*
|
||||
* @return the content
|
||||
*/
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets content.
|
||||
*
|
||||
* @param content the content
|
||||
*/
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets operation.
|
||||
*
|
||||
* @return the operation
|
||||
*/
|
||||
public ConfigOperation getOperation() {
|
||||
return operation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets operation.
|
||||
*
|
||||
* @param operation the operation
|
||||
*/
|
||||
public void setOperation(ConfigOperation operation) {
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
/**
|
||||
* The enum Config operation.
|
||||
*/
|
||||
public enum ConfigOperation {
|
||||
/**
|
||||
* Get config operation.
|
||||
*/
|
||||
GET,
|
||||
/**
|
||||
* Put config operation.
|
||||
*/
|
||||
PUT,
|
||||
/**
|
||||
* Putifabsent config operation.
|
||||
*/
|
||||
PUTIFABSENT,
|
||||
/**
|
||||
* Remove config operation.
|
||||
*/
|
||||
REMOVE
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
/**
|
||||
* The enum Config type.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public enum ConfigType {
|
||||
/**
|
||||
* File config type.
|
||||
*/
|
||||
File,
|
||||
/**
|
||||
* zookeeper config type.
|
||||
*/
|
||||
ZK,
|
||||
/**
|
||||
* Nacos config type.
|
||||
*/
|
||||
Nacos,
|
||||
/**
|
||||
* Apollo config type.
|
||||
*/
|
||||
Apollo,
|
||||
/**
|
||||
* Consul config type
|
||||
*/
|
||||
Consul,
|
||||
/**
|
||||
* Etcd3 config type
|
||||
*/
|
||||
Etcd3,
|
||||
/**
|
||||
* spring cloud config type
|
||||
*/
|
||||
SpringCloudConfig,
|
||||
/**
|
||||
* Custom config type
|
||||
*/
|
||||
Custom;
|
||||
|
||||
/**
|
||||
* Gets type.
|
||||
*
|
||||
* @param name the name
|
||||
* @return the type
|
||||
*/
|
||||
public static ConfigType getType(String name) {
|
||||
for (ConfigType configType : values()) {
|
||||
if (configType.name().equalsIgnoreCase(name)) {
|
||||
return configType;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("not support config type: " + name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The interface Configuration.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public interface Configuration {
|
||||
|
||||
/**
|
||||
* Gets short.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param defaultValue the default value
|
||||
* @param timeoutMills the timeout mills
|
||||
* @return the short
|
||||
*/
|
||||
short getShort(String dataId, int defaultValue, long timeoutMills);
|
||||
|
||||
/**
|
||||
* Gets short.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param defaultValue the default value
|
||||
* @return the int
|
||||
*/
|
||||
short getShort(String dataId, short defaultValue);
|
||||
|
||||
/**
|
||||
* Gets short.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @return the int
|
||||
*/
|
||||
short getShort(String dataId);
|
||||
|
||||
/**
|
||||
* Gets int.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param defaultValue the default value
|
||||
* @param timeoutMills the timeout mills
|
||||
* @return the int
|
||||
*/
|
||||
int getInt(String dataId, int defaultValue, long timeoutMills);
|
||||
|
||||
/**
|
||||
* Gets int.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param defaultValue the default value
|
||||
* @return the int
|
||||
*/
|
||||
int getInt(String dataId, int defaultValue);
|
||||
|
||||
/**
|
||||
* Gets int.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @return the int
|
||||
*/
|
||||
int getInt(String dataId);
|
||||
|
||||
/**
|
||||
* Gets long.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param defaultValue the default value
|
||||
* @param timeoutMills the timeout mills
|
||||
* @return the long
|
||||
*/
|
||||
long getLong(String dataId, long defaultValue, long timeoutMills);
|
||||
|
||||
/**
|
||||
* Gets long.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param defaultValue the default value
|
||||
* @return the long
|
||||
*/
|
||||
long getLong(String dataId, long defaultValue);
|
||||
|
||||
/**
|
||||
* Gets long.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @return the long
|
||||
*/
|
||||
long getLong(String dataId);
|
||||
|
||||
/**
|
||||
* Gets duration.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @return the duration
|
||||
*/
|
||||
Duration getDuration(String dataId);
|
||||
|
||||
/**
|
||||
* Gets duration.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param defaultValue the default value
|
||||
* @return the duration
|
||||
*/
|
||||
Duration getDuration(String dataId, Duration defaultValue);
|
||||
|
||||
/**
|
||||
* Gets duration.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param defaultValue the default value
|
||||
* @param timeoutMills the timeout mills
|
||||
* @return he duration
|
||||
*/
|
||||
Duration getDuration(String dataId, Duration defaultValue, long timeoutMills);
|
||||
|
||||
/**
|
||||
* Gets boolean.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param defaultValue the default value
|
||||
* @param timeoutMills the timeout mills
|
||||
* @return the boolean
|
||||
*/
|
||||
boolean getBoolean(String dataId, boolean defaultValue, long timeoutMills);
|
||||
|
||||
/**
|
||||
* Gets boolean.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param defaultValue the default value
|
||||
* @return the boolean
|
||||
*/
|
||||
boolean getBoolean(String dataId, boolean defaultValue);
|
||||
|
||||
/**
|
||||
* Gets boolean.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @return the boolean
|
||||
*/
|
||||
boolean getBoolean(String dataId);
|
||||
|
||||
/**
|
||||
* Gets config.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param defaultValue the default value
|
||||
* @param timeoutMills the timeout mills
|
||||
* @return the config
|
||||
*/
|
||||
String getConfig(String dataId, String defaultValue, long timeoutMills);
|
||||
|
||||
/**
|
||||
* Gets config.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param defaultValue the default value
|
||||
* @return the config
|
||||
*/
|
||||
String getConfig(String dataId, String defaultValue);
|
||||
|
||||
/**
|
||||
* Gets config.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param timeoutMills the timeout mills
|
||||
* @return the config
|
||||
*/
|
||||
String getConfig(String dataId, long timeoutMills);
|
||||
|
||||
/**
|
||||
* Gets config.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @return the config
|
||||
*/
|
||||
String getConfig(String dataId);
|
||||
|
||||
/**
|
||||
* Put config boolean.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param content the content
|
||||
* @param timeoutMills the timeout mills
|
||||
* @return the boolean
|
||||
*/
|
||||
boolean putConfig(String dataId, String content, long timeoutMills);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param defaultValue the default value
|
||||
* @param timeoutMills the timeout mills
|
||||
* @return the Latest config
|
||||
*/
|
||||
String getLatestConfig(String dataId, String defaultValue, long timeoutMills);
|
||||
|
||||
/**
|
||||
* Put config boolean.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param content the content
|
||||
* @return the boolean
|
||||
*/
|
||||
boolean putConfig(String dataId, String content);
|
||||
|
||||
/**
|
||||
* Put config if absent boolean.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param content the content
|
||||
* @param timeoutMills the timeout mills
|
||||
* @return the boolean
|
||||
*/
|
||||
boolean putConfigIfAbsent(String dataId, String content, long timeoutMills);
|
||||
|
||||
/**
|
||||
* Put config if absent boolean.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param content the content
|
||||
* @return the boolean
|
||||
*/
|
||||
boolean putConfigIfAbsent(String dataId, String content);
|
||||
|
||||
/**
|
||||
* Remove config boolean.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param timeoutMills the timeout mills
|
||||
* @return the boolean
|
||||
*/
|
||||
boolean removeConfig(String dataId, long timeoutMills);
|
||||
|
||||
/**
|
||||
* Remove config boolean.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @return the boolean
|
||||
*/
|
||||
boolean removeConfig(String dataId);
|
||||
|
||||
/**
|
||||
* Add config listener.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param listener the listener
|
||||
*/
|
||||
void addConfigListener(String dataId, ConfigurationChangeListener listener);
|
||||
|
||||
/**
|
||||
* Remove config listener.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param listener the listener
|
||||
*/
|
||||
void removeConfigListener(String dataId, ConfigurationChangeListener listener);
|
||||
|
||||
/**
|
||||
* Gets config listeners.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @return the config listeners
|
||||
*/
|
||||
Set<ConfigurationChangeListener> getConfigListeners(String dataId);
|
||||
|
||||
/**
|
||||
* Gets config from sys pro.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @return the config from sys pro
|
||||
*/
|
||||
default String getConfigFromSysPro(String dataId) {
|
||||
return System.getProperty(dataId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import io.seata.common.util.DurationUtil;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
|
||||
/**
|
||||
* @author funkye
|
||||
*/
|
||||
public class ConfigurationCache implements ConfigurationChangeListener {
|
||||
|
||||
private static final String METHOD_PREFIX = "get";
|
||||
|
||||
private static final String METHOD_LATEST_CONFIG = METHOD_PREFIX + "LatestConfig";
|
||||
|
||||
private static final ConcurrentHashMap<String, ObjectWrapper> CONFIG_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
private Map<String, HashSet<ConfigurationChangeListener>> configListenersMap = new HashMap<>();
|
||||
|
||||
public static void addConfigListener(String dataId, ConfigurationChangeListener... listeners) {
|
||||
if (StringUtils.isBlank(dataId)) {
|
||||
return;
|
||||
}
|
||||
synchronized (ConfigurationCache.class) {
|
||||
HashSet<ConfigurationChangeListener> listenerHashSet =
|
||||
getInstance().configListenersMap.computeIfAbsent(dataId, key -> new HashSet<>());
|
||||
if (!listenerHashSet.contains(getInstance())) {
|
||||
ConfigurationFactory.getInstance().addConfigListener(dataId, getInstance());
|
||||
listenerHashSet.add(getInstance());
|
||||
}
|
||||
if (null != listeners && listeners.length > 0) {
|
||||
for (ConfigurationChangeListener listener : listeners) {
|
||||
if (!listenerHashSet.contains(listener)) {
|
||||
listenerHashSet.add(listener);
|
||||
ConfigurationFactory.getInstance().addConfigListener(dataId, listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ConfigurationCache getInstance() {
|
||||
return ConfigurationCacheInstance.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChangeEvent(ConfigurationChangeEvent event) {
|
||||
ObjectWrapper wrapper = CONFIG_CACHE.get(event.getDataId());
|
||||
// The wrapper.data only exists in the cache when it is not null.
|
||||
if (StringUtils.isNotBlank(event.getNewValue())) {
|
||||
if (wrapper == null) {
|
||||
CONFIG_CACHE.put(event.getDataId(), new ObjectWrapper(event.getNewValue(), null));
|
||||
} else {
|
||||
Object newValue = new ObjectWrapper(event.getNewValue(), null).convertData(wrapper.getType());
|
||||
if (!Objects.equals(wrapper.getData(), newValue)) {
|
||||
CONFIG_CACHE.put(event.getDataId(), new ObjectWrapper(newValue, wrapper.getType()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
CONFIG_CACHE.remove(event.getDataId());
|
||||
}
|
||||
}
|
||||
|
||||
public Configuration proxy(Configuration originalConfiguration) {
|
||||
return (Configuration)Enhancer.create(Configuration.class,
|
||||
(MethodInterceptor)(proxy, method, args, methodProxy) -> {
|
||||
if (method.getName().startsWith(METHOD_PREFIX)
|
||||
&& !method.getName().equalsIgnoreCase(METHOD_LATEST_CONFIG)) {
|
||||
String rawDataId = (String)args[0];
|
||||
ObjectWrapper wrapper = CONFIG_CACHE.get(rawDataId);
|
||||
String type = method.getName().substring(METHOD_PREFIX.length());
|
||||
if (!ObjectWrapper.supportType(type)) {
|
||||
type = null;
|
||||
}
|
||||
if (null == wrapper) {
|
||||
Object result = method.invoke(originalConfiguration, args);
|
||||
// The wrapper.data only exists in the cache when it is not null.
|
||||
if (result != null) {
|
||||
wrapper = new ObjectWrapper(result, type);
|
||||
CONFIG_CACHE.put(rawDataId, wrapper);
|
||||
}
|
||||
}
|
||||
return wrapper == null ? null : wrapper.convertData(type);
|
||||
}
|
||||
return method.invoke(originalConfiguration, args);
|
||||
});
|
||||
}
|
||||
|
||||
private static class ConfigurationCacheInstance {
|
||||
private static final ConfigurationCache INSTANCE = new ConfigurationCache();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
CONFIG_CACHE.clear();
|
||||
}
|
||||
|
||||
private static class ObjectWrapper {
|
||||
|
||||
static final String INT = "Int";
|
||||
static final String BOOLEAN = "Boolean";
|
||||
static final String DURATION = "Duration";
|
||||
static final String LONG = "Long";
|
||||
static final String SHORT = "Short";
|
||||
|
||||
private final Object data;
|
||||
private final String type;
|
||||
|
||||
ObjectWrapper(Object data, String type) {
|
||||
this.data = data;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Object getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Object convertData(String aType) {
|
||||
if (data != null && Objects.equals(type, aType)) {
|
||||
return data;
|
||||
}
|
||||
if (data != null) {
|
||||
if (INT.equals(aType)) {
|
||||
return Integer.parseInt(data.toString());
|
||||
} else if (BOOLEAN.equals(aType)) {
|
||||
return Boolean.parseBoolean(data.toString());
|
||||
} else if (DURATION.equals(aType)) {
|
||||
return DurationUtil.parse(data.toString());
|
||||
} else if (LONG.equals(aType)) {
|
||||
return Long.parseLong(data.toString());
|
||||
} else if (SHORT.equals(aType)) {
|
||||
return Short.parseShort(data.toString());
|
||||
}
|
||||
return String.valueOf(data);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean supportType(String type) {
|
||||
return INT.equalsIgnoreCase(type)
|
||||
|| BOOLEAN.equalsIgnoreCase(type)
|
||||
|| DURATION.equalsIgnoreCase(type)
|
||||
|| LONG.equalsIgnoreCase(type)
|
||||
|| SHORT.equalsIgnoreCase(type);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
/**
|
||||
* The type Configuration change event.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public class ConfigurationChangeEvent {
|
||||
|
||||
private String dataId;
|
||||
private String oldValue;
|
||||
private String newValue;
|
||||
private String namespace;
|
||||
private ConfigurationChangeType changeType;
|
||||
private static final String DEFAULT_NAMESPACE = "DEFAULT";
|
||||
|
||||
|
||||
public ConfigurationChangeEvent(){
|
||||
|
||||
}
|
||||
|
||||
public ConfigurationChangeEvent(String dataId, String newValue) {
|
||||
this(dataId, DEFAULT_NAMESPACE, null, newValue, ConfigurationChangeType.MODIFY);
|
||||
}
|
||||
|
||||
public ConfigurationChangeEvent(String dataId, String namespace, String oldValue, String newValue,
|
||||
ConfigurationChangeType type) {
|
||||
this.dataId = dataId;
|
||||
this.namespace = namespace;
|
||||
this.oldValue = oldValue;
|
||||
this.newValue = newValue;
|
||||
this.changeType = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets data id.
|
||||
*
|
||||
* @return the data id
|
||||
*/
|
||||
public String getDataId() {
|
||||
return dataId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets data id.
|
||||
*
|
||||
* @param dataId the data id
|
||||
*/
|
||||
public ConfigurationChangeEvent setDataId(String dataId) {
|
||||
this.dataId = dataId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets old value.
|
||||
*
|
||||
* @return the old value
|
||||
*/
|
||||
public String getOldValue() {
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets old value.
|
||||
*
|
||||
* @param oldValue the old value
|
||||
*/
|
||||
public ConfigurationChangeEvent setOldValue(String oldValue) {
|
||||
this.oldValue = oldValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets new value.
|
||||
*
|
||||
* @return the new value
|
||||
*/
|
||||
public String getNewValue() {
|
||||
return newValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets new value.
|
||||
*
|
||||
* @param newValue the new value
|
||||
*/
|
||||
public ConfigurationChangeEvent setNewValue(String newValue) {
|
||||
this.newValue = newValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets change type.
|
||||
*
|
||||
* @return the change type
|
||||
*/
|
||||
public ConfigurationChangeType getChangeType() {
|
||||
return changeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets change type.
|
||||
*
|
||||
* @param changeType the change type
|
||||
*/
|
||||
public ConfigurationChangeEvent setChangeType(ConfigurationChangeType changeType) {
|
||||
this.changeType = changeType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets namespace.
|
||||
*
|
||||
* @return the namespace
|
||||
*/
|
||||
public String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets namespace.
|
||||
*
|
||||
* @param namespace the namespace
|
||||
*/
|
||||
public ConfigurationChangeEvent setNamespace(String namespace) {
|
||||
this.namespace = namespace;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.seata.common.thread.NamedThreadFactory;
|
||||
|
||||
/**
|
||||
* The interface Configuration change listener.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public interface ConfigurationChangeListener {
|
||||
|
||||
/**
|
||||
* The constant CORE_LISTENER_THREAD.
|
||||
*/
|
||||
int CORE_LISTENER_THREAD = 1;
|
||||
/**
|
||||
* The constant MAX_LISTENER_THREAD.
|
||||
*/
|
||||
int MAX_LISTENER_THREAD = 1;
|
||||
/**
|
||||
* The constant EXECUTOR_SERVICE.
|
||||
*/
|
||||
ExecutorService EXECUTOR_SERVICE = new ThreadPoolExecutor(CORE_LISTENER_THREAD, MAX_LISTENER_THREAD,
|
||||
Integer.MAX_VALUE, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(),
|
||||
new NamedThreadFactory("configListenerOperate", MAX_LISTENER_THREAD));
|
||||
|
||||
/**
|
||||
* Process.
|
||||
*
|
||||
* @param event the event
|
||||
*/
|
||||
void onChangeEvent(ConfigurationChangeEvent event);
|
||||
|
||||
/**
|
||||
* On process event.
|
||||
*
|
||||
* @param event the event
|
||||
*/
|
||||
default void onProcessEvent(ConfigurationChangeEvent event) {
|
||||
getExecutorService().submit(() -> {
|
||||
beforeEvent();
|
||||
onChangeEvent(event);
|
||||
afterEvent();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* On shut down.
|
||||
*/
|
||||
default void onShutDown() {
|
||||
getExecutorService().shutdownNow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets executor service.
|
||||
*
|
||||
* @return the executor service
|
||||
*/
|
||||
default ExecutorService getExecutorService() {
|
||||
return EXECUTOR_SERVICE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Before event.
|
||||
*/
|
||||
default void beforeEvent() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* After event.
|
||||
*/
|
||||
default void afterEvent() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
/**
|
||||
* The enum Configuration change type.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public enum ConfigurationChangeType {
|
||||
/**
|
||||
* Add configuration change type.
|
||||
*/
|
||||
ADD,
|
||||
/**
|
||||
* Modify configuration change type.
|
||||
*/
|
||||
MODIFY,
|
||||
/**
|
||||
* Delete configuration change type.
|
||||
*/
|
||||
DELETE
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import io.seata.common.exception.NotSupportYetException;
|
||||
import io.seata.common.loader.EnhancedServiceLoader;
|
||||
import io.seata.common.loader.EnhancedServiceNotFoundException;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The type Configuration factory.
|
||||
*
|
||||
* @author slievrly
|
||||
* @author Geng Zhang
|
||||
*/
|
||||
public final class ConfigurationFactory {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationFactory.class);
|
||||
|
||||
private static final String REGISTRY_CONF_DEFAULT = "registry";
|
||||
private static final String ENV_SYSTEM_KEY = "SEATA_ENV";
|
||||
public static final String ENV_PROPERTY_KEY = "seataEnv";
|
||||
|
||||
private static final String SYSTEM_PROPERTY_SEATA_CONFIG_NAME = "seata.config.name";
|
||||
|
||||
private static final String ENV_SEATA_CONFIG_NAME = "SEATA_CONFIG_NAME";
|
||||
|
||||
public static Configuration CURRENT_FILE_INSTANCE;
|
||||
|
||||
static {
|
||||
load();
|
||||
}
|
||||
|
||||
private static void load() {
|
||||
String seataConfigName = System.getProperty(SYSTEM_PROPERTY_SEATA_CONFIG_NAME);
|
||||
if (seataConfigName == null) {
|
||||
seataConfigName = System.getenv(ENV_SEATA_CONFIG_NAME);
|
||||
}
|
||||
if (seataConfigName == null) {
|
||||
seataConfigName = REGISTRY_CONF_DEFAULT;
|
||||
}
|
||||
String envValue = System.getProperty(ENV_PROPERTY_KEY);
|
||||
if (envValue == null) {
|
||||
envValue = System.getenv(ENV_SYSTEM_KEY);
|
||||
}
|
||||
Configuration configuration = (envValue == null) ? new FileConfiguration(seataConfigName,
|
||||
false) : new FileConfiguration(seataConfigName + "-" + envValue, false);
|
||||
Configuration extConfiguration = null;
|
||||
try {
|
||||
extConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration);
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("load Configuration:{}", extConfiguration == null ? configuration.getClass().getSimpleName()
|
||||
: extConfiguration.getClass().getSimpleName());
|
||||
}
|
||||
} catch (EnhancedServiceNotFoundException ignore) {
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("failed to load extConfiguration:{}", e.getMessage(), e);
|
||||
}
|
||||
CURRENT_FILE_INSTANCE = extConfiguration == null ? configuration : extConfiguration;
|
||||
}
|
||||
|
||||
private static final String NAME_KEY = "name";
|
||||
private static final String FILE_TYPE = "file";
|
||||
|
||||
private static volatile Configuration instance = null;
|
||||
|
||||
/**
|
||||
* Gets instance.
|
||||
*
|
||||
* @return the instance
|
||||
*/
|
||||
public static Configuration getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (Configuration.class) {
|
||||
if (instance == null) {
|
||||
instance = buildConfiguration();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static Configuration buildConfiguration() {
|
||||
String configTypeName = CURRENT_FILE_INSTANCE.getConfig(
|
||||
ConfigurationKeys.FILE_ROOT_CONFIG + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR
|
||||
+ ConfigurationKeys.FILE_ROOT_TYPE);
|
||||
|
||||
if (StringUtils.isBlank(configTypeName)) {
|
||||
throw new NotSupportYetException("config type can not be null");
|
||||
}
|
||||
ConfigType configType = ConfigType.getType(configTypeName);
|
||||
|
||||
Configuration extConfiguration = null;
|
||||
Configuration configuration;
|
||||
if (ConfigType.File == configType) {
|
||||
String pathDataId = String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR,
|
||||
ConfigurationKeys.FILE_ROOT_CONFIG, FILE_TYPE, NAME_KEY);
|
||||
String name = CURRENT_FILE_INSTANCE.getConfig(pathDataId);
|
||||
configuration = new FileConfiguration(name);
|
||||
try {
|
||||
extConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration);
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("load Configuration:{}", extConfiguration == null
|
||||
? configuration.getClass().getSimpleName() : extConfiguration.getClass().getSimpleName());
|
||||
}
|
||||
} catch (EnhancedServiceNotFoundException ignore) {
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("failed to load extConfiguration:{}", e.getMessage(), e);
|
||||
}
|
||||
} else {
|
||||
configuration = EnhancedServiceLoader
|
||||
.load(ConfigurationProvider.class, Objects.requireNonNull(configType).name()).provide();
|
||||
}
|
||||
try {
|
||||
Configuration configurationCache;
|
||||
if (null != extConfiguration) {
|
||||
configurationCache = ConfigurationCache.getInstance().proxy(extConfiguration);
|
||||
} else {
|
||||
configurationCache = ConfigurationCache.getInstance().proxy(configuration);
|
||||
}
|
||||
if (null != configurationCache) {
|
||||
extConfiguration = configurationCache;
|
||||
}
|
||||
} catch (EnhancedServiceNotFoundException ignore) {
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("failed to load configurationCacheProvider:{}", e.getMessage(), e);
|
||||
}
|
||||
return null == extConfiguration ? configuration : extConfiguration;
|
||||
}
|
||||
|
||||
protected static void reload() {
|
||||
ConfigurationCache.getInstance().clear();
|
||||
load();
|
||||
instance = null;
|
||||
getInstance();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
/**
|
||||
* The type Configuration keys.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public interface ConfigurationKeys {
|
||||
/**
|
||||
* The constant FILE_ROOT_REGISTRY.
|
||||
*/
|
||||
String FILE_ROOT_REGISTRY = "registry";
|
||||
/**
|
||||
* The constant FILE_ROOT_CONFIG.
|
||||
*/
|
||||
String FILE_ROOT_CONFIG = "config";
|
||||
/**
|
||||
* The constant SEATA_FILE_ROOT_CONFIG
|
||||
*/
|
||||
String SEATA_FILE_ROOT_CONFIG = "seata";
|
||||
/**
|
||||
* The constant FILE_CONFIG_SPLIT_CHAR.
|
||||
*/
|
||||
String FILE_CONFIG_SPLIT_CHAR = ".";
|
||||
/**
|
||||
* The constant FILE_ROOT_TYPE.
|
||||
*/
|
||||
String FILE_ROOT_TYPE = "type";
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
/**
|
||||
* the interface configuration provider
|
||||
* @author xingfudeshi@gmail.com
|
||||
*/
|
||||
public interface ConfigurationProvider {
|
||||
/**
|
||||
* provide a AbstractConfiguration implementation instance
|
||||
* @return Configuration
|
||||
*/
|
||||
Configuration provide();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
/**
|
||||
* the interface ext configuration provider
|
||||
* @author xingfudeshi@gmail.com
|
||||
*/
|
||||
public interface ExtConfigurationProvider {
|
||||
/**
|
||||
* provide a AbstractConfiguration implementation instance
|
||||
* @param originalConfiguration
|
||||
* @return configuration
|
||||
*/
|
||||
Configuration provide(Configuration originalConfiguration);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
import io.seata.common.loader.EnhancedServiceLoader;
|
||||
import io.seata.config.file.FileConfig;
|
||||
import java.io.File;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author wangwei-ying
|
||||
*/
|
||||
public class FileConfigFactory {
|
||||
|
||||
public static final String DEFAULT_TYPE = "CONF";
|
||||
|
||||
public static final String YAML_TYPE = "YAML";
|
||||
|
||||
private static final LinkedHashMap<String, String> SUFFIX_MAP = new LinkedHashMap<String, String>(4) {
|
||||
{
|
||||
put("conf", DEFAULT_TYPE);
|
||||
put("properties", DEFAULT_TYPE);
|
||||
put("yml", YAML_TYPE);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public static FileConfig load() {
|
||||
return loadService(DEFAULT_TYPE, null, null);
|
||||
}
|
||||
|
||||
public static FileConfig load(File targetFile, String name) {
|
||||
String fileName = targetFile.getName();
|
||||
String configType = getConfigType(fileName);
|
||||
return loadService(configType, new Class[]{File.class, String.class}, new Object[]{targetFile, name});
|
||||
}
|
||||
|
||||
private static String getConfigType(String fileName) {
|
||||
String configType = DEFAULT_TYPE;
|
||||
int suffixIndex = fileName.lastIndexOf(".");
|
||||
if (suffixIndex > 0) {
|
||||
configType = SUFFIX_MAP.getOrDefault(fileName.substring(suffixIndex + 1), DEFAULT_TYPE);
|
||||
}
|
||||
|
||||
return configType;
|
||||
}
|
||||
|
||||
private static FileConfig loadService(String name, Class[] argsType, Object[] args) {
|
||||
FileConfig fileConfig = EnhancedServiceLoader.load(FileConfig.class, name, argsType, args);
|
||||
return fileConfig;
|
||||
}
|
||||
|
||||
public static Set<String> getSuffixSet() {
|
||||
return SUFFIX_MAP.keySet();
|
||||
}
|
||||
|
||||
public synchronized static void register(String suffix, String beanActiveName) {
|
||||
SUFFIX_MAP.put(suffix, beanActiveName);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.netty.util.internal.ConcurrentSet;
|
||||
import io.seata.common.thread.NamedThreadFactory;
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.config.ConfigFuture.ConfigOperation;
|
||||
import io.seata.config.file.FileConfig;
|
||||
import org.apache.commons.lang.ObjectUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The type FileConfiguration.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public class FileConfiguration extends AbstractConfiguration {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(FileConfiguration.class);
|
||||
|
||||
private FileConfig fileConfig;
|
||||
|
||||
private ExecutorService configOperateExecutor;
|
||||
|
||||
private static final int CORE_CONFIG_OPERATE_THREAD = 1;
|
||||
|
||||
private static final int MAX_CONFIG_OPERATE_THREAD = 2;
|
||||
|
||||
private static final long LISTENER_CONFIG_INTERVAL = 1 * 1000;
|
||||
|
||||
private static final String REGISTRY_TYPE = "file";
|
||||
|
||||
public static final String SYS_FILE_RESOURCE_PREFIX = "file:";
|
||||
|
||||
private final ConcurrentMap<String, Set<ConfigurationChangeListener>> configListenersMap = new ConcurrentHashMap<>(
|
||||
8);
|
||||
|
||||
private final Map<String, String> listenedConfigMap = new HashMap<>(8);
|
||||
|
||||
private final String targetFilePath;
|
||||
|
||||
private volatile long targetFileLastModified;
|
||||
|
||||
private final String name;
|
||||
|
||||
private final FileListener fileListener = new FileListener();
|
||||
|
||||
private final boolean allowDynamicRefresh;
|
||||
|
||||
/**
|
||||
* Note that:this constructor is only used to create proxy with CGLIB
|
||||
* see io.seata.spring.boot.autoconfigure.provider.SpringBootConfigurationProvider#provide
|
||||
*/
|
||||
public FileConfiguration() {
|
||||
this.name = null;
|
||||
this.targetFilePath = null;
|
||||
this.allowDynamicRefresh = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new File configuration.
|
||||
*
|
||||
* @param name the name
|
||||
*/
|
||||
public FileConfiguration(String name) {
|
||||
this(name, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new File configuration.
|
||||
*
|
||||
* @param name the name
|
||||
* @param allowDynamicRefresh the allow dynamic refresh
|
||||
*/
|
||||
public FileConfiguration(String name, boolean allowDynamicRefresh) {
|
||||
LOGGER.info("The file name of the operation is {}", name);
|
||||
File file = getConfigFile(name);
|
||||
if (file == null) {
|
||||
targetFilePath = null;
|
||||
} else {
|
||||
targetFilePath = file.getPath();
|
||||
fileConfig = FileConfigFactory.load(file, name);
|
||||
}
|
||||
/*
|
||||
* For seata-server side the conf file should always exists.
|
||||
* For application(or client) side,conf file may not exists when using seata-spring-boot-starter
|
||||
*/
|
||||
if (targetFilePath == null) {
|
||||
fileConfig = FileConfigFactory.load();
|
||||
this.allowDynamicRefresh = false;
|
||||
} else {
|
||||
targetFileLastModified = new File(targetFilePath).lastModified();
|
||||
this.allowDynamicRefresh = allowDynamicRefresh;
|
||||
}
|
||||
|
||||
this.name = name;
|
||||
configOperateExecutor = new ThreadPoolExecutor(CORE_CONFIG_OPERATE_THREAD, MAX_CONFIG_OPERATE_THREAD,
|
||||
Integer.MAX_VALUE, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(),
|
||||
new NamedThreadFactory("configOperate", MAX_CONFIG_OPERATE_THREAD));
|
||||
}
|
||||
|
||||
private File getConfigFile(String name) {
|
||||
try {
|
||||
if (name == null) {
|
||||
throw new IllegalArgumentException("name can't be null");
|
||||
}
|
||||
|
||||
boolean filePathCustom = name.startsWith(SYS_FILE_RESOURCE_PREFIX);
|
||||
String filePath = filePathCustom ? name.substring(SYS_FILE_RESOURCE_PREFIX.length()) : name;
|
||||
String decodedPath = URLDecoder.decode(filePath, StandardCharsets.UTF_8.name());
|
||||
|
||||
File targetFile = getFileFromFileSystem(decodedPath);
|
||||
if (targetFile != null) {
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("The configuration file used is {}", targetFile.getPath());
|
||||
}
|
||||
return targetFile;
|
||||
}
|
||||
|
||||
if (!filePathCustom) {
|
||||
File classpathFile = getFileFromClasspath(name);
|
||||
if (classpathFile != null) {
|
||||
return classpathFile;
|
||||
}
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
LOGGER.error("decode name error: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private File getFileFromFileSystem(String decodedPath) {
|
||||
|
||||
// run with jar file and not package third lib into jar file, this.getClass().getClassLoader() will be null
|
||||
URL resourceUrl = this.getClass().getClassLoader().getResource("");
|
||||
String[] tryPaths = null;
|
||||
if (resourceUrl != null) {
|
||||
tryPaths = new String[]{
|
||||
// first: project dir
|
||||
resourceUrl.getPath() + decodedPath,
|
||||
// second: system path
|
||||
decodedPath
|
||||
};
|
||||
} else {
|
||||
tryPaths = new String[]{
|
||||
decodedPath
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
for (String tryPath : tryPaths) {
|
||||
File targetFile = new File(tryPath);
|
||||
if (targetFile.exists()) {
|
||||
return targetFile;
|
||||
}
|
||||
|
||||
// try to append config suffix
|
||||
for (String s : FileConfigFactory.getSuffixSet()) {
|
||||
targetFile = new File(tryPath + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR + s);
|
||||
if (targetFile.exists()) {
|
||||
return targetFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private File getFileFromClasspath(String name) throws UnsupportedEncodingException {
|
||||
URL resource = this.getClass().getClassLoader().getResource(name);
|
||||
if (resource == null) {
|
||||
for (String s : FileConfigFactory.getSuffixSet()) {
|
||||
resource = this.getClass().getClassLoader().getResource(name + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR + s);
|
||||
if (resource != null) {
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("The configuration file used is {}", resource.getPath());
|
||||
}
|
||||
String path = resource.getPath();
|
||||
path = URLDecoder.decode(path, StandardCharsets.UTF_8.name());
|
||||
return new File(path);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("The configuration file used is {}", name);
|
||||
}
|
||||
String path = resource.getPath();
|
||||
path = URLDecoder.decode(path, StandardCharsets.UTF_8.name());
|
||||
return new File(path);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) {
|
||||
String value = getConfigFromSysPro(dataId);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
ConfigFuture configFuture = new ConfigFuture(dataId, defaultValue, ConfigOperation.GET, timeoutMills);
|
||||
configOperateExecutor.submit(new ConfigOperateRunnable(configFuture));
|
||||
Object getValue = configFuture.get();
|
||||
return getValue == null ? null : String.valueOf(getValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putConfig(String dataId, String content, long timeoutMills) {
|
||||
ConfigFuture configFuture = new ConfigFuture(dataId, content, ConfigOperation.PUT, timeoutMills);
|
||||
configOperateExecutor.submit(new ConfigOperateRunnable(configFuture));
|
||||
return (Boolean) configFuture.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) {
|
||||
ConfigFuture configFuture = new ConfigFuture(dataId, content, ConfigOperation.PUTIFABSENT, timeoutMills);
|
||||
configOperateExecutor.submit(new ConfigOperateRunnable(configFuture));
|
||||
return (Boolean) configFuture.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeConfig(String dataId, long timeoutMills) {
|
||||
ConfigFuture configFuture = new ConfigFuture(dataId, null, ConfigOperation.REMOVE, timeoutMills);
|
||||
configOperateExecutor.submit(new ConfigOperateRunnable(configFuture));
|
||||
return (Boolean) configFuture.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addConfigListener(String dataId, ConfigurationChangeListener listener) {
|
||||
if (StringUtils.isBlank(dataId) || listener == null) {
|
||||
return;
|
||||
}
|
||||
configListenersMap.computeIfAbsent(dataId, key -> new ConcurrentSet<>())
|
||||
.add(listener);
|
||||
listenedConfigMap.put(dataId, ConfigurationFactory.getInstance().getConfig(dataId));
|
||||
|
||||
// Start config change listener for the dataId.
|
||||
fileListener.addListener(dataId, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeConfigListener(String dataId, ConfigurationChangeListener listener) {
|
||||
if (StringUtils.isBlank(dataId) || listener == null) {
|
||||
return;
|
||||
}
|
||||
Set<ConfigurationChangeListener> configListeners = getConfigListeners(dataId);
|
||||
if (CollectionUtils.isNotEmpty(configListeners)) {
|
||||
configListeners.remove(listener);
|
||||
if (configListeners.isEmpty()) {
|
||||
configListenersMap.remove(dataId);
|
||||
listenedConfigMap.remove(dataId);
|
||||
}
|
||||
}
|
||||
listener.onShutDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ConfigurationChangeListener> getConfigListeners(String dataId) {
|
||||
return configListenersMap.get(dataId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeName() {
|
||||
return REGISTRY_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type Config operate runnable.
|
||||
*/
|
||||
class ConfigOperateRunnable implements Runnable {
|
||||
|
||||
private ConfigFuture configFuture;
|
||||
|
||||
/**
|
||||
* Instantiates a new Config operate runnable.
|
||||
*
|
||||
* @param configFuture the config future
|
||||
*/
|
||||
public ConfigOperateRunnable(ConfigFuture configFuture) {
|
||||
this.configFuture = configFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (configFuture != null) {
|
||||
if (configFuture.isTimeout()) {
|
||||
setFailResult(configFuture);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (allowDynamicRefresh) {
|
||||
long tempLastModified = new File(targetFilePath).lastModified();
|
||||
if (tempLastModified > targetFileLastModified) {
|
||||
FileConfig tempConfig = FileConfigFactory.load(new File(targetFilePath), name);
|
||||
if (tempConfig != null) {
|
||||
fileConfig = tempConfig;
|
||||
targetFileLastModified = tempLastModified;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (configFuture.getOperation() == ConfigOperation.GET) {
|
||||
String result = fileConfig.getString(configFuture.getDataId());
|
||||
configFuture.setResult(result);
|
||||
} else if (configFuture.getOperation() == ConfigOperation.PUT) {
|
||||
//todo
|
||||
configFuture.setResult(Boolean.TRUE);
|
||||
} else if (configFuture.getOperation() == ConfigOperation.PUTIFABSENT) {
|
||||
//todo
|
||||
configFuture.setResult(Boolean.TRUE);
|
||||
} else if (configFuture.getOperation() == ConfigOperation.REMOVE) {
|
||||
//todo
|
||||
configFuture.setResult(Boolean.TRUE);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
setFailResult(configFuture);
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Could not found property {}, try to use default value instead. exception:{}",
|
||||
configFuture.getDataId(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setFailResult(ConfigFuture configFuture) {
|
||||
if (configFuture.getOperation() == ConfigOperation.GET) {
|
||||
String result = configFuture.getContent();
|
||||
configFuture.setResult(result);
|
||||
} else {
|
||||
configFuture.setResult(Boolean.FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The type FileListener.
|
||||
*/
|
||||
class FileListener implements ConfigurationChangeListener {
|
||||
|
||||
private final Map<String, Set<ConfigurationChangeListener>> dataIdMap = new HashMap<>();
|
||||
|
||||
private final ExecutorService executor = new ThreadPoolExecutor(CORE_LISTENER_THREAD, MAX_LISTENER_THREAD, 0L,
|
||||
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(),
|
||||
new NamedThreadFactory("fileListener", MAX_LISTENER_THREAD));
|
||||
|
||||
/**
|
||||
* Instantiates a new FileListener.
|
||||
*/
|
||||
FileListener() {}
|
||||
|
||||
public synchronized void addListener(String dataId, ConfigurationChangeListener listener) {
|
||||
// only the first time add listener will trigger on process event
|
||||
if (dataIdMap.isEmpty()) {
|
||||
fileListener.onProcessEvent(new ConfigurationChangeEvent());
|
||||
}
|
||||
|
||||
dataIdMap.computeIfAbsent(dataId, value -> new HashSet<>()).add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChangeEvent(ConfigurationChangeEvent event) {
|
||||
while (true) {
|
||||
for (String dataId : dataIdMap.keySet()) {
|
||||
try {
|
||||
String currentConfig =
|
||||
ConfigurationFactory.getInstance().getLatestConfig(dataId, null, DEFAULT_CONFIG_TIMEOUT);
|
||||
if (StringUtils.isNotBlank(currentConfig)) {
|
||||
String oldConfig = listenedConfigMap.get(dataId);
|
||||
if (ObjectUtils.notEqual(currentConfig, oldConfig)) {
|
||||
listenedConfigMap.put(dataId, currentConfig);
|
||||
event.setDataId(dataId).setNewValue(currentConfig).setOldValue(oldConfig);
|
||||
|
||||
for (ConfigurationChangeListener listener : dataIdMap.get(dataId)) {
|
||||
listener.onChangeEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception exx) {
|
||||
LOGGER.error("fileListener execute error, dataId :{}", dataId, exx);
|
||||
}
|
||||
}
|
||||
try {
|
||||
Thread.sleep(LISTENER_CONFIG_INTERVAL);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("fileListener thread sleep error:{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutorService getExecutorService() {
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config.file;
|
||||
|
||||
|
||||
/**
|
||||
* @author wangwei-ying
|
||||
*/
|
||||
public interface FileConfig {
|
||||
/**
|
||||
* @param path path expression
|
||||
*/
|
||||
String getString(String path);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config.file;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
|
||||
import io.seata.common.loader.LoadLevel;
|
||||
import io.seata.common.loader.Scope;
|
||||
import io.seata.config.FileConfigFactory;
|
||||
import io.seata.config.FileConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* @author wangwei-ying
|
||||
*/
|
||||
@LoadLevel(name = FileConfigFactory.DEFAULT_TYPE,scope = Scope.PROTOTYPE)
|
||||
public class SimpleFileConfig implements FileConfig {
|
||||
|
||||
private Config fileConfig;
|
||||
|
||||
public SimpleFileConfig() {
|
||||
fileConfig = ConfigFactory.load();
|
||||
}
|
||||
|
||||
public SimpleFileConfig(File file, String name) {
|
||||
if (name.startsWith(FileConfiguration.SYS_FILE_RESOURCE_PREFIX)) {
|
||||
Config appConfig = ConfigFactory.parseFileAnySyntax(file);
|
||||
fileConfig = ConfigFactory.load(appConfig);
|
||||
} else {
|
||||
fileConfig = ConfigFactory.load(file.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(String path) {
|
||||
return fileConfig.getString(path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config.file;
|
||||
|
||||
import io.seata.common.loader.LoadLevel;
|
||||
import io.seata.common.loader.Scope;
|
||||
import io.seata.config.FileConfigFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author wangwei-ying
|
||||
*/
|
||||
@LoadLevel(name = FileConfigFactory.YAML_TYPE, order = 1, scope = Scope.PROTOTYPE)
|
||||
public class YamlFileConfig implements FileConfig {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(YamlFileConfig.class);
|
||||
private Map configMap;
|
||||
|
||||
public YamlFileConfig(File file, String name) {
|
||||
Yaml yaml = new Yaml();
|
||||
try {
|
||||
configMap = (Map) yaml.load(new FileInputStream(file));
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new IllegalArgumentException("file not found");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(String path) {
|
||||
try {
|
||||
Map config = configMap;
|
||||
String[] dataId = path.split("\\.");
|
||||
for (int i = 0; i < dataId.length - 1; i++) {
|
||||
if (config.containsKey(dataId[i])) {
|
||||
config = (Map) config.get(dataId[i]);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Object value = config.get(dataId[dataId.length - 1]);
|
||||
return value == null ? null : String.valueOf(value);
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("get config data error" + path, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
io.seata.config.file.SimpleFileConfig
|
||||
io.seata.config.file.YamlFileConfig
|
||||
8
config/seata-config-core/src/main/resources/file.conf
Normal file
8
config/seata-config-core/src/main/resources/file.conf
Normal file
@@ -0,0 +1,8 @@
|
||||
service {
|
||||
#transaction service group mapping
|
||||
vgroupMapping.my_test_tx_group = "default"
|
||||
#only support when registry.type=file, please don't set multiple addresses
|
||||
default.grouplist = "127.0.0.1:8091"
|
||||
#disable seata
|
||||
disableGlobalTransaction = false
|
||||
}
|
||||
76
config/seata-config-core/src/main/resources/registry.conf
Normal file
76
config/seata-config-core/src/main/resources/registry.conf
Normal file
@@ -0,0 +1,76 @@
|
||||
registry {
|
||||
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
|
||||
type = "file"
|
||||
|
||||
nacos {
|
||||
application = "seata-server"
|
||||
serverAddr = "localhost"
|
||||
namespace = ""
|
||||
cluster = "default"
|
||||
}
|
||||
eureka {
|
||||
serviceUrl = "http://localhost:8761/eureka"
|
||||
application = "default"
|
||||
weight = "1"
|
||||
}
|
||||
redis {
|
||||
serverAddr = "localhost:6379"
|
||||
db = "0"
|
||||
}
|
||||
zk {
|
||||
cluster = "default"
|
||||
serverAddr = "127.0.0.1:2181"
|
||||
sessionTimeout = 6000
|
||||
connectTimeout = 2000
|
||||
}
|
||||
consul {
|
||||
cluster = "default"
|
||||
serverAddr = "127.0.0.1:8500"
|
||||
}
|
||||
etcd3 {
|
||||
cluster = "default"
|
||||
serverAddr = "http://localhost:2379"
|
||||
}
|
||||
sofa {
|
||||
serverAddr = "127.0.0.1:9603"
|
||||
application = "default"
|
||||
region = "DEFAULT_ZONE"
|
||||
datacenter = "DefaultDataCenter"
|
||||
cluster = "default"
|
||||
group = "SEATA_GROUP"
|
||||
addressWaitTime = "3000"
|
||||
}
|
||||
file {
|
||||
name = "file.conf"
|
||||
}
|
||||
}
|
||||
|
||||
config {
|
||||
# file、nacos 、apollo、zk、consul、etcd3
|
||||
type = "file"
|
||||
|
||||
nacos {
|
||||
serverAddr = "localhost"
|
||||
namespace = ""
|
||||
group = "SEATA_GROUP"
|
||||
}
|
||||
consul {
|
||||
serverAddr = "127.0.0.1:8500"
|
||||
}
|
||||
apollo {
|
||||
appId = "seata-server"
|
||||
apolloMeta = "http://192.168.1.204:8801"
|
||||
namespace = "application"
|
||||
}
|
||||
zk {
|
||||
serverAddr = "127.0.0.1:2181"
|
||||
sessionTimeout = 6000
|
||||
connectTimeout = 2000
|
||||
}
|
||||
etcd3 {
|
||||
serverAddr = "http://localhost:2379"
|
||||
}
|
||||
file {
|
||||
name = "file.conf"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
/**
|
||||
* @author wangwei-ying
|
||||
*/
|
||||
public interface ConfigProperty {
|
||||
final String ENV_PROPERTY_KEY = "seataEnv";
|
||||
final String SYSTEM_PROPERTY_SEATA_CONFIG_NAME = "seata.config.name";
|
||||
final String REGISTRY_CONF_DEFAULT = "registry";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
import io.seata.common.util.DurationUtil;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* @author jsbxyyx
|
||||
*/
|
||||
public class ConfigurationCacheTests {
|
||||
|
||||
@Test
|
||||
public void testChangeValue() throws Exception {
|
||||
Configuration configuration = new FileConfiguration("registry");
|
||||
configuration = ConfigurationCache.getInstance().proxy(configuration);
|
||||
configuration.getBoolean("aaa", false);
|
||||
ConfigurationCache.getInstance().onChangeEvent(new ConfigurationChangeEvent("aaa", "true"));
|
||||
boolean aaa = configuration.getBoolean("aaa", false);
|
||||
Assertions.assertTrue(aaa);
|
||||
|
||||
configuration.getShort("bbb", (short) 0);
|
||||
ConfigurationCache.getInstance().onChangeEvent(new ConfigurationChangeEvent("bbb", "1"));
|
||||
short bbb = configuration.getShort("bbb", (short) 0);
|
||||
Assertions.assertEquals((short) 1, bbb);
|
||||
|
||||
configuration.getDuration("ccc", Duration.ZERO);
|
||||
ConfigurationCache.getInstance().onChangeEvent(new ConfigurationChangeEvent("ccc", "1s"));
|
||||
Duration ccc = configuration.getDuration("ccc", Duration.ZERO);
|
||||
Assertions.assertEquals(ccc, DurationUtil.parse("1s"));
|
||||
|
||||
configuration.getInt("ddd", 0);
|
||||
ConfigurationCache.getInstance().onChangeEvent(new ConfigurationChangeEvent("ddd", "1"));
|
||||
int ddd = configuration.getInt("ddd", 0);
|
||||
Assertions.assertEquals(1, ddd);
|
||||
|
||||
configuration.getLong("eee", 0);
|
||||
ConfigurationCache.getInstance().onChangeEvent(new ConfigurationChangeEvent("eee", "1"));
|
||||
long eee = configuration.getLong("eee", 0);
|
||||
Assertions.assertEquals((long) 1, eee);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author slievrly
|
||||
*/
|
||||
class FileConfigurationTest {
|
||||
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
}
|
||||
|
||||
@Test
|
||||
void addConfigListener() throws InterruptedException {
|
||||
Configuration fileConfig = ConfigurationFactory.getInstance();
|
||||
CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
boolean value = fileConfig.getBoolean("service.disableGlobalTransaction");
|
||||
fileConfig.addConfigListener("service.disableGlobalTransaction", (event) -> {
|
||||
Assertions.assertEquals(Boolean.parseBoolean(event.getNewValue()), !Boolean.parseBoolean(event.getOldValue()));
|
||||
countDownLatch.countDown();
|
||||
});
|
||||
System.setProperty("service.disableGlobalTransaction", String.valueOf(!value));
|
||||
Assertions.assertTrue(countDownLatch.await(2000, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.seata.config.ConfigProperty.ENV_PROPERTY_KEY;
|
||||
import static io.seata.config.ConfigProperty.SYSTEM_PROPERTY_SEATA_CONFIG_NAME;
|
||||
import static io.seata.config.ConfigProperty.REGISTRY_CONF_DEFAULT;
|
||||
|
||||
/**
|
||||
* @author wangwei-ying
|
||||
*/
|
||||
class ProConfigurationFactoryTest {
|
||||
|
||||
@Test
|
||||
void getInstance() {
|
||||
System.setProperty(ENV_PROPERTY_KEY, "test-pro");
|
||||
System.setProperty(SYSTEM_PROPERTY_SEATA_CONFIG_NAME, REGISTRY_CONF_DEFAULT);
|
||||
ConfigurationFactory.reload();
|
||||
Assertions.assertEquals(ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig("config.file.name"), "file-test-pro.conf");
|
||||
Assertions.assertEquals(ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig("config.file.testBlank"), "");
|
||||
Assertions.assertEquals(ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig("config.file.testNull"), null);
|
||||
Assertions.assertEquals(ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig("config.file.testExist"), null);
|
||||
Configuration instance = ConfigurationFactory.getInstance();
|
||||
Assertions.assertEquals(instance.getConfig("service.disableGlobalTransaction"), "true");
|
||||
Assertions.assertEquals(instance.getConfig("service.default.grouplist"), "127.0.0.1:8092");
|
||||
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void afterAll() {
|
||||
System.clearProperty(ENV_PROPERTY_KEY);
|
||||
System.clearProperty(SYSTEM_PROPERTY_SEATA_CONFIG_NAME);
|
||||
ConfigurationFactory.reload();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.seata.config.ConfigProperty.ENV_PROPERTY_KEY;
|
||||
import static io.seata.config.ConfigProperty.SYSTEM_PROPERTY_SEATA_CONFIG_NAME;
|
||||
import static io.seata.config.ConfigProperty.REGISTRY_CONF_DEFAULT;
|
||||
|
||||
|
||||
/**
|
||||
* @author wangwei-ying
|
||||
*/
|
||||
class RegistryConfigurationFactoryTest {
|
||||
|
||||
@Test
|
||||
void getInstance() {
|
||||
System.setProperty(ENV_PROPERTY_KEY,"test");
|
||||
System.setProperty(SYSTEM_PROPERTY_SEATA_CONFIG_NAME,REGISTRY_CONF_DEFAULT);
|
||||
ConfigurationFactory.reload();
|
||||
Assertions.assertEquals(ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig("config.file.name"),"file-test.conf");
|
||||
Configuration instance = ConfigurationFactory.getInstance();
|
||||
Assertions.assertEquals(instance.getConfig("service.disableGlobalTransaction"),"true");
|
||||
Assertions.assertEquals(instance.getConfig("service.default.grouplist"), "127.0.0.1:8091");
|
||||
|
||||
}
|
||||
@AfterAll
|
||||
public static void afterAll(){
|
||||
System.clearProperty(ENV_PROPERTY_KEY);
|
||||
System.clearProperty(SYSTEM_PROPERTY_SEATA_CONFIG_NAME);
|
||||
ConfigurationFactory.reload();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import static io.seata.config.ConfigProperty.ENV_PROPERTY_KEY;
|
||||
import static io.seata.config.ConfigProperty.SYSTEM_PROPERTY_SEATA_CONFIG_NAME;
|
||||
import static io.seata.config.ConfigProperty.REGISTRY_CONF_DEFAULT;
|
||||
|
||||
/**
|
||||
* @author wangwei-ying
|
||||
*/
|
||||
class YamlConfigurationFactoryTest {
|
||||
|
||||
@Test
|
||||
public void getInstance() {
|
||||
System.setProperty(ENV_PROPERTY_KEY, "test-yaml");
|
||||
System.setProperty(SYSTEM_PROPERTY_SEATA_CONFIG_NAME, REGISTRY_CONF_DEFAULT);
|
||||
ConfigurationFactory.reload();
|
||||
Assertions.assertEquals(ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig("config.file.name"), "file-test-yaml.conf");
|
||||
Assertions.assertEquals(ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig("config.file.testBlank"), "");
|
||||
Assertions.assertEquals(ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig("config.file.testNull"), null);
|
||||
Assertions.assertEquals(ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig("config.file.testExist"), null);
|
||||
Configuration instance = ConfigurationFactory.getInstance();
|
||||
Assertions.assertEquals(instance.getConfig("service.disableGlobalTransaction"), "true");
|
||||
Assertions.assertEquals(instance.getConfig("service.default.grouplist"), "127.0.0.1:8093");
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void afterAll() {
|
||||
System.clearProperty(ENV_PROPERTY_KEY);
|
||||
System.clearProperty(SYSTEM_PROPERTY_SEATA_CONFIG_NAME);
|
||||
ConfigurationFactory.reload();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
service {
|
||||
#transaction service group mapping
|
||||
vgroupMapping.my_test_tx_group = "default"
|
||||
#only support when registry.type=file, please don't set multiple addresses
|
||||
default.grouplist = "127.0.0.1:8092"
|
||||
#disable seata
|
||||
disableGlobalTransaction = true
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
service {
|
||||
#transaction service group mapping
|
||||
vgroupMapping.my_test_tx_group = "default"
|
||||
#only support when registry.type=file, please don't set multiple addresses
|
||||
default.grouplist = "127.0.0.1:8093"
|
||||
#disable seata
|
||||
disableGlobalTransaction = true
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
service {
|
||||
#transaction service group mapping
|
||||
vgroupMapping.my_test_tx_group = "default"
|
||||
#only support when registry.type=file, please don't set multiple addresses
|
||||
default.grouplist = "127.0.0.1:8091"
|
||||
#disable seata
|
||||
disableGlobalTransaction = true
|
||||
}
|
||||
8
config/seata-config-core/src/test/resources/file.conf
Normal file
8
config/seata-config-core/src/test/resources/file.conf
Normal file
@@ -0,0 +1,8 @@
|
||||
service {
|
||||
#transaction service group mapping
|
||||
vgroupMapping.my_test_tx_group = "default"
|
||||
#only support when registry.type=file, please don't set multiple addresses
|
||||
default.grouplist = "127.0.0.1:8091"
|
||||
#disable seata
|
||||
disableGlobalTransaction = false
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
|
||||
#Copyright 1999-2019 Seata.io Group.
|
||||
#
|
||||
#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
|
||||
#
|
||||
#http://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.
|
||||
registry.type=file
|
||||
registry.file.name=file-test-pro.conf
|
||||
|
||||
config.type=file
|
||||
config.file.name=file-test-pro.conf
|
||||
config.file.testBlank=
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
registry:
|
||||
type: file
|
||||
file:
|
||||
name: file.conf
|
||||
config:
|
||||
type: file
|
||||
file:
|
||||
name: file-test-yaml.conf
|
||||
# for test
|
||||
testBlank: ''
|
||||
testNull:
|
||||
@@ -0,0 +1,73 @@
|
||||
registry {
|
||||
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
|
||||
type = "file"
|
||||
|
||||
nacos {
|
||||
serverAddr = "localhost"
|
||||
namespace = ""
|
||||
cluster = "default"
|
||||
}
|
||||
eureka {
|
||||
serviceUrl = "http://localhost:8761/eureka"
|
||||
application = "default"
|
||||
weight = "1"
|
||||
}
|
||||
redis {
|
||||
serverAddr = "localhost:6379"
|
||||
db = "0"
|
||||
}
|
||||
zk {
|
||||
cluster = "default"
|
||||
serverAddr = "127.0.0.1:2181"
|
||||
sessionTimeout = 6000
|
||||
connectTimeout = 2000
|
||||
}
|
||||
consul {
|
||||
cluster = "default"
|
||||
serverAddr = "127.0.0.1:8500"
|
||||
}
|
||||
etcd3 {
|
||||
cluster = "default"
|
||||
serverAddr = "http://localhost:2379"
|
||||
}
|
||||
sofa {
|
||||
serverAddr = "127.0.0.1:9603"
|
||||
application = "default"
|
||||
region = "DEFAULT_ZONE"
|
||||
datacenter = "DefaultDataCenter"
|
||||
cluster = "default"
|
||||
group = "SEATA_GROUP"
|
||||
addressWaitTime = "3000"
|
||||
}
|
||||
file {
|
||||
name = "file-test.conf"
|
||||
}
|
||||
}
|
||||
|
||||
config {
|
||||
# file、nacos 、apollo、zk、consul、etcd3
|
||||
type = "file"
|
||||
|
||||
nacos {
|
||||
serverAddr = "localhost"
|
||||
namespace = ""
|
||||
}
|
||||
consul {
|
||||
serverAddr = "127.0.0.1:8500"
|
||||
}
|
||||
apollo {
|
||||
appId = "seata-server"
|
||||
apolloMeta = "http://192.168.1.204:8801"
|
||||
}
|
||||
zk {
|
||||
serverAddr = "127.0.0.1:2181"
|
||||
sessionTimeout = 6000
|
||||
connectTimeout = 2000
|
||||
}
|
||||
etcd3 {
|
||||
serverAddr = "http://localhost:2379"
|
||||
}
|
||||
file {
|
||||
name = "file-test.conf"
|
||||
}
|
||||
}
|
||||
73
config/seata-config-core/src/test/resources/registry.conf
Normal file
73
config/seata-config-core/src/test/resources/registry.conf
Normal file
@@ -0,0 +1,73 @@
|
||||
registry {
|
||||
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
|
||||
type = "file"
|
||||
|
||||
nacos {
|
||||
serverAddr = "localhost"
|
||||
namespace = ""
|
||||
cluster = "default"
|
||||
}
|
||||
eureka {
|
||||
serviceUrl = "http://localhost:8761/eureka"
|
||||
application = "default"
|
||||
weight = "1"
|
||||
}
|
||||
redis {
|
||||
serverAddr = "localhost:6379"
|
||||
db = "0"
|
||||
}
|
||||
zk {
|
||||
cluster = "default"
|
||||
serverAddr = "127.0.0.1:2181"
|
||||
sessionTimeout = 6000
|
||||
connectTimeout = 2000
|
||||
}
|
||||
consul {
|
||||
cluster = "default"
|
||||
serverAddr = "127.0.0.1:8500"
|
||||
}
|
||||
etcd3 {
|
||||
cluster = "default"
|
||||
serverAddr = "http://localhost:2379"
|
||||
}
|
||||
sofa {
|
||||
serverAddr = "127.0.0.1:9603"
|
||||
application = "default"
|
||||
region = "DEFAULT_ZONE"
|
||||
datacenter = "DefaultDataCenter"
|
||||
cluster = "default"
|
||||
group = "SEATA_GROUP"
|
||||
addressWaitTime = "3000"
|
||||
}
|
||||
file {
|
||||
name = "file.conf"
|
||||
}
|
||||
}
|
||||
|
||||
config {
|
||||
# file、nacos 、apollo、zk、consul、etcd3
|
||||
type = "file"
|
||||
|
||||
nacos {
|
||||
serverAddr = "localhost"
|
||||
namespace = ""
|
||||
}
|
||||
consul {
|
||||
serverAddr = "127.0.0.1:8500"
|
||||
}
|
||||
apollo {
|
||||
appId = "seata-server"
|
||||
apolloMeta = "http://192.168.1.204:8801"
|
||||
}
|
||||
zk {
|
||||
serverAddr = "127.0.0.1:2181"
|
||||
sessionTimeout = 6000
|
||||
connectTimeout = 2000
|
||||
}
|
||||
etcd3 {
|
||||
serverAddr = "http://localhost:2379"
|
||||
}
|
||||
file {
|
||||
name = "file.conf"
|
||||
}
|
||||
}
|
||||
35
config/seata-config-custom/pom.xml
Normal file
35
config/seata-config-custom/pom.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 1999-2019 Seata.io Group.
|
||||
~
|
||||
~ 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
|
||||
~
|
||||
~ http://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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-config</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>seata-config-custom</artifactId>
|
||||
<name>seata-config-custom ${project.version}</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-config-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config.custom;
|
||||
|
||||
import io.seata.common.loader.EnhancedServiceLoader;
|
||||
import io.seata.common.loader.LoadLevel;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.config.ConfigType;
|
||||
import io.seata.config.Configuration;
|
||||
import io.seata.config.ConfigurationKeys;
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
import io.seata.config.ConfigurationProvider;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author ggndnn
|
||||
*/
|
||||
@LoadLevel(name = "Custom")
|
||||
public class CustomConfigurationProvider implements ConfigurationProvider {
|
||||
@Override
|
||||
public Configuration provide() {
|
||||
String pathDataId = ConfigurationKeys.FILE_ROOT_CONFIG + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR
|
||||
+ ConfigType.Custom.name().toLowerCase() + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR
|
||||
+ "name";
|
||||
String name = ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig(pathDataId);
|
||||
if (StringUtils.isBlank(name)) {
|
||||
throw new IllegalArgumentException("name value of custom config type must not be blank");
|
||||
}
|
||||
if (Stream.of(ConfigType.values())
|
||||
.anyMatch(ct -> ct.name().equalsIgnoreCase(name))) {
|
||||
throw new IllegalArgumentException(String.format("custom config type name %s is not allowed", name));
|
||||
}
|
||||
return EnhancedServiceLoader.load(ConfigurationProvider.class, name).provide();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
io.seata.config.custom.CustomConfigurationProvider
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
public class CustomConfigurationForTest extends AbstractConfiguration {
|
||||
private Properties properties;
|
||||
|
||||
public CustomConfigurationForTest(String name) {
|
||||
try (InputStream input = CustomConfigurationForTest.class.getClassLoader().getResourceAsStream(name)) {
|
||||
properties = new Properties();
|
||||
properties.load(input);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeName() {
|
||||
return "forTest";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) {
|
||||
return properties.getProperty(dataId, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putConfig(String dataId, String content, long timeoutMills) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeConfig(String dataId, long timeoutMills) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addConfigListener(String dataId, ConfigurationChangeListener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeConfigListener(String dataId, ConfigurationChangeListener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ConfigurationChangeListener> getConfigListeners(String dataId) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
import io.seata.common.loader.LoadLevel;
|
||||
|
||||
/**
|
||||
* @author ggndnn
|
||||
*/
|
||||
@LoadLevel(name = "forTest")
|
||||
public class CustomConfigurationProviderForTest implements ConfigurationProvider {
|
||||
@Override
|
||||
public Configuration provide() {
|
||||
return new CustomConfigurationForTest("custom_for_test.properties");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author ggndnn
|
||||
*/
|
||||
public class CustomConfigurationTest {
|
||||
@Test
|
||||
public void testCustomConfigLoad() throws Exception {
|
||||
Configuration configuration = ConfigurationFactory.getInstance();
|
||||
Assertions.assertTrue(null != configuration);
|
||||
Properties properties;
|
||||
try (InputStream input = CustomConfigurationForTest.class.getClassLoader().getResourceAsStream("custom_for_test.properties")) {
|
||||
properties = new Properties();
|
||||
properties.load(input);
|
||||
}
|
||||
Assertions.assertNotNull(properties);
|
||||
for (String name : properties.stringPropertyNames()) {
|
||||
String value = properties.getProperty(name);
|
||||
Assertions.assertNotNull(value);
|
||||
Assertions.assertEquals(value, configuration.getConfig(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
io.seata.config.CustomConfigurationProviderForTest
|
||||
@@ -0,0 +1,20 @@
|
||||
#
|
||||
# Copyright 1999-2019 Seata.io Group.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# http://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.
|
||||
#
|
||||
|
||||
transport.type=TCP
|
||||
transport.server=NIO
|
||||
service.default.grouplist=127.0.0.1:8091
|
||||
client.lock.retry.internal=10
|
||||
@@ -0,0 +1,7 @@
|
||||
config {
|
||||
type = "custom"
|
||||
|
||||
custom {
|
||||
name = "forTest"
|
||||
}
|
||||
}
|
||||
49
config/seata-config-etcd3/pom.xml
Normal file
49
config/seata-config-etcd3/pom.xml
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 1999-2019 Seata.io Group.
|
||||
~
|
||||
~ 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
|
||||
~
|
||||
~ http://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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-config</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>seata-config-etcd3</artifactId>
|
||||
<name>seata-config-etcd3 ${project.version}</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-config-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.etcd</groupId>
|
||||
<artifactId>jetcd-core</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config.etcd3;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.etcd.jetcd.ByteSequence;
|
||||
import io.etcd.jetcd.Client;
|
||||
import io.etcd.jetcd.KeyValue;
|
||||
import io.etcd.jetcd.Watch;
|
||||
import io.etcd.jetcd.kv.DeleteResponse;
|
||||
import io.etcd.jetcd.kv.GetResponse;
|
||||
import io.etcd.jetcd.kv.PutResponse;
|
||||
import io.etcd.jetcd.kv.TxnResponse;
|
||||
import io.etcd.jetcd.op.Cmp;
|
||||
import io.etcd.jetcd.op.CmpTarget;
|
||||
import io.etcd.jetcd.op.Op;
|
||||
import io.etcd.jetcd.options.PutOption;
|
||||
import io.etcd.jetcd.watch.WatchResponse;
|
||||
import io.netty.util.internal.ConcurrentSet;
|
||||
import io.seata.common.exception.ShouldNeverHappenException;
|
||||
import io.seata.common.thread.NamedThreadFactory;
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.config.AbstractConfiguration;
|
||||
import io.seata.config.ConfigFuture;
|
||||
import io.seata.config.Configuration;
|
||||
import io.seata.config.ConfigurationChangeEvent;
|
||||
import io.seata.config.ConfigurationChangeListener;
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static io.netty.util.CharsetUtil.UTF_8;
|
||||
import static io.seata.config.ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR;
|
||||
import static io.seata.config.ConfigurationKeys.FILE_ROOT_CONFIG;
|
||||
|
||||
/**
|
||||
* The type Etcd configuration.
|
||||
*
|
||||
* @author xingfudeshi @gmail.com
|
||||
*/
|
||||
public class EtcdConfiguration extends AbstractConfiguration {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(EtcdConfiguration.class);
|
||||
private static volatile EtcdConfiguration instance;
|
||||
private static volatile Client client;
|
||||
|
||||
private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE;
|
||||
private static final String SERVER_ADDR_KEY = "serverAddr";
|
||||
private static final String CONFIG_TYPE = "etcd3";
|
||||
private static final String FILE_CONFIG_KEY_PREFIX = FILE_ROOT_CONFIG + FILE_CONFIG_SPLIT_CHAR + CONFIG_TYPE
|
||||
+ FILE_CONFIG_SPLIT_CHAR;
|
||||
private static final int THREAD_POOL_NUM = 1;
|
||||
private static final int MAP_INITIAL_CAPACITY = 8;
|
||||
private ExecutorService etcdConfigExecutor;
|
||||
private ConcurrentMap<String, Set<ConfigurationChangeListener>> configListenersMap = new ConcurrentHashMap<>(
|
||||
MAP_INITIAL_CAPACITY);
|
||||
|
||||
private static final long VERSION_NOT_EXIST = 0;
|
||||
|
||||
private EtcdConfiguration() {
|
||||
etcdConfigExecutor = new ThreadPoolExecutor(THREAD_POOL_NUM, THREAD_POOL_NUM, Integer.MAX_VALUE,
|
||||
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(),
|
||||
new NamedThreadFactory("etcd-config-executor", THREAD_POOL_NUM));
|
||||
}
|
||||
|
||||
/**
|
||||
* get instance
|
||||
*
|
||||
* @return instance
|
||||
*/
|
||||
public static EtcdConfiguration getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (EtcdConfiguration.class) {
|
||||
if (instance == null) {
|
||||
instance = new EtcdConfiguration();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeName() {
|
||||
return CONFIG_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) {
|
||||
String value = getConfigFromSysPro(dataId);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
ConfigFuture configFuture = new ConfigFuture(dataId, defaultValue, ConfigFuture.ConfigOperation.GET,
|
||||
timeoutMills);
|
||||
etcdConfigExecutor.execute(
|
||||
() -> complete(getClient().getKVClient().get(ByteSequence.from(dataId, UTF_8)), configFuture));
|
||||
return (String)configFuture.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putConfig(String dataId, String content, long timeoutMills) {
|
||||
ConfigFuture configFuture = new ConfigFuture(dataId, content, ConfigFuture.ConfigOperation.PUT, timeoutMills);
|
||||
etcdConfigExecutor.execute(() -> complete(
|
||||
getClient().getKVClient().put(ByteSequence.from(dataId, UTF_8), ByteSequence.from(content, UTF_8)),
|
||||
configFuture));
|
||||
return (Boolean)configFuture.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) {
|
||||
ConfigFuture configFuture = new ConfigFuture(dataId, content, ConfigFuture.ConfigOperation.PUTIFABSENT,
|
||||
timeoutMills);
|
||||
etcdConfigExecutor.execute(() -> {
|
||||
//use etcd transaction to ensure the atomic operation
|
||||
complete(client.getKVClient().txn()
|
||||
//whether the key exists
|
||||
.If(new Cmp(ByteSequence.from(dataId, UTF_8), Cmp.Op.EQUAL, CmpTarget.version(VERSION_NOT_EXIST)))
|
||||
//not exist,then will create
|
||||
.Then(Op.put(ByteSequence.from(dataId, UTF_8), ByteSequence.from(content, UTF_8), PutOption.DEFAULT))
|
||||
.commit(), configFuture);
|
||||
});
|
||||
return (Boolean)configFuture.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeConfig(String dataId, long timeoutMills) {
|
||||
ConfigFuture configFuture = new ConfigFuture(dataId, null, ConfigFuture.ConfigOperation.REMOVE, timeoutMills);
|
||||
etcdConfigExecutor.execute(() -> complete(getClient().getKVClient().delete(ByteSequence.from(dataId, UTF_8)), configFuture));
|
||||
return (Boolean)configFuture.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addConfigListener(String dataId, ConfigurationChangeListener listener) {
|
||||
if (StringUtils.isBlank(dataId) || listener == null) {
|
||||
return;
|
||||
}
|
||||
EtcdListener etcdListener = new EtcdListener(dataId, listener);
|
||||
configListenersMap.computeIfAbsent(dataId, key -> new ConcurrentSet<>())
|
||||
.add(etcdListener);
|
||||
etcdListener.onProcessEvent(new ConfigurationChangeEvent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeConfigListener(String dataId, ConfigurationChangeListener listener) {
|
||||
if (StringUtils.isBlank(dataId) || listener == null) {
|
||||
return;
|
||||
}
|
||||
Set<ConfigurationChangeListener> configListeners = getConfigListeners(dataId);
|
||||
if (CollectionUtils.isNotEmpty(configListeners)) {
|
||||
ConfigurationChangeListener target;
|
||||
for (ConfigurationChangeListener entry : configListeners) {
|
||||
target = ((EtcdListener)entry).getTargetListener();
|
||||
if (listener.equals(target)) {
|
||||
entry.onShutDown();
|
||||
configListeners.remove(entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ConfigurationChangeListener> getConfigListeners(String dataId) {
|
||||
return configListenersMap.get(dataId);
|
||||
}
|
||||
|
||||
/**
|
||||
* get client
|
||||
*
|
||||
* @return client
|
||||
*/
|
||||
private static Client getClient() {
|
||||
if (client == null) {
|
||||
synchronized (EtcdConfiguration.class) {
|
||||
if (client == null) {
|
||||
client = Client.builder().endpoints(FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + SERVER_ADDR_KEY))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* complete the future
|
||||
*
|
||||
* @param completableFuture
|
||||
* @param configFuture
|
||||
* @param <T>
|
||||
*/
|
||||
private <T> void complete(CompletableFuture<T> completableFuture, ConfigFuture configFuture) {
|
||||
try {
|
||||
T response = completableFuture.get();
|
||||
if (response instanceof GetResponse) {
|
||||
List<KeyValue> keyValues = ((GetResponse)response).getKvs();
|
||||
if (CollectionUtils.isNotEmpty(keyValues)) {
|
||||
ByteSequence value = keyValues.get(0).getValue();
|
||||
if (value != null) {
|
||||
configFuture.setResult(value.toString(UTF_8));
|
||||
}
|
||||
}
|
||||
} else if (response instanceof PutResponse) {
|
||||
configFuture.setResult(Boolean.TRUE);
|
||||
} else if (response instanceof TxnResponse) {
|
||||
boolean result = ((TxnResponse)response).isSucceeded();
|
||||
//create key if file does not exist)
|
||||
if (result) {
|
||||
configFuture.setResult(Boolean.TRUE);
|
||||
}
|
||||
} else if (response instanceof DeleteResponse) {
|
||||
configFuture.setResult(Boolean.TRUE);
|
||||
} else {
|
||||
throw new ShouldNeverHappenException("unsupported response type");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("error occurred while completing the future{}", e.getMessage(),e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* the type config change notifier
|
||||
*/
|
||||
private static class EtcdListener implements ConfigurationChangeListener {
|
||||
private final String dataId;
|
||||
private final ConfigurationChangeListener listener;
|
||||
private Watch.Watcher watcher;
|
||||
private final ExecutorService executor = new ThreadPoolExecutor(CORE_LISTENER_THREAD, MAX_LISTENER_THREAD, 0L,
|
||||
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(),
|
||||
new NamedThreadFactory("etcdListener", MAX_LISTENER_THREAD));
|
||||
|
||||
/**
|
||||
* Instantiates a new Etcd listener.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param listener the listener
|
||||
*/
|
||||
public EtcdListener(String dataId, ConfigurationChangeListener listener) {
|
||||
this.dataId = dataId;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the listener
|
||||
*
|
||||
* @return ConfigurationChangeListener target listener
|
||||
*/
|
||||
public ConfigurationChangeListener getTargetListener() {
|
||||
return this.listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShutDown() {
|
||||
this.watcher.close();
|
||||
getExecutorService().shutdownNow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChangeEvent(ConfigurationChangeEvent event) {
|
||||
Watch watchClient = getClient().getWatchClient();
|
||||
watcher = watchClient.watch(ByteSequence.from(dataId, UTF_8), new Watch.Listener() {
|
||||
|
||||
@Override
|
||||
public void onNext(WatchResponse watchResponse) {
|
||||
try {
|
||||
GetResponse getResponse = getClient().getKVClient().get(ByteSequence.from(dataId, UTF_8)).get();
|
||||
List<KeyValue> keyValues = getResponse.getKvs();
|
||||
if (CollectionUtils.isNotEmpty(keyValues)) {
|
||||
event.setDataId(dataId).setNewValue(keyValues.get(0).getValue().toString(UTF_8));
|
||||
listener.onChangeEvent(event);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("error occurred while getting value{}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable throwable) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutorService getExecutorService() {
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config.etcd3;
|
||||
|
||||
import io.seata.common.loader.LoadLevel;
|
||||
import io.seata.config.Configuration;
|
||||
import io.seata.config.ConfigurationProvider;
|
||||
|
||||
/**
|
||||
* @author xingfudeshi@gmail.com
|
||||
*/
|
||||
@LoadLevel(name = "Etcd3", order = 1)
|
||||
public class EtcdConfigurationProvider implements ConfigurationProvider {
|
||||
@Override
|
||||
public Configuration provide() {
|
||||
return EtcdConfiguration.getInstance();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
io.seata.config.etcd3.EtcdConfigurationProvider
|
||||
40
config/seata-config-nacos/pom.xml
Normal file
40
config/seata-config-nacos/pom.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 1999-2019 Seata.io Group.
|
||||
~
|
||||
~ 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
|
||||
~
|
||||
~ http://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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-config</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>seata-config-nacos</artifactId>
|
||||
<name>seata-config-nacos ${project.version}</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-config-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>nacos-client</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config.nacos;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import com.alibaba.nacos.api.NacosFactory;
|
||||
import com.alibaba.nacos.api.config.ConfigService;
|
||||
import com.alibaba.nacos.api.config.listener.AbstractSharedListener;
|
||||
import com.alibaba.nacos.api.exception.NacosException;
|
||||
|
||||
import io.seata.common.exception.NotSupportYetException;
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.config.AbstractConfiguration;
|
||||
import io.seata.config.Configuration;
|
||||
import io.seata.config.ConfigurationChangeEvent;
|
||||
import io.seata.config.ConfigurationChangeListener;
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
import io.seata.config.ConfigurationKeys;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
/**
|
||||
* The type Nacos configuration.
|
||||
*
|
||||
* @author slievrly
|
||||
*/
|
||||
public class NacosConfiguration extends AbstractConfiguration {
|
||||
private static volatile NacosConfiguration instance;
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(NacosConfiguration.class);
|
||||
private static final String DEFAULT_GROUP = "SEATA_GROUP";
|
||||
private static final String DEFAULT_DATA_ID = "seata.properties";
|
||||
private static final String GROUP_KEY = "group";
|
||||
private static final String PRO_SERVER_ADDR_KEY = "serverAddr";
|
||||
private static final String NACOS_DATA_ID_KEY = "dataId";
|
||||
private static final String ENDPOINT_KEY = "endpoint";
|
||||
private static final String CONFIG_TYPE = "nacos";
|
||||
private static final String DEFAULT_NAMESPACE = "";
|
||||
private static final String PRO_NAMESPACE_KEY = "namespace";
|
||||
private static final String USER_NAME = "username";
|
||||
private static final String PASSWORD = "password";
|
||||
private static final String ACCESS_KEY = "accessKey";
|
||||
private static final String SECRET_KEY = "secretKey";
|
||||
private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE;
|
||||
private static volatile ConfigService configService;
|
||||
private static final int MAP_INITIAL_CAPACITY = 8;
|
||||
private static final ConcurrentMap<String, ConcurrentMap<ConfigurationChangeListener, NacosListener>> CONFIG_LISTENERS_MAP
|
||||
= new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY);
|
||||
private static volatile Properties seataConfig = new Properties();
|
||||
|
||||
/**
|
||||
* Get instance of NacosConfiguration
|
||||
*
|
||||
* @return instance
|
||||
*/
|
||||
public static NacosConfiguration getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (NacosConfiguration.class) {
|
||||
if (instance == null) {
|
||||
instance = new NacosConfiguration();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new Nacos configuration.
|
||||
*/
|
||||
private NacosConfiguration() {
|
||||
if (configService == null) {
|
||||
try {
|
||||
configService = NacosFactory.createConfigService(getConfigProperties());
|
||||
initSeataConfig();
|
||||
} catch (NacosException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) {
|
||||
String value = getConfigFromSysPro(dataId);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
value = seataConfig.getProperty(dataId);
|
||||
|
||||
if (null == value) {
|
||||
try {
|
||||
value = configService.getConfig(dataId, getNacosGroup(), timeoutMills);
|
||||
} catch (NacosException exx) {
|
||||
LOGGER.error(exx.getErrMsg());
|
||||
}
|
||||
}
|
||||
|
||||
return value == null ? defaultValue : value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putConfig(String dataId, String content, long timeoutMills) {
|
||||
boolean result = false;
|
||||
try {
|
||||
if (!seataConfig.isEmpty()) {
|
||||
seataConfig.setProperty(dataId, content);
|
||||
result = configService.publishConfig(getNacosDataId(), getNacosGroup(), getSeataConfigStr());
|
||||
} else {
|
||||
result = configService.publishConfig(dataId, getNacosGroup(), content);
|
||||
}
|
||||
} catch (NacosException exx) {
|
||||
LOGGER.error(exx.getErrMsg());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) {
|
||||
throw new NotSupportYetException("not support atomic operation putConfigIfAbsent");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeConfig(String dataId, long timeoutMills) {
|
||||
boolean result = false;
|
||||
try {
|
||||
if (!seataConfig.isEmpty()) {
|
||||
seataConfig.remove(dataId);
|
||||
result = configService.publishConfig(getNacosDataId(), getNacosGroup(), getSeataConfigStr());
|
||||
} else {
|
||||
result = configService.removeConfig(dataId, getNacosGroup());
|
||||
}
|
||||
} catch (NacosException exx) {
|
||||
LOGGER.error(exx.getErrMsg());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addConfigListener(String dataId, ConfigurationChangeListener listener) {
|
||||
if (StringUtils.isBlank(dataId) || listener == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
NacosListener nacosListener = new NacosListener(dataId, listener);
|
||||
CONFIG_LISTENERS_MAP.computeIfAbsent(dataId, key -> new ConcurrentHashMap<>())
|
||||
.put(listener, nacosListener);
|
||||
configService.addListener(dataId, getNacosGroup(), nacosListener);
|
||||
} catch (Exception exx) {
|
||||
LOGGER.error("add nacos listener error:{}", exx.getMessage(), exx);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeConfigListener(String dataId, ConfigurationChangeListener listener) {
|
||||
if (StringUtils.isBlank(dataId) || listener == null) {
|
||||
return;
|
||||
}
|
||||
Set<ConfigurationChangeListener> configChangeListeners = getConfigListeners(dataId);
|
||||
if (CollectionUtils.isNotEmpty(configChangeListeners)) {
|
||||
for (ConfigurationChangeListener entry : configChangeListeners) {
|
||||
if (listener.equals(entry)) {
|
||||
NacosListener nacosListener = null;
|
||||
Map<ConfigurationChangeListener, NacosListener> configListeners = CONFIG_LISTENERS_MAP.get(dataId);
|
||||
if (configListeners != null) {
|
||||
nacosListener = configListeners.get(listener);
|
||||
configListeners.remove(entry);
|
||||
}
|
||||
if (nacosListener != null) {
|
||||
configService.removeListener(dataId, getNacosGroup(), nacosListener);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ConfigurationChangeListener> getConfigListeners(String dataId) {
|
||||
Map<ConfigurationChangeListener, NacosListener> configListeners = CONFIG_LISTENERS_MAP.get(dataId);
|
||||
if (CollectionUtils.isNotEmpty(configListeners)) {
|
||||
return configListeners.keySet();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Properties getConfigProperties() {
|
||||
Properties properties = new Properties();
|
||||
if (System.getProperty(ENDPOINT_KEY) != null) {
|
||||
properties.setProperty(ENDPOINT_KEY, System.getProperty(ENDPOINT_KEY));
|
||||
properties.put(ACCESS_KEY, Objects.toString(System.getProperty(ACCESS_KEY), ""));
|
||||
properties.put(SECRET_KEY, Objects.toString(System.getProperty(SECRET_KEY), ""));
|
||||
} else if (System.getProperty(PRO_SERVER_ADDR_KEY) != null) {
|
||||
properties.setProperty(PRO_SERVER_ADDR_KEY, System.getProperty(PRO_SERVER_ADDR_KEY));
|
||||
} else {
|
||||
String address = FILE_CONFIG.getConfig(getNacosAddrFileKey());
|
||||
if (address != null) {
|
||||
properties.setProperty(PRO_SERVER_ADDR_KEY, address);
|
||||
}
|
||||
}
|
||||
|
||||
if (System.getProperty(PRO_NAMESPACE_KEY) != null) {
|
||||
properties.setProperty(PRO_NAMESPACE_KEY, System.getProperty(PRO_NAMESPACE_KEY));
|
||||
} else {
|
||||
String namespace = FILE_CONFIG.getConfig(getNacosNameSpaceFileKey());
|
||||
if (namespace == null) {
|
||||
namespace = DEFAULT_NAMESPACE;
|
||||
}
|
||||
properties.setProperty(PRO_NAMESPACE_KEY, namespace);
|
||||
}
|
||||
String userName = StringUtils.isNotBlank(System.getProperty(USER_NAME)) ? System.getProperty(USER_NAME)
|
||||
: FILE_CONFIG.getConfig(getNacosUserName());
|
||||
if (StringUtils.isNotBlank(userName)) {
|
||||
String password = StringUtils.isNotBlank(System.getProperty(PASSWORD)) ? System.getProperty(PASSWORD)
|
||||
: FILE_CONFIG.getConfig(getNacosPassword());
|
||||
if (StringUtils.isNotBlank(password)) {
|
||||
properties.setProperty(USER_NAME, userName);
|
||||
properties.setProperty(PASSWORD, password);
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
private static String getNacosNameSpaceFileKey() {
|
||||
return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE, PRO_NAMESPACE_KEY);
|
||||
}
|
||||
|
||||
private static String getNacosAddrFileKey() {
|
||||
return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE, PRO_SERVER_ADDR_KEY);
|
||||
}
|
||||
|
||||
private static String getNacosGroupKey() {
|
||||
return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE, GROUP_KEY);
|
||||
}
|
||||
|
||||
private static String getNacosDataIdKey() {
|
||||
return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE, NACOS_DATA_ID_KEY);
|
||||
}
|
||||
|
||||
private static String getNacosUserName() {
|
||||
return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE,
|
||||
USER_NAME);
|
||||
}
|
||||
|
||||
private static String getNacosPassword() {
|
||||
return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE,
|
||||
PASSWORD);
|
||||
}
|
||||
|
||||
private static String getNacosGroup() {
|
||||
return FILE_CONFIG.getConfig(getNacosGroupKey(), DEFAULT_GROUP);
|
||||
}
|
||||
|
||||
private static String getNacosDataId() {
|
||||
return FILE_CONFIG.getConfig(getNacosDataIdKey(), DEFAULT_DATA_ID);
|
||||
}
|
||||
|
||||
private static String getSeataConfigStr() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
Enumeration<?> enumeration = seataConfig.propertyNames();
|
||||
while (enumeration.hasMoreElements()) {
|
||||
String key = (String) enumeration.nextElement();
|
||||
String property = seataConfig.getProperty(key);
|
||||
sb.append(key).append("=").append(property).append("\n");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void initSeataConfig() {
|
||||
try {
|
||||
String nacosDataId = getNacosDataId();
|
||||
String config = configService.getConfig(nacosDataId, getNacosGroup(), DEFAULT_CONFIG_TIMEOUT);
|
||||
if (StringUtils.isNotBlank(config)) {
|
||||
try (Reader reader = new InputStreamReader(new ByteArrayInputStream(config.getBytes()), StandardCharsets.UTF_8)) {
|
||||
seataConfig.load(reader);
|
||||
}
|
||||
NacosListener nacosListener = new NacosListener(nacosDataId, null);
|
||||
configService.addListener(nacosDataId, getNacosGroup(), nacosListener);
|
||||
}
|
||||
} catch (NacosException | IOException e) {
|
||||
LOGGER.error("init config properties error", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeName() {
|
||||
return CONFIG_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-blocking subscriptions prohibit adding subscriptions in the thread pool to prevent thread termination
|
||||
*/
|
||||
public static class NacosListener extends AbstractSharedListener {
|
||||
private final String dataId;
|
||||
private final ConfigurationChangeListener listener;
|
||||
|
||||
/**
|
||||
* Instantiates a new Nacos listener.
|
||||
*
|
||||
* @param dataId the data id
|
||||
* @param listener the listener
|
||||
*/
|
||||
public NacosListener(String dataId, ConfigurationChangeListener listener) {
|
||||
this.dataId = dataId;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets target listener.
|
||||
*
|
||||
* @return the target listener
|
||||
*/
|
||||
public ConfigurationChangeListener getTargetListener() {
|
||||
return this.listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void innerReceive(String dataId, String group, String configInfo) {
|
||||
//The new configuration method to puts all configurations into a dateId
|
||||
if (getNacosDataId().equals(dataId)) {
|
||||
Properties seataConfigNew = new Properties();
|
||||
if (StringUtils.isNotBlank(configInfo)) {
|
||||
try (Reader reader = new InputStreamReader(new ByteArrayInputStream(configInfo.getBytes()), StandardCharsets.UTF_8)) {
|
||||
seataConfigNew.load(reader);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("load config properties error", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//Get all the monitored dataids and judge whether it has been modified
|
||||
for (Map.Entry<String, ConcurrentMap<ConfigurationChangeListener, NacosListener>> entry : CONFIG_LISTENERS_MAP.entrySet()) {
|
||||
String listenedDataId = entry.getKey();
|
||||
String propertyOld = seataConfig.getProperty(listenedDataId, "");
|
||||
String propertyNew = seataConfigNew.getProperty(listenedDataId, "");
|
||||
if (!propertyOld.equals(propertyNew)) {
|
||||
ConfigurationChangeEvent event = new ConfigurationChangeEvent()
|
||||
.setDataId(listenedDataId)
|
||||
.setNewValue(propertyNew)
|
||||
.setNamespace(group);
|
||||
|
||||
ConcurrentMap<ConfigurationChangeListener, NacosListener> configListeners = entry.getValue();
|
||||
for (ConfigurationChangeListener configListener : configListeners.keySet()) {
|
||||
configListener.onProcessEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
seataConfig = seataConfigNew;
|
||||
return;
|
||||
}
|
||||
|
||||
//Compatible with old writing
|
||||
ConfigurationChangeEvent event = new ConfigurationChangeEvent().setDataId(dataId).setNewValue(configInfo)
|
||||
.setNamespace(group);
|
||||
listener.onProcessEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config.nacos;
|
||||
|
||||
import io.seata.common.loader.LoadLevel;
|
||||
import io.seata.config.Configuration;
|
||||
import io.seata.config.ConfigurationProvider;
|
||||
|
||||
/**
|
||||
* @author xingfudeshi@gmail.com
|
||||
*/
|
||||
@LoadLevel(name = "Nacos", order = 1)
|
||||
public class NacosConfigurationProvider implements ConfigurationProvider {
|
||||
@Override
|
||||
public Configuration provide() {
|
||||
return NacosConfiguration.getInstance();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
io.seata.config.nacos.NacosConfigurationProvider
|
||||
25
config/seata-config-spring-cloud/pom.xml
Normal file
25
config/seata-config-spring-cloud/pom.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>seata-config</artifactId>
|
||||
<groupId>io.seata</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>seata-config-spring-cloud</artifactId>
|
||||
<name>seata-config-spring-cloud ${project.version}</name>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-config-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config.springcloud;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Import({SpringApplicationContextProviderRegistrar.class})
|
||||
public @interface EnableSeataSpringConfig {
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config.springcloud;
|
||||
|
||||
import io.seata.common.holder.ObjectHolder;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
|
||||
import static io.seata.common.Constants.OBJECT_KEY_SPRING_APPLICATION_CONTEXT;
|
||||
|
||||
/**
|
||||
* @author xingfudeshi@gmail.com
|
||||
* The type spring application context provider
|
||||
*/
|
||||
public class SpringApplicationContextProvider implements ApplicationContextAware, BeanFactoryPostProcessor {
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
ObjectHolder.INSTANCE.setObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT, applicationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { }
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config.springcloud;
|
||||
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
|
||||
import static io.seata.common.Constants.BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER;
|
||||
|
||||
/**
|
||||
* @author xingfudeshi@gmail.com
|
||||
* The type spring application context provider registrar
|
||||
*/
|
||||
public class SpringApplicationContextProviderRegistrar implements ImportBeanDefinitionRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
|
||||
if (!registry.containsBeanDefinition(BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER)) {
|
||||
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(SpringApplicationContextProvider.class).getBeanDefinition();
|
||||
registry.registerBeanDefinition(BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER, beanDefinition);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config.springcloud;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import io.seata.common.holder.ObjectHolder;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.config.AbstractConfiguration;
|
||||
import io.seata.config.ConfigurationChangeListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
public class SpringCloudConfiguration extends AbstractConfiguration {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(SpringCloudConfiguration.class);
|
||||
private static final String CONFIG_TYPE = "SpringCloudConfig";
|
||||
private static volatile SpringCloudConfiguration instance;
|
||||
private static final String PREFIX = "seata.";
|
||||
|
||||
public static SpringCloudConfiguration getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (SpringCloudConfiguration.class) {
|
||||
if (instance == null) {
|
||||
instance = new SpringCloudConfiguration();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
private SpringCloudConfiguration() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeName() {
|
||||
return CONFIG_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) {
|
||||
ApplicationContext applicationContext = ObjectHolder.INSTANCE.getObject(ApplicationContext.class);
|
||||
if (applicationContext == null || applicationContext.getEnvironment() == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
String conf = applicationContext.getEnvironment().getProperty(PREFIX + dataId);
|
||||
return StringUtils.isNotBlank(conf) ? conf : defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putConfig(String dataId, String content, long timeoutMills) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeConfig(String dataId, long timeoutMills) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addConfigListener(String dataId, ConfigurationChangeListener listener) {
|
||||
LOGGER.warn("dynamic listening is not supported spring cloud config");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeConfigListener(String dataId, ConfigurationChangeListener listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ConfigurationChangeListener> getConfigListeners(String dataId) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config.springcloud;
|
||||
|
||||
import io.seata.common.loader.LoadLevel;
|
||||
import io.seata.config.Configuration;
|
||||
import io.seata.config.ConfigurationProvider;
|
||||
|
||||
@LoadLevel(name = "SpringCloudConfig", order = 1)
|
||||
public class SpringCloudConfigurationProvider implements ConfigurationProvider {
|
||||
@Override
|
||||
public Configuration provide() {
|
||||
return SpringCloudConfiguration.getInstance();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
io.seata.config.springcloud.SpringCloudConfigurationProvider
|
||||
40
config/seata-config-zk/pom.xml
Normal file
40
config/seata-config-zk/pom.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 1999-2019 Seata.io Group.
|
||||
~
|
||||
~ 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
|
||||
~
|
||||
~ http://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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-config</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>seata-config-zk</artifactId>
|
||||
<name>seata-config-zk ${project.version}</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-config-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.101tec</groupId>
|
||||
<artifactId>zkclient</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config.zk;
|
||||
|
||||
import org.I0Itec.zkclient.exception.ZkMarshallingError;
|
||||
import org.I0Itec.zkclient.serialize.ZkSerializer;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Default zk serializer.
|
||||
* <p>
|
||||
* If the user is not configured in config.zk.serializer configuration item, then use default serializer.
|
||||
*
|
||||
* @author zhangchenghui.dev@gmail.com
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class DefaultZkSerializer implements ZkSerializer {
|
||||
|
||||
@Override
|
||||
public byte[] serialize(Object data) throws ZkMarshallingError {
|
||||
return String.valueOf(data).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object deserialize(byte[] bytes) throws ZkMarshallingError {
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config.zk;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.seata.common.exception.NotSupportYetException;
|
||||
import io.seata.common.thread.NamedThreadFactory;
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.config.AbstractConfiguration;
|
||||
import io.seata.config.Configuration;
|
||||
import io.seata.config.ConfigurationChangeEvent;
|
||||
import io.seata.config.ConfigurationChangeListener;
|
||||
import io.seata.config.ConfigurationChangeType;
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
import org.I0Itec.zkclient.IZkDataListener;
|
||||
import org.I0Itec.zkclient.ZkClient;
|
||||
import org.I0Itec.zkclient.serialize.ZkSerializer;
|
||||
import org.apache.zookeeper.CreateMode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static io.seata.config.ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR;
|
||||
import static io.seata.config.ConfigurationKeys.FILE_ROOT_CONFIG;
|
||||
import static io.seata.config.ConfigurationKeys.SEATA_FILE_ROOT_CONFIG;
|
||||
|
||||
/**
|
||||
* The type Zookeeper configuration.
|
||||
*
|
||||
* @author crazier.huang
|
||||
*/
|
||||
public class ZookeeperConfiguration extends AbstractConfiguration {
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(ZookeeperConfiguration.class);
|
||||
|
||||
private static final String CONFIG_TYPE = "zk";
|
||||
private static final String ZK_PATH_SPLIT_CHAR = "/";
|
||||
private static final String ROOT_PATH = ZK_PATH_SPLIT_CHAR + SEATA_FILE_ROOT_CONFIG;
|
||||
private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE;
|
||||
private static final String SERVER_ADDR_KEY = "serverAddr";
|
||||
private static final String SESSION_TIMEOUT_KEY = "sessionTimeout";
|
||||
private static final String CONNECT_TIMEOUT_KEY = "connectTimeout";
|
||||
private static final String AUTH_USERNAME = "username";
|
||||
private static final String AUTH_PASSWORD = "password";
|
||||
private static final String SERIALIZER_KEY = "serializer";
|
||||
private static final String CONFIG_PATH_KEY = "nodePath";
|
||||
private static final int THREAD_POOL_NUM = 1;
|
||||
private static final int DEFAULT_SESSION_TIMEOUT = 6000;
|
||||
private static final int DEFAULT_CONNECT_TIMEOUT = 2000;
|
||||
private static final String DEFAULT_CONFIG_PATH = ROOT_PATH + "/seata.properties";
|
||||
private static final String FILE_CONFIG_KEY_PREFIX = FILE_ROOT_CONFIG + FILE_CONFIG_SPLIT_CHAR + CONFIG_TYPE
|
||||
+ FILE_CONFIG_SPLIT_CHAR;
|
||||
private static final ExecutorService CONFIG_EXECUTOR = new ThreadPoolExecutor(THREAD_POOL_NUM, THREAD_POOL_NUM,
|
||||
Integer.MAX_VALUE, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(),
|
||||
new NamedThreadFactory("ZKConfigThread", THREAD_POOL_NUM));
|
||||
private static volatile ZkClient zkClient;
|
||||
private static final int MAP_INITIAL_CAPACITY = 8;
|
||||
private static final ConcurrentMap<String, ConcurrentMap<ConfigurationChangeListener, ZKListener>> CONFIG_LISTENERS_MAP
|
||||
= new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY);
|
||||
private static volatile Properties seataConfig = new Properties();
|
||||
|
||||
/**
|
||||
* Instantiates a new Zookeeper configuration.
|
||||
*/
|
||||
public ZookeeperConfiguration() {
|
||||
if (zkClient == null) {
|
||||
synchronized (ZookeeperConfiguration.class) {
|
||||
if (zkClient == null) {
|
||||
ZkSerializer zkSerializer = getZkSerializer();
|
||||
String serverAddr = FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + SERVER_ADDR_KEY);
|
||||
int sessionTimeout = FILE_CONFIG.getInt(FILE_CONFIG_KEY_PREFIX + SESSION_TIMEOUT_KEY, DEFAULT_SESSION_TIMEOUT);
|
||||
int connectTimeout = FILE_CONFIG.getInt(FILE_CONFIG_KEY_PREFIX + CONNECT_TIMEOUT_KEY, DEFAULT_CONNECT_TIMEOUT);
|
||||
zkClient = new ZkClient(serverAddr, sessionTimeout, connectTimeout, zkSerializer);
|
||||
String username = FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + AUTH_USERNAME);
|
||||
String password = FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + AUTH_PASSWORD);
|
||||
if (!StringUtils.isBlank(username) && !StringUtils.isBlank(password)) {
|
||||
StringBuilder auth = new StringBuilder(username).append(":").append(password);
|
||||
zkClient.addAuthInfo("digest", auth.toString().getBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!zkClient.exists(ROOT_PATH)) {
|
||||
zkClient.createPersistent(ROOT_PATH, true);
|
||||
}
|
||||
initSeataConfig();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeName() {
|
||||
return CONFIG_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) {
|
||||
String value = getConfigFromSysPro(dataId);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
value = seataConfig.getProperty(dataId);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
FutureTask<String> future = new FutureTask<>(() -> {
|
||||
String path = ROOT_PATH + ZK_PATH_SPLIT_CHAR + dataId;
|
||||
if (!zkClient.exists(path)) {
|
||||
LOGGER.warn("config {} is not existed, return defaultValue {} ",
|
||||
dataId, defaultValue);
|
||||
return defaultValue;
|
||||
}
|
||||
String value1 = zkClient.readData(path);
|
||||
return StringUtils.isNullOrEmpty(value1) ? defaultValue : value1;
|
||||
});
|
||||
CONFIG_EXECUTOR.execute(future);
|
||||
try {
|
||||
return future.get(timeoutMills, TimeUnit.MILLISECONDS);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("getConfig {} error or timeout, return defaultValue {}, exception:{} ",
|
||||
dataId, defaultValue, e.getMessage());
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putConfig(String dataId, String content, long timeoutMills) {
|
||||
if (!seataConfig.isEmpty()) {
|
||||
seataConfig.setProperty(dataId, content);
|
||||
zkClient.writeData(getConfigPath(), getSeataConfigStr());
|
||||
return true;
|
||||
}
|
||||
|
||||
FutureTask<Boolean> future = new FutureTask<>(() -> {
|
||||
String path = ROOT_PATH + ZK_PATH_SPLIT_CHAR + dataId;
|
||||
if (!zkClient.exists(path)) {
|
||||
zkClient.create(path, content, CreateMode.PERSISTENT);
|
||||
} else {
|
||||
zkClient.writeData(path, content);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
CONFIG_EXECUTOR.execute(future);
|
||||
try {
|
||||
return future.get(timeoutMills, TimeUnit.MILLISECONDS);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("putConfig {}, value: {} is error or timeout, exception: {}",
|
||||
dataId, content, e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) {
|
||||
throw new NotSupportYetException("not support atomic operation putConfigIfAbsent");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeConfig(String dataId, long timeoutMills) {
|
||||
if (!seataConfig.isEmpty()) {
|
||||
seataConfig.remove(dataId);
|
||||
zkClient.writeData(getConfigPath(), getSeataConfigStr());
|
||||
return true;
|
||||
}
|
||||
|
||||
FutureTask<Boolean> future = new FutureTask<>(() -> {
|
||||
String path = ROOT_PATH + ZK_PATH_SPLIT_CHAR + dataId;
|
||||
return zkClient.delete(path);
|
||||
});
|
||||
CONFIG_EXECUTOR.execute(future);
|
||||
try {
|
||||
return future.get(timeoutMills, TimeUnit.MILLISECONDS);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("removeConfig {} is error or timeout, exception:{}", dataId, e.getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addConfigListener(String dataId, ConfigurationChangeListener listener) {
|
||||
if (StringUtils.isBlank(dataId) || listener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!seataConfig.isEmpty()) {
|
||||
ZKListener zkListener = new ZKListener(dataId, listener);
|
||||
CONFIG_LISTENERS_MAP.computeIfAbsent(dataId, key -> new ConcurrentHashMap<>())
|
||||
.put(listener, zkListener);
|
||||
return;
|
||||
}
|
||||
|
||||
String path = ROOT_PATH + ZK_PATH_SPLIT_CHAR + dataId;
|
||||
if (zkClient.exists(path)) {
|
||||
ZKListener zkListener = new ZKListener(path, listener);
|
||||
CONFIG_LISTENERS_MAP.computeIfAbsent(dataId, key -> new ConcurrentHashMap<>())
|
||||
.put(listener, zkListener);
|
||||
zkClient.subscribeDataChanges(path, zkListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeConfigListener(String dataId, ConfigurationChangeListener listener) {
|
||||
if (StringUtils.isBlank(dataId) || listener == null) {
|
||||
return;
|
||||
}
|
||||
Set<ConfigurationChangeListener> configChangeListeners = getConfigListeners(dataId);
|
||||
if (CollectionUtils.isNotEmpty(configChangeListeners)) {
|
||||
String path = ROOT_PATH + ZK_PATH_SPLIT_CHAR + dataId;
|
||||
if (zkClient.exists(path)) {
|
||||
for (ConfigurationChangeListener entry : configChangeListeners) {
|
||||
if (listener.equals(entry)) {
|
||||
ZKListener zkListener = null;
|
||||
Map<ConfigurationChangeListener, ZKListener> configListeners = CONFIG_LISTENERS_MAP.get(dataId);
|
||||
if (configListeners != null) {
|
||||
zkListener = configListeners.get(listener);
|
||||
configListeners.remove(entry);
|
||||
}
|
||||
if (zkListener != null) {
|
||||
zkClient.unsubscribeDataChanges(path, zkListener);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ConfigurationChangeListener> getConfigListeners(String dataId) {
|
||||
ConcurrentMap<ConfigurationChangeListener, ZKListener> configListeners = CONFIG_LISTENERS_MAP.get(dataId);
|
||||
if (CollectionUtils.isNotEmpty(configListeners)) {
|
||||
return configListeners.keySet();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void initSeataConfig() {
|
||||
String configPath = getConfigPath();
|
||||
String config = zkClient.readData(configPath, true);
|
||||
if (StringUtils.isNotBlank(config)) {
|
||||
try (Reader reader = new InputStreamReader(new ByteArrayInputStream(config.getBytes()), StandardCharsets.UTF_8)) {
|
||||
seataConfig.load(reader);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("init config properties error", e);
|
||||
}
|
||||
ZKListener zkListener = new ZKListener(configPath, null);
|
||||
zkClient.subscribeDataChanges(configPath, zkListener);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getConfigPath() {
|
||||
return FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + CONFIG_PATH_KEY, DEFAULT_CONFIG_PATH);
|
||||
}
|
||||
|
||||
private static String getSeataConfigStr() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
Enumeration<?> enumeration = seataConfig.propertyNames();
|
||||
while (enumeration.hasMoreElements()) {
|
||||
String key = (String) enumeration.nextElement();
|
||||
String property = seataConfig.getProperty(key);
|
||||
sb.append(key).append("=").append(property).append("\n");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* The type Zk listener.
|
||||
*/
|
||||
public static class ZKListener implements IZkDataListener {
|
||||
|
||||
private String path;
|
||||
private ConfigurationChangeListener listener;
|
||||
|
||||
/**
|
||||
* Instantiates a new Zk listener.
|
||||
*
|
||||
* @param path the path
|
||||
* @param listener the listener
|
||||
*/
|
||||
public ZKListener(String path, ConfigurationChangeListener listener) {
|
||||
this.path = path;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDataChange(String s, Object o) {
|
||||
if (s.equals(getConfigPath())) {
|
||||
Properties seataConfigNew = new Properties();
|
||||
if (StringUtils.isNotBlank(o.toString())) {
|
||||
try (Reader reader = new InputStreamReader(new ByteArrayInputStream(o.toString().getBytes()), StandardCharsets.UTF_8)) {
|
||||
seataConfigNew.load(reader);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("load config properties error", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, ConcurrentMap<ConfigurationChangeListener, ZKListener>> entry : CONFIG_LISTENERS_MAP.entrySet()) {
|
||||
String listenedDataId = entry.getKey();
|
||||
String propertyOld = seataConfig.getProperty(listenedDataId, "");
|
||||
String propertyNew = seataConfigNew.getProperty(listenedDataId, "");
|
||||
if (!propertyOld.equals(propertyNew)) {
|
||||
ConfigurationChangeEvent event = new ConfigurationChangeEvent()
|
||||
.setDataId(listenedDataId)
|
||||
.setNewValue(propertyNew)
|
||||
.setChangeType(ConfigurationChangeType.MODIFY);
|
||||
|
||||
ConcurrentMap<ConfigurationChangeListener, ZKListener> configListeners = entry.getValue();
|
||||
for (ConfigurationChangeListener configListener : configListeners.keySet()) {
|
||||
configListener.onProcessEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
seataConfig = seataConfigNew;
|
||||
|
||||
return;
|
||||
}
|
||||
String dataId = s.replaceFirst(ROOT_PATH + ZK_PATH_SPLIT_CHAR, "");
|
||||
ConfigurationChangeEvent event = new ConfigurationChangeEvent().setDataId(dataId).setNewValue(o.toString())
|
||||
.setChangeType(ConfigurationChangeType.MODIFY);
|
||||
listener.onProcessEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDataDeleted(String s) {
|
||||
String dataId = s.replaceFirst(ROOT_PATH + ZK_PATH_SPLIT_CHAR, "");
|
||||
ConfigurationChangeEvent event = new ConfigurationChangeEvent().setDataId(dataId).setChangeType(
|
||||
ConfigurationChangeType.DELETE);
|
||||
listener.onProcessEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
private ZkSerializer getZkSerializer() {
|
||||
ZkSerializer zkSerializer = null;
|
||||
String serializer = FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + SERIALIZER_KEY);
|
||||
if (StringUtils.isNotBlank(serializer)) {
|
||||
try {
|
||||
Class<?> clazz = Class.forName(serializer);
|
||||
Constructor<?> constructor = clazz.getDeclaredConstructor();
|
||||
constructor.setAccessible(true);
|
||||
zkSerializer = (ZkSerializer) constructor.newInstance();
|
||||
} catch (ClassNotFoundException cfe) {
|
||||
LOGGER.warn("No zk serializer class found, serializer:{}", serializer, cfe);
|
||||
} catch (Throwable cause) {
|
||||
LOGGER.warn("found zk serializer encountered an unknown exception", cause);
|
||||
}
|
||||
}
|
||||
if (zkSerializer == null) {
|
||||
zkSerializer = new DefaultZkSerializer();
|
||||
LOGGER.info("Use default zk serializer: io.seata.config.zk.DefaultZkSerializer.");
|
||||
}
|
||||
return zkSerializer;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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 io.seata.config.zk;
|
||||
|
||||
import io.seata.common.loader.LoadLevel;
|
||||
import io.seata.config.Configuration;
|
||||
import io.seata.config.ConfigurationProvider;
|
||||
|
||||
/**
|
||||
* @author xingfudeshi@gmail.com
|
||||
*/
|
||||
@LoadLevel(name = "ZK", order = 1)
|
||||
public class ZookeeperConfigurationProvider implements ConfigurationProvider {
|
||||
@Override
|
||||
public Configuration provide() {
|
||||
try {
|
||||
return new ZookeeperConfiguration();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
io.seata.config.zk.ZookeeperConfigurationProvider
|
||||
Reference in New Issue
Block a user