From 6e3c30516e776619f5a6cf68f3fc95b92effa74b Mon Sep 17 00:00:00 2001
From: "vito.he" <hxmhlt@163.com>
Date: Thu, 13 Jun 2019 21:43:14 +0800
Subject: [PATCH] Ftr:config center for start

---
 common/config/environment.go               |  75 +++++++++++++-
 common/constant/default.go                 |   1 +
 config/base_config.go                      | 108 +++++++++++++++++++--
 config/base_config_test.go                 |  60 ++++++++++++
 config/config_loader.go                    |   3 +-
 config/registry_config.go                  |  12 +--
 config_center/configuration_parser.go      |  25 +++++
 config_center/configuration_parser_test.go |  14 +++
 config_center/dynamic_configuration.go     |   2 +
 config_center/zookeeper/factory.go         |   1 +
 config_center/zookeeper/impl.go            |   8 ++
 go.mod                                     |   1 +
 go.sum                                     |   2 +
 13 files changed, 293 insertions(+), 19 deletions(-)
 create mode 100644 config/base_config_test.go
 create mode 100644 config_center/configuration_parser.go
 create mode 100644 config_center/configuration_parser_test.go

diff --git a/common/config/environment.go b/common/config/environment.go
index ce9c33363..d92c000a3 100644
--- a/common/config/environment.go
+++ b/common/config/environment.go
@@ -1,8 +1,36 @@
+/*
+ * 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 config
 
-import "sync"
+import (
+	"container/list"
+	"sync"
+)
 
+// There is dubbo.properties file and application level config center configuration which higner than normal config center in java. So in java the
+// configuration sequence will be config center > application level config center > dubbo.properties > spring bean configuration.
+// But in go, neither the dubbo.properties file or application level config center configuration will not support for the time being.
+// We just have config center configuration which can override configuration in consumer.yaml & provider.yaml.
+// But for add these features in future ,I finish the environment struct following Environment class in java.
 type Environment struct {
+	configCenterFirst bool
+	externalConfigs   sync.Map
+	externalConfigMap sync.Map
 }
 
 var instance *Environment
@@ -11,7 +39,50 @@ var once sync.Once
 
 func GetEnvInstance() *Environment {
 	once.Do(func() {
-		instance = &Environment{}
+		instance = &Environment{configCenterFirst: true}
 	})
 	return instance
 }
+
+//func (env *Environment) SetConfigCenterFirst() {
+//	env.configCenterFirst = true
+//}
+
+//func (env *Environment) ConfigCenterFirst() bool {
+//	return env.configCenterFirst
+//}
+
+func (env *Environment) UpdateExternalConfigMap(externalMap map[string]string) {
+	for k, v := range externalMap {
+		env.externalConfigMap.Store(k, v)
+	}
+}
+
+func (env *Environment) Configuration(prefix string) *list.List {
+	list := list.New()
+	memConf := newInmemoryConfiguration(prefix)
+	memConf.setProperties(env.externalConfigMap)
+	list.PushBack(memConf)
+	return list
+}
+
+type InmemoryConfiguration struct {
+	prefix string
+	store  sync.Map
+}
+
+func newInmemoryConfiguration(prefix string) *InmemoryConfiguration {
+	return &InmemoryConfiguration{prefix: prefix}
+}
+func (conf *InmemoryConfiguration) setProperties(p sync.Map) {
+	conf.store = p
+}
+
+func (conf *InmemoryConfiguration) GetProperty(key string) string {
+	v, ok := conf.store.Load(conf.prefix + "." + key)
+	if ok {
+		return v.(string)
+	} else {
+		return ""
+	}
+}
diff --git a/common/constant/default.go b/common/constant/default.go
index 85f61f30f..75869d552 100644
--- a/common/constant/default.go
+++ b/common/constant/default.go
@@ -17,6 +17,7 @@
 
 package constant
 
+const DUBBO = "dubbo"
 const (
 	DEFAULT_WEIGHT = 100     //
 	DEFAULT_WARMUP = 10 * 60 // in java here is 10*60*1000 because of System.currentTimeMillis() is measured in milliseconds & in go time.Unix() is second
diff --git a/config/base_config.go b/config/base_config.go
index edecb85b0..dc7a2f19a 100644
--- a/config/base_config.go
+++ b/config/base_config.go
@@ -18,29 +18,37 @@ package config
 
 import (
 	"context"
+	"github.com/apache/dubbo-go/common/constant"
+	"reflect"
+	"strconv"
+	"strings"
 )
 import (
-	"github.com/apache/dubbo-go/common"
-	"github.com/apache/dubbo-go/common/logger"
 	perrors "github.com/pkg/errors"
 )
 import (
+	"github.com/apache/dubbo-go/common"
+	"github.com/apache/dubbo-go/common/config"
 	"github.com/apache/dubbo-go/common/extension"
+	"github.com/apache/dubbo-go/common/logger"
 	"github.com/apache/dubbo-go/config_center"
 )
 
 type baseConfig struct {
-	// application
-	ApplicationConfig  ApplicationConfig `yaml:"application_config" json:"application_config,omitempty"`
 	ConfigCenterConfig ConfigCenterConfig
 	configCenterUrl    *common.URL
+	prefix             string
+	fatherConfig       interface{}
 }
 
-func (c *baseConfig) startConfigCenter(ctx context.Context) {
+func (c *baseConfig) startConfigCenter(ctx context.Context) error {
 	var err error
 	*c.configCenterUrl, err = common.NewURL(ctx, c.ConfigCenterConfig.Address)
-	c.prepareEnvironment()
-
+	if c.prepareEnvironment() != nil {
+		return perrors.WithMessagef(err, "start config center error!")
+	}
+	c.fresh()
+	return err
 }
 
 func (c *baseConfig) prepareEnvironment() error {
@@ -48,12 +56,92 @@ func (c *baseConfig) prepareEnvironment() error {
 	factory := extension.GetConfigCenterFactory(c.ConfigCenterConfig.Protocol)
 	dynamicConfig, err := factory.GetDynamicConfiguration(c.configCenterUrl)
 	if err != nil {
-		logger.Errorf("get dynamic configuration error , error message is %v", err)
-		return err
+		logger.Errorf("Get dynamic configuration error , error message is %v", err)
+		return perrors.WithStack(err)
 	}
 	content, err := dynamicConfig.GetConfig(c.ConfigCenterConfig.ConfigFile, config_center.WithGroup(c.ConfigCenterConfig.Group))
 	if err != nil {
 		logger.Errorf("Get config content in dynamic configuration error , error message is %v", err)
-		return err
+		return perrors.WithStack(err)
+	}
+	mapContent, err := dynamicConfig.Parser().Parse(content)
+	if err != nil {
+		return perrors.WithStack(err)
 	}
+	config.GetEnvInstance().UpdateExternalConfigMap(mapContent)
+	return nil
+}
+
+func (c *baseConfig) fresh() {
+	configList := config.GetEnvInstance().Configuration(c.Prefix())
+	config := configList.Front().Value.(*config.InmemoryConfiguration)
+	val := reflect.Indirect(reflect.ValueOf(c.fatherConfig))
+	for i := 0; i < val.NumField(); i++ {
+		if key := val.Type().Field(i).Tag.Get("property"); key != "-" && key != "" {
+			f := val.Field(i)
+			if f.IsValid() {
+				value := config.GetProperty(key)
+				setValue := func(f reflect.Value) {
+					if f.Kind() == reflect.Int {
+						x, err := strconv.Atoi(value)
+						if err != nil {
+							logger.Errorf("Dynamic change the configuration in struct {%v} field {%v} error ,error message is {%v}",
+								val.Type().Name(), val.Type().Field(i).Name, err)
+						} else {
+							if !f.OverflowInt(int64(x)) {
+								f.SetInt(int64(x))
+							} else {
+								logger.Errorf("Dynamic change the configuration in struct {%v} field {%v} error ,error message is {%v}",
+									val.Type().Name(), val.Type().Field(i).Name, perrors.Errorf("the int64 value {%v} from config center is  overflow", int64(x)))
+							}
+						}
+
+					}
+					if f.Kind() == reflect.String {
+						f.SetString(value)
+					}
+					if f.Kind() == reflect.Bool {
+						x, err := strconv.ParseBool(value)
+						if err != nil {
+							logger.Errorf("Dynamic change the configuration in struct {%v} field {%v} error ,error message is {%v}",
+								val.Type().Name(), val.Type().Field(i).Name, err)
+						}
+						f.SetBool(x)
+					}
+					if f.Kind() == reflect.Float64 {
+						x, err := strconv.ParseFloat(value, 64)
+						if err != nil {
+							logger.Errorf("Dynamic change the configuration in struct {%v} field {%v} error ,error message is {%v}",
+								val.Type().Name(), val.Type().Field(i).Name, err)
+						} else {
+							if !f.OverflowFloat(x) {
+								f.SetFloat(x)
+							} else {
+								logger.Errorf("Dynamic change the configuration in struct {%v} field {%v} error ,error message is {%v}",
+									val.Type().Name(), val.Type().Field(i).Name, perrors.Errorf("the float64 value {%v} from config center is  overflow", x))
+							}
+						}
+					}
+				}
+				setValue(f)
+				if f.Kind() == reflect.Ptr {
+					setValue(f.Elem())
+				}
+			}
+		}
+	}
+}
+
+func (c *baseConfig) SetPrefix(prefix string) {
+	c.prefix = prefix
+}
+func (c *baseConfig) Prefix() string {
+	if c.prefix == "" {
+		return constant.DUBBO + "." + strings.ToLower(strings.Replace(reflect.Indirect(reflect.ValueOf(c.fatherConfig)).Type().Name(), "Config", "", -1))
+	} else {
+		return c.prefix
+	}
+}
+func (c *baseConfig) SetFatherConfig(fatherConfig interface{}) {
+	c.fatherConfig = fatherConfig
 }
diff --git a/config/base_config_test.go b/config/base_config_test.go
new file mode 100644
index 000000000..efdcb2b29
--- /dev/null
+++ b/config/base_config_test.go
@@ -0,0 +1,60 @@
+package config
+
+import (
+	"github.com/apache/dubbo-go/common/config"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func Test_refresh(t *testing.T) {
+	//Id         string `required:"true" yaml:"id"  json:"id,omitempty"`
+	//Type       string `required:"true" yaml:"type"  json:"type,omitempty"`
+	//TimeoutStr string `yaml:"timeout" default:"5s" json:"timeout,omitempty"` // unit: second
+	//Group      string `yaml:"group" json:"group,omitempty"`
+	////for registry
+	//Address  string `yaml:"address" json:"address,omitempty"`
+	//Username string `yaml:"username" json:"address,omitempty"`
+	//Password string `yaml:"password" json:"address,omitempty"`
+
+	c := &baseConfig{}
+	mockMap := map[string]string{}
+	mockMap["dubbo.registry.type"] = "zookeeper"
+	mockMap["dubbo.registry.timeout"] = "3s"
+	mockMap["dubbo.registry.group"] = "hangzhou"
+	mockMap["dubbo.registry.address"] = "zookeeper://172.0.0.1:2181"
+	mockMap["dubbo.registry.username"] = "admin"
+	mockMap["dubbo.registry.password"] = "admin"
+	config.GetEnvInstance().UpdateExternalConfigMap(mockMap)
+	father := &RegistryConfig{Type: "1111"}
+	c.SetFatherConfig(father)
+	c.fresh()
+	assert.Equal(t, "zookeeper", father.Type)
+	assert.Equal(t, "zookeeper", father.Type)
+}
+
+func Test_refreshWithPrefix(t *testing.T) {
+	//Id         string `required:"true" yaml:"id"  json:"id,omitempty"`
+	//Type       string `required:"true" yaml:"type"  json:"type,omitempty"`
+	//TimeoutStr string `yaml:"timeout" default:"5s" json:"timeout,omitempty"` // unit: second
+	//Group      string `yaml:"group" json:"group,omitempty"`
+	////for registry
+	//Address  string `yaml:"address" json:"address,omitempty"`
+	//Username string `yaml:"username" json:"address,omitempty"`
+	//Password string `yaml:"password" json:"address,omitempty"`
+
+	c := &baseConfig{}
+	mockMap := map[string]string{}
+	mockMap["dubbo.customRegistry.type"] = "zookeeper"
+	mockMap["dubbo.customRegistry.timeout"] = "3s"
+	mockMap["dubbo.customRegistry.group"] = "hangzhou"
+	mockMap["dubbo.customRegistry.address"] = "zookeeper://172.0.0.1:2181"
+	mockMap["dubbo.customRegistry.username"] = "admin"
+	mockMap["dubbo.customRegistry.password"] = "admin"
+	config.GetEnvInstance().UpdateExternalConfigMap(mockMap)
+	father := &RegistryConfig{Type: "1111"}
+	c.SetPrefix("dubbo.customRegistry")
+	c.SetFatherConfig(father)
+	c.fresh()
+	assert.Equal(t, "zookeeper", father.Type)
+	assert.Equal(t, "zookeeper", father.Type)
+}
diff --git a/config/config_loader.go b/config/config_loader.go
index af586af94..7ee88024f 100644
--- a/config/config_loader.go
+++ b/config/config_loader.go
@@ -125,7 +125,8 @@ func providerInit(confProFile string) error {
 type ConsumerConfig struct {
 	baseConfig
 	Filter string `yaml:"filter" json:"filter,omitempty"`
-
+	// application
+	ApplicationConfig ApplicationConfig `yaml:"application_config" json:"application_config,omitempty"`
 	// client
 	Connect_Timeout string `default:"100ms"  yaml:"connect_timeout" json:"connect_timeout,omitempty"`
 	ConnectTimeout  time.Duration
diff --git a/config/registry_config.go b/config/registry_config.go
index 6dcf5bde7..a4bc27551 100644
--- a/config/registry_config.go
+++ b/config/registry_config.go
@@ -31,13 +31,13 @@ import (
 
 type RegistryConfig struct {
 	Id         string `required:"true" yaml:"id"  json:"id,omitempty"`
-	Type       string `required:"true" yaml:"type"  json:"type,omitempty"`
-	TimeoutStr string `yaml:"timeout" default:"5s" json:"timeout,omitempty"` // unit: second
-	Group      string `yaml:"group" json:"group,omitempty"`
+	Type       string `required:"true" yaml:"type"  json:"type,omitempty" property:"type"`
+	TimeoutStr string `yaml:"timeout" default:"5s" json:"timeout,omitempty" property:"timeout"` // unit: second
+	Group      string `yaml:"group" json:"group,omitempty" property:"group"`
 	//for registry
-	Address  string `yaml:"address" json:"address,omitempty"`
-	Username string `yaml:"username" json:"address,omitempty"`
-	Password string `yaml:"password" json:"address,omitempty"`
+	Address  string `yaml:"address" json:"address,omitempty" property:"address"`
+	Username string `yaml:"username" json:"address,omitempty" property:"username"`
+	Password string `yaml:"password" json:"address,omitempty"  property:"password"`
 }
 
 func loadRegistries(registriesIds []ConfigRegistry, registries []RegistryConfig, roleType common.RoleType) []*common.URL {
diff --git a/config_center/configuration_parser.go b/config_center/configuration_parser.go
new file mode 100644
index 000000000..8c5498512
--- /dev/null
+++ b/config_center/configuration_parser.go
@@ -0,0 +1,25 @@
+package config_center
+
+import (
+	"github.com/magiconair/properties"
+)
+import (
+	"github.com/apache/dubbo-go/common/logger"
+)
+
+type ConfigurationParser interface {
+	Parse(string) (map[string]string, error)
+}
+
+//for support properties file in config center
+type DefaultConfigurationParser struct {
+}
+
+func (parser *DefaultConfigurationParser) Parse(content string) (map[string]string, error) {
+	properties, err := properties.LoadString(content)
+	if err != nil {
+		logger.Errorf("Parse the content {%v} in DefaultConfigurationParser error ,error message is {%v}", content, err)
+		return nil, err
+	}
+	return properties.Map(), nil
+}
diff --git a/config_center/configuration_parser_test.go b/config_center/configuration_parser_test.go
new file mode 100644
index 000000000..b97bc7e8d
--- /dev/null
+++ b/config_center/configuration_parser_test.go
@@ -0,0 +1,14 @@
+package config_center
+
+import (
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestDefaultConfigurationParser_Parser(t *testing.T) {
+	parser := &DefaultConfigurationParser{}
+	m, err := parser.Parse("dubbo.registry.address=172.0.0.1\ndubbo.registry.name=test")
+	assert.NoError(t, err)
+	assert.Equal(t, 2, len(m))
+	assert.Equal(t, "172.0.0.1", m["dubbo.registry.address"])
+}
diff --git a/config_center/dynamic_configuration.go b/config_center/dynamic_configuration.go
index e497ce728..3b829507b 100644
--- a/config_center/dynamic_configuration.go
+++ b/config_center/dynamic_configuration.go
@@ -31,6 +31,8 @@ const DEFAULT_GROUP = "dubbo"
 const DEFAULT_CONFIG_TIMEOUT = "10s"
 
 type DynamicConfiguration interface {
+	Parser() ConfigurationParser
+	SetParser(ConfigurationParser)
 	AddListener(string, remoting.ConfigurationListener, ...Option)
 	RemoveListener(string, remoting.ConfigurationListener, ...Option)
 	GetConfig(string, ...Option) (string, error)
diff --git a/config_center/zookeeper/factory.go b/config_center/zookeeper/factory.go
index 3862db55b..29eb3a346 100644
--- a/config_center/zookeeper/factory.go
+++ b/config_center/zookeeper/factory.go
@@ -40,6 +40,7 @@ func (f *zookeeperDynamicConfigurationFactory) GetDynamicConfiguration(url *comm
 	var err error
 	once.Do(func() {
 		dynamicConfiguration, err = newZookeeperDynamicConfiguration(url)
+		dynamicConfiguration.SetParser(&config_center.DefaultConfigurationParser{})
 	})
 	return dynamicConfiguration, err
 
diff --git a/config_center/zookeeper/impl.go b/config_center/zookeeper/impl.go
index dd46a7606..a45d3f6b0 100644
--- a/config_center/zookeeper/impl.go
+++ b/config_center/zookeeper/impl.go
@@ -46,6 +46,7 @@ type zookeeperDynamicConfiguration struct {
 	listenerLock  sync.Mutex
 	listener      *zookeeper.ZkEventListener
 	cacheListener *CacheListener
+	parser        config_center.ConfigurationParser
 }
 
 func newZookeeperDynamicConfiguration(url *common.URL) (*zookeeperDynamicConfiguration, error) {
@@ -108,6 +109,13 @@ func (c *zookeeperDynamicConfiguration) GetConfigs(key string, opts ...config_ce
 	return c.GetConfig(key, opts...)
 }
 
+func (c *zookeeperDynamicConfiguration) Parser() config_center.ConfigurationParser {
+	return c.parser
+}
+func (c *zookeeperDynamicConfiguration) SetParser(p config_center.ConfigurationParser) {
+	c.parser = p
+}
+
 func (r *zookeeperDynamicConfiguration) ZkClient() *zookeeper.ZookeeperClient {
 	return r.client
 }
diff --git a/go.mod b/go.mod
index 4c2276429..d00f8e9f3 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module github.com/apache/dubbo-go
 require (
 	github.com/dubbogo/getty v1.0.7
 	github.com/dubbogo/hessian2 v1.0.1
+	github.com/magiconair/properties v1.8.1
 	github.com/pkg/errors v0.8.1
 	github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec
 	github.com/stretchr/testify v1.3.0
diff --git a/go.sum b/go.sum
index 973f80fc3..f00aec7e0 100644
--- a/go.sum
+++ b/go.sum
@@ -9,6 +9,8 @@ github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
 github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
 github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
+github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-- 
GitLab