//@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" "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{} } // 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 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 } // GetDataInterface will give you string func (ab *AbstractBusinessLogicHolder) GetDataInterface(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() *AbstractBusinessLogicHolder { ab.localServiceData = make(map[string]interface{}) 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 } // 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) } // 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 } // 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 } // 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 } for _, step := range sb.steps { // Validation if step.IsValidator { validationError := sb.executeValidationFunction(step.ValidationDataKey, step.ValidationFunc) if errormdl.CheckErr(validationError) != nil { return nil, errormdl.CheckErr(validationError) } continue } //Load Data if step.processDataFunc != nil { daoError := step.processDataFunc(sb.businessLogicHolder) if errormdl.CheckErr1(daoError) != nil { loggermdl.LogError(daoError) return nil, errormdl.CheckErr1(daoError) } } //Run step func tmp, blError := step.RunFunc() if errormdl.CheckErr2(blError) != nil { loggermdl.LogError(blError) return nil, errormdl.CheckErr2(blError) } // Validation using conditions result, evaluteError := conditions.Evaluate(step.expr, tmp) if errormdl.CheckErr3(evaluteError) != nil { loggermdl.LogError(evaluteError) return nil, errormdl.CheckErr3(evaluteError) } // if validation fails if !result { // loggermdl.LogWarn(step.Stepname, "Failed", result) return sb.executeErrorFunction(step.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 { 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 { 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) }