| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423 |
- // Copyright 2019 Yunion
- //
- // 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 db
- import (
- "context"
- "reflect"
- "yunion.io/x/jsonutils"
- "yunion.io/x/log"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/pkg/gotypes"
- "yunion.io/x/pkg/util/version"
- "yunion.io/x/sqlchemy"
- "yunion.io/x/onecloud/pkg/apis"
- "yunion.io/x/onecloud/pkg/httperrors"
- "yunion.io/x/onecloud/pkg/mcclient"
- "yunion.io/x/onecloud/pkg/util/stringutils2"
- )
- type Caller struct {
- modelVal reflect.Value
- funcName string
- inputs []interface{}
- funcVal reflect.Value
- }
- func NewCaller(model interface{}, fName string) *Caller {
- return &Caller{
- modelVal: reflect.ValueOf(model),
- funcName: fName,
- }
- }
- func (c *Caller) Inputs(inputs ...interface{}) *Caller {
- c.inputs = inputs
- return c
- }
- func (c *Caller) Call() ([]reflect.Value, error) {
- return callObject(c.modelVal, c.funcName, c.inputs...)
- }
- func call(obj interface{}, fName string, inputs ...interface{}) ([]reflect.Value, error) {
- return callObject(reflect.ValueOf(obj), fName, inputs...)
- }
- func findFunc(modelVal reflect.Value, fName string) (reflect.Value, error) {
- funcVal := modelVal.MethodByName(fName)
- if !funcVal.IsValid() || funcVal.IsNil() {
- log.Debugf("find method %s for %s", fName, modelVal.Type())
- if modelVal.Kind() != reflect.Ptr {
- return funcVal, errors.Wrapf(httperrors.ErrNotImplemented, "%s not implemented", fName)
- }
- modelVal = modelVal.Elem()
- if modelVal.Kind() != reflect.Struct {
- return funcVal, errors.Wrapf(httperrors.ErrNotImplemented, "%s not implemented", fName)
- }
- modelType := modelVal.Type()
- for i := 0; i < modelType.NumField(); i += 1 {
- fieldType := modelType.Field(i)
- if fieldType.Anonymous {
- fieldValue := modelVal.Field(i)
- if fieldValue.Kind() != reflect.Ptr && fieldValue.CanAddr() {
- newFuncVal, err := findFunc(fieldValue.Addr(), fName)
- if err == nil {
- if !funcVal.IsValid() || funcVal.IsNil() {
- funcVal = newFuncVal
- } else {
- return funcVal, errors.Wrapf(httperrors.ErrConflict, "%s is ambiguous", fName)
- }
- }
- } else if fieldValue.Kind() == reflect.Ptr {
- newFuncVal, err := findFunc(fieldValue, fName)
- if err == nil {
- if !funcVal.IsValid() || funcVal.IsNil() {
- funcVal = newFuncVal
- } else {
- return funcVal, errors.Wrapf(httperrors.ErrConflict, "%s is ambiguous", fName)
- }
- }
- }
- }
- }
- if !funcVal.IsValid() || funcVal.IsNil() {
- return funcVal, errors.Wrapf(httperrors.ErrNotImplemented, "%s is not implemented", fName)
- }
- }
- return funcVal, nil
- }
- const (
- MethodNotFoundError = errors.Error("MethodNotFoundError")
- )
- func callObject(modelVal reflect.Value, fName string, inputs ...interface{}) ([]reflect.Value, error) {
- funcVal := modelVal.MethodByName(fName)
- if !funcVal.IsValid() || funcVal.IsNil() {
- return nil, errors.Wrapf(MethodNotFoundError, "%s method not found, please check service version, current version: %s", fName, version.GetShortString())
- }
- return callFunc(funcVal, fName, inputs...)
- }
- func callFunc(funcVal reflect.Value, fName string, inputs ...interface{}) ([]reflect.Value, error) {
- funcType := funcVal.Type()
- paramLen := funcType.NumIn()
- if paramLen != len(inputs) {
- return nil, httperrors.NewInternalServerError("%s method params length not match, expected %d, input %d", fName, paramLen, len(inputs))
- }
- params := make([]*param, paramLen)
- for i := range inputs {
- params[i] = newParam(funcType.In(i), inputs[i])
- }
- args, err := convertParams(params)
- if err != nil {
- return nil, err
- }
- return funcVal.Call(args), nil
- }
- func convertParams(params []*param) ([]reflect.Value, error) {
- ret := make([]reflect.Value, 0)
- for _, p := range params {
- val, err := p.convert()
- if err != nil {
- return ret, err
- }
- ret = append(ret, val)
- }
- return ret, nil
- }
- type param struct {
- pType reflect.Type
- input interface{}
- }
- func newParam(pType reflect.Type, input interface{}) *param {
- return ¶m{
- pType: pType,
- input: input,
- }
- }
- func isJSONObject(input interface{}) (jsonutils.JSONObject, bool) {
- val := reflect.ValueOf(input)
- obj, ok := val.Interface().(jsonutils.JSONObject)
- if !ok {
- return nil, false
- }
- return obj, true
- }
- func (p *param) convert() (reflect.Value, error) {
- if p.input == nil {
- return reflect.New(p.pType).Elem(), nil
- }
- obj, ok := isJSONObject(p.input)
- if !ok {
- return reflect.ValueOf(p.input), nil
- }
- // generate object by type
- val := reflect.New(p.pType)
- err := obj.Unmarshal(val.Interface())
- if err != nil {
- return reflect.Value{}, errors.Wrapf(err, "unable to convert '%v' to Type %q", p.input, p.pType.Name())
- }
- return val.Elem(), nil
- }
- func ValueToJSONObject(out reflect.Value) jsonutils.JSONObject {
- return _valueToJSONObject(out, false)
- }
- func _valueToJSONObject(out reflect.Value, allFields bool) jsonutils.JSONObject {
- if gotypes.IsNil(out.Interface()) {
- return nil
- }
- if obj, ok := isJSONObject(out); ok {
- return obj
- }
- if allFields {
- return jsonutils.MarshalAll(out.Interface())
- } else {
- return jsonutils.Marshal(out.Interface())
- }
- }
- func ValueToJSONDict(out reflect.Value) *jsonutils.JSONDict {
- return _valueToJSONDict(out, false)
- }
- func _valueToJSONDict(out reflect.Value, allFields bool) *jsonutils.JSONDict {
- jsonObj := _valueToJSONObject(out, allFields)
- if jsonObj == nil {
- return nil
- }
- return jsonObj.(*jsonutils.JSONDict)
- }
- func ValueToError(out reflect.Value) error {
- errVal := out.Interface()
- if !gotypes.IsNil(errVal) {
- return errVal.(error)
- }
- return nil
- }
- func mergeInputOutputData(input *jsonutils.JSONDict, resVal reflect.Value) *jsonutils.JSONDict {
- output := _valueToJSONDict(resVal, true)
- // preserve the input info not returned by caller
- ret := input.Copy()
- jsonMap, _ := output.GetMap()
- for k, v := range jsonMap {
- if input.Contains(k) && v == jsonutils.JSONNull {
- ret.Remove(k)
- continue
- }
- if v != jsonutils.JSONNull && !v.IsZero() {
- ret.Set(k, v)
- }
- }
- return ret
- }
- func ValidateCreateData(funcName string, manager IModelManager, ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
- ret, err := call(manager, funcName, ctx, userCred, ownerId, query, data)
- if err != nil {
- return nil, httperrors.NewGeneralError(err)
- }
- if len(ret) != 2 {
- return nil, httperrors.NewInternalServerError("Invald %s return value", funcName)
- }
- resVal := ret[0]
- if err := ValueToError(ret[1]); err != nil {
- return nil, err
- }
- return mergeInputOutputData(data, resVal), nil
- }
- func ExpandBatchCreateData(manager IModelManager, ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict, index int) (*jsonutils.JSONDict, error) {
- funcName := "ExpandBatchCreateData"
- ret, err := call(manager, funcName, ctx, userCred, ownerId, query, data, index)
- if err != nil {
- return nil, errors.Wrapf(err, "call %s", funcName)
- }
- if len(ret) != 2 {
- return nil, httperrors.NewInternalServerError("Invald %s return value", funcName)
- }
- resVal := ret[0]
- if err := ValueToError(ret[1]); err != nil {
- return nil, errors.Wrap(err, "ValueToError")
- }
- return mergeInputOutputData(data, resVal), nil
- }
- func ListItemFilter(manager IModelManager, ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
- return _callListQueryFilter(manager, "ListItemFilter", ctx, q, userCred, query)
- }
- func ExtendListQuery(manager IModelManager, ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
- return _callListQueryFilter(manager, "ExtendListQuery", ctx, q, userCred, query)
- }
- func _callListQueryFilter(manager IModelManager, funcName string, ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
- ret, err := call(manager, funcName, ctx, q, userCred, query)
- if err != nil {
- return nil, httperrors.NewGeneralError(err)
- }
- if len(ret) != 2 {
- return nil, httperrors.NewInternalServerError("Invald %s return value count %d", funcName, len(ret))
- }
- if err := ValueToError(ret[1]); err != nil {
- return nil, err
- }
- return ret[0].Interface().(*sqlchemy.SQuery), nil
- }
- func OrderByExtraFields(
- manager IModelManager,
- ctx context.Context,
- q *sqlchemy.SQuery,
- userCred mcclient.TokenCredential,
- query jsonutils.JSONObject,
- ) (*sqlchemy.SQuery, error) {
- ret, err := call(manager, "OrderByExtraFields", ctx, q, userCred, query)
- if err != nil {
- return nil, httperrors.NewGeneralError(err)
- }
- if len(ret) != 2 {
- return nil, httperrors.NewInternalServerError("Invald OrderByExtraFields return value count %d", len(ret))
- }
- if err := ValueToError(ret[1]); err != nil {
- return nil, err
- }
- return ret[0].Interface().(*sqlchemy.SQuery), nil
- }
- func FetchCustomizeColumns(
- manager IModelManager,
- ctx context.Context,
- userCred mcclient.TokenCredential,
- query jsonutils.JSONObject,
- objs []interface{},
- fields stringutils2.SSortedStrings,
- isList bool,
- ) ([]*jsonutils.JSONDict, error) {
- ret, err := call(manager, "FetchCustomizeColumns", ctx, userCred, query, objs, fields, isList)
- if err != nil {
- return nil, httperrors.NewGeneralError(err)
- }
- if len(ret) != 1 {
- return nil, httperrors.NewInternalServerError("Invalid FetchCustomizeColumns return value count %d", len(ret))
- }
- if ret[0].IsNil() {
- return nil, nil
- }
- if ret[0].Kind() != reflect.Slice {
- return nil, httperrors.NewInternalServerError("Invalid FetchCustomizeColumns return value type, not a slice!")
- }
- if ret[0].Len() != len(objs) {
- return nil, httperrors.NewInternalServerError("Invalid FetchCustomizeColumns return value, inconsistent obj count: input %d != output %d", len(objs), ret[0].Len())
- }
- showReason := false
- if query.Contains("show_fail_reason") {
- showReason = true
- }
- retVal := make([]*jsonutils.JSONDict, ret[0].Len())
- for i := 0; i < ret[0].Len(); i += 1 {
- jsonDict := ValueToJSONDict(ret[0].Index(i))
- // NOTE: don't use obj update jsonDict as retval
- jsonDict.Update(jsonutils.Marshal(objs[i]).(*jsonutils.JSONDict))
- out := apis.ModelBaseDetails{
- CanDelete: true,
- CanUpdate: true,
- }
- err = ValidateDeleteCondition(objs[i].(IModel), ctx, jsonDict)
- if err != nil {
- out.CanDelete = false
- if showReason {
- out.DeleteFailReason = httperrors.NewErrorFromGeneralError(ctx, err)
- }
- }
- err = ValidateUpdateCondition(objs[i].(IModel), ctx)
- if err != nil {
- out.CanUpdate = false
- if showReason {
- out.UpdateFailReason = httperrors.NewErrorFromGeneralError(ctx, err)
- }
- }
- jsonDict.Update(jsonutils.Marshal(out))
- retVal[i] = jsonDict
- }
- return retVal, nil
- }
- func ValidateUpdateData(model IModel, ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
- ret, err := call(model, "ValidateUpdateData", ctx, userCred, query, data)
- if err != nil {
- return nil, httperrors.NewGeneralError(err)
- }
- if len(ret) != 2 {
- return nil, httperrors.NewInternalServerError("Invald ValidateUpdateData return value")
- }
- resVal := ret[0]
- if err := ValueToError(ret[1]); err != nil {
- return nil, err
- }
- return mergeInputOutputData(data, resVal), nil
- }
- func CustomizeDelete(model IModel, ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
- ret, err := call(model, "CustomizeDelete", ctx, userCred, query, data)
- if err != nil {
- return httperrors.NewGeneralError(err)
- }
- if len(ret) != 1 {
- return httperrors.NewInternalServerError("Invald CustomizeDelete return value")
- }
- return ValueToError(ret[0])
- }
- func ValidateDeleteCondition(model IModel, ctx context.Context, data jsonutils.JSONObject) error {
- ret, err := call(model, "ValidateDeleteCondition", ctx, data)
- if err != nil {
- return httperrors.NewGeneralError(err)
- }
- if len(ret) != 1 {
- return httperrors.NewInternalServerError("Invald ValidateDeleteCondition return value")
- }
- return ValueToError(ret[0])
- }
- func ValidateUpdateCondition(model IModel, ctx context.Context) error {
- ret, err := call(model, "ValidateUpdateCondition", ctx)
- if err != nil {
- return httperrors.NewGeneralError(err)
- }
- if len(ret) != 1 {
- return httperrors.NewInternalServerError("Invald ValidateUpdateCondition return value")
- }
- return ValueToError(ret[0])
- }
|