diff --git a/service/service-registry.go b/service/service-registry.go new file mode 100644 index 0000000..ca770b6 --- /dev/null +++ b/service/service-registry.go @@ -0,0 +1,241 @@ +package service + +import ( + "fmt" + "reflect" + "strings" + "sync" + "unicode" + "unicode/utf8" +) + +var ( + // Precompute the reflect.Type of error and http.Request + typeOfError = reflect.TypeOf((*error)(nil)).Elem() +) + +// ---------------------------------------------------------------------------- +// service +// ---------------------------------------------------------------------------- + +type ServiceMeta struct { + name string // name of service + rcvrV reflect.Value // receiver of methods for the service + rcvrT reflect.Type // type of the receiver + methods map[string]*MethodMeta // registered methods +} + +func (sm *ServiceMeta) ReceiverType() reflect.Type { + return sm.rcvrT +} + +func (sm *ServiceMeta) ReceiverValue() reflect.Value { + return sm.rcvrV +} + +type MethodMeta struct { + method reflect.Method // receiver method + paramTypes []reflect.Type // type of the request argument + returnType reflect.Type // type of the response argument +} + +func (mm *MethodMeta) Call(in []reflect.Value) []reflect.Value { + return mm.method.Func.Call(in) +} + +func (mm *MethodMeta) ParamValues() (values []reflect.Value, instances []interface{}) { + if nil == mm.paramTypes || 0 == len(mm.paramTypes) { + return nil, nil + } + + pCount := len(mm.paramTypes) + values = make([]reflect.Value, pCount) + instances = make([]interface{}, pCount) + + for indexI := 0; indexI < pCount; indexI++ { + values[indexI] = getValue(mm.paramTypes[indexI]) + instances[indexI] = values[indexI].Interface() + } + + return +} + +func getValue(t reflect.Type) reflect.Value { + rt := t + if rt.Kind() == reflect.Ptr { + rt = rt.Elem() + } + + // rv := reflect.New(rt) + // if rt.Kind() != reflect.Struct { + // rv = reflect.Indirect(rv) + // } + + // var rv reflect.Value + + // switch rt.Kind() { + // case reflect.Slice: + // rv = reflect.New(reflect.SliceOf(rt.Elem())) + // default: + // rv = reflect.New(rt) + // } + + return reflect.New(rt) +} + +// ---------------------------------------------------------------------------- +// ServiceRegistry +// ---------------------------------------------------------------------------- + +// ServiceRegistry is a registry for services. +type ServiceRegistry struct { + mutex sync.RWMutex + services map[string]*ServiceMeta +} + +func (sm *ServiceRegistry) GetService(name string) interface{} { + sm.mutex.RLock() + defer sm.mutex.RUnlock() + if sm.services == nil { + return nil + } + return sm.services[name] +} + +// register adds a new service using reflection to extract its methods. +func (sm *ServiceRegistry) Register(rcvr interface{}, name string) error { + // Setup service. + s := &ServiceMeta{ + name: name, + rcvrV: reflect.ValueOf(rcvr), + rcvrT: reflect.TypeOf(rcvr), + methods: make(map[string]*MethodMeta), + } + if name == "" { + s.name = reflect.Indirect(s.rcvrV).Type().Name() + if !isExported(s.name) { + return fmt.Errorf("ServiceRegistry: type %q is not exported", s.name) + } + } + if s.name == "" { + return fmt.Errorf("ServiceRegistry: no service name for type %q", + s.rcvrT.String()) + } + + var err error + // Setup methods. +Loop: + for i := 0; i < s.rcvrT.NumMethod(); i++ { + m := s.rcvrT.Method(i) + mt := m.Type + // Method must be exported. + if m.PkgPath != "" { + continue + } + + var paramTypes []reflect.Type + var returnType reflect.Type + + pCount := mt.NumIn() - 1 + + if 0 < pCount { + paramTypes = make([]reflect.Type, pCount) + + for indexI := 0; indexI < pCount; indexI++ { + pt := mt.In(indexI + 1) + if err = validateType(mt.In(indexI + 1)); nil != err { + return err + } + paramTypes[indexI] = pt + } + } + + switch mt.NumOut() { + case 1: + if t := mt.Out(0); t != typeOfError { + continue Loop + } + case 2: + if t := mt.Out(0); !isExportedOrBuiltin(t) { + continue Loop + } + + if t := mt.Out(1); t != typeOfError { + continue Loop + } + rt := mt.Out(0) + if err = validateType(rt); nil != err { + return err + } + returnType = rt + default: + continue + } + + s.methods[m.Name] = &MethodMeta{ + method: m, + paramTypes: paramTypes, + returnType: returnType, + } + } + if len(s.methods) == 0 { + return fmt.Errorf("ServiceRegistry: %q has no exported methods of suitable type", s.name) + } + // Add to the map. + sm.mutex.Lock() + defer sm.mutex.Unlock() + if sm.services == nil { + sm.services = make(map[string]*ServiceMeta) + } else if _, ok := sm.services[s.name]; ok { + return fmt.Errorf("ServiceRegistry: service already defined: %q", s.name) + } + sm.services[s.name] = s + return nil +} + +func validateType(t reflect.Type) error { + if t.Kind() == reflect.Struct { + return fmt.Errorf("Type is Struct. Pass by reference, i.e. *%s", t) + } + return nil +} + +// get returns a registered service given a method name. +// +// The method name uses a dotted notation as in "Service.Method". +func (sm *ServiceRegistry) Get(method string) (*ServiceMeta, *MethodMeta, error) { + parts := strings.Split(method, ".") + if len(parts) != 2 { + err := fmt.Errorf("ServiceRegistry: service/method request ill-formed: %q", method) + return nil, nil, err + } + sm.mutex.Lock() + service := sm.services[parts[0]] + sm.mutex.Unlock() + if service == nil { + err := fmt.Errorf("ServiceRegistry: can't find service %q", method) + return nil, nil, err + } + MethodMeta := service.methods[parts[1]] + if MethodMeta == nil { + err := fmt.Errorf("ServiceRegistry: can't find method %q", method) + return nil, nil, err + } + return service, MethodMeta, nil +} + +// isExported returns true of a string is an exported (upper case) name. +func isExported(name string) bool { + rune, _ := utf8.DecodeRuneInString(name) + return unicode.IsUpper(rune) +} + +// isExportedOrBuiltin returns true if a type is exported or a builtin. +func isExportedOrBuiltin(t reflect.Type) bool { + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + // PkgPath will be non-empty even for an exported type, + // so we need to check the type name as well. + return isExported(t.Name()) || t.PkgPath() == "" +}