github.com/wfusion/gofusion@v1.1.14/config/registry.go (about)

     1  package config
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log"
     7  	"reflect"
     8  	"sort"
     9  	"strings"
    10  	"sync"
    11  	"syscall"
    12  
    13  	"github.com/iancoleman/strcase"
    14  	"github.com/mitchellh/mapstructure"
    15  	"github.com/pkg/errors"
    16  	"github.com/wfusion/gofusion/common/utils/serialize/json"
    17  
    18  	"github.com/wfusion/gofusion/common/di"
    19  	"github.com/wfusion/gofusion/common/utils"
    20  	"github.com/wfusion/gofusion/common/utils/clone"
    21  )
    22  
    23  var (
    24  	Registry = &registry{di: di.Dig, initOnce: new(sync.Once), closeCh: make(chan struct{})}
    25  
    26  	initLocker   sync.RWMutex
    27  	registryLock sync.RWMutex
    28  	registryMap  = map[string]Configurable{"": Registry}
    29  )
    30  
    31  const (
    32  	componentConfigFieldName = "Base"
    33  )
    34  
    35  func Use(appName string, opts ...utils.OptionExtender) Configurable {
    36  	registryLock.RLock()
    37  	defer registryLock.RUnlock()
    38  	cfg, ok := registryMap[appName]
    39  	if !ok {
    40  		panic(errors.Errorf("app register config not found: %s", appName))
    41  	}
    42  	return cfg
    43  }
    44  
    45  func New(appName string) Configurable {
    46  	registryLock.Lock()
    47  	defer registryLock.Unlock()
    48  	if reg, ok := registryMap[appName]; ok {
    49  		return reg
    50  	}
    51  
    52  	reg := &registry{
    53  		di:       di.NewDI(),
    54  		appName:  appName,
    55  		initOnce: new(sync.Once),
    56  		closeCh:  make(chan struct{}),
    57  	}
    58  	registryMap[appName] = reg
    59  	return reg
    60  }
    61  
    62  type registry struct {
    63  	di                 di.DI
    64  	appName            string
    65  	debug              bool
    66  	loadComponentsOnce sync.Once
    67  	initOnce           *sync.Once
    68  	initWg             sync.WaitGroup
    69  	closeCh            chan struct{}
    70  
    71  	componentList      []*Component
    72  	businessConfig     any
    73  	businessConfigType reflect.Type
    74  	componentConfigs   any
    75  }
    76  
    77  type initOption struct {
    78  	debug          bool
    79  	bizCtx         context.Context
    80  	customLoadFunc loadConfigFunc
    81  	filenames      []string
    82  }
    83  
    84  func Ctx(ctx context.Context) utils.OptionFunc[initOption] {
    85  	return func(o *initOption) {
    86  		o.bizCtx = ctx
    87  	}
    88  }
    89  
    90  func Loader(fn func(any, ...utils.OptionExtender)) utils.OptionFunc[initOption] {
    91  	return func(o *initOption) {
    92  		o.customLoadFunc = fn
    93  	}
    94  }
    95  
    96  func Files(filenames []string) utils.OptionFunc[initOption] {
    97  	return func(o *initOption) {
    98  		o.filenames = filenames
    99  	}
   100  }
   101  
   102  func Debug() utils.OptionFunc[initOption] {
   103  	return func(o *initOption) {
   104  		o.debug = true
   105  	}
   106  }
   107  
   108  func (r *registry) Init(businessConfig any, opts ...utils.OptionExtender) (gracefully func()) {
   109  	initLocker.Lock()
   110  	defer initLocker.Unlock()
   111  
   112  	r.initWg.Add(1)
   113  	r.initOnce.Do(func() {
   114  		opt := utils.ApplyOptions[initOption](opts...)
   115  		r.debug = opt.debug
   116  		r.closeCh = make(chan struct{})
   117  
   118  		// context
   119  		parent := context.Background()
   120  		if opt.bizCtx != nil {
   121  			parent = opt.bizCtx
   122  		}
   123  
   124  		// load config function
   125  		loadFn := loadConfig
   126  		if opt.customLoadFunc != nil {
   127  			loadFn = opt.customLoadFunc
   128  		}
   129  
   130  		gracefully = r.initByConfigFile(parent, businessConfig, loadFn, opts...)
   131  	})
   132  	if gracefully == nil {
   133  		// give back
   134  		reflect.Indirect(reflect.ValueOf(businessConfig)).Set(reflect.ValueOf(r.businessConfig))
   135  
   136  		once := new(sync.Once)
   137  		gracefully = func() {
   138  			once.Do(func() {
   139  				r.initWg.Done()
   140  			})
   141  		}
   142  	}
   143  	return
   144  }
   145  
   146  func (r *registry) AddComponent(name string, constructor any, opts ...ComponentOption) {
   147  	if name[0] < 'A' || name[0] > 'Z' {
   148  		panic("component name should start with A-Z")
   149  	}
   150  	for idx, com := range r.componentList {
   151  		if com.name == name {
   152  			r.componentList = append(r.componentList[:idx], r.componentList[idx+1:]...)
   153  		}
   154  	}
   155  	opt := newOptions()
   156  	for _, fn := range opts {
   157  		fn(opt)
   158  	}
   159  
   160  	com := &Component{
   161  		name:       name,
   162  		isCore:     opt.isCoreComponent,
   163  		flagString: opt.flagValue,
   164  	}
   165  
   166  	hasYamlTag := false
   167  	hasJsonTag := false
   168  	hasTomlTag := false
   169  	for _, tag := range opt.tagList {
   170  		hasYamlTag = strings.HasPrefix(tag, "`yaml:")
   171  		hasJsonTag = strings.HasPrefix(tag, "`json:")
   172  		hasTomlTag = strings.HasPrefix(tag, "`toml:")
   173  	}
   174  	lowerName := strcase.ToSnake(name)
   175  	if name == ComponentI18n {
   176  		lowerName = strings.ToLower(name)
   177  	}
   178  	if !hasYamlTag {
   179  		opt.tagList = append(opt.tagList, fmt.Sprintf(`yaml:"%s"`, lowerName))
   180  	}
   181  	if !hasJsonTag {
   182  		opt.tagList = append(opt.tagList, fmt.Sprintf(`json:"%s"`, lowerName))
   183  	}
   184  	if !hasTomlTag {
   185  		opt.tagList = append(opt.tagList, fmt.Sprintf(`toml:"%s"`, lowerName))
   186  	}
   187  	if len(opt.tagList) > 0 {
   188  		com.tag = strings.Join(opt.tagList, " ")
   189  	}
   190  
   191  	com.constructor, com.constructorInputType = parseConstructor(constructor)
   192  
   193  	r.addComponent(com)
   194  }
   195  
   196  func (r *registry) LoadComponentConfig(name string, componentConfig any) (err error) {
   197  	val := reflect.ValueOf(componentConfig)
   198  	typ := val.Type()
   199  	if typ.Kind() != reflect.Ptr {
   200  		return errors.New("componentConfig should be pointer")
   201  	}
   202  
   203  	var found bool
   204  	for _, com := range r.componentList {
   205  		if com.name == name {
   206  			found = true
   207  			break
   208  		}
   209  	}
   210  	if !found {
   211  		return errors.Errorf("no such component [%s]", name)
   212  	}
   213  
   214  	// load config
   215  	if r.componentConfigs == nil {
   216  		return
   217  	}
   218  	componentConfigsValue := utils.IndirectValue(reflect.ValueOf(clone.Clone(r.componentConfigs)))
   219  	if !componentConfigsValue.IsValid() {
   220  		return errors.Errorf("component configs not initialize now [%s]", name)
   221  	}
   222  	componentConfigValue := componentConfigsValue.FieldByName(componentConfigFieldName).FieldByName(name)
   223  
   224  	if componentConfigValue.Type().Kind() == reflect.Ptr {
   225  		if componentConfigValue.IsNil() {
   226  			return
   227  		}
   228  		componentConfigValue = componentConfigValue.Elem()
   229  	}
   230  	if componentConfigValue.Type() == typ.Elem() || componentConfigValue.Type().ConvertibleTo(typ.Elem()) {
   231  		val.Elem().Set(reflect.ValueOf(clone.Clone(componentConfigValue.Convert(typ.Elem()).Interface())))
   232  		return
   233  	}
   234  
   235  	decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   236  		Metadata:         nil,
   237  		Result:           componentConfig,
   238  		TagName:          "yaml",
   239  		WeaklyTypedInput: true,
   240  	})
   241  	if err != nil {
   242  		return
   243  	}
   244  	return decoder.Decode(componentConfigValue.Interface())
   245  }
   246  
   247  func (r *registry) GetAllConfigs() any {
   248  	val := reflect.New(r.makeAllConfigStruct())
   249  	derefVal := reflect.Indirect(val)
   250  
   251  	// business configs
   252  	businessConfigsVal := reflect.Indirect(reflect.ValueOf(r.businessConfig))
   253  	numFields := businessConfigsVal.NumField()
   254  	for i := 0; i < numFields; i++ {
   255  		derefVal.Field(i + 1).Set(businessConfigsVal.Field(i))
   256  	}
   257  
   258  	// component configs
   259  	derefComponentConfigsVal := derefVal.FieldByName(componentConfigFieldName)
   260  	componentConfigsVal := reflect.Indirect(reflect.ValueOf(r.componentConfigs)).FieldByName(componentConfigFieldName)
   261  	numFields = componentConfigsVal.NumField()
   262  	for i := 0; i < numFields; i++ {
   263  		derefComponentConfigsVal.Field(i).Set(componentConfigsVal.Field(i))
   264  	}
   265  	return clone.Clone(val.Interface())
   266  }
   267  
   268  func (r *registry) initByConfigFile(parent context.Context, businessConfig any,
   269  	loadFn loadConfigFunc, opts ...utils.OptionExtender) func() {
   270  	r.loadComponents()
   271  	r.checkBusinessConfig(businessConfig)
   272  
   273  	businessConfigVal := reflect.ValueOf(businessConfig)
   274  	r.businessConfigType = utils.IndirectType(businessConfigVal.Type())
   275  	r.businessConfig = reflect.New(r.businessConfigType).Interface()
   276  	r.componentConfigs = reflect.New(r.makeComponentsConfigStruct()).Interface()
   277  
   278  	r.initAllConfigByLoadFunc(loadFn, opts...)
   279  	r.initAllConfigByFlag()
   280  
   281  	appName := r.AppName()
   282  	registryLock.Lock()
   283  	if _, ok := registryMap[appName]; !ok {
   284  		registryMap[appName] = r
   285  	}
   286  	registryLock.Unlock()
   287  
   288  	// decrypt
   289  	CryptoDecryptByTag(r.businessConfig, AppName(r.AppName()))
   290  	CryptoDecryptByTag(r.componentConfigs, AppName(r.AppName()))
   291  
   292  	// give back
   293  	reflect.Indirect(reflect.ValueOf(businessConfig)).Set(reflect.ValueOf(r.businessConfig))
   294  
   295  	return r.initComponents(parent)
   296  }
   297  
   298  func (r *registry) getBaseObject() reflect.Value {
   299  	return reflect.Indirect(reflect.ValueOf(r.componentConfigs)).FieldByName(componentConfigFieldName)
   300  }
   301  
   302  func (r *registry) makeComponentsConfigStruct() reflect.Type {
   303  	fieldList := r.makeComponentsConfigFields()
   304  	return reflect.StructOf([]reflect.StructField{
   305  		{
   306  			Name:      componentConfigFieldName,
   307  			Type:      reflect.StructOf(fieldList),
   308  			Tag:       `yaml:"base" json:"base" toml:"base"`,
   309  			Anonymous: true,
   310  		},
   311  	})
   312  }
   313  
   314  func (r *registry) makeComponentsConfigFields() []reflect.StructField {
   315  	fieldList := make([]reflect.StructField, len(r.componentList))
   316  	for i := 0; i < len(r.componentList); i++ {
   317  		component := r.componentList[i]
   318  		fieldList[i] = reflect.StructField{
   319  			Name: component.name,
   320  			Type: component.constructorInputType,
   321  			Tag:  reflect.StructTag(component.tag),
   322  		}
   323  	}
   324  
   325  	return fieldList
   326  }
   327  
   328  func (r *registry) makeAllConfigStruct() reflect.Type {
   329  	/* AllConfig struct may look like below
   330  	type AllConfig struct {
   331  		XXXBase struct {
   332  			Debug       bool
   333  			App         string
   334  			DB          map[string]*db.Conf
   335  			Redis       map[string]*redis.Conf
   336  			Log         *log.Conf
   337  			...
   338  		} `yaml:"base" json:"base" toml:"base"`
   339  
   340  		BusinessConfigField1
   341  	    BusinessConfigField2
   342  		BusinessConfigField3
   343  
   344  		...
   345  	}
   346  	*/
   347  
   348  	numFields := r.businessConfigType.NumField()
   349  	fieldList := make([]reflect.StructField, 0, numFields+1)
   350  	fieldList = append(fieldList, reflect.StructField{
   351  		Name:      componentConfigFieldName,
   352  		Type:      reflect.StructOf(r.makeComponentsConfigFields()),
   353  		Tag:       `yaml:"base" json:"base" toml:"base"`,
   354  		Anonymous: true,
   355  	})
   356  	for i := 0; i < numFields; i++ {
   357  		fieldList = append(fieldList, r.businessConfigType.Field(i))
   358  	}
   359  
   360  	return reflect.StructOf(fieldList)
   361  }
   362  
   363  func (r *registry) loadComponents() {
   364  	r.loadComponentsOnce.Do(func() {
   365  		// app
   366  		r.AddComponent(ComponentApp, func(context.Context, string, ...utils.OptionExtender) func() { return nil },
   367  			WithTag("yaml", "app"), WithTag("json", "app"), WithTag("toml", "app"),
   368  			WithFlag(utils.AnyPtr("null")),
   369  		)
   370  
   371  		// debug
   372  		r.AddComponent(ComponentDebug, func(context.Context, bool, ...utils.OptionExtender) func() { return nil },
   373  			WithTag("yaml", "debug"), WithTag("json", "debug"), WithTag("toml", "debug"),
   374  			WithFlag(utils.AnyPtr("null")),
   375  		)
   376  
   377  		// crypto
   378  		r.AddComponent(ComponentCrypto, CryptoConstruct,
   379  			WithTag("yaml", "crypto"), WithTag("json", "crypto"), WithTag("toml", "crypto"),
   380  			WithFlag(&cryptoFlagString),
   381  		)
   382  
   383  		for _, item := range getComponents() {
   384  			r.AddComponent(item.name, item.constructor, item.opt...)
   385  		}
   386  
   387  		/* example */
   388  		// registry.AddComponent("ComponentExample", func(context.Context, string) func() { return nil },
   389  		//    WithTag("custom_tag", "val"), WithTag("yaml", "val"))
   390  	})
   391  }
   392  
   393  func (r *registry) initAllConfigByLoadFunc(loadFn loadConfigFunc, opts ...utils.OptionExtender) {
   394  	if loadFn != nil {
   395  		loadFn(r.businessConfig, opts...)
   396  		loadFn(r.componentConfigs, opts...)
   397  	}
   398  }
   399  
   400  func (r *registry) initAllConfigByFlag() {
   401  	configVal := utils.IndirectValue(reflect.ValueOf(r.componentConfigs)).FieldByName(componentConfigFieldName)
   402  	for i := 0; i < len(r.componentList); i++ {
   403  		com := r.componentList[i]
   404  		if utils.IsStrPtrBlank(com.flagString) {
   405  			continue
   406  		}
   407  		switch com.name {
   408  		case ComponentApp:
   409  			if utils.IsStrNotBlank(appFlagString) {
   410  				configVal.FieldByName(com.name).SetString(appFlagString)
   411  			}
   412  		case ComponentDebug:
   413  			if debugFlag {
   414  				configVal.FieldByName(com.name).SetBool(debugFlag)
   415  			}
   416  		default:
   417  			comValp := configVal.FieldByName(com.name).Addr()
   418  			utils.MustSuccess(json.Unmarshal([]byte(*com.flagString), comValp.Interface()))
   419  
   420  			// process defaults
   421  			_ = utils.ParseTag(comValp.Interface(), utils.ParseTagName("default"),
   422  				utils.ParseTagUnmarshalType(utils.UnmarshalTypeYaml))
   423  		}
   424  	}
   425  
   426  	if len(appBizFlagString) > 0 {
   427  		utils.MustSuccess(json.Unmarshal([]byte(appBizFlagString), &r.businessConfig))
   428  	}
   429  }
   430  
   431  func (r *registry) initComponents(parent context.Context) func() {
   432  	ctx, cancel := context.WithCancel(parent)
   433  	ctxVal := reflect.ValueOf(ctx)
   434  	o1 := reflect.ValueOf(utils.OptionExtender(AppName(r.appName)))
   435  	o2 := reflect.ValueOf(utils.OptionExtender(DI(r.di)))
   436  
   437  	baseObject := r.getBaseObject()
   438  	destructors := make([]reflect.Value, 0, len(r.componentList))
   439  	componentNames := make([]string, 0, len(r.componentList))
   440  	hasCallbackComponentNames := make([]string, 0, len(r.componentList))
   441  	for i := 0; i < len(r.componentList); i++ {
   442  		com := r.componentList[i]
   443  		comArgs := reflect.ValueOf(clone.Clone(baseObject.FieldByName(com.name).Interface()))
   444  		componentNames = append(componentNames, com.name)
   445  		if out := com.constructor.Call([]reflect.Value{ctxVal, comArgs, o1, o2}); len(out) > 0 && !out[0].IsNil() {
   446  			destructors = append(destructors, out[0])
   447  			hasCallbackComponentNames = append(hasCallbackComponentNames, com.name)
   448  		}
   449  	}
   450  
   451  	/* print summary to stdout */
   452  	pid := syscall.Getpid()
   453  	app := r.AppName()
   454  	log.SetFlags(log.Lshortfile | log.Ldate | log.Lmicroseconds)
   455  	log.Printf("%v [Gofusion] %s initialized total %d components below: %s\n",
   456  		pid, app, len(componentNames), strings.Join(componentNames, ", "))
   457  
   458  	once := new(sync.Once)
   459  	return func() {
   460  		once.Do(func() {
   461  			initLocker.Lock()
   462  			defer initLocker.Unlock()
   463  
   464  			defer close(r.closeCh)
   465  
   466  			r.initWg.Done()
   467  			r.initWg.Wait()
   468  			cancel()
   469  			for i := len(destructors) - 1; i >= 0; i-- {
   470  				log.Printf("%v [Gofusion] %s %s exiting...", pid, app, hasCallbackComponentNames[i])
   471  				destructors[i].Call(nil)
   472  				log.Printf("%v [Gofusion] %s %s exited", pid, app, hasCallbackComponentNames[i])
   473  			}
   474  
   475  			r.di.Clear()
   476  			r.businessConfig = nil
   477  			r.componentConfigs = nil
   478  			r.initOnce = new(sync.Once)
   479  		})
   480  	}
   481  }
   482  
   483  func (r *registry) addComponent(com *Component) {
   484  	firstNonCoreComIndex := -1
   485  	for i, cp := range r.componentList {
   486  		if !cp.isCore {
   487  			firstNonCoreComIndex = i
   488  			break
   489  		}
   490  	}
   491  	if !com.isCore || firstNonCoreComIndex == -1 {
   492  		r.componentList = append(r.componentList, com)
   493  		sort.SliceStable(r.componentList, func(i, j int) bool {
   494  			// core component would not be sorted
   495  			if r.componentList[i].isCore || r.componentList[j].isCore {
   496  				return false
   497  			}
   498  
   499  			orderA := indexComponent(r.componentList[i].name)
   500  			if orderA == -1 {
   501  				return false
   502  			}
   503  			orderB := indexComponent(r.componentList[j].name)
   504  			if orderB == -1 {
   505  				return true
   506  			}
   507  
   508  			return orderA < orderB
   509  		})
   510  
   511  		return
   512  	}
   513  	list := make([]*Component, len(r.componentList)+1)
   514  	for i := range list {
   515  		if i < firstNonCoreComIndex {
   516  			list[i] = r.componentList[i]
   517  		} else if i == firstNonCoreComIndex {
   518  			list[i] = com
   519  		} else {
   520  			list[i] = r.componentList[i-1]
   521  		}
   522  	}
   523  
   524  	r.componentList = list
   525  }
   526  
   527  func (r *registry) checkBusinessConfig(businessConfig any) {
   528  	typ := reflect.TypeOf(businessConfig)
   529  	if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Ptr {
   530  		panic(errors.New("businessConfig should be a **struct"))
   531  	}
   532  }