servicebuildermdl.go 8.62 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/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"
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)
// 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
}

// Build will create memory for your data
func (ab *AbstractBusinessLogicHolder) Build() {
	ab.localServiceData = make(map[string]interface{})
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
}

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

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

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

// 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)
	}
Roshan Patil's avatar
Roshan Patil committed
	_, 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 {
Ajit Jagtap's avatar
Ajit Jagtap committed
			// 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 nil, 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)
}