chore(project): 添加项目配置文件和忽略规则

- 添加 Babel 配置文件支持 ES6+ 语法转换
- 添加 ESLint 忽略规则和配置文件
- 添加 Git 忽略规则文件
- 添加 Travis CI 配置文件
- 添加 1.4.2 版本变更日志文件
- 添加 Helm 图表辅助模板文件
- 添加 Helm 忽略规则文件
This commit is contained in:
2026-03-27 17:36:48 +08:00
commit c2453d6434
1703 changed files with 277582 additions and 0 deletions

40
config/pom.xml Normal file
View 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>

View 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>

View 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>

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1 @@
io.seata.config.apollo.ApolloConfigurationProvider

View 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>

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1 @@
io.seata.config.consul.ConsulConfigurationProvider

View 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>

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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() {
}
}

View File

@@ -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
}

View File

@@ -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();
}
}

View File

@@ -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";
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,2 @@
io.seata.config.file.SimpleFileConfig
io.seata.config.file.YamlFileConfig

View 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
}

View 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"
}
}

View File

@@ -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";
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View 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:8092"
#disable seata
disableGlobalTransaction = true
}

View 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:8093"
#disable seata
disableGlobalTransaction = true
}

View 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 = true
}

View 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
}

View File

@@ -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=

View File

@@ -0,0 +1,11 @@
registry:
type: file
file:
name: file.conf
config:
type: file
file:
name: file-test-yaml.conf
# for test
testBlank: ''
testNull:

View 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-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"
}
}

View 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"
}
}

View 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>

View File

@@ -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();
}
}

View File

@@ -0,0 +1 @@
io.seata.config.custom.CustomConfigurationProvider

View File

@@ -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();
}
}

View File

@@ -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");
}
}

View File

@@ -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));
}
}
}

View File

@@ -0,0 +1 @@
io.seata.config.CustomConfigurationProviderForTest

View File

@@ -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

View File

@@ -0,0 +1,7 @@
config {
type = "custom"
custom {
name = "forTest"
}
}

View 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>

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1 @@
io.seata.config.etcd3.EtcdConfigurationProvider

View 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>

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1 @@
io.seata.config.nacos.NacosConfigurationProvider

View 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>

View File

@@ -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 {
}

View File

@@ -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 { }
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1 @@
io.seata.config.springcloud.SpringCloudConfigurationProvider

View 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>

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1 @@
io.seata.config.zk.ZookeeperConfigurationProvider