servicebuildermdl.go 19 KiB
Newer Older
Ajit Jagtap's avatar
Ajit Jagtap committed
//@author  Ajit Jagtap
//@version Mon Jul 09 2018 14:00:05 GMT+0530 (IST)

// Package servicebuildermdl will help you run BL and fetch data.
package servicebuildermdl

import (
Roshan Patil's avatar
Roshan Patil committed
	"database/sql"
Ajit Jagtap's avatar
Ajit Jagtap committed
	"strings"
Ajit Jagtap's avatar
Ajit Jagtap committed
	"sync"
Ajit Jagtap's avatar
Ajit Jagtap committed

Roshan Patil's avatar
Roshan Patil committed
	"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/utiliymdl/guidmdl"
	"github.com/tidwall/sjson"

	linq "gopkg.in/ahmetb/go-linq.v3"

	"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/constantmdl"

	"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/validationmdl"

	"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/errormdl"
	"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/loggermdl"

	"github.com/zhouzhuojie/conditions"

Ajit Jagtap's avatar
Ajit Jagtap committed
	"github.com/tidwall/gjson"
// GlobalConfigModel - GlobalConfigModel
type GlobalConfigModel struct {
	Key          string   `json:"key"`
	Value        string   `json:"value"`
	Restrictions []string `json:"restrictions"`
}

var globalConfig map[string]GlobalConfigModel
var globalConfigMutex sync.Mutex
Ajit Jagtap's avatar
Ajit Jagtap committed
var ruleCache map[string]conditions.Expr
Ajit Jagtap's avatar
Ajit Jagtap committed
var mutex = &sync.Mutex{}
Ajit Jagtap's avatar
Ajit Jagtap committed

func init() {
Ajit Jagtap's avatar
Ajit Jagtap committed
	ruleCache = make(map[string]conditions.Expr)
	globalConfig = make(map[string]GlobalConfigModel)
	globalConfigMutex = sync.Mutex{}
// DebugInfo - DebugInfo
type DebugInfo struct {
	StackTrace      strings.Builder `json:"stackTrace"`
	PerformanceInfo strings.Builder `json:"performanceInfo"`
}

// LoadData is a method sign for loader methods
type LoadData = func(ab *AbstractBusinessLogicHolder) error

// FinalStepProcessOutput is a method sign for loader methods
type FinalStepProcessOutput = func(ab *AbstractBusinessLogicHolder) (*interface{}, error)

// AbstractBusinessLogicHolder use this type to inheritance
type AbstractBusinessLogicHolder struct {
Kunal Taitkar's avatar
Kunal Taitkar committed
	localServiceData  map[string]interface{}
	pricipalObject    Principal
	globalConfigData  map[string]GlobalConfigModel
	GlobalErrorCode   int
	ServiceError      interface{}
	TransactionEnable bool
Kunal Taitkar's avatar
Kunal Taitkar committed
	TXN               *sql.Tx
}

// SetGlobalConfig - SetGlobalConfig
func SetGlobalConfig(configs map[string]GlobalConfigModel) {
	globalConfigMutex.Lock()
	defer globalConfigMutex.Unlock()
	once.Do(func() {
		if configs != nil {
			globalConfig = configs
		}
	})
}

// GetDataString will give you string
func (ab *AbstractBusinessLogicHolder) GetDataString(key string) (string, bool) {
	//check in map
	temp, found := ab.localServiceData[key]
	if errormdl.CheckBool(!found) {
		return "", false
	}
	// cast it
	value, ok := temp.(string)
	if errormdl.CheckBool1(!ok) {
		return "", false
	}
	return value, true
}

// GetDataInt will give you int
func (ab *AbstractBusinessLogicHolder) GetDataInt(key string) (int64, bool) {
	//check in map
	temp, found := ab.localServiceData[key]
	if errormdl.CheckBool(!found) {
		return 0, false
	}
	// cast it
	value, ok := temp.(int64)
	if errormdl.CheckBool1(!ok) {
		return 0, false
	}
	return value, true
}

// GetDataInterface will give you int
func (ab *AbstractBusinessLogicHolder) GetDataInterface(key string) (interface{}, bool) {
	//check in map
	temp, found := ab.localServiceData[key]
	if errormdl.CheckBool(!found) {
		return nil, false
	}
	return temp, true
}

// GetDataResultset will give you int
func (ab *AbstractBusinessLogicHolder) GetDataResultset(key string) (*gjson.Result, bool) {
	//check in map
	temp, found := ab.localServiceData[key]
	if errormdl.CheckBool(!found) {
		loggermdl.LogWarn("Key not found -", key)
		return &gjson.Result{}, false
	}
	// cast it
	value, ok := temp.(*gjson.Result)

	if errormdl.CheckBool1(!ok) {
		return &gjson.Result{}, false
	}
	return value, true
}

func (ab *AbstractBusinessLogicHolder) GetMQLRequestData() (*gjson.Result, bool) {
	//check in map
	temp, found := ab.localServiceData[constantmdl.MQLRequestData]
	if errormdl.CheckBool(!found) {
		loggermdl.LogWarn("MQL Request Data not Found")
		return &gjson.Result{}, false
	}
	// cast it
	value, ok := temp.(*gjson.Result)

	if errormdl.CheckBool1(!ok) {
		return &gjson.Result{}, false
	}
	return value, true
}

// GetDataBool will give you int
func (ab *AbstractBusinessLogicHolder) GetDataBool(key string) (bool, bool) {
	//check in map
	temp, found := ab.localServiceData[key]
	if errormdl.CheckBool(!found) {
		return false, false
	}
	// cast it
	value, ok := temp.(bool)
	if errormdl.CheckBool1(!ok) {
		return false, false
	}
	return value, true
}

// GetCustomData will give you string
func (ab *AbstractBusinessLogicHolder) GetCustomData(key string) (interface{}, bool) {
	//check in map
	temp, found := ab.localServiceData[key]
	if errormdl.CheckBool(!found) {
		return 0, false
	}
	// cast it
	return temp, true
}

// GetGlobalConfigString - return string value for global config key
func (ab *AbstractBusinessLogicHolder) GetGlobalConfigString(key string) (string, bool) {
	globalConfigMutex.Lock()
	defer globalConfigMutex.Unlock()
	value, found := ab.globalConfigData[key]
	if errormdl.CheckBool(!found) {
		return "", false
	}
	if len(value.Restrictions) > 0 {
Roshan Patil's avatar
Roshan Patil committed
		if linq.From(value.Restrictions).WhereT(func(str string) bool {
Roshan Patil's avatar
Roshan Patil committed
			return str == "Open" || str == "OPEN"
Roshan Patil's avatar
Roshan Patil committed
		}).Any() {
			return value.Value, true
		}
		if (linq.From(value.Restrictions).WhereT(func(str string) bool {
			return str == "Restricted" || str == "RESTRICTED"
		}).Any()) && ab.pricipalObject.UserID != "" {
			return value.Value, true
		}

		for i := 0; i < len(value.Restrictions); i++ {
			for j := 0; j < len(ab.pricipalObject.Groups); j++ {
				if ab.pricipalObject.Groups[j] == value.Restrictions[i] {
					return value.Value, true
				}
			}
		}
		return "", false
	}
	return value.Value, true
Roshan Patil's avatar
Roshan Patil committed
// New will create memory for your data
func (ab *AbstractBusinessLogicHolder) New(principalObject *Principal) *AbstractBusinessLogicHolder {
	ab.localServiceData = make(map[string]interface{})
	ab.globalConfigData = globalConfig
	ab.pricipalObject = *principalObject
Roshan Patil's avatar
Roshan Patil committed
	ab.GlobalErrorCode = 0
	ab.ServiceError = nil
Roshan Patil's avatar
Roshan Patil committed
	return ab
Ajit Jagtap's avatar
Ajit Jagtap committed
// SetResultset will return map data with finaldata key
func (ab *AbstractBusinessLogicHolder) SetResultset(key string, obj *gjson.Result) {
	ab.localServiceData[key] = obj
}

// SetMQLRequestData - set value
func (ab *AbstractBusinessLogicHolder) SetMQLRequestData(obj *gjson.Result) {
	ab.localServiceData[constantmdl.MQLRequestData] = obj
}

Roshan Patil's avatar
Roshan Patil committed
// SetByteData will set byte data as gjson.Result
func (ab *AbstractBusinessLogicHolder) SetByteData(key string, obj []byte) {
	rs := gjson.ParseBytes(obj)
	ab.localServiceData[key] = &rs
Roshan Patil's avatar
Roshan Patil committed
// SetMQLToken will set token in header
func (ab *AbstractBusinessLogicHolder) SetMQLToken(token string) {
	ab.localServiceData["MQLToken"] = token
}

// SetCustomData set custom user data in map
func (ab *AbstractBusinessLogicHolder) SetCustomData(key string, data interface{}) {
	ab.localServiceData[key] = data
// SetMQLToken will set token in header
func (ab *AbstractBusinessLogicHolder) SetErrorData(data interface{}) {
	ab.ServiceError = data
}

// GetFinalData will return map data with finaldata key
func (ab *AbstractBusinessLogicHolder) GetFinalData() *interface{} {
	a := ab.localServiceData["finaldata"]
Ajit Jagtap's avatar
Ajit Jagtap committed
// SetFinalData will return map data with finaldata key
func (ab *AbstractBusinessLogicHolder) SetFinalData(data interface{}) {
	ab.localServiceData["finaldata"] = data
Roshan Patil's avatar
Roshan Patil committed
// SetErrorCode - SetErrorCode in service context
func (ab *AbstractBusinessLogicHolder) SetErrorCode(code int) {
	ab.GlobalErrorCode = code
}

// GetErrorData - GetErrorData in service context
func (ab *AbstractBusinessLogicHolder) GetErrorData() interface{} {
	if ab == nil {
		return nil
	}
	return ab.ServiceError
}

Roshan Patil's avatar
Roshan Patil committed
// GetErrorCode - GetErrorCode in service context
func (ab *AbstractBusinessLogicHolder) GetErrorCode() int {
Roshan Patil's avatar
Roshan Patil committed
	if ab == nil {
		return 0
	}
Roshan Patil's avatar
Roshan Patil committed
	return ab.GlobalErrorCode
}

// EchoBL sample EchoBL logic handler
func (ab *AbstractBusinessLogicHolder) EchoBL() (map[string]interface{}, error) {
Ajit Jagtap's avatar
Ajit Jagtap committed
	// loggermdl.LogWarn("EchoBL called")
	return map[string]interface{}{
		"ok": int64(1),
}

// Step help to maintain steps
type Step struct {
	Stepname          string
	expr              conditions.Expr
	processDataFunc   LoadData
	IsValidator       bool
	ValidationDataKey string
	ValidationFunc    func(interface{}) error
	RunFunc           func() (map[string]interface{}, error)
	ErrorFunc         func() (map[string]interface{}, error)
	JumpStep          string
}

// ServiceBuilder will help you to run steps
type ServiceBuilder struct {
	ServiceName         string
	steps               []Step
	businessLogicHolder *AbstractBusinessLogicHolder
	ServiceError        error
}

// GetSB Gives you service builder from where you can run steps
func GetSB(name string, ab *AbstractBusinessLogicHolder) *ServiceBuilder {
	newsb := ServiceBuilder{}
	newsb.ServiceName = name
	newsb.businessLogicHolder = ab
	return &newsb
}

// AddStep will add func step with rule
// Stepname : Give Name to step. It will appear in log.
// Rule : Give Ybl rule
// blfunc : Give Business Logic function pointer
// errorfunc : Give Error  function pointer
func (sb *ServiceBuilder) AddStep(stepname, rule string, ld LoadData, blfunc, errorfunc func() (map[string]interface{}, error)) *ServiceBuilder {
	if errormdl.CheckErr(sb.ServiceError) != nil {
		loggermdl.LogError(sb.ServiceError)
		return sb
	}
	step := Step{}
Ajit Jagtap's avatar
Ajit Jagtap committed

	//Check rule in cache
Ajit Jagtap's avatar
Ajit Jagtap committed
	cachedRule, found := ruleCache[rule]
Ajit Jagtap's avatar
Ajit Jagtap committed
	if errormdl.CheckBool(found) {
Ajit Jagtap's avatar
Ajit Jagtap committed
		step.expr = cachedRule
	} else {

		// Parse the condition language and get expression
		p := conditions.NewParser(strings.NewReader(rule))
		expr, err := p.Parse()
		if errormdl.CheckErr1(err) != nil {
			loggermdl.LogError("Error in step: ", stepname, err)
			sb.ServiceError = errormdl.CheckErr1(err)
Roshan Patil's avatar
Roshan Patil committed
			return sb
Ajit Jagtap's avatar
Ajit Jagtap committed
		}
Roshan Patil's avatar
Roshan Patil committed
		step.expr = expr
Ajit Jagtap's avatar
Ajit Jagtap committed
		mutex.Lock()
Ajit Jagtap's avatar
Ajit Jagtap committed
		ruleCache[rule] = expr
Ajit Jagtap's avatar
Ajit Jagtap committed
		mutex.Unlock()
	step.RunFunc = blfunc
	step.ErrorFunc = errorfunc
	step.Stepname = stepname
	step.processDataFunc = ld
	sb.steps = append(sb.steps, step)
	return sb
}

// AddStepWithGoTo - AddStep with goto pointer
func (sb *ServiceBuilder) AddStepWithGoTo(stepname, rule string, ld LoadData, blfunc, errorfunc func() (map[string]interface{}, error), gotoStepName string) *ServiceBuilder {
	if errormdl.CheckErr(sb.ServiceError) != nil {
		loggermdl.LogError(sb.ServiceError)
		return sb
	}
	step := Step{}

	//Check rule in cache
	mutex.Lock()
	cachedRule, found := ruleCache[rule]
	mutex.Unlock()
	if errormdl.CheckBool(found) {
		step.expr = cachedRule
	} else {

		// Parse the condition language and get expression
		p := conditions.NewParser(strings.NewReader(rule))
		expr, err := p.Parse()
		if errormdl.CheckErr1(err) != nil {
			loggermdl.LogError("Error in step: ", stepname, err)
			sb.ServiceError = errormdl.CheckErr1(err)
			return sb
		}
		step.expr = expr
		mutex.Lock()
		ruleCache[rule] = expr
		mutex.Unlock()
	}

	step.RunFunc = blfunc
	step.ErrorFunc = errorfunc
	step.Stepname = stepname
	step.processDataFunc = ld
	step.JumpStep = gotoStepName
	sb.steps = append(sb.steps, step)
	return sb
}

// AddValidation add validation step for give dataKey
func (sb *ServiceBuilder) AddValidation(dataKey string, validationfunc func(interface{}) error) *ServiceBuilder {
	if errormdl.CheckErr(sb.ServiceError) != nil {
		loggermdl.LogError(sb.ServiceError)
		return sb
	}
	step := Step{}
	step.IsValidator = true
	step.ValidationDataKey = dataKey
	step.ValidationFunc = validationfunc
	step.Stepname = "Validation Step"
	sb.steps = append(sb.steps, step)
	return sb
}

func (sb *ServiceBuilder) findStepIndex(stepName string) (int, bool) {
	for i, step := range sb.steps {
		if step.Stepname == stepName {
			return i, true
		}
	}
	return 0, false
}

// Run all Steps one by one
func (sb *ServiceBuilder) Run(fn FinalStepProcessOutput) (*interface{}, error) {
	if errormdl.CheckErr(sb.ServiceError) != nil {
		loggermdl.LogError(sb.ServiceError)
		return nil, errormdl.CheckErr(sb.ServiceError)
	}
Roshan Patil's avatar
Roshan Patil committed
	_, ok := sb.businessLogicHolder.localServiceData["finaldata"]
	if ok {
		return sb.businessLogicHolder.GetFinalData(), nil
	}
	maxStepCount := 100
	for i := 0; i < len(sb.steps); i++ {
		if maxStepCount == 0 {
			loggermdl.LogError("Your steps are in recursion and cross limit of 100 steps")
			return nil, errormdl.Wrap("Your steps are in recursion and cross limit of 100 steps")
		// Validation
		if sb.steps[i].IsValidator {
			validationError := sb.executeValidationFunction(sb.steps[i].ValidationDataKey, sb.steps[i].ValidationFunc)
			if errormdl.CheckErr(validationError) != nil {
				return nil, errormdl.CheckErr(validationError)
			}
			continue
		}

		//Load Data
		if sb.steps[i].processDataFunc != nil {
			daoError := sb.steps[i].processDataFunc(sb.businessLogicHolder)
			if errormdl.CheckErr1(daoError) != nil {
				loggermdl.LogError(daoError)
				return nil, errormdl.CheckErr1(daoError)
		}

		//Run step func
		tmp, blError := sb.steps[i].RunFunc()
		if errormdl.CheckErr2(blError) != nil {
			loggermdl.LogError(blError)
			return nil, errormdl.CheckErr2(blError)
		}
		// Validation using conditions
		result, evaluteError := conditions.Evaluate(sb.steps[i].expr, tmp)
		if errormdl.CheckErr3(evaluteError) != nil {
			loggermdl.LogError(evaluteError)
			return nil, errormdl.CheckErr3(evaluteError)
		}
		// if validation fails
		if !result {
			// loggermdl.LogWarn(sb.steps[i].Stepname, "Failed", result)
			// jump step is a functionality like go to on particular step
			if sb.steps[i].JumpStep != "" {
				_, recoveryError := sb.executeErrorFunction(sb.steps[i].ErrorFunc)
				if errormdl.CheckErr(recoveryError) != nil {
					loggermdl.LogError(recoveryError)
					return nil, errormdl.CheckErr(recoveryError)
				}
				index, ok := sb.findStepIndex(sb.steps[i].JumpStep)
				if !ok {
					loggermdl.LogError("Step Name spcify in GOTO not found: " + sb.steps[i].JumpStep)
					return nil, errormdl.Wrap("Step Name spcify in GOTO not found: " + sb.steps[i].JumpStep)
				}
				i = index - 1
				continue
			return sb.executeErrorFunction(sb.steps[i].ErrorFunc)
	return sb.finalOutput(fn)
}

// executeValidationFunction exceute when validation failed for any step
func (sb *ServiceBuilder) executeValidationFunction(dataKey string, fn func(interface{}) error) error {
	validationData, ok := sb.businessLogicHolder.localServiceData[dataKey]
	if !ok {
		loggermdl.LogError("Data Not Found For Validation: " + dataKey)
		return errormdl.Wrap("Data Not Found For Validation: " + dataKey)
	}
	if fn == nil {
		return validationmdl.ValidateStruct(validationData)
	}
	return fn(validationData)
}

// executeErrorFunction exceute when validation failed for any step
func (sb *ServiceBuilder) executeErrorFunction(fn func() (map[string]interface{}, error)) (*interface{}, error) {
	if fn == nil {
		loggermdl.LogError("Data Validation failed and No recovery function found")
		return nil, errormdl.Wrap("Data Validation failed and No recovery function found")
	_, err := fn()
	if errormdl.CheckErr(err) != nil {
		loggermdl.LogError(err)
		return nil, errormdl.CheckErr(err)
	}
	return sb.businessLogicHolder.GetFinalData(), nil
// finalOutput return Final output
func (sb *ServiceBuilder) finalOutput(fn FinalStepProcessOutput) (*interface{}, error) {
	if fn == nil {
		return sb.businessLogicHolder.GetFinalData(), nil
	}
	return fn(sb.businessLogicHolder)
}

// Principal - Object inside JWT token
type Principal struct {
	UserID            string    `json:"userId"`
	Groups            []string  `json:"groups"`
	SessionExpiration time.Time `json:"sessionExpiration"`
Roshan Patil's avatar
Roshan Patil committed
	ClientIP          string    `json:"clientIP"`
	HitsCount         int       `json:"hitsCount"`
Roshan Patil's avatar
Roshan Patil committed
	Token             string    `json:"token"`
Roshan Patil's avatar
Roshan Patil committed
	Metadata          string    `json:"metadata"`
}

// // SetPrincipalObject - Set Principal object to BLHolder
// func (ab *AbstractBusinessLogicHolder) SetPrincipalObject(object *Principal) {
// 	ab.pricipalObject = *object
// }

// GetPrincipalObject - return Principal object from BLHolder
func (ab *AbstractBusinessLogicHolder) GetPrincipalObject() *Principal {
	return &ab.pricipalObject
}

// APIResponse - APIResponse
type APIResponse struct {
	StatusCode int
	Body       []byte
	Headers    []Header
}
type Header struct {
	Key   string
	Value []string
}

// GetResposeObject - return Response object from BLHolder
func (ab *AbstractBusinessLogicHolder) GetResposeObject(responseKey string) (*APIResponse, error) {
	tmp, ok := ab.localServiceData[responseKey]
	if !ok {
		return &APIResponse{}, errormdl.Wrap("Response not found for key: " + responseKey)
	}
	value, ok := tmp.(APIResponse)
	if !ok {
		return &APIResponse{}, errormdl.Wrap("Data inside memory is not of type APIResponse: " + responseKey)
	}
	return &value, nil
}
Roshan Patil's avatar
Roshan Patil committed

// FetchValues -FetchValues
func (ab *AbstractBusinessLogicHolder) FetchValues(keyName, query string) (gjson.Result, int, error) {
	var result gjson.Result
	if keyName == "Principal" {
		if query == "userId" {
			result = gjson.Parse(`{"loginId":"` + ab.GetPrincipalObject().UserID + `"}`).Get("loginId")

		}
		if query == "groups" {
			var err error
			groupData := ""
			for _, group := range ab.GetPrincipalObject().Groups {
				groupData, err = sjson.Set(groupData, "-1", group)
				if err != nil {
					loggermdl.LogError(err)
					return result, errormdl.SJSONERROR, err
				}
			}
			result = gjson.Parse(groupData)
		}
		if query == "clientIP" {
			result = gjson.Parse(`{"clientIP":"` + ab.GetPrincipalObject().ClientIP + `"}`).Get("clientIP")
		}
		if query == "token" {
			result = gjson.Parse(`{"token":"` + ab.GetPrincipalObject().Token + `"}`).Get("token")
		}
		if strings.Contains(query, "metadata") {
			if strings.Contains(query, ":") {
				token := strings.Split(query, ":")
				if len(token) > 0 {
					result = gjson.Parse(ab.GetPrincipalObject().Metadata).Get(token[1])
				}
			} else {
				result = gjson.Parse(ab.GetPrincipalObject().Metadata)
			}
		}

		return result, errormdl.NOERROR, nil
	}

	if keyName == "GlobalConfig" {
		result, ok := ab.GetGlobalConfigString(query)
		if !ok {
			loggermdl.LogError("Key Not Found in global config: " + query)
			return gjson.Parse(result), errormdl.KEYNOTFOUND, errormdl.Wrap("Key Not Found in global config: " + query)
		}
		loggermdl.LogInfo(result)
		return gjson.Parse(`{"config":"` + result + `"}`).Get("config"), errormdl.NOERROR, nil
	}

	if keyName == "~tokenUserId" {
		return gjson.Parse(`{"loginId":"` + ab.GetPrincipalObject().UserID + `"}`).Get("loginId"), errormdl.NOERROR, nil
	}

	if keyName == "~TIME" {
		TIME, err := sjson.Set("{}", "time", time.Now().Unix())
		if errormdl.CheckErr(err) != nil {
			loggermdl.LogError(err)
			return result, errormdl.SJSONERROR, errormdl.CheckErr(err)
		}
		return gjson.Parse(TIME).Get("time"), errormdl.NOERROR, nil
	}

	if keyName == "~GUID" {
		return gjson.Parse(`{"guid":"` + guidmdl.GetGUID() + `"}`).Get("guid"), errormdl.NOERROR, nil
	}

	if keyName == "EMPTYJSON" {
		return gjson.Parse("{}"), errormdl.NOERROR, nil
	}

	rs, ok := ab.GetDataResultset(keyName)
	if !ok {
		loggermdl.LogError("Key Not Found: " + keyName)
		return result, errormdl.KEYNOTFOUND, errormdl.Wrap("Key Not Found: " + keyName)
	}
	if query != "*" {
		result = rs.Get(query)
	} else {
		result = *rs
	}
	return result, errormdl.NOERROR, nil
}

// GetAllAbsData - GetAllAbsData
func (ab *AbstractBusinessLogicHolder) GetAbLocalServiceData() map[string]interface{} {
	return ab.localServiceData
}