servicebuildermdl.go 14.5 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 (
Ajit Jagtap's avatar
Ajit Jagtap committed
	"strings"
Ajit Jagtap's avatar
Ajit Jagtap committed
	"sync"
Ajit Jagtap's avatar
Ajit Jagtap committed

	"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"

Ajit Jagtap's avatar
Ajit Jagtap committed
	"github.com/oleksandr/conditions"
	"github.com/tidwall/gjson"
var globalConfig map[string]string
var once sync.Once

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]string)
// 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 {
	localServiceData map[string]interface{}
	globalConfigData map[string]string
Roshan Patil's avatar
Roshan Patil committed
	GlobalErrorCode  int
}

// SetGlobalConfig - SetGlobalConfig
func SetGlobalConfig(configs map[string]string) {
	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) {
	value, found := ab.globalConfigData[key]
	if errormdl.CheckBool(!found) {
		return "", false
	}
	return 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
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
// 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
}

// GetErrorCode - GetErrorCode in service context
func (ab *AbstractBusinessLogicHolder) GetErrorCode() int {
	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"`
	HitsCount         int       `json:"hitsCount"`
}

// // 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
}

// 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
}