mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-06-23 13:07:49 +08:00
326 lines
9.7 KiB
Go
326 lines
9.7 KiB
Go
// 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"
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"yunion.io/x/jsonutils"
|
|
"yunion.io/x/log"
|
|
"yunion.io/x/pkg/errors"
|
|
"yunion.io/x/pkg/gotypes"
|
|
"yunion.io/x/sqlchemy"
|
|
|
|
"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
|
|
}
|
|
|
|
func callObject(modelVal reflect.Value, fName string, inputs ...interface{}) ([]reflect.Value, error) {
|
|
funcVal := modelVal.MethodByName(fName)
|
|
return callFunc(funcVal, fName, inputs...)
|
|
}
|
|
|
|
func callFunc(funcVal reflect.Value, fName string, inputs ...interface{}) ([]reflect.Value, error) {
|
|
if !funcVal.IsValid() || funcVal.IsNil() {
|
|
return nil, httperrors.NewActionNotFoundError(fmt.Sprintf("%s method not found", fName))
|
|
}
|
|
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 := convertParams(params)
|
|
return funcVal.Call(args), nil
|
|
}
|
|
|
|
func convertParams(params []*param) []reflect.Value {
|
|
ret := make([]reflect.Value, 0)
|
|
for _, p := range params {
|
|
ret = append(ret, p.convert())
|
|
}
|
|
return ret
|
|
}
|
|
|
|
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 {
|
|
if p.input == nil {
|
|
return reflect.New(p.pType).Elem()
|
|
}
|
|
obj, ok := isJSONObject(p.input)
|
|
if !ok {
|
|
return reflect.ValueOf(p.input)
|
|
}
|
|
// generate object by type
|
|
val := reflect.New(p.pType)
|
|
obj.Unmarshal(val.Interface())
|
|
return val.Elem()
|
|
}
|
|
|
|
func ValueToJSONObject(out reflect.Value) jsonutils.JSONObject {
|
|
if gotypes.IsNil(out.Interface()) {
|
|
return nil
|
|
}
|
|
|
|
if obj, ok := isJSONObject(out); ok {
|
|
return obj
|
|
}
|
|
return jsonutils.Marshal(out.Interface())
|
|
}
|
|
|
|
func ValueToJSONDict(out reflect.Value) *jsonutils.JSONDict {
|
|
jsonObj := ValueToJSONObject(out)
|
|
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(data *jsonutils.JSONDict, resVal reflect.Value) *jsonutils.JSONDict {
|
|
retJson := ValueToJSONDict(resVal)
|
|
// preserve the input info not returned by caller
|
|
data.Update(retJson)
|
|
return data
|
|
}
|
|
|
|
func ValidateCreateData(manager IModelManager, ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
|
|
ret, err := call(manager, "ValidateCreateData", ctx, userCred, ownerId, query, data)
|
|
if err != nil {
|
|
return nil, httperrors.NewGeneralError(err)
|
|
}
|
|
if len(ret) != 2 {
|
|
return nil, httperrors.NewInternalServerError("Invald ValidateCreateData return value")
|
|
}
|
|
resVal := ret[0]
|
|
if err := ValueToError(ret[1]); err != nil {
|
|
return nil, err
|
|
}
|
|
return mergeInputOutputData(data, resVal), nil
|
|
}
|
|
|
|
func ListItemFilter(manager IModelManager, ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
|
|
ret, err := call(manager, "ListItemFilter", ctx, q, userCred, query)
|
|
if err != nil {
|
|
return nil, httperrors.NewGeneralError(err)
|
|
}
|
|
if len(ret) != 2 {
|
|
return nil, httperrors.NewInternalServerError("Invald ListItemFilter return value count %d", 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 GetExtraDetails(model IModel, ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, isList bool) (*jsonutils.JSONDict, error) {
|
|
ret, err := call(model, "GetExtraDetails", ctx, userCred, query, isList)
|
|
if err != nil {
|
|
return nil, httperrors.NewGeneralError(err)
|
|
}
|
|
if len(ret) != 2 {
|
|
return nil, httperrors.NewInternalServerError("Invalid GetExtraDetails return value count %d", len(ret))
|
|
}
|
|
if err := ValueToError(ret[1]); err != nil {
|
|
return nil, err
|
|
}
|
|
return ValueToJSONDict(ret[0]), 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())
|
|
}
|
|
retVal := make([]*jsonutils.JSONDict, ret[0].Len())
|
|
for i := 0; i < ret[0].Len(); i += 1 {
|
|
jsonDict := ValueToJSONDict(ret[0].Index(i))
|
|
jsonDict.Update(jsonutils.Marshal(objs[i]))
|
|
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])
|
|
}
|