//@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 ( "database/sql" "strings" "sync" "time" "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" "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 var once sync.Once var ruleCache map[string]conditions.Expr var mutex = &sync.Mutex{} func init() { 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 { localServiceData map[string]interface{} pricipalObject Principal globalConfigData map[string]GlobalConfigModel GlobalErrorCode int ServiceError interface{} TransactionEnable bool IgnoreStrictMode bool 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 { if linq.From(value.Restrictions).WhereT(func(str string) bool { return str == "Open" || str == "OPEN" }).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 } // 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 ab.GlobalErrorCode = 0 ab.ServiceError = nil return ab } // 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 } // SetByteData will set byte data as gjson.Result func (ab *AbstractBusinessLogicHolder) SetByteData(key string, obj []byte) { rs := gjson.ParseBytes(obj) ab.localServiceData[key] = &rs } // 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"] return &a } // SetFinalData will return map data with finaldata key func (ab *AbstractBusinessLogicHolder) SetFinalData(data interface{}) { ab.localServiceData["finaldata"] = data } // 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 } // GetErrorCode - GetErrorCode in service context func (ab *AbstractBusinessLogicHolder) GetErrorCode() int { if ab == nil { return 0 } return ab.GlobalErrorCode } // 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 } 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) { 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) } 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"` ClientIP string `json:"clientIP"` HitsCount int `json:"hitsCount"` Token string `json:"token"` 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 } // 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 }