support consul metadata center implementation (#405)

diff --git a/dubbo-admin-server/pom.xml b/dubbo-admin-server/pom.xml
index 44c6828..02115b0 100644
--- a/dubbo-admin-server/pom.xml
+++ b/dubbo-admin-server/pom.xml
@@ -139,6 +139,16 @@
         </dependency>
 
         <dependency>
+            <groupId>com.ecwid.consul</groupId>
+            <artifactId>consul-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.pszymczyk.consul</groupId>
+            <artifactId>embedded-consul</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
             <groupId>io.springfox</groupId>
             <artifactId>springfox-swagger2</artifactId>
         </dependency>
diff --git a/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/registry/metadata/impl/ConsulMetaDataCollector.java b/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/registry/metadata/impl/ConsulMetaDataCollector.java
new file mode 100644
index 0000000..fa5a225
--- /dev/null
+++ b/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/registry/metadata/impl/ConsulMetaDataCollector.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.
+ */
+
+package org.apache.dubbo.admin.registry.metadata.impl;
+
+
+import org.apache.dubbo.admin.registry.metadata.MetaDataCollector;
+
+import com.ecwid.consul.v1.ConsulClient;
+import com.ecwid.consul.v1.Response;
+import com.ecwid.consul.v1.kv.model.GetValue;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.metadata.identifier.MetadataIdentifier;
+
+import java.util.Objects;
+
+
+public class ConsulMetaDataCollector implements MetaDataCollector {
+    private static final Logger LOG = LoggerFactory.getLogger(ConsulMetaDataCollector.class);
+    private static final int DEFAULT_PORT = 8500;
+    private URL url;
+    private ConsulClient client;
+
+    @Override
+    public URL getUrl() {
+        return this.url;
+    }
+
+    @Override
+    public void setUrl(URL url) {
+        this.url = url;
+    }
+
+    @Override
+    public void init() {
+        Objects.requireNonNull(this.url, "metadataUrl require not null");
+        String host = this.url.getHost();
+        int port = this.url.getPort() != 0 ? url.getPort() : DEFAULT_PORT;
+        this.client = new ConsulClient(host, port);
+    }
+
+    @Override
+    public String getProviderMetaData(MetadataIdentifier key) {
+        return doGetMetaData(key);
+    }
+
+    @Override
+    public String getConsumerMetaData(MetadataIdentifier key) {
+        return doGetMetaData(key);
+    }
+
+    private String doGetMetaData(MetadataIdentifier key) {
+        try {
+            Response<GetValue> response = this.client.getKVValue(key.getUniqueKey(MetadataIdentifier.KeyTypeEnum.UNIQUE_KEY));
+            return response.getValue().getDecodedValue();
+        } catch (Exception e) {
+            LOG.error(String.format("Failed to fetch metadata for %s from consul, cause: %s",
+                    key.getUniqueKey(MetadataIdentifier.KeyTypeEnum.UNIQUE_KEY), e.getMessage()), e);
+        }
+        return null;
+    }
+
+    //just for test
+    ConsulClient getClient() {
+        return this.client;
+    }
+}
diff --git a/dubbo-admin-server/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.admin.registry.metadata.MetaDataCollector b/dubbo-admin-server/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.admin.registry.metadata.MetaDataCollector
index d9e532d..fc4c855 100644
--- a/dubbo-admin-server/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.admin.registry.metadata.MetaDataCollector
+++ b/dubbo-admin-server/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.admin.registry.metadata.MetaDataCollector
@@ -1,3 +1,4 @@
 zookeeper=org.apache.dubbo.admin.registry.metadata.impl.ZookeeperMetaDataCollector
 redis=org.apache.dubbo.admin.registry.metadata.impl.RedisMetaDataCollector
+consul=org.apache.dubbo.admin.registry.metadata.impl.ConsulMetaDataCollector
 nacos=org.apache.dubbo.admin.registry.metadata.impl.NacosMetaDataCollector
\ No newline at end of file
diff --git a/dubbo-admin-server/src/test/java/org/apache/dubbo/admin/registry/metadata/impl/ConsulMetaDataCollectorTest.java b/dubbo-admin-server/src/test/java/org/apache/dubbo/admin/registry/metadata/impl/ConsulMetaDataCollectorTest.java
new file mode 100644
index 0000000..5cf93d4
--- /dev/null
+++ b/dubbo-admin-server/src/test/java/org/apache/dubbo/admin/registry/metadata/impl/ConsulMetaDataCollectorTest.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.
+ */
+
+package org.apache.dubbo.admin.registry.metadata.impl;
+
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.pszymczyk.consul.ConsulProcess;
+import com.pszymczyk.consul.ConsulStarterBuilder;
+import org.apache.dubbo.common.Constants;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder;
+import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
+import org.apache.dubbo.metadata.definition.model.MethodDefinition;
+import org.apache.dubbo.metadata.definition.util.ClassUtils;
+import org.apache.dubbo.metadata.identifier.MetadataIdentifier;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class ConsulMetaDataCollectorTest {
+
+    private final Gson gson = new Gson();
+    private ConsulProcess consul;
+    private ConsulMetaDataCollector consulMetaDataCollector;
+
+    @Before
+    public void setUp() {
+        consul = ConsulStarterBuilder.consulStarter().build().start();
+        consulMetaDataCollector = new ConsulMetaDataCollector();
+        consulMetaDataCollector.setUrl(URL.valueOf("consul://127.0.0.1:" + consul.getHttpPort()));
+        consulMetaDataCollector.init();
+    }
+
+    @After
+    public void tearDown() {
+        consul.close();
+        consulMetaDataCollector = null;
+    }
+
+    @Test
+    public void testGetProviderMetaData() {
+        MetadataIdentifier identifier = buildIdentifier(true);
+
+        Map<String, String> params = new HashMap<>();
+        params.put("key1", "value1");
+        params.put("key2", "true");
+
+        FullServiceDefinition definition = ServiceDefinitionBuilder.buildFullDefinition(ServiceA.class, params);
+
+        String metadata = gson.toJson(definition);
+        consulMetaDataCollector.getClient().setKVValue(identifier.getUniqueKey(MetadataIdentifier.KeyTypeEnum.UNIQUE_KEY), metadata);
+
+        String providerMetaData = consulMetaDataCollector.getProviderMetaData(identifier);
+        Assert.assertEquals(metadata, providerMetaData);
+
+        FullServiceDefinition retDef = gson.fromJson(providerMetaData, FullServiceDefinition.class);
+        Assert.assertEquals(ServiceA.class.getCanonicalName(), retDef.getCanonicalName());
+        Assert.assertEquals(ClassUtils.getCodeSource(ServiceA.class), retDef.getCodeSource());
+        Assert.assertEquals(params, retDef.getParameters());
+
+        //method def assertions
+        Assert.assertNotNull(retDef.getMethods());
+        Assert.assertEquals(3, retDef.getMethods().size());
+        List<String> methodNames = retDef.getMethods().stream().map(MethodDefinition::getName).sorted().collect(Collectors.toList());
+        Assert.assertEquals("method1", methodNames.get(0));
+        Assert.assertEquals("method2", methodNames.get(1));
+        Assert.assertEquals("method3", methodNames.get(2));
+    }
+
+
+    @Test
+    public void testGetConsumerMetaData() {
+        MetadataIdentifier identifier = buildIdentifier(false);
+
+        Map<String, String> consumerParams = new HashMap<>();
+        consumerParams.put("k1", "v1");
+        consumerParams.put("k2", "1");
+        consumerParams.put("k3", "true");
+        String metadata = gson.toJson(consumerParams);
+        consulMetaDataCollector.getClient().setKVValue(identifier.getUniqueKey(MetadataIdentifier.KeyTypeEnum.UNIQUE_KEY), metadata);
+
+        String consumerMetaData = consulMetaDataCollector.getConsumerMetaData(identifier);
+        Map<String, String> retParams = gson.fromJson(consumerMetaData, new TypeToken<Map<String, String>>() {
+        }.getType());
+        Assert.assertEquals(consumerParams, retParams);
+    }
+
+
+    private MetadataIdentifier buildIdentifier(boolean isProducer) {
+        MetadataIdentifier identifier = new MetadataIdentifier();
+        identifier.setApplication(String.format("metadata-%s-test", isProducer ? "provider" : "consumer"));
+        identifier.setGroup("group");
+        identifier.setServiceInterface(ServiceA.class.getName());
+        identifier.setSide(isProducer ? Constants.PROVIDER_SIDE : Constants.CONSUMER_SIDE);
+        identifier.setVersion("1.0.0");
+        return identifier;
+    }
+
+
+    interface ServiceA {
+        void method1();
+
+        void method2() throws IOException;
+
+        String method3();
+    }
+
+}
diff --git a/pom.xml b/pom.xml
index 869873f..0ad8425 100644
--- a/pom.xml
+++ b/pom.xml
@@ -64,7 +64,9 @@
 		<netty-version>4.1.30.Final</netty-version>
 		<jacoco-version>0.8.2</jacoco-version>
 		<jedis-version>2.9.0</jedis-version>
-		<apollo-version>1.2.0</apollo-version>
+    <apollo-version>1.2.0</apollo-version>
+		<consul-version>1.4.2</consul-version>
+		<consul-embedded-version>2.0.0</consul-embedded-version>
 		<nacos-version>1.0.0</nacos-version>
 		<guava-version>20.0</guava-version>
 		<snakeyaml-version>1.24</snakeyaml-version>
@@ -173,11 +175,21 @@
 			</dependency>
 
 			<dependency>
+				<groupId>com.ecwid.consul</groupId>
+				<artifactId>consul-api</artifactId>
+				<version>${consul-version}</version>
+			</dependency>
+			<dependency>
+				<groupId>com.pszymczyk.consul</groupId>
+				<artifactId>embedded-consul</artifactId>
+				<version>${consul-embedded-version}</version>
+			</dependency>
+          
+ 			<dependency> 
 				<groupId>com.google.guava</groupId>
 				<artifactId>guava</artifactId>
 				<version>${guava-version}</version>
 			</dependency>
-
 		</dependencies>
 	</dependencyManagement>