servicebuildermdl.go 11.81 KiB
//@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 (
	"strings"
	"sync"
	"time"
	"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/validationmdl"
	"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/errormdl"
	"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/loggermdl"
	"github.com/oleksandr/conditions"
	"github.com/tidwall/gjson"
var ruleCache map[string]conditions.Expr
var mutex = &sync.Mutex{}
func init() {
	ruleCache = make(map[string]conditions.Expr)
// 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{}
	pricipalObject   Principal
// 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
// GetDataResultset will give you int
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
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 } // 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 } // New will create memory for your data func (ab *AbstractBusinessLogicHolder) New(principalObject *Principal) *AbstractBusinessLogicHolder { ab.localServiceData = make(map[string]interface{}) ab.pricipalObject = *principalObject return ab } // SetResultset will return map data with finaldata key func (ab *AbstractBusinessLogicHolder) SetResultset(key string, obj *gjson.Result) { ab.localServiceData[key] = obj } // SetByteData will set byte data as gjson.Result func (ab *AbstractBusinessLogicHolder) SetByteData(key string, obj []byte) { rs := gjson.ParseBytes(obj) ab.localServiceData[key] = &rs } // 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"] return &a }
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
// SetFinalData will return map data with finaldata key func (ab *AbstractBusinessLogicHolder) SetFinalData(data interface{}) { ab.localServiceData["finaldata"] = data } // EchoBL sample EchoBL logic handler func (ab *AbstractBusinessLogicHolder) EchoBL() (map[string]interface{}, error) { // loggermdl.LogWarn("EchoBL called") return map[string]interface{}{ "ok": int64(1), }, nil } // 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{} //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
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
} step.expr = expr mutex.Lock() ruleCache[rule] = expr 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) {
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
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) } _, 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") } maxStepCount-- // 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)
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
} 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 }