diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000000..b8425e1f9d6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+# Except this file !.gitignore
+.classpath
+.project
+.settings
+target
+.project
+.idea
+.DS_Store
+/logs
+*.iml
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 00000000000..e4e74b6ec19
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,31 @@
+notifications:
+ email:
+ recipients:
+ - nacos_dev@linux.alibaba.com
+ - dev-nacos@googlegroups.com
+ on_success: change
+ on_failure: always
+
+language: java
+
+matrix:
+ include:
+ # On OSX, run with default JDK only.
+ # - os: osx
+ # On Linux, run with specific JDKs only.
+ - os: linux
+ env: CUSTOM_JDK="oraclejdk8"
+
+before_install:
+ - echo 'MAVEN_OPTS="$MAVEN_OPTS -Xmx1024m -XX:MaxPermSize=512m -XX:+BytecodeVerificationLocal"' >> ~/.mavenrc
+ - cat ~/.mavenrc
+ - if [ "$TRAVIS_OS_NAME" == "osx" ]; then export JAVA_HOME=$(/usr/libexec/java_home); fi
+ - if [ "$TRAVIS_OS_NAME" == "linux" ]; then jdk_switcher use "$CUSTOM_JDK"; fi
+
+script:
+ - travis_retry mvn -B clean apache-rat:check
+ - travis_retry mvn -B package jacoco:report coveralls:report
+
+after_success:
+ - mvn clean install -Pit-test
+ - mvn sonar:sonar -Psonar-apache
diff --git a/BUILDING b/BUILDING
new file mode 100644
index 00000000000..e7e968b8b45
--- /dev/null
+++ b/BUILDING
@@ -0,0 +1,38 @@
+Build Instructions for NACOS
+
+====================================================
+
+(1) Prerequisites
+
+ JDK 1.8+ is required in order to compile and run Nacos.
+
+ nacos utilizes Maven as a distribution management and packaging tool. Version 3.0.3 or later is required.
+ Maven installation and configuration instructions can be found here:
+
+ http://maven.apache.org/run-maven/index.html
+
+
+(2) Run test cases
+
+ Execute the following command in order to compile and run test cases of each components:
+
+ $ mvn test
+
+
+(3) Import projects to Eclipse IDE
+
+ First, generate eclipse project files:
+
+ $ mvn -U eclipse:eclipse
+
+ Then, import to eclipse by specifying the root directory of the project via:
+
+ [File] > [Import] > [Existing Projects into Workspace].
+
+
+(4) Build distribution packages
+
+ Execute the following command in order to build the tar.gz packages and install JAR into local repository:
+
+ #build nacos
+ $ mvn -Prelease-nacos -DskipTests clean install -U
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000000..e75d1970000
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,31 @@
+## How To Contribute
+
+We are always very happy to have contributions, whether for trivial cleanups or big new features.
+We want to have high quality, well documented codes for each programming language.
+
+Nor is code the only way to contribute to the project. We strongly value documentation, integration with other project, and gladly accept improvements for these aspects.
+
+## Contributing code
+
+To submit a change for inclusion, please do the following:
+
+#### If the change is non-trivial please include some unit tests that cover the new functionality.
+#### If you are introducing a completely new feature or API it is a good idea to start a wiki and get consensus on the basic design first.
+#### It is our job to follow up on patches in a timely fashion. Nag us if we aren't doing our job (sometimes we drop things).
+
+## Becoming a Committer
+
+We are always interested in adding new contributors. What we look for are series of contributions, good taste and ongoing interest in the project. If you are interested in becoming a committer, please let one of the existing committers know and they can help you walk through the process.
+
+Nowadays,we have several important contribution points:
+#### Wiki & JavaDoc
+#### Nacos Console
+#### Nacos SDK(C++\.Net\Php\Python\Go\Node.js)
+
+##### Prerequisite
+If you want to contribute the above listing points, you must abide our some prerequisites:
+
+###### Readability - API must have Javadoc,some very important methods also must have javadoc
+###### Testability - 80% above unit test coverage about main process
+###### Maintainability - Comply with our [PMD spec](style/codeStyle.xml), and at least 3 month update frequency
+###### Deployability - We encourage you to deploy into [maven repository](http://search.maven.org/)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000000..7f77f44e739
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (properties) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
\ No newline at end of file
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 00000000000..dcaaa07ecf4
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,32 @@
+Nacos
+Copyright 2018-2019 The Apache Software Foundation
+
+This product includes software developed at
+The Alibaba MiddleWare Group.
+
+------
+This product has a bundle Spring Boot:
+ The Spring Boot Project
+ =================
+
+Please visit the Spring Boot web site for more information:
+
+ * https://spring.io/projects/spring-boot
+
+Copyright 2014 The Spring Boot Project
+
+The Spring Boot Project licenses this file to you 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.
+
+Also, please refer to each LICENSE..txt file, which is located in
+the 'license' directory of the distribution file, for the license terms of the
+components that this product depends on.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 00000000000..3b3b20c911c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,49 @@
+## Nacos
+
+
+[](https://gitter.im/alibaba/nacos?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](https://www.apache.org/licenses/LICENSE-2.0.html)
+[](https://travis-ci.org/alibaba/nacos)
+
+-------
+
+Nacos is an easy-to-use platform desgined for dynamic service discovery and configuration and service management. It helps you to build cloud native applications and microservices platform easily.
+
+Service is a first-class citizen in Nacos. Nacos supports almost all type of services,for example,[Dubbo/gRPC service](xx)、[Spring Cloud RESTFul service](xx) or [Kubernetes service](xx).
+
+Nacos provides four major funcations.
+
+* **Service Discovery and Service Health Check**
+
+ Nacos makes it simple for services to register themselves and to discover other services via a DNS or HTTP interface. Nacos also provides real-time healthchecks of services to prevent sending requests to unhealthy hosts or service instance.
+
+* **Dynamic Configuration Management**
+
+ Dynamic Configuration Service allows you to manage configurations of all services in a centralized and dynamic manner across all environments. Nacos eliminates the need to redeploy applications and services when configurations are updated,which makes configuration changes more efficient and agile.
+
+* **Dynamic DNS Service**
+
+ Nacos supports weighted routing, making it easier for you to implement mid-tier load balancing, flexible routing policies, flow control, and simple DNS resolution services in the production environment within your data center. It helps you to implement DNS-based service discovery easily and prevent applications from coupling to vendor-specific service discovery APIs.
+
+* **Service and MetaData Management**
+
+ Nacos provides an easy-to-use service dashboard to help you manage your services metadata, configuration, kubernetes DNS, service health and metrics statistics.
+
+
+## Quick Start (TODO)
+It is super easy to get started with your first project.
+
+https://nacos.io/#/docs/quick-start.md
+
+Quick start for other open-source projects:
+
+[quick start with spring cloud](xx)
+[quick start with dubbo](xx)
+[quick start with kubernetes](xx)
+[more...](xx)
+
+
+## Documentation
+
+You can view full documentation on the Nacos website:
+
+[https://nacos.io/#/blog](https://nacos.io/#/blog)
diff --git a/client/pom.xml b/client/pom.xml
new file mode 100644
index 00000000000..78ddf9da055
--- /dev/null
+++ b/client/pom.xml
@@ -0,0 +1,107 @@
+
+
+ com.alibaba.nacos
+ nacos-all
+ 0.1.0
+
+
+ 4.0.0
+
+ nacos-client
+ jar
+
+ nacos-client ${project.version}
+ http://maven.apache.org
+
+
+ UTF-8
+
+
+
+
+ org.slf4j
+ slf4j-api
+ provided
+
+
+ log4j
+ log4j
+ provided
+
+
+ org.apache.logging.log4j
+ log4j-core
+ 2.8.2
+ provided
+
+
+ commons-logging
+ commons-logging
+ provided
+
+
+ org.slf4j
+ slf4j-log4j12
+ provided
+
+
+ org.apache.logging.log4j
+ log4j-slf4j-impl
+ provided
+
+
+ org.slf4j
+ jcl-over-slf4j
+ provided
+
+
+ junit
+ junit
+ test
+
+
+ ${project.groupId}
+ nacos-common
+
+
+ com.alibaba
+ fastjson
+
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ ch.qos.logback
+ logback-core
+
+
+
+ com.google.guava
+ guava
+
+
+
+ commons-codec
+ commons-codec
+
+
+
+ org.codehaus.jackson
+ jackson-mapper-lgpl
+
+
+ net.jcip
+ jcip-annotations
+ true
+
+
+ com.github.spotbugs
+ spotbugs-annotations
+ true
+
+
+
+
diff --git a/client/src/main/java/com/alibaba/nacos/api/NacosFactory.java b/client/src/main/java/com/alibaba/nacos/api/NacosFactory.java
new file mode 100644
index 00000000000..747bc7a45b8
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/NacosFactory.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api;
+
+import java.util.Properties;
+
+import com.alibaba.nacos.api.config.ConfigFactory;
+import com.alibaba.nacos.api.config.ConfigService;
+import com.alibaba.nacos.api.exception.NacosException;
+import com.alibaba.nacos.api.naming.NamingFactory;
+import com.alibaba.nacos.api.naming.NamingService;
+
+/**
+ * Nacos Factory
+ *
+ * @author Nacos
+ *
+ */
+public class NacosFactory {
+
+ /**
+ * Create config
+ *
+ * @param properties
+ * init param
+ * @return config
+ * @throws NacosException
+ * Exception
+ */
+ public static ConfigService createConfigService(Properties properties) throws NacosException {
+ return ConfigFactory.createConfigService(properties);
+ }
+
+ /**
+ * Create config
+ *
+ * @param serverAddr
+ * server list
+ * @return config
+ * @throws NacosException
+ * Exception
+ */
+ public static ConfigService createConfigService(String serverAddr) throws NacosException {
+ return ConfigFactory.createConfigService(serverAddr);
+ }
+
+ /**
+ * Create Naming
+ *
+ * @param serverAddr
+ * server list
+ * @return Naming
+ * @throws NacosException
+ * Exception
+ */
+ public static NamingService createNamingService(String serverAddr) throws NacosException {
+ return NamingFactory.createNamingService(serverAddr);
+ }
+
+ /**
+ * Create Naming
+ *
+ * @param properties
+ * init param
+ * @return Naming
+ * @throws NacosException
+ * Exception
+ */
+ public static NamingService createNamingService(Properties properties) throws NacosException {
+ return NamingFactory.createNamingService(properties);
+ }
+
+}
diff --git a/client/src/main/java/com/alibaba/nacos/api/PropertyKeyConst.java b/client/src/main/java/com/alibaba/nacos/api/PropertyKeyConst.java
new file mode 100644
index 00000000000..1fc3394301f
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/PropertyKeyConst.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api;
+
+/**
+ * properties key
+ * @author Nacos
+ *
+ */
+public class PropertyKeyConst {
+
+ public final static String ENDPOINT = "endpoint";
+ public final static String NAMESPACE = "namespace";
+ public final static String ACCESS_KEY = "accessKey";
+ public final static String SECRET_KEY = "secretKey";
+ public final static String SERVER_ADDR = "serverAddr";
+ public final static String CONTEXT_PATH = "contextPath";
+ public final static String CLUSTER_NAME = "clusterName";
+ public final static String ENCODE = "encode";
+
+}
diff --git a/client/src/main/java/com/alibaba/nacos/api/config/ConfigFactory.java b/client/src/main/java/com/alibaba/nacos/api/config/ConfigFactory.java
new file mode 100644
index 00000000000..9bfc7e7a4bd
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/config/ConfigFactory.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api.config;
+
+import java.util.Properties;
+
+import com.alibaba.nacos.api.PropertyKeyConst;
+import com.alibaba.nacos.api.exception.NacosException;
+import com.alibaba.nacos.client.config.NacosConfigService;
+
+/**
+ * Config Factory
+ *
+ * @author Nacos
+ *
+ */
+public class ConfigFactory {
+
+ /**
+ * Create Config
+ *
+ * @param properties
+ * init param
+ * @return Config
+ * @throws NacosException
+ * Exception
+ */
+ public static ConfigService createConfigService(Properties properties) throws NacosException {
+ return new NacosConfigService(properties);
+ }
+
+ /**
+ * Create Config
+ *
+ * @param ServerAddr
+ * serverlist
+ * @return Config
+ * @throws NacosException
+ * Exception
+ */
+ public static ConfigService createConfigService(String serverAddr) throws NacosException {
+ Properties properties = new Properties();
+ properties.put(PropertyKeyConst.SERVER_ADDR, serverAddr);
+ return new NacosConfigService(properties);
+ }
+
+}
diff --git a/client/src/main/java/com/alibaba/nacos/api/config/ConfigService.java b/client/src/main/java/com/alibaba/nacos/api/config/ConfigService.java
new file mode 100644
index 00000000000..9a96a528bef
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/config/ConfigService.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api.config;
+
+import com.alibaba.nacos.api.config.listener.Listener;
+import com.alibaba.nacos.api.exception.NacosException;
+
+/**
+ * Config Interface
+ *
+ * @author Nacos
+ *
+ */
+public interface ConfigService {
+
+ /**
+ * Get Configuration
+ *
+ * @param dataId
+ * Config ID
+ * @param group
+ * Config Group
+ * @param timeoutMs
+ * read timeout
+ * @return config value
+ * @throws NacosException
+ * NacosException
+ */
+ public String getConfig(String dataId, String group, long timeoutMs) throws NacosException;
+
+ /**
+ * Add a listener to the configuration, after the server to modify the
+ * configuration, the client will use the incoming listener callback.
+ * Recommended asynchronous processing, the application can implement the
+ * getExecutor method in the ManagerListener, provide a thread pool of
+ * execution. If provided, use the main thread callback, May block other
+ * configurations or be blocked by other configurations.
+ *
+ * @param dataId
+ * Config ID
+ * @param group
+ * Config Group
+ * @param listener
+ * listener
+ * @throws NacosException
+ * NacosException
+ */
+ public void addListener(String dataId, String group, Listener listener) throws NacosException;
+
+ /**
+ * publish config.
+ *
+ * @param dataId
+ * Config ID
+ * @param group
+ * Config Group
+ * @param content
+ * Config Content
+ * @return Whether publish
+ * @throws NacosException
+ * NacosException
+ */
+ public boolean publishConfig(String dataId, String group, String content) throws NacosException;
+
+ /**
+ * Remove Config
+ *
+ * @param dataId
+ * Config ID
+ * @param group
+ * Config Group
+ * @return whether remove
+ * @throws NacosException
+ * NacosException
+ */
+ public boolean removeConfig(String dataId, String group) throws NacosException;
+
+ /**
+ * Remove Listener
+ *
+ * @param dataId
+ * Config ID
+ * @param group
+ * Config Group
+ * @param listener
+ * listener
+ */
+ public void removeListener(String dataId, String group, Listener listener);
+
+}
diff --git a/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigContext.java b/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigContext.java
new file mode 100644
index 00000000000..2b26df8e4ca
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigContext.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api.config.filter;
+
+/**
+ * Config Context Interface
+ *
+ * @author Nacos
+ *
+ */
+public interface IConfigContext {
+ /**
+ * get context by key
+ *
+ * @param key
+ * @return context
+ */
+ public Object getParameter(String key);
+
+ /**
+ * set context
+ *
+ * @param key
+ * key
+ * @param value
+ * value
+ */
+ public void setParameter(String key, Object value);
+}
diff --git a/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigFilter.java b/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigFilter.java
new file mode 100644
index 00000000000..e3007640808
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigFilter.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api.config.filter;
+
+import com.alibaba.nacos.api.exception.NacosException;
+
+/**
+ * Config Filter Interface
+ *
+ * @author Nacos
+ *
+ */
+public interface IConfigFilter {
+ /**
+ * Init Fuction
+ *
+ * @param filterConfig
+ * Filter Config
+ */
+ void init(IFilterConfig filterConfig);
+
+ /**
+ * do filter
+ *
+ * @param request
+ * request
+ * @param response
+ * response
+ * @param filterChain
+ * filter Chain
+ * @throws NacosException
+ * exception
+ */
+ void doFilter(IConfigRequest request, IConfigResponse response, IConfigFilterChain filterChain)
+ throws NacosException;
+
+ /**
+ * deploy
+ */
+ void deploy();
+
+ /**
+ * order
+ *
+ * @return
+ */
+ int getOrder();
+
+ /**
+ * filterName
+ *
+ * @return
+ */
+ String getFilterName();
+
+}
diff --git a/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigFilterChain.java b/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigFilterChain.java
new file mode 100644
index 00000000000..ac084d67e49
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigFilterChain.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api.config.filter;
+
+import com.alibaba.nacos.api.exception.NacosException;
+
+/**
+ * Config Filter Chain Interface
+ *
+ * @author Nacos
+ *
+ */
+public interface IConfigFilterChain {
+ /**
+ * Filter aciton
+ *
+ * @param request
+ * request
+ * @param response
+ * response
+ * @throws NacosException
+ * NacosException
+ */
+ public void doFilter(IConfigRequest request, IConfigResponse response) throws NacosException;
+
+}
diff --git a/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigRequest.java b/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigRequest.java
new file mode 100644
index 00000000000..308fe06a6a8
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigRequest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api.config.filter;
+
+/**
+ * Config Request Interface
+ *
+ * @author Nacos
+ *
+ */
+public interface IConfigRequest {
+ /**
+ * get param
+ *
+ * @param key
+ * key
+ * @return value
+ */
+ public Object getParameter(String key);
+
+ /**
+ * get config context
+ *
+ * @return
+ */
+ public IConfigContext getConfigContext();
+
+}
diff --git a/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigResponse.java b/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigResponse.java
new file mode 100644
index 00000000000..69b5aca07e6
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigResponse.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api.config.filter;
+
+/**
+ * Config Response Interface
+ *
+ * @author Nacos
+ *
+ */
+public interface IConfigResponse {
+ /**
+ * get param
+ *
+ * @param key
+ * key
+ * @return value
+ */
+ public Object getParameter(String key);
+
+ /**
+ * get context
+ *
+ * @return configContext
+ */
+ public IConfigContext getConfigContext();
+
+}
diff --git a/client/src/main/java/com/alibaba/nacos/api/config/filter/IFilterConfig.java b/client/src/main/java/com/alibaba/nacos/api/config/filter/IFilterConfig.java
new file mode 100644
index 00000000000..55a931ba843
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/config/filter/IFilterConfig.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api.config.filter;
+/**
+ * Filter Config Interface
+ * @author Nacos
+ *
+ */
+public interface IFilterConfig {
+
+ /**
+ * get filter name
+ *
+ * @return
+ */
+ public String getFilterName();
+
+ /**
+ * get param
+ *
+ * @param name
+ * @return param
+ */
+ public Object getInitParameter(String name);
+
+}
diff --git a/client/src/main/java/com/alibaba/nacos/api/config/listener/AbstractListener.java b/client/src/main/java/com/alibaba/nacos/api/config/listener/AbstractListener.java
new file mode 100644
index 00000000000..817e0c5c566
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/config/listener/AbstractListener.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api.config.listener;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Listner Adapter,use default notify thread
+ *
+ * @author water.lyl
+ *
+ */
+@SuppressWarnings("PMD.AbstractClassShouldStartWithAbstractNamingRule")
+public abstract class AbstractListener implements Listener {
+
+ /**
+ * use default Executor
+ */
+ public Executor getExecutor() {
+ return null;
+ }
+
+}
diff --git a/client/src/main/java/com/alibaba/nacos/api/config/listener/AbstractSharedListener.java b/client/src/main/java/com/alibaba/nacos/api/config/listener/AbstractSharedListener.java
new file mode 100644
index 00000000000..dd0f83dee6d
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/config/listener/AbstractSharedListener.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api.config.listener;
+
+import java.util.concurrent.Executor;
+
+/**
+ * shared listener
+ *
+ * @author Nacos
+ *
+ */
+@SuppressWarnings("PMD.AbstractClassShouldStartWithAbstractNamingRule")
+public abstract class AbstractSharedListener implements Listener {
+ private volatile String dataId;
+ private volatile String group;
+
+ final public void fillContext(String dataId, String group) {
+ this.dataId = dataId;
+ this.group = group;
+ }
+
+ @Override
+ final public void receiveConfigInfo(String configInfo) {
+ innerReceive(dataId, group, configInfo);
+ }
+
+ @Override
+ public Executor getExecutor() {
+ return null;
+ }
+
+ /**
+ * receive
+ *
+ * @param dataId
+ * data ID
+ * @param group
+ * group
+ * @param configInfo
+ * content
+ */
+ public abstract void innerReceive(String dataId, String group, String configInfo);
+}
diff --git a/client/src/main/java/com/alibaba/nacos/api/config/listener/Listener.java b/client/src/main/java/com/alibaba/nacos/api/config/listener/Listener.java
new file mode 100644
index 00000000000..f8dc734e801
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/config/listener/Listener.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api.config.listener;
+
+import java.util.concurrent.Executor;
+
+
+/**
+ * Listener for watch config
+ *
+ * @author Nacos
+ *
+ */
+public interface Listener {
+
+ /**
+ * Executor to excute this receive
+ *
+ * @return Executor
+ */
+ public Executor getExecutor();
+
+
+ /**
+ * 接收配置信息
+ *
+ * @param configInfo 配置值
+ */
+ public void receiveConfigInfo(final String configInfo);
+}
diff --git a/client/src/main/java/com/alibaba/nacos/api/exception/NacosException.java b/client/src/main/java/com/alibaba/nacos/api/exception/NacosException.java
new file mode 100644
index 00000000000..7c5ffecd83a
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/exception/NacosException.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api.exception;
+
+/**
+ * Nacos Exception
+ *
+ * @author Nacos
+ *
+ */
+public class NacosException extends Exception{
+
+ /**
+ * serialVersionUID
+ */
+ private static final long serialVersionUID = -3913902031489277776L;
+
+ private int errCode;
+
+ private String errMsg;
+
+ public NacosException() {
+ };
+
+ public NacosException(int errCode, String errMsg) {
+ this.errCode = errCode;
+ this.errMsg = errMsg;
+ }
+
+ public int getErrCode() {
+ return errCode;
+ }
+
+ public String getErrMsg() {
+ return errMsg;
+ }
+
+ public void setErrCode(int errCode) {
+ this.errCode = errCode;
+ }
+
+ public void setErrMsg(String errMsg) {
+ this.errMsg = errMsg;
+ }
+
+ @Override
+ public String toString() {
+ return "ErrCode:" + errCode + ",ErrMsg:" + errMsg;
+ }
+
+ /**
+ * client error code
+ * -400 -503 throw exception to user
+ */
+ /**
+ * invalid param(参数错误)
+ */
+ public static final int CLIENT_INVALID_PARAM = -400;
+ /**
+ * over client threshold(超过server端的限流阈值)
+ */
+ public static final int CLIENT_OVER_THRESHOLD = -503;
+
+
+ /**
+ * server error code
+ * 400 403 throw exception to user
+ * 500 502 503 change ip and retry
+ */
+
+ /**
+ * invalid param(参数错误)
+ */
+ public static final int INVALID_PARAM = 400;
+ /**
+ * no right(鉴权失败)
+ */
+ public static final int NO_RIGHT = 403;
+ /**
+ * conflict(写并发冲突)
+ */
+ public static final int CONFLICT = 409;
+ /**
+ * server error(server异常,如超时)
+ */
+ public static final int SERVER_ERROR = 500;
+ /**
+ * bad gateway(路由异常,如nginx后面的Server挂掉)
+ */
+ public static final int BAD_GATEWAY = 502;
+ /**
+ * over threshold(超过server端的限流阈值)
+ */
+ public static final int OVER_THRESHOLD = 503;
+
+
+}
diff --git a/client/src/main/java/com/alibaba/nacos/api/naming/NamingFactory.java b/client/src/main/java/com/alibaba/nacos/api/naming/NamingFactory.java
new file mode 100644
index 00000000000..2607f05d7b9
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/naming/NamingFactory.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api.naming;
+
+import java.util.Properties;
+
+import com.alibaba.nacos.client.naming.NacosNamingService;
+
+/**
+ * @author dungu.zpf
+ */
+public class NamingFactory {
+
+ public static NamingService createNamingService(String serverList) {
+ return new NacosNamingService(serverList);
+ }
+
+ public static NamingService createNamingService(Properties properties) {
+ return new NacosNamingService(properties);
+ }
+}
diff --git a/client/src/main/java/com/alibaba/nacos/api/naming/NamingService.java b/client/src/main/java/com/alibaba/nacos/api/naming/NamingService.java
new file mode 100644
index 00000000000..a8f9763ae99
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/naming/NamingService.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api.naming;
+
+import java.util.List;
+
+import com.alibaba.nacos.api.exception.NacosException;
+import com.alibaba.nacos.api.naming.listener.EventListener;
+import com.alibaba.nacos.api.naming.pojo.Instance;
+
+/**
+ * @author dungu.zpf
+ */
+public interface NamingService {
+
+ /**
+ * Register a instance to service
+ *
+ * @param serviceName name of service
+ * @param ip instance ip
+ * @param port instance port
+ * @throws NacosException
+ */
+ void registerInstance(String serviceName, String ip, int port) throws NacosException;
+
+ /**
+ * Register a instance to service with specified cluster name
+ *
+ * @param serviceName name of service
+ * @param ip instance ip
+ * @param port instance port
+ * @param clusterName instance cluster name
+ * @throws NacosException
+ */
+ void registerInstance(String serviceName, String ip, int port, String clusterName) throws NacosException;
+
+ /**
+ * Register a instance to service with specified instance properties
+ *
+ * @param serviceName name of service
+ * @param instance instance to register
+ * @throws NacosException
+ */
+ void registerInstance(String serviceName, Instance instance) throws NacosException;
+
+ /**
+ * Deregister instance from a service
+ *
+ * @param serviceName name of service
+ * @param ip instance ip
+ * @param port instance port
+ * @throws NacosException
+ */
+ void deregisterInstance(String serviceName, String ip, int port) throws NacosException;
+
+ /**
+ * Deregister instance with specified cluster name from a service
+ *
+ * @param serviceName name of service
+ * @param ip instance ip
+ * @param port instance port
+ * @param clusterName instance cluster name
+ * @throws NacosException
+ */
+ void deregisterInstance(String serviceName, String ip, int port, String clusterName) throws NacosException;
+
+ /**
+ * Get all instances of a service
+ *
+ * @param serviceName name of service
+ * @return A list of instance
+ * @throws NacosException
+ */
+ List getAllInstances(String serviceName) throws NacosException;
+
+ /**
+ * Get all instances within specified clusters of a service
+ *
+ * @param serviceName name of service
+ * @param clusters list of cluster
+ * @return A list of qualified instance
+ * @throws NacosException
+ */
+ List getAllInstances(String serviceName, List clusters) throws NacosException;
+
+ /**
+ * Get qualified instances of service
+ *
+ * @param serviceName name of service
+ * @param healthy a flag to indicate returning healthy or unhealthy instances
+ * @return A qualified list of instance
+ * @throws NacosException
+ */
+ List selectInstances(String serviceName, boolean healthy) throws NacosException;
+
+ /**
+ * Get qualified instances within specified clusters of service
+ *
+ * @param serviceName name of service
+ * @param clusters list of cluster
+ * @param healthy a flag to indicate returning healthy or unhealthy instances
+ * @return A qualified list of instance
+ * @throws NacosException
+ */
+ List selectInstances(String serviceName, List clusters, boolean healthy) throws NacosException;
+
+ /**
+ * Select one healthy instance of service using predefined load balance strategy
+ *
+ * @param serviceName name of service
+ * @return qualified instance
+ * @throws NacosException
+ */
+ Instance selectOneHealthyInstance(String serviceName) throws NacosException;
+
+ /**
+ * Select one healthy instance of service using predefined load balance strategy
+ *
+ * @param serviceName name of service
+ * @param clusters a list of clusters should the instance belongs to
+ * @return qualified instance
+ * @throws NacosException
+ */
+ Instance selectOneHealthyInstance(String serviceName, List clusters) throws NacosException;
+
+ /**
+ * Subscribe service to receive events of instances alteration
+ *
+ * @param serviceName name of service
+ * @param listener event listener
+ * @throws NacosException
+ */
+ void subscribe(String serviceName, EventListener listener) throws NacosException;
+
+ /**
+ * Subscribe service to receive events of instances alteration
+ *
+ * @param serviceName name of service
+ * @param clusters list of cluster
+ * @param listener event listener
+ * @throws NacosException
+ */
+ void subscribe(String serviceName, List clusters, EventListener listener) throws NacosException;
+
+ /**
+ * Unsubscribe event listener of service
+ *
+ * @param serviceName name of service
+ * @param listener event listener
+ * @throws NacosException
+ */
+ void unsubscribe(String serviceName, EventListener listener) throws NacosException;
+
+ /**
+ * Unsubscribe event listener of service
+ *
+ * @param serviceName name of service
+ * @param clusters list of cluster
+ * @param listener event listener
+ * @throws NacosException
+ */
+ void unsubscribe(String serviceName, List clusters, EventListener listener) throws NacosException;
+}
diff --git a/client/src/main/java/com/alibaba/nacos/api/naming/listener/Event.java b/client/src/main/java/com/alibaba/nacos/api/naming/listener/Event.java
new file mode 100644
index 00000000000..224d1ce60a2
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/naming/listener/Event.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api.naming.listener;
+
+/**
+ * event interface
+ * @author dungu.zpf
+ */
+public interface Event {
+}
diff --git a/client/src/main/java/com/alibaba/nacos/api/naming/listener/EventListener.java b/client/src/main/java/com/alibaba/nacos/api/naming/listener/EventListener.java
new file mode 100644
index 00000000000..b0f0b94201c
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/naming/listener/EventListener.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api.naming.listener;
+
+/**
+ * event listener
+ * @author Nacos
+ *
+ */
+public interface EventListener {
+ /**
+ * callback event
+ * @param event
+ */
+ void onEvent(Event event);
+}
diff --git a/client/src/main/java/com/alibaba/nacos/api/naming/listener/NamingEvent.java b/client/src/main/java/com/alibaba/nacos/api/naming/listener/NamingEvent.java
new file mode 100644
index 00000000000..94b42a95463
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/naming/listener/NamingEvent.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api.naming.listener;
+
+import java.util.List;
+
+import com.alibaba.nacos.api.naming.pojo.Instance;
+
+/**
+ * @author dungu.zpf
+ */
+public class NamingEvent implements Event {
+
+ private String serviceName;
+
+ private List instances;
+
+ public NamingEvent(String serviceName, List instances) {
+ this.serviceName = serviceName;
+ this.instances = instances;
+ }
+
+ public String getServiceName() {
+ return serviceName;
+ }
+
+ public void setServiceName(String serviceName) {
+ this.serviceName = serviceName;
+ }
+
+ public List getInstances() {
+ return instances;
+ }
+
+ public void setInstances(List instances) {
+ this.instances = instances;
+ }
+}
diff --git a/client/src/main/java/com/alibaba/nacos/api/naming/pojo/AbstractHealthChecker.java b/client/src/main/java/com/alibaba/nacos/api/naming/pojo/AbstractHealthChecker.java
new file mode 100644
index 00000000000..f6a46e57ded
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/naming/pojo/AbstractHealthChecker.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api.naming.pojo;
+
+import com.alibaba.nacos.client.naming.utils.StringUtils;
+
+import java.util.Objects;
+
+/**
+ * @author dungu.zpf
+ */
+public abstract class AbstractHealthChecker implements Cloneable {
+
+ protected String type = "unknown";
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public static class Http extends AbstractHealthChecker {
+ public static final String TYPE = "HTTP";
+
+ private String path = StringUtils.EMPTY;
+ private String headers = StringUtils.EMPTY;
+
+ private int expectedResponseCode = 200;
+
+ public Http() {
+ this.type = TYPE;
+ }
+
+ public int getExpectedResponseCode() {
+ return expectedResponseCode;
+ }
+
+ public void setExpectedResponseCode(int expectedResponseCode) {
+ this.expectedResponseCode = expectedResponseCode;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ public String getHeaders() {
+ return headers;
+ }
+
+ public void setHeaders(String headers) {
+ this.headers = headers;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(path, headers, expectedResponseCode);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Http)) {
+ return false;
+ }
+
+ Http other = (Http) obj;
+
+ if (!StringUtils.equals(type, other.getType())) {
+ return false;
+ }
+
+ if (!StringUtils.equals(path, other.getPath())) {
+ return false;
+ }
+ if (!StringUtils.equals(headers, other.getHeaders())) {
+ return false;
+ }
+ return expectedResponseCode == other.getExpectedResponseCode();
+ }
+ }
+
+ public static class Tcp extends AbstractHealthChecker {
+ public static final String TYPE = "TCP";
+
+ public Tcp() {
+ this.type = TYPE;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(TYPE);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof Tcp;
+
+ }
+ }
+
+ public static class Mysql extends AbstractHealthChecker {
+ public static final String TYPE = "MYSQL";
+
+ private String user;
+ private String pwd;
+ private String cmd;
+
+ public Mysql() {
+ this.type = TYPE;
+ }
+
+ public String getCmd() {
+ return cmd;
+ }
+
+ public String getPwd() {
+ return pwd;
+ }
+
+ public String getUser() {
+ return user;
+ }
+
+ public void setUser(String user) {
+ this.user = user;
+ }
+
+ public void setCmd(String cmd) {
+ this.cmd = cmd;
+ }
+
+ public void setPwd(String pwd) {
+ this.pwd = pwd;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(user, pwd, cmd);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Mysql)) {
+ return false;
+ }
+
+ Mysql other = (Mysql) obj;
+
+ if (!StringUtils.equals(user, other.getUser())) {
+ return false;
+ }
+
+ if (!StringUtils.equals(pwd, other.getPwd())) {
+ return false;
+ }
+
+ return StringUtils.equals(cmd, other.getCmd());
+
+ }
+ }
+}
diff --git a/client/src/main/java/com/alibaba/nacos/api/naming/pojo/Cluster.java b/client/src/main/java/com/alibaba/nacos/api/naming/pojo/Cluster.java
new file mode 100644
index 00000000000..eec4a3e1b05
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/naming/pojo/Cluster.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api.naming.pojo;
+
+import com.alibaba.nacos.client.naming.utils.StringUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author dungu.zpf
+ */
+public class Cluster {
+
+ /**
+ * Name of belonging service
+ */
+ private String serviceName;
+
+ /**
+ * Name of cluster
+ */
+ private String name = StringUtils.EMPTY;
+
+ /**
+ * Health check config of this cluster
+ */
+ private AbstractHealthChecker healthChecker = new AbstractHealthChecker.Tcp();
+
+ /**
+ * Default registered port for instances in this cluster.
+ */
+ private int defaultPort = 80;
+
+ /**
+ * Default health check port of instances in this cluster.
+ */
+ private int defaultCheckPort = 80;
+
+ /**
+ * Whether or not use instance port to do health check.
+ */
+ private boolean useIPPort4Check = true;
+
+
+ private Map metadata = new HashMap<>();
+
+ public Cluster() {
+
+ }
+
+ public Cluster(String clusterName) {
+ this.name = clusterName;
+ }
+
+ public String getServiceName() {
+ return serviceName;
+ }
+
+ public void setServiceName(String serviceName) {
+ this.serviceName = serviceName;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public AbstractHealthChecker getHealthChecker() {
+ return healthChecker;
+ }
+
+ public void setHealthChecker(AbstractHealthChecker healthChecker) {
+ this.healthChecker = healthChecker;
+ }
+
+ public int getDefaultPort() {
+ return defaultPort;
+ }
+
+ public void setDefaultPort(int defaultPort) {
+ this.defaultPort = defaultPort;
+ }
+
+ public int getDefaultCheckPort() {
+ return defaultCheckPort;
+ }
+
+ public void setDefaultCheckPort(int defaultCheckPort) {
+ this.defaultCheckPort = defaultCheckPort;
+ }
+
+ public boolean isUseIPPort4Check() {
+ return useIPPort4Check;
+ }
+
+ public void setUseIPPort4Check(boolean useIPPort4Check) {
+ this.useIPPort4Check = useIPPort4Check;
+ }
+
+ public Map getMetadata() {
+ return metadata;
+ }
+
+ public void setMetadata(Map metadata) {
+ this.metadata = metadata;
+ }
+}
diff --git a/client/src/main/java/com/alibaba/nacos/api/naming/pojo/Instance.java b/client/src/main/java/com/alibaba/nacos/api/naming/pojo/Instance.java
new file mode 100644
index 00000000000..55ecfaa044d
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/naming/pojo/Instance.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api.naming.pojo;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.annotation.JSONField;
+import com.alibaba.nacos.client.naming.utils.StringUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author dungu.zpf
+ */
+public class Instance {
+
+ /**
+ * Unique ID of this instance.
+ */
+ private String instanceId;
+
+ /**
+ * Instance ip
+ */
+ private String ip;
+
+ /**
+ * Instance port
+ */
+ private int port;
+
+ /**
+ * Instance weight
+ */
+ private double weight = 1.0D;
+
+ /**
+ * Instance health status
+ */
+ @JSONField(name = "valid")
+ private boolean healthy = true;
+
+ /**
+ * Cluster information of instance
+ */
+ @JSONField(serialize = false)
+ private Cluster cluster = new Cluster();
+
+ /**
+ * Service information of instance
+ */
+ @JSONField(serialize = false)
+ private Service service;
+
+ /**
+ * User extended attributes
+ */
+ private Map metadata = new HashMap<>();
+
+ public String getInstanceId() {
+ return instanceId;
+ }
+
+ public void setInstanceId(String instanceId) {
+ this.instanceId = instanceId;
+ }
+
+ public String getIp() {
+ return ip;
+ }
+
+ public void setIp(String ip) {
+ this.ip = ip;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public double getWeight() {
+ return weight;
+ }
+
+ public void setWeight(double weight) {
+ this.weight = weight;
+ }
+
+ public boolean isHealthy() {
+ return healthy;
+ }
+
+ public void setHealthy(boolean healthy) {
+ this.healthy = healthy;
+ }
+
+ public Cluster getCluster() {
+ return cluster;
+ }
+
+ public void setCluster(Cluster cluster) {
+ this.cluster = cluster;
+ }
+
+ public Service getService() {
+ return service;
+ }
+
+ public void setService(Service service) {
+ this.service = service;
+ }
+
+ public Map getMetadata() {
+ return metadata;
+ }
+
+ public void setMetadata(Map metadata) {
+ this.metadata = metadata;
+ }
+
+ public void addMetadata(String key, String value) {
+ this.metadata.put(key, value);
+ }
+
+ @Override
+ public String toString() {
+ return JSON.toJSONString(this);
+ }
+
+ public String toInetAddr() {
+ return ip + ":" + port;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Instance)) {
+ return false;
+ }
+
+ Instance host = (Instance) obj;
+
+ return StringUtils.equals(toString(), host.toString());
+ }
+
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+}
diff --git a/client/src/main/java/com/alibaba/nacos/api/naming/pojo/Service.java b/client/src/main/java/com/alibaba/nacos/api/naming/pojo/Service.java
new file mode 100644
index 00000000000..e5274f1cb2d
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/api/naming/pojo/Service.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.api.naming.pojo;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author dungu.zpf
+ */
+public class Service {
+
+ /**
+ * Service name
+ */
+ private String name;
+
+ /**
+ * Protect threshold
+ */
+ private float protectThreshold = 0.0F;
+
+ /**
+ * Application name of this service
+ */
+ private String app;
+
+ /**
+ * Service group which is meant to classify services into different sets.
+ */
+ private String group;
+
+ /**
+ * Health check mode.
+ */
+ private String healthCheckMode;
+
+ public Service(String name) {
+ this.name = name;
+ }
+
+ private Map metadata = new HashMap<>();
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getHealthCheckMode() {
+ return healthCheckMode;
+ }
+
+ public void setHealthCheckMode(String healthCheckMode) {
+ this.healthCheckMode = healthCheckMode;
+ }
+
+ public float getProtectThreshold() {
+ return protectThreshold;
+ }
+
+ public void setProtectThreshold(float protectThreshold) {
+ this.protectThreshold = protectThreshold;
+ }
+
+ public String getApp() {
+ return app;
+ }
+
+ public void setApp(String app) {
+ this.app = app;
+ }
+
+ public String getGroup() {
+ return group;
+ }
+
+ public void setGroup(String group) {
+ this.group = group;
+ }
+
+ public Map getMetadata() {
+ return metadata;
+ }
+
+ public void setMetadata(Map metadata) {
+ this.metadata = metadata;
+ }
+
+ public void addMetadata(String key, String value) {
+ this.metadata.put(key, value);
+ }
+}
diff --git a/client/src/main/java/com/alibaba/nacos/client/config/NacosConfigService.java b/client/src/main/java/com/alibaba/nacos/client/config/NacosConfigService.java
new file mode 100644
index 00000000000..3b4953e1039
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/client/config/NacosConfigService.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.client.config;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
+
+import com.alibaba.nacos.api.PropertyKeyConst;
+import com.alibaba.nacos.api.config.ConfigService;
+import com.alibaba.nacos.api.config.listener.Listener;
+import com.alibaba.nacos.api.exception.NacosException;
+import com.alibaba.nacos.client.config.common.Constants;
+import com.alibaba.nacos.client.config.filter.impl.ConfigFilterChainManager;
+import com.alibaba.nacos.client.config.filter.impl.ConfigRequest;
+import com.alibaba.nacos.client.config.filter.impl.ConfigResponse;
+import com.alibaba.nacos.client.config.impl.ClientWorker;
+import com.alibaba.nacos.client.config.impl.LocalConfigInfoProcessor;
+import com.alibaba.nacos.client.config.impl.ServerHttpAgent;
+import com.alibaba.nacos.client.config.impl.HttpSimpleClient.HttpResult;
+import com.alibaba.nacos.client.config.utils.ContentUtils;
+import com.alibaba.nacos.client.config.utils.LogUtils;
+import com.alibaba.nacos.client.config.utils.ParamUtils;
+import com.alibaba.nacos.client.config.utils.TenantUtil;
+import com.alibaba.nacos.client.logger.Logger;
+import com.alibaba.nacos.client.logger.support.LoggerHelper;
+import com.alibaba.nacos.client.utils.StringUtils;
+
+/**
+ * Config Impl
+ * @author Nacos
+ *
+ */
+@SuppressWarnings("PMD.ServiceOrDaoClassShouldEndWithImplRule")
+public class NacosConfigService implements ConfigService {
+
+ final static public Logger log = LogUtils.logger(NacosConfigService.class);
+ public final long POST_TIMEOUT = 3000L;
+ /**
+ * http agent
+ */
+ private ServerHttpAgent agent;
+ /**
+ * longpulling
+ */
+ private ClientWorker worker;
+ private String namespace;
+ private String encode;
+ private ConfigFilterChainManager configFilterChainManager = new ConfigFilterChainManager();
+
+ public NacosConfigService(Properties properties) throws NacosException {
+ String encodeTmp = properties.getProperty(PropertyKeyConst.ENCODE);
+ if (StringUtils.isBlank(encodeTmp)) {
+ encode = Constants.ENCODE;
+ } else {
+ encode = encodeTmp.trim();
+ }
+ String namespaceTmp = properties.getProperty(PropertyKeyConst.NAMESPACE);
+ if (StringUtils.isBlank(namespaceTmp)) {
+ namespace = TenantUtil.getUserTenant();
+ properties.put(PropertyKeyConst.NAMESPACE, namespace);
+ } else {
+ namespace = namespaceTmp;
+ properties.put(PropertyKeyConst.NAMESPACE, namespace);
+ }
+ agent = new ServerHttpAgent(properties);
+ agent.start();
+ worker = new ClientWorker(agent, configFilterChainManager);
+ }
+
+ @Override
+ public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {
+ return getConfigInner(namespace, dataId, group, timeoutMs);
+ }
+
+ @Override
+ public void addListener(String dataId, String group, Listener listener) throws NacosException {
+ worker.addTenantListeners(dataId, group, Arrays.asList(listener));
+ }
+
+ @Override
+ public boolean publishConfig(String dataId, String group, String content) throws NacosException {
+ return publishConfigInner(namespace, dataId, group, null, null, null, content);
+ }
+
+ @Override
+ public boolean removeConfig(String dataId, String group) throws NacosException {
+ return removeConfigInner(namespace, dataId, group, null);
+ }
+
+ @Override
+ public void removeListener(String dataId, String group, Listener listener) {
+ worker.removeTenantListener(dataId, group, listener);
+ }
+
+ private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
+ group = null2defaultGroup(group);
+ ParamUtils.checkKeyParam(dataId, group);
+ ConfigResponse cr = new ConfigResponse();
+
+ cr.setDataId(dataId);
+ cr.setTenant(tenant);
+ cr.setGroup(group);
+
+ // 优先使用本地配置
+ String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
+ if (content != null) {
+ log.warn(agent.getName(), "[get-config] get failover ok, dataId={}, group={}, tenant={}, config={}", dataId,
+ group, tenant, ContentUtils.truncateContent(content));
+ cr.setContent(content);
+ configFilterChainManager.doFilter(null, cr);
+ content = cr.getContent();
+ return content;
+ }
+
+ try {
+ content = worker.getServerConfig(dataId, group, tenant, timeoutMs);
+ cr.setContent(content);
+ configFilterChainManager.doFilter(null, cr);
+ content = cr.getContent();
+ return content;
+ } catch (NacosException ioe) {
+ if (NacosException.NO_RIGHT == ioe.getErrCode()) {
+ throw ioe;
+ }
+ log.warn("NACOS-0003",
+ LoggerHelper.getErrorCodeStr("NACOS", "NACOS-0003", "环境问题", "get from server error"));
+ log.warn(agent.getName(), "[get-config] get from server error, dataId={}, group={}, tenant={}, msg={}",
+ dataId, group, tenant, ioe.toString());
+ }
+
+ log.warn(agent.getName(), "[get-config] get snapshot ok, dataId={}, group={}, tenant={}, config={}", dataId,
+ group, tenant, ContentUtils.truncateContent(content));
+ content = LocalConfigInfoProcessor.getSnapshot(agent.getName(), dataId, group, tenant);
+ cr.setContent(content);
+ configFilterChainManager.doFilter(null, cr);
+ content = cr.getContent();
+ return content;
+ }
+
+ private String null2defaultGroup(String group) {
+ return (null == group) ? Constants.DEFAULT_GROUP : group.trim();
+ }
+
+ private boolean removeConfigInner(String tenant, String dataId, String group, String tag) throws NacosException {
+ group = null2defaultGroup(group);
+ ParamUtils.checkKeyParam(dataId, group);
+ String url = Constants.CONFIG_CONTROLLER_PATH;
+ List params = new ArrayList();
+ params.add("dataId");
+ params.add(dataId);
+ params.add("group");
+ params.add(group);
+ if (StringUtils.isNotEmpty(tenant)) {
+ params.add("tenant");
+ params.add(tenant);
+ }
+ if (StringUtils.isNotEmpty(tag)) {
+ params.add("tag");
+ params.add(tag);
+ }
+ HttpResult result = null;
+ try {
+ result = agent.httpDelete(url, null, params, encode, POST_TIMEOUT);
+ } catch (IOException ioe) {
+ log.warn("[remove] error, " + dataId + ", " + group + ", " + tenant + ", msg: " + ioe.toString());
+ return false;
+ }
+
+ if (HttpURLConnection.HTTP_OK == result.code) {
+ log.info(agent.getName(), "[remove] ok, dataId={}, group={}, tenant={}", dataId, group, tenant);
+ return true;
+ } else if (HttpURLConnection.HTTP_FORBIDDEN == result.code) {
+ log.warn(agent.getName(), "[remove] error, dataId={}, group={}, tenant={}, code={}, msg={}", dataId, group,
+ tenant, result.code, result.content);
+ throw new NacosException(result.code, result.content);
+ } else {
+ log.warn(agent.getName(), "[remove] error, dataId={}, group={}, tenant={}, code={}, msg={}", dataId, group,
+ tenant, result.code, result.content);
+ return false;
+ }
+ }
+
+ private boolean publishConfigInner(String tenant, String dataId, String group, String tag, String appName,
+ String betaIps, String content) throws NacosException {
+ group = null2defaultGroup(group);
+ ParamUtils.checkParam(dataId, group, content);
+
+ ConfigRequest cr = new ConfigRequest();
+ cr.setDataId(dataId);
+ cr.setTenant(tenant);
+ cr.setGroup(group);
+ cr.setContent(content);
+ configFilterChainManager.doFilter(cr, null);
+ content = cr.getContent();
+
+ String url = Constants.CONFIG_CONTROLLER_PATH;
+ List params = new ArrayList();
+ params.add("dataId");
+ params.add(dataId);
+ params.add("group");
+ params.add(group);
+ params.add("content");
+ params.add(content);
+ if (StringUtils.isNotEmpty(tenant)) {
+ params.add("tenant");
+ params.add(tenant);
+ }
+ if (StringUtils.isNotEmpty(appName)) {
+ params.add("appName");
+ params.add(appName);
+ }
+ if (StringUtils.isNotEmpty(tag)) {
+ params.add("tag");
+ params.add(tag);
+ }
+
+ List headers = new ArrayList();
+ if (StringUtils.isNotEmpty(betaIps)) {
+ headers.add("betaIps");
+ headers.add(betaIps);
+ }
+
+ HttpResult result = null;
+ try {
+ result = agent.httpPost(url, headers, params, encode, POST_TIMEOUT);
+ } catch (IOException ioe) {
+ log.warn("NACOS-0006",
+ LoggerHelper.getErrorCodeStr("NACOS", "NACOS-0006", "环境问题", "[publish-single] exception"));
+ log.warn(agent.getName(), "[publish-single] exception, dataId={}, group={}, msg={}", dataId, group,
+ ioe.toString());
+ return false;
+ }
+
+ if (HttpURLConnection.HTTP_OK == result.code) {
+ log.info(agent.getName(), "[publish-single] ok, dataId={}, group={}, tenant={}, config={}", dataId, group,
+ tenant, ContentUtils.truncateContent(content));
+ return true;
+ } else if (HttpURLConnection.HTTP_FORBIDDEN == result.code) {
+ log.warn(agent.getName(), "[publish-single] error, dataId={}, group={}, tenant={}, code={}, msg={}", dataId,
+ group, tenant, result.code, result.content);
+ throw new NacosException(result.code, result.content);
+ } else {
+ log.warn(agent.getName(), "[publish-single] error, dataId={}, group={}, tenant={}, code={}, msg={}", dataId,
+ group, tenant, result.code, result.content);
+ return false;
+ }
+
+ }
+
+}
diff --git a/client/src/main/java/com/alibaba/nacos/client/config/common/Constants.java b/client/src/main/java/com/alibaba/nacos/client/config/common/Constants.java
new file mode 100644
index 00000000000..6fe35f92315
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/client/config/common/Constants.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.client.config.common;
+
+/**
+ * Constant
+ *
+ * @author Nacos
+ *
+ */
+public class Constants {
+
+ public static final String CLIENT_VERSION_HEADER = "Client-Version";
+
+ public static final String CLIENT_VERSION = "3.0.0";
+
+ public static int DATA_IN_BODY_VERSION = 204;
+
+ public static final String DEFAULT_GROUP = "DEFAULT_GROUP";
+
+ public static final String APPNAME = "AppName";
+
+ public static final String UNKNOWN_APP = "UnknownApp";
+
+ public static final String DEFAULT_DOMAINNAME = "commonconfig.config-host.taobao.com";
+
+ public static final String DAILY_DOMAINNAME = "commonconfig.taobao.net";
+
+ public static final int DEFAULT_PORT = 8080;
+
+ public static final String NULL = "";
+
+ public static final String DATAID = "dataId";
+
+ public static final String GROUP = "group";
+
+ public static final String LAST_MODIFIED = "Last-Modified";
+
+ public static final String ACCEPT_ENCODING = "Accept-Encoding";
+
+ public static final String CONTENT_ENCODING = "Content-Encoding";
+
+ public static final String PROBE_MODIFY_REQUEST = "Listening-Configs";
+
+ public static final String PROBE_MODIFY_RESPONSE = "Probe-Modify-Response";
+
+ public static final String PROBE_MODIFY_RESPONSE_NEW = "Probe-Modify-Response-New";
+
+ public static final String USE_ZIP = "true";
+
+ public static final String CONTENT_MD5 = "Content-MD5";
+
+ public static final String CONFIG_VERSION = "Config-Version";
+
+ public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
+
+ public static final String SPACING_INTERVAL = "client-spacing-interval";
+
+ public static final String BASE_PATH = "/v1/cs";
+
+ public static final String CONFIG_CONTROLLER_PATH = BASE_PATH + "/configs";
+
+ /**
+ * second
+ */
+ public static final int ASYNC_UPDATE_ADDRESS_INTERVAL = 300;
+
+ /**
+ * second
+ */
+ public static final int POLLING_INTERVAL_TIME = 15;
+
+ /**
+ * millisecond
+ */
+ public static final int ONCE_TIMEOUT = 2000;
+
+ /**
+ * millisecond
+ */
+ public static final int CONN_TIMEOUT = 2000;
+
+ /**
+ * millisecond
+ */
+ public static final int SO_TIMEOUT = 60000;
+
+ /**
+ * millisecond
+ */
+ public static final int RECV_WAIT_TIMEOUT = ONCE_TIMEOUT * 5;
+
+ public static final String ENCODE = "UTF-8";
+
+ public static final String MAP_FILE = "map-file.js";
+
+ public static final int FLOW_CONTROL_THRESHOLD = 20;
+
+ public static final int FLOW_CONTROL_SLOT = 10;
+
+ public static final int FLOW_CONTROL_INTERVAL = 1000;
+
+ public static final String LINE_SEPARATOR = Character.toString((char) 1);
+
+ public static final String WORD_SEPARATOR = Character.toString((char) 2);
+
+ public static final String LONGPULLING_LINE_SEPARATOR = "\r\n";
+
+ public static final String CLIENT_APPNAME_HEADER = "Client-AppName";
+ public static final String CLIENT_REQUEST_TS_HEADER = "Client-RequestTS";
+ public static final String CLIENT_REQUEST_TOKEN_HEADER = "Client-RequestToken";
+
+ public static final int ATOMIC_MAX_SIZE = 1000;
+}
diff --git a/client/src/main/java/com/alibaba/nacos/client/config/common/GroupKey.java b/client/src/main/java/com/alibaba/nacos/client/config/common/GroupKey.java
new file mode 100644
index 00000000000..fce61eddc38
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/client/config/common/GroupKey.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.client.config.common;
+
+import com.alibaba.nacos.client.utils.StringUtils;
+
+/**
+ * Synthesize the form of dataId+groupId. Escapes reserved characters in dataId
+ * and groupId.
+ *
+ * @author Nacos
+ */
+public class GroupKey {
+
+ static public String getKey(String dataId, String group) {
+ StringBuilder sb = new StringBuilder();
+ urlEncode(dataId, sb);
+ sb.append('+');
+ urlEncode(group, sb);
+ return sb.toString();
+ }
+
+ static public String getKeyTenant(String dataId, String group, String tenant) {
+ StringBuilder sb = new StringBuilder();
+ urlEncode(dataId, sb);
+ sb.append('+');
+ urlEncode(group, sb);
+ if (StringUtils.isNotEmpty(tenant)) {
+ sb.append('+');
+ urlEncode(tenant, sb);
+ }
+ return sb.toString();
+ }
+
+ static public String getKey(String dataId, String group, String datumStr) {
+ StringBuilder sb = new StringBuilder();
+ urlEncode(dataId, sb);
+ sb.append('+');
+ urlEncode(group, sb);
+ sb.append('+');
+ urlEncode(datumStr, sb);
+ return sb.toString();
+ }
+
+ static public String[] parseKey(String groupKey) {
+ StringBuilder sb = new StringBuilder();
+ String dataId = null;
+ String group = null;
+ String tenant = null;
+
+ for (int i = 0; i < groupKey.length(); ++i) {
+ char c = groupKey.charAt(i);
+ if ('+' == c) {
+ if (null == dataId) {
+ dataId = sb.toString();
+ sb.setLength(0);
+ } else if (null == group) {
+ group = sb.toString();
+ sb.setLength(0);
+ } else {
+ throw new IllegalArgumentException("invalid groupkey:" + groupKey);
+ }
+ } else if ('%' == c) {
+ char next = groupKey.charAt(++i);
+ char nextnext = groupKey.charAt(++i);
+ if ('2' == next && 'B' == nextnext) {
+ sb.append('+');
+ } else if ('2' == next && '5' == nextnext) {
+ sb.append('%');
+ } else {
+ throw new IllegalArgumentException("invalid groupkey:" + groupKey);
+ }
+ } else {
+ sb.append(c);
+ }
+ }
+
+ if (StringUtils.isBlank(group)) {
+ group = sb.toString();
+ if (group.length() == 0) {
+ throw new IllegalArgumentException("invalid groupkey:" + groupKey);
+ }
+ } else {
+ tenant = sb.toString();
+ if (group.length() == 0) {
+ throw new IllegalArgumentException("invalid groupkey:" + groupKey);
+ }
+ }
+
+ return new String[] { dataId, group, tenant };
+ }
+
+ /**
+ * + -> %2B
+ * % -> %25
+ */
+ static void urlEncode(String str, StringBuilder sb) {
+ for (int idx = 0; idx < str.length(); ++idx) {
+ char c = str.charAt(idx);
+ if ('+' == c) {
+ sb.append("%2B");
+ } else if ('%' == c) {
+ sb.append("%25");
+ } else {
+ sb.append(c);
+ }
+ }
+ }
+
+}
diff --git a/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigContext.java b/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigContext.java
new file mode 100644
index 00000000000..23c65f6ec3e
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigContext.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.client.config.filter.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.alibaba.nacos.api.config.filter.IConfigContext;
+
+/**
+ * Config Context
+ *
+ * @author Nacos
+ *
+ */
+public class ConfigContext implements IConfigContext {
+
+ private Map param = new HashMap();
+
+ @Override
+ public Object getParameter(String key) {
+ return param.get(key);
+ }
+
+ @Override
+ public void setParameter(String key, Object value) {
+ param.put(key, value);
+ }
+
+}
diff --git a/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigFilterChainManager.java b/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigFilterChainManager.java
new file mode 100644
index 00000000000..1566113ed80
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigFilterChainManager.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.client.config.filter.impl;
+
+import java.util.List;
+
+import com.alibaba.nacos.api.config.filter.IConfigFilter;
+import com.alibaba.nacos.api.config.filter.IConfigFilterChain;
+import com.alibaba.nacos.api.config.filter.IConfigRequest;
+import com.alibaba.nacos.api.config.filter.IConfigResponse;
+import com.alibaba.nacos.api.exception.NacosException;
+import com.google.common.collect.Lists;
+
+/**
+ * Config Filter Chain Management
+ *
+ * @author Nacos
+ *
+ */
+public class ConfigFilterChainManager implements IConfigFilterChain {
+
+ private List filters = Lists.newArrayList();
+
+ public synchronized ConfigFilterChainManager addFilter(IConfigFilter filter) {
+ // 根据order大小顺序插入
+ int i = 0;
+ while (i < this.filters.size()) {
+ IConfigFilter currentValue = this.filters.get(i);
+ if (currentValue.getFilterName().equals(filter.getFilterName())) {
+ break;
+ }
+ if (filter.getOrder() >= currentValue.getOrder() && i < this.filters.size()) {
+ i++;
+ } else {
+ this.filters.add(i, filter);
+ break;
+ }
+ }
+
+ if (i == this.filters.size()) {
+ this.filters.add(i, filter);
+ }
+ return this;
+ }
+
+
+ @Override
+ public void doFilter(IConfigRequest request, IConfigResponse response) throws NacosException {
+ new VirtualFilterChain(this.filters).doFilter(request, response);
+ }
+
+ private static class VirtualFilterChain implements IConfigFilterChain {
+
+ private final List extends IConfigFilter> additionalFilters;
+
+ private int currentPosition = 0;
+
+ public VirtualFilterChain(List extends IConfigFilter> additionalFilters) {
+ this.additionalFilters = additionalFilters;
+ }
+
+ @Override
+ public void doFilter(final IConfigRequest request, final IConfigResponse response) throws NacosException {
+ if (this.currentPosition == this.additionalFilters.size()) {
+ return;
+ } else {
+ this.currentPosition++;
+ IConfigFilter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
+ nextFilter.doFilter(request, response, this);
+ }
+ }
+ }
+
+}
diff --git a/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigRequest.java b/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigRequest.java
new file mode 100644
index 00000000000..100b10d39c2
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigRequest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.client.config.filter.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.alibaba.nacos.api.config.filter.IConfigContext;
+import com.alibaba.nacos.api.config.filter.IConfigRequest;
+
+/**
+ * Config Request
+ *
+ * @author Nacos
+ *
+ */
+public class ConfigRequest implements IConfigRequest {
+
+ private Map param = new HashMap();
+
+ private IConfigContext configContext = new ConfigContext();
+
+ public String getTenant() {
+ return (String) param.get("tenant");
+ }
+
+ public void setTenant(String tenant) {
+ param.put("tenant", tenant);
+ }
+
+ public String getDataId() {
+ return (String) param.get("dataId");
+ }
+
+ public void setDataId(String dataId) {
+ param.put("dataId", dataId);
+ }
+
+ public String getGroup() {
+ return (String) param.get("group");
+ }
+
+ public void setGroup(String group) {
+ param.put("group", group);
+ }
+
+ public String getContent() {
+ return (String) param.get("content");
+ }
+
+ public void setContent(String content) {
+ param.put("content", content);
+ }
+
+ @Override
+ public Object getParameter(String key) {
+ return param.get(key);
+ }
+
+ @Override
+ public IConfigContext getConfigContext() {
+ return configContext;
+ }
+
+}
diff --git a/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigResponse.java b/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigResponse.java
new file mode 100644
index 00000000000..c864331cc6b
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigResponse.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.client.config.filter.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.alibaba.nacos.api.config.filter.IConfigContext;
+import com.alibaba.nacos.api.config.filter.IConfigResponse;
+
+/**
+ * Config Response
+ *
+ * @author Nacos
+ *
+ */
+public class ConfigResponse implements IConfigResponse {
+
+ private Map param = new HashMap();
+
+ private IConfigContext configContext = new ConfigContext();
+
+ public String getTenant() {
+ return (String) param.get("tenant");
+ }
+
+ public void setTenant(String tenant) {
+ param.put("tenant", tenant);
+ }
+
+ public String getDataId() {
+ return (String) param.get("dataId");
+ }
+
+ public void setDataId(String dataId) {
+ param.put("dataId", dataId);
+ }
+
+ public String getGroup() {
+ return (String) param.get("group");
+ }
+
+ public void setGroup(String group) {
+ param.put("group", group);
+ }
+
+ public String getContent() {
+ return (String) param.get("content");
+ }
+
+ public void setContent(String content) {
+ param.put("content", content);
+ }
+
+ @Override
+ public Object getParameter(String key) {
+ return param.get(key);
+ }
+
+ @Override
+ public IConfigContext getConfigContext() {
+ return configContext;
+ }
+
+}
diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/CacheData.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/CacheData.java
new file mode 100644
index 00000000000..008c810205c
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/CacheData.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.client.config.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import com.alibaba.nacos.api.config.listener.AbstractSharedListener;
+import com.alibaba.nacos.api.config.listener.Listener;
+import com.alibaba.nacos.api.exception.NacosException;
+import com.alibaba.nacos.client.config.common.Constants;
+import com.alibaba.nacos.client.config.filter.impl.ConfigFilterChainManager;
+import com.alibaba.nacos.client.config.filter.impl.ConfigResponse;
+import com.alibaba.nacos.client.config.utils.LogUtils;
+import com.alibaba.nacos.client.config.utils.MD5;
+import com.alibaba.nacos.client.config.utils.TenantUtil;
+import com.alibaba.nacos.client.logger.Logger;
+
+/**
+ * Listner Management
+ *
+ * @author Nacos
+ *
+ */
+public class CacheData {
+
+ final static public Logger log = LogUtils.logger(CacheData.class);
+
+ public boolean isInitializing() {
+ return isInitializing;
+ }
+
+ public void setInitializing(boolean isInitializing) {
+ this.isInitializing = isInitializing;
+ }
+
+ public String getMd5() {
+ return md5;
+ }
+
+ public String getTenant() {
+ return tenant;
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public void setContent(String newContent) {
+ this.content = newContent;
+ this.md5 = getMd5String(content);
+ }
+
+ /**
+ * Add listener
+ *
+ * @param listener
+ * listener
+ */
+ public void addListener(Listener listener) {
+ if (null == listener) {
+ throw new IllegalArgumentException("listener is null");
+ }
+ ManagerListenerWrap wrap = new ManagerListenerWrap(listener);
+ if (listeners.addIfAbsent(wrap)) {
+ log.info(name, "[add-listener] ok, tenant={}, dataId={}, group={}, cnt={}", tenant, dataId, group,
+ listeners.size());
+ }
+ }
+
+ public void removeListener(Listener listener) {
+ if (null == listener) {
+ throw new IllegalArgumentException("listener is null");
+ }
+ ManagerListenerWrap wrap = new ManagerListenerWrap(listener);
+ if (listeners.remove(wrap)) {
+ log.info(name, "[remove-listener] ok, dataId={}, group={}, cnt={}", dataId, group, listeners.size());
+ }
+ }
+
+ /**
+ * 返回监听器列表上的迭代器,只读。保证不返回NULL。
+ */
+ public List getListeners() {
+ List result = new ArrayList();
+ for (ManagerListenerWrap wrap : listeners) {
+ result.add(wrap.listener);
+ }
+ return result;
+ }
+
+
+ public long getLocalConfigInfoVersion() {
+ return localConfigLastModified;
+ }
+ public void setLocalConfigInfoVersion(long localConfigLastModified) {
+ this.localConfigLastModified = localConfigLastModified;
+ }
+
+
+ public boolean isUseLocalConfigInfo() {
+ return isUseLocalConfig;
+ }
+ public void setUseLocalConfigInfo(boolean useLocalConfigInfo) {
+ this.isUseLocalConfig = useLocalConfigInfo;
+ if (!useLocalConfigInfo) {
+ localConfigLastModified = -1;
+ }
+ }
+
+ public int getTaskId() {
+ return taskId;
+ }
+
+ public void setTaskId(int taskId) {
+ this.taskId = taskId;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((dataId == null) ? 0 : dataId.hashCode());
+ result = prime * result + ((group == null) ? 0 : group.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (null == obj || obj.getClass() != getClass()) {
+ return false;
+ }
+ if (this == obj) {
+ return true;
+ }
+ CacheData other = (CacheData) obj;
+ return dataId.equals(other.dataId) && group.equals(other.group);
+ }
+
+ @Override
+ public String toString() {
+ return "CacheData [" + dataId + ", " + group + "]";
+ }
+
+ void checkListenerMd5() {
+ for (ManagerListenerWrap wrap : listeners) {
+ if (!md5.equals(wrap.lastCallMd5)) {
+ safeNotifyListener(dataId, group, content, md5, wrap);
+ }
+ }
+ }
+
+ private void safeNotifyListener(final String dataId, final String group, final String content,
+ final String md5, final ManagerListenerWrap listenerWrap) {
+ final Listener listener = listenerWrap.listener;
+
+ Runnable job = new Runnable() {
+ public void run() {
+ ClassLoader myClassLoader = Thread.currentThread().getContextClassLoader();
+ ClassLoader appClassLoader= listener.getClass().getClassLoader();
+ try {
+ if(listener instanceof AbstractSharedListener){
+ AbstractSharedListener adapter = (AbstractSharedListener) listener;
+ adapter.fillContext(dataId, group);
+ log.info(name, "[notify-context] dataId={}, group={}, md5={}", dataId, group, md5);
+ }
+ // 执行回调之前先将线程classloader设置为具体webapp的classloader,以免回调方法中调用spi接口是出现异常或错用(多应用部署才会有该问题)。
+ Thread.currentThread().setContextClassLoader(appClassLoader);
+
+ ConfigResponse cr = new ConfigResponse();
+ cr.setDataId(dataId);
+ cr.setGroup(group);
+ cr.setContent(content);
+ configFilterChainManager.doFilter(null, cr);
+ String contentTmp = cr.getContent();
+ listener.receiveConfigInfo(contentTmp);
+ listenerWrap.lastCallMd5 = md5;
+ log.info(
+ name,
+ "[notify-ok] dataId={}, group={}, md5={}, listener={} ",
+ dataId, group, md5, listener);
+ } catch (NacosException de) {
+ log.error(name, "NACOS-XXXX",
+ "[notify-error] dataId={}, group={}, md5={}, listener={} errCode={} errMsg={}", dataId,
+ group, md5, listener, de.getErrCode(), de.getErrMsg());
+ } catch (Throwable t) {
+ log.error(name, "NACOS-XXXX",
+ "[notify-error] dataId={}, group={}, md5={}, listener={} tx={}", dataId, group, md5,
+ listener, t.getCause());
+ }
+ finally
+ {
+ Thread.currentThread().setContextClassLoader(myClassLoader);
+ }
+ }
+ };
+
+ final long startNotify = System.currentTimeMillis();
+ try {
+ if (null != listener.getExecutor()) {
+ listener.getExecutor().execute(job);
+ } else {
+ job.run();
+ }
+ } catch (Throwable t) {
+ log.error(
+ name,
+ "NACOS-XXXX",
+ "[notify-error] dataId={}, group={}, md5={}, listener={} throwable={}",
+ dataId, group, md5, listener, t.getCause());
+ }
+ final long finishNotify = System.currentTimeMillis();
+ log.info(name, "[notify-listener] time cost={}ms in ClientWorker, dataId={}, group={}, md5={}, listener={} ",(finishNotify - startNotify), dataId ,group, md5, listener);
+ }
+
+ static public String getMd5String(String config) {
+ return (null == config) ? Constants.NULL : MD5.getInstance().getMD5String(config);
+ }
+
+ private String loadCacheContentFromDiskLocal(String name, String dataId, String group, String tenant) {
+ String content = LocalConfigInfoProcessor.getFailover(name, dataId, group, tenant);
+ content = (null != content) ? content
+ : LocalConfigInfoProcessor.getSnapshot(name, dataId, group, tenant);
+ return content;
+ }
+
+ public CacheData(ConfigFilterChainManager configFilterChainManager, String name, String dataId, String group) {
+ if (null == dataId || null == group) {
+ throw new IllegalArgumentException("dataId=" + dataId + ", group=" + group);
+ }
+ this.name = name;
+ this.configFilterChainManager = configFilterChainManager;
+ this.dataId = dataId;
+ this.group = group;
+ this.tenant = TenantUtil.getUserTenant();
+ listeners = new CopyOnWriteArrayList();
+ this.isInitializing = true;
+ this.content = loadCacheContentFromDiskLocal(name, dataId, group, tenant);
+ this.md5 = getMd5String(content);
+ }
+
+ public CacheData(ConfigFilterChainManager configFilterChainManager, String name, String dataId, String group, String tenant) {
+ if (null == dataId || null == group) {
+ throw new IllegalArgumentException("dataId=" + dataId + ", group=" + group);
+ }
+ this.name = name;
+ this.configFilterChainManager = configFilterChainManager;
+ this.dataId = dataId;
+ this.group = group;
+ this.tenant = tenant;
+ listeners = new CopyOnWriteArrayList();
+ this.isInitializing = true;
+ this.content = loadCacheContentFromDiskLocal(name, dataId, group, tenant);
+ this.md5 = getMd5String(content);
+ }
+
+ // ==================
+
+ private final String name;
+ private final ConfigFilterChainManager configFilterChainManager;
+ public final String dataId;
+ public final String group;
+ public final String tenant;
+ private final CopyOnWriteArrayList listeners;
+
+ private volatile String md5;
+ /**
+ * whether use local config
+ */
+ private volatile boolean isUseLocalConfig = false;
+ /**
+ * last motify time
+ */
+ private volatile long localConfigLastModified;
+ private volatile String content;
+ private int taskId;
+ private volatile boolean isInitializing = true;
+}
+
+class ManagerListenerWrap {
+ final Listener listener;
+ String lastCallMd5 = CacheData.getMd5String(null);
+
+ ManagerListenerWrap(Listener listener) {
+ this.listener = listener;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (null == obj || obj.getClass() != getClass()) {
+ return false;
+ }
+ if (obj == this) {
+ return true;
+ }
+ ManagerListenerWrap other = (ManagerListenerWrap) obj;
+ return listener.equals(other.listener);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode();
+ }
+
+}
diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java
new file mode 100644
index 00000000000..5f60a81fc51
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java
@@ -0,0 +1,541 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.client.config.impl;
+
+import static com.alibaba.nacos.client.config.common.Constants.LINE_SEPARATOR;
+import static com.alibaba.nacos.client.config.common.Constants.WORD_SEPARATOR;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import com.alibaba.nacos.api.config.listener.Listener;
+import com.alibaba.nacos.api.exception.NacosException;
+import com.alibaba.nacos.client.config.common.Constants;
+import com.alibaba.nacos.client.config.common.GroupKey;
+import com.alibaba.nacos.client.config.filter.impl.ConfigFilterChainManager;
+import com.alibaba.nacos.client.config.impl.HttpSimpleClient.HttpResult;
+import com.alibaba.nacos.client.config.utils.ContentUtils;
+import com.alibaba.nacos.client.config.utils.LogUtils;
+import com.alibaba.nacos.client.config.utils.MD5;
+import com.alibaba.nacos.client.config.utils.TenantUtil;
+import com.alibaba.nacos.client.logger.Logger;
+import com.alibaba.nacos.client.logger.support.LoggerHelper;
+import com.alibaba.nacos.client.utils.ParamUtil;
+import com.alibaba.nacos.client.utils.StringUtils;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * Longpulling
+ *
+ * @author Nacos
+ *
+ */
+public class ClientWorker {
+
+ final static public Logger log = LogUtils.logger(ClientWorker.class);
+
+ public void addListeners(String dataId, String group, List extends Listener> listeners) {
+ group = null2defaultGroup(group);
+ CacheData cache = addCacheDataIfAbsent(dataId, group);
+ for (Listener listener : listeners) {
+ cache.addListener(listener);
+ }
+ }
+
+ public void removeListener(String dataId, String group, Listener listener) {
+ group = null2defaultGroup(group);
+ CacheData cache = getCache(dataId, group);
+ if (null != cache) {
+ cache.removeListener(listener);
+ if (cache.getListeners().isEmpty()) {
+ removeCache(dataId, group);
+ }
+ }
+ }
+
+ public void addTenantListeners(String dataId, String group, List extends Listener> listeners) {
+ group = null2defaultGroup(group);
+ String tenant = agent.getTenant();
+ CacheData cache = addCacheDataIfAbsent(dataId, group, tenant);
+ for (Listener listener : listeners) {
+ cache.addListener(listener);
+ }
+ }
+
+ public void removeTenantListener(String dataId, String group, Listener listener) {
+ group = null2defaultGroup(group);
+ String tenant = agent.getTenant();
+ CacheData cache = getCache(dataId, group, tenant);
+ if (null != cache) {
+ cache.removeListener(listener);
+ if (cache.getListeners().isEmpty()) {
+ removeCache(dataId, group, tenant);
+ }
+ }
+ }
+
+ @SuppressFBWarnings("JLM_JSR166_UTILCONCURRENT_MONITORENTER")
+ void removeCache(String dataId, String group) {
+ String groupKey = GroupKey.getKey(dataId, group);
+ synchronized (cacheMap) {
+ Map copy = new HashMap(cacheMap.get());
+ copy.remove(groupKey);
+ cacheMap.set(copy);
+ }
+ log.info(agent.getName(), "[unsubscribe] {}", groupKey);
+ }
+
+ @SuppressFBWarnings("JLM_JSR166_UTILCONCURRENT_MONITORENTER")
+ void removeCache(String dataId, String group, String tenant) {
+ String groupKey = GroupKey.getKeyTenant(dataId, group, tenant);
+ synchronized (cacheMap) {
+ Map copy = new HashMap(cacheMap.get());
+ copy.remove(groupKey);
+ cacheMap.set(copy);
+ }
+ log.info(agent.getName(), "[unsubscribe] {}", groupKey);
+ }
+
+ @SuppressFBWarnings("JLM_JSR166_UTILCONCURRENT_MONITORENTER")
+ public CacheData addCacheDataIfAbsent(String dataId, String group) {
+ CacheData cache = getCache(dataId, group);
+ if (null != cache) {
+ return cache;
+ }
+
+ String key = GroupKey.getKey(dataId, group);
+ cache = new CacheData(configFilterChainManager, agent.getName(), dataId, group);
+
+ synchronized (cacheMap) {
+ CacheData cacheFromMap = getCache(dataId, group);
+ // multiple listeners on the same dataid+group and race condition,so double check again
+ //other listener thread beat me to set to cacheMap
+ if(null != cacheFromMap) {
+ cache = cacheFromMap;
+ //reset so that server not hang this check
+ cache.setInitializing(true);
+ } else {
+ int taskId = cacheMap.get().size() / (int) ParamUtil.getPerTaskConfigSize();
+ cache.setTaskId(taskId);
+ }
+
+ Map copy = new HashMap(cacheMap.get());
+ copy.put(key, cache);
+ cacheMap.set(copy);
+ }
+
+ log.info(agent.getName(), "[subscribe] {}", key);
+
+ return cache;
+ }
+ @SuppressFBWarnings("JLM_JSR166_UTILCONCURRENT_MONITORENTER")
+ public CacheData addCacheDataIfAbsent(String dataId, String group, String tenant) {
+ CacheData cache = getCache(dataId, group, tenant);
+ if (null != cache) {
+ return cache;
+ }
+ String key = GroupKey.getKeyTenant(dataId, group, tenant);
+ cache = new CacheData(configFilterChainManager, agent.getName(), dataId, group, tenant);
+ synchronized (cacheMap) {
+ CacheData cacheFromMap = getCache(dataId, group, tenant);
+ // multiple listeners on the same dataid+group and race condition,so
+ // double check again
+ // other listener thread beat me to set to cacheMap
+ if (null != cacheFromMap) {
+ cache = cacheFromMap;
+ // reset so that server not hang this check
+ cache.setInitializing(true);
+ }
+
+ Map copy = new HashMap(cacheMap.get());
+ copy.put(key, cache);
+ cacheMap.set(copy);
+ }
+ log.info(agent.getName(), "[subscribe] {}", key);
+ return cache;
+ }
+
+ public CacheData getCache(String dataId, String group) {
+ return getCache(dataId, group, TenantUtil.getUserTenant());
+ }
+
+ public CacheData getCache(String dataId, String group, String tenant) {
+ if (null == dataId || null == group) {
+ throw new IllegalArgumentException();
+ }
+ return cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant));
+ }
+
+
+ public String getServerConfig(String dataId, String group, String tenant, long readTimeout)
+ throws NacosException {
+ if (StringUtils.isBlank(group)) {
+ group = Constants.DEFAULT_GROUP;
+ }
+
+ HttpResult result = null;
+ try {
+ List params = null;
+ if (StringUtils.isBlank(tenant)) {
+ params = Arrays.asList("dataId", dataId, "group", group);
+ } else {
+ params = Arrays.asList("dataId", dataId, "group", group, "tenant", tenant);
+ }
+ result = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout);
+ } catch (IOException e) {
+ log.error(agent.getName(), "NACOS-XXXX",
+ "[sub-server] get server config exception, dataId={}, group={}, tenant={}, msg={}", dataId, group,
+ tenant, e.toString());
+ throw new NacosException(NacosException.SERVER_ERROR, e.getMessage());
+ }
+
+ switch (result.code) {
+ case HttpURLConnection.HTTP_OK:
+ LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, result.content);
+ return result.content;
+ case HttpURLConnection.HTTP_NOT_FOUND:
+ LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, null);
+ return null;
+ case HttpURLConnection.HTTP_CONFLICT: {
+ log.error(agent.getName(), "NACOS-XXXX",
+ "[sub-server-error] get server config being modified concurrently, dataId={}, group={}, tenant={}",
+ dataId, group, tenant);
+ throw new NacosException(NacosException.CONFLICT,
+ "data being modified, dataId=" + dataId + ",group=" + group + ",tenant=" + tenant);
+ }
+ case HttpURLConnection.HTTP_FORBIDDEN: {
+ log.error(agent.getName(), "NACOS-XXXX", "[sub-server-error] no right, dataId={}, group={}, tenant={}",
+ dataId, group, tenant);
+ throw new NacosException(result.code, result.content);
+ }
+ default: {
+ log.error(agent.getName(), "NACOS-XXXX", "[sub-server-error] dataId={}, group={}, tenant={}, code={}",
+ dataId, group, tenant, result.code);
+ throw new NacosException(result.code,
+ "http error, code=" + result.code + ",dataId=" + dataId + ",group=" + group + ",tenant=" + tenant);
+ }
+ }
+ }
+
+ private void checkLocalConfig(CacheData cacheData) {
+ final String dataId = cacheData.dataId;
+ final String group = cacheData.group;
+ final String tenant = cacheData.tenant;
+ File path = LocalConfigInfoProcessor.getFailoverFile(agent.getName(), dataId, group, tenant);
+
+ // 没有 -> 有
+ if (!cacheData.isUseLocalConfigInfo() && path.exists()) {
+ String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
+ String md5 = MD5.getInstance().getMD5String(content);
+ cacheData.setUseLocalConfigInfo(true);
+ cacheData.setLocalConfigInfoVersion(path.lastModified());
+ cacheData.setContent(content);
+
+ log.warn(agent.getName(),
+ "[failover-change] failover file created. dataId={}, group={}, tenant={}, md5={}, content={}",
+ dataId, group, tenant, md5, ContentUtils.truncateContent(content));
+ return;
+ }
+
+ // 有 -> 没有。不通知业务监听器,从server拿到配置后通知。
+ if (cacheData.isUseLocalConfigInfo() && !path.exists()) {
+ cacheData.setUseLocalConfigInfo(false);
+ log.warn(agent.getName(), "[failover-change] failover file deleted. dataId={}, group={}, tenant={}", dataId,
+ group, tenant);
+ return;
+ }
+
+ // 有变更
+ if (cacheData.isUseLocalConfigInfo() && path.exists()
+ && cacheData.getLocalConfigInfoVersion() != path.lastModified()) {
+ String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
+ String md5 = MD5.getInstance().getMD5String(content);
+ cacheData.setUseLocalConfigInfo(true);
+ cacheData.setLocalConfigInfoVersion(path.lastModified());
+ cacheData.setContent(content);
+ log.warn(agent.getName(),
+ "[failover-change] failover file changed. dataId={}, group={}, tenant={}, md5={}, content={}",
+ dataId, group, tenant, md5, ContentUtils.truncateContent(content));
+ return;
+ }
+ }
+
+ private String null2defaultGroup(String group) {
+ return (null == group) ? Constants.DEFAULT_GROUP : group.trim();
+ }
+
+ public void checkConfigInfo() {
+ // 分任务
+ int listenerSize = cacheMap.get().size();
+ // 向上取整为批数
+ int longingTaskCount = (int) Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize());
+ if (longingTaskCount > currentLongingTaskCount) {
+ for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) {
+ // 要判断任务是否在执行 这块需要好好想想。 任务列表现在是无序的。变化过程可能有问题
+ executorService.execute(new LongPullingRunnable(i));
+ }
+ currentLongingTaskCount = longingTaskCount;
+ }
+ }
+
+ /**
+ * 从Server获取值变化了的DataID列表。返回的对象里只有dataId和group是有效的。 保证不返回NULL。
+ */
+ List checkUpdateDataIds(List cacheDatas, List inInitializingCacheList) {
+ StringBuilder sb = new StringBuilder();
+ for (CacheData cacheData : cacheDatas) {
+ if (!cacheData.isUseLocalConfigInfo()) {
+ sb.append(cacheData.dataId).append(WORD_SEPARATOR);
+ sb.append(cacheData.group).append(WORD_SEPARATOR);
+ if (StringUtils.isBlank(cacheData.tenant)) {
+ sb.append(cacheData.getMd5()).append(LINE_SEPARATOR);
+ } else {
+ sb.append(cacheData.getMd5()).append(WORD_SEPARATOR);
+ sb.append(cacheData.getTenant()).append(LINE_SEPARATOR);
+ }
+ if (cacheData.isInitializing()) {
+ // cacheData 首次出现在cacheMap中&首次check更新
+ inInitializingCacheList
+ .add(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant));
+ }
+ }
+ }
+ boolean isInitializingCacheList = !inInitializingCacheList.isEmpty();
+ return checkUpdateConfigStr(sb.toString(), isInitializingCacheList);
+ }
+
+ /**
+ * 从Server获取值变化了的DataID列表。返回的对象里只有dataId和group是有效的。 保证不返回NULL。
+ */
+ List checkUpdateConfigStr(String probeUpdateString, boolean isInitializingCacheList) {
+
+ List params = Arrays.asList(Constants.PROBE_MODIFY_REQUEST, probeUpdateString);
+ long timeout = TimeUnit.SECONDS.toMillis(30L);
+
+ List headers = new ArrayList(2);
+ headers.add("Long-Pulling-Timeout");
+ headers.add("" + timeout);
+
+ // told server do not hang me up if new initializing cacheData added in
+ if (isInitializingCacheList) {
+ headers.add("Long-Pulling-Timeout-No-Hangup");
+ headers.add("true");
+ }
+
+ if (StringUtils.isBlank(probeUpdateString)) {
+ return Collections.emptyList();
+ }
+
+ try {
+ HttpResult result = agent.httpPost(Constants.CONFIG_CONTROLLER_PATH + "/listener", headers, params,
+ agent.getEncode(), timeout);
+
+ if (HttpURLConnection.HTTP_OK == result.code) {
+ setHealthServer(true);
+ return parseUpdateDataIdResponse(result.content);
+ } else {
+ setHealthServer(false);
+ if (result.code == HttpURLConnection.HTTP_INTERNAL_ERROR) {
+ log.error("NACOS-0007", LoggerHelper.getErrorCodeStr("Nacos", "Nacos-0007", "环境问题",
+ "[check-update] get changed dataId error"));
+ }
+ log.error(agent.getName(), "NACOS-XXXX", "[check-update] get changed dataId error, code={}",
+ result.code);
+ }
+ } catch (IOException e) {
+ setHealthServer(false);
+ log.error(agent.getName(), "NACOS-XXXX", "[check-update] get changed dataId exception, msg={}",
+ e.toString());
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * 从HTTP响应拿到变化的groupKey。保证不返回NULL。
+ */
+ private List parseUpdateDataIdResponse(String response) {
+ if (StringUtils.isBlank(response)) {
+ return Collections.emptyList();
+ }
+
+ try {
+ response = URLDecoder.decode(response, "UTF-8");
+ } catch (Exception e) {
+ log.error(agent.getName(), "NACOS-XXXX", "[polling-resp] decode modifiedDataIdsString error", e);
+ }
+
+ List updateList = new LinkedList();
+
+ for (String dataIdAndGroup : response.split(LINE_SEPARATOR)) {
+ if (!StringUtils.isBlank(dataIdAndGroup)) {
+ String[] keyArr = dataIdAndGroup.split(WORD_SEPARATOR);
+ String dataId = keyArr[0];
+ String group = keyArr[1];
+ if (keyArr.length == 2) {
+ updateList.add(GroupKey.getKey(dataId, group));
+ log.info(agent.getName(), "[polling-resp] config changed. dataId={}, group={}", dataId, group);
+ } else if (keyArr.length == 3) {
+ String tenant = keyArr[2];
+ updateList.add(GroupKey.getKeyTenant(dataId, group, tenant));
+ log.info(agent.getName(), "[polling-resp] config changed. dataId={}, group={}, tenant={}", dataId,
+ group, tenant);
+ } else {
+ log.error(agent.getName(), "NACOS-XXXX", "[polling-resp] invalid dataIdAndGroup error",
+ dataIdAndGroup);
+ }
+ }
+ }
+ return updateList;
+ }
+
+ @SuppressWarnings("PMD.ThreadPoolCreationRule")
+ public ClientWorker(final ServerHttpAgent agent, final ConfigFilterChainManager configFilterChainManager) {
+ this.agent = agent;
+ this.configFilterChainManager = configFilterChainManager;
+ executor = Executors.newScheduledThreadPool(1, new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r);
+ t.setName("com.alibaba.nacos.client.Worker." + agent.getName());
+ t.setDaemon(true);
+ return t;
+ }
+ });
+
+ executorService = Executors.newCachedThreadPool(new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r);
+ t.setName("com.alibaba.nacos.client.Worker.longPulling" + agent.getName());
+ t.setDaemon(true);
+ return t;
+ }
+ });
+
+ executor.scheduleWithFixedDelay(new Runnable() {
+ public void run() {
+ try {
+ checkConfigInfo();
+ } catch (Throwable e) {
+ log.error(agent.getName(), "NACOS-XXXX", "[sub-check] rotate check error", e);
+ }
+ }
+ }, 1L, 10L, TimeUnit.MILLISECONDS);
+ }
+
+ class LongPullingRunnable implements Runnable {
+ private int taskId;
+
+ public LongPullingRunnable(int taskId) {
+ this.taskId = taskId;
+ }
+
+ public void run() {
+ try {
+ List cacheDatas = new ArrayList();
+ // check failover config
+ for (CacheData cacheData : cacheMap.get().values()) {
+ if (cacheData.getTaskId() == taskId) {
+ cacheDatas.add(cacheData);
+ try {
+ checkLocalConfig(cacheData);
+ if (cacheData.isUseLocalConfigInfo()) {
+ cacheData.checkListenerMd5();
+ }
+ } catch (Exception e) {
+ log.error("NACOS-CLIENT", "get local config info error", e);
+ }
+ }
+ }
+
+ List inInitializingCacheList = new ArrayList();
+ // check server config
+ List changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);
+
+ for (String groupKey : changedGroupKeys) {
+ String[] key = GroupKey.parseKey(groupKey);
+ String dataId = key[0];
+ String group = key[1];
+ String tenant = null;
+ if (key.length == 3) {
+ tenant = key[2];
+ }
+ try {
+ String content = getServerConfig(dataId, group, tenant, 3000L);
+ CacheData cache = cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant));
+ cache.setContent(content);
+ log.info(agent.getName(), "[data-received] dataId={}, group={}, tenant={}, md5={}, content={}",
+ dataId, group, tenant, cache.getMd5(), ContentUtils.truncateContent(content));
+ } catch (NacosException ioe) {
+ log.error(agent.getName(), "NACOS-XXXX",
+ "[get-update] get changed config exception. dataId={}, group={}, tenant={}, msg={}",
+ dataId, group, tenant, ioe.toString());
+ }
+ }
+ for (CacheData cacheData : cacheDatas) {
+ if (!cacheData.isInitializing() || inInitializingCacheList
+ .contains(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant))) {
+ cacheData.checkListenerMd5();
+ cacheData.setInitializing(false);
+ }
+ }
+ inInitializingCacheList.clear();
+ } catch (Throwable e) {
+ log.error("500", "longPulling error", e);
+ } finally {
+ executorService.execute(this);
+ }
+ }
+ }
+
+ // =================
+
+ public boolean isHealthServer() {
+ return isHealthServer;
+ }
+
+ private void setHealthServer(boolean isHealthServer) {
+ this.isHealthServer = isHealthServer;
+ }
+
+ final ScheduledExecutorService executor;
+ final ExecutorService executorService;
+ /**
+ * groupKey -> cacheData
+ */
+ AtomicReference
+*
+* Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode
+* character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc).
+*
+*
+* This class is not thread-safe. Each thread should use its own instance.
+*
+*
+* @see RFC 2045
+* @author Apache Software Foundation
+* @since 1.0
+* @version $Revision: 1080712 $
+*/
+public class Base64 {
+
+ /**
+ * BASE32 characters are 6 bits in length.
+ * They are formed by taking a block of 3 octets to form a 24-bit string,
+ * which is converted into 4 BASE64 characters.
+ */
+ private static final int BITS_PER_ENCODED_BYTE = 6;
+ private static final int BYTES_PER_UNENCODED_BLOCK = 3;
+ private static final int BYTES_PER_ENCODED_BLOCK = 4;
+
+ /**
+ * Chunk separator per RFC 2045 section 2.1.
+ *
+ *
+ * N.B. The next major release may break compatibility and make this field private.
+ *
+ *
+ * @see RFC 2045 section 2.1
+ */
+ static final byte[] CHUNK_SEPARATOR = {'\r', '\n'};
+
+ /**
+ * This array is a lookup table that translates 6-bit positive integer index values into their "Base64 Alphabet"
+ * equivalents as specified in Table 1 of RFC 2045.
+ *
+ * Thanks to "commons" project in ws.apache.org for this code.
+ * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
+ */
+ private static final byte[] STANDARD_ENCODE_TABLE = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
+ };
+
+ /**
+ * This is a copy of the STANDARD_ENCODE_TABLE above, but with + and /
+ * changed to - and _ to make the encoded Base64 results more URL-SAFE.
+ * This table is only used when the Base64's mode is set to URL-SAFE.
+ */
+ private static final byte[] URL_SAFE_ENCODE_TABLE = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
+ };
+
+ /**
+ * This array is a lookup table that translates Unicode characters drawn from the "Base64 Alphabet" (as specified in
+ * Table 1 of RFC 2045) into their 6-bit positive integer equivalents. Characters that are not in the Base64
+ * alphabet but fall within the bounds of the array are translated to -1.
+ *
+ * Note: '+' and '-' both decode to 62. '/' and '_' both decode to 63. This means decoder seamlessly handles both
+ * URL_SAFE and STANDARD base64. (The encoder, on the other hand, needs to know ahead of time what to emit).
+ *
+ * Thanks to "commons" project in ws.apache.org for this code.
+ * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
+ */
+ private static final byte[] DECODE_TABLE = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, 52, 53, 54,
+ 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34,
+ 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
+ };
+
+ /**
+ * Base64 uses 6-bit fields.
+ */
+ /** Mask used to extract 6 bits, used when encoding */
+ private static final int MASK_6BITS = 0x3f;
+
+ // The static final fields above are used for the original static byte[] methods on Base64.
+ // The private member fields below are used with the new streaming approach, which requires
+ // some state be preserved between calls of encode() and decode().
+
+ /**
+ * Encode table to use: either STANDARD or URL_SAFE. Note: the DECODE_TABLE above remains static because it is able
+ * to decode both STANDARD and URL_SAFE streams, but the encodeTable must be a member variable so we can switch
+ * between the two modes.
+ */
+ private final byte[] encodeTable;
+
+ /**
+ * Only one decode table currently; keep for consistency with Base32 code
+ */
+ private final byte[] decodeTable = DECODE_TABLE;
+
+ /**
+ * Line separator for encoding. Not used when decoding. Only used if lineLength > 0.
+ */
+ private final byte[] lineSeparator;
+
+ /**
+ * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing.
+ * decodeSize = 3 + lineSeparator.length;
+ */
+ private final int decodeSize;
+
+ /**
+ * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing.
+ * encodeSize = 4 + lineSeparator.length;
+ */
+ private final int encodeSize;
+
+ /**
+ * Place holder for the bytes we're dealing with for our based logic.
+ * Bitwise operations store and extract the encoding or decoding from this variable.
+ */
+ private int bitWorkArea;
+
+ /**
+ * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.
+ *
+ * When encoding the line length is 0 (no chunking), and the encoding table is STANDARD_ENCODE_TABLE.
+ *
+ *
+ *
+ * When decoding all variants are supported.
+ *
+ */
+ public Base64() {
+ this(0, CHUNK_SEPARATOR, false);
+ }
+
+ /**
+ * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.
+ *
+ * When encoding the line length and line separator are given in the constructor, and the encoding table is
+ * STANDARD_ENCODE_TABLE.
+ *
+ *
+ * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data.
+ *
+ *
+ * When decoding all variants are supported.
+ *
+ *
+ * @param lineLength
+ * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of 4).
+ * If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when decoding.
+ * @param lineSeparator
+ * Each line of encoded data will end with this sequence of bytes.
+ * @param urlSafe
+ * Instead of emitting '+' and '/' we emit '-' and '_' respectively. urlSafe is only applied to encode
+ * operations. Decoding seamlessly handles both modes.
+ * @throws IllegalArgumentException
+ * The provided lineSeparator included some base64 characters. That's not going to work!
+ * @since 1.4
+ */
+ public Base64(int lineLength, byte[] lineSeparator, boolean urlSafe) {
+ chunkSeparatorLength = lineSeparator == null ? 0 : lineSeparator.length;
+ unencodedBlockSize = BYTES_PER_UNENCODED_BLOCK;
+ encodedBlockSize = BYTES_PER_ENCODED_BLOCK;
+ this.lineLength = (lineLength > 0 && chunkSeparatorLength > 0) ? (lineLength / encodedBlockSize) * encodedBlockSize : 0;
+ // TODO could be simplified if there is no requirement to reject invalid line sep when length <=0
+ // @see test case Base64Test.testConstructors()
+ if (lineSeparator != null) {
+ if (containsAlphabetOrPad(lineSeparator)) {
+ String sep = null;
+ try {
+ sep = new String(lineSeparator, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ }
+ throw new IllegalArgumentException("lineSeparator must not contain base64 characters: [" + sep + "]");
+ }
+ if (lineLength > 0){
+ this.encodeSize = BYTES_PER_ENCODED_BLOCK + lineSeparator.length;
+ this.lineSeparator = new byte[lineSeparator.length];
+ System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length);
+ } else {
+ this.encodeSize = BYTES_PER_ENCODED_BLOCK;
+ this.lineSeparator = null;
+ }
+ } else {
+ this.encodeSize = BYTES_PER_ENCODED_BLOCK;
+ this.lineSeparator = null;
+ }
+ this.decodeSize = this.encodeSize - 1;
+ this.encodeTable = urlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE;
+ }
+
+ /**
+ *
+ * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with
+ * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, so flush last
+ * remaining bytes (if not multiple of 3).
+ *
+ *
+ * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach.
+ * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
+ *
+ *
+ * @param in
+ * byte[] array of binary data to base64 encode.
+ * @param inPos
+ * Position to start reading data from.
+ * @param inAvail
+ * Amount of bytes available from input for encoding.
+ */
+ void encode(byte[] in, int inPos, int inAvail) {
+ if (eof) {
+ return;
+ }
+ if (inAvail < 0) {
+ eof = true;
+ if (0 == modulus && lineLength == 0) {
+ return;
+ }
+ ensureBufferSize(encodeSize);
+ int savedPos = pos;
+ switch (modulus) {
+ case 1 :
+ buffer[pos++] = encodeTable[(bitWorkArea >> 2) & MASK_6BITS];
+ buffer[pos++] = encodeTable[(bitWorkArea << 4) & MASK_6BITS];
+
+ if (encodeTable == STANDARD_ENCODE_TABLE) {
+ buffer[pos++] = PAD;
+ buffer[pos++] = PAD;
+ }
+ break;
+
+ case 2 :
+ buffer[pos++] = encodeTable[(bitWorkArea >> 10) & MASK_6BITS];
+ buffer[pos++] = encodeTable[(bitWorkArea >> 4) & MASK_6BITS];
+ buffer[pos++] = encodeTable[(bitWorkArea << 2) & MASK_6BITS];
+
+ if (encodeTable == STANDARD_ENCODE_TABLE) {
+ buffer[pos++] = PAD;
+ }
+ break;
+ default:
+ break;
+ }
+ currentLinePos += pos - savedPos;
+ /**
+ * if currentPos == 0 we are at the start of a line, so don't add CRLF
+ */
+ if (lineLength > 0 && currentLinePos > 0) {
+ System.arraycopy(lineSeparator, 0, buffer, pos, lineSeparator.length);
+ pos += lineSeparator.length;
+ }
+ } else {
+ for (int i = 0; i < inAvail; i++) {
+ ensureBufferSize(encodeSize);
+ modulus = (modulus+1) % BYTES_PER_UNENCODED_BLOCK;
+ int b = in[inPos++];
+ if (b < 0) {
+ b += 256;
+ }
+ bitWorkArea = (bitWorkArea << 8) + b;
+ if (0 == modulus) {
+ buffer[pos++] = encodeTable[(bitWorkArea >> 18) & MASK_6BITS];
+ buffer[pos++] = encodeTable[(bitWorkArea >> 12) & MASK_6BITS];
+ buffer[pos++] = encodeTable[(bitWorkArea >> 6) & MASK_6BITS];
+ buffer[pos++] = encodeTable[bitWorkArea & MASK_6BITS];
+ currentLinePos += BYTES_PER_ENCODED_BLOCK;
+ if (lineLength > 0 && lineLength <= currentLinePos) {
+ System.arraycopy(lineSeparator, 0, buffer, pos, lineSeparator.length);
+ pos += lineSeparator.length;
+ currentLinePos = 0;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ *
+ * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once
+ * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1"
+ * call is not necessary when decoding, but it doesn't hurt, either.
+ *
+ *
+ * Ignores all non-base64 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are
+ * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in,
+ * garbage-out philosophy: it will not check the provided data for validity.
+ *
+ *
+ * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach.
+ * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
+ *
+ *
+ * @param in
+ * byte[] array of ascii data to base64 decode.
+ * @param inPos
+ * Position to start reading data from.
+ * @param inAvail
+ * Amount of bytes available from input for encoding.
+ */
+ void decode(byte[] in, int inPos, int inAvail) {
+ if (eof) {
+ return;
+ }
+ if (inAvail < 0) {
+ eof = true;
+ }
+ for (int i = 0; i < inAvail; i++) {
+ ensureBufferSize(decodeSize);
+ byte b = in[inPos++];
+ if (b == PAD) {
+ // We're done.
+ eof = true;
+ break;
+ } else {
+ if (b >= 0 && b < DECODE_TABLE.length) {
+ int result = DECODE_TABLE[b];
+ if (result >= 0) {
+ modulus = (modulus+1) % BYTES_PER_ENCODED_BLOCK;
+ bitWorkArea = (bitWorkArea << BITS_PER_ENCODED_BYTE) + result;
+ if (modulus == 0) {
+ buffer[pos++] = (byte) ((bitWorkArea >> 16) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea >> 8) & MASK_8BITS);
+ buffer[pos++] = (byte) (bitWorkArea & MASK_8BITS);
+ }
+ }
+ }
+ }
+ }
+
+ // Two forms of EOF as far as base64 decoder is concerned: actual
+ // EOF (-1) and first time '=' character is encountered in stream.
+ // This approach makes the '=' padding characters completely optional.
+ if (eof && modulus != 0) {
+ ensureBufferSize(decodeSize);
+
+ // We have some spare bits remaining
+ // Output all whole multiples of 8 bits and ignore the rest
+ switch (modulus) {
+ // case 1: // 6 bits - ignore entirely
+ // break;
+ case 2 :
+ bitWorkArea = bitWorkArea >> 4;
+ buffer[pos++] = (byte) ((bitWorkArea) & MASK_8BITS);
+ break;
+ case 3 :
+ bitWorkArea = bitWorkArea >> 2;
+ buffer[pos++] = (byte) ((bitWorkArea >> 8) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea) & MASK_8BITS);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * Encodes binary data using the base64 algorithm but does not chunk the output.
+ *
+ * @param binaryData
+ * binary data to encode
+ * @return byte[] containing Base64 characters in their UTF-8 representation.
+ */
+ public static byte[] encodeBase64(byte[] binaryData) {
+ return encodeBase64(binaryData, false, false, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks.
+ *
+ * @param binaryData
+ * Array containing binary data to encode.
+ * @param isChunked
+ * if true this encoder will chunk the base64 output into 76 character blocks
+ * @param urlSafe
+ * if true this encoder will emit - and _ instead of the usual + and / characters.
+ * @param maxResultSize
+ * The maximum result size to accept.
+ * @return Base64-encoded data.
+ * @throws IllegalArgumentException
+ * Thrown when the input array needs an output array bigger than maxResultSize
+ * @since 1.4
+ */
+ public static byte[] encodeBase64(byte[] binaryData, boolean isChunked, boolean urlSafe, int maxResultSize) {
+ if (binaryData == null || binaryData.length == 0) {
+ return binaryData;
+ }
+
+ // Create this so can use the super-class method
+ // Also ensures that the same roundings are performed by the ctor and the code
+ Base64 b64 = isChunked ? new Base64(MIME_CHUNK_SIZE, CHUNK_SEPARATOR, urlSafe) : new Base64(0, CHUNK_SEPARATOR, urlSafe);
+ long len = b64.getEncodedLength(binaryData);
+ if (len > maxResultSize) {
+ throw new IllegalArgumentException("Input array too big, the output array would be bigger (" +
+ len +
+ ") than the specified maximum size of " +
+ maxResultSize);
+ }
+
+ return b64.encode(binaryData);
+ }
+
+
+
+ /**
+ * Decodes Base64 data into octets
+ *
+ * @param base64Data
+ * Byte array containing Base64 data
+ * @return Array containing decoded data.
+ */
+ public static byte[] decodeBase64(byte[] base64Data) {
+ return new Base64().decode(base64Data);
+ }
+
+
+ /**
+ * Returns whether or not the octet is in the Base32 alphabet.
+ *
+ * @param octet
+ * The value to test
+ * @return true if the value is defined in the the Base32 alphabet false otherwise.
+ */
+ protected boolean isInAlphabet(byte octet) {
+ return octet >= 0 && octet < decodeTable.length && decodeTable[octet] != -1;
+ }
+
+ /**
+ * Below from base class
+ */
+
+ /**
+ * MIME chunk size per RFC 2045 section 6.8.
+ *
+ *
+ * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any
+ * equal signs.
+ *
+ *
+ * @see RFC 2045 section 6.8
+ */
+ private static final int MIME_CHUNK_SIZE = 76;
+
+ private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2;
+
+ /**
+ * Defines the default buffer size - currently {@value}
+ * - must be large enough for at least one encoded block+separator
+ */
+ private static final int DEFAULT_BUFFER_SIZE = 8192;
+
+ /** Mask used to extract 8 bits, used in decoding bytes */
+ private static final int MASK_8BITS = 0xff;
+
+ /**
+ * Byte used to pad output.
+ */
+ private static final byte PAD_DEFAULT = '=';
+
+ private static final byte PAD = PAD_DEFAULT;
+
+ /** Number of bytes in each full block of unencoded data, e.g. 4 for Base64 and 5 for Base32 */
+ private final int unencodedBlockSize;
+
+ /** Number of bytes in each full block of encoded data, e.g. 3 for Base64 and 8 for Base32 */
+ private final int encodedBlockSize;
+
+ /**
+ * Chunksize for encoding. Not used when decoding.
+ * A value of zero or less implies no chunking of the encoded data.
+ * Rounded down to nearest multiple of encodedBlockSize.
+ */
+ private final int lineLength;
+
+ /**
+ * Size of chunk separator. Not used unless {@link #lineLength} > 0.
+ */
+ private final int chunkSeparatorLength;
+
+ /**
+ * Buffer for streaming.
+ */
+ private byte[] buffer;
+
+ /**
+ * Position where next character should be written in the buffer.
+ */
+ private int pos;
+
+ /**
+ * Position where next character should be read from the buffer.
+ */
+ private int readPos;
+
+ /**
+ * Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this object becomes useless,
+ * and must be thrown away.
+ */
+ private boolean eof;
+
+ /**
+ * Variable tracks how many characters have been written to the current line. Only used when encoding. We use it to
+ * make sure each encoded line never goes beyond lineLength (if lineLength > 0).
+ */
+ private int currentLinePos;
+
+ /**
+ * Writes to the buffer only occur after every 3/5 reads when encoding, and every 4/8 reads when decoding.
+ * This variable helps track that.
+ */
+ private int modulus;
+
+ /**
+ * Ensure that the buffer has room for size bytes
+ *
+ * @param size minimum spare space required
+ */
+ private void ensureBufferSize(int size){
+ if ((buffer == null) || (buffer.length < pos + size)){
+ if (buffer == null) {
+ buffer = new byte[DEFAULT_BUFFER_SIZE];
+ pos = 0;
+ readPos = 0;
+ } else {
+ byte[] b = new byte[buffer.length * DEFAULT_BUFFER_RESIZE_FACTOR];
+ System.arraycopy(buffer, 0, b, 0, buffer.length);
+ buffer = b;
+ }
+ }
+ }
+
+ /**
+ * Extracts buffered data into the provided byte[] array, starting at position bPos,
+ * up to a maximum of bAvail bytes. Returns how many bytes were actually extracted.
+ *
+ * @param b
+ * byte[] array to extract the buffered data into.
+ * @param bPos
+ * position in byte[] array to start extraction at.
+ * @param bAvail
+ * amount of bytes we're allowed to extract. We may extract fewer (if fewer are available).
+ * @return The number of bytes successfully extracted into the provided byte[] array.
+ */
+ private int readResults(byte[] b, int bPos, int bAvail) {
+ if (buffer != null) {
+ int len = Math.min(pos - readPos, bAvail);
+ System.arraycopy(buffer, readPos, b, bPos, len);
+ readPos += len;
+ if (readPos >= pos) {
+ buffer = null;
+ }
+ return len;
+ }
+ return eof ? -1 : 0;
+ }
+
+ /**
+ * Resets this object to its initial newly constructed state.
+ */
+ private void reset() {
+ buffer = null;
+ pos = 0;
+ readPos = 0;
+ currentLinePos = 0;
+ modulus = 0;
+ eof = false;
+ }
+
+ /**
+ * Decodes a byte[] containing characters in the Base-N alphabet.
+ *
+ * @param pArray
+ * A byte array containing Base-N character data
+ * @return a byte array containing binary data
+ */
+ private byte[] decode(byte[] pArray) {
+ reset();
+ if (pArray == null || pArray.length == 0) {
+ return pArray;
+ }
+ decode(pArray, 0, pArray.length);
+ decode(pArray, 0, -1);
+ byte[] result = new byte[pos];
+ readResults(result, 0, result.length);
+ return result;
+ }
+
+ /**
+ * Encodes a byte[] containing binary data, into a byte[] containing characters in the alphabet.
+ *
+ * @param pArray
+ * a byte array containing binary data
+ * @return A byte array containing only the basen alphabetic character data
+ */
+ private byte[] encode(byte[] pArray) {
+ reset();
+ if (pArray == null || pArray.length == 0) {
+ return pArray;
+ }
+ encode(pArray, 0, pArray.length);
+ encode(pArray, 0, -1);
+ byte[] buf = new byte[pos - readPos];
+ readResults(buf, 0, buf.length);
+ return buf;
+ }
+
+ /**
+ * Tests a given byte array to see if it contains any characters within the alphabet or PAD.
+ *
+ * Intended for use in checking line-ending arrays
+ *
+ * @param arrayOctet
+ * byte array to test
+ * @return true if any byte is a valid character in the alphabet or PAD; false otherwise
+ */
+ private boolean containsAlphabetOrPad(byte[] arrayOctet) {
+ if (arrayOctet == null) {
+ return false;
+ }
+ for (int i = 0; i < arrayOctet.length; i++) {
+ if (PAD == arrayOctet[i] || isInAlphabet(arrayOctet[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Calculates the amount of space needed to encode the supplied array.
+ *
+ * @param pArray byte[] array which will later be encoded
+ *
+ * @return amount of space needed to encoded the supplied array.
+ * Returns a long since a max-len array will require > Integer.MAX_VALUE
+ */
+ private long getEncodedLength(byte[] pArray) {
+ // Calculate non-chunked size - rounded up to allow for padding
+ // cast to long is needed to avoid possibility of overflow
+ long len = ((pArray.length + unencodedBlockSize-1) / unencodedBlockSize) * (long) encodedBlockSize;
+ if (lineLength > 0) {
+ /**
+ * Round up to nearest multiple
+ */
+ len += ((len + lineLength-1) / lineLength) * chunkSeparatorLength;
+ }
+ return len;
+ }
+}
diff --git a/client/src/main/java/com/alibaba/nacos/client/identify/Constants.java b/client/src/main/java/com/alibaba/nacos/client/identify/Constants.java
new file mode 100644
index 00000000000..2f566f7549f
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/client/identify/Constants.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.client.identify;
+
+/**
+ * Identify Constants
+ *
+ * @author Nacos
+ *
+ */
+public class Constants {
+ public static final String ACCESS_KEY = "accessKey";
+
+ public static final String SECRET_KEY = "secretKey";
+
+ public static final String PROPERTIES_FILENAME = "spas.properties";
+
+ public static final String CREDENTIAL_PATH = "/home/admin/.spas_key/";
+
+ public static final String CREDENTIAL_DEFAULT = "default";
+
+ public static final String DOCKER_CREDENTIAL_PATH = "/etc/instanceInfo";
+
+ public static final String DOCKER_ACCESS_KEY = "env_spas_accessKey";
+
+ public static final String DOCKER_SECRET_KEY = "env_spas_secretKey";
+
+ public static final String ENV_ACCESS_KEY = "spas_accessKey";
+
+ public static final String ENV_SECRET_KEY = "spas_secretKey";
+
+ public static final String NO_APP_NAME = "";
+
+}
diff --git a/client/src/main/java/com/alibaba/nacos/client/identify/CredentialListener.java b/client/src/main/java/com/alibaba/nacos/client/identify/CredentialListener.java
new file mode 100644
index 00000000000..943facba928
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/client/identify/CredentialListener.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.client.identify;
+
+/**
+ * Credential Listener
+ *
+ * @author Nacos
+ *
+ */
+public interface CredentialListener {
+ /**
+ * update Credential
+ */
+ public void onUpdateCredential();
+}
diff --git a/client/src/main/java/com/alibaba/nacos/client/identify/CredentialService.java b/client/src/main/java/com/alibaba/nacos/client/identify/CredentialService.java
new file mode 100644
index 00000000000..cf8f792ca72
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/client/identify/CredentialService.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.client.identify;
+
+import com.alibaba.nacos.client.config.utils.LogUtils;
+import com.alibaba.nacos.client.logger.Logger;
+import com.alibaba.nacos.client.utils.StringUtils;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Credential Service
+ *
+ * @author Nacos
+ *
+ */
+public final class CredentialService implements SpasCredentialLoader {
+ static final public Logger log = LogUtils.logger(CredentialService.class);
+ private static ConcurrentHashMap instances = new ConcurrentHashMap();
+
+ private String appName;
+ private Credentials credentials = new Credentials();
+ private CredentialWatcher watcher;
+ private CredentialListener listener;
+
+ private CredentialService(String appName) {
+ if (appName == null) {
+ String value = System.getProperty("project.name");
+ if (StringUtils.isNotEmpty(value)) {
+ appName = value;
+ }
+ }
+ this.appName = appName;
+ watcher = new CredentialWatcher(appName, this);
+ }
+
+
+ public static CredentialService getInstance() {
+ return getInstance(null);
+ }
+
+ public static CredentialService getInstance(String appName) {
+ String key = appName != null ? appName : Constants.NO_APP_NAME;
+ CredentialService instance = instances.get(key);
+ if (instance == null) {
+ instance = new CredentialService(appName);
+ CredentialService previous = instances.putIfAbsent(key, instance);
+ if (previous != null) {
+ instance = previous;
+ }
+ }
+ return instance;
+ }
+
+ public static CredentialService freeInstance() {
+ return freeInstance(null);
+ }
+
+ public static CredentialService freeInstance(String appName) {
+ String key = appName != null ? appName : Constants.NO_APP_NAME;
+ CredentialService instance = instances.remove(key);
+ if (instance != null) {
+ instance.free();
+ }
+ return instance;
+ }
+
+ public void free() {
+ if (watcher != null) {
+ watcher.stop();
+ }
+ log.info(appName, this.getClass().getSimpleName() + " is freed");
+ }
+
+ public Credentials getCredential() {
+ Credentials localCredential = credentials;
+ if (localCredential.valid()) {
+ return localCredential;
+ }
+ return credentials;
+ }
+
+ public void setCredential(Credentials credential) {
+ boolean changed = !(credentials == credential || (credentials != null && credentials.identical(credential)));
+ credentials = credential;
+ if (changed && listener != null) {
+ listener.onUpdateCredential();
+ }
+ }
+
+ public void setStaticCredential(Credentials credential) {
+ if (watcher != null) {
+ watcher.stop();
+ }
+ setCredential(credential);
+ }
+
+ public void registerCredentialListener(CredentialListener listener) {
+ this.listener = listener;
+ }
+
+ @Deprecated
+ public void setAccessKey(String accessKey) {
+ credentials.setAccessKey(accessKey);
+ }
+
+ @Deprecated
+ public void setSecretKey(String secretKey) {
+ credentials.setSecretKey(secretKey);
+ }
+
+ @Deprecated
+ public String getAccessKey() {
+ return credentials.getAccessKey();
+ }
+
+ @Deprecated
+ public String getSecretKey() {
+ return credentials.getSecretKey();
+ }
+
+}
diff --git a/client/src/main/java/com/alibaba/nacos/client/identify/CredentialWatcher.java b/client/src/main/java/com/alibaba/nacos/client/identify/CredentialWatcher.java
new file mode 100644
index 00000000000..553849ba948
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/client/identify/CredentialWatcher.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.client.identify;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Properties;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import com.alibaba.nacos.client.config.utils.LogUtils;
+import com.alibaba.nacos.client.logger.Logger;
+import com.alibaba.nacos.client.utils.StringUtils;
+
+/**
+ * Credential Watcher
+ *
+ * @author Nacos
+ *
+ */
+public class CredentialWatcher {
+ static final public Logger SpasLogger = LogUtils.logger(CredentialWatcher.class);
+ private static final long REFRESH_INTERVAL = 10 * 1000;
+
+ private CredentialService serviceInstance;
+ private String appName;
+ private String propertyPath;
+ private TimerTask watcher;
+ private boolean stopped;
+
+ @SuppressWarnings("PMD.AvoidUseTimerRule")
+ public CredentialWatcher(String appName, CredentialService serviceInstance) {
+ this.appName = appName;
+ this.serviceInstance = serviceInstance;
+ loadCredential(true);
+ watcher = new TimerTask() {
+ private Timer timer = new Timer(true);
+ private long modified = 0;
+
+ {
+ timer.schedule(this, REFRESH_INTERVAL, REFRESH_INTERVAL);
+ }
+
+ @Override
+ public void run() {
+ synchronized (this) {
+ if (stopped) {
+ return;
+ }
+ boolean reload = false;
+ if (propertyPath == null) {
+ reload = true;
+ } else {
+ File file = new File(propertyPath);
+ long lastModified = file.lastModified();
+ if (modified != lastModified) {
+ reload = true;
+ modified = lastModified;
+ }
+ }
+ if (reload) {
+ loadCredential(false);
+ }
+ }
+ }
+ };
+ }
+
+ public void stop() {
+ if (stopped) {
+ return;
+ }
+ if (watcher != null) {
+ synchronized (watcher) {
+ watcher.cancel();
+ stopped = true;
+ }
+ }
+ SpasLogger.info(appName, this.getClass().getSimpleName() + " is stopped");
+ }
+
+ private void loadCredential(boolean init) {
+ boolean logWarn = init;
+ if (propertyPath == null) {
+ URL url = ClassLoader.getSystemResource(Constants.PROPERTIES_FILENAME);
+ if (url != null) {
+ propertyPath = url.getPath();
+ }
+ if (propertyPath == null || propertyPath.isEmpty()) {
+
+ String value = System.getProperty("spas.identity");
+ if (StringUtils.isNotEmpty(value)) {
+ propertyPath = value;
+ }
+ if (propertyPath == null || propertyPath.isEmpty()) {
+ propertyPath = Constants.CREDENTIAL_PATH + (appName == null ? Constants.CREDENTIAL_DEFAULT : appName);
+ }
+ else {
+ if (logWarn) {
+ SpasLogger.info(appName, "Defined credential file: -D" + "spas.identity" + "=" + propertyPath);
+ }
+ }
+ }
+ else {
+ if (logWarn) {
+ SpasLogger.info(appName, "Load credential file from classpath: " + Constants.PROPERTIES_FILENAME);
+ }
+ }
+ }
+
+ InputStream propertiesIS = null;
+ do {
+ try {
+ propertiesIS = new FileInputStream(propertyPath);
+ } catch (FileNotFoundException e) {
+ if (appName != null && !appName.equals(Constants.CREDENTIAL_DEFAULT) && propertyPath.equals(Constants.CREDENTIAL_PATH + appName)) {
+ propertyPath = Constants.CREDENTIAL_PATH + Constants.CREDENTIAL_DEFAULT;
+ continue;
+ }
+ if (!Constants.DOCKER_CREDENTIAL_PATH.equals(propertyPath)) {
+ propertyPath = Constants.DOCKER_CREDENTIAL_PATH;
+ continue;
+ }
+ }
+ break;
+ } while (true);
+
+ String accessKey = null;
+ String secretKey = null;
+ if (propertiesIS == null) {
+ propertyPath = null;
+ accessKey = System.getenv(Constants.ENV_ACCESS_KEY);
+ secretKey = System.getenv(Constants.ENV_SECRET_KEY);
+ if (accessKey == null && secretKey == null) {
+ if (logWarn) {
+ SpasLogger.info(appName, "No credential found");
+ }
+ return;
+ }
+ }
+ else {
+ Properties properties = new Properties();
+ try {
+ properties.load(propertiesIS);
+ } catch (IOException e) {
+ SpasLogger.error("26", "Unable to load credential file, appName:" + appName
+ + "Unable to load credential file " + propertyPath, e);
+ propertyPath = null;
+ return;
+ } finally {
+ try {
+ propertiesIS.close();
+ } catch (IOException e) {
+ SpasLogger.error("27", "Unable to close credential file, appName:" + appName
+ + "Unable to close credential file " + propertyPath, e);
+ }
+ }
+
+ if (logWarn) {
+ SpasLogger.info(appName, "Load credential file " + propertyPath);
+ }
+
+ if (!Constants.DOCKER_CREDENTIAL_PATH.equals(propertyPath)) {
+ if (properties.containsKey(Constants.ACCESS_KEY)) {
+ accessKey = properties.getProperty(Constants.ACCESS_KEY);
+ }
+ if (properties.containsKey(Constants.SECRET_KEY)) {
+ secretKey = properties.getProperty(Constants.SECRET_KEY);
+ }
+ } else {
+ if (properties.containsKey(Constants.DOCKER_ACCESS_KEY)) {
+ accessKey = properties.getProperty(Constants.DOCKER_ACCESS_KEY);
+ }
+ if (properties.containsKey(Constants.DOCKER_SECRET_KEY)) {
+ secretKey = properties.getProperty(Constants.DOCKER_SECRET_KEY);
+ }
+ }
+ }
+
+ if (accessKey != null) {
+ accessKey = accessKey.trim();
+ }
+ if (secretKey != null) {
+ secretKey = secretKey.trim();
+ }
+
+ Credentials credential = new Credentials(accessKey, secretKey);
+ if (!credential.valid()) {
+ SpasLogger.warn("1", "Credential file missing required property" + appName + "Credential file missing "
+ + Constants.ACCESS_KEY + " or " + Constants.SECRET_KEY);
+ propertyPath = null;
+ // return;
+ }
+
+ serviceInstance.setCredential(credential);
+ }
+}
diff --git a/client/src/main/java/com/alibaba/nacos/client/identify/Credentials.java b/client/src/main/java/com/alibaba/nacos/client/identify/Credentials.java
new file mode 100644
index 00000000000..f54100bf6b8
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/client/identify/Credentials.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.client.identify;
+
+/**
+ * Credentials
+ *
+ * @author Nacos
+ *
+ */
+public class Credentials implements SpasCredential {
+
+ private volatile String accessKey;
+
+ private volatile String secretKey;
+
+ public Credentials(String accessKey, String secretKey) {
+ this.accessKey = accessKey;
+ this.secretKey = secretKey;
+ }
+
+ public Credentials() {
+ this(null, null);
+ }
+
+ public String getAccessKey() {
+ return accessKey;
+ }
+
+ public void setAccessKey(String accessKey) {
+ this.accessKey = accessKey;
+ }
+
+ public String getSecretKey() {
+ return secretKey;
+ }
+
+ public void setSecretKey(String secretKey) {
+ this.secretKey = secretKey;
+ }
+
+ public boolean valid() {
+ return accessKey != null && !accessKey.isEmpty() && secretKey != null && !secretKey.isEmpty();
+ }
+
+ public boolean identical(Credentials other) {
+ return this == other ||
+ (other != null &&
+ (accessKey == null && other.accessKey == null || accessKey != null && accessKey.equals(other.accessKey)) &&
+ (secretKey == null && other.secretKey == null || secretKey != null && secretKey.equals(other.secretKey)));
+ }
+}
diff --git a/client/src/main/java/com/alibaba/nacos/client/identify/STSConfig.java b/client/src/main/java/com/alibaba/nacos/client/identify/STSConfig.java
new file mode 100644
index 00000000000..e555c535351
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/client/identify/STSConfig.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.client.identify;
+
+import com.alibaba.nacos.client.utils.StringUtils;
+
+/**
+ * Sts config
+ *
+ * @author Nacos
+ */
+@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule")
+public class STSConfig {
+ private static final String RAM_SECURITY_CREDENTIALS_URL
+ = "";
+ private String ramRoleName;
+ /**
+ * STS 临时凭证有效期剩余多少时开始刷新(允许本地时间比 STS 服务时间最多慢多久)
+ */
+ private int timeToRefreshInMillisecond = 3 * 60 * 1000;
+ /**
+ * 获取 STS 临时凭证的元数据接口(包含角色名称)
+ */
+ private String securityCredentialsUrl;
+ /**
+ * 设定 STS 临时凭证,不再通过元数据接口获取
+ */
+ private String securityCredentials;
+ /**
+ * 是否缓存
+ */
+ private boolean cacheSecurityCredentials = true;
+
+ private static class Singleton {
+ private static final STSConfig INSTANCE = new STSConfig();
+ }
+
+ private STSConfig() {
+ String ramRoleName = System.getProperty("ram.role.name");
+ if (!StringUtils.isBlank(ramRoleName)) {
+ setRamRoleName(ramRoleName);
+ }
+
+ String timeToRefreshInMillisecond = System.getProperty("time.to.refresh.in.millisecond");
+ if (!StringUtils.isBlank(timeToRefreshInMillisecond)) {
+ setTimeToRefreshInMillisecond(Integer.parseInt(timeToRefreshInMillisecond));
+ }
+
+ String securityCredentials = System.getProperty("security.credentials");
+ if (!StringUtils.isBlank(securityCredentials)) {
+ setSecurityCredentials(securityCredentials);
+ }
+
+ String securityCredentialsUrl = System.getProperty("security.credentials.url");
+ if (!StringUtils.isBlank(securityCredentialsUrl)) {
+ setSecurityCredentialsUrl(securityCredentialsUrl);
+ }
+
+ String cacheSecurityCredentials = System.getProperty("cache.security.credentials");
+ if (!StringUtils.isBlank(cacheSecurityCredentials)) {
+ setCacheSecurityCredentials(Boolean.valueOf(cacheSecurityCredentials));
+ }
+ }
+
+ public static STSConfig getInstance() {
+ return Singleton.INSTANCE;
+ }
+
+ public String getRamRoleName() {
+ return ramRoleName;
+ }
+
+ public void setRamRoleName(String ramRoleName) {
+ this.ramRoleName = ramRoleName;
+ }
+
+ public int getTimeToRefreshInMillisecond() {
+ return timeToRefreshInMillisecond;
+ }
+
+ public void setTimeToRefreshInMillisecond(int timeToRefreshInMillisecond) {
+ this.timeToRefreshInMillisecond = timeToRefreshInMillisecond;
+ }
+
+ public String getSecurityCredentialsUrl() {
+ if (securityCredentialsUrl == null && ramRoleName != null) {
+ return RAM_SECURITY_CREDENTIALS_URL + ramRoleName;
+ }
+ return securityCredentialsUrl;
+ }
+
+ public void setSecurityCredentialsUrl(String securityCredentialsUrl) {
+ this.securityCredentialsUrl = securityCredentialsUrl;
+ }
+
+ public String getSecurityCredentials() {
+ return securityCredentials;
+ }
+
+ public void setSecurityCredentials(String securityCredentials) {
+ this.securityCredentials = securityCredentials;
+ }
+
+ public boolean isSTSOn() {
+ return StringUtils.isNotEmpty(getSecurityCredentials()) || StringUtils.isNotEmpty(getSecurityCredentialsUrl());
+ }
+
+ public boolean isCacheSecurityCredentials() {
+ return cacheSecurityCredentials;
+ }
+
+ public void setCacheSecurityCredentials(boolean cacheSecurityCredentials) {
+ this.cacheSecurityCredentials = cacheSecurityCredentials;
+ }
+}
diff --git a/client/src/main/java/com/alibaba/nacos/client/identify/SpasCredential.java b/client/src/main/java/com/alibaba/nacos/client/identify/SpasCredential.java
new file mode 100644
index 00000000000..8d6a2bd919f
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/client/identify/SpasCredential.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.client.identify;
+
+/**
+ * Spas Credential Interface
+ *
+ * @author Nacos
+ *
+ */
+public interface SpasCredential {
+ /**
+ * get AccessKey
+ *
+ * @return AccessKey
+ */
+ public String getAccessKey();
+
+ /**
+ * get SecretKey
+ *
+ * @return SecretKey
+ */
+ public String getSecretKey();
+}
diff --git a/client/src/main/java/com/alibaba/nacos/client/identify/SpasCredentialLoader.java b/client/src/main/java/com/alibaba/nacos/client/identify/SpasCredentialLoader.java
new file mode 100644
index 00000000000..12012ee2a21
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/client/identify/SpasCredentialLoader.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.client.identify;
+
+/**
+ * Spas Credential Loader
+ *
+ * @author Nacos
+ *
+ */
+public interface SpasCredentialLoader {
+ /**
+ * get Credential
+ *
+ * @return Credential
+ */
+ SpasCredential getCredential();
+}
diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/Level.java b/client/src/main/java/com/alibaba/nacos/client/logger/Level.java
new file mode 100644
index 00000000000..38522f80113
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/client/logger/Level.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.client.logger;
+
+/**
+ * 阿里中间件日志级别
+ *
+ * @author zhuyong 2014年3月20日 上午9:57:27
+ */
+public enum Level {
+ /**
+ * log level
+ */
+ DEBUG("DEBUG"), INFO("INFO"), WARN("WARN"), ERROR("ERROR"), OFF("OFF");
+
+ private String name;
+
+ Level(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public static Level codeOf(String level) {
+ for (Level l : Level.values()) {
+ if (l.name.equals(level)) {
+ return l;
+ }
+ }
+
+ return OFF;
+ }
+}
diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/Logger.java b/client/src/main/java/com/alibaba/nacos/client/logger/Logger.java
new file mode 100644
index 00000000000..f38c3e811f1
--- /dev/null
+++ b/client/src/main/java/com/alibaba/nacos/client/logger/Logger.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.nacos.client.logger;
+
+import com.alibaba.nacos.client.logger.option.ActivateOption;
+
+/**
+ *