From fa5167ce1626a4f922cc9e4b3496a6666812505d Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Thu, 4 Oct 2018 00:52:24 +0530 Subject: [PATCH 001/125] expr based condition --- common/model/types.go | 4 +- rete/filternode.go | 10 +- rete/joinnode.go | 15 ++- ruleapi/condition.go | 15 ++- ruleapi/exprcondition.go | 63 +++++++++++++ ruleapi/rule.go | 186 ++++++++++++++++++++++++++++++++++++- ruleapi/tests/cn_1_test.go | 98 +++++++++++++++++++ 7 files changed, 381 insertions(+), 10 deletions(-) create mode 100644 ruleapi/exprcondition.go create mode 100644 ruleapi/tests/cn_1_test.go diff --git a/common/model/types.go b/common/model/types.go index 2258a93..ee2f0e9 100644 --- a/common/model/types.go +++ b/common/model/types.go @@ -26,16 +26,18 @@ type MutableRule interface { SetAction(actionFn ActionFunction) SetPriority(priority int) SetContext(ctx RuleContext) + AddExprCondition (conditionName string, cExpr string, ctx RuleContext) (error) } //Condition interface to maintain/get various condition properties type Condition interface { GetName() string - GetEvaluator() ConditionEvaluator + //GetEvaluator() ConditionEvaluator GetRule() Rule GetIdentifiers() []TupleType GetContext() RuleContext String() string + Evaluate(string, string, map[TupleType]Tuple, RuleContext) (bool, error) } // RuleSession to maintain rules and assert tuples against those rules diff --git a/rete/filternode.go b/rete/filternode.go index 0781935..251d32a 100644 --- a/rete/filternode.go +++ b/rete/filternode.go @@ -94,9 +94,13 @@ func (fn *filterNodeImpl) assertObjects(ctx context.Context, handles []reteHandl } tupleMap := convertToTupleMap(tuples) cv := fn.conditionVar - toPropagate := cv.GetEvaluator()(cv.GetName(), cv.GetRule().GetName(), tupleMap, cv.GetContext()) - if toPropagate { - fn.nodeLinkVar.propagateObjects(ctx, handles) + toPropagate, err := cv.Evaluate(cv.GetName(), cv.GetRule().GetName(), tupleMap, cv.GetContext()) + if err == nil { + if toPropagate { + fn.nodeLinkVar.propagateObjects(ctx, handles) + } + } else { + //todo } } } diff --git a/rete/joinnode.go b/rete/joinnode.go index 6f533e1..fe4d5e8 100644 --- a/rete/joinnode.go +++ b/rete/joinnode.go @@ -164,6 +164,8 @@ func (jn *joinNodeImpl) assertObjects(ctx context.Context, handles []reteHandle, func (jn *joinNodeImpl) assertFromRight(ctx context.Context, handles []reteHandle, joinedHandles []reteHandle) { + var err error + //TODO: other stuff. right now focus on tuple table jn.joinRightObjects(handles, joinedHandles) tupleTableRow := newJoinTableRow(handles) @@ -181,7 +183,10 @@ func (jn *joinNodeImpl) assertFromRight(ctx context.Context, handles []reteHandl } else { tupleMap := copyIntoTupleMap(joinedHandles) cv := jn.conditionVar - toPropagate = cv.GetEvaluator()(cv.GetName(), cv.GetRule().GetName(), tupleMap, cv.GetContext()) + toPropagate, err = cv.Evaluate(cv.GetName(), cv.GetRule().GetName(), tupleMap, cv.GetContext()) + if err != nil { + //todo + } } if toPropagate { jn.nodeLinkVar.propagateObjects(ctx, joinedHandles) @@ -212,6 +217,9 @@ func (jn *joinNodeImpl) joinRightObjects(rightHandles []reteHandle, joinedHandle } func (jn *joinNodeImpl) assertFromLeft(ctx context.Context, handles []reteHandle, joinedHandles []reteHandle) { + + var err error + jn.joinLeftObjects(handles, joinedHandles) //TODO: other stuff. right now focus on tuple table tupleTableRow := newJoinTableRow(handles) @@ -229,7 +237,10 @@ func (jn *joinNodeImpl) assertFromLeft(ctx context.Context, handles []reteHandle } else { tupleMap := copyIntoTupleMap(joinedHandles) cv := jn.conditionVar - toPropagate = cv.GetEvaluator()(cv.GetName(), cv.GetRule().GetName(), tupleMap, cv.GetContext()) + toPropagate, err = cv.Evaluate(cv.GetName(), cv.GetRule().GetName(), tupleMap, cv.GetContext()) + if err != nil { + //todo + } } if toPropagate { jn.nodeLinkVar.propagateObjects(ctx, joinedHandles) diff --git a/ruleapi/condition.go b/ruleapi/condition.go index 7ae71e3..33f9c8d 100644 --- a/ruleapi/condition.go +++ b/ruleapi/condition.go @@ -34,9 +34,9 @@ func (cnd *conditionImpl) GetContext() model.RuleContext { return cnd.ctx } -func (cnd *conditionImpl) GetEvaluator() model.ConditionEvaluator { - return cnd.cfn -} +//func (cnd *conditionImpl) GetEvaluator() model.ConditionEvaluator { +// return cnd.cfn +//} func (cnd *conditionImpl) String() string { return "[Condition: name:" + cnd.name + ", idrs: TODO]" @@ -52,3 +52,12 @@ func (cnd *conditionImpl) GetRule() model.Rule { func (cnd *conditionImpl) GetTupleTypeAlias() []model.TupleType { return cnd.identifiers } + +func (cnd *conditionImpl) Evaluate (condName string, ruleNm string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) (bool, error) { + result := false + if cnd.cfn != nil { + result = cnd.cfn (condName, ruleNm, tuples, ctx) + } + + return result, nil +} diff --git a/ruleapi/exprcondition.go b/ruleapi/exprcondition.go new file mode 100644 index 0000000..8f0697e --- /dev/null +++ b/ruleapi/exprcondition.go @@ -0,0 +1,63 @@ +package ruleapi + +import ( + "github.com/project-flogo/rules/common/model" +) + + +type exprConditionImpl struct { + name string + rule model.Rule + identifiers []model.TupleType + cExpr string + ctx model.RuleContext +} + +func newExprCondition(name string, rule model.Rule, identifiers []model.TupleType, cExpr string, ctx model.RuleContext) model.Condition { + c := exprConditionImpl{} + c.initExprConditionImpl(name, rule, identifiers, cExpr, ctx) + return &c +} + +func (cnd *exprConditionImpl) initExprConditionImpl(name string, rule model.Rule, identifiers []model.TupleType, cExpr string, ctx model.RuleContext) { + cnd.name = name + cnd.rule = rule + cnd.identifiers = append(cnd.identifiers, identifiers...) + cnd.cExpr = cExpr + cnd.ctx = ctx +} + +func (cnd *exprConditionImpl) GetIdentifiers() []model.TupleType { + return cnd.identifiers +} +func (cnd *exprConditionImpl) GetContext() model.RuleContext { + return cnd.ctx +} + +func (cnd *exprConditionImpl) GetEvaluator() model.ConditionEvaluator { + return nil +} + +func (cnd *exprConditionImpl) String() string { + return "[Condition: name:" + cnd.name + ", idrs: TODO]" +} + +func (cnd *exprConditionImpl) GetName() string { + return cnd.name +} + +func (cnd *exprConditionImpl) GetRule() model.Rule { + return cnd.rule +} +func (cnd *exprConditionImpl) GetTupleTypeAlias() []model.TupleType { + return cnd.identifiers +} + +func (cnd *exprConditionImpl) Evaluate (condName string, ruleNm string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) (bool, error) { + result := false + if cnd.cExpr != "" { + //todo + } + + return result, nil +} \ No newline at end of file diff --git a/ruleapi/rule.go b/ruleapi/rule.go index c4fc1af..a63b924 100644 --- a/ruleapi/rule.go +++ b/ruleapi/rule.go @@ -4,6 +4,9 @@ import ( "fmt" "strings" + "github.com/TIBCOSoftware/flogo-lib/core/mapper/exprmapper/expression" + "github.com/TIBCOSoftware/flogo-lib/core/mapper/exprmapper/expression/expr" + "github.com/TIBCOSoftware/flogo-lib/core/mapper/exprmapper/funcexprtype" "github.com/project-flogo/rules/common/model" ) @@ -73,6 +76,23 @@ func (rule *ruleImpl) addCond(conditionName string, idrs []model.TupleType, cfn } } +func (rule *ruleImpl) addExprCond(conditionName string, idrs []model.TupleType, cExpr string, ctx model.RuleContext) { + condition := newExprCondition(conditionName, rule, idrs, cExpr, ctx) + rule.conditions = append(rule.conditions, condition) + + for _, cidr := range idrs { + if len(rule.identifiers) == 0 { + rule.identifiers = append(rule.identifiers, cidr) + } else { + for _, ridr := range rule.identifiers { + if cidr != ridr { + rule.identifiers = append(rule.identifiers, cidr) + break + } + } + } + } +} func (rule *ruleImpl) GetPriority() int { return rule.priority @@ -103,7 +123,7 @@ func (rule *ruleImpl) SetAction(actionFn model.ActionFunction) { rule.actionFn = actionFn } -func (rule *ruleImpl) AddCondition(conditionName string, idrs []string, cFn model.ConditionEvaluator, ctx model.RuleContext) (err error) { +func (rule *ruleImpl) AddCondition2(conditionName string, idrs []string, cFn model.ConditionEvaluator, ctx model.RuleContext) (err error) { typeDeps := []model.TupleType{} for _, idr := range idrs { aliasProp := strings.Split(string(idr), ".") @@ -138,6 +158,170 @@ func (rule *ruleImpl) AddCondition(conditionName string, idrs []string, cFn mode return err } +func (rule *ruleImpl) AddCondition(conditionName string, idrs []string, cFn model.ConditionEvaluator, ctx model.RuleContext) (err error) { + typeDeps, err := rule.addDeps(idrs) + if err != nil { + return err + } + rule.addCond(conditionName, typeDeps, cFn, ctx, true) + return err +} + +func (rule *ruleImpl) addDeps(idrs []string) ([]model.TupleType, error) { + typeDeps := []model.TupleType{} + for _, idr := range idrs { + aliasProp := strings.Split(string(idr), ".") + alias := model.TupleType(aliasProp[0]) + + if model.GetTupleDescriptor(model.TupleType(alias)) == nil { + return typeDeps, fmt.Errorf("Tuple type not found [%s]", string(alias)) + } + + exists, _ := model.Contains(typeDeps, alias) + if !exists { + typeDeps = append(typeDeps, alias) + } + if len(aliasProp) == 2 { //specifically 2, else do not consider + prop := aliasProp[1] + + td := model.GetTupleDescriptor(model.TupleType(alias)) + if prop != "none" && td.GetProperty(prop) == nil { //"none" is a special case + return typeDeps, fmt.Errorf("TupleType property not found [%s]", prop) + } + + propMap, found := rule.deps[alias] + if !found { + propMap = map[string]bool{} + rule.deps[alias] = propMap + } + propMap[prop] = true + } + } + + return typeDeps, nil +} + func (rule *ruleImpl) GetDeps() map[model.TupleType]map[string]bool { return rule.deps } + +func (rule *ruleImpl) AddExprCondition(conditionName string, cstr string, ctx model.RuleContext) error { + + e, err := expression.ParseExpression(cstr) + if err != nil { + return err + } + exprn := e.(*expr.Expression) + refs, err := getRefs(exprn) + if err != nil { + return err + } + + err = validateRefs(refs) + if err != nil { + return err + } + + typeDeps, err := rule.addDeps(refs) + if err != nil { + return err + } + rule.addExprCond(conditionName, typeDeps, cstr, ctx) + return err + +} + +//func parseRefs(refs map[string]bool) (map[string]map[string]bool, error) { +// +// aliasAndRefs := make(map[string]map[string]bool) +// +// for val, _ := range refs { +// +// vals := strings.Split(val, ".") +// +// td := model.GetTupleDescriptor(model.TupleType(vals[0])) +// if td == nil { +// return aliasAndRefs, fmt.Errorf("Invalid TupleType [%s]", vals[0]) +// } +// +// prop := td.GetProperty(vals[1]) +// if prop == nil { +// return aliasAndRefs, fmt.Errorf("Property [%s] not found in TupleType [%s]", vals[1], vals[0]) +// } +// +// propsMap, found := aliasAndRefs[vals[0]] +// if !found { +// propsMap = make(map[string]bool) +// aliasAndRefs[vals[0]] = propsMap +// } +// +// propsMap[vals[1]] = true +// +// } +// +// return aliasAndRefs, nil +//} + +func validateRefs(refs []string) (error) { + for _, ref := range refs { + vals := strings.Split(ref, ".") + td := model.GetTupleDescriptor(model.TupleType(vals[0])) + if td == nil { + return fmt.Errorf("Invalid TupleType [%s]", vals[0]) + } + prop := td.GetProperty(vals[1]) + if prop == nil { + return fmt.Errorf("Property [%s] not found in TupleType [%s]", vals[1], vals[0]) + } + } + return nil +} + +func getRefs(e *expr.Expression) ([]string, error) { + refs := make(map[string]bool) + keys := []string{} + + err := getRefRecursively(e, refs) + if err != nil { + return keys, err + } + + for key, _ := range refs { + keys = append (keys, key) + } + return keys, err +} + +func getRefRecursively(e *expr.Expression, refs map[string]bool) error { + + if e == nil { + return nil + } + err := getRefsInternal(e.Left, refs) + if err != nil { + return err + } + err = getRefsInternal(e.Right, refs) + if err != nil { + return err + } + return nil +} + +func getRefsInternal(e *expr.Expression, refs map[string]bool) error { + if e.Type == funcexprtype.EXPRESSION { + getRefRecursively(e, refs) + } else if e.Type == funcexprtype.REF || e.Type == funcexprtype.ARRAYREF { + value := e.Value.(string) + if strings.Index(value, "$") == 0 { + value = value[1:len(value)] + split := strings.Split(value, ".") + if split != nil && len(split) != 2 { + return fmt.Errorf("Invalid tokens [%s]", value) + } + + refs[value] = true + } + } + return nil +} diff --git a/ruleapi/tests/cn_1_test.go b/ruleapi/tests/cn_1_test.go new file mode 100644 index 0000000..0a631ca --- /dev/null +++ b/ruleapi/tests/cn_1_test.go @@ -0,0 +1,98 @@ +package tests + +import ( + "testing" + "github.com/TIBCOSoftware/flogo-lib/core/data" + "fmt" + "github.com/TIBCOSoftware/flogo-lib/core/mapper/exprmapper/expression/expr" + "github.com/TIBCOSoftware/flogo-lib/core/mapper/exprmapper/funcexprtype" + "strings" + "github.com/project-flogo/rules/ruleapi" +) + +func Test_1_Con(t *testing.T) { + + createRuleSession() + + //a1, _ := data.NewAttribute("name", data.TypeString, "n1") + //a2, _ := data.NewAttribute("age2", data.TypeInteger, 10) + //a3, _ := data.NewAttribute("age", data.TypeInteger, 10) + // + //attr := []*data.Attribute{a1, a2, a3} + // + //simpleScope := data.NewSimpleScope(attr, nil) + // + ////fmt.Printf("bc index %d\n", strings.Index("abc", "bc")) + // + //r := data.GetBasicResolver() + // + //e, err := expression.ParseExpression(`($.age == $.age2) && ($.age2 == 11)`) + //exprn := e.(*expr.Expression) + //getRefs(exprn) + //if err == nil { + // v, err2 := e.EvalWithScope(simpleScope, r) + // if err2 != nil { + // t.Error(err2) + // } else { + // t.Logf("Value: %t\n", v) + // } + //} else { + // t.Error(err) + //} + + r1 := ruleapi.NewRule("r1") + err := r1.AddExprCondition("c1", "($t1.p1 == $t2.p2) && ($t1.p3 > $t2.p2)", nil) + if err != nil { + t.Errorf("error: %s\n", err) + } + + +} + +func getRefs(e *expr.Expression) (map[string]bool, error) { + refs := make(map[string]bool) + err := getRefRecursively(e, refs) + return refs, err +} + +func getRefRecursively (e *expr.Expression, refs map[string]bool) (error) { + + if e == nil { + return nil + } + err := getRefsInternal(e.Left, refs) + if err != nil { + return err + } + err = getRefsInternal(e.Right, refs) + if err != nil { + return err + } + return nil +} + +func getRefsInternal(e *expr.Expression, refs map[string]bool) (error) { + if e.Type == funcexprtype.EXPRESSION { + getRefRecursively(e, refs) + } else if e.Type == funcexprtype.ARRAYREF { + value := e.Value.(string) + + split := strings.Split(value, ".") + if split != nil && len(split) != 2 { + return fmt.Errorf("Invalid tokens [%s]", value) + } + + refs[value] = true + } + return nil +} + +type RuleExpressionResolver struct { + +} + +func (r *RuleExpressionResolver) Resolve(toResolve string, scope data.Scope) (value interface{}, err error) { + fmt.Printf("Resolve: [%s]\n", toResolve) + return nil, nil +} + From c4a62bf778f249fda4ddb264f901c81d4616383c Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Sat, 20 Oct 2018 19:35:34 +0530 Subject: [PATCH 002/125] expr based condition --- ruleapi/exprcondition.go | 23 ++++++++- ruleapi/tests/cn_1_test.go | 99 ++++++++++++++++++-------------------- 2 files changed, 69 insertions(+), 53 deletions(-) diff --git a/ruleapi/exprcondition.go b/ruleapi/exprcondition.go index 8f0697e..721579f 100644 --- a/ruleapi/exprcondition.go +++ b/ruleapi/exprcondition.go @@ -2,6 +2,9 @@ package ruleapi import ( "github.com/project-flogo/rules/common/model" + "github.com/TIBCOSoftware/flogo-lib/core/mapper/exprmapper/expression" + "github.com/TIBCOSoftware/flogo-lib/core/mapper/exprmapper/expression/expr" + "github.com/TIBCOSoftware/flogo-lib/core/data" ) @@ -56,8 +59,26 @@ func (cnd *exprConditionImpl) GetTupleTypeAlias() []model.TupleType { func (cnd *exprConditionImpl) Evaluate (condName string, ruleNm string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) (bool, error) { result := false if cnd.cExpr != "" { - //todo + e, err := expression.ParseExpression(cnd.cExpr) + exprn := e.(*expr.Expression) + if err != nil { + return result, err + } + td := TuplePropertyResolver{} + res, err := exprn.EvalWithScope(nil, &td) + + result = res.(bool) } return result, nil +} + +type TuplePropertyResolver struct { + +} + +func (t *TuplePropertyResolver) Resolve(toResolve string, scope data.Scope) (value interface{}, err error) { + + + return nil, nil } \ No newline at end of file diff --git a/ruleapi/tests/cn_1_test.go b/ruleapi/tests/cn_1_test.go index 0a631ca..eb69a50 100644 --- a/ruleapi/tests/cn_1_test.go +++ b/ruleapi/tests/cn_1_test.go @@ -2,11 +2,6 @@ package tests import ( "testing" - "github.com/TIBCOSoftware/flogo-lib/core/data" - "fmt" - "github.com/TIBCOSoftware/flogo-lib/core/mapper/exprmapper/expression/expr" - "github.com/TIBCOSoftware/flogo-lib/core/mapper/exprmapper/funcexprtype" - "strings" "github.com/project-flogo/rules/ruleapi" ) @@ -48,51 +43,51 @@ func Test_1_Con(t *testing.T) { } - -func getRefs(e *expr.Expression) (map[string]bool, error) { - refs := make(map[string]bool) - err := getRefRecursively(e, refs) - return refs, err -} - -func getRefRecursively (e *expr.Expression, refs map[string]bool) (error) { - - if e == nil { - return nil - } - err := getRefsInternal(e.Left, refs) - if err != nil { - return err - } - err = getRefsInternal(e.Right, refs) - if err != nil { - return err - } - return nil -} - -func getRefsInternal(e *expr.Expression, refs map[string]bool) (error) { - if e.Type == funcexprtype.EXPRESSION { - getRefRecursively(e, refs) - } else if e.Type == funcexprtype.ARRAYREF { - value := e.Value.(string) - - split := strings.Split(value, ".") - if split != nil && len(split) != 2 { - return fmt.Errorf("Invalid tokens [%s]", value) - } - - refs[value] = true - } - return nil -} - -type RuleExpressionResolver struct { - -} - -func (r *RuleExpressionResolver) Resolve(toResolve string, scope data.Scope) (value interface{}, err error) { - fmt.Printf("Resolve: [%s]\n", toResolve) - return nil, nil -} +// +//func getRefs(e *expr.Expression) (map[string]bool, error) { +// refs := make(map[string]bool) +// err := getRefRecursively(e, refs) +// return refs, err +//} +// +//func getRefRecursively (e *expr.Expression, refs map[string]bool) (error) { +// +// if e == nil { +// return nil +// } +// err := getRefsInternal(e.Left, refs) +// if err != nil { +// return err +// } +// err = getRefsInternal(e.Right, refs) +// if err != nil { +// return err +// } +// return nil +//} + +//func getRefsInternal(e *expr.Expression, refs map[string]bool) (error) { +// if e.Type == funcexprtype.EXPRESSION { +// getRefRecursively(e, refs) +// } else if e.Type == funcexprtype.ARRAYREF { +// value := e.Value.(string) +// +// split := strings.Split(value, ".") +// if split != nil && len(split) != 2 { +// return fmt.Errorf("Invalid tokens [%s]", value) +// } +// +// refs[value] = true +// } +// return nil +//} + +//type RuleExpressionResolver struct { +// +//} +// +//func (r *RuleExpressionResolver) Resolve(toResolve string, scope data.Scope) (value interface{}, err error) { +// fmt.Printf("Resolve: [%s]\n", toResolve) +// return nil, nil +//} From be0d36d55c443d0379be4b5633046198f070c83d Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Sun, 21 Oct 2018 17:25:46 +0530 Subject: [PATCH 003/125] expr based condition --- common/model/types.go | 1 - ruleapi/exprcondition.go | 68 ++++++++++++++++++++----- ruleapi/rule.go | 31 ------------ ruleapi/tests/cn_1_test.go | 93 ---------------------------------- ruleapi/tests/common.go | 3 ++ ruleapi/tests/expr_1_test.go | 44 ++++++++++++++++ ruleapi/tests/expr_2_test.go | 45 ++++++++++++++++ ruleapi/tests/expr_3_test.go | 44 ++++++++++++++++ ruleapi/tests/expr_4_test.go | 44 ++++++++++++++++ ruleapi/tests/rtctxn_1_test.go | 2 +- 10 files changed, 236 insertions(+), 139 deletions(-) delete mode 100644 ruleapi/tests/cn_1_test.go create mode 100644 ruleapi/tests/expr_1_test.go create mode 100644 ruleapi/tests/expr_2_test.go create mode 100644 ruleapi/tests/expr_3_test.go create mode 100644 ruleapi/tests/expr_4_test.go diff --git a/common/model/types.go b/common/model/types.go index ee2f0e9..ca04954 100644 --- a/common/model/types.go +++ b/common/model/types.go @@ -32,7 +32,6 @@ type MutableRule interface { //Condition interface to maintain/get various condition properties type Condition interface { GetName() string - //GetEvaluator() ConditionEvaluator GetRule() Rule GetIdentifiers() []TupleType GetContext() RuleContext diff --git a/ruleapi/exprcondition.go b/ruleapi/exprcondition.go index 721579f..7f92408 100644 --- a/ruleapi/exprcondition.go +++ b/ruleapi/exprcondition.go @@ -1,18 +1,19 @@ package ruleapi import ( - "github.com/project-flogo/rules/common/model" + "github.com/TIBCOSoftware/flogo-lib/core/data" "github.com/TIBCOSoftware/flogo-lib/core/mapper/exprmapper/expression" "github.com/TIBCOSoftware/flogo-lib/core/mapper/exprmapper/expression/expr" - "github.com/TIBCOSoftware/flogo-lib/core/data" + "github.com/project-flogo/rules/common/model" + "reflect" + "strings" ) - type exprConditionImpl struct { name string rule model.Rule identifiers []model.TupleType - cExpr string + cExpr string ctx model.RuleContext } @@ -56,7 +57,7 @@ func (cnd *exprConditionImpl) GetTupleTypeAlias() []model.TupleType { return cnd.identifiers } -func (cnd *exprConditionImpl) Evaluate (condName string, ruleNm string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) (bool, error) { +func (cnd *exprConditionImpl) Evaluate(condName string, ruleNm string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) (bool, error) { result := false if cnd.cExpr != "" { e, err := expression.ParseExpression(cnd.cExpr) @@ -64,21 +65,62 @@ func (cnd *exprConditionImpl) Evaluate (condName string, ruleNm string, tuples m if err != nil { return result, err } - td := TuplePropertyResolver{} - res, err := exprn.EvalWithScope(nil, &td) - - result = res.(bool) + td := tuplePropertyResolver{} + scope := tupleScope{tuples} + res, err := exprn.EvalWithData(tuples, &scope, &td) + if err != nil { + return false, err + } else if reflect.TypeOf(res).Kind() == reflect.Bool { + result = res.(bool) + } } return result, nil } -type TuplePropertyResolver struct { +////////////////////////////////////////////////////////// +type tupleScope struct { + tuples map[model.TupleType]model.Tuple +} +func (ts *tupleScope) GetAttr(name string) (attr *data.Attribute, exists bool) { + return nil, false } -func (t *TuplePropertyResolver) Resolve(toResolve string, scope data.Scope) (value interface{}, err error) { +// SetAttrValue sets the value of the specified attribute +func (ts *tupleScope) SetAttrValue(name string, value interface{}) error { + return nil +} +/////////////////////////////////////////////////////////// +type tuplePropertyResolver struct { +} - return nil, nil -} \ No newline at end of file +func (t *tuplePropertyResolver) Resolve(toResolve string, scope data.Scope) (value interface{}, err error) { + + toResolve = toResolve[1:] + aliasAndProp := strings.Split(toResolve, ".") + + ts := scope.(*tupleScope) + var v interface{} + if ts != nil { + tuple := ts.tuples[model.TupleType(aliasAndProp[0])].(model.Tuple) + if tuple != nil { + + p := tuple.GetTupleDescriptor().GetProperty(aliasAndProp[1]) + switch p.PropType { + case data.TypeString: + v, err = tuple.GetString(aliasAndProp[1]) + case data.TypeInteger: + v, err = tuple.GetInt(aliasAndProp[1]) + case data.TypeLong: + v, err = tuple.GetLong(aliasAndProp[1]) + case data.TypeDouble: + v, err = tuple.GetDouble(aliasAndProp[1]) + case data.TypeBoolean: + v, err = tuple.GetBool(aliasAndProp[1]) + } + } + } + return v, err +} diff --git a/ruleapi/rule.go b/ruleapi/rule.go index a63b924..1681246 100644 --- a/ruleapi/rule.go +++ b/ruleapi/rule.go @@ -231,37 +231,6 @@ func (rule *ruleImpl) AddExprCondition(conditionName string, cstr string, ctx mo } -//func parseRefs(refs map[string]bool) (map[string]map[string]bool, error) { -// -// aliasAndRefs := make(map[string]map[string]bool) -// -// for val, _ := range refs { -// -// vals := strings.Split(val, ".") -// -// td := model.GetTupleDescriptor(model.TupleType(vals[0])) -// if td == nil { -// return aliasAndRefs, fmt.Errorf("Invalid TupleType [%s]", vals[0]) -// } -// -// prop := td.GetProperty(vals[1]) -// if prop == nil { -// return aliasAndRefs, fmt.Errorf("Property [%s] not found in TupleType [%s]", vals[1], vals[0]) -// } -// -// propsMap, found := aliasAndRefs[vals[0]] -// if !found { -// propsMap = make(map[string]bool) -// aliasAndRefs[vals[0]] = propsMap -// } -// -// propsMap[vals[1]] = true -// -// } -// -// return aliasAndRefs, nil -//} - func validateRefs(refs []string) (error) { for _, ref := range refs { vals := strings.Split(ref, ".") diff --git a/ruleapi/tests/cn_1_test.go b/ruleapi/tests/cn_1_test.go deleted file mode 100644 index eb69a50..0000000 --- a/ruleapi/tests/cn_1_test.go +++ /dev/null @@ -1,93 +0,0 @@ -package tests - -import ( - "testing" - "github.com/project-flogo/rules/ruleapi" -) - -func Test_1_Con(t *testing.T) { - - createRuleSession() - - //a1, _ := data.NewAttribute("name", data.TypeString, "n1") - //a2, _ := data.NewAttribute("age2", data.TypeInteger, 10) - //a3, _ := data.NewAttribute("age", data.TypeInteger, 10) - // - //attr := []*data.Attribute{a1, a2, a3} - // - //simpleScope := data.NewSimpleScope(attr, nil) - // - ////fmt.Printf("bc index %d\n", strings.Index("abc", "bc")) - // - //r := data.GetBasicResolver() - // - //e, err := expression.ParseExpression(`($.age == $.age2) && ($.age2 == 11)`) - //exprn := e.(*expr.Expression) - //getRefs(exprn) - //if err == nil { - // v, err2 := e.EvalWithScope(simpleScope, r) - // if err2 != nil { - // t.Error(err2) - // } else { - // t.Logf("Value: %t\n", v) - // } - //} else { - // t.Error(err) - //} - - r1 := ruleapi.NewRule("r1") - err := r1.AddExprCondition("c1", "($t1.p1 == $t2.p2) && ($t1.p3 > $t2.p2)", nil) - if err != nil { - t.Errorf("error: %s\n", err) - } - - -} -// -//func getRefs(e *expr.Expression) (map[string]bool, error) { -// refs := make(map[string]bool) -// err := getRefRecursively(e, refs) -// return refs, err -//} -// -//func getRefRecursively (e *expr.Expression, refs map[string]bool) (error) { -// -// if e == nil { -// return nil -// } -// err := getRefsInternal(e.Left, refs) -// if err != nil { -// return err -// } -// err = getRefsInternal(e.Right, refs) -// if err != nil { -// return err -// } -// return nil -//} - -//func getRefsInternal(e *expr.Expression, refs map[string]bool) (error) { -// if e.Type == funcexprtype.EXPRESSION { -// getRefRecursively(e, refs) -// } else if e.Type == funcexprtype.ARRAYREF { -// value := e.Value.(string) -// -// split := strings.Split(value, ".") -// if split != nil && len(split) != 2 { -// return fmt.Errorf("Invalid tokens [%s]", value) -// } -// -// refs[value] = true -// } -// return nil -//} - -//type RuleExpressionResolver struct { -// -//} -// -//func (r *RuleExpressionResolver) Resolve(toResolve string, scope data.Scope) (value interface{}, err error) { -// fmt.Printf("Resolve: [%s]\n", toResolve) -// return nil, nil -//} - diff --git a/ruleapi/tests/common.go b/ruleapi/tests/common.go index b051379..4e4e8f5 100644 --- a/ruleapi/tests/common.go +++ b/ruleapi/tests/common.go @@ -33,6 +33,7 @@ func trueCondition(ruleName string, condName string, tuples map[model.TupleType] } func emptyAction(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + } func printTuples(t *testing.T, oprn string, tupleMap map[string]map[string]model.Tuple) { @@ -58,3 +59,5 @@ type txnCtx struct { Testing *testing.T TxnCnt int } + +type TestKey struct{} \ No newline at end of file diff --git a/ruleapi/tests/expr_1_test.go b/ruleapi/tests/expr_1_test.go new file mode 100644 index 0000000..36a6656 --- /dev/null +++ b/ruleapi/tests/expr_1_test.go @@ -0,0 +1,44 @@ +package tests + +import ( + "testing" + "github.com/project-flogo/rules/ruleapi" + "github.com/project-flogo/rules/common/model" + "golang.org/x/net/context" +) + +//1 condition, 1 expression +func Test_1_Expr(t *testing.T) { + + rs, _ := createRuleSession() + r1 := ruleapi.NewRule("r1") + r1.AddExprCondition("c1", "$t1.p1 > $t2.p1", nil) + r1.SetAction(a1) + rs.AddRule(r1) + + rs.Start(nil) + + var ctx context.Context + + t1, _ := model.NewTupleWithKeyValues("t1", "t1") + t1.SetInt(nil,"p1", 2) + t1.SetDouble(nil,"p2", 1.3) + t1.SetString(nil,"p3", "t3") + + ctx = context.WithValue(context.TODO(), TestKey{}, t) + rs.Assert(ctx, t1) + + t2, _ := model.NewTupleWithKeyValues("t2", "t2") + t2.SetInt(nil,"p1", 1) + t2.SetDouble(nil,"p2", 1.1) + t2.SetString(nil,"p3", "t3") + + ctx = context.WithValue(context.TODO(), TestKey{}, t) + rs.Assert(ctx, t2) + rs.Unregister() +} + +func a1(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + t := ctx.Value(TestKey{}).(*testing.T) + t.Logf("Test_1_Expr executed!") +} \ No newline at end of file diff --git a/ruleapi/tests/expr_2_test.go b/ruleapi/tests/expr_2_test.go new file mode 100644 index 0000000..1997dc8 --- /dev/null +++ b/ruleapi/tests/expr_2_test.go @@ -0,0 +1,45 @@ +package tests + +import ( + "testing" + "github.com/project-flogo/rules/ruleapi" + "github.com/project-flogo/rules/common/model" + "golang.org/x/net/context" +) + +//2 conditions, 1 expr each +func Test_2_Expr(t *testing.T) { + + rs, _ := createRuleSession() + r1 := ruleapi.NewRule("r1") + r1.AddExprCondition("c1", "$t1.p1 > $t2.p1", nil) + r1.AddExprCondition("c2", "$t1.p1 == 2", nil) + r1.SetAction(a2) + rs.AddRule(r1) + + rs.Start(nil) + + var ctx context.Context + + t1, _ := model.NewTupleWithKeyValues("t1", "t1") + t1.SetInt(nil,"p1", 2) + t1.SetDouble(nil,"p2", 1.3) + t1.SetString(nil,"p3", "t3") + + ctx = context.WithValue(context.TODO(), TestKey{}, t) + rs.Assert(ctx, t1) + + t2, _ := model.NewTupleWithKeyValues("t2", "t2") + t2.SetInt(nil,"p1", 1) + t2.SetDouble(nil,"p2", 1.1) + t2.SetString(nil,"p3", "t3") + + ctx = context.WithValue(context.TODO(), TestKey{}, t) + rs.Assert(ctx, t2) + rs.Unregister() +} + +func a2(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + t := ctx.Value(TestKey{}).(*testing.T) + t.Logf("Test_2_Expr executed!") +} \ No newline at end of file diff --git a/ruleapi/tests/expr_3_test.go b/ruleapi/tests/expr_3_test.go new file mode 100644 index 0000000..3a97575 --- /dev/null +++ b/ruleapi/tests/expr_3_test.go @@ -0,0 +1,44 @@ +package tests + +import ( + "testing" + "github.com/project-flogo/rules/ruleapi" + "github.com/project-flogo/rules/common/model" + "golang.org/x/net/context" +) + +//1 conditions, 2 expr +func Test_3_Expr(t *testing.T) { + + rs, _ := createRuleSession() + r1 := ruleapi.NewRule("r1") + r1.AddExprCondition("c1", "($t1.p1 > $t2.p1) && ($t1.p2 > $t2.p2)", nil) + r1.SetAction(a3) + rs.AddRule(r1) + + rs.Start(nil) + + var ctx context.Context + + t1, _ := model.NewTupleWithKeyValues("t1", "t1") + t1.SetInt(nil,"p1", 2) + t1.SetDouble(nil,"p2", 1.3) + t1.SetString(nil,"p3", "t3") + + ctx = context.WithValue(context.TODO(), TestKey{}, t) + rs.Assert(ctx, t1) + + t2, _ := model.NewTupleWithKeyValues("t2", "t2") + t2.SetInt(nil,"p1", 1) + t2.SetDouble(nil,"p2", 1.1) + t2.SetString(nil,"p3", "t3") + + ctx = context.WithValue(context.TODO(), TestKey{}, t) + rs.Assert(ctx, t2) + rs.Unregister() +} + +func a3(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + t := ctx.Value(TestKey{}).(*testing.T) + t.Logf("Test_3_Expr executed!") +} \ No newline at end of file diff --git a/ruleapi/tests/expr_4_test.go b/ruleapi/tests/expr_4_test.go new file mode 100644 index 0000000..547f62b --- /dev/null +++ b/ruleapi/tests/expr_4_test.go @@ -0,0 +1,44 @@ +package tests + +import ( + "testing" + "github.com/project-flogo/rules/ruleapi" + "github.com/project-flogo/rules/common/model" + "golang.org/x/net/context" +) + +//1 conditions, 3 expr +func Test_4_Expr(t *testing.T) { + + rs, _ := createRuleSession() + r1 := ruleapi.NewRule("r1") + r1.AddExprCondition("c1", "($t1.p1 > $t2.p1) && (($t1.p2 > $t2.p2) && ($t1.p3 == $t2.p3))", nil) + r1.SetAction(a4) + rs.AddRule(r1) + + rs.Start(nil) + + var ctx context.Context + + t1, _ := model.NewTupleWithKeyValues("t1", "t1") + t1.SetInt(nil,"p1", 2) + t1.SetDouble(nil,"p2", 1.3) + t1.SetString(nil,"p3", "t3") + + ctx = context.WithValue(context.TODO(), TestKey{}, t) + rs.Assert(ctx, t1) + + t2, _ := model.NewTupleWithKeyValues("t2", "t2") + t2.SetInt(nil,"p1", 1) + t2.SetDouble(nil,"p2", 1.1) + t2.SetString(nil,"p3", "t3") + + ctx = context.WithValue(context.TODO(), TestKey{}, t) + rs.Assert(ctx, t2) + rs.Unregister() +} + +func a4(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + t := ctx.Value(TestKey{}).(*testing.T) + t.Logf("Test_4_Expr executed!") +} \ No newline at end of file diff --git a/ruleapi/tests/rtctxn_1_test.go b/ruleapi/tests/rtctxn_1_test.go index 28ac038..6333aaf 100644 --- a/ruleapi/tests/rtctxn_1_test.go +++ b/ruleapi/tests/rtctxn_1_test.go @@ -14,7 +14,7 @@ func Test_T1(t *testing.T) { rs, _ := createRuleSession() rule := ruleapi.NewRule("R1") - rule.AddCondition("R1_c1", []string{"t1.none"}, trueCondition, nil) + rule.AddCondition("R1_c1", []string{"t1.none"}, trueCondition, t) rule.SetAction(emptyAction) rule.SetPriority(1) rs.AddRule(rule) From 1a28a292070b41c8b3692f531c776926134cee59 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Tue, 23 Oct 2018 14:19:17 +0530 Subject: [PATCH 004/125] expr based condition --- ruleapi/tests/expr_1_test.go | 55 ++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/ruleapi/tests/expr_1_test.go b/ruleapi/tests/expr_1_test.go index 36a6656..57557d0 100644 --- a/ruleapi/tests/expr_1_test.go +++ b/ruleapi/tests/expr_1_test.go @@ -5,6 +5,7 @@ import ( "github.com/project-flogo/rules/ruleapi" "github.com/project-flogo/rules/common/model" "golang.org/x/net/context" + "github.com/TIBCOSoftware/flogo-lib/core/mapper/exprmapper/expression" ) //1 condition, 1 expression @@ -12,7 +13,7 @@ func Test_1_Expr(t *testing.T) { rs, _ := createRuleSession() r1 := ruleapi.NewRule("r1") - r1.AddExprCondition("c1", "$t1.p1 > $t2.p1", nil) + r1.AddExprCondition("c1", "$t2.p2 > $t1.p1", nil) r1.SetAction(a1) rs.AddRule(r1) @@ -21,7 +22,7 @@ func Test_1_Expr(t *testing.T) { var ctx context.Context t1, _ := model.NewTupleWithKeyValues("t1", "t1") - t1.SetInt(nil,"p1", 2) + t1.SetInt(nil,"p1", 1) t1.SetDouble(nil,"p2", 1.3) t1.SetString(nil,"p3", "t3") @@ -30,7 +31,7 @@ func Test_1_Expr(t *testing.T) { t2, _ := model.NewTupleWithKeyValues("t2", "t2") t2.SetInt(nil,"p1", 1) - t2.SetDouble(nil,"p2", 1.1) + t2.SetDouble(nil,"p2", 1.0001) t2.SetString(nil,"p3", "t3") ctx = context.WithValue(context.TODO(), TestKey{}, t) @@ -41,4 +42,52 @@ func Test_1_Expr(t *testing.T) { func a1(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { t := ctx.Value(TestKey{}).(*testing.T) t.Logf("Test_1_Expr executed!") +} + +func Test_Eval (t *testing.T) { + expr, _ := expression.ParseExpression("1 == 1.23") + i, err := expr.Eval() + if err != nil { + t.Fatalf("error %s\n", err) + } + res := i.(bool) + if res { + t.Errorf("Expected false, got : %t\n ", res) + } +} + +func Test_Eval2 (t *testing.T) { + expr, _ := expression.ParseExpression("1 < 1.23") + i, err := expr.Eval() + if err != nil { + t.Fatalf("error %s\n", err) + } + res := i.(bool) + if !res { + t.Errorf("Expected true, got : %t\n ", res) + } +} + +func Test_Eval3 (t *testing.T) { + expr, _ := expression.ParseExpression("1.23 == 1") + i, err := expr.Eval() + if err != nil { + t.Fatalf("error %s\n", err) + } + res := i.(bool) + if res { + t.Errorf("Expected false, got : %t\n ", res) + } +} + +func Test_Eval4 (t *testing.T) { + expr, _ := expression.ParseExpression("1.23 > 1") + i, err := expr.Eval() + if err != nil { + t.Fatalf("error %s\n", err) + } + res := i.(bool) + if !res { + t.Errorf("Expected true, got : %t\n ", res) + } } \ No newline at end of file From 98283b6a75c17a3ef9fa60394a26054c8b670660 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Tue, 23 Oct 2018 14:20:16 +0530 Subject: [PATCH 005/125] expr based condition --- ruleapi/tests/expr_5_test.go | 44 ++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 ruleapi/tests/expr_5_test.go diff --git a/ruleapi/tests/expr_5_test.go b/ruleapi/tests/expr_5_test.go new file mode 100644 index 0000000..4fdfc77 --- /dev/null +++ b/ruleapi/tests/expr_5_test.go @@ -0,0 +1,44 @@ +package tests + +import ( + "testing" + "github.com/project-flogo/rules/ruleapi" + "github.com/project-flogo/rules/common/model" + "golang.org/x/net/context" +) + +//1 arithmetic operation +func Test_5_Expr(t *testing.T) { + + rs, _ := createRuleSession() + r1 := ruleapi.NewRule("r1") + r1.AddExprCondition("c1", "(($t1.p1 + $t2.p1) == 5) && (($t1.p2 > $t2.p2) && ($t1.p3 == $t2.p3))", nil) + r1.SetAction(a5) + rs.AddRule(r1) + + rs.Start(nil) + + var ctx context.Context + + t1, _ := model.NewTupleWithKeyValues("t1", "t1") + t1.SetInt(nil,"p1", 2) + t1.SetDouble(nil,"p2", 1.3) + t1.SetString(nil,"p3", "t3") + + ctx = context.WithValue(context.TODO(), TestKey{}, t) + rs.Assert(ctx, t1) + + t2, _ := model.NewTupleWithKeyValues("t2", "t2") + t2.SetInt(nil,"p1", 1) + t2.SetDouble(nil,"p2", 1.1) + t2.SetString(nil,"p3", "t3") + + ctx = context.WithValue(context.TODO(), TestKey{}, t) + rs.Assert(ctx, t2) + rs.Unregister() +} + +func a5(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + t := ctx.Value(TestKey{}).(*testing.T) + t.Logf("Test_5_Expr executed!") +} \ No newline at end of file From b8a741d920c21fe833c645302a2790a8c8410da9 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Tue, 5 Mar 2019 16:22:30 +0530 Subject: [PATCH 006/125] Refactored to use the new expression API of project-flogo/core --- common/model/tuple.go | 7 +- common/model/tuplekey.go | 2 +- ruleapi/exprcondition.go | 92 +++++++++++++++---------- ruleapi/rule.go | 130 +++++++++++++++++++---------------- ruleapi/tests/expr_1_test.go | 116 ++++++++++++++++--------------- ruleapi/tests/expr_2_test.go | 22 +++--- ruleapi/tests/expr_3_test.go | 20 +++--- ruleapi/tests/expr_4_test.go | 20 +++--- ruleapi/tests/expr_5_test.go | 20 +++--- 9 files changed, 233 insertions(+), 196 deletions(-) diff --git a/common/model/tuple.go b/common/model/tuple.go index d7e3bd3..700b0d6 100644 --- a/common/model/tuple.go +++ b/common/model/tuple.go @@ -23,6 +23,7 @@ type Tuple interface { GetBool(name string) (val bool, err error) //GetDateTime(name string) time.Time GetKey() TupleKey + GetMap() map[string]interface{} } //MutableTuple mutable part of the tuple @@ -210,7 +211,7 @@ func (t *tupleImpl) initTupleWithKeyValues(td *TupleDescriptor, values ...interf t.key = tk //populate the tuple key fields with the key values for _, keyProp := range td.GetKeyProps() { - t.tuples [keyProp] = tk.GetValue(keyProp) + t.tuples[keyProp] = tk.GetValue(keyProp) } return err } @@ -273,3 +274,7 @@ func (t *tupleImpl) isKeyProp(propName string) bool { } return found } + +func (t *tupleImpl) GetMap() map[string]interface{} { + return t.tuples +} diff --git a/common/model/tuplekey.go b/common/model/tuplekey.go index 996e2e3..7a12d62 100644 --- a/common/model/tuplekey.go +++ b/common/model/tuplekey.go @@ -2,8 +2,8 @@ package model import ( "fmt" - "reflect" "github.com/project-flogo/core/data/coerce" + "reflect" ) // TupleKey primary key of a tuple diff --git a/ruleapi/exprcondition.go b/ruleapi/exprcondition.go index 7f92408..26734b0 100644 --- a/ruleapi/exprcondition.go +++ b/ruleapi/exprcondition.go @@ -1,14 +1,24 @@ package ruleapi import ( - "github.com/TIBCOSoftware/flogo-lib/core/data" - "github.com/TIBCOSoftware/flogo-lib/core/mapper/exprmapper/expression" - "github.com/TIBCOSoftware/flogo-lib/core/mapper/exprmapper/expression/expr" + "github.com/project-flogo/core/data" + "github.com/project-flogo/core/data/expression" + "github.com/project-flogo/core/data/expression/script" + "github.com/project-flogo/core/data/resolve" "github.com/project-flogo/rules/common/model" "reflect" - "strings" ) +var td tuplePropertyResolver +var resolver resolve.CompositeResolver +var factory expression.Factory + +func init() { + td = tuplePropertyResolver{} + resolver = resolve.NewCompositeResolver(map[string]resolve.Resolver{".": &td}) + factory = script.NewExprFactory(resolver) +} + type exprConditionImpl struct { name string rule model.Rule @@ -60,14 +70,14 @@ func (cnd *exprConditionImpl) GetTupleTypeAlias() []model.TupleType { func (cnd *exprConditionImpl) Evaluate(condName string, ruleNm string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) (bool, error) { result := false if cnd.cExpr != "" { - e, err := expression.ParseExpression(cnd.cExpr) - exprn := e.(*expr.Expression) + //e, err := expression.ParseExpression(cnd.cExpr) + exprn, err := factory.NewExpr(cnd.cExpr) if err != nil { return result, err } - td := tuplePropertyResolver{} + scope := tupleScope{tuples} - res, err := exprn.EvalWithData(tuples, &scope, &td) + res, err := exprn.Eval(&scope) if err != nil { return false, err } else if reflect.TypeOf(res).Kind() == reflect.Bool { @@ -83,8 +93,12 @@ type tupleScope struct { tuples map[model.TupleType]model.Tuple } -func (ts *tupleScope) GetAttr(name string) (attr *data.Attribute, exists bool) { - return nil, false +func (ts *tupleScope) GetValue(name string) (value interface{}, exists bool) { + return false, true +} + +func (ts *tupleScope) SetValue(name string, value interface{}) error { + return nil } // SetAttrValue sets the value of the specified attribute @@ -96,31 +110,39 @@ func (ts *tupleScope) SetAttrValue(name string, value interface{}) error { type tuplePropertyResolver struct { } -func (t *tuplePropertyResolver) Resolve(toResolve string, scope data.Scope) (value interface{}, err error) { +//func (t *tuplePropertyResolver) Resolve(toResolve string, scope data.Scope) (value interface{}, err error) { +func (t *tuplePropertyResolver) Resolve(scope data.Scope, item string, field string) (interface{}, error) { + //toResolve = toResolve[1:] + //aliasAndProp := strings.Split(toResolve, ".") + // + //var v interface{} + //if ts != nil { + // tuple := ts.tuples[model.TupleType(aliasAndProp[0])].(model.Tuple) + // if tuple != nil { + // + // p := tuple.GetTupleDescriptor().GetProperty(aliasAndProp[1]) + // switch p.PropType { + // case data.TypeString: + // v, err = tuple.GetString(aliasAndProp[1]) + // case data.TypeInteger: + // v, err = tuple.GetInt(aliasAndProp[1]) + // case data.TypeLong: + // v, err = tuple.GetLong(aliasAndProp[1]) + // case data.TypeDouble: + // v, err = tuple.GetDouble(aliasAndProp[1]) + // case data.TypeBoolean: + // v, err = tuple.GetBool(aliasAndProp[1]) + // } + // } + //} + //return v, err + ts := scope.(*tupleScope) + tuple := ts.tuples[model.TupleType(field)] + m := tuple.GetMap() + return m, nil - toResolve = toResolve[1:] - aliasAndProp := strings.Split(toResolve, ".") +} - ts := scope.(*tupleScope) - var v interface{} - if ts != nil { - tuple := ts.tuples[model.TupleType(aliasAndProp[0])].(model.Tuple) - if tuple != nil { - - p := tuple.GetTupleDescriptor().GetProperty(aliasAndProp[1]) - switch p.PropType { - case data.TypeString: - v, err = tuple.GetString(aliasAndProp[1]) - case data.TypeInteger: - v, err = tuple.GetInt(aliasAndProp[1]) - case data.TypeLong: - v, err = tuple.GetLong(aliasAndProp[1]) - case data.TypeDouble: - v, err = tuple.GetDouble(aliasAndProp[1]) - case data.TypeBoolean: - v, err = tuple.GetBool(aliasAndProp[1]) - } - } - } - return v, err +func (*tuplePropertyResolver) GetResolverInfo() *resolve.ResolverInfo { + return resolve.NewResolverInfo(false, false) } diff --git a/ruleapi/rule.go b/ruleapi/rule.go index 1681246..18aee4d 100644 --- a/ruleapi/rule.go +++ b/ruleapi/rule.go @@ -2,11 +2,9 @@ package ruleapi import ( "fmt" + "regexp" "strings" - "github.com/TIBCOSoftware/flogo-lib/core/mapper/exprmapper/expression" - "github.com/TIBCOSoftware/flogo-lib/core/mapper/exprmapper/expression/expr" - "github.com/TIBCOSoftware/flogo-lib/core/mapper/exprmapper/funcexprtype" "github.com/project-flogo/rules/common/model" ) @@ -207,17 +205,15 @@ func (rule *ruleImpl) GetDeps() map[model.TupleType]map[string]bool { func (rule *ruleImpl) AddExprCondition(conditionName string, cstr string, ctx model.RuleContext) error { - e, err := expression.ParseExpression(cstr) - if err != nil { - return err - } - exprn := e.(*expr.Expression) - refs, err := getRefs(exprn) - if err != nil { - return err - } + //e, err := expression.ParseExpression(cstr) + //if err != nil { + // return err + //} + //exprn := e.(*expr.Expression) + //refs, err := getRefs(exprn) + refs := getRefs(cstr) - err = validateRefs(refs) + err := validateRefs(refs) if err != nil { return err } @@ -227,12 +223,13 @@ func (rule *ruleImpl) AddExprCondition(conditionName string, cstr string, ctx mo return err } rule.addExprCond(conditionName, typeDeps, cstr, ctx) - return err + return nil } -func validateRefs(refs []string) (error) { +func validateRefs(refs []string) error { for _, ref := range refs { + ref := strings.TrimPrefix(ref, "$.") vals := strings.Split(ref, ".") td := model.GetTupleDescriptor(model.TupleType(vals[0])) if td == nil { @@ -246,51 +243,62 @@ func validateRefs(refs []string) (error) { return nil } -func getRefs(e *expr.Expression) ([]string, error) { - refs := make(map[string]bool) - keys := []string{} - - err := getRefRecursively(e, refs) - if err != nil { - return keys, err - } - - for key, _ := range refs { - keys = append (keys, key) - } - return keys, err -} - -func getRefRecursively(e *expr.Expression, refs map[string]bool) error { - - if e == nil { - return nil - } - err := getRefsInternal(e.Left, refs) - if err != nil { - return err - } - err = getRefsInternal(e.Right, refs) - if err != nil { - return err - } - return nil -} - -func getRefsInternal(e *expr.Expression, refs map[string]bool) error { - if e.Type == funcexprtype.EXPRESSION { - getRefRecursively(e, refs) - } else if e.Type == funcexprtype.REF || e.Type == funcexprtype.ARRAYREF { - value := e.Value.(string) - if strings.Index(value, "$") == 0 { - value = value[1:len(value)] - split := strings.Split(value, ".") - if split != nil && len(split) != 2 { - return fmt.Errorf("Invalid tokens [%s]", value) - } - - refs[value] = true - } +// +//func getRefs(e *expr.Expression) ([]string, error) { +// refs := make(map[string]bool) +// keys := []string{} +// +// err := getRefRecursively(e, refs) +// if err != nil { +// return keys, err +// } +// +// for key, _ := range refs { +// keys = append (keys, key) +// } +// return keys, err +//} +// +//func getRefRecursively(e *expr.Expression, refs map[string]bool) error { +// +// if e == nil { +// return nil +// } +// err := getRefsInternal(e.Left, refs) +// if err != nil { +// return err +// } +// err = getRefsInternal(e.Right, refs) +// if err != nil { +// return err +// } +// return nil +//} +// +//func getRefsInternal(e *expr.Expression, refs map[string]bool) error { +// if e.Type == funcexprtype.EXPRESSION { +// getRefRecursively(e, refs) +// } else if e.Type == funcexprtype.REF || e.Type == funcexprtype.ARRAYREF { +// value := e.Value.(string) +// if strings.Index(value, "$") == 0 { +// value = value[1:len(value)] +// split := strings.Split(value, ".") +// if split != nil && len(split) != 2 { +// return fmt.Errorf("Invalid tokens [%s]", value) +// } +// +// refs[value] = true +// } +// } +// return nil +//} + +func getRefs(cstr string) []string { + keys2 := []string{} + re := regexp.MustCompile(`\$\.(\w+\.\w+)`) + keys := re.FindAllStringSubmatch(cstr, -1) + for _, k := range keys { + keys2 = append(keys2, k[1]) } - return nil + return keys2 } diff --git a/ruleapi/tests/expr_1_test.go b/ruleapi/tests/expr_1_test.go index 57557d0..66c46c3 100644 --- a/ruleapi/tests/expr_1_test.go +++ b/ruleapi/tests/expr_1_test.go @@ -1,11 +1,10 @@ package tests import ( - "testing" - "github.com/project-flogo/rules/ruleapi" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/ruleapi" "golang.org/x/net/context" - "github.com/TIBCOSoftware/flogo-lib/core/mapper/exprmapper/expression" + "testing" ) //1 condition, 1 expression @@ -13,7 +12,7 @@ func Test_1_Expr(t *testing.T) { rs, _ := createRuleSession() r1 := ruleapi.NewRule("r1") - r1.AddExprCondition("c1", "$t2.p2 > $t1.p1", nil) + r1.AddExprCondition("c1", "$.t2.p2 > $.t1.p1", nil) r1.SetAction(a1) rs.AddRule(r1) @@ -22,17 +21,17 @@ func Test_1_Expr(t *testing.T) { var ctx context.Context t1, _ := model.NewTupleWithKeyValues("t1", "t1") - t1.SetInt(nil,"p1", 1) - t1.SetDouble(nil,"p2", 1.3) - t1.SetString(nil,"p3", "t3") + t1.SetInt(nil, "p1", 1) + t1.SetDouble(nil, "p2", 1.3) + t1.SetString(nil, "p3", "t3") ctx = context.WithValue(context.TODO(), TestKey{}, t) rs.Assert(ctx, t1) t2, _ := model.NewTupleWithKeyValues("t2", "t2") - t2.SetInt(nil,"p1", 1) - t2.SetDouble(nil,"p2", 1.0001) - t2.SetString(nil,"p3", "t3") + t2.SetInt(nil, "p1", 1) + t2.SetDouble(nil, "p2", 1.0001) + t2.SetString(nil, "p3", "t3") ctx = context.WithValue(context.TODO(), TestKey{}, t) rs.Assert(ctx, t2) @@ -44,50 +43,53 @@ func a1(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[m t.Logf("Test_1_Expr executed!") } -func Test_Eval (t *testing.T) { - expr, _ := expression.ParseExpression("1 == 1.23") - i, err := expr.Eval() - if err != nil { - t.Fatalf("error %s\n", err) - } - res := i.(bool) - if res { - t.Errorf("Expected false, got : %t\n ", res) - } -} - -func Test_Eval2 (t *testing.T) { - expr, _ := expression.ParseExpression("1 < 1.23") - i, err := expr.Eval() - if err != nil { - t.Fatalf("error %s\n", err) - } - res := i.(bool) - if !res { - t.Errorf("Expected true, got : %t\n ", res) - } -} - -func Test_Eval3 (t *testing.T) { - expr, _ := expression.ParseExpression("1.23 == 1") - i, err := expr.Eval() - if err != nil { - t.Fatalf("error %s\n", err) - } - res := i.(bool) - if res { - t.Errorf("Expected false, got : %t\n ", res) - } -} - -func Test_Eval4 (t *testing.T) { - expr, _ := expression.ParseExpression("1.23 > 1") - i, err := expr.Eval() - if err != nil { - t.Fatalf("error %s\n", err) - } - res := i.(bool) - if !res { - t.Errorf("Expected true, got : %t\n ", res) - } -} \ No newline at end of file +// +// These standalone tests are not relevant anymore as the expression API has changed +// +//func Test_Eval (t *testing.T) { +// expr, _ := expression.ParseExpression("1 == 1.23") +// i, err := expr.Eval() +// if err != nil { +// t.Fatalf("error %s\n", err) +// } +// res := i.(bool) +// if res { +// t.Errorf("Expected false, got : %t\n ", res) +// } +//} +// +//func Test_Eval2 (t *testing.T) { +// expr, _ := expression.ParseExpression("1 < 1.23") +// i, err := expr.Eval() +// if err != nil { +// t.Fatalf("error %s\n", err) +// } +// res := i.(bool) +// if !res { +// t.Errorf("Expected true, got : %t\n ", res) +// } +//} +// +//func Test_Eval3 (t *testing.T) { +// expr, _ := expression.ParseExpression("1.23 == 1") +// i, err := expr.Eval() +// if err != nil { +// t.Fatalf("error %s\n", err) +// } +// res := i.(bool) +// if res { +// t.Errorf("Expected false, got : %t\n ", res) +// } +//} +// +//func Test_Eval4 (t *testing.T) { +// expr, _ := expression.ParseExpression("1.23 > 1") +// i, err := expr.Eval() +// if err != nil { +// t.Fatalf("error %s\n", err) +// } +// res := i.(bool) +// if !res { +// t.Errorf("Expected true, got : %t\n ", res) +// } +//} diff --git a/ruleapi/tests/expr_2_test.go b/ruleapi/tests/expr_2_test.go index 1997dc8..3be99f6 100644 --- a/ruleapi/tests/expr_2_test.go +++ b/ruleapi/tests/expr_2_test.go @@ -1,10 +1,10 @@ package tests import ( - "testing" - "github.com/project-flogo/rules/ruleapi" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/ruleapi" "golang.org/x/net/context" + "testing" ) //2 conditions, 1 expr each @@ -12,8 +12,8 @@ func Test_2_Expr(t *testing.T) { rs, _ := createRuleSession() r1 := ruleapi.NewRule("r1") - r1.AddExprCondition("c1", "$t1.p1 > $t2.p1", nil) - r1.AddExprCondition("c2", "$t1.p1 == 2", nil) + r1.AddExprCondition("c1", "$.t1.p1 > $.t2.p1", nil) + r1.AddExprCondition("c2", "$.t1.p1 == 2", nil) r1.SetAction(a2) rs.AddRule(r1) @@ -22,17 +22,17 @@ func Test_2_Expr(t *testing.T) { var ctx context.Context t1, _ := model.NewTupleWithKeyValues("t1", "t1") - t1.SetInt(nil,"p1", 2) - t1.SetDouble(nil,"p2", 1.3) - t1.SetString(nil,"p3", "t3") + t1.SetInt(nil, "p1", 2) + t1.SetDouble(nil, "p2", 1.3) + t1.SetString(nil, "p3", "t3") ctx = context.WithValue(context.TODO(), TestKey{}, t) rs.Assert(ctx, t1) t2, _ := model.NewTupleWithKeyValues("t2", "t2") - t2.SetInt(nil,"p1", 1) - t2.SetDouble(nil,"p2", 1.1) - t2.SetString(nil,"p3", "t3") + t2.SetInt(nil, "p1", 1) + t2.SetDouble(nil, "p2", 1.1) + t2.SetString(nil, "p3", "t3") ctx = context.WithValue(context.TODO(), TestKey{}, t) rs.Assert(ctx, t2) @@ -42,4 +42,4 @@ func Test_2_Expr(t *testing.T) { func a2(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { t := ctx.Value(TestKey{}).(*testing.T) t.Logf("Test_2_Expr executed!") -} \ No newline at end of file +} diff --git a/ruleapi/tests/expr_3_test.go b/ruleapi/tests/expr_3_test.go index 3a97575..8012566 100644 --- a/ruleapi/tests/expr_3_test.go +++ b/ruleapi/tests/expr_3_test.go @@ -1,10 +1,10 @@ package tests import ( - "testing" - "github.com/project-flogo/rules/ruleapi" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/ruleapi" "golang.org/x/net/context" + "testing" ) //1 conditions, 2 expr @@ -12,7 +12,7 @@ func Test_3_Expr(t *testing.T) { rs, _ := createRuleSession() r1 := ruleapi.NewRule("r1") - r1.AddExprCondition("c1", "($t1.p1 > $t2.p1) && ($t1.p2 > $t2.p2)", nil) + r1.AddExprCondition("c1", "($.t1.p1 > $.t2.p1) && ($.t1.p2 > $.t2.p2)", nil) r1.SetAction(a3) rs.AddRule(r1) @@ -21,17 +21,17 @@ func Test_3_Expr(t *testing.T) { var ctx context.Context t1, _ := model.NewTupleWithKeyValues("t1", "t1") - t1.SetInt(nil,"p1", 2) - t1.SetDouble(nil,"p2", 1.3) - t1.SetString(nil,"p3", "t3") + t1.SetInt(nil, "p1", 2) + t1.SetDouble(nil, "p2", 1.3) + t1.SetString(nil, "p3", "t3") ctx = context.WithValue(context.TODO(), TestKey{}, t) rs.Assert(ctx, t1) t2, _ := model.NewTupleWithKeyValues("t2", "t2") - t2.SetInt(nil,"p1", 1) - t2.SetDouble(nil,"p2", 1.1) - t2.SetString(nil,"p3", "t3") + t2.SetInt(nil, "p1", 1) + t2.SetDouble(nil, "p2", 1.1) + t2.SetString(nil, "p3", "t3") ctx = context.WithValue(context.TODO(), TestKey{}, t) rs.Assert(ctx, t2) @@ -41,4 +41,4 @@ func Test_3_Expr(t *testing.T) { func a3(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { t := ctx.Value(TestKey{}).(*testing.T) t.Logf("Test_3_Expr executed!") -} \ No newline at end of file +} diff --git a/ruleapi/tests/expr_4_test.go b/ruleapi/tests/expr_4_test.go index 547f62b..936942e 100644 --- a/ruleapi/tests/expr_4_test.go +++ b/ruleapi/tests/expr_4_test.go @@ -1,10 +1,10 @@ package tests import ( - "testing" - "github.com/project-flogo/rules/ruleapi" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/ruleapi" "golang.org/x/net/context" + "testing" ) //1 conditions, 3 expr @@ -12,7 +12,7 @@ func Test_4_Expr(t *testing.T) { rs, _ := createRuleSession() r1 := ruleapi.NewRule("r1") - r1.AddExprCondition("c1", "($t1.p1 > $t2.p1) && (($t1.p2 > $t2.p2) && ($t1.p3 == $t2.p3))", nil) + r1.AddExprCondition("c1", "($.t1.p1 > $.t2.p1) && (($.t1.p2 > $.t2.p2) && ($.t1.p3 == $.t2.p3))", nil) r1.SetAction(a4) rs.AddRule(r1) @@ -21,17 +21,17 @@ func Test_4_Expr(t *testing.T) { var ctx context.Context t1, _ := model.NewTupleWithKeyValues("t1", "t1") - t1.SetInt(nil,"p1", 2) - t1.SetDouble(nil,"p2", 1.3) - t1.SetString(nil,"p3", "t3") + t1.SetInt(nil, "p1", 2) + t1.SetDouble(nil, "p2", 1.3) + t1.SetString(nil, "p3", "t3") ctx = context.WithValue(context.TODO(), TestKey{}, t) rs.Assert(ctx, t1) t2, _ := model.NewTupleWithKeyValues("t2", "t2") - t2.SetInt(nil,"p1", 1) - t2.SetDouble(nil,"p2", 1.1) - t2.SetString(nil,"p3", "t3") + t2.SetInt(nil, "p1", 1) + t2.SetDouble(nil, "p2", 1.1) + t2.SetString(nil, "p3", "t3") ctx = context.WithValue(context.TODO(), TestKey{}, t) rs.Assert(ctx, t2) @@ -41,4 +41,4 @@ func Test_4_Expr(t *testing.T) { func a4(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { t := ctx.Value(TestKey{}).(*testing.T) t.Logf("Test_4_Expr executed!") -} \ No newline at end of file +} diff --git a/ruleapi/tests/expr_5_test.go b/ruleapi/tests/expr_5_test.go index 4fdfc77..5676e2b 100644 --- a/ruleapi/tests/expr_5_test.go +++ b/ruleapi/tests/expr_5_test.go @@ -1,10 +1,10 @@ package tests import ( - "testing" - "github.com/project-flogo/rules/ruleapi" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/ruleapi" "golang.org/x/net/context" + "testing" ) //1 arithmetic operation @@ -12,7 +12,7 @@ func Test_5_Expr(t *testing.T) { rs, _ := createRuleSession() r1 := ruleapi.NewRule("r1") - r1.AddExprCondition("c1", "(($t1.p1 + $t2.p1) == 5) && (($t1.p2 > $t2.p2) && ($t1.p3 == $t2.p3))", nil) + r1.AddExprCondition("c1", "(($.t1.p1 + $.t2.p1) == 5) && (($.t1.p2 > $.t2.p2) && ($.t1.p3 == $.t2.p3))", nil) r1.SetAction(a5) rs.AddRule(r1) @@ -21,17 +21,17 @@ func Test_5_Expr(t *testing.T) { var ctx context.Context t1, _ := model.NewTupleWithKeyValues("t1", "t1") - t1.SetInt(nil,"p1", 2) - t1.SetDouble(nil,"p2", 1.3) - t1.SetString(nil,"p3", "t3") + t1.SetInt(nil, "p1", 1) + t1.SetDouble(nil, "p2", 1.3) + t1.SetString(nil, "p3", "t3") ctx = context.WithValue(context.TODO(), TestKey{}, t) rs.Assert(ctx, t1) t2, _ := model.NewTupleWithKeyValues("t2", "t2") - t2.SetInt(nil,"p1", 1) - t2.SetDouble(nil,"p2", 1.1) - t2.SetString(nil,"p3", "t3") + t2.SetInt(nil, "p1", 4) + t2.SetDouble(nil, "p2", 1.1) + t2.SetString(nil, "p3", "t3") ctx = context.WithValue(context.TODO(), TestKey{}, t) rs.Assert(ctx, t2) @@ -41,4 +41,4 @@ func Test_5_Expr(t *testing.T) { func a5(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { t := ctx.Value(TestKey{}).(*testing.T) t.Logf("Test_5_Expr executed!") -} \ No newline at end of file +} From 2b51feacd17c629e5c3f7029de93c4ec38e411fd Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Thu, 6 Dec 2018 14:33:54 +0530 Subject: [PATCH 007/125] iterator interface for join table iteration --- rete/joinnode.go | 10 +++++++--- rete/jointable.go | 19 +++++++++++++++++-- rete/network.go | 4 +++- rete/rowiterator.go | 24 ++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 rete/rowiterator.go diff --git a/rete/joinnode.go b/rete/joinnode.go index 6f533e1..849612b 100644 --- a/rete/joinnode.go +++ b/rete/joinnode.go @@ -169,7 +169,9 @@ func (jn *joinNodeImpl) assertFromRight(ctx context.Context, handles []reteHandl tupleTableRow := newJoinTableRow(handles) jn.rightTable.addRow(tupleTableRow) //TODO: rete listeners etc. - for tupleTableRowLeft := range jn.leftTable.getMap() { + rIterator:= jn.leftTable.iterator() + for rIterator.hasNext() { + tupleTableRowLeft := rIterator.next() success := jn.joinLeftObjects(tupleTableRowLeft.getHandles(), joinedHandles) if !success { //TODO: handle it @@ -217,8 +219,10 @@ func (jn *joinNodeImpl) assertFromLeft(ctx context.Context, handles []reteHandle tupleTableRow := newJoinTableRow(handles) jn.leftTable.addRow(tupleTableRow) //TODO: rete listeners etc. - for tupleTableRowRight := range jn.rightTable.getMap() { - success := jn.joinRightObjects(tupleTableRowRight.getHandles(), joinedHandles) + rIterator := jn.rightTable.iterator() + for rIterator.hasNext() { + tupleTableRowRight := rIterator.next() + success := jn.joinRightObjects(tupleTableRowRight.getHandles(), joinedHandles) if !success { //TODO: handle it continue diff --git a/rete/jointable.go b/rete/jointable.go index 9007347..66918f3 100644 --- a/rete/jointable.go +++ b/rete/jointable.go @@ -1,14 +1,18 @@ package rete -import "github.com/project-flogo/rules/common/model" +import ( + "github.com/project-flogo/rules/common/model" + "container/list" +) type joinTable interface { addRow(row joinTableRow) //list of Tuples getID() int len() int - getMap() map[joinTableRow]joinTableRow + //getMap() map[joinTableRow]joinTableRow removeRow(row joinTableRow) getRule() model.Rule + iterator() rowIterator } type joinTableImpl struct { @@ -58,3 +62,14 @@ func (jt *joinTableImpl) getMap() map[joinTableRow]joinTableRow { func (jt *joinTableImpl) getRule() model.Rule { return jt.rule } + +func (jt *joinTableImpl) iterator() rowIterator { + ri := rowIteratorImpl{} + ri.table = jt.table + ri.kList = list.List{} + for k, _:= range jt.table { + ri.kList.PushBack(k) + } + ri.curr = ri.kList.Front() + return &ri +} \ No newline at end of file diff --git a/rete/network.go b/rete/network.go index 4b3bc2b..09cec8e 100644 --- a/rete/network.go +++ b/rete/network.go @@ -197,7 +197,9 @@ func removeRefsFromReteHandles(joinTableVar joinTable) { if joinTableVar == nil { return } - for tableRow := range joinTableVar.getMap() { + rIterator := joinTableVar.iterator() + for rIterator.hasNext() { + tableRow := rIterator.next() for _, handle := range tableRow.getHandles() { handle.removeJoinTable(joinTableVar) } diff --git a/rete/rowiterator.go b/rete/rowiterator.go new file mode 100644 index 0000000..77a7900 --- /dev/null +++ b/rete/rowiterator.go @@ -0,0 +1,24 @@ +package rete + +import "container/list" + +type rowIterator interface { + hasNext() bool + next() joinTableRow +} + +type rowIteratorImpl struct { + table map[joinTableRow]joinTableRow + kList list.List + curr *list.Element +} + +func (ri *rowIteratorImpl) hasNext() bool { + return ri.curr != nil +} + +func (ri *rowIteratorImpl) next() joinTableRow { + val := ri.curr.Value.(joinTableRow) + ri.curr = ri.curr.Next() + return val +} From 556f7a2463b46d85ddee61a3b7762a2231b5fca0 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Thu, 6 Dec 2018 15:07:03 +0530 Subject: [PATCH 008/125] assign integer id to jointablerows --- rete/joinnode.go | 4 ++-- rete/jointablerow.go | 13 ++++++++++--- rete/node.go | 3 ++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/rete/joinnode.go b/rete/joinnode.go index 849612b..1f5bc18 100644 --- a/rete/joinnode.go +++ b/rete/joinnode.go @@ -166,7 +166,7 @@ func (jn *joinNodeImpl) assertFromRight(ctx context.Context, handles []reteHandl //TODO: other stuff. right now focus on tuple table jn.joinRightObjects(handles, joinedHandles) - tupleTableRow := newJoinTableRow(handles) + tupleTableRow := newJoinTableRow(handles, jn.nw.incrementAndGetId()) jn.rightTable.addRow(tupleTableRow) //TODO: rete listeners etc. rIterator:= jn.leftTable.iterator() @@ -216,7 +216,7 @@ func (jn *joinNodeImpl) joinRightObjects(rightHandles []reteHandle, joinedHandle func (jn *joinNodeImpl) assertFromLeft(ctx context.Context, handles []reteHandle, joinedHandles []reteHandle) { jn.joinLeftObjects(handles, joinedHandles) //TODO: other stuff. right now focus on tuple table - tupleTableRow := newJoinTableRow(handles) + tupleTableRow := newJoinTableRow(handles, jn.nw.incrementAndGetId()) jn.leftTable.addRow(tupleTableRow) //TODO: rete listeners etc. rIterator := jn.rightTable.iterator() diff --git a/rete/jointablerow.go b/rete/jointablerow.go index 390c4fd..e086360 100644 --- a/rete/jointablerow.go +++ b/rete/jointablerow.go @@ -1,23 +1,30 @@ package rete type joinTableRow interface { + getID() int getHandles() []reteHandle } type joinTableRowImpl struct { + id int handles []reteHandle } -func newJoinTableRow(handles []reteHandle) joinTableRow { +func newJoinTableRow(handles []reteHandle, id int) joinTableRow { jtr := joinTableRowImpl{} - jtr.initJoinTableRow(handles) + jtr.initJoinTableRow(handles, id) return &jtr } -func (jtr *joinTableRowImpl) initJoinTableRow(handles []reteHandle) { +func (jtr *joinTableRowImpl) initJoinTableRow(handles []reteHandle, id int) { jtr.handles = append([]reteHandle{}, handles...) + jtr.id = id } func (jtr *joinTableRowImpl) getHandles() []reteHandle { return jtr.handles } + +func (jtr *joinTableRowImpl) getID() int { + return jtr.id +} diff --git a/rete/node.go b/rete/node.go index 36a69f8..0b0d7b9 100644 --- a/rete/node.go +++ b/rete/node.go @@ -23,6 +23,7 @@ type nodeImpl struct { nodeLinkVar nodeLink id int rule model.Rule + nw Network } //NewNode ... returns a new node @@ -33,7 +34,7 @@ type nodeImpl struct { //} func (n *nodeImpl) initNodeImpl(nw Network, rule model.Rule, identifiers []model.TupleType) { - + n.nw = nw n.id = nw.incrementAndGetId() n.identifiers = identifiers From 6ec22a212a800be8d01ffbd4830c84747051f852 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Thu, 6 Dec 2018 16:12:30 +0530 Subject: [PATCH 009/125] handle holds ids of tables and rows --- rete/jointable.go | 22 +++++++++++++--------- rete/network.go | 10 ++++++++++ rete/retehandle.go | 32 +++++++++++++++++--------------- rete/rowiterator.go | 5 +++-- 4 files changed, 43 insertions(+), 26 deletions(-) diff --git a/rete/jointable.go b/rete/jointable.go index 66918f3..5de2d08 100644 --- a/rete/jointable.go +++ b/rete/jointable.go @@ -10,14 +10,14 @@ type joinTable interface { getID() int len() int //getMap() map[joinTableRow]joinTableRow - removeRow(row joinTableRow) + removeRow(rowID int) getRule() model.Rule iterator() rowIterator } type joinTableImpl struct { id int - table map[joinTableRow]joinTableRow + table map[int]joinTableRow idr []model.TupleType rule model.Rule } @@ -25,13 +25,17 @@ type joinTableImpl struct { func newJoinTable(nw Network, rule model.Rule, identifiers []model.TupleType) joinTable { jT := joinTableImpl{} jT.initJoinTableImpl(nw, rule, identifiers) + + //add it to all join tables collection before returning + reteNw := nw.(*reteNetworkImpl) + reteNw.allJoinTables[jT.getID()] = &jT return &jT } func (jt *joinTableImpl) initJoinTableImpl(nw Network, rule model.Rule, identifiers []model.TupleType) { jt.id = nw.incrementAndGetId() jt.idr = identifiers - jt.table = map[joinTableRow]joinTableRow{} + jt.table = map[int]joinTableRow{} jt.rule = rule } @@ -40,24 +44,24 @@ func (jt *joinTableImpl) getID() int { } func (jt *joinTableImpl) addRow(row joinTableRow) { - jt.table[row] = row + jt.table[row.getID()] = row for i := 0; i < len(row.getHandles()); i++ { handle := row.getHandles()[i] handle.addJoinTableRowRef(row, jt) } } -func (jt *joinTableImpl) removeRow(row joinTableRow) { - delete(jt.table, row) +func (jt *joinTableImpl) removeRow(rowID int) { + delete(jt.table, rowID) } func (jt *joinTableImpl) len() int { return len(jt.table) } -func (jt *joinTableImpl) getMap() map[joinTableRow]joinTableRow { - return jt.table -} +//func (jt *joinTableImpl) getMap() map[joinTableRow]joinTableRow { +// return jt.table +//} func (jt *joinTableImpl) getRule() model.Rule { return jt.rule diff --git a/rete/network.go b/rete/network.go index 09cec8e..e6bb129 100644 --- a/rete/network.go +++ b/rete/network.go @@ -43,6 +43,8 @@ type Network interface { GetAssertedTupleByStringKey(key string) model.Tuple //RtcTransactionHandler RegisterRtcTransactionHandler(txnHandler model.RtcTransactionHandler, txnContext interface{}) + + getJoinTable(joinTableID int) joinTable } type reteNetworkImpl struct { @@ -66,6 +68,8 @@ type reteNetworkImpl struct { crudLock sync.Mutex txnHandler model.RtcTransactionHandler txnContext interface{} + + allJoinTables map[int]joinTable } //NewReteNetwork ... creates a new rete network @@ -81,6 +85,7 @@ func (nw *reteNetworkImpl) initReteNetwork() { nw.ruleNameNodesOfRule = make(map[string]*list.List) nw.ruleNameClassNodeLinksOfRule = make(map[string]*list.List) nw.allHandles = make(map[string]reteHandle) + nw.allJoinTables = make(map[int]joinTable) } func (nw *reteNetworkImpl) AddRule(rule model.Rule) (err error) { @@ -646,6 +651,7 @@ func (nw *reteNetworkImpl) getOrCreateHandle(ctx context.Context, tuple model.Tu if h == nil { h1 := handleImpl{} h1.initHandleImpl() + h1.nw = nw h1.setTuple(tuple) h = &h1 nw.allHandles[tuple.GetKey().String()] = h @@ -668,3 +674,7 @@ func (nw *reteNetworkImpl) RegisterRtcTransactionHandler(txnHandler model.RtcTra nw.txnHandler = txnHandler nw.txnContext = txnContext } + +func (nw *reteNetworkImpl) getJoinTable(joinTableID int) joinTable { + return nw.allJoinTables[joinTableID] +} \ No newline at end of file diff --git a/rete/retehandle.go b/rete/retehandle.go index 05b734b..be46a6b 100644 --- a/rete/retehandle.go +++ b/rete/retehandle.go @@ -18,9 +18,11 @@ type reteHandle interface { type handleImpl struct { tuple model.Tuple - tablesAndRows map[joinTable]*list.List + //keys are jointable-ids and values are lists of row-ids in the corresponding join table + tablesAndRows map[int]*list.List rtcStatus uint8 + nw Network } func (hdl *handleImpl) setTuple(tuple model.Tuple) { @@ -28,7 +30,7 @@ func (hdl *handleImpl) setTuple(tuple model.Tuple) { } func (hdl *handleImpl) initHandleImpl() { - hdl.tablesAndRows = make(map[joinTable]*list.List) + hdl.tablesAndRows = make(map[int]*list.List) hdl.rtcStatus = 0x00 } @@ -43,12 +45,12 @@ func getOrCreateHandle(ctx context.Context, tuple model.Tuple) reteHandle { func (hdl *handleImpl) addJoinTableRowRef(joinTableRowVar joinTableRow, joinTableVar joinTable) { - rowsForJoinTable := hdl.tablesAndRows[joinTableVar] + rowsForJoinTable := hdl.tablesAndRows[joinTableVar.getID()] if rowsForJoinTable == nil { rowsForJoinTable = list.New() - hdl.tablesAndRows[joinTableVar] = rowsForJoinTable + hdl.tablesAndRows[joinTableVar.getID()] = rowsForJoinTable } - rowsForJoinTable.PushBack(joinTableRowVar) + rowsForJoinTable.PushBack(joinTableRowVar.getID()) } @@ -59,8 +61,8 @@ func (hdl *handleImpl) removeJoinTableRowRefs(changedProps map[string]bool) { emptyJoinTables := list.New() - for joinTable, listOfRows := range hdl.tablesAndRows { - + for joinTableID, rowIDs := range hdl.tablesAndRows { + joinTable := hdl.nw.getJoinTable(joinTableID) toDelete := false if changedProps != nil { rule := joinTable.getRule() @@ -82,25 +84,25 @@ func (hdl *handleImpl) removeJoinTableRowRefs(changedProps map[string]bool) { continue } - for e := listOfRows.Front(); e != nil; e = e.Next() { - row := e.Value.(joinTableRow) - joinTable.removeRow(row) + for e := rowIDs.Front(); e != nil; e = e.Next() { + rowID := e.Value.(int) + joinTable.removeRow(rowID) } if joinTable.len() == 0 { - emptyJoinTables.PushBack(joinTable) + emptyJoinTables.PushBack(joinTable.getID()) } } for e := emptyJoinTables.Front(); e != nil; e = e.Next() { - emptyJoinTable := e.Value.(joinTable) - delete(hdl.tablesAndRows, emptyJoinTable) + joinTableID := e.Value.(int) + delete(hdl.tablesAndRows, joinTableID) } } //Used when a rule is deleted. See Network.RemoveRule func (hdl *handleImpl) removeJoinTable(joinTableVar joinTable) { - _, ok := hdl.tablesAndRows[joinTableVar] + _, ok := hdl.tablesAndRows[joinTableVar.getID()] if ok { - delete(hdl.tablesAndRows, joinTableVar) + delete(hdl.tablesAndRows, joinTableVar.getID()) } } diff --git a/rete/rowiterator.go b/rete/rowiterator.go index 77a7900..6e09dfa 100644 --- a/rete/rowiterator.go +++ b/rete/rowiterator.go @@ -8,7 +8,7 @@ type rowIterator interface { } type rowIteratorImpl struct { - table map[joinTableRow]joinTableRow + table map[int]joinTableRow kList list.List curr *list.Element } @@ -18,7 +18,8 @@ func (ri *rowIteratorImpl) hasNext() bool { } func (ri *rowIteratorImpl) next() joinTableRow { - val := ri.curr.Value.(joinTableRow) + id := ri.curr.Value.(int) + val := ri.table[id] ri.curr = ri.curr.Next() return val } From 59c5dd230e429958f4f9d7be6e25a25133d85845 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Sat, 8 Dec 2018 17:28:35 +0530 Subject: [PATCH 010/125] refactor for jointables and rows --- common/model/tuple.go | 2 +- common/model/tuplekey.go | 1 + common/model/types.go | 7 +-- examples/flogo/trackntrace/functions.go | 4 +- examples/rulesapp/main.go | 2 +- examples/trackntrace/trackntrace_test.go | 14 +++-- rete/context.go | 14 ++--- rete/joinnode.go | 14 ++--- rete/jointable.go | 35 +++++++------ rete/jointablerow.go | 2 +- rete/network.go | 8 +-- rete/node.go | 2 +- rete/retehandle.go | 61 +++++++++------------- rete/retehandletable.go | 65 ++++++++++++++++++++++++ rete/rowiterator.go | 11 ++++ rete/rtcmodified.go | 4 +- rete/rtctxn.go | 2 +- ruleapi/tests/rtctxn_1_test.go | 6 +-- ruleapi/tests/rtctxn_2_test.go | 4 +- ruleapi/tests/rtctxn_3_test.go | 4 +- ruleapi/tests/rtctxn_4_test.go | 4 +- ruleapi/tests/rtctxn_5_test.go | 26 +++++----- ruleapi/tests/rtctxn_6_test.go | 30 +++++------ ruleapi/tests/rtctxn_7_test.go | 8 +-- 24 files changed, 191 insertions(+), 139 deletions(-) create mode 100644 rete/retehandletable.go diff --git a/common/model/tuple.go b/common/model/tuple.go index d7e3bd3..bf0c421 100644 --- a/common/model/tuple.go +++ b/common/model/tuple.go @@ -210,7 +210,7 @@ func (t *tupleImpl) initTupleWithKeyValues(td *TupleDescriptor, values ...interf t.key = tk //populate the tuple key fields with the key values for _, keyProp := range td.GetKeyProps() { - t.tuples [keyProp] = tk.GetValue(keyProp) + t.tuples[keyProp] = tk.GetValue(keyProp) } return err } diff --git a/common/model/tuplekey.go b/common/model/tuplekey.go index 996e2e3..0cc5118 100644 --- a/common/model/tuplekey.go +++ b/common/model/tuplekey.go @@ -2,6 +2,7 @@ package model import ( "fmt" + "github.com/TIBCOSoftware/flogo-lib/core/data" "reflect" "github.com/project-flogo/core/data/coerce" ) diff --git a/common/model/types.go b/common/model/types.go index 2258a93..3633ac7 100644 --- a/common/model/types.go +++ b/common/model/types.go @@ -71,7 +71,6 @@ type RuleSession interface { //RtcTransactionHandler RegisterRtcTransactionHandler(txnHandler RtcTransactionHandler, handlerCtx interface{}) - } //ConditionEvaluator is a function pointer for handling condition evaluations on the server side @@ -92,10 +91,9 @@ type ValueChangeListener interface { type RtcTxn interface { //map of type and map of key/tuple - GetRtcAdded () map[string]map[string]Tuple + GetRtcAdded() map[string]map[string]Tuple GetRtcModified() map[string]map[string]RtcModified GetRtcDeleted() map[string]map[string]Tuple - } type RtcModified interface { @@ -103,5 +101,4 @@ type RtcModified interface { GetModifiedProps() map[string]bool } -type RtcTransactionHandler func (ctx context.Context, rs RuleSession, txn RtcTxn, txnContext interface{}) - +type RtcTransactionHandler func(ctx context.Context, rs RuleSession, txn RtcTxn, txnContext interface{}) diff --git a/examples/flogo/trackntrace/functions.go b/examples/flogo/trackntrace/functions.go index 06de784..fc1a9fc 100644 --- a/examples/flogo/trackntrace/functions.go +++ b/examples/flogo/trackntrace/functions.go @@ -10,8 +10,8 @@ import ( ) var ( - lastEventType string - currentEventType string + lastEventType string + currentEventType string ) //add this sample file to your flogo project diff --git a/examples/rulesapp/main.go b/examples/rulesapp/main.go index 0cfef18..d6ffe24 100644 --- a/examples/rulesapp/main.go +++ b/examples/rulesapp/main.go @@ -4,9 +4,9 @@ import ( "context" "fmt" + "github.com/project-flogo/rules/common" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "github.com/project-flogo/rules/common" ) func main() { diff --git a/examples/trackntrace/trackntrace_test.go b/examples/trackntrace/trackntrace_test.go index b370547..6fd19b3 100644 --- a/examples/trackntrace/trackntrace_test.go +++ b/examples/trackntrace/trackntrace_test.go @@ -1,16 +1,16 @@ package trackntrace import ( + "github.com/project-flogo/rules/common" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "github.com/project-flogo/rules/common" - "testing" - "time" - "io/ioutil" - "strconv" "context" "fmt" + "io/ioutil" + "strconv" + "testing" + "time" ) func TestPkgFlowNormal(t *testing.T) { @@ -197,8 +197,6 @@ func TestSameTupleInstanceAssert(t *testing.T) { rs.Unregister() } - - func createRuleSessionAndRules(t *testing.T) (model.RuleSession, error) { rs, _ := ruleapi.GetOrCreateRuleSession("asession") @@ -392,4 +390,4 @@ func packagedelayedAction(ctx context.Context, rs model.RuleSession, ruleName st fmt.Printf("Package is now delayed id[%s]\n", pkgid) } -type TestKey struct {} \ No newline at end of file +type TestKey struct{} diff --git a/rete/context.go b/rete/context.go index 2b2f4ae..9fbe3de 100644 --- a/rete/context.go +++ b/rete/context.go @@ -4,8 +4,8 @@ import ( "container/list" "context" - "github.com/project-flogo/rules/common/model" "fmt" + "github.com/project-flogo/rules/common/model" ) var reteCTXKEY = model.RetecontextKeyType{} @@ -31,7 +31,6 @@ type reteCtx interface { resetModified() printRtcChangeList() - } //store any context, may not know all keys upfront @@ -52,7 +51,6 @@ type reteCtxImpl struct { //modified tuples in the current RTC rtcModifyMap map[string]model.RtcModified - } func newReteCtxImpl(network Network, rs model.RuleSession) reteCtx { @@ -107,15 +105,15 @@ func (rctx *reteCtxImpl) getRtcDeleted() map[string]model.Tuple { return rctx.deleteMap } -func (rctx *reteCtxImpl) addToRtcAdded (tuple model.Tuple) { +func (rctx *reteCtxImpl) addToRtcAdded(tuple model.Tuple) { rctx.addMap[tuple.GetKey().String()] = tuple } -func (rctx *reteCtxImpl) addToRtcModified (tuple model.Tuple) { +func (rctx *reteCtxImpl) addToRtcModified(tuple model.Tuple) { rctx.addMap[tuple.GetKey().String()] = tuple } -func (rctx *reteCtxImpl) addToRtcDeleted (tuple model.Tuple) { +func (rctx *reteCtxImpl) addToRtcDeleted(tuple model.Tuple) { rctx.deleteMap[tuple.GetKey().String()] = tuple } @@ -125,8 +123,6 @@ func (rctx *reteCtxImpl) addRuleModifiedToOpsList() { } } - - func (rctx *reteCtxImpl) normalize() { //remove from modify map, those in add map @@ -139,7 +135,7 @@ func (rctx *reteCtxImpl) normalize() { } } -func (rctx *reteCtxImpl) copyRuleModifiedToRtcModified () { +func (rctx *reteCtxImpl) copyRuleModifiedToRtcModified() { for k, v := range rctx.modifyMap { rctx.rtcModifyMap[k] = v } diff --git a/rete/joinnode.go b/rete/joinnode.go index 1f5bc18..2b4873d 100644 --- a/rete/joinnode.go +++ b/rete/joinnode.go @@ -166,10 +166,10 @@ func (jn *joinNodeImpl) assertFromRight(ctx context.Context, handles []reteHandl //TODO: other stuff. right now focus on tuple table jn.joinRightObjects(handles, joinedHandles) - tupleTableRow := newJoinTableRow(handles, jn.nw.incrementAndGetId()) - jn.rightTable.addRow(tupleTableRow) + //tupleTableRow := newJoinTableRow(handles, jn.nw.incrementAndGetId()) + jn.rightTable.addRow(handles) //TODO: rete listeners etc. - rIterator:= jn.leftTable.iterator() + rIterator := jn.leftTable.getRowIterator() for rIterator.hasNext() { tupleTableRowLeft := rIterator.next() success := jn.joinLeftObjects(tupleTableRowLeft.getHandles(), joinedHandles) @@ -216,13 +216,13 @@ func (jn *joinNodeImpl) joinRightObjects(rightHandles []reteHandle, joinedHandle func (jn *joinNodeImpl) assertFromLeft(ctx context.Context, handles []reteHandle, joinedHandles []reteHandle) { jn.joinLeftObjects(handles, joinedHandles) //TODO: other stuff. right now focus on tuple table - tupleTableRow := newJoinTableRow(handles, jn.nw.incrementAndGetId()) - jn.leftTable.addRow(tupleTableRow) + //tupleTableRow := newJoinTableRow(handles, jn.nw.incrementAndGetId()) + jn.leftTable.addRow(handles) //TODO: rete listeners etc. - rIterator := jn.rightTable.iterator() + rIterator := jn.rightTable.getRowIterator() for rIterator.hasNext() { tupleTableRowRight := rIterator.next() - success := jn.joinRightObjects(tupleTableRowRight.getHandles(), joinedHandles) + success := jn.joinRightObjects(tupleTableRowRight.getHandles(), joinedHandles) if !success { //TODO: handle it continue diff --git a/rete/jointable.go b/rete/jointable.go index 5de2d08..69184ce 100644 --- a/rete/jointable.go +++ b/rete/jointable.go @@ -2,17 +2,18 @@ package rete import ( "github.com/project-flogo/rules/common/model" - "container/list" ) type joinTable interface { - addRow(row joinTableRow) //list of Tuples getID() int - len() int - //getMap() map[joinTableRow]joinTableRow - removeRow(rowID int) getRule() model.Rule - iterator() rowIterator + + addRow(handles []reteHandle) + removeRow(rowID int) + getRowIterator() rowIterator + + getRowCount() int + //getMap() map[joinTableRow]joinTableRow } type joinTableImpl struct { @@ -20,6 +21,7 @@ type joinTableImpl struct { table map[int]joinTableRow idr []model.TupleType rule model.Rule + nw Network } func newJoinTable(nw Network, rule model.Rule, identifiers []model.TupleType) joinTable { @@ -37,13 +39,17 @@ func (jt *joinTableImpl) initJoinTableImpl(nw Network, rule model.Rule, identifi jt.idr = identifiers jt.table = map[int]joinTableRow{} jt.rule = rule + jt.nw = nw } func (jt *joinTableImpl) getID() int { return jt.id } -func (jt *joinTableImpl) addRow(row joinTableRow) { +func (jt *joinTableImpl) addRow(handles []reteHandle) { + + row := newJoinTableRow(handles, jt.nw.incrementAndGetId()) + jt.table[row.getID()] = row for i := 0; i < len(row.getHandles()); i++ { handle := row.getHandles()[i] @@ -55,7 +61,7 @@ func (jt *joinTableImpl) removeRow(rowID int) { delete(jt.table, rowID) } -func (jt *joinTableImpl) len() int { +func (jt *joinTableImpl) getRowCount() int { return len(jt.table) } @@ -67,13 +73,6 @@ func (jt *joinTableImpl) getRule() model.Rule { return jt.rule } -func (jt *joinTableImpl) iterator() rowIterator { - ri := rowIteratorImpl{} - ri.table = jt.table - ri.kList = list.List{} - for k, _:= range jt.table { - ri.kList.PushBack(k) - } - ri.curr = ri.kList.Front() - return &ri -} \ No newline at end of file +func (jt *joinTableImpl) getRowIterator() rowIterator { + return newRowIterator(jt.table) +} diff --git a/rete/jointablerow.go b/rete/jointablerow.go index e086360..a24d035 100644 --- a/rete/jointablerow.go +++ b/rete/jointablerow.go @@ -6,7 +6,7 @@ type joinTableRow interface { } type joinTableRowImpl struct { - id int + id int handles []reteHandle } diff --git a/rete/network.go b/rete/network.go index e6bb129..cf435d5 100644 --- a/rete/network.go +++ b/rete/network.go @@ -202,11 +202,11 @@ func removeRefsFromReteHandles(joinTableVar joinTable) { if joinTableVar == nil { return } - rIterator := joinTableVar.iterator() + rIterator := joinTableVar.getRowIterator() for rIterator.hasNext() { tableRow := rIterator.next() for _, handle := range tableRow.getHandles() { - handle.removeJoinTable(joinTableVar) + handle.removeJoinTable(joinTableVar.getID()) } } } @@ -649,7 +649,7 @@ func (nw *reteNetworkImpl) assertInternal(ctx context.Context, tuple model.Tuple func (nw *reteNetworkImpl) getOrCreateHandle(ctx context.Context, tuple model.Tuple) reteHandle { h := nw.allHandles[tuple.GetKey().String()] if h == nil { - h1 := handleImpl{} + h1 := reteHandleImpl{} h1.initHandleImpl() h1.nw = nw h1.setTuple(tuple) @@ -677,4 +677,4 @@ func (nw *reteNetworkImpl) RegisterRtcTransactionHandler(txnHandler model.RtcTra func (nw *reteNetworkImpl) getJoinTable(joinTableID int) joinTable { return nw.allJoinTables[joinTableID] -} \ No newline at end of file +} diff --git a/rete/node.go b/rete/node.go index 0b0d7b9..e0f42db 100644 --- a/rete/node.go +++ b/rete/node.go @@ -23,7 +23,7 @@ type nodeImpl struct { nodeLinkVar nodeLink id int rule model.Rule - nw Network + nw Network } //NewNode ... returns a new node diff --git a/rete/retehandle.go b/rete/retehandle.go index be46a6b..fe3cd96 100644 --- a/rete/retehandle.go +++ b/rete/retehandle.go @@ -1,7 +1,6 @@ package rete import ( - "container/list" "context" "github.com/project-flogo/rules/common/model" @@ -13,28 +12,25 @@ type reteHandle interface { getTuple() model.Tuple addJoinTableRowRef(joinTableRowVar joinTableRow, joinTableVar joinTable) removeJoinTableRowRefs(changedProps map[string]bool) - removeJoinTable(joinTableVar joinTable) + removeJoinTable(joinTableID int) } -type handleImpl struct { - tuple model.Tuple - //keys are jointable-ids and values are lists of row-ids in the corresponding join table - tablesAndRows map[int]*list.List - +type reteHandleImpl struct { + tuple model.Tuple rtcStatus uint8 - nw Network + nw Network + rhRef reteHandleRefs } -func (hdl *handleImpl) setTuple(tuple model.Tuple) { +func (hdl *reteHandleImpl) setTuple(tuple model.Tuple) { hdl.tuple = tuple } -func (hdl *handleImpl) initHandleImpl() { - hdl.tablesAndRows = make(map[int]*list.List) - hdl.rtcStatus = 0x00 +func (hdl *reteHandleImpl) initHandleImpl() { + hdl.rhRef = newReteHandleRefsImpl() } -func (hdl *handleImpl) getTuple() model.Tuple { +func (hdl *reteHandleImpl) getTuple() model.Tuple { return hdl.tuple } @@ -43,25 +39,21 @@ func getOrCreateHandle(ctx context.Context, tuple model.Tuple) reteHandle { return reteCtxVar.getNetwork().getOrCreateHandle(ctx, tuple) } -func (hdl *handleImpl) addJoinTableRowRef(joinTableRowVar joinTableRow, joinTableVar joinTable) { - - rowsForJoinTable := hdl.tablesAndRows[joinTableVar.getID()] - if rowsForJoinTable == nil { - rowsForJoinTable = list.New() - hdl.tablesAndRows[joinTableVar.getID()] = rowsForJoinTable - } - rowsForJoinTable.PushBack(joinTableRowVar.getID()) - +func (hdl *reteHandleImpl) addJoinTableRowRef(joinTableRowVar joinTableRow, joinTableVar joinTable) { + hdl.rhRef.addEntry(joinTableVar.getID(), joinTableRowVar.getID()) } -func (hdl *handleImpl) removeJoinTableRowRefs(changedProps map[string]bool) { +func (hdl *reteHandleImpl) removeJoinTableRowRefs(changedProps map[string]bool) { tuple := hdl.tuple alias := tuple.GetTupleType() - emptyJoinTables := list.New() + //emptyJoinTables := list.New() + + hdlTblIter := hdl.newHdlTblIterator() - for joinTableID, rowIDs := range hdl.tablesAndRows { + for hdlTblIter.hasNext() { + joinTableID, rowIDs := hdlTblIter.next() joinTable := hdl.nw.getJoinTable(joinTableID) toDelete := false if changedProps != nil { @@ -88,21 +80,16 @@ func (hdl *handleImpl) removeJoinTableRowRefs(changedProps map[string]bool) { rowID := e.Value.(int) joinTable.removeRow(rowID) } - if joinTable.len() == 0 { - emptyJoinTables.PushBack(joinTable.getID()) - } - } - for e := emptyJoinTables.Front(); e != nil; e = e.Next() { - joinTableID := e.Value.(int) - delete(hdl.tablesAndRows, joinTableID) + hdl.rhRef.removeEntry(joinTableID) } } //Used when a rule is deleted. See Network.RemoveRule -func (hdl *handleImpl) removeJoinTable(joinTableVar joinTable) { - _, ok := hdl.tablesAndRows[joinTableVar.getID()] - if ok { - delete(hdl.tablesAndRows, joinTableVar.getID()) - } +func (hdl *reteHandleImpl) removeJoinTable(joinTableID int) { + hdl.rhRef.removeEntry(joinTableID) } + +//func (hdl *reteHandleImpl) deleteRefsToJoinTables (jointTableID int) { +// delete (hdl.tablesAndRows, jointTableID) +//} diff --git a/rete/retehandletable.go b/rete/retehandletable.go new file mode 100644 index 0000000..41fe562 --- /dev/null +++ b/rete/retehandletable.go @@ -0,0 +1,65 @@ +package rete + +import "container/list" + +type reteHandleRefs interface { + addEntry(jointTableID int, rowID int) + removeEntry(jointTableID int) +} + +type reteHandleRefsImpl struct { + //keys are jointable-ids and values are lists of row-ids in the corresponding join table + tablesAndRows map[int]*list.List +} + +func newReteHandleRefsImpl() reteHandleRefs { + hdlJt := reteHandleRefsImpl{} + hdlJt.tablesAndRows = make(map[int]*list.List) + return &hdlJt +} + +func (h *reteHandleRefsImpl) addEntry(jointTableID int, rowID int) { + rowsForJoinTable := h.tablesAndRows[jointTableID] + if rowsForJoinTable == nil { + rowsForJoinTable = list.New() + h.tablesAndRows[jointTableID] = rowsForJoinTable + } + rowsForJoinTable.PushBack(rowID) +} + +func (h *reteHandleRefsImpl) removeEntry(jointTableID int) { + delete(h.tablesAndRows, jointTableID) +} + +type hdlTblIterator interface { + hasNext() bool + next() (int, *list.List) +} + +type hdlTblIteratorImpl struct { + hdlJtImpl *reteHandleRefsImpl + kList list.List + curr *list.Element +} + +func (ri *hdlTblIteratorImpl) hasNext() bool { + return ri.curr != nil +} + +func (ri *hdlTblIteratorImpl) next() (int, *list.List) { + id := ri.curr.Value.(int) + lst := ri.hdlJtImpl.tablesAndRows[id] + ri.curr = ri.curr.Next() + return id, lst +} + +func (hdl *reteHandleImpl) newHdlTblIterator() hdlTblIterator { + ri := hdlTblIteratorImpl{} + ri.hdlJtImpl = hdl.rhRef.(*reteHandleRefsImpl) + ri.kList = list.List{} + for k, _ := range ri.hdlJtImpl.tablesAndRows { + ri.kList.PushBack(k) + } + ri.curr = ri.kList.Front() + return &ri +} diff --git a/rete/rowiterator.go b/rete/rowiterator.go index 6e09dfa..905afed 100644 --- a/rete/rowiterator.go +++ b/rete/rowiterator.go @@ -13,6 +13,17 @@ type rowIteratorImpl struct { curr *list.Element } +func newRowIterator(jTable map[int]joinTableRow) rowIterator { + ri := rowIteratorImpl{} + ri.table = jTable + ri.kList = list.List{} + for k, _ := range jTable { + ri.kList.PushBack(k) + } + ri.curr = ri.kList.Front() + return &ri +} + func (ri *rowIteratorImpl) hasNext() bool { return ri.curr != nil } diff --git a/rete/rtcmodified.go b/rete/rtcmodified.go index 3282dd1..8c9efd5 100644 --- a/rete/rtcmodified.go +++ b/rete/rtcmodified.go @@ -7,7 +7,7 @@ type rtcModifiedImpl struct { props map[string]bool } -func NewRtcModified (tuple model.Tuple) model.RtcModified { +func NewRtcModified(tuple model.Tuple) model.RtcModified { rm := rtcModifiedImpl{} rm.tuple = tuple rm.props = make(map[string]bool) @@ -24,4 +24,4 @@ func (rm *rtcModifiedImpl) GetModifiedProps() map[string]bool { func (rm *rtcModifiedImpl) addProp(prop string) { rm.props[prop] = true -} \ No newline at end of file +} diff --git a/rete/rtctxn.go b/rete/rtctxn.go index 4e0fcb6..f0756ba 100644 --- a/rete/rtctxn.go +++ b/rete/rtctxn.go @@ -50,7 +50,7 @@ func (tx *rtcTxnImpl) groupModifiedByType(modifiedTxn map[string]model.RtcModifi } } -func (tx *rtcTxnImpl) groupAddedByType(addedTxn map[string]model.Tuple) { +func (tx *rtcTxnImpl) groupAddedByType(addedTxn map[string]model.Tuple) { for key, tuple := range addedTxn { tdType := tuple.GetTupleDescriptor().Name tupleMap, found := tx.added[tdType] diff --git a/ruleapi/tests/rtctxn_1_test.go b/ruleapi/tests/rtctxn_1_test.go index 28ac038..ab77ada 100644 --- a/ruleapi/tests/rtctxn_1_test.go +++ b/ruleapi/tests/rtctxn_1_test.go @@ -29,8 +29,6 @@ func Test_T1(t *testing.T) { } - - func t1Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { t := handlerCtx.(*testing.T) @@ -38,7 +36,7 @@ func t1Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lA := len(rtxn.GetRtcAdded()) if lA != 1 { t.Errorf("RtcAdded: Expected [%d], got [%d]\n", 1, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } lM := len(rtxn.GetRtcModified()) if lM != 0 { @@ -48,6 +46,6 @@ func t1Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 0 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 0, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } diff --git a/ruleapi/tests/rtctxn_2_test.go b/ruleapi/tests/rtctxn_2_test.go index 1eb4415..4813263 100644 --- a/ruleapi/tests/rtctxn_2_test.go +++ b/ruleapi/tests/rtctxn_2_test.go @@ -35,7 +35,7 @@ func t2Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lA := len(rtxn.GetRtcAdded()) if lA != 0 { t.Errorf("RtcAdded: Expected [%d], got [%d]\n", 0, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } lM := len(rtxn.GetRtcModified()) if lM != 0 { @@ -46,6 +46,6 @@ func t2Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 0 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 0, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } diff --git a/ruleapi/tests/rtctxn_3_test.go b/ruleapi/tests/rtctxn_3_test.go index 2281e1f..a2022a1 100644 --- a/ruleapi/tests/rtctxn_3_test.go +++ b/ruleapi/tests/rtctxn_3_test.go @@ -39,7 +39,7 @@ func t3Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lA := len(rtxn.GetRtcAdded()) if lA != 1 { t.Errorf("RtcAdded: Types expected [%d], got [%d]\n", 1, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } else { //ok @@ -60,6 +60,6 @@ func t3Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 0 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 0, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } diff --git a/ruleapi/tests/rtctxn_4_test.go b/ruleapi/tests/rtctxn_4_test.go index dbbb303..642782d 100644 --- a/ruleapi/tests/rtctxn_4_test.go +++ b/ruleapi/tests/rtctxn_4_test.go @@ -40,7 +40,7 @@ func t4Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lA := len(rtxn.GetRtcAdded()) if lA != 1 { t.Errorf("RtcAdded: Types expected [%d], got [%d]\n", 1, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } else { //ok @@ -59,6 +59,6 @@ func t4Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 0 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 0, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } diff --git a/ruleapi/tests/rtctxn_5_test.go b/ruleapi/tests/rtctxn_5_test.go index 5c65fa0..2515d9a 100644 --- a/ruleapi/tests/rtctxn_5_test.go +++ b/ruleapi/tests/rtctxn_5_test.go @@ -40,17 +40,17 @@ func r5_action(ctx context.Context, rs model.RuleSession, ruleName string, tuple t1 := tuples[model.TupleType("t1")].(model.MutableTuple) //t1.SetString(ctx, "p3", "v3") id, _ := t1.GetString("id") - if id == "t11" { - tk, _:= model.NewTupleKeyWithKeyValues("t1", "t10") - t10 := rs.GetAssertedTuple (tk).(model.MutableTuple) + if id == "t11" { + tk, _ := model.NewTupleKeyWithKeyValues("t1", "t10") + t10 := rs.GetAssertedTuple(tk).(model.MutableTuple) if t10 != nil { t10.SetString(ctx, "p3", "v3") t10.SetDouble(ctx, "p2", 11.11) } - } else if (id == "t13") { + } else if id == "t13" { //delete t11 - tk, _:= model.NewTupleKeyWithKeyValues("t1", "t11") - t11 := rs.GetAssertedTuple (tk).(model.MutableTuple) + tk, _ := model.NewTupleKeyWithKeyValues("t1", "t11") + t11 := rs.GetAssertedTuple(tk).(model.MutableTuple) if t11 != nil { rs.Delete(ctx, t11) } @@ -66,7 +66,7 @@ func t5Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lA := len(rtxn.GetRtcAdded()) if lA != 1 { t.Errorf("RtcAdded: Types expected [%d], got [%d]\n", 1, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } lM := len(rtxn.GetRtcModified()) if lM != 0 { @@ -76,13 +76,13 @@ func t5Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 0 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 0, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } else if txnCtx.TxnCnt == 2 { lA := len(rtxn.GetRtcAdded()) if lA != 1 { t.Errorf("RtcAdded: Types expected [%d], got [%d]\n", 1, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } lM := len(rtxn.GetRtcModified()) if lM != 1 { @@ -92,13 +92,13 @@ func t5Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 0 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 0, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } else if txnCtx.TxnCnt == 3 { lA := len(rtxn.GetRtcAdded()) if lA != 1 { t.Errorf("RtcAdded: Types expected [%d], got [%d]\n", 1, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } lM := len(rtxn.GetRtcModified()) if lM != 0 { @@ -108,7 +108,7 @@ func t5Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 1 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 1, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } -} \ No newline at end of file +} diff --git a/ruleapi/tests/rtctxn_6_test.go b/ruleapi/tests/rtctxn_6_test.go index 3fc6ad4..fe6e3c9 100644 --- a/ruleapi/tests/rtctxn_6_test.go +++ b/ruleapi/tests/rtctxn_6_test.go @@ -38,17 +38,17 @@ func Test_T6(t *testing.T) { func r6_action(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { t1 := tuples[model.TupleType("t1")].(model.MutableTuple) id, _ := t1.GetString("id") - if id == "t11" { - tk, _:= model.NewTupleKeyWithKeyValues("t1", "t10") - t10 := rs.GetAssertedTuple (tk).(model.MutableTuple) + if id == "t11" { + tk, _ := model.NewTupleKeyWithKeyValues("t1", "t10") + t10 := rs.GetAssertedTuple(tk).(model.MutableTuple) if t10 != nil { t10.SetString(ctx, "p3", "v3") t10.SetDouble(ctx, "p2", 11.11) } - } else if (id == "t13") { + } else if id == "t13" { //delete t11 - tk, _:= model.NewTupleKeyWithKeyValues("t1", "t11") - t11 := rs.GetAssertedTuple (tk).(model.MutableTuple) + tk, _ := model.NewTupleKeyWithKeyValues("t1", "t11") + t11 := rs.GetAssertedTuple(tk).(model.MutableTuple) if t11 != nil { rs.Delete(ctx, t11) } @@ -70,7 +70,7 @@ func t6Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lA := len(rtxn.GetRtcAdded()) if lA != 1 { t.Errorf("RtcAdded: Expected [%d], got [%d]\n", 1, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } lM := len(rtxn.GetRtcModified()) if lM != 0 { @@ -80,13 +80,13 @@ func t6Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 0 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 0, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } else if txnCtx.TxnCnt == 2 { lA := len(rtxn.GetRtcAdded()) if lA != 1 { t.Errorf("RtcAdded: Types expected [%d], got [%d]\n", 1, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } lM := len(rtxn.GetRtcModified()) if lM != 1 { @@ -96,18 +96,18 @@ func t6Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 0 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 0, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } else if txnCtx.TxnCnt == 3 { lA := len(rtxn.GetRtcAdded()) if lA != 1 { t.Errorf("RtcAdded: Types expected [%d], got [%d]\n", 1, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } else { - added, _ := rtxn.GetRtcAdded() ["t1"] + added, _ := rtxn.GetRtcAdded()["t1"] if len(added) != 2 { t.Errorf("RtcAdded: Tuples expected [%d], got [%d]\n", 2, len(added)) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } } lM := len(rtxn.GetRtcModified()) @@ -118,7 +118,7 @@ func t6Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 1 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 1, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } -} \ No newline at end of file +} diff --git a/ruleapi/tests/rtctxn_7_test.go b/ruleapi/tests/rtctxn_7_test.go index 6eb03ca..6fc8305 100644 --- a/ruleapi/tests/rtctxn_7_test.go +++ b/ruleapi/tests/rtctxn_7_test.go @@ -33,7 +33,7 @@ func Test_T7(t *testing.T) { func r7_action(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { t1 := tuples[model.TupleType("t1")].(model.MutableTuple) id, _ := t1.GetString("id") - if id == "t10" { + if id == "t10" { rs.Delete(ctx, t1) } } @@ -47,7 +47,7 @@ func t7Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lA := len(rtxn.GetRtcAdded()) if lA != 1 { t.Errorf("RtcAdded: Expected [%d], got [%d]\n", 1, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } lM := len(rtxn.GetRtcModified()) if lM != 0 { @@ -57,7 +57,7 @@ func t7Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 1 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 1, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } -} \ No newline at end of file +} From 1d111091ac1eb7a4d53cde43beb504b46d5e6e17 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Sun, 9 Dec 2018 13:38:15 +0530 Subject: [PATCH 011/125] refactor for jointables and rows --- rete/jointable.go | 20 ++++++++------ ...andletable.go => jointablerefsinhandle.go} | 16 ++++++------ rete/retehandle.go | 26 ++++++++++--------- 3 files changed, 34 insertions(+), 28 deletions(-) rename rete/{retehandletable.go => jointablerefsinhandle.go} (75%) diff --git a/rete/jointable.go b/rete/jointable.go index 69184ce..a1918b5 100644 --- a/rete/jointable.go +++ b/rete/jointable.go @@ -8,8 +8,9 @@ type joinTable interface { getID() int getRule() model.Rule - addRow(handles []reteHandle) - removeRow(rowID int) + addRow(handles []reteHandle) joinTableRow + removeRow(rowID int) joinTableRow + getRow(rowID int) joinTableRow getRowIterator() rowIterator getRowCount() int @@ -46,7 +47,7 @@ func (jt *joinTableImpl) getID() int { return jt.id } -func (jt *joinTableImpl) addRow(handles []reteHandle) { +func (jt *joinTableImpl) addRow(handles []reteHandle) joinTableRow { row := newJoinTableRow(handles, jt.nw.incrementAndGetId()) @@ -55,20 +56,19 @@ func (jt *joinTableImpl) addRow(handles []reteHandle) { handle := row.getHandles()[i] handle.addJoinTableRowRef(row, jt) } + return row } -func (jt *joinTableImpl) removeRow(rowID int) { +func (jt *joinTableImpl) removeRow(rowID int) joinTableRow { + row := jt.table[rowID] delete(jt.table, rowID) + return row } func (jt *joinTableImpl) getRowCount() int { return len(jt.table) } -//func (jt *joinTableImpl) getMap() map[joinTableRow]joinTableRow { -// return jt.table -//} - func (jt *joinTableImpl) getRule() model.Rule { return jt.rule } @@ -76,3 +76,7 @@ func (jt *joinTableImpl) getRule() model.Rule { func (jt *joinTableImpl) getRowIterator() rowIterator { return newRowIterator(jt.table) } + +func (jt *joinTableImpl) getRow(rowID int) joinTableRow { + return jt.table[rowID] +} diff --git a/rete/retehandletable.go b/rete/jointablerefsinhandle.go similarity index 75% rename from rete/retehandletable.go rename to rete/jointablerefsinhandle.go index 41fe562..7a63f1d 100644 --- a/rete/retehandletable.go +++ b/rete/jointablerefsinhandle.go @@ -2,23 +2,23 @@ package rete import "container/list" -type reteHandleRefs interface { +type joinTableRefsInHdl interface { addEntry(jointTableID int, rowID int) removeEntry(jointTableID int) } -type reteHandleRefsImpl struct { +type joinTableRefsInHdlImpl struct { //keys are jointable-ids and values are lists of row-ids in the corresponding join table tablesAndRows map[int]*list.List } -func newReteHandleRefsImpl() reteHandleRefs { - hdlJt := reteHandleRefsImpl{} +func newJoinTableRefsInHdlImpl() joinTableRefsInHdl { + hdlJt := joinTableRefsInHdlImpl{} hdlJt.tablesAndRows = make(map[int]*list.List) return &hdlJt } -func (h *reteHandleRefsImpl) addEntry(jointTableID int, rowID int) { +func (h *joinTableRefsInHdlImpl) addEntry(jointTableID int, rowID int) { rowsForJoinTable := h.tablesAndRows[jointTableID] if rowsForJoinTable == nil { rowsForJoinTable = list.New() @@ -27,7 +27,7 @@ func (h *reteHandleRefsImpl) addEntry(jointTableID int, rowID int) { rowsForJoinTable.PushBack(rowID) } -func (h *reteHandleRefsImpl) removeEntry(jointTableID int) { +func (h *joinTableRefsInHdlImpl) removeEntry(jointTableID int) { delete(h.tablesAndRows, jointTableID) } @@ -37,7 +37,7 @@ type hdlTblIterator interface { } type hdlTblIteratorImpl struct { - hdlJtImpl *reteHandleRefsImpl + hdlJtImpl *joinTableRefsInHdlImpl kList list.List curr *list.Element } @@ -55,7 +55,7 @@ func (ri *hdlTblIteratorImpl) next() (int, *list.List) { func (hdl *reteHandleImpl) newHdlTblIterator() hdlTblIterator { ri := hdlTblIteratorImpl{} - ri.hdlJtImpl = hdl.rhRef.(*reteHandleRefsImpl) + ri.hdlJtImpl = hdl.jtRefs.(*joinTableRefsInHdlImpl) ri.kList = list.List{} for k, _ := range ri.hdlJtImpl.tablesAndRows { ri.kList.PushBack(k) diff --git a/rete/retehandle.go b/rete/retehandle.go index fe3cd96..da369ec 100644 --- a/rete/retehandle.go +++ b/rete/retehandle.go @@ -17,9 +17,8 @@ type reteHandle interface { type reteHandleImpl struct { tuple model.Tuple - rtcStatus uint8 nw Network - rhRef reteHandleRefs + jtRefs joinTableRefsInHdl } func (hdl *reteHandleImpl) setTuple(tuple model.Tuple) { @@ -27,7 +26,7 @@ func (hdl *reteHandleImpl) setTuple(tuple model.Tuple) { } func (hdl *reteHandleImpl) initHandleImpl() { - hdl.rhRef = newReteHandleRefsImpl() + hdl.jtRefs = newJoinTableRefsInHdlImpl() } func (hdl *reteHandleImpl) getTuple() model.Tuple { @@ -40,7 +39,7 @@ func getOrCreateHandle(ctx context.Context, tuple model.Tuple) reteHandle { } func (hdl *reteHandleImpl) addJoinTableRowRef(joinTableRowVar joinTableRow, joinTableVar joinTable) { - hdl.rhRef.addEntry(joinTableVar.getID(), joinTableRowVar.getID()) + hdl.jtRefs.addEntry(joinTableVar.getID(), joinTableRowVar.getID()) } func (hdl *reteHandleImpl) removeJoinTableRowRefs(changedProps map[string]bool) { @@ -75,21 +74,24 @@ func (hdl *reteHandleImpl) removeJoinTableRowRefs(changedProps map[string]bool) if !toDelete { continue } - + //Remove rows from corresponding join tables for e := rowIDs.Front(); e != nil; e = e.Next() { rowID := e.Value.(int) - joinTable.removeRow(rowID) + row := joinTable.removeRow(rowID) + + //Remove other refs recursively. + for _, otherHdl := range row.getHandles() { + otherHdl.removeJoinTableRowRefs(nil) + } + } - hdl.rhRef.removeEntry(joinTableID) + //Remove the reference to the table itself + hdl.jtRefs.removeEntry(joinTableID) } } //Used when a rule is deleted. See Network.RemoveRule func (hdl *reteHandleImpl) removeJoinTable(joinTableID int) { - hdl.rhRef.removeEntry(joinTableID) + hdl.jtRefs.removeEntry(joinTableID) } - -//func (hdl *reteHandleImpl) deleteRefsToJoinTables (jointTableID int) { -// delete (hdl.tablesAndRows, jointTableID) -//} From 69c5fc8814cca35dc78ebd589a4386b924904321 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Sun, 9 Dec 2018 14:20:38 +0530 Subject: [PATCH 012/125] refactor for jointables and rows --- rete/network.go | 7 +------ rete/retehandle.go | 24 ++++++++++++++++++++---- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/rete/network.go b/rete/network.go index cf435d5..f3654de 100644 --- a/rete/network.go +++ b/rete/network.go @@ -649,11 +649,7 @@ func (nw *reteNetworkImpl) assertInternal(ctx context.Context, tuple model.Tuple func (nw *reteNetworkImpl) getOrCreateHandle(ctx context.Context, tuple model.Tuple) reteHandle { h := nw.allHandles[tuple.GetKey().String()] if h == nil { - h1 := reteHandleImpl{} - h1.initHandleImpl() - h1.nw = nw - h1.setTuple(tuple) - h = &h1 + h = newReteHandleImpl(nw, tuple) nw.allHandles[tuple.GetKey().String()] = h } return h @@ -661,7 +657,6 @@ func (nw *reteNetworkImpl) getOrCreateHandle(ctx context.Context, tuple model.Tu func (nw *reteNetworkImpl) getHandle(tuple model.Tuple) reteHandle { h := nw.allHandles[tuple.GetKey().String()] - return h } diff --git a/rete/retehandle.go b/rete/retehandle.go index da369ec..db1dd27 100644 --- a/rete/retehandle.go +++ b/rete/retehandle.go @@ -13,19 +13,31 @@ type reteHandle interface { addJoinTableRowRef(joinTableRowVar joinTableRow, joinTableVar joinTable) removeJoinTableRowRefs(changedProps map[string]bool) removeJoinTable(joinTableID int) + getTupleKey() model.TupleKey } type reteHandleImpl struct { - tuple model.Tuple - nw Network - jtRefs joinTableRefsInHdl + //this is "transient" + tuple model.Tuple + //this is the identity of the handle. + tupleKey model.TupleKey + nw Network + jtRefs joinTableRefsInHdl +} + +func newReteHandleImpl(nw *reteNetworkImpl, tuple model.Tuple) reteHandle { + h1 := reteHandleImpl{} + h1.initHandleImpl(nw, tuple) + return &h1 } func (hdl *reteHandleImpl) setTuple(tuple model.Tuple) { hdl.tuple = tuple } -func (hdl *reteHandleImpl) initHandleImpl() { +func (hdl *reteHandleImpl) initHandleImpl(nw *reteNetworkImpl, tuple model.Tuple) { + hdl.nw = nw + hdl.setTuple(tuple) hdl.jtRefs = newJoinTableRefsInHdlImpl() } @@ -33,6 +45,10 @@ func (hdl *reteHandleImpl) getTuple() model.Tuple { return hdl.tuple } +func (hdl *reteHandleImpl) getTupleKey() model.TupleKey { + return hdl.tupleKey +} + func getOrCreateHandle(ctx context.Context, tuple model.Tuple) reteHandle { reteCtxVar := getReteCtx(ctx) return reteCtxVar.getNetwork().getOrCreateHandle(ctx, tuple) From dca233e23abc14b31b03d867bc028c70c8ad18c2 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Mon, 10 Dec 2018 11:52:27 +0530 Subject: [PATCH 013/125] refactor for jointables and rows --- common/model/types.go | 15 +++++++++++++++ rete/classnodelink.go | 2 +- rete/filternode.go | 2 +- rete/idelem.go | 18 ++++++++++++++++++ rete/jointable.go | 24 ++++++++++-------------- rete/jointablerow.go | 16 ++++++---------- rete/network.go | 1 + rete/node.go | 22 ++++------------------ rete/nodelink.go | 22 ++++++++++------------ rete/retehandle.go | 29 ++++++++++++++++------------- rete/rulenode.go | 2 +- 11 files changed, 83 insertions(+), 70 deletions(-) create mode 100644 rete/idelem.go diff --git a/common/model/types.go b/common/model/types.go index 3633ac7..24aa8cb 100644 --- a/common/model/types.go +++ b/common/model/types.go @@ -102,3 +102,18 @@ type RtcModified interface { } type RtcTransactionHandler func(ctx context.Context, rs RuleSession, txn RtcTxn, txnContext interface{}) + +type IntegerID interface { + getID() int + setID(ID int) +} +type IntegerIDImpl struct { + id int +} + +func (i *IntegerIDImpl) getID() int { + return i.id +} +func (i *IntegerIDImpl) setID(ID int) { + i.id = ID +} diff --git a/rete/classnodelink.go b/rete/classnodelink.go index 15c2d16..3a1607c 100644 --- a/rete/classnodelink.go +++ b/rete/classnodelink.go @@ -24,7 +24,7 @@ func newClassNodeLink(nw Network, classNodeVar classNode, child node, rule model } func (cnl *classNodeLinkImpl) initClassNodeLinkImpl(nw Network, classNodeVar classNode, child node, rule model.Rule, identifierVar model.TupleType) { - initClassNodeLink(nw, &cnl.nodeLinkImpl, child) + cnl.initClassNodeLink(nw, child) cnl.classNodeVar = classNodeVar cnl.rule = rule cnl.identifierVar = identifierVar diff --git a/rete/filternode.go b/rete/filternode.go index 0781935..1d424db 100644 --- a/rete/filternode.go +++ b/rete/filternode.go @@ -70,7 +70,7 @@ func (fn *filterNodeImpl) String() string { linkTo += "r" + strconv.Itoa(fn.nodeLinkVar.getChild().getID()) } - return "\t[FilterNode id(" + strconv.Itoa(fn.nodeImpl.id) + ") link(" + linkTo + "):\n" + + return "\t[FilterNode id(" + strconv.Itoa(fn.nodeImpl.getID()) + ") link(" + linkTo + "):\n" + "\t\tIdentifier = " + model.IdentifiersToString(fn.identifiers) + " ;\n" + "\t\tCondition Identifiers = " + cond + ";\n" + "\t\tCondition = " + fn.conditionVar.String() + "]" diff --git a/rete/idelem.go b/rete/idelem.go new file mode 100644 index 0000000..a483736 --- /dev/null +++ b/rete/idelem.go @@ -0,0 +1,18 @@ +package rete + +type nwElemId interface { + setID(nw Network) + getID() int +} +type nwElemIdImpl struct { + ID int + nw Network +} + +func (ide *nwElemIdImpl) setID(nw Network) { + ide.nw = nw + ide.ID = nw.incrementAndGetId() +} +func (ide *nwElemIdImpl) getID() int { + return ide.ID +} diff --git a/rete/jointable.go b/rete/jointable.go index a1918b5..878a62c 100644 --- a/rete/jointable.go +++ b/rete/jointable.go @@ -5,7 +5,7 @@ import ( ) type joinTable interface { - getID() int + nwElemId getRule() model.Rule addRow(handles []reteHandle) joinTableRow @@ -14,15 +14,13 @@ type joinTable interface { getRowIterator() rowIterator getRowCount() int - //getMap() map[joinTableRow]joinTableRow } type joinTableImpl struct { - id int + nwElemIdImpl table map[int]joinTableRow idr []model.TupleType rule model.Rule - nw Network } func newJoinTable(nw Network, rule model.Rule, identifiers []model.TupleType) joinTable { @@ -36,20 +34,15 @@ func newJoinTable(nw Network, rule model.Rule, identifiers []model.TupleType) jo } func (jt *joinTableImpl) initJoinTableImpl(nw Network, rule model.Rule, identifiers []model.TupleType) { - jt.id = nw.incrementAndGetId() + jt.setID(nw) jt.idr = identifiers jt.table = map[int]joinTableRow{} jt.rule = rule - jt.nw = nw -} - -func (jt *joinTableImpl) getID() int { - return jt.id } func (jt *joinTableImpl) addRow(handles []reteHandle) joinTableRow { - row := newJoinTableRow(handles, jt.nw.incrementAndGetId()) + row := newJoinTableRow(handles, jt.nw) jt.table[row.getID()] = row for i := 0; i < len(row.getHandles()); i++ { @@ -60,9 +53,12 @@ func (jt *joinTableImpl) addRow(handles []reteHandle) joinTableRow { } func (jt *joinTableImpl) removeRow(rowID int) joinTableRow { - row := jt.table[rowID] - delete(jt.table, rowID) - return row + row, found := jt.table[rowID] + if found { + delete(jt.table, rowID) + return row + } + return nil } func (jt *joinTableImpl) getRowCount() int { diff --git a/rete/jointablerow.go b/rete/jointablerow.go index a24d035..c020792 100644 --- a/rete/jointablerow.go +++ b/rete/jointablerow.go @@ -1,30 +1,26 @@ package rete type joinTableRow interface { - getID() int + nwElemId getHandles() []reteHandle } type joinTableRowImpl struct { - id int + nwElemIdImpl handles []reteHandle } -func newJoinTableRow(handles []reteHandle, id int) joinTableRow { +func newJoinTableRow(handles []reteHandle, nw Network) joinTableRow { jtr := joinTableRowImpl{} - jtr.initJoinTableRow(handles, id) + jtr.initJoinTableRow(handles, nw) return &jtr } -func (jtr *joinTableRowImpl) initJoinTableRow(handles []reteHandle, id int) { +func (jtr *joinTableRowImpl) initJoinTableRow(handles []reteHandle, nw Network) { + jtr.setID(nw) jtr.handles = append([]reteHandle{}, handles...) - jtr.id = id } func (jtr *joinTableRowImpl) getHandles() []reteHandle { return jtr.handles } - -func (jtr *joinTableRowImpl) getID() int { - return jtr.id -} diff --git a/rete/network.go b/rete/network.go index f3654de..15327d3 100644 --- a/rete/network.go +++ b/rete/network.go @@ -80,6 +80,7 @@ func NewReteNetwork() Network { } func (nw *reteNetworkImpl) initReteNetwork() { + nw.currentId = 0 nw.allRules = make(map[string]model.Rule) nw.allClassNodes = make(map[string]classNode) nw.ruleNameNodesOfRule = make(map[string]*list.List) diff --git a/rete/node.go b/rete/node.go index e0f42db..fcb9f21 100644 --- a/rete/node.go +++ b/rete/node.go @@ -12,31 +12,21 @@ import ( //node a building block of the rete network type node interface { abstractNode + nwElemId getIdentifiers() []model.TupleType - getID() int addNodeLink(nodeLink) assertObjects(ctx context.Context, handles []reteHandle, isRight bool) } type nodeImpl struct { + nwElemIdImpl identifiers []model.TupleType nodeLinkVar nodeLink - id int rule model.Rule - nw Network } -//NewNode ... returns a new node -//func newNode(nw Network, rule model.Rule, identifiers []model.TupleType) node { -// n := nodeImpl{} -// n.initNodeImpl(nw, rule, identifiers) -// return &n -//} - func (n *nodeImpl) initNodeImpl(nw Network, rule model.Rule, identifiers []model.TupleType) { - n.nw = nw - n.id = nw.incrementAndGetId() - + n.setID(nw) n.identifiers = identifiers n.rule = rule } @@ -45,16 +35,12 @@ func (n *nodeImpl) getIdentifiers() []model.TupleType { return n.identifiers } -func (n *nodeImpl) getID() int { - return n.id -} - func (n *nodeImpl) addNodeLink(nl nodeLink) { n.nodeLinkVar = nl } func (n *nodeImpl) String() string { - str := "id:" + strconv.Itoa(n.id) + ", idrs:" + str := "id:" + strconv.Itoa(n.getID()) + ", idrs:" for _, nodeIdentifier := range n.identifiers { str += string(nodeIdentifier) + "," } diff --git a/rete/nodelink.go b/rete/nodelink.go index 011a702..fde3b4f 100644 --- a/rete/nodelink.go +++ b/rete/nodelink.go @@ -9,6 +9,7 @@ import ( //nodelink connects 2 nodes, a rete building block type nodeLink interface { + nwElemId String() string getChild() node isRightNode() bool @@ -19,17 +20,14 @@ type nodeLink interface { } type nodeLinkImpl struct { + nwElemIdImpl convert []int numIdentifiers int - - parent node - parentIds []model.TupleType - - child node - childIds []model.TupleType - - isRight bool - id int + parent node + parentIds []model.TupleType + child node + childIds []model.TupleType + isRight bool } func newNodeLink(nw Network, parent node, child node, isRight bool) nodeLink { @@ -39,7 +37,7 @@ func newNodeLink(nw Network, parent node, child node, isRight bool) nodeLink { } func (nl *nodeLinkImpl) initNodeLink(nw Network, parent node, child node, isRight bool) { - nl.id = nw.incrementAndGetId() + nl.setID(nw) nl.child = child nl.isRight = isRight @@ -60,8 +58,8 @@ func (nl *nodeLinkImpl) initNodeLink(nw Network, parent node, child node, isRigh } //initialize node link : for use with ClassNodeLink -func initClassNodeLink(nw Network, nl *nodeLinkImpl, child node) { - nl.id = nw.incrementAndGetId() +func (nl *nodeLinkImpl) initClassNodeLink(nw Network, child node) { + nl.setID(nw) nl.child = child nl.childIds = child.getIdentifiers() } diff --git a/rete/retehandle.go b/rete/retehandle.go index db1dd27..4631f8a 100644 --- a/rete/retehandle.go +++ b/rete/retehandle.go @@ -8,6 +8,7 @@ import ( //Holds a tuple reference and related state type reteHandle interface { + nwElemId setTuple(tuple model.Tuple) getTuple() model.Tuple addJoinTableRowRef(joinTableRowVar joinTableRow, joinTableVar joinTable) @@ -17,11 +18,9 @@ type reteHandle interface { } type reteHandleImpl struct { - //this is "transient" - tuple model.Tuple - //this is the identity of the handle. + nwElemIdImpl + tuple model.Tuple tupleKey model.TupleKey - nw Network jtRefs joinTableRefsInHdl } @@ -36,7 +35,7 @@ func (hdl *reteHandleImpl) setTuple(tuple model.Tuple) { } func (hdl *reteHandleImpl) initHandleImpl(nw *reteNetworkImpl, tuple model.Tuple) { - hdl.nw = nw + hdl.setID(nw) hdl.setTuple(tuple) hdl.jtRefs = newJoinTableRefsInHdlImpl() } @@ -63,8 +62,6 @@ func (hdl *reteHandleImpl) removeJoinTableRowRefs(changedProps map[string]bool) tuple := hdl.tuple alias := tuple.GetTupleType() - //emptyJoinTables := list.New() - hdlTblIter := hdl.newHdlTblIterator() for hdlTblIter.hasNext() { @@ -90,16 +87,22 @@ func (hdl *reteHandleImpl) removeJoinTableRowRefs(changedProps map[string]bool) if !toDelete { continue } - //Remove rows from corresponding join tables + //this can happen if some other handle removed a row as a result of retraction + if rowIDs == nil { + continue + } + ////Remove rows from corresponding join tables for e := rowIDs.Front(); e != nil; e = e.Next() { rowID := e.Value.(int) row := joinTable.removeRow(rowID) - - //Remove other refs recursively. - for _, otherHdl := range row.getHandles() { - otherHdl.removeJoinTableRowRefs(nil) + if row != nil { + //Remove other refs recursively. + for _, otherHdl := range row.getHandles() { + //if otherHdl != nil { + otherHdl.removeJoinTableRowRefs(nil) + //} + } } - } //Remove the reference to the table itself diff --git a/rete/rulenode.go b/rete/rulenode.go index 01ce4e9..1112ff4 100644 --- a/rete/rulenode.go +++ b/rete/rulenode.go @@ -26,7 +26,7 @@ func newRuleNode(rule model.Rule) ruleNode { } func (rn *ruleNodeImpl) String() string { - return "\t[RuleNode id(" + strconv.Itoa(rn.id) + "): \n" + + return "\t[RuleNode id(" + strconv.Itoa(rn.getID()) + "): \n" + "\t\tIdentifier = " + model.IdentifiersToString(rn.identifiers) + " ;\n" + "\t\tRule = " + rn.rule.GetName() + "]\n" } From 1b9bf49d0084b22bbe2e412852e33c4a183f2003 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Tue, 11 Dec 2018 14:23:34 +0530 Subject: [PATCH 014/125] refactor for jointables and rows --- common/model/types.go | 17 +------ examples/trackntrace/trackntrace_test.go | 3 ++ rete/factory.go | 39 +++++++++++++++ rete/joinnode.go | 4 +- rete/jointable.go | 61 ---------------------- rete/jointablememimpl.go | 64 ++++++++++++++++++++++++ rete/jointablerefsinhandle.go | 59 ---------------------- rete/jointablerefsinhandlememimpl.go | 60 ++++++++++++++++++++++ rete/jointablerow.go | 20 -------- rete/jointablerowmemimpl.go | 21 ++++++++ rete/network.go | 27 ++++++++++ rete/retehandle.go | 2 +- rete/utils.go | 4 +- ruleapi/rulesession.go | 10 ++++ 14 files changed, 232 insertions(+), 159 deletions(-) create mode 100644 rete/factory.go create mode 100644 rete/jointablememimpl.go create mode 100644 rete/jointablerefsinhandlememimpl.go create mode 100644 rete/jointablerowmemimpl.go diff --git a/common/model/types.go b/common/model/types.go index 24aa8cb..45169bd 100644 --- a/common/model/types.go +++ b/common/model/types.go @@ -71,6 +71,8 @@ type RuleSession interface { //RtcTransactionHandler RegisterRtcTransactionHandler(txnHandler RtcTransactionHandler, handlerCtx interface{}) + + SetConfig(config map[string]string) } //ConditionEvaluator is a function pointer for handling condition evaluations on the server side @@ -102,18 +104,3 @@ type RtcModified interface { } type RtcTransactionHandler func(ctx context.Context, rs RuleSession, txn RtcTxn, txnContext interface{}) - -type IntegerID interface { - getID() int - setID(ID int) -} -type IntegerIDImpl struct { - id int -} - -func (i *IntegerIDImpl) getID() int { - return i.id -} -func (i *IntegerIDImpl) setID(ID int) { - i.id = ID -} diff --git a/examples/trackntrace/trackntrace_test.go b/examples/trackntrace/trackntrace_test.go index 6fd19b3..acdbd88 100644 --- a/examples/trackntrace/trackntrace_test.go +++ b/examples/trackntrace/trackntrace_test.go @@ -199,6 +199,9 @@ func TestSameTupleInstanceAssert(t *testing.T) { func createRuleSessionAndRules(t *testing.T) (model.RuleSession, error) { rs, _ := ruleapi.GetOrCreateRuleSession("asession") + props := make(map[string]string) + props["jtstore"] = "memory" + rs.SetConfig(props) tupleDescFileAbsPath := common.GetAbsPathForResource("src/github.com/project-flogo/rules/examples/trackntrace/trackntrace.json") diff --git a/rete/factory.go b/rete/factory.go new file mode 100644 index 0000000..6023444 --- /dev/null +++ b/rete/factory.go @@ -0,0 +1,39 @@ +package rete + +import ( + "github.com/project-flogo/rules/common/model" +) + +type StoreProvider int + +const ( + Memory = iota + Redis +) + +type TypeFactory struct { + config map[string]string +} + +func NewFactory(config map[string]string) TypeFactory { + tf := TypeFactory{config} + return tf +} + +func (f TypeFactory) getJoinTable(nw Network, rule model.Rule, identifiers []model.TupleType) joinTable { + jtStore := f.config["jtstore"] + if jtStore == "" || jtStore == "memory" { + jt := newJoinTable(nw, rule, identifiers) + return jt + } + return nil +} + +func (f TypeFactory) getJoinTableRefs() joinTableRefsInHdl { + jtType := f.config["jtstore"] + if jtType == "" || jtType == "memory" { + jtRef := newJoinTableRefsInHdlImpl() + return jtRef + } + return nil +} diff --git a/rete/joinnode.go b/rete/joinnode.go index 2b4873d..096a6f2 100644 --- a/rete/joinnode.go +++ b/rete/joinnode.go @@ -41,8 +41,8 @@ func (jn *joinNodeImpl) initjoinNodeImplVar(nw Network, rule model.Rule, leftIdr jn.leftIdrs = leftIdrs jn.rightIdrs = rightIdrs jn.conditionVar = conditionVar - jn.leftTable = newJoinTable(nw, rule, leftIdrs) - jn.rightTable = newJoinTable(nw, rule, rightIdrs) + jn.leftTable = nw.getFactory().getJoinTable(nw, rule, leftIdrs) + jn.rightTable = nw.getFactory().getJoinTable(nw, rule, rightIdrs) jn.setJoinIdentifiers() } diff --git a/rete/jointable.go b/rete/jointable.go index 878a62c..a949d27 100644 --- a/rete/jointable.go +++ b/rete/jointable.go @@ -15,64 +15,3 @@ type joinTable interface { getRowCount() int } - -type joinTableImpl struct { - nwElemIdImpl - table map[int]joinTableRow - idr []model.TupleType - rule model.Rule -} - -func newJoinTable(nw Network, rule model.Rule, identifiers []model.TupleType) joinTable { - jT := joinTableImpl{} - jT.initJoinTableImpl(nw, rule, identifiers) - - //add it to all join tables collection before returning - reteNw := nw.(*reteNetworkImpl) - reteNw.allJoinTables[jT.getID()] = &jT - return &jT -} - -func (jt *joinTableImpl) initJoinTableImpl(nw Network, rule model.Rule, identifiers []model.TupleType) { - jt.setID(nw) - jt.idr = identifiers - jt.table = map[int]joinTableRow{} - jt.rule = rule -} - -func (jt *joinTableImpl) addRow(handles []reteHandle) joinTableRow { - - row := newJoinTableRow(handles, jt.nw) - - jt.table[row.getID()] = row - for i := 0; i < len(row.getHandles()); i++ { - handle := row.getHandles()[i] - handle.addJoinTableRowRef(row, jt) - } - return row -} - -func (jt *joinTableImpl) removeRow(rowID int) joinTableRow { - row, found := jt.table[rowID] - if found { - delete(jt.table, rowID) - return row - } - return nil -} - -func (jt *joinTableImpl) getRowCount() int { - return len(jt.table) -} - -func (jt *joinTableImpl) getRule() model.Rule { - return jt.rule -} - -func (jt *joinTableImpl) getRowIterator() rowIterator { - return newRowIterator(jt.table) -} - -func (jt *joinTableImpl) getRow(rowID int) joinTableRow { - return jt.table[rowID] -} diff --git a/rete/jointablememimpl.go b/rete/jointablememimpl.go new file mode 100644 index 0000000..b03f2cf --- /dev/null +++ b/rete/jointablememimpl.go @@ -0,0 +1,64 @@ +package rete + +import "github.com/project-flogo/rules/common/model" + +type joinTableImpl struct { + nwElemIdImpl + table map[int]joinTableRow + idr []model.TupleType + rule model.Rule +} + +func newJoinTable(nw Network, rule model.Rule, identifiers []model.TupleType) joinTable { + jT := joinTableImpl{} + jT.initJoinTableImpl(nw, rule, identifiers) + + //add it to all join tables collection before returning + reteNw := nw.(*reteNetworkImpl) + reteNw.allJoinTables[jT.getID()] = &jT + return &jT +} + +func (jt *joinTableImpl) initJoinTableImpl(nw Network, rule model.Rule, identifiers []model.TupleType) { + jt.setID(nw) + jt.idr = identifiers + jt.table = map[int]joinTableRow{} + jt.rule = rule +} + +func (jt *joinTableImpl) addRow(handles []reteHandle) joinTableRow { + + row := newJoinTableRow(handles, jt.nw) + + jt.table[row.getID()] = row + for i := 0; i < len(row.getHandles()); i++ { + handle := row.getHandles()[i] + handle.addJoinTableRowRef(row, jt) + } + return row +} + +func (jt *joinTableImpl) removeRow(rowID int) joinTableRow { + row, found := jt.table[rowID] + if found { + delete(jt.table, rowID) + return row + } + return nil +} + +func (jt *joinTableImpl) getRowCount() int { + return len(jt.table) +} + +func (jt *joinTableImpl) getRule() model.Rule { + return jt.rule +} + +func (jt *joinTableImpl) getRowIterator() rowIterator { + return newRowIterator(jt.table) +} + +func (jt *joinTableImpl) getRow(rowID int) joinTableRow { + return jt.table[rowID] +} diff --git a/rete/jointablerefsinhandle.go b/rete/jointablerefsinhandle.go index 7a63f1d..e78c7c3 100644 --- a/rete/jointablerefsinhandle.go +++ b/rete/jointablerefsinhandle.go @@ -1,65 +1,6 @@ package rete -import "container/list" - type joinTableRefsInHdl interface { addEntry(jointTableID int, rowID int) removeEntry(jointTableID int) } - -type joinTableRefsInHdlImpl struct { - //keys are jointable-ids and values are lists of row-ids in the corresponding join table - tablesAndRows map[int]*list.List -} - -func newJoinTableRefsInHdlImpl() joinTableRefsInHdl { - hdlJt := joinTableRefsInHdlImpl{} - hdlJt.tablesAndRows = make(map[int]*list.List) - return &hdlJt -} - -func (h *joinTableRefsInHdlImpl) addEntry(jointTableID int, rowID int) { - rowsForJoinTable := h.tablesAndRows[jointTableID] - if rowsForJoinTable == nil { - rowsForJoinTable = list.New() - h.tablesAndRows[jointTableID] = rowsForJoinTable - } - rowsForJoinTable.PushBack(rowID) -} - -func (h *joinTableRefsInHdlImpl) removeEntry(jointTableID int) { - delete(h.tablesAndRows, jointTableID) -} - -type hdlTblIterator interface { - hasNext() bool - next() (int, *list.List) -} - -type hdlTblIteratorImpl struct { - hdlJtImpl *joinTableRefsInHdlImpl - kList list.List - curr *list.Element -} - -func (ri *hdlTblIteratorImpl) hasNext() bool { - return ri.curr != nil -} - -func (ri *hdlTblIteratorImpl) next() (int, *list.List) { - id := ri.curr.Value.(int) - lst := ri.hdlJtImpl.tablesAndRows[id] - ri.curr = ri.curr.Next() - return id, lst -} - -func (hdl *reteHandleImpl) newHdlTblIterator() hdlTblIterator { - ri := hdlTblIteratorImpl{} - ri.hdlJtImpl = hdl.jtRefs.(*joinTableRefsInHdlImpl) - ri.kList = list.List{} - for k, _ := range ri.hdlJtImpl.tablesAndRows { - ri.kList.PushBack(k) - } - ri.curr = ri.kList.Front() - return &ri -} diff --git a/rete/jointablerefsinhandlememimpl.go b/rete/jointablerefsinhandlememimpl.go new file mode 100644 index 0000000..05e3609 --- /dev/null +++ b/rete/jointablerefsinhandlememimpl.go @@ -0,0 +1,60 @@ +package rete + +import "container/list" + +type joinTableRefsInHdlImpl struct { + //keys are jointable-ids and values are lists of row-ids in the corresponding join table + tablesAndRows map[int]*list.List +} + +func newJoinTableRefsInHdlImpl() joinTableRefsInHdl { + hdlJt := joinTableRefsInHdlImpl{} + hdlJt.tablesAndRows = make(map[int]*list.List) + return &hdlJt +} + +func (h *joinTableRefsInHdlImpl) addEntry(jointTableID int, rowID int) { + rowsForJoinTable := h.tablesAndRows[jointTableID] + if rowsForJoinTable == nil { + rowsForJoinTable = list.New() + h.tablesAndRows[jointTableID] = rowsForJoinTable + } + rowsForJoinTable.PushBack(rowID) +} + +func (h *joinTableRefsInHdlImpl) removeEntry(jointTableID int) { + delete(h.tablesAndRows, jointTableID) +} + +type hdlTblIterator interface { + hasNext() bool + next() (int, *list.List) +} + +type hdlTblIteratorImpl struct { + hdlJtImpl *joinTableRefsInHdlImpl + kList list.List + curr *list.Element +} + +func (ri *hdlTblIteratorImpl) hasNext() bool { + return ri.curr != nil +} + +func (ri *hdlTblIteratorImpl) next() (int, *list.List) { + id := ri.curr.Value.(int) + lst := ri.hdlJtImpl.tablesAndRows[id] + ri.curr = ri.curr.Next() + return id, lst +} + +func (hdl *reteHandleImpl) newHdlTblIterator() hdlTblIterator { + ri := hdlTblIteratorImpl{} + ri.hdlJtImpl = hdl.jtRefs.(*joinTableRefsInHdlImpl) + ri.kList = list.List{} + for k, _ := range ri.hdlJtImpl.tablesAndRows { + ri.kList.PushBack(k) + } + ri.curr = ri.kList.Front() + return &ri +} diff --git a/rete/jointablerow.go b/rete/jointablerow.go index c020792..790bf7c 100644 --- a/rete/jointablerow.go +++ b/rete/jointablerow.go @@ -4,23 +4,3 @@ type joinTableRow interface { nwElemId getHandles() []reteHandle } - -type joinTableRowImpl struct { - nwElemIdImpl - handles []reteHandle -} - -func newJoinTableRow(handles []reteHandle, nw Network) joinTableRow { - jtr := joinTableRowImpl{} - jtr.initJoinTableRow(handles, nw) - return &jtr -} - -func (jtr *joinTableRowImpl) initJoinTableRow(handles []reteHandle, nw Network) { - jtr.setID(nw) - jtr.handles = append([]reteHandle{}, handles...) -} - -func (jtr *joinTableRowImpl) getHandles() []reteHandle { - return jtr.handles -} diff --git a/rete/jointablerowmemimpl.go b/rete/jointablerowmemimpl.go new file mode 100644 index 0000000..bbf18fc --- /dev/null +++ b/rete/jointablerowmemimpl.go @@ -0,0 +1,21 @@ +package rete + +type joinTableRowImpl struct { + nwElemIdImpl + handles []reteHandle +} + +func newJoinTableRow(handles []reteHandle, nw Network) joinTableRow { + jtr := joinTableRowImpl{} + jtr.initJoinTableRow(handles, nw) + return &jtr +} + +func (jtr *joinTableRowImpl) initJoinTableRow(handles []reteHandle, nw Network) { + jtr.setID(nw) + jtr.handles = append([]reteHandle{}, handles...) +} + +func (jtr *joinTableRowImpl) getHandles() []reteHandle { + return jtr.handles +} diff --git a/rete/network.go b/rete/network.go index 15327d3..5fb4b31 100644 --- a/rete/network.go +++ b/rete/network.go @@ -45,6 +45,10 @@ type Network interface { RegisterRtcTransactionHandler(txnHandler model.RtcTransactionHandler, txnContext interface{}) getJoinTable(joinTableID int) joinTable + SetConfig(config map[string]string) + GetConfigValue(key string) string + GetConfig() map[string]string + getFactory() TypeFactory } type reteNetworkImpl struct { @@ -70,6 +74,9 @@ type reteNetworkImpl struct { txnContext interface{} allJoinTables map[int]joinTable + config map[string]string + + factory TypeFactory } //NewReteNetwork ... creates a new rete network @@ -87,6 +94,7 @@ func (nw *reteNetworkImpl) initReteNetwork() { nw.ruleNameClassNodeLinksOfRule = make(map[string]*list.List) nw.allHandles = make(map[string]reteHandle) nw.allJoinTables = make(map[int]joinTable) + nw.factory = TypeFactory{} } func (nw *reteNetworkImpl) AddRule(rule model.Rule) (err error) { @@ -674,3 +682,22 @@ func (nw *reteNetworkImpl) RegisterRtcTransactionHandler(txnHandler model.RtcTra func (nw *reteNetworkImpl) getJoinTable(joinTableID int) joinTable { return nw.allJoinTables[joinTableID] } + +func (nw *reteNetworkImpl) SetConfig(config map[string]string) { + nw.config = config + nw.factory = TypeFactory{config} + +} + +func (nw *reteNetworkImpl) GetConfigValue(key string) string { + val, _ := nw.config[key] + return val +} + +func (nw *reteNetworkImpl) GetConfig() map[string]string { + return nw.config +} + +func (nw *reteNetworkImpl) getFactory() TypeFactory { + return nw.factory +} diff --git a/rete/retehandle.go b/rete/retehandle.go index 4631f8a..ca8336d 100644 --- a/rete/retehandle.go +++ b/rete/retehandle.go @@ -37,7 +37,7 @@ func (hdl *reteHandleImpl) setTuple(tuple model.Tuple) { func (hdl *reteHandleImpl) initHandleImpl(nw *reteNetworkImpl, tuple model.Tuple) { hdl.setID(nw) hdl.setTuple(tuple) - hdl.jtRefs = newJoinTableRefsInHdlImpl() + hdl.jtRefs = nw.getFactory().getJoinTableRefs() } func (hdl *reteHandleImpl) getTuple() model.Tuple { diff --git a/rete/utils.go b/rete/utils.go index 399f011..62e22ef 100644 --- a/rete/utils.go +++ b/rete/utils.go @@ -2,7 +2,9 @@ package rete //translation utilities between handles/tuples to pass to user conditions and actions -import "github.com/project-flogo/rules/common/model" +import ( + "github.com/project-flogo/rules/common/model" +) func copyIntoTupleArray(handles []reteHandle) []model.Tuple { tuples := make([]model.Tuple, len(handles)) diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index 6df0fc9..ac524ad 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -24,6 +24,7 @@ type rulesessionImpl struct { timers map[interface{}]*time.Timer startupFn model.StartupRSFunction started bool + config map[string]string } func GetOrCreateRuleSession(name string) (model.RuleSession, error) { @@ -174,3 +175,12 @@ func (rs *rulesessionImpl) GetAssertedTuple(key model.TupleKey) model.Tuple { func (rs *rulesessionImpl) RegisterRtcTransactionHandler(txnHandler model.RtcTransactionHandler, txnContext interface{}) { rs.reteNetwork.RegisterRtcTransactionHandler(txnHandler, txnContext) } + +func (rs *rulesessionImpl) SetConfig(config map[string]string) { + if rs.config == nil { + rs.config = config + } + if rs.reteNetwork != nil && rs.reteNetwork.GetConfig() != nil { + rs.reteNetwork.SetConfig(config) + } +} From 2ec7942008072d2bfa6e82fcdb40594047c4e864 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Wed, 12 Dec 2018 22:54:06 +0530 Subject: [PATCH 015/125] refactor interfaces in order to separate out and organize in-mem implementation --- common/model/tuplekey.go | 1 - rete/classnode.go | 3 +- rete/classnodelink.go | 9 +- rete/common/types.go | 42 +++++++ rete/context.go | 12 +- rete/debug.test | Bin 3773818 -> 0 bytes rete/factory.go | 24 ++-- rete/filternode.go | 19 +-- rete/idelem.go | 18 --- .../memimpl/jointablecollectionimpl.go | 7 ++ rete/internal/memimpl/jointablememimpl.go | 66 +++++++++++ .../memimpl}/jointablerefsinhandlememimpl.go | 40 +++---- .../memimpl/jointablerowiterator.go} | 20 ++-- rete/internal/memimpl/jointablerowmemimpl.go | 23 ++++ rete/internal/types/types.go | 77 ++++++++++++ rete/joinnode.go | 55 ++++----- rete/jointable.go | 17 --- rete/jointablememimpl.go | 64 ---------- rete/jointablerefsinhandle.go | 6 - rete/jointablerow.go | 6 - rete/jointablerowmemimpl.go | 21 ---- rete/network.go | 110 +++++++----------- rete/node.go | 15 +-- rete/nodelink.go | 27 ++--- rete/opsList.go | 13 ++- rete/retehandle.go | 62 +++++----- rete/rulenode.go | 5 +- rete/utils.go | 9 +- ruleapi/rulesession.go | 11 +- 29 files changed, 418 insertions(+), 364 deletions(-) create mode 100644 rete/common/types.go delete mode 100755 rete/debug.test delete mode 100644 rete/idelem.go create mode 100644 rete/internal/memimpl/jointablecollectionimpl.go create mode 100644 rete/internal/memimpl/jointablememimpl.go rename rete/{ => internal/memimpl}/jointablerefsinhandlememimpl.go (64%) rename rete/{rowiterator.go => internal/memimpl/jointablerowiterator.go} (53%) create mode 100644 rete/internal/memimpl/jointablerowmemimpl.go create mode 100644 rete/internal/types/types.go delete mode 100644 rete/jointable.go delete mode 100644 rete/jointablememimpl.go delete mode 100644 rete/jointablerefsinhandle.go delete mode 100644 rete/jointablerow.go delete mode 100644 rete/jointablerowmemimpl.go diff --git a/common/model/tuplekey.go b/common/model/tuplekey.go index 0cc5118..996e2e3 100644 --- a/common/model/tuplekey.go +++ b/common/model/tuplekey.go @@ -2,7 +2,6 @@ package model import ( "fmt" - "github.com/TIBCOSoftware/flogo-lib/core/data" "reflect" "github.com/project-flogo/core/data/coerce" ) diff --git a/rete/classnode.go b/rete/classnode.go index 71b4916..60c97e2 100644 --- a/rete/classnode.go +++ b/rete/classnode.go @@ -5,6 +5,7 @@ import ( "context" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/internal/types" ) //classNode holds links to filter and join nodes eventually leading upto the rule node @@ -76,7 +77,7 @@ func (cn *classNodeImpl) String() string { func (cn *classNodeImpl) assert(ctx context.Context, tuple model.Tuple, changedProps map[string]bool) { handle := getOrCreateHandle(ctx, tuple) - handles := make([]reteHandle, 1) + handles := make([]types.ReteHandle, 1) handles[0] = handle propagate := false for e := cn.getClassNodeLinks().Front(); e != nil; e = e.Next() { diff --git a/rete/classnodelink.go b/rete/classnodelink.go index 3a1607c..92cae46 100644 --- a/rete/classnodelink.go +++ b/rete/classnodelink.go @@ -1,6 +1,9 @@ package rete -import "github.com/project-flogo/rules/common/model" +import ( + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/internal/types" +) //classNodeLink links the classNode to the rest of the model.Rule's network type classNodeLink interface { @@ -17,13 +20,13 @@ type classNodeLinkImpl struct { classNodeVar classNode } -func newClassNodeLink(nw Network, classNodeVar classNode, child node, rule model.Rule, identifierVar model.TupleType) classNodeLink { +func newClassNodeLink(nw types.Network, classNodeVar classNode, child node, rule model.Rule, identifierVar model.TupleType) classNodeLink { cnl := classNodeLinkImpl{} cnl.initClassNodeLinkImpl(nw, classNodeVar, child, rule, identifierVar) return &cnl } -func (cnl *classNodeLinkImpl) initClassNodeLinkImpl(nw Network, classNodeVar classNode, child node, rule model.Rule, identifierVar model.TupleType) { +func (cnl *classNodeLinkImpl) initClassNodeLinkImpl(nw types.Network, classNodeVar classNode, child node, rule model.Rule, identifierVar model.TupleType) { cnl.initClassNodeLink(nw, child) cnl.classNodeVar = classNodeVar cnl.rule = rule diff --git a/rete/common/types.go b/rete/common/types.go new file mode 100644 index 0000000..2818dc6 --- /dev/null +++ b/rete/common/types.go @@ -0,0 +1,42 @@ +package common + +import ( + "context" + "github.com/project-flogo/rules/common/model" +) + +type RtcOprn int + +const ( + ADD RtcOprn = 1 + iota + RETRACT + MODIFY + DELETE +) + +//Network ... the rete network +type Network interface { + AddRule(model.Rule) error + String() string + RemoveRule(string) model.Rule + GetRules() []model.Rule + Assert(ctx context.Context, rs model.RuleSession, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) + Retract(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) + GetAssertedTuple(key model.TupleKey) model.Tuple + GetAssertedTupleByStringKey(key string) model.Tuple + RegisterRtcTransactionHandler(txnHandler model.RtcTransactionHandler, txnContext interface{}) + SetConfig(config map[string]string) + GetConfigValue(key string) string + GetConfig() map[string]string + + //private + //retractInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) + //assertInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) + //getOrCreateHandle(ctx context.Context, tuple model.Tuple) ReteHandle + //getHandle(tuple model.Tuple) ReteHandle + //IncrementAndGetId() int + //GetJoinTable(joinTableID int) JoinTable + //getFactory() TypeFactory + + //AddToAllJoinTables (jT JoinTable) +} diff --git a/rete/context.go b/rete/context.go index 9fbe3de..d3acb27 100644 --- a/rete/context.go +++ b/rete/context.go @@ -13,7 +13,7 @@ var reteCTXKEY = model.RetecontextKeyType{} type reteCtx interface { getConflictResolver() conflictRes getOpsList() *list.List - getNetwork() Network + getNetwork() *reteNetworkImpl getRuleSession() model.RuleSession OnValueChange(tuple model.Tuple, prop string) @@ -37,7 +37,7 @@ type reteCtx interface { type reteCtxImpl struct { cr conflictRes opsList *list.List - network Network + network *reteNetworkImpl rs model.RuleSession //newly added tuples in the current RTC @@ -53,7 +53,7 @@ type reteCtxImpl struct { rtcModifyMap map[string]model.RtcModified } -func newReteCtxImpl(network Network, rs model.RuleSession) reteCtx { +func newReteCtxImpl(network *reteNetworkImpl, rs model.RuleSession) reteCtx { reteCtxVal := reteCtxImpl{} reteCtxVal.cr = newConflictRes() reteCtxVal.opsList = list.New() @@ -74,7 +74,7 @@ func (rctx *reteCtxImpl) getOpsList() *list.List { return rctx.opsList } -func (rctx *reteCtxImpl) getNetwork() Network { +func (rctx *reteCtxImpl) getNetwork() *reteNetworkImpl { return rctx.network } @@ -171,13 +171,13 @@ func getReteCtx(ctx context.Context) reteCtx { return intr.(reteCtx) } -func newReteCtx(ctx context.Context, network Network, rs model.RuleSession) (context.Context, reteCtx) { +func newReteCtx(ctx context.Context, network *reteNetworkImpl, rs model.RuleSession) (context.Context, reteCtx) { reteCtxVar := newReteCtxImpl(network, rs) ctx = context.WithValue(ctx, reteCTXKEY, reteCtxVar) return ctx, reteCtxVar } -func getOrSetReteCtx(ctx context.Context, network Network, rs model.RuleSession) (reteCtx, bool, context.Context) { +func getOrSetReteCtx(ctx context.Context, network *reteNetworkImpl, rs model.RuleSession) (reteCtx, bool, context.Context) { isRecursive := false newCtx := ctx reteCtxVar := getReteCtx(ctx) diff --git a/rete/debug.test b/rete/debug.test deleted file mode 100755 index 2eca2fbb31f4df8c1eac182e3d8a5988f0c7d0a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3773818 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

;Qj}OeRhd4h2 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

%Evn+F0-wI!_5E>)4fe71z0qxw#!fz^zcWaKT1 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

R#Ec4;o%ZIK6QIPC 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%0DyYbQ(JdY;iP#fsf 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�s$y2qBIK7*rwF5tWP_l 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 OgR0AQDbL+>Z;W3!i^>y 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

^uEiH)x=15{>_cfBZe9@?FDq|#iryL2P( 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

o4h0H;j2hK=LShLfaEK#*HT+OSj8_}d zY1uymIX-0q+KDg@4~j6+{TzsiY8n` z5N7?%d>ENo0+FvR;QsZBF29h-KETe#g$-L@I3)7f#I+0PyF&pT>7Ws6q~qL8xh>;6 zDt$s~RQw3FjfL}KTu||;I3Kw_F(&o&F5yjT$G71_52~{a2J%AUwoxfCK($TWvHXJS ze8MH8#BEE#@{1Mq=-Vt^TpS#5WK%Yf!ikRg#kTTj3YKn+YU$!r5gp>js1}zS(gQB2 zixJ4fsgUM6hfq7Eix194-)8CJa^enU!`;WxxK@f7)l$Iy#U0|ts1|ov>nzrHKy{|= zhH#W3q*BokokFOc-iEFD(L4li+ht5e3iz%^G_I8`Mzs`h!E#XPrY9l=P~!?Msk@CB zqFQLO zs9Rj7>k60@R>U6j;8aziR9y#KS|*wi%j9$M9-Sk)w>8)zDB0HPMoSY9f#ED$Gsv{Z2GE% zt!epXk8oKU#;s#5uUbBjt#8r%Vj`sb5ZX$?wJo)hw#B_wo~@RqJk5`3M z*`_9@i)ac$#W@NZ6Jl{uJG|^Sq%P^w76uOcuNzflzlj<8L8UT=SV43PFu%Wcc3m$usgvh`Xu( zuCzY0omSbOB_P(oc0Mych4o~51NyhIzkc1L}FyhK$1fpj)GWb{7P;s zs3#IrGhT!2X`kM03Sru(1F;#H4wT&Sl^ygF#*Qfko|72aa>7rD#nl2Na=dfv9FHlA zmNvdl(J_$@Djn1ATva?**dZq;I_D%#w&IA&Y7Ot2AzsN(V022mbH4F~jJDXGcIV=p zh!w|UcW#{%F$K|T@IYkdeDnJu(NcQsQ^6n!%yUdTR^L{fhdWml_YikT`{7vY*eiL;HQ*g@!yx%VCq__fU67X~ZT2NoUFr$f~?YRdS|nWUmUX*B#swOeHKtw}%VaXBV17ZoRoD5Vue&pvcR{Tqfd`p=NoT{)A*Pow|i3LXIaQ zD!!ZuzM^`*rcJAAunkR>aV7#XR2JJ=(?$->8zf7mtF20fO74YG+Wu%C zzGop1C0n)^N3DAI$dVasgnIb*JeD|Xg}sTSYGb%`K$f4r zsnD1M`mlUAHYWc_4~rF=V@U5Lj)X45zFqJreYfNRy)79xevqz9O6{{4(%YvoYGx3+ zB_ExlN9oZ?dX%p79-Vqe>G*PUF!3e5(`58s6HJS^lLCj>WP?4&BIuM%+=I+1Td>2~ z>D~fz#uO64=*q^2mGx`;N;++H?LWKe+<#-~w6{Q*J~)?199v}!;w$~ZjD?fF+kBIriz&^D3#5>a zq~)ZF3UAUOWB6mICRE6PZoOfiU&7=WMd%Nbejp%Z=jIqJg_tyl@fV%W&&!yz-MY5l<=#L}xXQyND#rI-IE&MK zi-7>S*9C|)u&**9K9`9Wg2O5}gQ;JNy;pa=im75O5YUJp-TI0;@Vg7{l|377uJgsu zKwKOMqzJcPg<>S6v%6A`m?Ir2JqF6i~s@rI-<8`XlQ0YNRO~S288h=hB*Xa zvBR1_I=B&VT0R3V4H3~@8Gsvduz(bF8g8LUBoNTf{+w?iz&CHSGcE~;^zFdJssWaM z)C3~|Z30M?VR`wJoB{`3+-JsoqWu-7{7HblqD{Ms2DIrwZ*Ubl7>{PW2?Au?1*Tf& zD$0=_6y^2UX|SG70oZKVIpFlkFd(@4C*Yozln%KEGseZ_39>7-?mBmZ^2~R{_?SN1 zA%MCyIh;J@xbB*p#mFp)MJ zU=yRTGuDkczCoGAcbmPFr_?8_H0Hh`c8*IIryBuVz&S5dE@iVv$QQx@nRb9N+y@4L zdpdYiN84dT3&E|PA?W?O z@OI+3?@o$_1cO5ken&&R#&vL5hz{+xt=6#+SvcVK4dx)iG1}(U8p4&k_H{achDI6Z z56(^Q3wVQUFPFyCts`LwtAY8IgA8CauMvU?KSR(k0dsY6D%K(%3@VALPRs6Tf6)bJyZJTi28_~h}^oV%gfQ3(u+4DP5>&!E#MhEH}_s%Fr` zNG)E+4=)`LbF9e-Bef*J=k34ef#L45hlmNCpliThl+6PkG>epsG7E|h7T^D8LA%U`VS>?t6nkoyPz#^Z zZvS8=7*7%u8BiL*4+R{K3t=q==P?rGqbXVh26^V%MvgX0#~@9xV-O`f5)22=6t|nt zLX@7VL$-%OQ8gcn>uo{tW6Y7KFn@vNha+YJ^PuQc()_X{YU`KbQ(?~#Nxui#@O~zr zb>$HNJ?b0{8aK&semMuj8Q$gp-e?bp>wzS2pL!zkMs79s;>S8G!fYh%7B39*$Ks6T zjj^5_6(sR6b|zIcm=_xXyZU7ihMqt^u5xR__7U5266U-7_4b3{x!rsdJ@buGR&PC1 zoz7vd3F>o^EgX1A=ezz~R1}1LTAq6X*_36f=*5H2RE9^fC$5)6GH9V zVHnSfSfZvXM#A(6Y(!mqW4Pz=8n#3ipOJky7pmN^e}8mt7Vl^X3_9Vi@yS3RHRJK2 zaet?-=5sX*MD2-9TUX|t30k1`6W<*qwr2FGSRmTZ_fumlwFR;lQLTC6N#6JpCL7_B zasA@{IG>D8KP1pOW&C`={XrMNb&PU*hE#>BAPBTRu#l(0pw1mEFquPF+k@Q3D=%O@ z2&5VS-i7W4vr(HSfO77h6`WtjreZO~-`{qQL@m^f%ee2bfcuoq^o%VKHaiF^5Guj9 z$UzG*E~FE#_c*UZC^jI>%VN-OZiUdGqwMbQ-{PHtTfI!Fbov5V(RU*N=Ya5mBO#e;s@fl5riR0-f zf{~~XvifwrPJ^utcb`bpU~O5nQ&0j~pWcJSV^cEO`4`OH-{c#I+BP3Gd2lWc>0lJ$4@?T`q;7IlPALwii6*B$Qv;p9y)P)_~DZ$k3IbGY3%dx&f-VI-|GE< zBf+|Qv{n)civdQe(@SNvr%#5AIE2|ElTGutWm^l;p$+V1BFpulM97WUV{^{ zM66p8I?+lGWs605!aIj>Fm5#A_7LZ#l8SecP=kw_!!!!>0iMv=s-EHUi6ogB6N@uq zzB5y7KBxk_CvBCGW6%+UK-)*|pX z<;q_(J2C-RDJJGIj)1E|1`~Cata(7hhGO4UJ$)T$|167Gk~lanrB;?5hTtl=*GUx} zFY&Yu=}1X?sr;C)W(^trO#gjQ3t1BURq>fuwGVxj(9`tY+6j-9S5;IcBzjPCyifj9 z-8NBQ|EhgsqZnTtX~)gT&Rk|zXZgr3i-&44scY05VY=0%A4AP{x&P|2+F6F|_6vHgAKmW)-h z<{FEK_5Xk?wN)VCN^LDrSNf`+j53-qndM4{P+5hwdQ7HA`nG>odFRSU)GV}vZ*%?7 zSBgEwhNF zKc{b1cU88W`VAM}+)wQ4f^STc2H-EvQlx5oCBX{$9B7r!S&L$WfMzNRXzMItYJK!wdAu= zMCy84o}sIRx}J$PA@Wb~$(JXU|K4t0h0I}{US@oFf)s2=xGcyd= zRz*Vk&W>5Kul+fGP5sk{4^XwG|K(N*GJ>nr7AvKLEZ{RZTx#9VbsH}O< zt`siw{Zle;dZNvgzalXf2WgmoZ4o)f4Mn>p!p|>Sc<`QmO$rUeTs+c1ON1Ox9^7%Wv>3D6tuy7&!I! z#Kc=KGEYOJl0=BdY|;6?&?wmxkS!JV3|h-ZLM-|2BEBc6c-U~!Z#?`0d`UbSfv!|v zm6mUIO61VD%w9GN4&9;p*)d|Dq%60AzLBDA!Nik333BRHnqCbE@-Xpg8WnLxoyyX^ zQ_G5_MYtmzzQi=;)gwEM@bHEm8mJ{HubfkFO6@yIUFfObEr14bnBLs7EuOUtfNCzG&H!!zV-eyI$7gueIt^fWt3) z5AtuksI`BgbH_?>_%Yx=wgbL+)9vLs{7uNetFT`Gn_qo$ox_hu|98O0tZKp9Zz}qK zE`9#v-&tIVlYer#GMmQ%A2WAAPY(Yj_^&6oUjBx?Wo!p_ z8y?2tYXROWs?Y!V^Q|j#__x7+u6}y_+NY{5;P5-K{-4$3uh#E>6xeEH8o|JU^R(es`j#Nn@ke6Ov?uf0F?3WqO=us867(g)iuOh3K7de_^lj1 zfX4w}ykD=|9R32-Ut@w^{=ni5+4yl6@sss@;6Gjm z{MNPIS9AD$mXdGH*6V*|?CeAiUkvbW2mGh=$Flw3?qGjoj$ZzWl(wmyd<*>7<$!}w*$WK{T%r?d`<9QW2s(# z+k>fW`<0IIf0-WNzRLEcocuLV{`Lwz{!l*GT@D`$`L|Z;@eN;FUxdT^yN#>#_&tYz zXv^Wp1I*)qpLMXoha7$v`u|$J{Pg1YHgos}b5;4fKGEY_l=l9`;nT7Hr|a?gYFx_C z;a>;)+Yb0uql?$#@P|$-{q2o<`NMXN?8o7oga2Be>+w}8%wYB3jqS%KJ-+bd$Jz0V zR&&Ar4tQ_z!9zLyJ!rqpdil?-KlcHL&j9`1U+D4c-#z{-hyMm(=2kua?TRKle(JVW z{;h3#{JS|iEaT+whyJIrU5|gSnH9<5d(H*>|4)w}UTpt84u1jc?{UC4y;MFAhu;VF z$GuB0Kcnk2b2$8SGgSL+?AGHWU+kRB;ir6}@^62s$DgZnq6>$AdW*uFd-eDYCE66_ z@Z&*$*M2>|%=UdRarmmuq5KZ&@wd&=?ES-U5I?-=$coOu`#wzTf0k<#CqH2>$j{Kr z|GmUPcK)OR=~jOQ~BKOX((lpcTMp|S8hU(@ zMayP!^5@J2hpVZ_x10XM77pKXFW?{7jcPeqQ@uSST~6)zmLHGZ3q0U3M1a+@U0pu`Np$)`Deb|%-;Y1F&FU9 z>G6?yuC(Fgzj#XFJr4MZEB3y_;X57x`?b=`cP}Y8o5QzStnju2epRK(%{aWPA?V*) zFMs)rjKLg!9{8`-Mvt#O?Aebw{BEfKwgdinasf7eCqe#=c6#}TZ#DEObTNdCG2tA3)Jm}bVhH5wG^qbpGPVQT{zkY#zFUv~wq^7wPX0lJ zP0;IKKITPs{`F1tzh96@>4R|_)6;7|>dwhuR|52JrN{rrH7n@(2xy9Vm<9e=r3j>E6P_VY!3{dsiF zi|qW*CX8PW_#Qtu>&3};f&OlX@|*WmZ+8Co?AuWOt@ZgY7jgMHPX0|OKX0lY|8$;x z)j50y#4opl{Kex6)aUSCz`Op@`_C6cpD4lM*F*d1zMwC^MezkLbNFJ%mHmvI`uu-U zs1iGWk^f7Dx60}9Yd_ucE+@akw@`j<^!SUDXHDktae(*c(%bKwWh2&b_}1$n|NZs$ z`*K>|Djfc&l?v~gr`Lbb4;`N2@clnfc+X8ezU1wx%Q^gwH$eaSdi?();=kqaVpWnl zmgw;xeBEIVhhM)+!QBq!*YMmZw*URce30KsFF)?nIJW&dkf!j~Qa!%msDGd5^zXP{ z;a#`%_ySAcWan>w0QqhQ`@g>=wjn3~9mv16KrjFD4A&tJzXkBFL3;e9xFQ2M{8Lj@ z{=E+J$JC$2=Klud-+oswzuMfIWjXn0z<D6zs^QWKhSNeNu>+x5M73#*xpNTLNKPW!zH)7g!^Le)a z)McBx-sOP5aHRqpze?p&c*~*vDDu?+H>dv==)c+y_{zn)v-2Oj0q=3Z7t6D$GAI8a z;Eh;)`*;1v{e3um2HgKy4)_-e&V7%=M?w2#<<^(q^OwsX=XE^+9z`Gsr z$G5(go5L5v_}N}B|8%7kw*9yQFt-Cf*QMdC|Bjjm`F~z7zu;$UZgcwYKCke0RCo0O z{$sggI&?$(n;ibgSp~4_=<%&`#<1fj^|Ak4SC8LPa{mNQ{_Yz}zQ+MSGrDwp4&VHm z!rS%q@@Jp@ke$EWdmZv$UypBJw(uvM{O7JJywO09f8u2Gxg5UL50HNc{FiHTvE!dt zu>2e9z|UjDxA2NrSoY5O4mPwDaBdhW9EyTD<{e^WjF?*a!{{YRfsc#i{q%;z4q{k#eB z%WkHZ|6yw3ST6r7k3#+}J$~+yupAuz$SKHwb3Oi}Z}|T6ZjkSBz&HP(6l?#o=ahWA zgz%hqrz?O{*z{fHgoxZ3+(SYrN{r!|6(H!e+~2h zy&iw%+Qqjx{6eUI<{3TyXyXEZa`=wW{(HT8eC^j}vE#?pAbxm%*5emmt}-ap<3 zn6*-mf9}Av3!MBfVEo;cPp|*{Z~6Nd7mj~8-2W8#ZX!GWJs0q@tz_c-8hM17Z+!`}t_c^v$A%O4GgarkKv|J)AtD|KQuJN~^J^mjd?&wqihS}x|~ zcY*l_d%eE=rrkXL3x|JfzVaVqfPVbr_MD_V96sL^g|{5=A9`}V!QmT%d~<_d|Dzw5 z|BJ)72E6Sc|J=Go?ESL`>c7W9zBTQOhdKEffH&UL>mOJCS$6z>FT`InQeXb-*Z+Bd zlV3@W-~VkGUI%>VJcWvKcrWU|QLlf+f9n_L@QGl5S4n;ROdc|xoxiII{^NF#KkP`} zm7M%Bvy}gN9Lj%QmAMl+ygeTLf1uv~n><&V?LW+E0silR|EF26oSgiXOBLSxx!(T2 zbuQC_!zY8=*qikDj&r;2=J2z?{>I08|7($_G<*Lr3g++ZclG6O&)Kz%lV5nISbHY? z*R@qIztlG)hI9C(puhK)9>4VFFVi@DWg*`d@;z_q@n5@l73T1zKT_pqZr0oX*NvUo z_&pou-#mTv^1uJ_kJ6m{Iw0TUAphXTj4~WPc8Aj6^@U#lVnYrmbNE%DzwO}vt#fT& z&f#|e-m0OOe<-46IEVMZ{I|VLZ@({|+Q{C&ro;V*?b73?=XoaI|J>D*CpmmesDJM5dVJjR z%ZVKR3i!X*A^+{FuHVSv|AhP-{q*vy$A8Ms|MW!tXXx>fZ5Ofa#|Di5Z|m`Q)`YRo zPcMT0t65BsuVri)!R7xn$oCf1mw)jFo9b}*JPVZncpcsv_I|&w~1AJKzg`F#aGXzx;kxejW#Wy#arliO-<7Dh|KYH2SpWG5+D~)4 z9zQld=_gM9afqLu9eV$dyk3Bf{|E5?FGi0)J?s7wPJR`H4c3?c{EgOQ9R4Quznbd( zFY)*PvEx5^r$YHT*l*RI#28M#1@+IhLvQ~LmHukR;r|EaXZ1%zD1E|J;$KH>zcY#* ze|{4D&n&6Or?t;G~1XV*FWUhrSH1HNJB>9;ui0I;9&iC+JL?Z>g> z&z(j5v2Kd~``7yNcTGFSjvwH(rBz3t|6z-FzsTudY=bH&+rfX!4Bg3&-&so(-d#$c z|MDZtZ{XyQM*l0L&;N&AT5aXT2Gk!{k{;jg=U(jkt;fNB zt`GG3w~zXP9Y1>-@ul_ld#Y4}`<(vwF#d1Q%m4M{5_bHpJ>=i(kpCxlK3t2FKOM@? z4xM4!kG1gpsN2DR_w6<-aq>IB_=9VhzWjE!JNhh#cggnumgqmM)Z4G>&vn`M zI}QBb-l>=0?Yn9_IQi$H{qZ{JKP1l}cKqh+!>az7W%T~jcH1*AbMk*d{4Txz)tk3t z*RL;w{Cmsl?e|>CmhAXnjw4EcYly!6JYVr;w*4rM>#ug}^*4%`?ETL=@L%IgJ^sSS zDQx~DVEoLzTA%+OZ+7p*+5aZ=|7?f+7arS;9ltmU`deS=^*>az*=bJxrx^bS==~>Q z;2^gDya({+41NB4oy^PL|K3CW2kPao%hA3Mr+)>szr*;)$WC3@`;Xob|J-Hu^6M=h z#@>HKzAMJQZi)80oF2ctd`UKb7Cxr@-h&K#WN0}KUmwcP7^KHHuG+CZhyN4gyK?INuj)6e+3~YG5Pv-m z`CotG@q(QEa2S7fJFGt}eCNYv9Nw1YcU$<6sn>svcQV`mYyt7lNY~r%&c65A_TvoH zUypW?v0-~KFHxTOK7e@pb=n)>?F^!MhUbNJFnRQ|26_4rw_zqaM@D`5Q88lunt;kfSX z`rUG$Df#AOdi?LlKV+YOXanVEAJFSxZcjdT{Oe`FTMqX36suE{%YV(Ul>VMMdi~2E z4F8S8$3gw^7SY$gH`^}j&*95>z<&M*S=Bkc|NX)!G=syp1^XL^^!EE`chW8nKOXGo zO4j2W{x~ofhaU%KvmNYzD1SIxfBURc`L#Ri<rtC@U z&#c4qS8@2cfVU6p%m3FN#n|;Pohm4KMw~wWZ|T!?9w&b&tY7vzJpa+UWH~l|gu(iG z?+CsAWB09!;N;J^qV#u-)Z^dFd8avt--h}-;n%fY&*2yR1oB7e<@f&g%ux*e2i!=uju6uNNvN8zitQnc^&X) zJw@2~`7f;hcfG2Ye{;KZ=*pE1Tu1|9K2<&I|`hWS-K(_wwO?*h<-_zsYFOqXMCw~sspEvaSrzBrs=l>s1 z1O0#3NzpQx8VsNg(y{nir9zXSf=98Le>^zQ)nH~!MgU-)_nw*G{l z1^Z9Zn`N*=jSN-Zd;%KZHsEM{l8JuRsHe)tq{+wEV8>trD)e+U1s_2(?M|J193!h0O>N8^N}F!R>~0B^+W?_Y1G)@JK}65uTd{L0Fw z-sR*UFCpen#rTEofbad~G`9X-uL1dY!1o&$iMl{wd<}K!{L4VZ%5=XEb{(Sinp6xdx678fqeS@ zQ;NSgtHM$aFZb`Zgnat`Q;IKpCf^ATe+lgGCGStA_!bA!uXA`~oH9Fo|0%`ScrIxR zhhHJ|mxrgY-u^MCPwwULa{qB#l;S&9jQ^R#M_~TL zG5)IjQ2bj7-T%koefyV3ihD@%{!WTtac0qP9A55UZV5bne<#JyJJ)hChyU|ErLXs_ z-u~yVST#9(VW|J~{hL(&@xK=A=I|!i&pfY}|LoTA9vuGCB&9!neh8-dyA`}1i2KRVmxT@F7A z<@X$7S{&Zms`78w(Bq3tx%dZ%Uy%;>bHESWxRH&2a{ql>_&?php60*pcN2$m z@*Bba(e`tC{YyS}>Q@fG4fLn`ms9!gb^7ighaUjX-+IXYbNHj+Kc-uc|M+6b zdmO$rO5%`Zm_K&9dkE+*b5r_XA{m%hE&2Ev#;h)(BI$Vl-T#&1Z}xh1Du>?+^6CDq z6#vAScRF$SMUa2Ge=Eg*^3>`G4&N2rhVI`=@uh!`xX9t3g8b9{S1EqYSx+2?ueeg# zkM5sJ@w>NH?#;1+xTuu6=-$b;@5O6BLzX!Hgr-}nBNLLz=T_;1nb z?Nd0seE*6i@O1xBDu3>b5tBK*eE*6q@O1xBitkruR}_br?_co#v{P=I{sSLivBC@Bgh% zUi3bPpMdd)?4LvBpBXUzYYzV?#*cKp|CW5>=D!?%4#W?-e+`xY(u^}rIs8oM|Ct54 zD?`A4ir@M2c-H^k1^d(eODI11(G#z7@~v@7Al-k2;y1^qujlZo(0;f+)7$S*m(6E6 z{NvDmTD|r8?;3G(6o+sAsnVbBpF#Cs8~!kR|LS{x(W5yGv#?%%)yKx3=Hz?9f9d`i zRK9ojEw=n;oe=Ry$oEdy%fC9M^OKx>`TiqY;^X!9?^@%+AP&DC+po=feD3w(yEuFX z`u`R^KIghJ|8V$A(EpT~#;sQ+5M{TE-JeS^bK z2mhzf?^F3-Oxv2s;X6V6q0jGAeBN6>jpgwBq5Rx4^!oo)=39fq4+Q!2`FAS6x97cO z9DWSo>GSUtf1rtHFo(~-Sk+$-**}EhKbiB%GaP;y#-B8O{=1%fdnSj!kN1z^djIYI z@`u$p{7h^=$n(ck|FU~xMsxUG5dZ1(#}xlvfvRl$_zBA2BF~Rfe3#7&_Hpvh#wpChpUOxBN6e4UMJ+3}k%p#7)o=P5p+XXTQd{=KmNkoEHve|uBZ3JzZb`#+Vt zD@TO?6hE?6tI8a{;5G%I>(43v=*mCX{I_Z-?q5XzgRVcP_<3`Vuj1rC_LjiPgRH-! z_;D}&%i1pq*B_GgTNFQ~*KgA~`89r5;=N@36~z~S{F`DN{$o{rlp;@(G;${s8kh)ZY_d{CkMQo6!Ecj_T!?sy*c@hxfhzvaq;+J*LMm8d$p* zhp!Lv>G~^bzo-uRT5$NQP=Dz9DT=S+srn*^m+#-Sg#L8>6vZe0kDouC21FbMoc;J8dDKuAid#wR^4%;qdbPogRUw>z^pTbzJ4|IeZEf8(n`y@vr}Ok-h)u z1?5NAA5r|@R`1t2`So%9nyeq9_zN@6nH;{0h(Ge6>yIe@+nHs`ad_YROU0uJbo~*< zH~VZbD?bk8)AdIb|Kr$NjXC-9{il|YPuCw&{FZv2mpQzA|EVqT9Dc>I}A8j%G>wi>!!d2qmKj`nRZ~QIf*Q4u4T7mz=%Hvf)m4dqI%DUSyqERkd-91QKrHVWTz*xud>L9S~gY9mjkJ@ z@Ri{)sphL@78?&t%3{!Hb70KKEax%Z64mmsCs}L~u~oF$FB9Bvc+Ak)R9Qq>z?|>< zR%-RE+wf&9*Z9s!eG}RZPVs@GRgP8VsF_KhGI-$NgaL`c#pN%~fz=Enn$>$ia^Lva zEL5pOecuT6p&G(svnDKBb}`H?{il45$C$<_d@N?+<}|il5o_YdQZOl>brKe}MU`Q24jrpC-p|g7E&w z_Mrb13g7wX-m?E4gkO#G2XOy@c(em#$B6Gza($lg@U#qKfBF7#o3TIUAMw+}Dvi#Q zGw0hLf|u_f_hbX#quA(ELjD`ne^!h3*H!8vBdNTSgnCo?ce|}NIJ|uSwWoLvZMKmX$TyPW*o^Mw3t;J3|~>gDYB!ek+Tez1JBBifF>AxdDD>B-^+-r?{M ze;`~3;=&wv&O*KwiVm_FL&>ros&bK6p7e{2U3hv}j>j(^7CZ;T7fzuAhn z{}0Pj<^5Zb{=>!ig*u@8%klUlqyA{g;eR|6h&S8t_{g_^llzrSYxc@F7191U@p8cj~I^`|4We=7QlzQn!mK0?G{ zdbYw#a{eXAe_WtH;thC?2yvM9zT1n9pQ!|o&w(Hg)3jpq4NiXj-75bm-wNZ~&y62E z`80>$1Lco+e2xU=V%qDS#!qqh$y9%MjstQ2)Vr@gho1xOAIf(Z<^A_|a#;_DUqs}a z!_|GLU#>EbQ9d#D3WrZ8cpIK$p!i0uZk^!pt0DfP{-OF0Bfi_;nZp+c|3$ndUUM%G zD*x86_a}1r2#DW)d^;Zh!uz{^;_%|FS^phzK2U$$hnwQ3?%6EYF9hjt6930}>*rB`rx`Tj&t z{a}1%juh{sU6MMA{7R_j)reEI%7yHO6K zgB_TE>VH>OojNG|ZwmE4`Tjf)gQxQM&e@g1$@jfKuW_(^>VJ)g-;d$S-}nBp=p05G zDL*Q|N8w4Ve0!$Qe{@(7p2}aIQfVP4U%o%bV+G5n_2Zszd5_vbWs#*bgtb+~c%BQ!)AGw@T>O#Ke}a(jbqDKD@zx`$XF2=( z-XGA$S^g{SdluyIzWw*x2IFb|hxQzCg3G^e|LP9GcxwOnQ(=2K`^){OZ3a)}KkMz? zkdyD*Ke6^nyFR$0X(8x{4g#4iKYq@`qr+=_~ z8o%8Ie!tGeZ{PksvB7w%|5F#+WC;DI(*A?ozsF+mXg8HB!>ILEorzre$^Cn52A`>s zV(8PN=>X3Ezk4inqlxLv$~KTZN|qY4~*?^ie(_a z$zZX=Ozfysd8#JiLo>u@nS+iO@{_a7ZTA<``PH+`ZdM!Y4T@Tl0liG;>BJxLVQY=Z=^BBbbpLkOX0JS zRE9k!yf;LS56+Ar{qs4K#N~Gk1NUd})Kd4?EQ#jJlZBpW$nh^Aywr+kzjTPqkrwh9%K_`M;b=a{M@m|9p7`!f|V>(SNhg7bpH%6P7b) z5P$s!LU*Y@%xlnn4m2tU{4^osh1{I}m);SV-4$g3y{=?lIy#J_&;o`uE23|x=#h-ozmPJJtxD$!h-k@i})Z9|N7Ck*|dLy z{0azZ-nXu;68d*^Ki<{%y>+FFFKz#|NbZS0T%QT!o9>C3ZNBe9^3N(4_{9u6R<0kN znFjo?oa^DcV7ZLX;-L1qhzn`d&q4qDlWz_r7SAcmH>r}+ANeOH|3n#KpGdR1;&=a|>@!f>UmW;+qTN)LZ>;({ z^ncm`?Ra#Ew2w{f^Q#gU?Ee%`B=slusnf3JA!#3@W}tmahp^88!beYs5dP!86&UaH zf5Kls1ph!v768DqXf;c9lT=zZUV+68?|R7g`q9ugsomFtuUKAo|-&->fb5 z??Ci#7efDdjx@`6-T>_lXtMt#8g^T-0e;+?$)hOvd zF5*8+p#7rti{hz_Cgh9fNAId9?UO<3*B=|zg|vP-@!$6ynL)%gsAm(Ye^lkb@_qV{ zi2b5`ss0pC)HU3se3zX$)miGFPRh4`i1JOMgyeiX;omVhdAj8H68|b8k#}f9(b#GR6Rlc^LD8vWC z@2*})&JP9gHwOFT{(zpxv&o-3Q10DMg$GLiv`BpJE$Xj4{QCz$`)K>V)%?SO2}p;h zRFUfoBCV>*|1D^L$9=6h@cD2X;=dz*jzHN1Z>*~-%Qpk~F+M-6%;A6j%7%T^ocQVQ zxsSDP>ucXhe9MsaOB^)5*}iWz|FC@Fl0DB}{8rc}sQ-`zL>_p43j4S4bw~l>z>n#3 z*I(};?eB*6B?mBGe^WL7`W-}63k?x?hno7m@v5cZ*XD$WC6Y=Shb z-lUdN|8$6N6(GJ{75p|6-=OzRY5nJ|{GZf^vc>KmmHuOp`0zrA_~yi)xxzv5A3rj^ zzpP&!Nc(HO4T?bdX72Mx0vOS^1JtWlkhZjiGLB{Z*}pf;XeN%@p%sL`sdt0c+i=u z{h43)p1FC7)IXwDpnskUp+DUIXA+Q48_0Rl>a*5K{WFOEH$v1eCw{{SjNhI5#Eq5w z>7;x|L;ni*_w>|*3TF`o$*B$JnAYn(Q0~7FX#>9j{&`pUzb*Wq_CNJ^*Mgk%Po_Wl za+1^^`?uJ?+6d!I{{4lcl>Scq7SvzJh7nuz<<62njl`ck&;h{xS1quQBR>*Ddiq-L znv&ln_b>ZI{qhL=(D?7fFGEV!QDL0 zf_?D)v^K;aC;iQ=%8So^pML#&S-)&z|J%@i!~F}e|LnwX5&rf$H=X0`AKnfwB>f*J ze){|8uQhm1#!nZSUpO8jeo{P9*Km<92E9_Rl(dgU;!gvxKW)F9_&tQbldD&Tw2wjV zU)o{$*6QvY9JL7OCh2I0@YQz0q; zI!Z{+Z{958`v%jn50}g5vpO0OpF6<#`5YNPbBO*Syq~~=`*2A$7+vaCcZq>*Zx|he{v&){U1eOh%^e~b5%%p z9&OxSwvRWUe>fT1$D%?JOZX2RA9LcT`qzqk>V0V+s((eiki<79esmv97et+^AoQ<8 z*LQ?{s0@JbC$Va%_64#X!a9ag9DYIm+j-~9{p{Kb`bPeoU)&FYvbHw}F2Q%zr()R1knx#nqW;Q*_8*-1^DCJD|Jl`5rpfZ{K<>Yq0Y7bjo%qo{n7+FBL;3t>P<$>X z;GKeuss9uvczQ~Ij~Kghg3o_Q`(DvfNa{Zn zPx(iYFQ&Da>AQb-5&O@C{sS#vCw@9V^S@5H_euXrBK#{u@T=Y>Q8LTvn^``~kHr>m zf7j=qgnvf}{-Oj=PXXrt`sYdG_sQ}#%69dAhwZNn_qOmK3+4xHXdjFDk^G0LRw=|Abje|mv`QvacNLTB6}U!=7kmniu?gnvi~eu^h_ zLHBnrbT3oQ7yrrq-6-HUNc=BO3CTJAeQCzj*QEYwME^RL0#f@>JfSyC5|BQ?P|1>YF5xT}=ZLvrAC+<(`f$@3q%2Mrd zk&GV>dUC=O(*L7~eQaCNc)8h^c^G&#a94q}t9CZG=r20DK|8(GA3F)1+zscvPBTcAZUf`c2 z>~9JGr2X4cA(_<=i%17+OXPdu(mrltpDrQBXR>Vh!%A97z8#g(V7cT^BmAX8*uONv z)6+8Ki#;8RMoIs)NPO!r`nU4H_tRK#e?;->s?7hXnIC%RC;+Q%g!;=dEW z3H+E=E%342pC~d4%6AgnpLP}fPg|5Py+75DUKs}RV!Gv4^cKl)5&kq=^#27vK6h=w z`gSLNy#F%bxL7u0xZn>OU%gpgT|n=Do%l(3Grp*N_AhCFdVie+9os1@Eu0p?!4XH-Q)X z86CR+d`a>r0smOwZ)yqul=dh0?@s*Ge-1Q`Zo&CaO>zHaE%F}6 z-&W}FJ4pNP#83Tm(#?h+3Ht>3XLsPYfFH*P9+ZF1iqDU7{OPiN5%!_|D<}T^kZnv# zHkdnH#(#Q$JubxkyAwb94f4PC_n^ko{njE72qIg1Qj3QsuSn%vtsXx8H-X5a;a^nAj z@FzX`bV*shH%R+lH$?khj^OEOs;Kf=k1cs79Fh8ah<#cEKfQl(;-~ZPuGtlrOMW{4 zzGo6>LF%tzXVs4RjQT^mVNaDiGQMH|ITiYEYh?Q_<4^ocJ`!C2X)=@nUlIOmzqOb9 zLk01>z&`kXaeU9N6TclG%rNG6+4i!u53V1;@rPJT#Ag{FaC{z~UvuK8zb|^}{z~cp zbbdKm@XNyn`&&@IOy9Sfe`p`Lq>-!bMH%1F{-}SdyuSz!j&G)hTK?oAzu)V2ublLM zgWNxk4B`JK!P8TrQos>5Ie$~BzYXmp>OWrSZ;6ZP_)qx&bnpq~e{^B%J(Axb^=p>2 zk2q-jcjEs_!TkS^<=Qh!+9!h8XGRG7IPp&>_USvWZ5`>KUZVeU;HUAyiGM!fzjDla zR`N%|{T7f5L zuz$L^zcccR_Hj^mpCGG+awsQ$v`bY`@99&%=Zhmfz&{oEU$;d2CHU$6ofCg6;Kele zkjWdQ{nBgB@mmX?k9OiG)yw#C*qB-}K6ps~f4b0L9v+B4xc<$FpWYuG z%%7u?tY7s0Xo2OU51LR;Pa@EoWA0l0sm{SzmCy8h_sm_6@!yG`wr^EmtP(HF zH=Wq$I`Gr@@5G;FqZXgd9xdfIedC`bKBq(cw~75J-fwvLfb|x?|0!4549S0k=)c}l zS5SV6M;=UX;0H*TUH!YAw7;9g=awPbUnhRKBqrCL`eJGUdf}Qh(Y%pB6&@ngQtGlQiOB8@?R%tmHSz{N@hu z9~$3i|AtDqL7x9LK2FLZ>u&@ZKRnT&y-OgP1~dDLwgKO=4Me;l80{-KEdLVtSyo->dx=q!SK^ZTy7uS)&t z`s7UzKk5Co6F=&WVsdW$M(*Dqbbq~D^5+xwC*#{r{1y;n+M(`s-}rL|wD0Sn{cR=s zKbGL9{htTrA2G*}@9*@FZzKPkaDPPS*RsnG*)S$sox-JkaQqza?^YjCSD1TM|G|L% zPj>kmgMi2k-}@IMy}b#pe^lB#x2lY>!`04L&wkTfO*M5=pX&J&lJ;FY;eW7^J!k?J_PwGQllcHrg z|NkyT|G|l$_D`zJ8kHjLPy1K7K2h>%|G|l$#-}Nt&7C6SLk4NzO94OaKREHHk@n@Y zrtfc;_Avp8<1F{VAU6e~+}k#r}S)jIVts?eG3WBK}DI$^DDke}gzl79pF6{Ti;Rb>8Qn zU`P0~L<9iso^ zq(9mn)HiC5?f;|DKj{ADBJk7xj}w0fAjb6AV;6q+l`pjKcfdY9=Zf}A)Gylq$u55@ zkdyLC4!Qm_(kR!}_stRDzhH^e{K5SPCw}_-;knl)`R?yX{Qnc|L*oy{gPQ(?&L6J- zG^MYMKS|{N@nxuAw0(5qZ{nAvKJ3i@XR^;-~u?jd(Nf0cjr>S)XocABFOvBDPh2ilf64P%_d>+yrl?qm^cB+db;y*>UC<5v~@K{&~cEh;%Z{SaB3QX-I%QuR&FX^_rj`}~v2eOGXv=5{|SByI1 ztH0o%5m3HUgnxR3{ptOq6+}qBFx7cFOmzR=@z=k6{Lp_kVSkMMzpLxK(_s9wCA0&C zj-FcNi#+#!tt{+jxu+!r5+|D2WM=i=}T1iQofD2i8CW&XcO_}~6&LK(@Q zMEE_n!chBDd?vH{R2&AlBOu*9%GF)^PZY$52-qLyX=$HFgnxde{Ko^&r#GjBF>8MEo&qF(@p%d05}TuPl_kz zFsS}v1FFmCmxJ_&*CGeSPbdCoh`_f-?;Iob_YnO{h0vek37ru|zNkFpq0goMbbKSM zg1V5dA13Zc*n;PO&*u&E?XN}0XN-mlO2=27_+21T)f&SX_v9~Qh5nHp!2iF6{&`hl zA6xi89p7`}r}nwDv-eWYJ|_A<_$NO9;>2H(SnTfM&G}{gNhkfQ#Uc9VPW*V^f?^JB zKJFVIFi89RS&06*6F;3_NLiNZd;XNpFZ?Fv7d*24+N$D@4e`x4zM<(P()Ba#PSy3T z53x!792O!zx5SKxNL>#F{t(@_mT!Gd2C>iN5d9Ns-#|9QSVZ_699>sRw!cwierItA zesC*F>pxF|Pndr7y}OC@e;4@wJ@Eg8x#GeC!v5s>@jQMe{sZz3f}ekUz5NhomxscG0L~UYZ58nuS5916Mq+yt@1CN_svhy{k47y;r~wjCMn;V zMLugOChbe3Q|clYG-Mu_e=*D~VD2ONePx-G)1-aUiT)*^TSE1B;$KZ-fbN&G(#{X^pv#7`LCz;-VSWxz)Z&M74a(Ps_2J(4M0^wWaaRP3L-{)Kdlaqzf6Z6B=aK&7CjN6! z#D81xQ~z<|xBSfN!w-jh%l$)w#s@|;SHD}N|Lnw1e{Xs#YJ;r5bbhlUlrOEn6i?I* z>VLM3&No+XVD7kLVACZm*DAX8hkMs(r?Ga<(Kt09qLy}sK4Wd|9eFJqWu#m ze)@assM0y4{~M(L78Sz(DW2*NF#p$kx2;_&?Vm*K|7r;TcjAxpGpi3Dw`p@#`X}9g zY-R}mcj7M&{8&%S&u*R8%k^HUiA(?t7g9uD*OPduV1 z@8A9MQ>0K1jedmh!Ny-{ioBTiUU0bs$6tE4Lgp}(;+_Jk9X4C(TX^KfbZF!g?EBBF z{}jlN?XVsG5>+|i9C&z3>+n+u^D~jW?)RVN_{W6c_xC3P)%KrJ z{Y&i|evjji%TN$3U%!3eITAl1`9S&C9LzVG<1er;1V7vl5L*8!<-b(zvYdYpvVWBj z{10mXsRzET!SP!m_#f2%3Kz{#oHXT(Q4P?zcqx=){@0a_J1=&CRvcjPK&9+K^`(2f0G6sI4 z{C)QnU&rygV15(%-Q|EE_Gt)Y%{-(0eVXk1isK*utHL0^r}ls2Pbrk%o8$jso8rgv z#d7u6gG`kJK2iN&O?IVo{MFM!@IPSresZj`Tt6ICzRl+G{14hcDchuvoc|5t?+W#o z+6TuI1B{a8gxcp`WHC8E62#x-D6hY3%7eAPAD;My?f=Ay@n?Ai>HmQ3`zMu~4(9Yv z3}K%K?Z4Ik>vy^TOptw&LfFSM;KAZUWTicCa{NueKe2wH|6{7G7yO@=@0W#Ivi9HY zR#>#Zr@zwsLHnO|?z-gq&mj9ug8l>YqhCHK|C(7JY~uLWi}6ppR1W`g66E(hVE=0A zxju6Lv>^Rog7zKtH`}ZD_Mr8*>&!l^e{Li8LBD)Z`&9oYht28#*2=*0#eTzs@=t9s zXAsAqXC2S~p!T`G@bG+&e>LgfqW<_izmgvOpZb5|lwbbk_@|Kgfc)5x2xeB_Q2rAA zx|ZYkPrB9h=s(E+p!Ije{72;b6NCKoHo1TF^TT{ncJ*(6XXOQs|4G`v!hSiXAp}tS zn7^)&`zHkHzchsXtvL^N|M>p%yPxCulS%*FZy((FFPr+m^HtTW9RCuiUl^bL{18V& z&>Pe~o44nZ`xgb-rzP}1gZLk?eE%xH;1b8b8|oM3$9!a0|7KNE<@?cs^#23m8|8n{ z^6gsuwj4hX;_pcO6XU-9fbqZcH_Jcd_$&RQEa2B4-!qJ92m@L0J*Mr)7n1SQzdju2 zcTP@%3s0$a%SLt8{-S@tuR#g&VmhT*MY+FR5dTAP|7a(}xYB>*&y%CRZ+;5L4^e+x z&QFQMdtbGS9@szsLG|xf?_v>7{}++}tm3z7{Wtr3p7znh9RDSJzMu&7d;TMT(hr6l z-w3jQr?-`TJe{Gx49_n-sC`DR`BKjR2Jyep9QZ4N{{N9b|Kq>O`^O;uGT=Y-`3oog zJxVPj9n#;nCpXK=UlhjwH^Tf2`i%#kk9bi2BZZ$2Sl^5MKOa&G*v~2ZSn&RZ2j!p8 z_z~aw4fKEHud3YA!1oS%{v-dD5t=X2D~5nLG}Oi(~ENa)?dD;e})bA zfqkQ_Z1Pk2DN7ra@Xf!F=M#s*gWTBPw6n<%YWbhD$v8e%&M)}&KMCtw{@tVWH=w=s z!2aG;Ke)=DAU~#C9=V&5l^@^#b!Vr7;JB61Abar#c`@x4-X`Gv4#@uw#5Wx8@%%^r z${XJ9%IRNtm(m}{l?>Rw^g->j=ilFC|G{s6)c+q?pW|7mDguuGKPdmu+flD@{IL)~ z+`mBkl1=?Bm2Ln3xeJcv68tl<{^I;s{tD_6?2qI8f|LHHpILpt^%X(<4b|^>Uybj9 zapJcK|7Xk6c60g{2Y&i|mJ`1X{Fv^k)#V0f|8aBGg?L|W!?>XnKTO9NH$YD57dOfN zv)_N(LHs`t@!$J7_)qkIt6w8Jyb_U>AN{i?`u}F|pC;MkH$iVqo|)czhj0A=iEodA ze_BHouLbXqbFz;O#G@eX+If1@tombo>jmw*zdsJoA35=(-k3HWKiaqd3H48}t*-FE z`Y{{UpF8oJNDS%n$+bM3eFmfc&>zQk(uv<9{J4IG>i=KY&rtp;Q+JdW_792=-J$*U z-&a68nO*%+P9^Jt+&|gx|JZ;22;x6|zey&4%jzkq$-`q(%~#Eq=J3G@sY$8H(Ppe! zy?Rnoa^iqm@?ZPDNzW#vCXbFb`={;|pCYliV0OBB;LW{@CD9-fdo zI6k&kQp||Hal>PumfuF317k*J{xmSATVnE1$<(g;fZ+)-RfS%I69(2ad%hf>*grO| zR_D~@*uL?d6Ne|q#MU&`cTXoK^dFo$I5FW_$=f$IF}a$V9Ge6_2j@4)eY1OAEp)NiuupY=8$2KB3()ZFOve zd_Pc7{VH}`tWWjLFUrnX&c9G zD;A;3k@ad$|w zYyam9Enw&Wx{~%6^~cl`Vjc(kVVGX%<}NPuFPsMVhxq=^s5eAEts?l2$V!^Syban3 z?SkjI_MMw1`iF1O_5Hi&8HV|SSif67uz$F|lp}(!+PnZ0c@)EYpek#gYu%T!fu75jcsbRP}h8Z1fUVqQ6f`&1%nzNk2IAr_R~j zljGkc`N!z_J7j}@VTJkSIR4vWe$?~2ov-DX=o1b@)|^~le}{WZgn_t)uyKjS&c|9s{+05_~KAUQVHKYkG3IjL_#yTK`` zVnAki%IM(l8^Ou{S!J1K)-5yLV5VF@{`@Jehl4ksiero{A3fU$)XF$Uxx+(;1; zGlv)a@g4I&`*zBr9O2;&Y5bDo3-Yk}WUwx0`cY`~7-{<+qLe)ufM;h`E57Zme!hdE=;`qnI^DAD^ zKdnTdpQE27mC0?$KRl2Z)31w8f0EQ)H%3Z;LXIL%s*h6+lbeR}3LZiqiTkQdWe zi#@r6<39!SZ|+HI+$ELpClwL>1o1Kt`zKLP#UCEXi|M!m{nv2(vi~6uH}q?C{Ses~ z!2@~y>GsxFIeyvy@Ev)SeM0g3`XL(rDGwEh<@i5<^=tMjo*&ECwJe_@|M2kZ4PSiq z(3*ITzs)Ljf%y^7U#GPC-CCH>kbihk{wH2J`8mfQ_a*R8Q{_wTQ?jc1-E%pwA^-58 z{O>GZ+JfUx67g3a7R=Akej3{6#>4`K{KJFtzqGIFWR73<|K#C<@}>O#c0}|8eSJZ7 zc~E?tvbjVfj$iiwXF_YQA7%JFX({SSGVg_ZqjKhPHOjXNm+=k5ke zIDWCCx$p3X;C~WAf_HdML;m4G`9JupQ9+LX`5nL?ulzHV{+8&&$v-?Of8V$+aUB0_ z;CGea?K7)BFb)#j@(&KmKQF1o8ytVqM+#td<@smJDn(ybB)+2jt=@6v;`n|2dvU!P z&f6zzKVNC0{!{*eE1J&d_%BRR*SjLsxDmB~&g#N<#W+7bD1WLsdIHBECF}1^QGaLh z_UR>veDeqwML$UV;X(PA9C)@R$N$zhfPnX_Q2l2?k-Fl-4Ecu#Slqg^8 zmO$0z?T`E!<#QPF4-d*e`qHFR9RGFjPj`9VK8?y{&;G9_-hYeZUosUm+R5|ZRK;$j zFUjfq3#EU}bFCvd{=#1=g!K*2UwpdyJ^e$$FaGeL_DTJ=0$YC@y$gu_y!|8Vsoz~~ zL<|>ycu@XZWm>TDt%2x&%fmaH*MDzu^?Q`K2aWa`HRtE9Df|Nk0$J2;w~S^kL_

2KfEF3#!ytTMY{*5moZ1f@kc6BI&_7t?mhDMLB_cGHwb z)-lDOPVE1-7-!=SO85#JxmLS9T0_x_l| z>7Rd@Lb%^k`crp!@yqLClXYhH>KJxxw9^MfASij^rfc(P)c`?0L=9paH7c_pI>nDYH z$XBbx=W|_sUsy*}isSoq92VjI!m*S@+&Vb|GLYEf8zKjo>d6S?=(+`cENPP*?G|%{~l@| zd~OBj(O^9fod-c)Ok3EE9^?2|gZ_T|6MigLC;m!@V%g_AKYd$9W5e*bd?Dty{Ob~w zIfLw9rtPj0oc;}Aeh&HRybV1E`XK&!>lalM<7+|wnP1+YmiEv786J>lA`zBD1-<`_ zvqo&@_~XA5@lo{q!21msln3(Db&DuA)1c~`gXLfQG>_aLi|k)MQ>bNE5M_Y#X1MQx zDi&jMGlj?TMnAvl|K$7df!CTU`RV>@UBvjCNAjDHe|SGI`duh~l!s}qZA;|(<-#_Z zfBJl7pdJzBl^jqXqmm)-e6zNbC?C^@yaOH;{0->(yDKoi=zqQho}0F#RD0$iVaQl0t>>)XS8N?qU%2yu9kMrJE9mQ)NMgDLjO~c>q8B;#rAH-h|#(zBU{Dgnr zwXWiIeT)3Xjffm*wP5`VJ~ZqPj=vxHy8-ivxQ^Td{O)6lKSGS03x2-#*x7Uhna&{Fw$qUQEYb8FfGx(g3-B9s*zeoK=|6FygeF~x7G5!4NZMnWJh~Iru>5u$2KBv)0@tS9l zzqrxC|Bz|0f36s@^$N#d@<+vQ;`-=(5QiEoUe^!EFXpZ__W7sr<*6KhnkZi@O6*?< z>!bb8i2=X+N5$`C|9L;|m;09l+2{B^r9ZA)G2l5m`w7MC^`idL{-NvFyn>I&{#6iv zf>{6IJD{EN;&W(EDqiz!_V^2jH~)p>-v;ZS{Q6gcbt}<|*L5y?{JnSgSjF)_1?7wU z_#Bi6_}%A`U;0mI`_#&K<_C`db$I>~`Eh->-9+(vf6gBN&)@HD!|^|#7HEHe{C`UE znir5?U%p#6yV&-{_#luU*PR(n6|d`J_V_3E8&-hR|Cz&q{8*1Y!0*0<{QCI+NVURp ze~uvktPSlW+TZ^iMKi_gy^Q>(k)|o%O&vSQ{&^7ppSuI~$L9o$=8D(#EAso|L&)~a zov+Gk9RGopf&3Qsvw+`y4f*xudvja)e>nc<(LTrv&!^fg6tDNXN^=^;#j`4Y^@HNS zcYE(*9KQ?d7xt44c#hP1TJf5{sWef-0=gB9fdA3^q?Hc#0H`O*K4XB4mN_w4cK zPF*FRZwTVA5A7pe@8kh~_aDfgGoXFUVk)14U7rb)>w9KqkoQ;YSTEv#bnyG}Oel6a z9w!g%$Evf;_6yJ7Tr+OB>_5}_>k6|3e>+ibSiaCd$NPa${3a4ZIwbzeVzGWGXn)+6 zg5M(=RvY-Se~9={{P_w0{+T1|`1B|HkDhHI^dBhfgU>HQ|NkMtX9U^jMRwUhQcUI} z5h+QTp9dyoc{(E6Og5|cd>PjabWZIXGqh7|Ok#3>xpJTzOFSSU%PIr;*oSzIBiihn zEQp6Er1TvSTdPB&cmg6;Jd+}pE|}tDy}IJr2){a#xjW08Iy#GPeZ-k~;3Oqk010j4 zfB~8L@hy*l)e(}q2TPsxDxs`=1|ti_=oFFjRJF*WVSS-&mK-J|rnXLK727vSlJ{cC z`wbr;tq9M35CdmsTx2FoIrNQ-gH;>Cp2A3yUUy)>5Yy|W2eIq#^UqK*>mfsVysI$u z-^lt%9EZRZc`+@XvSqu74?*|uJAgkYRQOQ*7@tD%@2L}ajpL8JrmP@_g?)#G^^Bz0 zzF~@bW4iXW)x)^)fzQSGza{Rku|DDWKvj^3eAq7I^Nsj3rj57!K1jrep!HRMuM~3& z?_#tO_o-1}e*{8m+3EwvIZRRi%e~(JSLh$KKFJj82dt6*XYNhlt(xBd@zrfk>Nen( zuv6+vIEqGPH!2MdLZeP9m8PpM3b7SRC6^4{gj13Xag`zSMkORyrVJkrlFZkf>GSzN z&sop=oPE|^yUy*~@Adkx*XudY*=rBa`+3&0)?V}0^E+7z?)!$5{g|}e9gX$#@r z{Nj#S2uv&Cv7Dg3m|4XVEuRl`~MO4{4k#*gpU{;1Aq%T0@(CxEbX3(mu-9 z#!s-@C#)RB^T+o7=VIIQALjN~pY-!FPKf)!_xAjeuO$ZBp3mct-q~q8$7CE5&jmN1 zZ`!}!-93M^*?;O|WdF; zaqpxY^=ubtqErDLo8g|z-(P3550AgZhorCfWE{r4pT~VTz%G<)tc7b>`Dqlh(0y9~N@>;;p?jxIcI`lJb?$I=^xR$(Szk$Np)5~+Rz%5~H98?bP)`C_%cDv+n<>#g&%qGJDeO>O@|dLZW(*dOJr_dLzNBZ@Ysla~1^CJx92 zul>c7|JwAw>vnx-Vzt&kOuqj@b;Y!=@*lhD?Tc*k`TfNK??ta^rv7_-kLkP1|3Yu@ z;lJ*xZ$J^xzBei^f zPuSAm??>e{(ca#(zyrD9J%?^7u*v85JBNhSOTy&yd!A`uWx4Kh3Ux zX+wP;zkM3fzkTrk!)6>@%_iTI`E&Tbq^W;?buGDQ9K9onoP+eSOaGa|0_+a{>ETny z*yNuj zm@nmA}q= z)+V3pR~%4pGt(dCZ?c|6b*+8%|1--@deJ8T2dY1E@L$&DuPJ}G_Kaae#{@DV?J(8MNx zlH`BK*|YzeaLI9(*yKm<(NBoIYPWwinZM_rMm|FH*wOm> z2>oXY3y=#wf2(VqzgAP`*Te(!2ZQ#9|CiDlEv8lE|J=?j_>}Y~3JZ`6{$1YJJ#G5) z`Y;ah3iv-??-LfLumHK>B`?2vxJ`afv47%qyZz6U{GND|=5G__Q2l^IjQ%r)1;_<| z;oj=Tf%O<~IseVFit6l$*y>xn)9G#<> z)V2CJO8=R{0_1|9H+befHu)!S)K7??uI0zP%=%Xi65%hcq@1b^lEm}%y=FPer(>qD z0J-4fkGR%4zkY$tFL0uJmLIQ1?F}NxcdIv0Okn|X!F$#pG}xy9v7hTFBo=Dq`Ci~my_C#e75WDl3x`v^<8K6DT5YebAGEI@DYOJ8}l zvQ7U|l)r)byFvMFIf(=E-w99h3@DHbzVhz(N803nF6EzFpzZHd`5u)2(DC}Q&YcG; zrcWGw99easVhRh83qCVi`)8Z{6McEYTD$x)^uFi(UiSWhNt!%Dj^CqWqObtD;1_T1 zd!9|cqP|NG{`)%pd46zE|2|Ux)mJI?fV9UW^{Oi-rN*W^*lg==r;MFZh*77>&g4-A z!;(W#Bg|2x$rGkrl02PxVnp)l@?C5wAHiQgFSic$-BZH$E19hxDNNt(d=z`K=K=(3lVG$~;$$li@OxLvQfuWB)VKrvJM8>CVHc zyo}3y@_zR8Pb9Pir2pRZhg|R>-xgQ4$^Q?@cb&9+FH8C8cQy6KG30_Dm4AQ2CckQT zy5p!yYLYMh-`}o3udC(&{jsiE$Cm$vT=17Z?fRHa{%p>F0r_5*@_QajGP`A;|Jp3h zE3?U8O!B>ed?nu}f!~9h`kzkn6DMaczv;T(Z`$P7C;R&W`CgXtvF@b2<#i0~4*q7- z1>f4_f6K|;B9Q(AvrGSl&8bdS0sH^l`ms(n`7>^(^bg4QvXp<@>6C8Wv$y|!Z?Bta zlV5}CH~v5xzxYi2U)niHcW`d^ae)8tE&rq31AqSAgYF{v<>n7fE24)EA}MA$=MGNF zbH39Y^P9Q-#R2K6kIMf7C*)Uop?__XU+(**+`jX#&QS?jzr3gRUCE64srD7O?Gkhx z7EJkPT>T1{zvbj}`_7$BYmr$J+WdKWV;e>>u+8WvAF}djHYN zbPv*PfUIle$CN%_PrcJJ{lFqR=5Z8+5;`^mo&!F2$AUtef6pF3_H9&2{j)*)cj644 zzmapm{}F{85fr5p4)q^|`8ni*dv)4hW|hB$%)70Oz|JIGO zl+FInQvOcdq4&oM+8_Hy@N>(?*Gcso@^R_7I%@MF{7;7{oi_vW72-ShS#u!izrdVd zSDE@xW6SmYfuQ`-*KjBChbyaVI;M~PHI-GT6Vu0~bZiFL9sC>rL~HpzlJb{-7WGHF zx_*;5%Wi+pcO0DLFU=pM^yhvZ^ruL4ikbR9JLc>Ek^bfO_v*ZW^j}GRQ&fHi^+)~P zUrP6{%9@y?Du=VoT3r3BmDR4Bo3={MuW(vU7SIyt>JQA)z4qiDzGDdS|m+NGl=rZ+aC>2U;c{HROD z4jMbTVA8038N?XcBW`H$*5 z+(CNX0hgme`8+-!?4Loo8edymF`;}dSXWyyAwVwpfJLVqZ<9Z8wZ6lvp#0oNYbz$m zFQflV2#^b2>De_`+2mKdUEkqXP=4aU+KLJC{fBBRCIrX@-}2u+ahv?p=gA#sSIquJ z57bsnkY7Mpm=GWr{IjN?TKA{$@78y?6_j64Lh^_pKSBSQ5Fi(P!tGxtZ2G?=^1TYm zFL}7OVuJjlN5~#T2#^bYa=(whv&k=|{tFI%1?9)q)K*N8?-CX!1jq$%_R!oJHu*e% zl!GJlGh8n-|KU9oE2e$!i1z-TNj|RJ`h`1IE$M2LKb-0}$*^#6lEtY%E13V^tEo>% ztLG{v9cu^NGHv z{=>LS`^C|JruEcc$av!GbCVP%e^cGB&auho`V|LH>UB{sQ~zK+i?74@EdS>atY_&M z{R_Rp-~01F*72!URKMU5YoP6K%7^~3f9onH=8v68I7d%!nRS1{Jdy7O<|hUHZ!1j7b5V$FG=4#Q;4&dVF8JdwoMN4ybXKvx z!=0etzh?Sx=lNiyf5|`OZ$zJq2#5YNp?oa)i_Q}vKrZ-oKizz&&Hmqs|0U{k#v(f! z`ERQDyQ)iROZ1AgtIJ}fGg0-vR6nEfo5=Z^!sOrd@S*S8U2QqUq*A0n7I6Bd*_m_Os(2zfl>O;gaEnVpY2>_ogaHH z>F=@MJZjH>$^F?#UUY+|#I=y1dE}JLV;n&)_{@5>t>bg+=IcBBH|*u_dq?T>agxJy z)lf*#e0~efOS+Q(6Cpq@xKsJrr)>7$Ao3HAF}-Vu@{iUMis(NR*GKq# zVhQyD5+Oh?_$A*zcaTl~0r%@W;%{pGxgQ~z{&@cubgZwK@cvbusqc;m@81$S$Akd6 z;LpW&Sode*{)J>1q~(_w`5*N^?q^`y*ZSXOcP&5Arhkz(yNYes@_BqVsDD%Te+r4d z)suwzO1b@Lza^p_lpi=0^r@aCeU$EJhW<-GxZx`5Un$pr>R(NFP$A>pt>v5TU)aCw zrFts*Oudx-i(am$JUWK`-3|3r(X;g+KGn~ilK*1u(`@n^-A;a;g?#lQ$$u&>KTgLU z9YcPU@RiXq0?H5YhF4U%p5!+&_h;e#k=+5*zj~_f-|}UjVJhvD0i5l^Azw2!TE@Gu zPm)BZ8012Kx99(IS$=`hznk3Ocp&Aa4+bUgb;s#+fLw6i_m~6ZpNsJ}^!w}6sb9zk z|7%wL5hTCd_;uHLRQ?6}cYTqMk`x^MyO4iT4-!S?GWsLX4g&z_4eky6pp$L=q5H7b zu$t0k2i7ryW4*P>u}=W5Gxv9b-r&7o?a|Vv|1(QS|0B}lr~Y_tf9MUKNNCNVe@frO zDE`PCb#P1aqriANxFAN_i#gboJTvuWA2r7k;+FX8*hB z{p%h^bxSFK{1ExvFTervACvJQtjE`9)4!(wk862%5z9Brzdvbw!8<%H-<5s{lzZS| z^0W2-dS71lB*`!L{^S0S7>(~>{r{f&KfDVw`#(zP81EnM|8OQk>-7E!@W0gC zcQn)g*`@UUt5HQok^VA{3cbOjp0+LQe>%$l#diNor-c6Z*l{%$+18J$Ii$ZsZ3h{@ z_Cw@zyMP1a_x)7oztnyt`gHmiz$Ip8jz5d^FE;n5;Qg-?XQu0ouJl9Tc5qYv@{hE9 zb-!*mr3LGMush;6elqJc*59;0_kYChr1vlM&r<%w--!P2WhsBw9oMq{ru<{6{p-5v z`2+4$d;T}|-yrFa_aF2GH}&t`cvv1b_p_9Ln!JCoFE`|ZLq2%v?&h!C z{QpCeAM2L(Ker$|{qv-}ik)xwf5-)YvdgFfoBU&_{p0lx=%1y0=&F#X#)53+nS z|L@pH7xYcbcc*2i|1CSU{@(R=`=?7{4dAx{?ot}xFOHe@Zz_LX8sARopQZdsBHw*_ z&+>uep^f+cN&1(oe-FNyYda-ir2bcEPybjy{ba}=Ddivb4S`&6$OWHy=D<;;f4TPGgVg`ykEV7E+J7PPqelDh zV99@GyCY0~t*hFcYfJy#-AI05{;qqm)?d1{6}T@65fK58lRqSAoy?NXRYzOK#PIAz#tb7@yV&%x|oD9~uYZ<9mcdF8B%E zzvu6FrrIBuf1x|FCVoZtodYkUHE)+_{ZS(TKOp3NcZkG$BXs{QHi$1zen;hzbrDtuK&!C^l!VD^hfFrYu}@ooR%M%Suy>8hyJo} z8RVJr&-(TKUN-sdNWL48U$T$#{k_QFy}ai=Hu*zIz88=mxoqF`SJDp$ z=^v0^QbGCMUKIcDuj@f!@^?8id4I@q`hUUsFCag1MaA@A2>HD-YtHW71RH+m$dz{|2*_H`J&Dn zhuY+SwkuP9Nd@J%6#cO;ohcW`KmPCUCvEZ{`zBL<DZSwQK&y-(M zLHVl^;( zh~vQzZf|q&`ZaQlbeKbT)1nNueUs6H& zFNyqk_dUP=S{yK8lTH4^T>cGD%a2@BG5znC`nUUTX8N1=$!mp1u%6Dlb;Ait!7 z^3f0Cch0oGDZl@man}0Jq+iJX0r`<@E2cl>yC-MLhu?ueIpc4yPuWM}jui*3#$dAmenEnd$2Kr>`kA5R1VL-I;VB7j8-WD;oEk_m7 zb$k!}Wi$2f@c4ddr5Y;nM|IV@y*{SjWH^WVdE8}`b{uwBPtC)Q(Dlx*)l>67&<^VR z{yp7T|7dfaPtpHSgU5BxOV__6^X&S=&+wj2%B7!ilK$ui1pXShK=`0fu6iej`hN>V z{$$2gom}p!|-43-d=I z*G)c^`tyX}ap&6)vHrf;|1PorcJhCJWZM3b`F8ye`AO^P+^qANJXV#W{wL#b1#$v@ z2RtA4d*jHY{bhfA^grGDv%Vpve~A3p`&vHiFUhO&rQg(%{pBzN$rT)sKW}oUwk$s( z`a^!vPn3^n9x3EU7TERwQ^wm8huQUaAJgd%d&3U}hH5kSj;`$g=J?|}v;Gqz{~nod zfPOAK_bZgYvVT70dU65q9CgNti&%fuzi*ZF=kH%82B*hgBMa^NpKzGYH~7vf?2q(! zH);JL7j_3PY<C^q8|?b4cf|iiKJOCKBMJ+^1%B$wjjj7nk6x{x;8Yd=Fw*yrCi;9?A4)HxZ^dz) zv+F4)%v!$xh3@!Q(i}X z?5rYuM9p!0FC7zoalAesT}SpL3Jb6s_@^%%eXkAQ^Eb*90_zil^(1qBW@=s}tqqJa z(Et{&gLm$)og~Z~Owch?SO6~Yrn8>=#D=ek#&0;A7izmY>i=2a#VnfPFjf`SH$ zNy&W5lBo?86XsQV^q=YPhcrHi{xgLI-~yjD^oLh%_LFVFRZQyLh4jD-`>_bA2XkF` zkM&@>6#8&Im?exNg7(Ar;mf+ynk}NYkJaTzq!-l< ziTuTx2tJ|n9Gsq-^ila=-~w;|-s=7~e3c&4_r_$Ry@@aPAbmdK(E2)}NhcGgrL?|; z{xe~{QS1nsqdWee#wS`cBk4P5WVeO2K!uUocUs;kS$a6ZPq< z?{~@wAJKJZ>EqbXH0PEmEC3hyEjx2Qu-UKuHhIECyZzQw*Y=3NS4A;lfA6Agw7)9R zm=pB<@we$b(XRr@qi@|ag$3XOue9W?!)*B8r1?e3Fh%1lG4hAG|7R|>tAnA2*}Gv9DSOi9etA6^zmMZ^pN# z_A|ZTX3#tkw^fdM^KrVMxRTo5j(FhvIC5Kx0T=jXtqUKu;VYX>&nJACA0Jy{$H(s# z4#0QKkuV4<5B%EZ&umD`(6F;5zhRuGrE}`cJ@Wm^L&*%EU3i6L@ zhwgf>oN4a#Z^=x`Y{9_o(#_QLjIpG0^gT@H@}Y72iFx)eS+J|dke_7 z;6I=H-+{LDEv52LwX2j`pAoy$Za=J#K>0U$A>BtfmL5g!OZziC+llPigysoS{*8B` zx(l^~U6;zkkN~@Z-+k)thi&$&y^{QSC_PvDFO>8nzyjcY(@gud6nn>y8Ipkj{Rhqq zv>z`~Sjep^4SC_G|8(CKgl`D?mx@N{_ZjzZ#NJ4+?~4mQXbz6`eXa;R2K~x|Gx05v zbjP}vvYHL85Y3vyBbzrxs^{bsyT<(EI3;I1u>R1!Tc z0E=|R@jEpxJc#E<`$uTIp?`eFYRYGaP<>eD$0oAG_s&;?l8^Qce3hiX80&3J9Kd77 zFS@jFHsdqp_u{)@60MNFSPvP+34`a!F?h-9Exi6H z)&F{>;A^;?E~G|Is{R*(5A{D+o*S>K{X~hKBwD?k(CLnKaSl!x9OWMPA?H@H?r++f z?B@sTe=D^8P)!EM`^^?Z(zU9)PLaLK>Ju={p-r?>~~{-{hatteLdux^C5u)=fQys{Fl$wFShhu zy@c{_=QO@VmiY3qK1teBkXzbY{8IBysxMH##+O{bqWV_zLp$ z8SDn$rOW@U>qCB|@=qN}{q3?oI(DDkepnyvilBdvq;#b9A>2P5r*`?()IL`I!B#P{R@2F49FM2H+^vj-haN4EBW^dvEO%b${)0UK`N})_z(j=NiJ}OW8Fr) zpS~XVH;{T4*7rL&VQ}CAf2Y}bt8D)F7d_sKR7%zFvqb&m%vK&to!Zu zGx7D_A@2{PezgPRlzsGbARoBEk-n&B-TdsyZuaj{orRt{cny@ zzlVH*lVXSmfAaCGHYU^8Sl@QQR=TB4dVY)ZV0QSv!TNfcR}R73P`=2zJIKd*a7X^& z{P+{UY|i*#Kjhz$Y`*|Lzk>L@X_@UKU-o5ijrAbF1rEQ%@pa8l=l-E``~SvgQ~A_{ z_PQef;=T;%a|1oVm-$C!ch^!Sf&H-kRkhS}%SbQjH}~(WrDk#eTA&|&I+cqExp8Sr zB7%4aX*hwG-u~CcY(HP}NAyeTxT7%d9m4lC^%bN2yC%Ez-TtG_ABh7p-ygNb|GZh} zB%fc9{4M{pUZTKPAN~EmjIUJiwUPNnBj2L(`MET{_%0>r=?e7$1|FrFqrX`3BGTnd_IcuZ{npizQlgQ=RUb- zd>y371uy+?<)MsEHP!XUJn_F<$$s&TX?)%z`^kPA_l$3b*bV$3@3s>eU#{T$PV9FI z*)O&^jn66BPx#*1Gd{C?X}4?LySDy=_iv~7d$9b=5+B+b{#cCzITYo#cY%&Kao{?X zW8edtd9SejV#fQUkjiI2fG_rFcJ>>F{ww4C@gJE-X0BHPE^y!i?>zV>-anxnz6I~m z9Ve#WZ)$CJ_%6b{?ZY$em+kwlg8&7e)%VC77+=vbI)B_N{+GLi^2Y;d|BGjd?_Ezn zCsA9!-|)J42K586YYYZ6IB`P>G^XVzcZQTnMuUw!~ROn<92-LzjsAI z8>z>e^DR^P`VXD{kPlqocuqtjW`5Y^^Pv51ll)Pa`d^$QXi!RmpC!Iev0ue`gOhi{ zLpp8J^%}pg&eu^q*x+6vh`5 z|3m+70r!8NA(aXG8xO%3_4I>W&D2my-&t``&A@kWy|3m+a z+DrciZ~=DX2md|AmcGNuer^C?JWG5_Lee)b@vgDo2XKJb#vkFGk63YOvV3Ww-`l8P zeM|3Ow`z?($=WOW~SXXcNk^!{*rrtKH=v$Nl(dipu)wM_fXDwBL8@g@$y zLb~GkufCmbw&jn_Ts{ZzsTZ@uhy4RljzG@gU(5S#FZl!bz%T7I@J7N{u6_Op*)Q&< z_g9Q(i7#LJapR>_0Sbg$Bz+MN`GHH4e?23jb>}wRe_!kw<=+NQ-@yKh?n~L(uS)7Y zn~Cp1DHr`mwEwvV4jh`eUq5FZe_ym*{h(+C)eril{V$OvKCH`1)X4O|vjw+XQ^%V) zOnk^cYu9yKA534oKXy|8pBLCa(UWzEsPTiF^}hz_XFezs-}9J1R6+mS@WITVgZNNB z|G?>cMS6Xovmv|m4O{ODzw~SFx&8;c!Ja?7wF7x|7tCl&jEa~joH}`IAV2l{}t?j{wvhGVmM)NM~>m2FTHs!&o4>! zzaV|L^Zb`t>GV~bvcs3EKO67gMX29P{{iG1@KW=0aDG^ic(VPG+f0}HDSTu$YJ4mC)R;f?s?0xCQf@nf!(^}< z__`nR?qq!C_$7beDzPG+zRuR{@S&ck>Sn&*(EdO? zKHXZZVFiF;^mtgG{*Dz&F? z%+mr~&D3?Mzx2vXU(^e|mijqi_A}GHUCX8Q*naT8RQk@Q^c|GmUo4&_zS8ZP?Qnsu zY9W0q*gm>)^~r|^?T7N^UCJLG?f;J8zGb%`_J4Qf!tZ4qCa(1LxPQHjzj-_NTt379 zV8=JM9R4`tix~UU+{W!A+Mhcv_0vQMzPK^IP=bC^V|~D#Xtztb1Np!Yj&cvjZ5vhV zV{5;?H-O5&IXS8Q>pj^Y1^EIT``5ej!0kw11K$Z!|3mp_;s74AfA^^g?fz@)-@U9m z*>6Z1pYygoeNBAmKQQ;3xf}KSz4)IeK#|T5)xNn-(El+1{=%CH-!*A`ewO&=jn(-* zmOnTt2_Br02v&(z^hd1P-??Bd=uR`I*reJ-)_H(;#09bD>S8BX_SKVUbr#|`3{FOcrLQA5Ab$*+Hka22 zr0^ks@U|d{m1%r_miSWpZl0#?1bk)k-b48k!3l$#>oUh3|JUcX^`R&7daH5i^o@OB zw_mB@f6pTS8veHm{wMJ!4!}(3NF8%kt?Lfq?`K7f{*69_&nJBTK~n$w(2mcS(=G%^ z|Bxr;2%^zHg!nX$8(U2;oObc(Lg#X4%GAQK&a{iC6i%2t_BcN1>`WdteWEk0?TCvf zOet*Jrn7VDs40bG#{^?0jGHobT4vO=v7;{uUNXIK6g?P^7&CU#giFRwn=oZuDr&-% zi>Hru&YwDUk~53uP^1w}nmT%73en_?3&+mr>|8Ky>{zE2V2&HzeL~^%Zqvq28P(aD zGImB`$f?m6PorzX&K67?d&z{U7f-j|%$N}1_uz+j=yJDhf0Bpp)^~VkX+JI?KMKyX zZ_yf0)O>;DQ^f8d*9@+A$GX9?H*ytINkd$j$$<;J<4K?Qu;Gi%*7qf3ot5il;(LVF zCGxs8|BmWO`sFZv>=jq1{WGeoe46*q=jrQDNy>BRn1Z*A&J#UM>)evZchNBs0&syJ zcM5$awMLD0=KABf;8PE2zxNFL1=lI?vxDmt_&B&ufsgmJPJz!O02g@C(lNtq`1t#d z9AXnSKAvY7OkeC1P_bE9f_F})pRb(F`;B&7gdk_I%8J%MSKF=fj62X4nm2^ym09@eD98j8P!^iVC zImBe%rfI)Hi}iW@_H0kO(0&jI7Y)Hb&5?hL){%XQN{`4qk0am$zvPwao(nf&ZRQ_;C&|YRt2*3sYNay+^Z20*5mmFe~ zb^a(a@^6O*^nRoDeh6n8e&;GbH9SQ&Qo77iKyq;Ix zlRm$Y?C*EWQDt{p#XxF0I`{=jsQGp}=JTa=o(cQGMNhA$m=J&qyy+9mto4`O z_vky~tt9_$%d{W9^OvaKKr!Kae?|2gC?}jA!=&iC5!Zv`-p&N={?8$HEt&E6Mu)e z=QD^T14eqkg^0`u&5|RK{`uzQ`_ZKi~r2 z8$PqY%$(opB&DQ(%HccX?qVZFGDXKHwaF(U&8F z`0)LNpE_xuh5ZV?u9*EI(qEyB`Bc4SoI|Y~oHSY(KIjwwXx#fjeDJ?N%CyaaFZY{@ z;j021#`ld@!+z3#us3{*X5RF)4d0K(_ZLbkh;J~~yVud}XV~Kj>7VrXf)DBY=J*@W z3*tli_WCu`e$j6$X1}oSgL#6_tD$j+*$?>28rS@f4d363GV@2-e!^E#|NARH_tYRh z_}>+V{ffTZKla;zir?5h+YkOwyt`iy8$R_*X8K0H-#_?v$#Zy6~u>mYF_=!{DJugVeb#%N(!f+k+a)5c?_~7jN;v{~vyJ(GVfcXavwnAv3EFR#j32Bs@^7@PV)mPl z_k^+D_!x{MJfSRq<0YS}XLLTTC_c1*KOFiV&rdr9 z_QU&w`!~F1gaYGXM*l&ue$4V1 z`5ft=+5RXC!56lk1K%%mWnB{R^^sZfO#9VsdE;$tKXd-v2c0wV75#3vUl=~j z(>1?uwov8?A>P>wzGXjO_9x>jG17Mi_2qaY)AKhYe^d+~=G&O{7wm6~bl)4kM}`(Q zV|-->KA-&0i>2|EhTy}zdEkIK3U_7t8Pa#2d{1p}_>j)ip4`gm+ejUy%K?;s-;n)W zn*W9QhXuRs_6z%dmh-R1hw*ygOU<)7(_X*Fb>OGGb4oK?|InD_)PHjr^%cjZp7E!~ zhjIx#k4is9}aOn*k?sBHJK-QAw#EKl!e3tZs0HjXc`;X7j;^*aUjhxN0>hjAF} zANm@NJNUFud+b`mP5sk*+fNj@z~|muh4(io*Z=3NB)zC#9{Y>Octb}Dup7AFG#Ra; znyGVp(0w#+lc4!0&Ura%*t zk2g&1$6wm5A@!#>R2^x2o$Xja|Cz8)f1)p4PlWyYyx_0M!;!yT5^A!uJ|tbdCw%Z76$) z&J%?N;0FKi__wX&^8T~4EjIzQH|0!Vwtd?Y8m}! z!oI4d^q&diSq}YY!al1d^q&d)tw#Q=u9(6CaD#t-aI1@K_k35dZjt zw11bKL*w^EGmg{O=boFZn6}e-9>0vzIi`Pj{t1NyonzYFNgqcC(|MxPYU*Q;u4M`f zzzzQT+CQ!HcWUtbP7amz_;A$A#9x)h7dUt?)J>8rE?c&O#@C6yZ%qC}<#p`&x=Ff@ z#u+%2zDVB>BZB`qPuEpU{Y8Jzr{@uc1>gp+nm>My&Hj&(|NFmFnJep5d3-i#|3+jl zF2@nN=gYqnsG?3Z!O`d4)>ReLb*=RI#EDfE(-t`{qW?_mPSoei=vt<*0Nmh{2KHNP z!_VvMIiQ|u;^*;VE+6AG2bT%{Q9zE&RQptYe-XWNm{zo;WA-OH$Mk{BzbK)5nXV9h zO6Z-&6c&IR{NnT4Zn5G2@Fx9)#8mxWHSq(lw}IY4M8M;2rg|4q!@l}{=aou|si_>R zS1HYi@cl3EHM*Y&-~WofPC62W1>gqn*WmjPZTQcm@k$Qye|7%ldO*;BhEx54_i09` zL-m2+@o=s;1^jDOv&{4G|5&d&imCPqc+#FZaDyLT_p+yL_#f`BpX0o2$KR8ZnDb}R zhX*EU7Zus;PtnSCl5`&L%TLGN9|tDseX>6v7vwxh4N>e#@~>#RM7sgpL+_;zaw(4C5hzUrM;=VC+bg4Zw~RU)sr+{_VF#D zYnis*qV+H7NykKml3(;);lKHE3i$v0@&3(JzbrTZ*Snf7q>sM&+Xf}?@T+J&1eOB# zW$J`JOG{GGe`w7AP3`+7{V5T_agvM|pg-P~_%P-uowsWUZ~NEdKN0>R=)cJSP2=V7 zySi_tzi;EocV2-TJeSg)1Mu&b^)Kw zY~W>0?tIBszn}CDy_=d){a(I5=T_4Qsn|~jaD0CR__O`~Tz9VDA4_E|z1MvD9^G{` zp6<|mgxn5#UV@1m$B@%UA_l$o?^{9t`C0luoEHfHy7c!0l+;6Ecko0)KM2na`+mTg zjdeRQzC@SDF_FLWOP$`>SKE{LFb4n6lMBWU+IC+MKlW^v3D!R}I}n_{*(-iOr@fXn%88)=#?fL0aGjcRl?i_z%{v zq5lMO`bfS;KMZiplYQS155K^DCWqeO{fCbGFWW!rX!|S``$q}C8~A>Ks-+PEH+Z24 z0RCm@*EQBBoQ{4>iFf2a^ScJF#D_8XO%1u=^0~wA3i=QFf1e&exfxlm73>`hM(hoWGp#uc(sRf8LY*E>zWSkTAA4l+oG2qXY3(okZeU$AF{8)ea5#jeQrT&l4T=v5^Amwr3=`)n!vE#&(Rhtm7kd8Mtoto0 z>C>vN_(^Krvz+(``MQ<%|6JT4_nFL*2>8#=OP=e(_+8mw75>BPPZEb!PT}{n#E*4| z@&3A=2KhCN^?$&zOX@%H{|Igf;lPd4qwn~R=bsiw1wYe>Crw13$C z;tyb6n5?@*`C;NW@c}0|%6;6wq0e#2{o&Al0sfQK{tx`4+1zjIwp#L04ea)hdRh?tANbw>NBCiP z@EaT7_9@{n_x;;e)P9dGqrN7ofBJ$SDFV*;IW1rp%u8~&>2gSGN8>VN3huA)FRPm* ztOxLBQhy;)YQF#F)OW}f63qPF zC$7&bbvV9TWz2_|R9DJdStlfq!)@jGJc)@3A5-PG{g1-;k$gDpvTu$X@ z0KcCle$*4tZw(k>`zuzo{tUaLoNap2>gyPP?vdILF+XY9M#6tedVJnHBs=?u&38zp zi^{bBz|A^c(0+8Fa0mx(oL<{*#| zTluaQ>iNJ?BK2qYQGIKI6;yw?E1iE;qwMS-=0E$Z z{eu11p#R8Ei~eE!*dGr5vzYp?+_Td7{Vefg-VW-+FeCPja~tS-5#%2)?cbQ}8|UDL z5FV9ySNJM-L(>0Iexm-|={35bXTbj(|KIHYIoe;V^>bn(|1%D`u{auL^`;IAQyt!caTWyTNxNB#f2 z`GmiJ+WzW@?C>kByE49y6}G=?>bpQJPt5{+-0-=ap&y@uc31 zdIPvwf4ElqbDTGHdIA3dGOmVta*3Q6B=bGde)Mre2v5)S)fZIl+C_hV!LWaV%g@06 z#ctE={3o@(QTzcg9O-{XK1KTDyB6veonG({+-Gw5Kdx(=aH{VegjQqGv==KxtZ zgm~aT2>Y5>u>1l3HMM`e#D@Qf$Eo}|BaPo{mYw}qpg&OXKu#Yc9{BH<{Oe|kAN3wQ zcgt;0e$4n$|3Ue=fy&R=J!$`On`ehVRsIL@$@n5Q44&nlG~Pc-nS@`cnZVE^}}{)~D>L~w;MoJaoe_T>>B z7(eRIu>Unh)PK^B_9j99PnP(xPoX*fwL{wPYJs*haGYd}i$%kLOFZra-}=CoN0Rtu ze)>|B|CE2d!8xg?c`fYrj~o3zFK^WKVb~K7Xd(TdhIdlo zf&F1ses=iZmGAI)M*aCNv{MZKZ!G?g_wU~D!*8HR^LAGzg8q;8%Zt>1lwNt9r*V=#FYMK52Z4)})>tK{K(l3p1jWa^*j_6(T2mG!hUVSx)e~gU3Jky>0 z|C%bP`SIS-cKh?ZI1a$SK=$EHL^H=Oay?xK#QZ{oYz%U*h#s%m2deD1SzN zbnw%R->g3uZldy^<~3q|zuPK1{1L1l5j;R}k-UH5C-DE~c&}wye+GWszq9Xg4&!%Z z{R#R{c>k2xQS^PKo25N?j2(ZG%v027Rrzr)QD8LsIhX4zA^%GBi|%9o9gui#A>l8M z2!53RYi}n1q4gEfQ#F1+1b@_+KZgDX*ctYpA@w4}1OKV0|7VHckq5$mI<9Jy{C=%# z;D49=-yK8qP^CQgTHEc<^Ab70{^omDpH&L&7vOi~L_+p?K)#6Jh7fMHZ-x(Pe*^Ci z?HK#J|BuQ~_kwh0a*ws+FE#S-&F~YW{xHvohy5Zl-Vtk^*)JCYU-2ByU|9W4)i&IJ z>&y4G;Xl7o`_T*FSI1Qhzi*WPz>j#$GJHVB?+_2Yf*6DU&Gy%qWv?8<_KzChAFDy@ ze*6G_zk>Kxmi7mJT#tMa#2ozJK@J?@)9ST7Jy`x@|NR@N{;$TR{l_~#JNtJ)J*S_3 zPl*`SRpMQVho3L3qss^6ix_TDtJAxn_D*h;T2MHxvs2)-YBg=_xUn+|j+;7VY;@H0 z>CtIZ$8~ltnJ|4q;Tac?y_mN~)#oqbqto?8ot@F+$I@1+tuDBD%IJK(6KiW{*a+To z_2}frtDPNr-kBFq89SX$4I3d_zD}4jeGg9#ZjMUWwUyHR=&EnFORg`J{)OFAzud5c z{JRC&SMpV9MBfR&1Yh}$e(&SEn=lW)bKw7=?TmE)Ga>bdUvzwt+<<;jZ>P3*91k)$ zt_MGF`Lf1A{GFtJu8C8!v4jd_|1;r7yRZpF4RCg_^ZF){7VpjiQqr+J-VZ1 z+Wyft`-VSWX1BlhvmL+sO4~m--LwGoBnNKr(_flsl=NM@!(IK zb#JX8{z>Bh-*WjMz+bwL_|+eF{4UBR!+$)%UnKD+{;0%**F1IO<3aq}1^?-vk^cwq z7q#Cv`-kCANcm~zUmy5G%FiNE2;BLw{g5F3u7bbrF1qk&QdZu-(GL5D-w&~WVz<5g zR9}n#huFVB@PkiXfBYFi{P6!m%D-*^e{RQp!|wp6$VK_@{21c@g1=O*HyPr=m!166 zaY6jU#r_jNBL5HIFWpD{UI_je>P2E_b_=l~&g!g|*_0PiojlQCkYZdVS&ifa?`YFW!ztR35X8)+97x?iHUzSn+ zl#2g!r2Oj!@aLYqfARYv_+8)+DSv<){Nn2OcMsYh_{VG~{}13V-Ou=)kn+>p3x4=L z_=xeNFACxxEcRbO>l6F{{-Q4X*Z!#g=NkFf2Y!j)Tlq75bG3(T`0LR826arp|GVyA z{BhJP4E*ZbJ^MfO1+P=<`9FgAQU9O$AM*bI{@hdcFaFef*}(4#{;>8-kz5b%KY!{M zLHxVKe|A&*(F@=&-Ou=AcrP3HJ>aiwdX7GZ-N8ROq-#d|cZuL%{Wkf30DqCYf9;=o zFBtfJ;14N(ibO5&`tCDxgZ6JH__uvP7q+H{qyE!v|Kd;Jy=>rD-|e~ngK`Ia^USdi z2Jxf(*+BMp1Nd{h?_d1!Kke@?SMbBXO)n~w2ypN7F8>7a_YwOa{ssAe0DtK|;&*{l z!CxxrV&X572=HD99{7C_e>K5Bn%=*D0Dn=BeY3xkc2=%|KOyxW z-~fJqFaCq;p;yhj7w;te#pd|*b8pZU#|Her=YGZ?)_*1ax+q_O6TCqFfWO}N)<=T& z|4Z7x6S@5xz@OV|-|(ZIn=5kRKWG=jp0L0Bz0SX-0Bmqaj=`tBeMfN+|8;`D>Lawz z$57h8M8~-}twbTj~obPekrQjMK^b5#YeQ4)w0SJ|fqf4A;XRE03OeU=TmX z=O#%3fb|tcz3ui7!@phdV;@n-!}=-A^8p@nKi9qC&lAN)|1oN25I@#e)GN^i0Psgo ztr-5!f|YTt)z1U|GFgA^%Q)%Y@Z-J{2TooP#Eomzi8E)ON02~|JNJ%ODl-K5%9m4>Ho83K8(5ly1DOf z7=HK-xZ7x4M*S1}BW^M97xk@}{VTdZwwYg$E|NSchIsHbYu20`wExvo{yb>p-{@%- z!*AwO_`jJ?fy2ZfW`8^fy!kf=KE&g5Sl@#Ak!8!N{GXYg-{$nw_)#qc=lQAp-zDO( zUFM5;e`>$cxrqE;<_-JRGV?=eV8Gcas-pTdqKD8vJIO%DW^g0|yrlS?*Ms)IM(n?O zu11LbTPpbRMDVcr*eK6Yp1@xCzOgU+gaF4bnJ18FrN^nm>~D_$Hmvi)g=GKh%)}Pm!uhT4B34MH}7|e zu$hj%Q)vI2&*~)!-?J)xzn)_1(NA9)`JkR+I-lInIXXv`enjVq5P%ze?#V-}-xun< zPT!HZM=QenumsZqb}Q>g`zaE|c|3kO?I+onz8^%?pZB+FO8F;B-y0&@wUY4jepqxZ z6ZXsW=|5BRd+C1C*Y88eMD?D~&r_$7VVEQUH~5WxAK7lRfAt6S4Y5^w#vdKpP%&YD zMsEr2$4KN~L-M=Q{shD6m}uU?bWHeTbdCx8Ge+n?6ZZQo8qtvUgG=w%2;AVn4c_+e zW!C)}k0X0>NKDZ9W1nZ*e-pa~+v^0f7ZJWk(Hp@{? zFWM`WD2b21H`aq>5nWCD8>(dln$9r+ZxNki;_rcZ{C%qKgpY{v$LanAonx9t6ZLpM zwX$yHpF{}24c>dx35#v`OJ>U*vd;wXV-ocLIeZWZZBL~Gl-u;z-2bpczV;v7vYn2D z``2xw0y%hmjgxsj?gMV{O%v{a*@oYlqwk3Qqu*Qno(kfBX`!}{KZWEFb!|!46aHM9 zSH@Iwtv;WaNIpo^^GyA`f=Tp^M4~I`*r)yXqI4}&!FBrBr~gb(4cE_$(0?Wb;0E6~ zZp}$H{I1|vtL^yz|6>2ozFV}-hX0y&T4QgL#?SLmgZ6L9{v9aa|EBXC;)m1sq}ZyQ%nf(8}QBa;Rzdlu0L>a`)m9EU+NM3oITYe_&nZ+T#sN1 zt4HMV#es0^oI~E=`JGW&--!8n1@!)k2iEt-&(?ZWmV3cDUsEZWniqxjTK;%l&xEM8 zGLIblCZIlr^+=fC=Ex0;YtbJm&%8KG(7`qMj2kzu4bE?SNa~-ltLeheDy#L|v?IjM z(fFyDO9t@u_4RRq#2+j;oj%&{BwD4`fyllIj+_8~%x|M?kPMGtKbisZ{*n0c!r?!i z9(bMS$EU{cQuZhO@!2##Tkt#Q+VNw5N#Hkeia3S!nZOy56DR+v?c;9J>E%lNVwq>} z%e-4(;`0r@CHiM`#$S4bZg1Qo_IK(1(~q;csqywKL-{6a1?M|F_-ff<|e-ix0Nj{~vKA z;BRg0LnmU?YQdvq-l`)ffYUoe>+4GV3ExQpCfCPsy}=`EKIi`*XdN1MG9?0{xt9_Xmv2zLVHD4bQ=PX7rPK zNh#@exE!j>$5bum`Pt|{LHW75i0t2%_BWRGQL*9K;fMeDvX2ks)JMPD1-hI>`8^o# zHHpua8@6LTo5bfyd_*|P(X&T)ejr(Xw$gUHQT%5O?N8?g@H?{4JlJKq_PV;{xAIW&kc!`JIksd=g_IN6YAN`;F{dxZg z)N-gSlJC&N?%+{RKN$G2KdE0mGyk@h@&q{HKU>hxi*X~tITQWq?X?{oiFbt~9d2#- zOBKdnB>TIq6#w~nJNf^NbpK&&Bq>AzxWT#p#sT=R8zkk1vA!bSKQrD*-lf!VBi@zx zi0mhY_gzfl@qUm)HLurO2NM3`$kF=w$iIuJ{C7L0*C#u%?C@{Edc{{V?Oy`_5j#5q z0N=4qT%@muU!5ZF3*@5+wf8wV$zP@Ngjbt4cnjO#6a4U>5}JSFcTVF^WQjkuUQTd| z7!DY=C8HnEeCO5&Z0mF4%L#vAf2;VY?CkGJIpZ1l zdr7`VyqHrh1y1od_+>4udzX0R7f194zj&9rDro;}#s7a@Lgi=IwEbh}XNMo(uZzik zF(OW3e|FTLVgDX7PKNJn0@p|*9`?t1+`qeH{bc@y|DgYWC(Dl%KH6$#dgZa{k5l-H?B`Q2od2p0Xg80=$W z?5}}!_!!TCKVyFc!~s&81oqd6jm-{!zO3(s zogr>K@Q6Ku^JCfH5Z41=-)foV719Ow;kjo$)4Hk6{&ze^{eH{S?=RL>Dx>_t z{#J;G{jonO;(_lx)a&FqVfOF(beYxuj}m^bU)p~XS>iw6DCc3Pr+(J{>>Bo8A^Su5 z5)b@?rN51h-So*92yzkh-6 z$Zs;sAK*9nYK%#_vqX4nNv2sCNN>qhB>1_&IRYm3|(?=gSR0 z!2hLQ0sp~$cUt+8ezmLjK_l>}>RU{}SnsQnfPQzwX~!FW1PwD1Tg|{q?Z4 zAN{MeUje_PD#0X4{{GUha+CNC`?udl<^Ox>@-sFuJN#ke@iWk#HSpgeeiDC8>kIpD zk?Z5zG=5i5Ll5NNyXGf;W&bzl@Ace7{&P0XA4B~|#xan5;Blk=4^FT2As+cl2p0e;8fNB$ec5BtB#`_qyCmx%wwrr7b9 z82%IXz1J#IFG@7kdcpobV&2KPO#i=6?BG|)OqYn@haY{m?%8vL_-_{bkKpm6^V50V znQF)H8~6_i>3=>{`jefOnK&zdsLLh4MJCR_&eZicyhmKY4g8KAA5{O+)t)Bc(MaFXor0Gz-#c(>LI`)))H{J8F`TDkRt_?L+NKPUfjuS@SQ z6EDcl{%6R3F{+owDPq+I@pI?!Ogo$T-IW`@m-hjn$m~H|4TOKCh6Js)ZcgnjW=z~P10sM_USmbDK|;)(6Qst z`|y?ABw@VNq5GMhrTH6)!|415I#0Bbj@`p4{pop3TX}v59hcGbn8E_|0{?yWn^)WP z>++m_LVVet^^3hh_=%uj;#K-SC=v9_eVx81N(BAfExC#b`jyguCg|tVy-d)r;I&-E z1pVUlET*sky}+B@e!OSXkH2rmAzn@E$KyZ%yUf{LXueryj z-_JBYk_<9V7N(!qk?<1Xd)JAR==-`vZHMXGoi;RgiwNJnF6+`zG1a8`3J&?B*Ol%k z`mzn>=M^+RhW;}_pWJqIOw|8Y(l1W>(K)8D0KLHP{OVEPrXTMw$05-|rytj|_#UL+ zJ&z|(tJ2$QBq=^eA3Jx}P)sfKuz`xKtdXSeD8F(jy@ReNDy00#!7Hwjr1^{WvHNfh z#nif!KK2&Vd7|Ad^l`!R8j2|_;0*Yn#nY|x^ZW<(6P&o#kN4RM>WA-uyNA+zHX?iv zJa!0u&zWc$CjPNlJTZ%BIlD|)HcQ>OVS@UZm@nCI{Qs_OwrcZ|27zUBxX z?7xa}yoki7_H~qcpDXcU40`3s1t+|`=!Kwusr3z$bq_N7MpCC!pUdX^(mbWY!Z?<3bXT^cy74@xI7T zKSvT4{J6pAbqnf;^($BR&(yE>X*3NIet!jhFs3?@h+8P<~D{(yw4v#q>jYZ|=8^^;PD080McLy`dlc9PxY8FYlz7?BYzn zg&dPZ4&u9Y{^cCfuh=v8|II&{+9sr5>iu_>UB63qYI~quz;jClS8T1urM+FL*QMVA z*Vm9664D=oc%GigVe=0CeAn=EeYX5B>;k^D|AS4~KQVrY_rpt#_1p{Sm+RT}3+u1M zcV^7*$zXmV;vIpwUiPU)Jp9wexQl$3#x1W>Mhx^qh~uK8(R1z-NuPyt1wRa5s-1 zQHV9v4FR5KQj?B>^U8(PwxIPF>QPE(qMNDx7I&#Wy_Uwji3$$V?X}XUDl4YLX*~tc z2aY^kIZ6Mltk}mB~m^m#%ljAllTIZTN3Y7f&|UQOsU9mv%kNM$Cu6hb6ej|X?F;V} zF#mb3T|dm<%Bu^IU@1bRn_kW_oU2xxmo5XyGLmI6+wV-=+#G# zTYY)?DAun)(r>Hi_W`Hhz#LU5{-EaB^(&O;K)?4S|HpUh>mlCMFD56x!T$4QS|3m1 zi;Q*@^eB{ZW4u498$BazmZJ{B2aQ`Xi1qVCzqsg^NATiZDyQ^IWT~I22ZW=ZjP)~) zocLYpqp`UKRG7x>rqaL{}arNkS|Bz z=lpiY5Y`X%&qbo&lWXb1!2Ij@{Ot4_hH_Igl`TjG$|2&E2=f$JcM=0Z?-h%A(!*}M*`SEiw-f7I& zM7zkBc-?wa>R+QB1>CqE_0c>-AB{5o{#)Pj(Z!s8m_P8Wq~Gu*RDW5TliGhKw$QF0 z=AXkqUzBoPEzW#D;QK%*?}0bUU0xs_`aPVNdA%by^p<_yaeYMM zmt#Eke*NBXB_0g<&XI`gI$YR_)6W=;yDQKS z^L2dLF9Ui+C12t_;L3v@koGR(yF-9*=m);cKYU@Zd?^zBhSB;;KQKR8-I$$zseWtu z&Mw45KlHmmzscgyX8H1+^mC*C272d9eafq4PrrnGdK>(n1sYXiHk0y2wO+tEX zIkCX^c@tUc_p#Vl{ZIT(6hyln@z8Il^h=<;aO8TFBdA9sJ@Tdg;9RQpgWhtR!gY9l z@!*qqep@3~^uzv8l^&z_-#gS^mGaXs$}auB6TeCnWR@@MW!|BgeubEKDDMsEH^GR9 z9vG*GJzOXz9QQ-N#Oqfizb{@S`eFWggvRsyL23QsH)W^a|3}`tz)Mxt?c+;Cfy2cC z5z*AP3W}mQk|`?c7Ewt7MTQ| zj_XR_6#u_Eo)+=<2$gTeGlDnbw^(q&4{G%b^LIJlznNE}{I=;>UmstvX8G%Eys4t^ z_;6pY%HO5>z1rJ7FZr)zKRF~HGTm)zx3Rn<@-mb!nTwy@|Eo3qlD!+GtGo^>BCo@cd`crzJ0`lp7-^#bvZC2Wv*PjYk`MYraQJ=H+ zSvi%zov{8fJfiOV_w6;ypMHlrnbr9I{ioVzl7q~ZdyN=n`lide0P(^j`L3wtMRpE5A%?ediQ>|H8WUwISC@N-t$5Dg9i@@e$=i@l4@5 zb@kJgze>u#(l;~ZU*%fR{5|{oc1+Let}DvXFT1U`-m(mTKTG4gw*E7*xglNw*GbAQ zb9L)Ni+6Uco3wSI)n9e2o3wSInWSUgq^%1L?&w%IX=_4bd%kX6XpHBzZqn9-M!)V@ zH)(4^%Lp%h9lrh3J2lInt^X`yo~!-(j@Qq2J8wG6Uu|6|(o?@KwDgOP{Mnk&@+}?t zvo)cWTRZY+YeGYNp82b-3&r!wpREZE@O=Hc(84eA4AUiZ`IdJ#oWJ>FejirM#~fSz z`M=Dcswp7V-|FLz$`LvfZe|gN`+NJ(q>2hCw%9qT}^0$k|z5k~%f1gV6K`j>N{$1_tAq-FE z`@N38$L8PFK0BNF`^~o-^H=!p$ID-5=LZ)xf0pXcru$ElzoUPB?}jt|>2*yZ`LuEC ztAloJ_Ws%W&*0{c_b=u;(Dd`GTNfJjZCn?c^ln@iTHL5{U1+6$#pBmn?`54$s zup1U*32KP>_VV~XHH@}%N%yxkCw*!d(;x12=f|B7g}3K*t$@3o7g|PGhWrTc5`Mmq zGc2K9{x3QV{zqXvdQzVn#HUT=TjndT-5xf}e}5Z)!WCWN>xty`#{5qm54WLM&%ZU! zX9Q;Mg!pk@X$JR(#hW94IL|SQgHjC--GsoFt>=PI;=D*;CFJMud=B@9QAmHdGvqTE z>(7BaSWxHoLLE$rBj5#ZkN?jEpTf%R7i%b@iN@dVg`;u$`NRV<7!jKX?&JAbus zD9$ehI=hr_nWyz0_ort0ubrQOKoXw&VB_HR`5!nK>BD-E9M(t}KA=6neHUj~xd|SI zJTj&ez*tljcHU&X9ds11^KWbGNw&s19^frjB_-S&rm&)_oiVy?7dK=FpQF+?a0h-G z`0z4=5kBzrA>Qr~b`9S9NB%3{GIRHQ?%rnk-|0&KXyq+mZ>RH*qlX)=A7cGY{q@v# z^)}QS-*2=Z)Lub*zIaNdVf6tm+%D2qn8+__-X#ek- zKEJti8su3(8-(Mt(GR>Flp>ii^uk0jgpm(XJA&&>l_s6{S$VpLGw8h4=$RhQpz~IX z&-HKyowpjp&7kvEqYC^3I&U@lJ?y|40n%&l*RwrpP`qh#APMFFuxViz>;DuvzNz-} zM3jGV>yGiw=!ZW4R8uF@^a4wToOeYX)k*%JVt*!1_3t#^to~2H^o`B=_k&hNi4F8@5oRUyl?{C}MCkN4aF z`*}8KFaI*`4J!X=?+u(m{-#gHGHT z`CnG%`C%&m*$*Owy7f)bk9-NMT`E)Sw~CBV>zg#ssdQ|~@)w_>!=))cAb&XLo#bEX z9-(n>_WMKn-+ZEP2g`qseAHJLG#HUB=93uLk@z%uJUBY?f zn0(p@NerR+hdcW{XYYTW^QY%9{}*8WP%=VVj<(K^o9Ey3->zcqzjIfx|DpGpw9|Tv zsN(af{2!A#r!wFJKjip;@)?qUi@f9#ksrZv2F05vpEj~26#t5?-n-cHFa6j2)dSNT7U^QrMyefR;> z)m`t=iT-_2{(q|ZFuwn#ef;(n$?N;`ZO+4rw>G9-Me-U?)4G=*lUF_^$B^NR4{D)&^q=G? zmpu-6*v^0bNc}%!8S+26F8@DSv;1qGFG=NJ^IhVV?p|C+AYSRtQD4L>-JebIAv23^Q{XPTcM^<#Yh>&~BvN^6$?&h&o|){E)ibDEplcct_l zB7NcevJKNcLh~P#uj=uy_v3egyyV5l+70n~uHR*^JYxAznE!KG|9=kokFKnH|9`q> z`5(b`2+>qu&nx09`JERp`Rlwh@c|!*-skr$m7hM8i+(>Ur2iwi&%)7vj(pls{s+?G z?xgPzE&rpQ1(Z^`)$@geBLv(X^7@v2~W?;1Xj@tXQ;PaFTL zyAFfBf<0Kd-FnwBrhlCyZeumDN!$3C{Vq@i@fpUCM*URRKR!BTKX(w`-~BOuv^)F* zx|DC3Epvx7-QQk2|Jxqox!*SaQ=fm0$CN&c{pWzrCn#Nn@f_fOEaOTr-W9{m5S@vz ztbdAo!yETl;P`Egi5MRPbe@4{UHf?kWrStWc?Q{w@q0CZ&NHaO&7kuP^t{e9$iS~b zm+~$1_T1tb&C36(CH{%Z@obmJjrrI9qTu`8oWTw7{m%mK4f=hf>i4=i!zXP#2jx}# zQMVc#$8)OiYZyMk$5;A6H)oh}-BQ`y@jCo}%8H^zWPn zwOE`>?>EnqPwzJm$!k4>#`SaL)y~lGK1SqajNy&UVFPD3{Ql%!%zvEPU!Gh3@$#R} zkMf_+Pr&q~<)ZvgWWA~Ug!Et5yav}bbe8{^{$)t-Wj=qxTiap8K<`IB!1*yic2n;j zZi_iWAa|Cx=QhJVaQ=DTUfdk#%K+b{-QnKwgCX9%vLEItfjr-r?9K=qIE?R0rU&M2 zfzB?;Mdn6-+VS#c@)>kD)5C*GqPS5$i%=ii72MpfhSOk==3_n2L;Y$P2zgljUA(Pd z4I@~81ed@+aDUc6pTD9X&MWEX?qdFO-|6QJUtvG4ba_8#cyOfmU-)i6XXwH4_3KUDkS1-?HQ+4=Lk zc8nhsf7$%~vGG&8mV3kfyY_cX<$9+$>+A1G;d6bt`Ux@l_I)25KPiwea9z9VnS^|2 z28+$fJYuKkcCq$Vk^Sq-D4+i5Uxsz-)0119%SYoYl27^^5v3#DUy%N$>zfQ8NY_sR zdG+5yt`Ao}{vo+>oqI_CS`QhL&ynxU7(s`C=B&}bTV(Pn4E60-oqv%$2K}i&|NOQ! zlTSKdN=N(o$sCuL9FEk!6ZRXFuHSG!pU!cW94e3d7e)FLGJ>9+jK7?o>fc86-`t|?6UNiZ;-QvU1xPl`3)mzLL*WGlb!Zel4q}=6CMK_)ev6|3P^Bn#rd-`}gVby@S|at)$Kq_#^wPD))d1JBsTN6^_!~ zNe{YjFK&fS$>|;oe39pQCXaYNAwMiN&r%`ZnGw=q1kGvJj#yYPpC3>@uVMVm z)t#SNysNoI)cU*fe79yF`r?<9EnKh@~yHRyP2B)Z$&|l}R%omsKePDh6`rFix zqf+bRO80pE(DzklXZ`EHabL66uV)yq`oGejfcy%zPdIO)^l82_AuqlDwZ1##Lx!Vs zm{i}6A+PV6&Kt{-PaEEjIe^!p2L4k!%v z*1~Mu1GV)_ppw(DUJtmB13u-G5gwrROV#81IKwn1IR7QA)dQ~O0+9%Q4d3T_oh-sK zyt}E7FB2m@z$Wak=TFD;z*O3Uul8{UT@i0&KD_&=>Gkz*h3{7pdrrTpt*b!cHzqmPvd%( zryAvxZhuG)Iem|L&VuqgjPt$~o+}eG{F&VEq4xw;K<)Y|e-B2ywk}-Fc_Q)nqW|ro)uY9VuKUn&s z?|XmDZxrkL55?cEnS45%KTeMiOFq|A-z3j~>6)N*1*!StYq+05{s+;2iN719`8MV6 zZH^1*`x?@}&M9%je7WSv>%EY<z^!Lh50Gq#cz3g{ySJ(4YZ#Rei_zJ14{<@`MfIT ziVQj*Ji)yoeIEEfkRH3rr{lb4VCjXlUjaKX;xca!zlZaifx*`y{TOepo{9D1Kxdca zCiA|w6Ygjxzme#ly1!$5`5Z4ld(Z0SckNX^hRTkZGXyrq_uJMVC&RJE9Qf9OKEDxu zqs(wU3eeIQWH5&bytXBt$M|$S$=1~S^~x4vfP2FZTlw&Vo$(AXn)WinHdH8&7;7;N z7gHV?+#4c{?^}9`Lp^TY)rVI^ScWaWA!s)7559~$aiKaJelYH@tCIaJO4R` z!2ISPT{q>IbRs|T%IDgW-y3uG8&u!_lm7kgH;wWuJ+^Mj&%M%I|HQ9t`Tg;YS%aI& z?}Rt;>eju#)yLOO`GuXxPyE`J-#(At{dhC^UGYYv{$+o+Zptt2M1JDew*2!$pYPUI(kZObp5xmRm5`JJ~4?TtG5m7ZKT<>yv)QvZ=(+wxo0 zw^zgY>-v72^K|3;TYYNXlwa71{KT(q`Q5ntBZX%A_olV~>hv%B%(^MRxD)w_U)%D# ztLK!V&E$9Kv-tkj$uIxxx+%Y;6Zwf>+w!~iQ`PmF$*56;!flz zer?O|(+4f<*Gzs}-hlRVo&55ztef&nI+36FwJpDs9(w)xX7YRHu2lWEYTcBdd%ctT zkNjGf-*12S%VzTX^OI=0)ahUK)pb*TVJGqvzqaLl>+7cc;!flz zer?O|p}BKrHk02~_u>7`cC?@KZ>*d0OFEIC__Zy+6R-MR?f2OiJ;P`QIqj|H%J&-IQO_iTuQ`ZTTI$==D3A>EFy>`PWzPZ|N`V zru^I=JE{N3uWk9=H~Zp0HNwvleA~-UD{Exhx*}VO8#3G>+`plHaim?GS`V#s)SnYr*<(Fde@Ai>dpzwApH7s0t@h;z9cu9$DZ+8 z&t~hd&M5dNqrqNoQS6-kw2kt;JJ(Aq{M`rZ&YSlh=*{v=*nbPG!1^=0s!w76EigIj z?YXD1-xt_mD{oJ5Zx{!=og-O(1kVF+PxkJ4gk_i&czfXm><0#p+Sl9l{|h_%yXZyi zM+P1byghuhpEEd~9}pugh6??c;AZFoy|62I82gn0U0OdS`z3c@^^BdL>xTJyMEe&j zUq$(S0_)MZUn=46ChB>a35#eI1Kgsg4@c`470-J-|3Z8_TnE{e4}72N`sIH^ z@{6g*;-v>yr+9lO?0S;>^W|UrAS;sbg%6xw--MxRsTYSu?OY7VMI_$yn(K#4YOSdWa z&AP{Yd4%-8so%H`%{` zz;j=L{Fart|62O@y|eND)a}0x-)k;EtzVY>())iE&)8Ky-wF52s9d$KU;IEmkp6wC zfc)jDeLBj|60Y}GI4XyWQ~pOVoF%FBgyctRogmXWhP({f<$wOYJ1wbS|NSGbpImVT z^zZVz_22(kGx?>@uT#83v<~u3zn)m~)4l@ja|-yt&v~9yyrK6mc^pOkOH%6~?~uNd z&oLbB%aQ+x{4BlK+%KLdpMKwC@-mbTnO}V4cbC+^zv_Pkr$hg~+422N-d{8M{a>#~ zO_zh#ql#C)|Ji!VWCLGrISG!Y^j-8`Q$P77dw=!&ntFeacm&@b>_2V4KJfC}fP_rj ze`)5bL4sN{;UBez0wad+zYw?dM-d2ljDAJl~7!rOkg8?z+13 zM&@r1X}zIY`(@R4y~pqkPN%e0Y_2asCR>eJ^iMcJ7OHntk0r*xo6w*Vh^P(>?5l@B!OTmf*aq+-`kqn0~kq zKeK6HXBaXNcAW1W@7=eCirKMVJ=&#j4I$b$W>2z+54h0mY8N6Y_IvGiK38ry!UuLg z)TgidA%52b_%hdCVKxdcaD)SpReWKb-zGvL+Lx?ANeX{)x_3}Mq8|)9+8tde5 z!1?aLDofuLDF2{;4Oc-Q?27y0ymR2?1F+W*=ZIJCfPcUZ^8WLKaK1XA{SevSI3Ekx zjUUqBVVpM)+=~4uc2)le`wxJ7F7@{C1?*1%e#-q4xh^=L9r*AG?_PXmV`q5ves7Qd zinF|cD=Xff?}7M%+=X8Lg`eX5d0@c#-o5Y%oW~9f<$QS=X*YCsT}=m>-#BH9=j*>e z!}$C1&)9aU^VWA+wST&JJyyF+W;SMk{?&3nf6BkNj{D9?yY}BD;(odkA4{KSTqb`m z&*4@3q(c5A?*EL=^ZU7~fUj9A}8wU@e+KaKtseuljE-z6vc`m-N-oqrjO z@$uTwbh9f@awg3`%zeIhy?pQB_r3c$P{aK?>ep!f=JL(O42bbvrg}7W{_)I5nSb(X zhabsvdcxGX$Uo)&=E^sGK0`)x5_z3-oFjh}+pkH{`;W~BuGHkYs3pVAkd&y#T9 zo8nKOPZH4KbnXjO`zA|1ecnh&K7Ia)RpCVR{}Z;8#OKNDcXY+aGfkms62^+EBeK^VU8`&u>=$-iiK^UD{uoKtHWk&(v7w=HPo|--~cb&l*-DqhWD9 zm;FZ18YbEPCtT$p^{gQr;O)V6J)L1Rzh6NKcHm_^Z&&f#J!=>P|Mq(nxodjXFpc5m zukYy$Il6~eAby~{oqxV;_rM(Zx8L{3A%4SK3_n5o3_3q8Li!D5zN+E52oETX^zoO^ zgB$R*-4H&0PbB_s&l*lX#>bc7S%WUgRc7wiF5R2S_r>q|Cn{Tez0INhP%mHo{)_t~ zBo3_H!22&eg7e~l75)A9xcnx5X9~Cx-!m(Zd>LoA0lRMD{UYm=7 zTZQxCfZF+K_}=Fcm*G^EUj^XSSF`VT?&cBN10ox{N@kKA= zJUd{**4{mQ4Cm7UIkq2?l?V@*O!wq**a6A6`~>0$bm>m!Z#JK}9Q8y0n7?19^T!LP zq5O75dxz&0RQUV#pJYHXW5%oR#wrZO*tw*(T{*(E8VUD;KmoBYcu0{Eo-~xSM|)PKdYc;zppI8XnNa_4*d{5$&f#;BMibhsP55Kb7Hi z>EgqA+2TVyIv@3&&B4Q83^l2Y>f&}nW8B)gf7{8&W6d3lFGhRE%xyp6P5Y@E3;&jF zz5m+p1K~NfbIyTL1y-r~2lHST3F@4?x&Gh%o2mKS#wqAd&{cEFGDwi{I-o3P- zuQLRE-`yf;IiU0Uk|}6!0{dR*{RhXxFEHkCZ?B$%_9US8Ybio_zz3i4?iGY(u=c54 z@ig266FL4CO-K6{Pe&9hxmbr;l(H7d0_f= z{`t(8(7py{eg|?!{Z~B|{(-v`KJFcm_tmlsEX zqpt1y>l5w+R!t<++Rb(iWD%dq^REMrU#aAe*qq;urM~?W)t++hM(*R*zWA7YtaAoZ z`{gHbd`aK`3jHtU?@fxYl7FJVk5|0D(}O3I=Q#jszeTN$e8>!p<@xpTB|iOm@_YC2 z{>2xRVaA^}vLuGk4CZ|EY?Hsn56@%!grvDx3|9Q5n#iRPDUFT^Wi2l(ntzApi zzI)rtPjb19=Lu;3BBcKtc}_^eetwSpPSO*m6V>8sFU6z$jJkdN70u*-BgP*qXkY7m z-K0-*`4_hK(XB1{JDx+Q^mZoyH~&TXCw+bSN-qCY{`}qPwERP+^PkDz*>|Y!diRd) z=0g6<+)%Fv8b5mG3e>gxb+o_3%$mvnUFuc({KQ21lR7_fFP?KE|J|9k-BNrn@{<@& zNPW(bACuw(@-sQlp!Z91Ud8%E{0RC#OYgyAo=7stmnx`DZW5HeI7`Od}qcAw6E+s ziXH6Rezw_pCjS!UUwX+8488G9$iLVj|8V0qlm8K%XG_?h_OUs49KV}MN^h@7^$!C+ za9)dlALU>2KZD;*@ge=c%kM&Tn%6_g`8IwZgI{>Q#1|}cP%8W$KA>CqJ5lZ7lm9|0 z-W>T8$X6cl;YZ||tdAH=$mD5eKlg`jCV#DeTS5K*?lROrx7XGG{nt$X6W9-T$NTgs z0UEy&pP|De`n?>MOF(}AExdd}@{)gs^^zkW(*F#et0TEd{yN{*9YXog|CuazH`en7 z@)_#6o8a|7A%7$FO5v2r>-V&E{*2mJbpD8uCnNLn*@r$^FaLWe|2aQ~{Ew)s{|Bs@ z{Kv4}qH#GTAkX?%^2yNQ1^zCF_<;PEE55xH-{;FAOMWoliYr^KQn%S?+y9w$h!|bUnbv~q3@f_WBX1&y;=Y3lcRhH zNzv;;7X2?96_(uguzqRXwS+^)`5`#J2heyzc6GZEc4zN7w==>6u1m#NN`;?0qb_jVPDMZ0u4E^iMFhOh@ruk2 zIFAUp>~QZM?TGPq;73pp8;>X~g01yp@JD^M7-5iN`jxFA5`mgcFD)s-hi{atw zy7vEOJ_Blp$W;4Zo6_}( z4nJV~Eq=%68){U$E@3-L;e_M|sh^vw&o>xPxv}0*xm?@b^J))Rw(P3D@8z%hr&wd1 zo5%Dg>`zI~i|Jqe=o0;3&i=K|MNG*5Mf$+>RLLuTTHTO%>fsvK zKlK0au7AqR$8wr)zjs;f_b1f;?8ou9tB}y-Gad2|Ke=Y|*Y7;Wa!X- zjxg)tfc(ii-;8p|l24yk9+IEOdAWq)s_TlM1Iw{zmy{IBI8p zUp&K!`N6wd?OUct<8l$hlex{=Ju{~N5r1EAIqU!U>+pD3hy0UZ&E((ydjtG^4o2q| zXq=Psl>GI3q;4Z$UI8DtMc)hZlE2;;^_N5XzhR@s`b6?Sp5Kdf{c};O{h4PtI`35J ziOK)|Eq*VUo&tIIb|YUTzdidwQGfrvE0KSf<|>-W-9vs`|NWgv{eSU$&IJeK_k9m$ z{kBbW`P;mfU8?`n=k9t}=eAKli~Zt&4!ba4`mSZkw~wpPe@OmWhLb$u%PU8|{k%br zGeqPyzL6a1Yu-HhRcenZeCln)?{75J`_GY&Uv6wKNzVsr9qX<>J<{|2pZETC&TYbQ zDk<~2@c6&h%inSQ!RB8opXur{lz+l`7_DTIY4b96jbXU@eF&X<>|Jq`1@jfA>Y2RK zPq|6{`knT8sn1u)fF@+Z&eHk7 zY+r@sui>~vO_JA3DePqxSn> ze_y}7eH!t1z*H_W)jn1E%wl~ReAB;AiuW+SuWFxW=&&vQt9=@fKbrZLyj5=7GMvgJ zFQ<_H4^lmSv*&Z<4^8nU^4F^V)4CJtS4Qn+)}tZ$cUZql-}T;nxS=nHXts}6@6Gwt ziwgM?!->g98UOy3$un8)^IR&YKRx{V3H9&KVwR8n{#fwd`YzJ?r|rGGRW354*oUn0 zDRcZKys)u+T2x<&h014~_n#%d zGkNW=P&s_zHGU5c^zUbp{>Og6`fGb%4wCDG>`%r!dcH#ci*+79{a49fNI4`2`0&&7 z2_u*v8QYw7*T!ACc3r05AJY87m<{{@tmbzLL%h8E@Ije7jAeb38c+S4#;3`5=W{P< zyoY>-d>7S2obhg#6p7^hggj

    _B!^z?hlvjv&Sn}9X1rs_ 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

    q3`&B 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

    vsi{{na( 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

      B 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ⅇS_RYSLc)bPNs#WzfpC?)I*R65y-u(WYSvfci(t}W&>49ZbDH~Y)ux7+? z)~CDY%TJgqlhI-L#Tid6P_B%iun^IBaSeFHj5EX?B>9;vDcI1rNbrD*wEs2-7`w`# ze>=dIJN1WjznTg@4_FAea^C|k$&CXSr*wDaw(IPIdGp1M$lS$@O7{4z_?IP|-|O~q zhIx694ZpPOBXREwtc~3!rGQP!{faBcA#4x}L8GNe0V{I4WhC^`ls+Zp>KjzW2rIz9 zTvNu$`uzZ_6IaHHYVy#!#-i$8t?thEjrD zN>(;SU)QUnswsN0eKafc_M&edf3t6s%*#acc|-^NkPi6e_U~`AkD2BtZ`qf#&C8J0 zQK_c8%DhJlj%4-B#ERTUhLwv?h3t(g_DdBhj%8(W>(Gr#)QK^fhen6%@fqbz&Ou%- z+Yl77-@ujhAzAEyOQL!}B$hKd0|7#Y9>Y{U=9iePM;@i~gzt~2*kkTzmnfDamS7g@ ze)xbEp9P``8GgY_sVvZA$_nLp&z_!z-7o1Cj4(@kKWOt~6^|Z%g9@G%@42*?Y<&is ztH!dw%eYA6=`0XH@hDkpb=7L9cF1IRDXJbzyt9f#X629TW8V6rI`$Tys6;=#N=L6|Ln3E>yAAv)U<=^*eGgpH1EE~h#qZoBzp{TbJhyj5oJ!9$ z9_|0C%;V?UJc_hSM|Z@cxpT>E$pL5WGkp&Q0qzH8o2 z+kYMl{zKqLfaCiY;F3NM{cFtFjXqqbeT8g%=D1awsr?kAfBS}=;Ggl$+kfm^g`MrU zWOyGJooqOt7GFX)O47TYQOJJtCFH;dDuV-!h^d+AXoQX0e?T)oe4xdZtGE|zoNUYE zy_0T-zThK)F@P)2iQtm@g!YNfU)S#1!Rluc9ePCrod!?H|5PuF&&C)0m(3aBJ*JxC z(a0V1^@<+*m90eG?F1x9!>Id(c$!|AVf-c0^?#++@#wbQ79M@trdobCz)uQ(q~E*< z{#T$4aQw#Kwfrs&^(UNvwa)zF3icFk|7V1X{@hpo!rWJ0X+Bq*&u(&oS%OZ^4tp z9icy*?wh*WAE(z#(_fCVY`6E6#^@zJp0X4wAJo7>mF2A=yT{I~(G zUS9^6G&A%Iwd09zQLpV4+LZNF#_BB9l=bh3dE6Q`k8SqtA#L6s4b0bO90o>J48EcpuWef z@kJEGQ^+B4&^^YF`wPg=pm`h`IXjl?f;i~o|G>E_v*-`V80)X ziylO~W>oo+9=-D z^lcgeKG!Y3Q;|!RIv_qHbo1)fpJv-(Gy^$BJXwsLi29z$O?v-H0M@6oc!(SiOxbjbbVnOxQ%p$GFe$k*AWagEIikU6tey4)Z0~P{~-&?>t^1G*c6P1j) za~#*Gd~HGHix^RB?c=Jzyybpv7K}UM-t*;S{67ALDx%{B@+gYu`Ev3dZ=ecrB2BxS#4;i-c|j(4vgU99=b59crnq$+rft3Yw9){S^Qe1O0_S3FNCdEkyf~?`MQ;s zpHuA- z9vAQJ8yyaO|BRSHlVQ(z&Azu0#^O}iNlc>kepW`QPtVLk(a9f$RNN?O{6ypRQJ)rW zD7T(%`P>2@Zan=N{BOYD0mo;bzgRx|g!_Wgq5s#e+S6?dDx3VrGgcE$kjv-{@6~Qo zMGQU?N#1O(g$8An>N%<;Gbn742noA>St~X|7exHKeKd~YwWZPW*#IAo&#S=K1GfW? z&wqhSdhuMlZe@jbTwU=Q)@KKHU&vhTt0te7Q@Y=mncjunr&6CG+o;5#^Q^N6MRis; zD)?05m=d(8AV`Hp*!?dg^&FWkCQ_*8i2u2d+hW}y6I;v0wp>#_Oy9?x0=^7b3Al3I z1up5W#kO2s%ZnD99^!f~`WbimMloZaRm}KR;BD(ZnF2{Df?1THA0n(GKP%gGlVmS$ zA13>gBIB9V!gWAc{KWSs%CIx3@yC|mo$!<58#Det!WIJP4LE*lz$Ll$tLyo9sm#2~ z2;ObwYxO@D>6UnYL#E(eRZ+l&OFrj8{BL@XXmg3!a%tEP$lu9@E(O05_-ROU?{d9% zx*|aEQ(}3r1IvTvinQgyWtL7Wd6tId{5N>~V|-r#uADuI8IxL-i&G zZ+my}YOk>T#kr7l<1z(45*PzG{_4Ob?Rj4yR(np_u$7IBncbF~&v1@wi+C?qOwbe9 zCjLQ~J3hcnRa|4ul#nH9#%h`@Uu&W3`pJFZ8-a&HUZkJwS^sgtvRMlkuMlr>6KSc5 z-dh!cKG2k-G0cBwhy1g5Kno}YB<(%_6^rN3UCA$=OztOp+biNozD3kxA%i?xLpt-x z)AhrPz^?+X1zi0$gG<^q&THp+Zz?uNhc~2^w*A!pNVLZSF@ly}K(L)+&cyltL3C)j zxko;HrLD&g$=}su^l!*UfKtHGKN?)pp6hecqK4&X9p2EvD{5xiP@^OTotxI zd^x^1fK0K8Dn>*(sF;U2X_HIyuoYQZe2P4y=*OsO6jF@aP@aInikQO8Ip zP5#L>mY*i*rT9kXiTl7G0iFOHKd*vIa`VLQ^lU9Z%kW`-m{E20Q}>JhO%CiltGq^( zA*xtEvUTrBLA;yj7opNX7&osq8*@rT2@(;tOKFD)%K~l6MlC z+s`WTtkh30k0hdI9^HDa<+rviT~D*XmjKHF$L}rRlIDl!sXe!s@fgkM5}jDT=52*~ zPBG9;IMv>(R31^ESo^hKnuurU!W`YFh|4rx7C~mg(o;~t4H2>o0>pKrMK9*w(@tWaX6xY7S#ZWlm7t|eu9``5AgBM3C^+@rrG2HL# zZiMQtv-BH;CizSIt^t1zcnNUy%m3b)e%I^qH|WzwlLlT9pY^(;0Svm8l)6%SN*}8Nj;<+-vS`Wu%!%6%)iq`l2E zc3icq)s^fr&orYey2p8@`Dd1&ozP1`Pud~z57s=O3~>DP1((zu?pK$0sRup#Ollq5 zmA5cj$)xQs#wD3Q1pk8jsKGLgqX}uO9jS+#!uBU$H=nNpUkm&UaCDynmoy^ubGYS> zuH;v9RxE0e84l`|&Hj_ob-rr$bJnrz|3v=>Cox>&k7a_xHJo0C%gLoaG1IzXCsUN4 zp9sojx|1EyIvPr$eXv#e-GVC=K3H;Oi)2#GddqL>^K`on2j3q!5ODlX2AAa4ZMS}Q zo{v@x9|o%h4Tmq05%PU+o8MO-C?jN~xOb7iZw~1-k)NxdJHQ_So&X%ZvH!I67KZ*G zSN(o>^&>sS)KAf!k#$9?xoBtibxdcU=$BGnD-|T_v~TK2KDRJ}#DX}+Gsr^U)R{q+ z{Vq313bIBO6co#&Bq+~Bb+cQ%dyKEk<7k$a61dI(hZ+(`btR6@+I6aTY*?mliVT-q z9{KH2@zL_zAFDC(v9U46{c!`LQ7j84M}|asOo$Ch@YpYNNDdE#up5c3sNYd~UdflL z{2rxCf_GH>L8TArd2prtV)i>KIVAUWm0u=@Oq<#^+V0U-Ss0Fo|K_=_`y$4;rG&jArOk^Vy=zW3c%=)O=1gpMTco@x1-{jrQ$g z``FL?sIRD&w&~|`ymLqCKG77xC(3hjxdABk&qI(`8YwK&GfJn&nQ}^F8jY^L+|IVC z@5QDSelIpM7j5=ZHFs2ObS{#>!Ql6)@v#ZHJ#$PBtEnYb?3WIvA$2wC-<$(8kTn+= z=a#FYoEjgD>T^|ZcLJz=)Ye=4|I+=gH~0u(Kfu-7IEl9D)}G#nIDr_7Yl?oce6;8K!DgJh#z>GO?#`?HbGgASLT99^ zBIr&>%+ExS%Z}3T%_3Y?U3OwHY!TkO_cJQ~IsC=ojNVZ$b}M58b$N}ROYPWiE9Bc` zFfeyWprZ8a>lu`4;_U_5{S&=2IH<+8LE%#I25&Npm1m{?qJmHO()dQ`=BHvP zjeiC=0cSmt^iDOJc*7Y2mn*-X;mJKojC50%spv;Ru--=r&+n#E>hwg9 zj5SIRP`3tIJ@iB38)RUvyGl(xZ~H;=U+M8v13nfQ54e7CIJl&}ty6CNbhT2o$&PqO zWRELV@&(IJGxXei_z?JSfmZ>?&www(`7j*MUEAr+br4fa*vs7xixsdXePABH zvETi|{?+N`yQ}SYr<#`{3GVWD{Z=2Z2lr#qa*hZ|!e}z%4PtvVgnz&E{c^piprUXF z&iHx|XRId?SW}2H_SFsj(XmebRAokM^pBC*B=(J#`9=DecrkD)(WVCKMR?Ph;j>hy z)QPc#DlZ+v!ld)Fm@?%Ts_ZY|hG-o5or*<_9v_tZ166rEKYA9IfvLphD)+cZDmOrB zzg*!3Y+Nuj2Tr_b{i*tIT8x?=(FEhpn3;7MzQnQ@Wvy5K`UodC(W=Hj zL~LCo@tnBV;(ug8^kr3WX7L0TzA0rzqL5)z&34<4-hb2ms|waNsD6?u%8RjxLeU z_+sh)g{nlA$8rP0`O2l^orwE2%S$LK*zkendsE1ljEfh+{|tNqIKDT1ZTWWhHVs|s zVT8VsDqEw2-i@l(=o{nhBl0dvMeEfC>Q#05_*YcPtLlx&D{$^#phn~|YBjh19sXzb zx={`4_{IN@f2z3W4gddDjVgaxF=gkCPN?kv;a}ossxT}4hlzcu&l4mZ(JKi>(E8tv z3NKM;-5s|K`tSm+=WDropVGNIU#L_;o> zONc=(ay)N3N2D^ZK4IxMk-yu&ZUBE6cnom#TfjTgcjt5K2i5q!?K}lL&k_Z-3o!g% zuP%Ws;O!^-44K<(Y76NnIgU8`)!=eUnFu)g2ZKv;{teD?=R@cJwrlD%;{VxoH$(Jb z)~ogSp9Q=Nm0qZ;(tM<{Y(3z=shy8a;LiZh1CEcEz$NXi9jwH(V$$0`mY@6`{rkWl1|9<({T6UZd-BISbm)o8oO_d!hRekd-THR#%ME%29nnkWT6%Hr zYaG1_@WH?^z|k8CE@}6Ecso&RcfmVdHahForF3_|dqJTKFN3ZoY3VkSuWRRP!EXcZ z1{~c@;F7v-=juhXR!&&39I@eYY#OD7OX?OQkIx6h)!i2ytx)CF7142#0^ckl!&!gR z*DPLZ$Omo#+WF`XUIh#W93NxBCCv!^6uNkKw!e#87srQTVx!y&49C;<7YOvd^N=R>wi`YFY8!55!mLwHTfUSJX#1&s zxefe6@9$lz3iLjPX+CQ5EuA*eN^Z(6XUT$UYV@Y=f#2eeC ziiF>{9|Eat^ut7F=REy?QQf_LLaZyceBU8_!k5ekTfkoj-UJ-qJHREm`KYtpv=iU9 z&W>KNY=Lm^H_T&E`XP`{t$n4uH>ftzC=xRhf|5%cd?GgkHr16_enw-fk>V@iXF52# zN8UWZ@v|IUlH;fI{LqP?&;<@l#DDA(aV-z&yAMUM$LSS4M1QH&!Ln38r8z3t&YLW= ze6&DkbjU|5I8GHj0u{FNf%L=h(Gu!Ctm$-L03VBHoicyH;<^2+moGi5V?V0g5ImW< zFHjqT&m!hA$b8-qHIF8B9}In=A1%g0>nQHr@(|Zl>K+(=4?#9OIA7M#YB2hW7A#WX5{( zZj|e3lH0JqkLYKT2v^N!s;-Z1mzmVNYnK(^w*hwpu740w)wD}P=uf<}->8o5GP}dz zMn&!%qZ&8FeEuq89#2@Uxxd=S`Svkhl;^(Cjku_#va3-*9^0$3oak+6T9o$Hqk_XwO~=7PtitS^ri)vJ zKWY64JNt;<-

      Eq@*5s|ow%>EP!8=Z9sM`K)DuJrBBgBjG&Sp+7REPF>uvV!1ef zHpk}F59OY>X96FGw{g?kU)NFqqr50JIl$800*$JW?#JN&1itJ*cX{YPfA4hLB{v1B z)7uGOqr8=UG1+S;+#J%a#R0CPI~#m4uuPodlBB%bzjb-VGqVjeoVwDu+%+X8cKMIE zLmh#93^Nz2pe}jW4-Crf1^zeS?||!X6m8?h^A5E$0sL0B z`ESHO^;H|u|32kXVW)0Ncvs;b^-jEO=$EwKfYuO0Bont}RsK1|>p{~LUDJKkK9Oo! z-lyu95X53~q7L#g!2Podj*2JD-9kf+<##3gB;jA?&nEC2ftvxx@BQGC+`2y{j5G1g zeiEMH*hsysL}fb%cHa|cYB>Zn>`o-10jQ+VXYu8op2ofUp_UIXJ6&&O-~)gmfa7Bt zxTHDZI9+vSM>$5vaY{_!C8tn%*v`IZjbmToA(i)f#Cuu|W*ST3P2+$>iWmT~pd3LM z&1h8GF4q{sA7eb(*ABOQt%a`ZPrn4;0{j+md>xWw`I-{yhxwiEk#uE zrQ;>O_Luq`2bem0^IIoIdt_ChvnQr7`$vO7C;IZ&IeMl)Ym-KXK zU%D;SoBL)z5nW?r$g&kJ*_XMl+o><`kwp+X3YiX?=aIht41^Gh{k0an4mb*Me7p}XY57IAe>8+~>b}XxvgLCdq&ZrGck?y}swJ4RnVEB^ zek~twsS>KQ1{-_KrlVIV&FN6*R^$jPk(MZN1bcsf(~)jsaf*`~YzE zQjllsCF|$5Ui|Q0^PBZDYmVId6D)Vi=7?&EtW)Okk#8Q)WNxN0cIsc}dyTl0%EqGw zqM{V_QiK=0aCtkLn+jj&qfYZ<#s6$QIn`hMl-KY(EA_Cx!GJ4NSr)_aGF1@d zx%I5sg5Kod#jgYX%aYL&u6G!?K_{7ej`u?2IG0P7+r& z-Fk43%GOns4W-{c7>G+vA`9QI)USQ!#rssiH3>iQyw<~QeKk-YHDP^S2L3Ye8sO^d zhJ0IJHDP?etljqKC5z`aSi4@L4otgGVr={<(>$J0<}uWKUTR<7w2u?aclNjK&u``j zQnSA=@ILp`(?GaAKtz{MNZlNLoSg>PMjXTy`;mTaw3hE@a$isuJrqla9yr17$K7Ob zn5gfL^8LdzaD-jR@(_&AWYOqNuN~?S=oi;jqu8L0#oI)G;@TDYM`vlBUxgJz3QC#! zhKXKj_YSILFIP2?tDlmpK&&3Lie@l_T&UQS(TcHHj~MQODrgN^ zWWLZi8PRWNsUm;8^JUd?tgZhx>euO6CkkR-Ay5Xm`mY3+G&Y=9zj2N@BD@%#v&1NW zm@cdTq&AxgHxuk^E&Ixg7kLz(ateA1So-oA0*c z<-+jUK3>xFC#{@&$_f)0qi37`GWb-hHhrr3WZ{vQ2khfg{V7|)o%+Ts@7d^N?s!Ch zHeVl@?5+>%cWFdt#JlO*AjRC#P4DAZ=XcWwCNg5+MJ3Uriem*}v4bN)NiHTmxmf&+ z)wzRWeP$WarJf&UAmTtv)P2)ik8^mTOlj(SbcSe2Gb^w*--^qEy}I$Bg)zSNJ#y(1=|vK=rj zBEuwR<=gccHmf7t*OMPSZIFd=@YlkhBLnRReH?y<+a+wpsgEJ15J1XuLk& zi`+l;G+B6!A)O5!==>J^4d6|{)$a%3lH9&=m-^k6UVE606J(1I(fRBWCB_Ek!LntX z6^fs{wvc{RNvz$k-(>Kqz@dO7DW{9y+}ZCg`?1dDbeoY1NLG07u~*H}QUotK&$ioI z@^t(8N5G#2UI1J<-vF1in||~SdP|5evS6{Cqx{rJ%6cx43-RahSx?KBA?~<>yq|Qg zu3AGrQl;s39S430a2VkDm&XKNdI%aQrL+msAzn&-dv(k8!x2E*&58n8Ex}zbg(8s@^q| z)~ylm8tmI9;J-lvgvsEZhQfHYIJy%TSSGc6-}2D{9k-sn0saqQC*b&)SZ?`P6~@z^ z)_FZKjwM8a@yNN$rE~Obirg7Bj=FA(n8zgZ*;kx)eW~A1c#q+VQRc~>@d^GoWOzYV zuZ&7=`|nr9-8c=7i0H!1BEPgDw^)y0UYf2)p!$I~=G-8W$Smv8FuW$Wcvx(F4=Kkl z6t~U&xf4B^5EA+fW@DTtP1eP#hi|U_q@XnoVf#}K*Z!NqUj|+STsfnn?@4lgG@kC{ zm#o?t8m?#v=NTzzYvj+tyAjnIDSFqGZG9BWmLc4SQ@9Kqk5uAS=Ljw#Gw>Iig&U)z z_3a1)v-+s4jG`>I9Qcx;2!9OdB@+IsewpR74n7>8bHJAaX914Se}YSD2+wP4f7$6g z8P5L`mo%80@W%Lc*4ve22$=An6k_j$+H!2j)`y|KeeGsY* zWs7x?F3LH?Px`ule!pbazT7v<`7MS%R7s+nE;(GQKxAGqya3^|fWqxl!2b9Uo#d2| zWP^p$`i$PEC#F$Ea|)oKvcJtU3wZ)2-SEx?O_tJkgI zlDf7p>O{xd>LjP*?>rMyv%Dw$S)O;Co#|6&+wzoFq|e_&z()fI0FK@Sa7hha+B0>a z=WJ$D=P~2vL5~Zsr_HU`6*AYhg!ERCpX)a_f^P&K1{}Se;F6Yy_K2f9-Dh(Ta++I2 zsYP@`k1;BtPsh!pPBcTm)Ym0YygU%0e9!nmHrHADq{K_Q(QM97;$0NSCI=b#P}iuK zjnxYj8}evnhRzKx(i5)Nv4`145-^n^d#JuRqSq+>J|-(xWwf=?)>~bVbUEgMUjy6# zxN>{|Zc?bPbYUou3ir2RaG(z3v_&7jS*sR(y!rf{HjiuBZ}Z(?^SRi3_Sg(}UuslP z?ud;+`p`3afZtP%jU%!eqD8MWTlZAMGj*i0SWXk-6RT%8c6s1EgP^KMR2-9+`pH1| z4gE36`2HfmEWIrBg(G~|Uuer&(=)w3PX(U_)B%#Dzwdf4_l^B~yLhpe_Z8t{-auY0 zSD;N{{%gt0$$#zu-v~SmxO!{>m*o0iXM5Y~4gsjPswa_*WkO}U@-9VLZ@oF4%(ou1 z8ZWZtiL?8uAwL;krQoB0v4Ep@2DqfB!}I%&P_L(RyGh?`^8c$=ixuKJ%n*5ejDK$) z|KiI8Ox%~{y~aTTnTt62E5#DIlyd`kR%x^(^8u`1in}Avh!;hdGMjOLziKSPNz+1k);IRAjz1*h)HcdCo2-J(iK1Ea zm3|m4w@5EIItxfdPLu=aFgc(dqAKP1(s+gCV<&V{(2@2otc-a>fsugY<4|x(@lfBc zvz`a_v_NF@wiEZS4rN+swq1y3n`%M#(mZ;a&z#MMwN~xD>`-2YyMgguq#(_(6dLMhP5A;ylNftDz#jDNuyBw`gR3=Txnp1sy$Jy9q z%e#vFQ{*r0aVz+Pz@vbp{}i~S4PE-H+qFF;2J%Yj9!=_M+kIoU;}UE7bCe)gnJEBQ z&V6mIdtU98mXBngbUh3K9|s%+I6fAGOLFH97k{~Ze(PNS96Q3JY^%C8Vs^O$WS9FD z2X&mR6M(_*%Mi-aQ{sI%nP=&vqct%E6l|%`{}O*))?F~3p1Cq#IDQNJcJ9aDkmneF%^g#^8@Dpv4X;19k4p2BV;R3B;@uOB<^WzrH%|Gt z;5gfL~t@BKLBYd78aNo12@H^y$w(%VYj?%dc0 z{w44=;OZmRuX8`%V|{e&#*FPt8Pqa>?jb%*&`+Nu^U1}^cI2j?TKaY5@7iM__&LD& zfTMpYxFpw)_ej6J8@J$V(M%;VJC%H(yjO$4$XA7h2aw#hcZ|ksEFasT_#l1O|xmaIj3{dN?xBQh>rTg!J z;NyWwfTX?mU&q&OI`Cc5_6}Sh(pyE|uHEhgzX#aRfu6H7>U!RH^g4E2JLPW-Ni~FY z+Q>6S8KmB``%^}s5RfG0a`n7tILY^t;N_jp2{}lKK;QGQh%NK1EcQ3z8`gg-7SgK?1GF*N~N$;n-l1A{>Gxi#?;IJ9=QczGr|h1WpHB zd9MPOv@n$WtUagGIscpV#XyePVqOX0_>7WMY;} zd@YrH7x9YDigCoO;LO!m=f<%o<@_U%jif$Xt<{LXxFU+^!Le!`DP)t+K>$%qfNdQ{ z%Hmc9C*R-WPm0FJYjHUdN$i&~#r#Zn_}TP$UA8L2gG{F0@7RKonV4JkWzQb?eYkG= zkzyaj)c}Aew!p3NFP#Qhx5iSn*#M$qkmp)}X57 zvKofqEB(g@?efRJR>7m@j}OY)fyANxuQo*>|B4D;g@IgDom_5wZ;Cch6`?or9)Ftq zt_nU7iId7f)(@C)WxQvLe^2?|;P`3hDOZvzM=Ye|$%PnC4(XT?D1Yw}i<%pWkjE{pN?y&UdIwo&%^A`Cm5^Pk_I~3N3S$C_*Py7&k74Q?l z^~Wp2pS$tuo*m{1Gf*oSsK?}rYAJq~eZV_Ve~$&M=_yNREBUy2;S=!BfPaR0%e=7L z^LZ$N6TRsUTGM3^D)am@Z$p^>=ppI+4+lRQI3952I2l}$+h4f(_8WTY)!$z-n-Z1O zosG8Wdi66(19+1eGooZw_p~ifGx@plJP5uCcr2tL<#BqsuD&dtxu>J0IdAUDhLFlh zP&mo+PhuSi^Yv=d`IdpFfWCk$M}P2dSB|-h(IDJINlZ3g-7{f7AYWIWOTaG$t_W#; zoANkn2T-B|JpTZjJL_(k|F#bK{~!3*fIl={t~mI&E0;MSq?NMjZdRtDo}!@u?*Q~( zI@l4^KWoc3ll)!zR)AjwTne~)y#id4Yd5#w*tOkuq28n(wdc90oafdn6rFs)J4>G| zyEpDjeq-rxBmX4tGR{5&|1S_3mZl#Am$cjal6J|g=QsmHPs4Nlvcd#zojoF&Jo|O{bTU|1HJ+reQ$Wz?cNoAr?cOs&BCeZ>{IaC=PcbR08Ci7iB0qT}`^Tln+6xoN(LMwSgNPi3Yr^sLEw}Jl)_z&Rd=ZyFt=+~`S-hqW9 zVW9{X7I{mvU_m@^+wh>|Jkz&fQ@zylwmxP;&++p^@JoQp0msiZ;F7*`eHc#ye)38M zHz*&=Vf0*a|98HUD!Iw2_-zgOXoZgBBfc;DAD{$qd<+Hu&U}RR60&lcu+ke=xH?>g zL+B(rT;0vTuzFbFA<~4PfX^3f{j7wp&d<_ z^^`^zzX+zz)a7_kJccXvJoZ_zBuw3_!-i{p(ejlXndWOW_ypi!!0~lD_;=kyYjW0No1#)M9wmk$WgnQMBpR!s;#e?&~<$M5d0G0a=`KR5V)jo-;Z}ciCl;v zE6Ke=$4R8USGftK<0Jyz#@|^!cfv=Kcd5VfF)^K2!080+5bf681d+Ir+my z;8y|H0K>$EmHL?s%i47poJ~Qm%Tq;9?qcmYppj9}UoPeEbxAJ#ahV z__!PVJM+;#r4@9!M?cB0bmp~Q{TsGEc0$MTkvb4tM_?G>_!tTPo%!fEr4`g2+o3)# zr9J@fBv&78As>yjS-o%!f6b?uFl+CSL(XoimKA6vj*2i^o6AMb!m z+WWZec(VHTQ%GybN7eW=A4h`M19Jh##{%&Gg^zDLg*3cr>th3STzzZt5__k9>bI3;vbXwstOTh8*U-18h zk8eAL)V^iwW6H!dAIrcSfr|jg$B)7P7e2o26tXqsV=Hvr{IL@}I0$=S!10j-E@|)c zN9XbKZKsg>w{3mQgpT9mN8qc0HGtz|E%^V!$G4qA+Co0ILC5j&Ie26e`rCjc+21)i zu(JmZZ`x3(FskSim#^#~1@==@QR1rxQfMjuD1!Tvov7JlnB6BkP&7q*nw;7HykqO3 zKBOi4u?FxnfwKWu4;O_$cl$8+bbPd5j1{DM_l!gHK>V3kK+veC6TwZ{vTP0MZ4L7i zdhdXL0(=cPdfg}6-*m zys3u2?-zgI=lwzDsluc3?w1?TN&2_u5}CSaXtnh)AG&egg|AD&uLsrvj;~w5C9OKm z-aj>ldPj2YIb`{)*~YWpQkvx$ak00we>LV})393?UR%*`GdJAF2x}KcwrKAi#fLYD z3q)OM?(SOtWcg@=j^iVFFnt540308~!6l6i?M>Z&bhmssF9;>6+f`2-?*Bh+X98GN zasB^$=iayEWqU~ofv~+GOTrQYf+C6pSp-Fhf{4}r43Mx$B#}+js!>rkfp5}Uob!QsAK0uWkQ-kNg4n2e71L8)i4SlBhXCu|juTEEx3<(C9shHVX){@HW>1=9jf zhQ`8Yu*#AiUan#V@x*);V?U5%i()f*pYk6KeDMj7=65vj_$jIM9D_UsoB}NGZO9U4 zm~+r`%spoZPfw|-a=f5_bNGSmU17I5JaQKvw$^#;z{-<+$1V zR$6dawYQvAUFZEd&ABynJaOwTvBS(&MGAUPR+(Y(V%?KlPBA}D3|8r}3pw3!1}1M6 z>1jk}u!@9pRHSF`9=Q=rugkxb7Ae6FZg6nK^oL5l6;`S1ES%;~bi8)MI{|MgZ+}7l z5PS-3yu2AYUQ0UJLmV*Q^c6?ME3eUAyA`{ybso+SHsBdz6sKteXZ5G3lPGRFRQ^Nr zs6dZxU)LaC3vL9K$L+`xx?QZ>`M5b(e?T6q>#A_>bJo(TRZAcvmj$UWr7eP_q z%kp-309NeK%JkBO&oS5qRJ+``B&DGBQ_ZUty>awP`h-u5IN2Z%NRV+r=X-Py$Y?-%qFqZtj1B5{^+@-quVbFM&0&j|Aw5znq!o*$@iD)wp~b?CI|a|?1KxChwu zc^+9p=W}ESTSR#XkhR^5h4FO-hYTw%o-m%)(` zhn$T&;yVIEI?-GAPtCK(%+$E49JvCV2`ta^ktN(|?iaWIOAg4BPWuA3xK>q9s;;Tg zIr(#)lV|Cie3nKF4~soGi-Pe$FPx&ChFt^sd8>!fY*3@oSN?C!?=ke-aosD(?}9d9 z`Gsa_e)pSxXqw^KiQkg>%d2WQQp&}8aH1=ETif6C56o}Y1?D&FyzhF>4H2Phax3(iW9ktwi^SA{OHlDM-C=Fp|~5D3OUt(Q5;?(v2OjjPM@{t zAIo>Ck6Vzx2ObBO-;C2WzmIR!?Syl$ejYG=f^nhbVZDEK=nK!S_kWlcJo1CjqrM2* zX#4GY(}{;T3OKrMEgqkg_S%bxOMojQEFB%5+o(5LmPmv|q`jZ@|2Ywi_|6EeqN%cbA?&MX>;F^YQ?qcRJj&qXV zS67{~sE%iRc1mw4@)&R&uzF8GmSE2T*!wJ!b{hxKT33PV`)%$r$Hz(Aw3Gp5qtRQ> zd*i%U()a7gTfm)0m-J7a_cIxDEWu`A)ymQ{s@GJnSXmv|37o*)6DK&$SbU8Ul7v?C z{&#p+G2i9=xpP?i27`f(*G0$@zGB983FEIr4rDRKSvg`+4Q)5`FZ+vH=3tAK*3?X{ z4*o)U>R%sOpZP`Dt&hz6g2CN-@2j}t!D&!m?(o7mx{S@;QQU>um$A<%H7ttftPEA^ zb&C!O-!0-$#KAERf{wy9UTAm-U;81CdeaR^9#`l&|E+S=c1k%CTk~xQz~cwSAfvJjRR1w)Nhv>CRu(DEt@4{c$m_ zh6w+`4JX(<%fePOn<7r6dK)~F>QPb zUgt9-JtJv-r7Qfv^Pl$AJ)Sx`XuQe`bUZ7GN4bgTdgR-{H-U}ke~=~IxmL&Ti!vV^sr))fw#pF#PXy{dLabyczyR<4&j<|@xiC#JX? zd4fnx!@1HZTyH?^blh&}8%O$RUaja&pjXP-KZL_P)&zm&m4Ph5&O>cG+KHDmsM@gP z2C;U@MmdXLuW@&V?CNZ`UIQrKM(;e{TW0jGK&}VZ0IT;NWC<;u&d+wzyI^(AvT0U# z-9QE%kI3j_0Q#@=oVPI_W{T@yMET8QyL^}$z~nX>BcH@8kK_DN`Nlx3te;MgHuRRG zSJETx?1FduH;RnAp-`0C3r zb}7w5ftM32)P}m4CKzqp&s8`DSPo)uJHQ7iWlZn7`ykRBk?| zOw6n~CCgdHQ!}Gkk=zy)o~|~#(Nn28aqcta+J;z3+zX8hX5Zo%NxXP+kbgC{?CyVX z`WOr);)8X3>xhqy@9oGBfE~cb_a$TrcK&0}2^=)OGO9@RLp0Qzy`YI6sGI0FJ?EWJ z5rnvN9N?Q0;;cyFXFv^z4vs9a~18^wCErT`cK^0(RjAY zR0ZMT;h10O{}9Io9Oc==5YrzhPGMpyXRa6OU5b?|vzBf+&@7{O>Q1(CI2s>eS>ciV zX17as+#`HEK7bx|<0bUNtZzlnK*Q5+csM6?%d$uNF}$+(BE<^275iGj&)l@9+z2NT z?sY>w2Nj>lzu)EJ`Hg=$X|DfQTyLpaReh+Ey;Lv%LAg2Xk$_kj$M`DmT{rYs7gzTg zDtm4yjQ9Q`u$9$@pUH?jmf&e>_spC7b*NxvF2!&In#jkvtr zs@z7Ev6bb@_15H&@;qZ*q!e&=pN7@Erx#s2kUy4^T z@`+#)u)NMeZqMrg?P16EC*U=0^`b?>t;t=sLsn6Ls~1yy$ztkyj}z37z;GU|_gB;u z>SLxH^b7|>CX(_J>&vL(B+c`Fc-ZmX3&?)~e+QOl`gxjXrD?bAxzvNEU(mk_$K<)? zZ&QrjX17=H85}lT?|p>(#^_jT%`9eh-Kj3zQp1->Br88HD21=N3J;D!vN{!$HQ#yg zvHjnr$Tx${!1DbZS;F1d>Goopncp6iubDk@#QsnEZ2bqZ;B0+=Ia)< zbL4**9xBapvqC+&H+YL06C1`rNZR+g{`2YTTXGUVONJfHQ%}|LuBc4SpH?AX18x8| z-Yv)y)|vGfyY6t%cqjK*nz*(gC`=!Fx)?nfa-K>n;wWx9UcR!$ubGz-j!~TY;D?5G zp5Jkfg1wFr)PwABE=|6S2c;9pQMp4yZbom03gTH{GM9X)?Vg@hu+B77$0spA6`#q- zXMuBpjn5in33eaR&U+6UpM^EGD}#C)c!QshNBCvnBs1FaE~?$^uR5Yoi7%+mat(QnJyuaVopC&2OxEztZL%sSYTmHX|VI;;ycxSnu72Z@btilKEsjE%uHS3XAcqTdxTggWD=h{-e*<(^bfqgR6n%vjJJc zA?sb}NKpCr>>?i7uUrOy%?SNOHTAcOa|ovwMjjvmWnFS1(5 zoDf(ZapW%X*bj#n42QqtPa}OS7U;rZh4!b>dWP0NkN5XLfs8ZPB3}Wn23G%#$P(=O zgi1{XBcnqY&>n&cexy=%LUtY&FMwzJRE?Rsnfk!Y)EFXs{DrQPf@4X`qVN< z^QuFy?Vqnhz6sn4EU&weCD{F!1I$NiYUfvJBi^z$xh1r>bAxx2at3r)HP|4A#k?J7 zA*t&>owhTz{x;rk^+&2BP7de}tp1V6UDU75*~|7S^bbY<{hm`=fc})BeXbjI!#-Su z&DT6JwQoGjqyu{Fde1e;>%j)2dw=UaoyvvG$EXm6s4aBHMOAJRj{G>mvHddlr9^KH+a|PJC{uNd#gO0UJYXh@mp?2A-ghB#fF^EeM^3np)NPZ z(nrJ?l5!JRgWFHN?BX%>L`8>Q?kX>I6OI;-SMyobE}~E5@M^zMVO~sSyo#+jS48$z z73{0Z0s>10xhFAgS{RIY2dE|TKkz*+FW&4%8r*_0*|Etb+&mNdmrPEVMQ)S?yHGtz zr{lV8x__=ny3l9JzsvPr4EdZp4Vtw2g*qMYC!MC5bbJQ+7vPt`rsE%wB|Kx+PY%$| zS6{Gn)eJG92rhMGH|+!VVA4r1u1&wnJD$5S&UOpbiT-$5#NpmN`kQluDx9d+Jj#}& z`kQ&k8^PCr*DL>myr~5bizwmbXZnHlz_?#1bKCORM8GQMk zYWMd=yBPc2;C(%leWLN=n2zJbaTa;bQv+DbIYH*$Di$4_9qTcOobDApkMnv%xLcrv zT}<=WtQ-y@^olN36NBIN;k>aQ9g^wy38ndiLvxwZx-4@s*9{~#mxCHD79HQu2%JQd z?#;_|`cx2qTklpQ*Mn<-O`oqLOR(eo1GI0Q(kD3pD!Z7*MvagdW6ipOV%En$it8e= zl8|+&de_g>Jz-^y=F^Hk%O`q1=bu0UuzUt1A0eNK!9Jc}zS)&4ZD4el%k8&WY+m=F z9q8%yRJ{65vosXHK=Y|XpY4}!LB1P&8(2Qg$P(^0>z%Lfe_TwvB=d_4<}c^k72QSB z8uV*cH-!UlU|$k%U>m%zv#9DG&w@ocKLz}}?Pd1K`jTYbJVv}h3Y~4NXc*U zkDz(Rm!)`)L!Jsw1rnrv?(A1!pYpw=6TQ@ZDFyML%Nkl9`Zjo1DIAXWBfSkt@sZ8M=EWzHhe86$gzWgr|OE7YS5lL|{P`yr%T4ORU+2H+FyqyXEn14Ag1%jF7 z<+i#ym8&#A=Ymu|mm*IF(}Cr;30Z<2r&&KSUC8f%AH_59tJ$QE-J#qj^EBq7im-NqG9&v_(GGOCZi7dhT1v!lJqvL09=V*3|gF{Q2kW5NGKy)EyPx7gj_Kyz6#{tzaM&PlIoX?P~s9DpCr;*?!N!FTfE)>ogU_LmLWLrztQD@1^Y1` zJImr3!o%Z(er5(fAf)pW-w^zj)cr^{y+vrcN~deIF4eD(MVYFZZW2fq=#x*)Vufx~&GvN!d_W|92 zjn5!t3ASDzupizxK8u#Ft*)}Msdt}nuSh2vZ@SJq{t%`-Q#^4Wp5|psLte?+)(BzQ zYIrR{ubr=NM!pw($8eE;WmqTstS+=O!D^c&bx2%#q^7{LI+!!yQb41+UR6B_6j zrb&_nq5fK(PR@$dJfswPGMEl*I&DIhu*8h#4x>H=?SoV%YEr)gwto$w|1a%d{01Gr zR`}cYqF`mjDFsJ^Wc-jN9Ads%JY?T;5X5h?F5J_q=bKA=+T5Y6(6qU~_kzdcJfxKW zCE~P%ifHL&=D~;*lP1haSxM=yW~hN)0b^9!KGx#6mVzo@?#CFdPUjmJ81yg_znRK! zLIt6rp>lR3CCKIG#xdq2VZ7Q?)O6Tr;!pf+J?Op)TSqVy*!Z7}Ea3?1K@k67S-dJ0 z{fc5t#_w?XP_rc7@3_wWSfNQTEYqZzFFVZ%`nXso#&V8x3GRGyVBKc;Ho(Wui+_Ur z3U~upzV9PTu;uYE>TSSxs#M-2-MoPAlq6eRpg)6!BW#%~a`hN&W07EFz0wz!jB7UO zbRD}owU4+M`5JHousr{SEa8^*dOq8&@c{GM1C%Sf-`l@2bnMoU+Zbw#1&@Ea!D9yx zN#4)1oIkNA!~h~ZP1Ue*U!}5Q1O3argb#WD0IM-7>Bo6JypMqCiZ92CUGBo==jXElM@uJBI1k7@xS3}$>$$V6JOIaRk>GeTucS= zb2jVrn6@UB9+xBE0UCi#kLQskR9vCc;av0VLi-lvTaX^6vPpYV?;ji3b!`s^kBusL zoGw;g8@#`#Ic?bMnN$hleS;gu3960o@!_Y4ZhmIoWGRC$^G2hQW>)Gn(I-10N;-I*TDYfq9H?FR3z z45ul23bw2`f(lQO&*&<8$%}E&o$JTs?qqc*8xJhJ#Cj63KG@0kq%G;=`r+I#dBhe} zrHXTpawvl~B8?yMw|QCIUZ8DG{OO1}_8-m5~vW8@b>J9%B$S(i3|tjqMP8UCCkvCWW$lw)ZvV_+aX zS)6uq5Tk=Za;MQCm6O|zfsJ_H)t@pGe%$qM_LW$E>ri%&j$fPMFXQ3J#n=FY9>B(L z7_tQ0uGo7%JH=0SVgi441$EyQliMD(l4XB$o!#t4bf<*Rz zLE>J`V=a1Yzw{mCUw~f%%VX{(nn$IvR~mhwead~yRjR&B(_3&{HFa!o)6@?l!Q<^{ z@K_mqR?6*D8@yL@o!_Pf4M{X8Yq7KavRGv_jc=E!y7qPFI69&3YPCO_jeucDDm^zh zj~$p|wTdzR;N(wtTAh%8vD898t52+dceW1Vr-v#Sn@Ukk{p8Ws98KoQhVFNjdLe`( zulgv*r`^>-lN;nm{SKXOO{9w*KRt*13U~w9bo&dk1Z!7vfcealrHq;uub7YJAvb`& zDp{CCD$rZ~gBub1uzoa_VST>G%eZ1Pi}68|)?a*Ss(+e-JPphM5@h_=*^azZxnUe3 zE0=ic4BSg6xbiFE)Wk_Di2$L&=xN}ccHVzK@(%Fbc6#i2wFB%oaWC#nJ@WDsvpB1^ z(VOF}n#23s%=`E9F5Ax*ekI}z0V9A-$77Ktw3_k$f!Bwnkg*OkC0LS`3)Wa7-lLa@ z`^pmWM(=LrtR2EpEGhrfS?ZwKvgX`Q;t`#$>(OW9`#t2Jfaiea<6oxvSbNJx;~%?I z`r0{M(9N5L&VZRYHPhgJ)7vCgI+0BQwV$!<=?-CWgB@}@MW!5nF1x%~yd=&a(LFcY z&qfY0>();te;XJo?dC;}B{HKF-ta%b@sPw!hqmwP_{@W^jn6vdTfl9=#^*s~35OG( zS&J4?Y}!Ys$=$0{I-*nhTgrLHM5l}vCpQdHY2ZgOF1RG1nfRXOw-^1k{TXmM{=dO7 z!16l=GAtB(+^zdt7MIglWDctdEV8O- zw-8!BktM>!qncL(ddtx(?ei1JKL;-Z%j=daG_Utex!iEa!N*nYc}=Qay*yZr>EG7# zgN*lkx@|q555Ld0>PGJ`dCo=|ZqvDxs57FpU0HoGh3XsbkALccYDy$68i|a^7|x(@ zV19l#PB(h7+@b8yDmtMO#*V+G|ZJ}WeLFy{%lMr#0jCb@8RrJa-(nK>e$5Pb|T7gsDFfZd2X|pFUO%adfy8>A9^FGPFIN; zJ*gGS_A|0B=JXc>vs#tsSh9TBmV*Y2>rej?eqXLX-J*iW!@(`68@<29IIT8H6=t07r}-Gh zrm;>}5Gss~RKvq3ux5h;@OK-sC+mkISXKIVf&%orVw};&4^JjQ2G^r_*;W z?@#bu@_94zHgF%X`oD)Pp$qvubw%}s2g>9vq5Wkt@2z-V^Vo}?1mA^6-nxiW2>JpE zUCL+CzE37^)0xaW>W!Wyywm3MI^>(dW?weo{y{Hbc_fe}95UYn9{bJoo$WKd^5yBS$R=W`yN%J~X?*u(59t2F8S+UiwywC0akB5vV=p}e^1B%_=@H8Yo@GNQM;lv_@sQ~)w|VKM4bk&-gV{z z%)+<}A$VWKFLb=;@qQcc)yQkXSBy?6Ux$bAs`?9T)Mdvn-4Az<;u;W{wUc>ahx5D zPb5x3XwiHsj6SJv)yS*CHNf)OV*YOXP5bOr&yA&LfjAmTY?2^XAPb>Ot`nWeloGg$BES~~o35Q%y zP4;wM^9S5$^jGr!1n-yhxC;4Zuo+nWcOXkRMEisLx72uM2ItWzwX{Bm_6uC`vQCG0 z&|}xZV>e<00eS<=BY`a8kn7;>cyz@-aGTM;g!kKgxEA?qU<p$nAO9bx3`_G&tAVsi`oXi&^eDkRSQPYo;9V zeycxoBV`hF0}`Yku>QzxyOccFdQhz?FYJ{@&ph)^DQ^prmxC3+#&fm#yVYZ#K@3gn zr%wrw2u=wvar`B6Mu*ZHaBVSqn$3Ge&#TC-;E%xSaX0CIZ{Kg9qG!ay6$_6ZRl2mg zx_Y|tnp1WvJEyfHV$LmMKk_>EFFC%?0?>GTOvn`$idDfpPDIkc7O97WhBlz%H2q4a zZw30w_*D>Zl-z4_$ejF?QU=F*JO?)kx@Y5Wb%=XLZ&4WEA?{}cSjaFG1&ypGt8$NUuw zmrPtUe`(G91vS;vr%agwrT$pi_2j+zV^6(k~&v^PN(_FxsfkV_2D z#!A1hzc3Wa^W#iC(t|ab&s^2&s${eZ)$yedAE&!{@1wk0t^8*{?m~q94!60x0WTZ_8Gu^dv~*(#rkrVtS@iz zUQy1PA!LQvM9S`eZ&l`X^ZiKAEV72xE{g9MOugQ7aVioN<)*hZzb)vu>G25iZt!zp z`9*Kh{010*5Btj>%`cfStjqt?-yuh_W(S`IvSz%+yEg3H;trENZTpa&=UGT3;AOx+|6%m-WBk&^=}#SN^lXd zysto(VC&z0c-!_kdEpQPy)QG(yv19~UF8GS4Cw;q`n`f8UH%8Hcei=3v_EekzXSFH zt2gp>t=G0Y_Svo+*)exnf_A48RbcXCzDJUO*Z-%yL;r-M^8d=e<@^4mFqs_{UHacx zttu+N;T!)?G>oxY@GpGBMBWC_j+qwgTYuE)Fz?n>`CWs2Ew~Zb{I~;I!h6O({Nvj? ztv4^eWNBS#@>tRg);+{)5Ej0e7Am({UA~KpJn!^w@SRJhQg9zuoLeqa#j^K%lk0Od zZLv2lJlw3VvL5nh&8rQ)3G_<-WNl`x3mgS3uj7#=G??=c_8#%%@F(DvIu>IJO}|EO zo!aWTjozTG)P_5y;%{(AV7VE??yA_I;GGbUXBTnU6@KUYui)@PZ%vo|Me|yZUR&R{ zBR>sx1Iz0zWC=Bqoue6A^w4m zU;MVzKFJ8=gd@BnBg)P!8$x6Q$;> zX3u@!^-uKf30$jT(D-kiPGfIR%_Gi2z7kvuY&v}xSwiw$q#17=sJzXxBZ2Ec8rJZIs$L`FNpP9 z?^(fe&1dfA|8{deb9?_=`i?axPVgs8%sDB`%Nh@#DO`r9I}h3$=Zqk|8jL>Sb2su% zup3xDzcGKeJnXYh`~h)Jy;_9Q4t#zjeEJ1^7Ju$eP!qmzbLzPSChI@KX>nr0zx)Ze zg>r5W<^0pnVwDMAElFNYuI5#IXKG*ec;wT;Twry{_-RIoG_jcEr-!)vK1) za1q)3Dt%-G2Mz1AgNAM|ia(vg7a?-D_p0X%nxsxI%*^ZGy^wP)Gu0g20K`MVNzbt#8b%e3-qf!N;9LOxphTjn8iC0iDfpHsWC-RA5E^pZEXz_x__rt+s zum0tA`X9clA2;iNa+Q9(?gsyIS@2mBeBREl*!uMz9^wp}MuUv|scbdTAK`O)Z(?+y zn&4iFW5uJw>8dYIQ^fI*$Qxbhr{Zb?*AwQbK?CJj;Y8UsR%wZ>eA-)v+6`_-AM8%B zMx5v`ld(7d4PdT--EUqfcc&Zf$N&B_`RhMbPPZ{3xmR(ETQ=dmbTK_izP=xZ2Wmog zRP{{{q|9!a-S5;J&cQgHZFTu3CUUaHF5fT=kr6zn1jHy%N=UVTuDtOWIm~&R@4xAK z>}Efqqvhs;+UvxRvOP6)pe5Ht)ZQfw?b}f=yz$sdPO5o;CajWOCOk&2Ve&I1{0(XjHeI3iibZxep%MJ9L zx8g_T7O9K46|{=q4)ez%uK-80r^ew9zh`)!>cJ(YL-_ZSki}}G81{K%=|u4!5R zc}HdlsYj5^VyaOTkx|E$p1;%Mr4PEH>s6F=yWUsNd1{CxxP+!+onGrrdPqBVH}X#K z17OqZE%SFfU;3>X|8=Yv;;O8I6r(wl74m{M_vrV<7=1_ReJbJu-ny{!0`t%~<|$eD z!qGn$rWN9zFbl6qIxS+-oDE~J@GIX8{@~d>RP!v`mTE6%BA*G)2A1bS^LN`$*k?zc ztCx3Ts=b>(fOqqEDE<3*H;2G5t-p!)+x5$zBEJlN1+4x*B1?!HyHx96qNDy9wlEb` zUL~s)*|^hsz}xAvpfiM-PG5DJ8=Nf}LMJpT=x>^h{^Gk*`cFna1-bjaPsA=DF&8P92$I?IcE-cHr*Rd}}T872s+hLDH+U z9ZAQ0!r5-JKzA@QcmPY1yT!<$!syw}JMB2&ugD*PPk~LZeMAUG)s}fv`umw~TV~e#m<~PQy^!wwG&J5HswQG`HeF5khx(^6fopcT zB)h+OFjb9ic%T?CrsZdGChYuPrHLHXs}~KD41woTpjYw#-RP+ol)BBu7|WX&E|uZF z4h>^93qT$jgt#XVs7EQ$T>uNnwo&2`8kTUW7Nqfib6Ux=z1} z`%>$RE08Y+mjMYV5Dmb?#mW1L$e*1f0GWc2Ok z-L_qP7x`208L;`~-mi7r{Wa@f>Y#ef9$P`>dPO7I?>xz-XO(kngb(L2<)^B<Z zjh+PYllrg{`FgMsSRUJuCA1m)yxnG;dr%%`!Rs zsINi$a7OjQ+7*GLy{71n(2l6v6g@lm{9$wlUfUkT7wb8ja=rV}EFP{3&j6|GW}Sv4=hpGdB{(GCC6 zrGcKU2K)Uv{yChJ5heT#P3zI)1V1-4O8v+cw;wmUp)bYwvp?EBL4Nh2>wSqu9GOD) zV$3<2)vTNz(Qa-*_c(9AQmJb?{O5Q4%3u9Yl;XdCDs`Rt&$mMIHzK_0vQ{!PlIgbl z@w8m%@?&Hf(qiRo2Ze6zs2J|Sx($r=!~(u9aK<-+B8czL-?`HN;I(ZaBd6K8QIrYN z)4sY?7UbUZIN$fG8)g!-ERqwtLB+=@ce@e#NupaPu|RG?~3*bHKJ7Zl@T zjKAynRa*ag-fzqCw~-$K-v?IzE@TPT9)JJ!Hz>!Q^t0`A#SV6kAMn1#5oKJ9Etfg@ zV!tdHt2G<_(T7s;AA~#{j09HyamW(7P=8OZT_Gzz!QDK0Wg|vN6+XrJkhu4e#zQ~i zSL=A!^IjY8Zz4Yk9sySGlgJWmysclqj_pPe@Ai6GVmd!qTUz;I5cPWHe3mwh@xWAn zm1O)Z|Gwb(yAxk$@_$FF{Tzlo9-IU$j|IpQ;>JJrPIE5nU_62jLgr!pTGch|+qfK#8;>xnLYiGUzJx74(8)$F&n+zRd>7Il;A3hK!4@6 zn(t%qDd#uR-v0*qUC;(B-^?b>caQO(|M3k6Z1>L4ODm}+zh9f$;=ixlHkI{$z_!JA zcE-kK6{tzHb>}48x|i9C4@YFhuts(d7z&+DWz-#0(|XNw9y}6;=OxHjf@=&n83!GF z{BKg%P}^q)@Yh&iOU`SYR->^#8k|98yCPI~Q3prGEp z$lOFvnQnBQ_hNiFv;EYCEcMr$c%vtQ9?74JkiQCU2A0P|$P#v%bxG?_p(76zuUIvy z;>=XuP@8|IkNlh!Y~p@NSM57-LYiLEBhKlnylAGHhx3F?mN!%%g*69gLFnm}c1-{;JjUL74H`bXW(V_`>qtHX*jrO^jtcs zes%+|e52;uWcWzB{sQ?;@CRV|{@eWBt~=OgN4}g|uj`ns1E|Z*?j;F;#!K%P8in_F+65^XQuW8>+wVw-+&j-ta<+T=BLKoUkd-S_q$FVJ= zbH`C{^fvQeyRQ6e*3mGOwN9hxXCiX7rBTnQFJsKwb)#1FLs8 zvV@BJbh~r^PY>As*!cEuNdGvrDcx;Izau+%4Bf=`+ymY_`Of1RU(Z@D08)b43}7UpF)wxFFSU*W)QWVg+1K0W*26K(h9m^y2Zf&)iahK%Z?1e znXnwjsgha4Hxy%+ud+KBniH*Ke8H&VaaVB{L4kVRm2bavRY~@@JGjn4F{QvPgw4VEUr|l=7 zLVg3h32b_ge^l%2@qkX}SDrgydfR;N-3ID0h=!WK$g&jd-Vfi2fH#>-IMdZ{h)y|M0Nw-=C2`1)l-SGxh_`)B2OQ?Qh5Q*q3M0Ia;Ip zU44W#yfq;B8`t^VEn<;q2Dbo)Weoid7pqIt9XQT+G_QH+wd)MmAa4X;1D4la$PyY% zzgl;I^*>U1H>d7l&94&u zww<~G`F`*qu>4AYWYX(yosad;@2}t5w|=sJG_`g`(1!JIj9;-e?l#8%6b>Fca)ZbF ze3KIRNRjg|mK&z9CZ5RpidZYPcsUN!G@3?AYxHSBhEc?nmaPm`{J}ei*&LWKFB3YqWR-b+iuE(7*p`Z9^ zp+2GXG4mK>933ejacUS;up+%7qIQQ=y(iZ+FuHg|sry`(Nv5mELS9x_Jgb*IWy(M4 zV$~z|$b29z8Y?!kPj9+{{`574T zvhc%2Iy~(9zX&s+^cZ+<4t7r)f3DMI3Guf1xe0kIxEt7X`4_T;k2mP_$atu0>9Rn~ zAS6lJLT_ZhA9CA5iT9b`ZTIetJCBAc)MAdB7cgU=tX_2aI5{l4MMKy-nt-#Y9%`GL z6=#($D;pOV!LCt3&)yr^$ z=M}14V&3wq$^Yh5e|#D8SHaD|#2w9lP6;uWx;G?hVUdrk9eLw|zbNPC&{W6l79LBR4`hb-aan@m1Dx<7t( z^SN=2nze8?mMGAYMV|f*(JSMdqHaTU%qCX;w|jpe2g0L<6@-`c?Tv#boVal@A~#uz zX-AI_#DpZ|-zt;g5QYZXEc*|RzNdeii8IOE@Jw|Y`|_&)nQGxtu~3Lp2ZM8~xc(#! ztBuUa*~DzG>)-FHA9C4Y$Y-R)it_hqt|U90%C~fUTZvCO-zB~oPjT)E6agFGk;oFB zG4^@;-xu9)eN1Hs!?4*?mT;*3GR8@(Y8R|tR8qQTzHFwFA>8WmsCGkIu?qqZiG%jO;)?D} z9Q%aSX>v2Ndzy?qT3q!DGox>LTl4DibgI6NL!Jnx0L!ZaS%Te9J4m`2UN!dcY#xV< zqR?67l=fl3hD*Q@?;4Ac?dg~D-ts?a9*yX+?Z#8c&w`%=%j0$A!{f1<)CpYI=arot z@L1rC?SqR*ctm*D=#D&^43FYxQuEJKkr#l)!1A~qS;8%5+*tSB{neYG9GUdcJc63j z7`-`tYt(I|{0EQxtyG|gydP&elW|Ff^I=9keei;o$7V{NdQ+*ZJ@s3bOx+AO&&`gl zQA6B*V%_8ho@4*%`e&(@D8`j)kehE7km~=c)3XhpHa+uyLR$a^02_~y$P&7quR0#H z>VipIc|TT=hB9xI?5*iT{}N*pD3-=cD}2e`Hp62rdMuCIkQ>3>!1CCEe0V&P*;+AS zmgxVIb4ec>9khpd)#N}sEZ)`Wu$T8+{k?x0aR!5-!0JB^`3UsyFB#Uk`$>mp!=oNO zc3oo|@(;k1!1DMISwh##RgeycC{{PS|EqG<@S#r6X+KN#E9WC$0>nw!@b=IZg<)#bs%u~ex9set!DGhn0Gpr zA8TH%=(XwV?dA>;kP9rYzQ_`usMGazm3i)GzGCXF<|PSCv){_>JsnE8F@MG_!a3n+ zcZf9hKVt+P^u2YTXdad5vF-eN2sQIm9d*_-fugb+|Oo=vMR9oYKZ?RXH(p5h#$3ySy7Ys)m$ero?+k#%w}YC z$Pf3F28Kj&>=SyaMmH)Zf+`*Fv(Yp?IK*N#kNHhuAsgM8GBBbPw^rx*e{?67o)q)A zsSRKaS<-JBRyH{dkHuW_RN^|ipUNI$E%vD*O1U#fZu&n68WkkGUB6HFo|2G%`5SIs{Bkt{y(q~SYGRpCD?O`RmLw}=lWx&VdgHQIkibM zZicc~d4W00S;1#7ZPc>WcdiYUi>thG8B z+{AgR3_j2BH!AhC+=n+Ixvo9^pmSFeg?77#D>6t9wNnQn& z-w;F2^uR{xIs9-&`JmVxv&S2tPN5Bx_!}Nej2>y% zzK*;V+zl*`ht1#ZIC^py*9C(4Z<0j67LK^RNa@_}{YyD-h9-&)>nZLasU+WU{a@-m ze~E8AOQ(wD4 zcDq*}a^49~rl*Kttcsv)lV zHyeJEu6vL_0(*hw@3mrbyEpMA@J zamRna@|2klmkeJdPGw2D3&mfR-?j_)AU_Bm0hZsR z$P(=M#MX~?{y;i1VaabQyE{*Pf254iDz?aC@;3KzPUHY*D%%!;1$T?l@4TGSe-!f3 zU@WluPe7Jn@4NqsStm&P1v!}Z7IzDS1WtHm$qBD*?r!Dyz`4t1gJ*b<7>RtH4(ril z+o=bU9|yaD}^GN~SQ z8{Ar{9uE-1E#H%U2C#_uF{yMs5MG0;~TGWC;h0_t-CYES(*jh`I)y29J;i zz*!ZfL1UrG|JPFbry`#P&IML~C9(t?{};^oJQe?gYNva=PIr$#p?j3X_|S8F;IK0K za?;9%`p{eRX+ob(kE~xsoP1CSET6u}5)RT{GJ##aa8=^c#LA18FDwm4Q^zMdrchmA z&&2LmVnJ)h@42EL<{wdGOanP?9Px4cb{QW%B{;EZ5FT16STh^R9%K<^2Tm zF7UMBO4cXS&-RDO^!xw8_A=7>GRKFw(`@)fUr+UuW07Zq*}$gjGGqz&8@oqquePds zC8nN>M+7ws2azM>7gg1DD@*4fOcvp^`KuN#nH}u=OPyDS*w{s?>uEZ@(O zC3Ln==mK9Fr%pz9d3C4PMj=__%o(T+K*m}N_;^C|nf8X(CwwkNz6x9mET2v0?>2w! zvrBwB8LMg2kVh~LIky*c^)!|hlLjK?hR^HhOAvqIllg0We1RUo^67^x;Rw>_EOBYj z>5xD&fvP$sP?O=a1bt%-pF5E60S^Gn=V4?CUon0a4x@Ypd`yQQ9B`wE-H6RbIyv^B z>(p?RQA*JS_H>U9&hm+#$|9X_d(mtA*TKKR2M{O)5~N<*dC_6y*YpeOaJXE-IecDG z1c@T&5_`PQX)t=%qQkZa_aJWvO~9td1xGOpCJ79e)9wKFFM(yH~(vz!KRWDzI z_Pokd&Xa;$jnC9R9D~Kzv)Dej+h=h4>vV|zHpS;?VoUkQf#*D z!=}#gXh4rmmmeZO1%3)Fj~3)3=0VL4R_T+e(zp+gR>Pym?@~M_BcBRp0n6iTS2WVEYdzEPGB(s*=1;`MnD~Ke z9iVy7gO{yW*CTHM-vE~P_mCybFzaRZJi~!`&$0bU&_2sI%DT{Ra4*{=F1IQ-1wF|Z z+HYT+n|rf!IZ2!rVSYV0JGj0Hi)g&|Nx$4WQ1f)&O7Sd1J^@Swmgkws66%hGXKh_O znw8sSfvO*ikdJtO=bjO_M68Qh81AVCaN0PqEFSGQnI(#rK_>r=erd;>k)H=G!18;= z{N1)s_IcoR?BK6qTDFY2)-h@U_-i;pT1p9(gSGxdYid1uHu55{3|RfO$P&hyeFJ+w zjE#Y_$@SNG3GAMVHLu6eYwN}P$e#(P z-=}zmkR? zvIJ}Y(3N&Qm0tVUt>?u@ii_mzNL;3H3vf?{jjErI^Jf#946k?4Ytt+7HftbY6tKLG zL6&f2>9yY>!9f$Oa)?f^di0i~SJLZIIcq1J`Yp@%Wny?1Uuh2ymHoIsE}82Hja8CvmzCe zmp8x)aMto@$;}qSrwM(wf82xo0r&)1K6{ZRG}r2N{lhI^41J}z>84dI1X$A@5h)W! zYaM?r4L0{gz^7uEPM`8UDLyNa>%ld^^0^*a!jYwql=pUY4w68vhEEInZ2EkT9QtF# zNduNo26B5o$@IaXwzO2d@|CV4{`;TO$X`s#RLo$LeVe-&2TG1}lHb?UuXQSh>vWli z9?N4L@{M2K@9Pqax(enc+uUW2kE4QVes9Zy{5L#W(UU-r)URHD zVxJ!j1D3~e$P%o*>J!F4!s4Z?maJY-ikqtubEcm(Y1Zu8MXN5FzoL4?g6hS!Blz_N zwaZ6bz!g(9;-rt+UG@A6=Bzdbz?FAMSC_|L%ucxlV_b=vgdM^_FIX-fjs4B|VC_%z zHk9i0S`QDKUN0m64!jL4&kv9#*mVZGj(r$Bi-$0mh9;G+rX+BY)sc2ySrw%{i$kqD zy$08r+K1_=_?wjtTYAMlYAKJz2+gStnqvDnyLR=8 zg|h2i1jiz$A~~&aDh#g{IM{Z++dJ5{fD&NSaTKzI&gUurOMb>sxRGgnFstt^v-+KK zDek-y98b-c{GR83C7JCUYS8Ie51#~lq~&2wr^Z7^S9guA%| z(^p+*i7-7&GhM->&G2l2M;SyUJ-h!E+eR=HSe|2$C0IX_)(^yglb)2!>Nz;)lEHYB z`%AsN-J9iY67?qK)Qy4a`e0mJ<=(X*JqGl0dD}y_m9XyFn-Z$%=3u( z&JOC6DX&fL4>jAEX8UK?xui&8Q^|et(05Mrr>SXr&0w^t_wT04!G*|If@^{0`!!?< zw!HqY1M1kz`xW9j()XQtLRahHoo;67Z4oaMXM3 z?|`wu@|}V#;eW|DS#BHgHQb&tF4eDvF}A}deNRd3Io=F@^cJOBP0@?P*C!$JC|&h}OdYnQK@ z&zb!dBWk!Bv$S}~3?3z=(~V#J_-dvZS=j45>fPfy%Zu5x9>6kfp4j(DMR8h<-ip7c z=08=)%Rnu#>97J>g6+TU`dTObj48;Cyo#BOvL5x8I+rgMKjwH{3;t(5D#q$~KF0fO zJYPZn75I(OaTxK`+k}PaDx`=OGT%4v@9}=>{l_352gU;lhw=WYD@b4dk1=Q%Ly1b` z+(FETc$3kyo_E@LmFFb*~8~hyD^mq+f!VKf* zt%o^Be^5Tb(K+H#Q1=5lp;lBYq_~#yWk~G~Hk-!a32F=$vr?pkPwYU$*m`1MoS+^2sZ&#&TIr5IWb$>eN7d zY8CoNVQ@xT%<_ksex+i(<}voe6pyo!D?v4|JZh08#0?KSzc~Ppiq$KZoTS~Y<<%`? zPxevodY3)GK^W?tDym0dDkB?etw#T2yx-0jUPpcl>;YE)JIE63ywj$Go?iv?yiQc4 z!6Ps8DpmwLdCUNOz@1ZOCog!NQ+J|?|3@kPbCDN;Wx(pMMV4U8#XkGO``6A;?<#zY zv6`7HgVJrBU~qik;J0R`5)(C#X7t$h|3l>e0QE_VM<%ia+t1kN!TVitSiDjuwi8#Zn9s#irexHx zK9e>p<~-(o&2>r#aujYLl@vy0>TG!snhmdc=(YK?5&0Wn8?fo~AhLwXraiUoMpt+h z51DUim6T4@Yxs@s<-thnESY#c<}qv;TZB`_Om~hDdma~!nqc!uI^Fi7KY@N}M@IgG zHE%E#SbkR^OSs#d8{M!&xBG|9FWJIyXz$Ol;DFwlaxm{P?=R_2`8cXIJD4YtuJUe+_bbYJ1ljep|x^uu1LJLm1>K;;yc6#k>sOSs0x$DcfZ#l;m2 zP)ul=rsLU4JQDm);@SOEeEoxBVB>i-vIM*SVcUtrjwkbUS`R-ll2-j}K= zEm*GGCp?+84twbd`YNVte)Z^2@Ll-*5cwtW8nFDnK$fuQ7M=btZ2zzFVWN=4JIm_8 zIm>ft!Q)tQ(ejx0t#s#-akO@rZo9*xuXr4V{mk>nv5zu@bB^gg*VjfFF?hjA$0>NB z;}Gb}q`$XCg-L(BtLN*ppaJ1oI)3x^rt0TLxpAI|S^~g>8MtqF1)fSgi z0JKv^e=E8Y=#up9^-p|Sfn$J8-PKJ;Q@KnI(JC zk9pfT3hxe5+2K*DT4iHG*$Zz<*_nY+;B>{lhZNqr3Y~6S;AO`@FC+gJ{2o}|e?ykA z$JpB(F>g*$Pno}PiO|-wVWIjvmw{RlWnqC^B(%nGm{PYN6{l-{WB--P*Cog+!CGMX zU4tyau7`JRT`}42PGcb!gGymlvC_1&Cdr2JEaV3b@%+exAzTKvr6Njlwa1=MzAHW42BviG!%vjH&`AnUE&G4}4 z^eOVcz~_eB{~(>nH(ecjLaC=SGfjyyLc-{u_IWCu>XB~%n}AKHJCG%`SijtN9!@`& z%&$~y#r^Vl{m0b*ak=tf?g*STs4u%mNh@`{e}?*gMM4@3co-G!-3^D z7FmLAPwhFQgXiN43~fp=r<};`@m2Guuc{W)Z|o^{1f~={u828Lcz>bdm2fg{sGBR~ zMyu~gQ`&Hr=Cu~RcAWSq@(;j|3>V3_!`nv)_|PpS>FCyKarLS*RtLUWiXc?vl&1C# znhg(XcC!4GBToV+2Q4Wvf=B1|qAsM%et86YL@Rk`AxsJ#JP+kavOBGYM*}(&e3$k1d)y!YH(%zI=`AylS%3+u4NpZF^x&&9QvP+etvuU0> zp32rdPv=vy8%^d@E%GJc3SfC{M3(TnX}7!9KAQYmXlbQ7mn2j9l@zLqHFSeHOg^6U zzU?~YMQl!u4I1-c{cVOnTeqEcXKTK%!^f7ZzAEaJfKp)j9)~Q!?h{)(_AcdXC%)pF z!^QwFHsUiquWq?<0raHDO(3WA!7~iEEJ(_(@w0^Lmo(3Mcvzm#BEJFN1eWI~$P(;4 z)}B8&5}tbTr88AbkbkDWkfS+AeF48Gy-l8T#sL3JHH(`gup#pF%p+)dk5_+j^=4MX zIhy}iFU9`~t54ow_Ks*@QO3V;(wfzTSd-H~2QN`T8ufgtb@deqj4uUFi?@UticC)%!Wz-}9lq zR_ju^Kkj_E$cJ!jrvFGrY8^N+!!|9i^#U3C^}-mk-}0$$RNW`?6qAJ89I z-XoADtZUG`PcU}fUEocB*P%F48re4aP;Z+Q%CtT9eK*zp`1~}aDmsZj@+HBBNdA^zee;= zL%-DHKO=t%J_D9tpP1%1$K01yRj+w=p&l0xVGUN(Ygaeg{|eR|Prug>4yN~)gXvFs zANtOTBk)f&mXrU-FkZSeR2Z)Gi?I=AUC51v0|ylK%BQ1ToAgFO{avc#vjn~g6QAwK zJHU5M9He}9_6L0=@#s)jRuYNP@E+~V$x%l+@$)s0Hgwwg(x~*PGX@+7BnXcq8t-;W zPk2mOgSW={t7@gn7Q&*?IaiN(%M6ctblUpy1oBV8^T6isFOel2S^a3AnI)yky1|g= zBTikVLuiX~sz#`{g4Lbb>^vy5(NnQZr&EuN6u&c(=Yey9gd=ZHI`Sf&lD8o}#i*tx zIjX@Z)9`3)$K!X%tzggpo`;zx^(STeJFD7_Ynlv?vdn1m9Loaa72qOZ^J^WlguN?| zq`lxCLJla}2}*eI(6=AB|T%+@=1^s2{ zm-eD(R@4~)N`U1z7FoibX1~IlC_cnY_wZgm;TMAd*$f4^HO9Eryx;_z~z z+QK~>Zjk&=L#>WS1w3ti{wne}!Ck<{<4t4{ z{EYW$H)pwwe4lf%U2z9-J)vAY$ddjY?6MU8Kla`{E~@JN13&lPnPE0|l|^s{+&~3J zTrxLs*GeUKO$9_y6odemvUD;lv{KqPO)_(>q_WLSX){|)GPkUhHb1s0rS)-1=J$T? zJ%<^Q0mRSm_50)ZJ@9_cGc)Jj^PK&h=WLzj{<_nOKEN=j`LnVq$xoK=lKltb(!smr zU>y92=`tNwyx>ToIr2J1-whY$_L4tTWT&J*Ch6~z47&wQ(MFxnr{2!PAK=b^kAi;z z*bIp99{^8g4DWZ&eC`^;uXUJ5PMejPQB;Wf$X2HOYS^ksWy+hH#1IAVc-Gn3rzcyd&>VXV>&V-R0jcDlh-R0o}DN z36ORI>(Eker^60Kv2w@B<;#aGLcW*5zXH7OAzzd9UbW@(Y*==qVd?5_SjxB@EZCin zeZUU~MgbxnCxIvPB41}_=kdV^lWZpVAJ%=(XruiSzs&*Y!?Yog*S?_kJOsH1x z3Ee|0QA6fCwe;KwIYfN@4E_{w1`u*y08d8r6Rv8UR9nuPdId9Cm{!J>b4sbi2FB`g zskj&FT8mPTa(|a5-_U06cJWT|PXW&YLcR~clX-xzFMojF`_+heU*fItKl%)hyv$s>%5lKor3|3yI2=e#af~q@lh2Lc2Y>}QP!d-I2h+Y|02u3D465IVkMkfqc-q2)9p|D zBxt;GX7V07Al45pN?EZccRu72a=!q6E3gd^@mm3&jBbV2E_~joc>=Y58jY*ia5h`^ zoVkS}pJ2_wgE$(Pa>aH7n>h@*K&IuVPvE2lS=QrH4fA`FE}2^MF>kXbv8@`U`EZ3toA|7Kp z%;Ms?TYJxM#--D!7EQ#Pin`392Q5ANRC!3pZlX1eJ+N`O8}4h#Qv3euI!2s-NNYS< zA9=4N&%#keL-^^eIM?qnEj%TVmn5d}90q?9_yZ8(S=dqwk5~uWwek6)c6cxfp!oO5 zE(i`T$Ne-|y%o0vcjQud?rYBS6A_*^xT^1I3HOjsuS-%8MjbT_AAa16X1GO5 zoG%%Q3{r%JUIXyg-}sK+H^48qx!!-7tTV``CI1$rn#cXMC6aWPN+*#*93zq*$WZDt z@@IP7s%`m1k_OA$&}G*OZorFLd>lb|Mg12ZX=ELM1VF?`AMj+#`F!0~&3|1UAGJpz zn8H7%P2s1~6#l2U`HgLpqjXbI5VCctG?bVuFO%eaO2GV=GB-%gK7pT@7ZSwou#La?gpbLEJ-xT6o&ueKE` zxrsQ$ku-9esp9g@g#UJYMe)8B{5wE7AmsZBJegA79~0~48{JN;;<+2@8&DW(zM^J2 z`6-&;^27Yr=gKD%&2P29O3$t+zUmHF68H{}dko9BYw;Qy?befz20sp%1dt&;LX-65 zR8nh%)8@9YJXQ{aXko0cy0)m2`(FybM7#Da_}_s)0TFNd)|xE&d|lD>tI-SAk^}1) zM&;4IBU^=Bq3n~T3c1-nC<8xNR**6f>MyLH%aF$^9q43Eim_XD8PX=)gI$x{tZIiA zZxbMwkoyVn&jM=!A@>&WWW@RlalS}X(py{Cpot!wc5|u`66dgSk?4S_2mXxnIPfR! zU0x5se=+|N+s4Qef$jjA|5du?sHgtu?7h{+b)PGP*whd?UiE^NaXA*iPa(%P@NWX| z{Ey{;8c18b7LJGsXIV4@Q^EabZQb=oSMbTeAV8$kDDY&&`G!}w-l(P&YKaq)|0Go% zCR80?C?7~HG!|_pRUKTDH_ENaE8VH3Qz`tn!+)wbegS_Ts0M_5=60HV5xieNkoWVi zE+0X>I;sv{7|P`rm0hw_E{E(wVfaG%Lt$+aavvR48-l5i3Z-0C-waUKD)Y&j zepo9n+J%z$w0LsDf0BUG_hayffo}mJ-x=^^#Cm0MzG9=&m%4vkJa_WelDSN(#&Sh$ z)Q#DS&T56SM`n|vaO8L&ES9+*3(Tl>PzP^}VdpMCT^D+#_ci&GW8CSy82l5!DnQ5| z606C7gs<=E`uSD#@2-#^n_a{VU8TQMcidm9^#8_JwZ4Nz@}ID=nSFJ4LG>i#h<6{O zo$dtC5yr~-(4yF1lCX+q1TM?b-E0_S_(U>%s$NbW0X-p%zrSRTnvI8bpGf*>gG`&j ze~^ryNP+qwSzjRkBhfMUdQ-UGh%uQzMrAZsqd&!`EfRMEP03-5h~w=$=vx`#FaCPS zRf>#@p(3CX3!d$F%xv5*`9%39AvaX)=lKBf6vOkuLGVX_Z+YBNc@gVTuS#EkMf}mk zmsqLSZ_adDHc?YWQ3;X|ff73^Nvx8~W$)mYYZ~|hU_Kzy_a*RT9=%UX*Rxx$C4IGJ z&?1Q|bX!gPbW(*bY9Ca16-u=U+u-`>Zj{Z&(YjaYYS&(dn{dSm-8zjax;xA=E@x}) zOY8Moh4z#?MBxrEQ%lnA6wu{ss}%VVTuAeDBWjMNXO)Mw_{ZgEHTA?`@Z*4601?hx z!IPQH$0u(!e%^{V&zV_;>X-{<(p_amm+HPy*65^Co&Op&jQ8u(w$ysWM;I_rH5dQ(?i=Y}YqtDA^=;USr>{9S;b`Wt;vqi|DkObd@A z&K=)Vz~=%5fCx`9crs6Ned^G=8eJZsy{0Yrg$M{HGEXY%L}ubvs4Od#k96z=X8kgd z(@P|}4k=XM(i%$Q?UHo=s%ujs8idUFqO4@x?hx>WOP+|wu-2GN4sS1|`-ykCNr2emd))UVql-5{D}z;8Yw z(2T_)DsGbRk)a!!C#CAXljNJF$0X=nI^b0Ofrz>Va@XJiE#@`ftw=9PEm}8!S7}i! zHQyz*aH{lal_oZSTxnsW{e{VYY4K=JaO;0(f?ojK35a;y2wpXtwD_xh{aWHN1LqSJ zWxAJCsCim{z_d#*mFvgvf`+C-`P$5mDm{@AQ01xQS8CAG5x_iT^iV9a#uY7=mP(jC zGztMp#o`|M04`fWy!690*{@PhEk`b($pe_u|7zhhI^5x$1U?^_3y5$Y2TvyFB`thQ z%db70)bx<{o2tb@3$V$Sui~w;yx8KjMXIv&QEBVbP?=RI+e6qA>_)^Lvd~RguxSX{ zMV4RT{t5 zt5!8MdlZXHo3s78o;qK|qQxDHA4#Od3&FKT0Th|hzR>|8(D!K{LQDa2aCEx>Os1k} z2Gcs~Kcr@%{BRG!?tf6O)UMCvg{w@r!l>>^Zbf^NQMj;L zMc)S<#f4}xHsAs|DlED@>^zU7f`!{%jV9zOEW4H$BO9io4Iy*5aQdhl-m?*Jm4$H9}STCe4U&#y+Wdqq90)gPiFGAO-0X&W+hWbP! z)a+B`sgRi@&p}VhDQOi4RUVX1Hz8Eez4z6jRB3J&$(`^+Sqm*(J-WK*owC6f0t)~U zuC?IFY5}Pk@FAGR z4bjQeTM9%^Fv4GNh4L-PVM7oWQ3-xe)^s!ZVVc}kkShjqQTZI!&B!JKQvo6OV(?@> z=lbY%SEEn5B0VT5nCsUT*^bANdN*aJ7kVt@R@?eYxeQYTQl%Vw9CN5BqB^$1&=Wh} zji}!-d{TEK3j#s_5x$P# z$%y{oS-!5h5$S@lcFo`cSqrzn9hXkirJoyNQQ_I7;HHyK5c?+C$9R*jjVV!{M?LLX zXn?0l_e!`G8J{%ch}7hDLLQ+f+X;RTuon>W{sErMI=2x4 z-DgZ}kD-sQrS5&)HID@gN(2X*xSQs~N6F>EU_ejB(_joM{F z?5v@Wyh_#`mUIKC!}6$vLE7CY!Oa$ggy?R;_Oq3$VJS*Uwk0(p?g^HR;SJXX??=YNDt%=@*t9=ZvjEg-@@7(AIG zK95%P%C+Pt?VdC-qNe(`OuyZ@Rqdz!$H+>3`d~F7LaX-EKB46>`Y;_jA3E81P@*1K zA7EEA5Obbsb0MQ zNSG1aVpyq{O7u_4D^Y4Ilqw^8%O?@)tnN?_(U`lx(WnbW)f6L7(X~dO%U@oA&*8zS zftBWgPs-GaB;wcp7$N53*4zMp_ri&8Vxbn*6R>cuNo-5#D{^ zj{`peBD`n8lWD`x0sOF0=f6?Kj5E!ysGmn=78Rst6j5zfBHg1czcQ^<$G5LY?37QG zE=9JQTjn!$(!8EUCwgAF97ZYguKhalWE$#lUN&)|E!pD+OHgz zSdUnwfyb271oxCvKzB{93ixY>ztqn8_C}lnkpLOeC(XR7b7y$`%|U0~twpempF%I(t`FA8191Quik~LUYh5k}O=*S0`*1c%>~hA7uhY^e zAAX5^{v!A{fpOHBie6`b32v|e++a!ki&^^_l1FO+vL$GQp4 z2Cc5=3$n_u*W#@Va>PIm%0EAYzesW=x#b)Pp3HP!zjtbU{Z5^=(aPAa~_ABMS?2chrAJHDmA%NCsWGL8{Nb8Pgj{fwb!x)S4g+-Lw&ekc@o#y;q1T}m{Nq~i!r6BR^9e~ zntVf(-Fm(S;ERC=0U_Vh;K@|+eJ58p|L&2OQBiN#Dr$9C%mL*LtxQHMljPnNb1UBz z1OM#(HTh1$e-VGN{fw+9kOT<%^1+j_bNy&B*OxXr{@hy->6}haflH-FwFPo3kw?tz zfbx-^r9@%Om4G_(R-KGCw6Blcg7(OCrpvm0&ANe>8vJ@jq)A(d&b`D)> z5{+1nX3Hh_2Cjg?njBT|lVXJGkAwlp1HeE)#Me^rWW@TytB9|d4wuVw`7Wah`3t?e zq@a+N6dX`~Gq8uH1YI{nYk7r){gZlhmF}Xo>ajQk%{QkpocsK*Ch&G`26@VV=9 zr*0+g1eTn-&9EqNKVku!y25r2diEjm@3_;n*#gsPN!@C6Q1Wj}dE;jZ_syXOlt33r znC$3>%w08H3r7j$6zy{b_^*J&T=r{fpWXQ-yHH(aMMV`^ra8oJ&dx$o@|FdR(Budm z18AuK#R*}erOxN_Y=qw;UVaCE8aU%2Pm}vC zm&r3siy9$T0OBTqEkzffvXN?>T%P2??)LZ|@Q(pc10uh@0iMj~O<8Aod49vCeEI1! zpda^G7gs5rP_TKmQZgM^t0OEvUXEF*X7U6q@4FL6!L`uELVX9liMBC$mRzi;1*SAb zOJ6oblaK0yj^MikJpm#A5dOQEcNKOc+6AgVqY?cD) z^xYWwdbJ-GaI+>)G5izd_XF@>0Q2V?6cF*W1w5HCi?nw5C%#|55%C0{M-~iFXNI&0LY4}t#C@%5#(xg; zJ7x&b;Spt(!lp*)reLx0 z6rCCKv!U`m5)HXCa50tar%W;o&<2vuv6}pEK|Yd@;#;~AeI6hH5b}2iPi7t0Z=QSg zD)b_r@@uo;wSvIM2Co^n8Z3zWN+T;u)V0#>mt<8dov4Hu9r7GYC{407Q^%2InJoH`RF--tlmfGIPGKS?(Z6K7yp*(6QgD=m3r2h!iDlFb%w@=pOnn|+2f6BL|;GYLJ zc*xbHdde-=keZ_uq7hZmY^vx1r*QvI!Y|Q(Xf*m52{@2X6P$BJ#Sm?^~>EsX1lyNz#Jmg3iiM0_x zcYqAV!?nwCxgJUrWI>G}Ra_1y{2U7ZC_df=zZcjKi1PC{crxc!YxP&}(yOS)-0^~i zb2agT0OADT69%=%o=+Y2ql(jryF_}T*VF+!;zQ&>+BOU2%W4TH=Cm_*F=gr1HO^V` zQEd2><>w@{FFsU1SEXtBdT5HfJ-HYBYG5rO!tpkEGLEI1{A2ifpGLHQbg8>2cW6!A z@k#-ylDEt1u?>49)f#^qqpPFJQaxKG_0%OAAEuqZaY`HOLQL6=A$9X;tn8C_$a1vW zpg1!$`HeTb%ViSyk-!*0$e$0M%uigewC$?qqif`^88by!8lE>DH%OI+sN>Kn9aa7` zu^mz$OrG7VVEP%+|FI<5{V?|t7aT=Z?>ODpGPVr8BN?D*nnttqvuL8gIa8Cr9P){B zeF40Fl#!VLGL)Zh;(DR0=)Zc#`{>M~e(FY5ye&8&k!ur=V$s957^FnI`;C=co)q{k zd73m%apwcnbDBJZ>1d^J#9ugeo_VNQS+*80mGIM!@KgB;7!4k13y5^> z0iMi@JbwP=G5ucvKl8lwOu#iQ#W~qEDzVpOO7UQk1=k2+U?+%11kX` z@3Y{^6cuabz3Bd{the0q*49a)?|V|~`>I{vZ3u9>W%B z7SKx`iPO85(orZDT~}-`MLN%*?JZdG!uPVI$ePQFqx>INNtQq3pDvFA)g)cQTcB%-pJYk9RVSK5_mE<@%TP>C}hSP&Lz;;)C? z$^3$(qYsO*%tW%|Z2u5_0!>*D#^G-Rm||EUi31&-^ELTXCb;D<2EP(`1Q7DC z1y4rkSFd6n#O3mjr7Jp+!GrBL+AvH~vRFW*&JQt-Kb_eZs+D%=%+%a;Ud83Bg8w3Z z!zUsy01iONHw!$ODqg=jK5le=$l`~SM3-3Z30!HBN-T$@l^EUqpcDr)dm>#^fq!Jv z=OtaSN;Q7EafSK`OMS2rLvL9R0%P={%@;hvk4uPk95;of$zvoerWk{rhlVj&-GsXu zQgLatyd4f`%e3<@Egg;^93q^`BqIw3LIDxZ{@}@^JfnrL**lF6CyuPZk|wI&%8a|M zTa8kgvDH>AF8o1x$d{!g%Bv)II1xhhAvHQ(4W1e@bw*<_xpaqWKZR;Ph3X}o9>JHV zGJCNWo{f-Kgl9kaAAysA2v5*0mxt%`#;=1Ynwed|gR{Y+24{z)g{Q=q6{zV3L2J^j zCOm~0$!jU4EeWK8fpj`3X{q#OO?a?UtDOi>*$OQ@3m~ru&-3870dE5$JeA-OGGO5e zg~m6W&S((1?hvJqO1jAZ$doP%X%#)4mtucR3rF%~cR1#OzZbY45aIX)JQ+J**S~6S zqr*Yvov$g!w5)cuY;?Kd$DmyXsodbZ3*F|Ql-0qkMWXHwT`O}JV>@Ga`9ay(MV^C& z=5EBvGXntm4xDDD{msLyhnZWW#B7pudMOdhld$G%iG*wHryJU1N1GdSz!Pr6 zLYDUE(7DkMJkTXVN>*$XY1q&CtQIdRQ{3(ReDJpeivTjz&oEy7`iqRtc+`S0Td+r6 zZ#Agzs^I?Z!MpAFhT4t4!Rx1D|1%)cp)Gha6SzJvzw!M?ZBgkctTMyKSmgrNVoDW? zZ6A7!KPev=*c%u(CSqk1{;kn;H1P<@=*tiK7_Dm~SLoz6$_N_5kg>1j`5*F#e%K4( zUkA1WLf%8*$#mu8#4&F*I^Pr|)(llJ%6i8~9c6{kDC;K$C+q)!0`RW{^&)l)NMW*% z?jI~_mdrkCu3sYQ&<7ev^YV*uyWvy=KGCvQW?!$#?MT(+qIjMNemam12)T3l?_!)K z>_)WDqya}92M0uAcs11Df|Ki(;_54a^@YY;i@-82M;ZJP@%I<_zk&1I?X75|$+{L0r3KUB9`Y(K zM>+fy@gb)p{{tpK$kC*Ex;o_Wn5+wg5P?jm-Cq!}LCYUQ;ink)J_PXL)Bq2cg8 zoaw^x@>1@9KKv5-^;Ph%18;ly-=zFn+ka@wXjn?68B$!99_3t)bMVs+xhQ}1nr>u$ zf&Ks)D!)xik6C zmODLe06!eK84&T&r1ZE_jw{l`$^9>ZU!r_efd2|O?BRct(&KXf>rIbRE{A=FTaGE< zrvupl8fCS7K3JBix=`_TRa?0RfO(r(~%Y|`@UTku!J z&l&K_O!UJ6A(s_AnaTXTO>u8Z&HbBAkO~J|{iPjjW!Z=B$XaO)E++-pVr(H&cO_#y zxmlBM0{j>9Jpq0VunrLNZ3Iu|+TyL&f^H;9Nb>8F8g*DEja>(H)?7i&<)!e?u|<>b z9Q+sZb<4(?AHWTOkZ%NdG9vzpxqj$s;_pi7aInmMI&~G8jaGrJ#ol(N0~mDrKg#kz z(Mv`<^^zuMDdZ4xz6pL8@F5`N+ykCWZM~&O{=22~^-~JP6)7S44@kIPcdhg?;s;>) z*tLzWrQ>DIzkpe8|Js7@2J{4ke|^D|5%Z^4+-rNeZ{^bC(k@9Vmu{wA<7=hm$T0w2 zNZL*9(I#)z{4K_NML0hKzZcjK2!9WOCsSWI6Q?vHQ)kzlVNAKjOKhNB;&Rz%yXCqC z{A?f}5ONiPC*wUnnjlq8tlgx=+C7LhfMMsQ7HjbE6)oP%;lGHtOW^f68214}K0oke z>WjB7|HF7I<8n=aze27@z&{Uc1cY2Kf+yoW-kKm)BjT<6RW07m!G9rNLavcr2lNJn ze97R+)E94!O4&Ku@19rcdvX4xDI34=2&5NLYcvCXbF9ZNor1ux2pL2ogVMM zAN*s$(|`!~dj7k3zgS3JQ?Aiuxr8v|FjYM0Dn4L{tl+QgU09lnoo8H&-y+Mh4fQ6 z-wk*K$N`dpaNx|7Mpgkl58Me%1UdjRaO??;0f2{rIlwTWHE`~6BRc@R3@iaA0$l-L z;6INU+55mVKp`*!Xb0$k?;gec5>NtU0m(oFaPASj57-JU2c`o(fI#4fhmC9}@H8+V zxEY86WZ;{JFjfOr0JDK4zz;Z9g71L`foxz9U`ClCmnEXEiFcoawjx&S`F$@}0BupYP@NCSEU zt$+*nBFw;Bz@tDmFc@eF{CN+;3~U7+0A>IKfR@0yrPu=xyb3%3WCHyG8*uS%{05W( z_XE>`zCcUhze|vxfUUqvARib3!~wp*&v&8E06YQ|0x3W|U;ut}BEJC-0lB~spfzx5 zG13~?4y*vC1HFI%;KxO{uLpPlm<0?1qJXn^8rjFdb3g$w9IykY??4^|?g1tMaex8% zdLj0H0gHgKKnH*Uhi^wXfnp#H=nhzc}|Ff4}0C7TUd~glb4a6BiaPSHyz@YiLc>EB~9`zBQG~MZ%#rk zRSgLky=SK97R|*Rs26XkrNZe+2`Gn|1#=({eK9>Vzc7J_mxl`G;yy{VY57pzXC;6X zp@qwByyIzevl{QXaV|-cM!Bp>#VbW!>*%@ABxWc*iYPI2S@ZDbCSre|T9Bk-HD9W3iIj3<>v*)OfG|utN zxmj7c>2p#uYWf*h2wY1N@x-+3qSW~X*(gno^J#jfDpnSb_qnPEyf{YonVA3?3RU98 zQBmfCq6DwCp+Q1-B*4OQ*A48tGVj#*5>E%o&*r z@)I$duX!W}mz)IyGeycvypXMQ@a4{DT4%H*iRytqX@j>@nwBi&mO z)EubP%PU0t!|euK)mJ%+H)SqJr!_wfyeT6OOE4R7GYwf9t$u;mT)>+_$|8LFHa2}HcSLeu&eQvU%Zy0Bh>YPt3H z#=L@}gxj;`&dET$7vkco`rH-5alOCiWP6QjdN!djGrhoTE1H!H2j256{j46(`Y1o zFV5!9$)*;MS_GVo-hiW=%p&itoOsiWg7o~EUV8}0J7`qqCEPkU59ch^9YEnGyC^Tc z0Vi1vJ3%{^m6PW+?SyC3Gc&St({mc|5BD=?H|)e)KT`OGw@mZ$77>eci?Vto5b@$p zZS~Pc)|cN@Hwb%PK`L@)gD=VO!bNSAYMGF8Ea(Z&rJYQ7xd^}cZai1 zmvv{8yut)(*HaN?3%vNtoy=8pcPd5<8Cl*$gi|VWg$=w;b>rQ)!lLw|2Hv1LfevSG zJwlVD-aX0B&d*G3=nd!AXAkSMBRg>2Fc{w==S<~wi z<_fpY(06u_i*Po0gL|1IiDtosZklIwK2E`>E#Z`f4>ut~$P* zGnYM4cTFK45l_@pW7Kx)DNf^FUSGki^>!}_RO711TgeeF8mj+l+|8L+U-m(%-U4G&do-kK7jRzmDwmO1Oxun5NmMUKWhhVCsF!gDn=5_LK14RK*6b(+;I(K~%^QJ$(=f{JH)-?}tWLP}MGk&5{Dl9zblik6S`gHqQljYBM8)B0a6E}4Zf)1SSqY%_*f%#_KaKSW_ll(@oZg) zjF6sFUv28~LUM@*w7J8Eax8yy!!1rN57eUVt*27sHZ#}TSV}mlzZ$P`rNu*H1F6ED zH&kG_vxZ)lnUlUCwGmmE<^-SyY2bDB^sYrH>r)5RxJk#@xdE^8L<>;A-dwy=IB(#s zn1!V-b805h#<^|4Rqp(04aZw94P6>4v$=)1XQv_GD4iN|Gc!}nzFnz|e5Dv$~&S)?$W;B$Ca6cGX+%3X#<|p3o(UPf3sYl7EE|Y5@DRyQ0)$# z8s?50@UOn1zwj>;1C#nj^1>bF%tTFE-;|=;`Lwy-2AjeS)KHD6hTw{{=JhMYYhHY>C~+mS69~lANju+_`ewV|9cGJO20c?+|Y`9T7MrG7iV60`rZE?QzOUaqVG!A z|93svEXEZ>c5=w*5VGwdT~)hcook)?d!_4~Y9DbOC%fF|f)Clw7OPs2of5G)g6!mt z^EZ;sHg;5Pr_U>@JN80H)t|o-&Lq(9R=cmA>_N&P+=zA>xP9iY$s0PTbWZT|swVydyjblfgm zQPmP|5CWAtZ=9~$*ukpWA}&j?C!P%L%^)Lb7PD}h|6KFCi5usx(p}CqCA*v}xkEfj z;StZXff5@$SaN@Ed1Fh7rL?>QR5dekEy*nR@8vDe;Fo_}y2S6{EyE?&D%=LDnr4fU zE@h#Ahiaco>F!ye#y_3JMgldT9TJQ0>hW1GvGzdC@15d1;a2-jQ$fi%kZLyo;`e@z z$MJ^Mjy0fUoBhXC~+$M=7Y)Sytj16uVvo-t)CuCjaA#X~c#O49Q zb+YjmqlDu(r-G7knk2Rc5WlA(^f`d|>@?$dKzvSt?6-T!pN8KSd;HFF521NVp-1yN(iG8v0Yfv&N zNQ=cD>G3_nde$Sn&X&87(&Z5!tECJNgji8N{X&|lr4-MH<%UTtKdcCpjC1q*n`0<@RjCMYZ(ee2Pn_ zFG;K##X@tD7L^wzu{YZ6YomRB7imur&!6}c`5h3SEup@YGpqfp`8SduI}M26pHkjN zeX!HE)24mX&}Xc}oR=`S00(jB<#Lgk2@I7>QEzyD?uYNGK1c_so}k|db`Ds~22kBX zpx=A}I|nW2%)kPQoUDZOxeMm8U*+GBTlJmeJ4>vz^LEh7O=K<4ap2IcE~Cl@be?sqsh?M2S2*z`5%u}#N$E_46RBCbQu=oK*xba=!_kxObE zRBrwUIiY;ZAMm+q%lRw(X8~)FT9uos@#eJ6i+Qf8d7Pz%i>TD!ZTg#`h76IOb2jMc8v+)oM;{H;uXGd(3LZyo|pdB9EjhUMPl6m3fJ|p zlQ=Jaub3sVKLGl@3ijWe7r#4D*JkkgHV-y|+}4;tiFF0&yY8?DaK7gE&D}Snd~Y5O znzDH!=;Y0*pf$#nVv^WsQwC_3X)$Q!#!I~JAvd_7p0#>~`}r5113aE)$*8x+0`%Q@ z*r}Ws?@vQ`3wW3pz+PM{{K@z{mVY+^c51EPogv>LPgREe0$LSvp7<~qugg?Ff+Q9Q zkpIE3`*L3RUxv@W^6#o(pRe`19U;CRpy!id6FkFLZkE^yfWG?;_CK5#?`J0^_8~yO ze+>H&=WBjv{_K*=pT9Ezyhqzkj?fMa={MZ-9++()hbH7tXLP zh>fzaPe3cej)EQwJMIxb-{(n;K+oqR9WTCm`ST35OKX6BkAt1SdF}1!2jcSw{JTA{ z57zp<3~kd_06l*M_C?N%=biYxntxXcdsD678REMok8>Mr0=-KZC-U=No~J8dALo3{ z@69FlIzZ371^YYBi{C3yd!6L%O%?3(oEN_{A7AuC{e5l#9quy{G|e~*aV(4z>G38{ zlXBRf)J~sf68jjSCn{in&H0+=5nro$oUMUPaK9b#i`MQhfWC9VCV2iHVTdrGy|w$> zCDy?n51M7q1{LN__9oolr`(74W(VJjR^+If3&ijDgx-rchDL>=6^@UJ2Ytq|%He*V z8T5GncZTmk{eAoq{vV7#c!dAr8KfydPy7qp#d-05VM{Q$b4FHRbTD+1qWd@cp1w2S zHV~uHWHOsA7OT}46}Fv~GABk0%#NO#-HebW*i-GOwH-{8GOQJHvpuXMs3WWwXmVIG z=)ka%pm;ufDtaer;dg*84qpaZ68;ouY4}T^j1}vaA;;|u|IThR9J5#A{Xf`$0?l7v zu%3Q5q1C|q@h7o+@F(IrhL0~&S(3fdmTVtmZ^pV*cSWycc=broBv+E!!+D2=*{9kf z?LF<0Oo`XW%gh|_8?UfPL^jTsxWd$Kk5b`+kPc9PBML|WdIHJ7EkGKu5TNl&L(d~N z5tou(wkvj>U2iwoee6cN2{B9YipNodh5-`Z6$!f|=N+((H~X|K|Kf*e=f3BRyj%C*4s>$e(1`V>zD&dj{w0dp;jMu$2Hk|1j(g zoZkfd4bIp1d@1V3H+dbo1NJ`7?}zQ;e0|R|v;_lsdyoQq5$9LJUc>o%pAVH#pXfq$ zpoUOC(16e|PTGteBMXehVZ?Ch~b-(e-_C~AeYqdJ=2WoS_K)(T~^246U`Kho|IbZt`&(B%u zMd>+qbvCD`9TO9Fnwg-bDfVfZSOJ(>^PP6=r01h5|D5B3Lt>X4F3_sZuFmN7B~~Xw znl9tJpl{&HL;T`B`i(2cgOpgU}z+XC3XwtvBU zG5$mRk%Im?ptt%z0s55x4$z(cpM!qke*pAr|D&LFn_<#0)Y^rHLe!)4{pZ8oZT`1` zJ|D6UttS!A53xOLJLv11-`Fg%w>Q5H`p)JZpmm#(%1HFIQUlWhCAKZ(b;z7) z&xFj=J5NVil)6e?NEg>R(7Mg37FFm$rbR7@LVqKw7}UoR@oQXTrcx&-t#oDs$FrDJ5rNQo_oTmV`b=YobIdHov1Uwn0f&>?xeqcq^70QA@c zJ_g+zuotu<-~#Bs0WMG$*A~yWk82MKtvKiv##ix$KF8Mz>gO8(8ss|=bg=JG&>MV5 zf{yXc0L}7U3|iYH&xU*e{n8Ki0M0*>=Wc(geimc4+RroS-U#%(4BN(eCu|qztDrA) z`MY&yp6{UkQxTy0kDN@pTGF9>3A<)&hxixi;`yxLZD4uhQO*M|1cC-%Xa;J#U;~Z15Cz)$LL1Qb7h*x(=0hA=B(smDPe4DFJ_r3%Is@vo z-G@4_#P$Sesci%3Yqk$i7oM@50qg#vHysxp(0Wy8p#1u}d|jwxU74Vo zk=dd3vMMv%AhWJ-YJ4*6$hS1!4*R(;G`K%H-md|DFVlRAib?qXLT%-##jMhu4Rv-def%l*l_`&#<C9yihie_HS6zZ2o12Cje+*&cuz=t zIQnlr<9i|flj9>C-%1WgsUx3tu1-WQn1H-s;d!C<8*6=*8Zxp(e~PRSbkKhI81ywx~bkJl1G>ZjuTj-I)`Dd>d#-MsW4mH9i zke_DQ-8g?8>>ix&4Vyr2l3){rjCK2Q4dv$?%Gd>WS*q*(OMLG}t9Os@nIH`o0TV?z+``^oZ|q&=bC=LI3hS3+l~G3C1`NAQ!u#>)XTi zeNTp?CkxQ`{ZZeDcGa^j9f)!?*rP1f^f0TDR}P?X5RqH2Nz*C zKdkZHV2|W{9_(eD-vs*u&R3n1*q?y+*wOMGos1sD14r`~IQCC%<0-Gwu!0nGG+#kE zo#ss_FH^pyt{?R-=>7gcoyMwQl&rT}LH(=&ph4Cc&<@t?LHk$-f)2LQ4CxKl;h z_@kDKbl5=qRS!fhA-izn67zB4#w9k*l~HFZI8ApWHSaM!0J_pt0{Vz)Bj{#R8R)B~ zS3%z}y$!m<^dacSrahpanZ5u$Y&s13t?4_^NzV-vo4@pRoMnc)tWG2mYJI=?z?#cPyu={X63HET# zr@)@Vc_-`(oWJb-7$q41^3@32#CZ$s?ws!ln?P=Q!|ucRB-leZp8|U#=RMyqSriNE z2a6Td-x2^CY>5HwU~zyZTDpLCv(QXvZ_7Z?A(o+_H(G{+jp^>)dxQ2h_Xizf z9twJ+c{u3J=9@t$nkR!!HBSZ2GS3BN0Qo?n(!(}z-U>UI^UYz0a6Sw+`AP13!0yTU zB-leZp8|UV=a*RSMhx6zSq{3wQUdz0zHE6L zbf@Kg(A}0jpr2ayf>u~6K($ysVYvXlFWO!L$uk}{f%r1$UJ1m%3!6avRHE#h}ammV-X%_aNw_eouft<+ld(Ilt#YU+{YY zbc5dp(9M2ZKwt8E8}uE&cR@8h&u+in;P?3L1+DP=8uVMg^oNL(pnpXC0eUv#EaNxaWgxSnCBM95^2jAqkklN+AZI0TppFp)$k+&m+!a=ne`c{Tk;O&Ma(0+y+K*t&KKyNjC3wq4p0!{H*j2XgG z<2H=Z-Y~ukdcgQS=#NI4c}%(Z9yDEFUF>rS{H2wk4`2EiwCd6&(EREm#MQj&e?9U_ zT_VP!2hf$>$$I(k^y}llszs{LVEZC_54OT?YQ0dlK(lUR+3n}Hm)Y+2|7jn^erbOi z{L^Gp7Vq3?z)3uF{rCAk6phITO9W6mysXgt!mqM$LvhL__%WP=%r0O6x zxq2#ildF%bE1QB|H`N;T8M@!Zj zf=YDjlVzt|E;GfcFr8D!^iDl9pfbWmQ%*CpI;~D$yfTZMl!we=Jg@|P%W$%bU4+68q*^I*ThXfd>w#2jjdc4k26 z0MHHL8&D&3k03qx!ia^Snm(qpqZ{)1YR9vn|2nEco0m2(MP0Dox*k1|P5aPKKe6cq zX#VE>%~*l4`8&`vo6mygyYg%LBeeycia3+mMz$3Zu#LUNbnJckBgnd6#`Uf2XLYsV zAGn!YW>pvtNo=mNRPkr`DDT>OMC`J?XM@(;wh#0>+i}o8Y^Oml*{VVLx{wVmP$$?o zM1d~bupIQ3Ew5f2pYocTJNvqlT{8R8^`pyOV_Ys_3|1_3!D@w`{=58o`#*#H7jAE5 z*RetNJCO??v>TBN&8=gR3um@3LeBfB{R!m4lkHD|zdQB` z@h7z(zWPp{fol;DrMx>2qnph)`1eC*K}$h7|eE-Rl-T zNS?fx_t(9xVK1MuU{D4;nZDt*H!7hXIJr#M`s3wMSe-&6kz~+Y*i_Io{2}i{Yl1!* z+W@)=e>8f35%gvJp(SIlfWCo0F`nPWc7Z>PKg1FKNCVv7j#k3n7qp++!i=>~1z!@C ziq?y@o!cJ$zxJ+<;NusgRgoMDN|n)J2i4k^zL%0vQx2*gTu&YODtirnz0Gzog}uvm zfy8dehhCA}Na zEwB>J<9$KPf>s1Q8>9=44elJ=HF#39$<0=Wo3IXeV}wtnIkHz|-^jc+sk0`{o;G{z zfz=0IINR3txv_A>T;^bsh+2X zp1R@GO{Y>$jXpKu)TC3BPfb0Qb}Hl4>{ErO7M!~C)Z$aiPpvz(`BZsT1(t;;oxehO z`T{Yi@G*lPww3e#umdFr7{v#kS|v+}yz zKIk`ql^(fgeAjsc*q#wHa^KrBfc1GO?WYOf4@iFC^#zezn-5Gm{llT3!fzb-@WJ7R zeO1nZc&}>uVb+dTTHg-p+1D&*hw#l2_7CVeM*U8$$H^kwMp9pMGU&9(Owiep^FeQm z+zHBBxmwlL@9YrO5oIAUDiL%<)CkZtb*?+D(_-{Nqc_}#cIEyJFN2nCcoX!$8(g4{ zEtAj_oxEi_D7$DtZz#&u6Sa3g*QCqpa0JBpUafW5Jb0xa*8TmSkL&*a!l!k9pS{h*IP_XHgRJrWdvJQ@_-95gg|66h4Qj&cS+1>PC=cpUnh zaqB@h#=Q($7WW3|TX8!<%j0%~?uq*Xv?A^h=#jW%pwbot;@`Bz4>WK~3sC!(cAzm^ z;z6sC612AiDFj{yugwQkqu=KOwC{D`b)46O*K^(g-oSYu@IIV3f;V#B1m47XGk7!S zE#OHrM*dpCTRHCwo-}3jJ?6aujrRwyt-G$qnrpGADqktY*lDgZAM|$RPSC~5V$h}N zCt+q(xfirpSq}Pu@&M=xa=YGeZ%%4=x*CrpoeVVfPSa;%>J^S z1^ISx0 z*X+4Q-E%!OtslJ}AorE%7ykkfPisLY0xFLf6FS4RS?;xep7Y3GKwIqsRQJEYJwUuL z+WNCJLpFNmRfhAROZ?9>F?EbN6>5~J7A?`{7o&weO9%!o^2dk@&E5g3UiXo@RYU7la6I-Hm zwR{^iS>0=s5<#K1ua8}iH*34OHkmeoS9j`EZ8{HL+mE5`y3tnAL64@6_IIn}53L6N zT|R~0d=bX{2CTbHHvEVEY&d~%pY{10;lAJ#ZnU#D#u%fH-D(=_cTl(6@85v#^5KvH zp*a|xEQt70x-H^P$RhLw&qTIntGK>kvE7er3N-CNUR*+aIYuy@9i3RBI)drtSnZg` zo^|}|n2J?r+BoLix*v7t*IimiqnX9)3%O1qWP=TLc>ads4WV4Kun*R7lW+nmcs zHkz&>-}S63o~`lFx{B1r-YR=kbn7;4+O}=iu6_IVF)=Z*9XfO*(@8b7C?{Su2?+@f zN9V*E)1}6A?b;2dJDKa;rbmsrUNt>ynO?O_Z!&${rf-c&s?#LbW%{{I{|1bR0%vl$ zy)>;dpd_WjQI=n2EFQWiX5-A0p-)aY(&McK=X6U4e%NN+^q-nNI_5yvZAE9T58U*5 zrx$a6k61PN+ul3w_*Y)i|NZE-(|!ngc+~#HR}20!FTdf_4x4BF8vgX8qdm9Ze!+0> z;N9)kXPs#A__#ye-r+nAv z-8(NScMtfW_464&20t?Tt1hnJ$AGq-%x>z#!ceeN6bar+H3{uA=V z_`}z|Isd=@D@X22*qV3RwkGv>(yqlWhHV7cNT3i%6C`ZnX$wK^&*FRfjW!rG^m}dh z;yHSjwkKTc^9{YfwtvE3@|!kCkp2sc5`2s%v&HJ`=N}Ll6x^(Ni;&Q;@Rkvgt+e9N zwq1Lai;kV*;uBB=x^(T<{kk64_w3cXPv4~Ee*Fgw95i^y&|x>+c+>C^BU5f3HG0h0 zapNaUoOH|NDN|F^(x+wM{ErzkvuDlD$(@sze`^7bf|@sf!ELuMyyMPAi=B5ZxqIn7 z_uf~$Z2A2UJh)+S9D?09$QuJZTZ|KP)qc7MF*lTSbUeD4?gD!$zR)q#VD4j=jY=r`Yf zckFoO_dopj)6XaVbMlv8fBXH9Q>Ux`{OiowzyJB~x$_q;{(Gq!>R>2*FgAmlM<{uq zZ?-`9>x)?ee<*qaF}EEIrDSu~f`vdA9EQ2{me4&%vQ}8}WM@$LegRrM@ z2ph_Vu^U*2sGBfLH-e3X66$6)iY=DLKp8cTjb{_sMD)lPvOA!&TZCDVhE}hXELpOo z7!cOd8iVhP@k^;!qdrnx?$zME#SM8c1&}ZBr?|LsNiidK>C)nI_`S4p2}JSceKD8H zo98uRRsDZ#{=8;GyP&+AVA>!hOlw8yioH?;J^bv5oEWI)MQH4iBV=e%UO`zn3>}G` z{i7HR0kV(5i1vS=eH2>n+wFJQF=DVk02&&V92LX*MGZth@y4iMkRjpqG|848oE7Pm9pqsI{B3ywi($&g^(VlB6Mi4)^e!*DaSJ&_Org5g^(u%oXZC{@h z8vMzHe=^=k`suc#4@`KVJn4Z$7q1U5|0wCn-?zn0-S=sdb*B7wiyixtdiwZn@fo;3 zX?e@NH!gVkK+-Ym>b+4v97-~W*p_W?`E^q1k{P5mkEOwJkp;-LJmyr=+f{pKP~e>CZ`@ z7Y|v%W}QeH<1^xr?Z*Ek73S|eXo@+Rv{z;=POx8+wgseI`ugB6Ne7xa|2A&;HR)*L z*p-hK{gyPg^!v4|`u?8uxUpSslKzjRoWGN{JhkVKq{3O#UfW%KDru}?-~E+?PIJA3 z&_7%mSA4jsT8r^BG4#-8m=6524fazoVX()-G>1Lx;Woo)%@DaKjbe`#KlY&cz0YB2 z-YtIn$+fg*h~7iG$s@~FA0hQ&8$3uh)dVz0N6*td;e9YtN4JcDhlCLVtn1K|%EJN+`ua@hoce1kVJW(_&5wtON+@jxmoCs$jHZ3N?Wa3>$>8!N%|{m=&1R@)nE- zrnXE4?G@1*eegx5l z&fLd5#k|zK!u+s#llc|%HuG-tr)a8mmRL(?OIOQO%TmijmS-%hEuUC)R$ptNHP$-F zI?OuWy43oh^&#sf>o)60)=#X*tyNZ?udi>Q?;zh{zDs>y_1)on(D$(K-G0mbp7bmA zTj#gl??u1uejoXL>^Ic^N&k2K_xkS+7#1`>Xd*P7uLSK1dN1g3P++jLg>Oi1$gLr^ zu+;DikrQl_Y?EzMZLixtvVCj&+qTmFNymgvsh!%zZHU_%_h#I#xR2xZ#T|}29`|&7 zvSVduTcRs*>xM5jxi+=mlCb5vi+wI8UF?5x(8bZ!GpgtQKkR)6d{o8u|LkshU1~^y z?9xjD8+vDh*hQiUsHh}BDAGc=0Sib`QL!sn!t)79Kn)@YN?A|@3mQZb#S%~yD^&#o zTp!<5 z1APo2O8*}4bC%PQBfEph-WmC_D~ROo$cvYNNL~n3W5&{vBfEphKKk~vmW6N<=`~EcS_S#q)LciDqt(&0|Z$ZPGYG`X53mk7cD8^E*!;hA(jgA)` zn;eTB?>h1w?;&LoQvQLI+mUj8@|%u>NV!t@9XpZo5K_L4--}578B#ZN7h&()9Y>)p zMv7ddUVv1KV0jvQdBRaOsAW*~pw>Zkg1Ta!==7kvLEVFz23-ZKFR69V^rWsqGm_GS zW+rtHx;N>npxH?Sg9hO@C&`yo6>FLNu`tQ{CMH|eUynRT-8TZms~ zo#*R(iwH57;*9bgP2E@Hu5A`qExsDRxk;fm^dW#KUrq$x!SemU2Uy;N*6$J!rExjV z#z(jjz4QY>WWN(Qjb(FO2J<-Lns{@l*akFKcO8hf^#ji}Zr6))Mfr41fjkvN>7N6t z7)2C=g4(snZjP5>j;&!Hi)tR%nu%7fsUX^x#$IQ0d1a0%)DdMyCoVUB#`{dgCb${p zgc{Rj9_x|01MUAH+9$=o_!vYohSi8DkRg-Ki+um4pgyMF!c_ZG@8YSBskq-Vnt^;T zza9KG-p)}WQFt#Wh9rWQeN#u^9UT=K1%AwUU&}~JGX9f%ci(KpHVTUkL%UvdY*ldi zHqSENCMyeezzI# z_tVDvJ(}O|6lJaan|+Jv&Hk+stI7O+&oJKaH<;ePhq62axRB*lz%?wt1zZQBGN1a?-)8$};KwZg40L=Z_g@Ft zj^*otBUqjde1YXRfje0K9T>V(Zd(`FiRHe)F)ZH?e2L{vz%N<;3b=>mlfdeq%l*^@ zCbHZXcrD8rz&S?wLdedbKyc|(0d)6foLr4MiplHr573O@uup zv>7Zx(Ub9r1RE)XqU%96`ocIHDIUArX&}J@P(qqrdEQh><=t z1kM^FB=RlruwbgQg@!c%H~P^|h8k(Yj5MJ}ns6gcn32ZlUpp2i_Y;vuTgg2{G@<>3 z(`Y86-|b*ncp~I5BTb}C6TOJiL@y+}k<#duD-Meq153D(K2jbxW+N<74*J#$Gtx&I zM>l%z{E=a`VGoUNM`?no?igmIk2cbW#`Gk6D4mJXyXS*oTFfzaitsKW!i_d!Q&D^> zlFf03%-{0|@m~Fd->n@Z@lFC!dUKoy`sP>hODg-#Z}pwuv${O4Ilcvb^KS;x`Bdhc ze-Xa_Rh$U={=Wo7`y2}-BH26^`+W6+4qO+osu#>bo#1}0USR$P5x=AC2hlNufypep zfpuA~4AjetZXHvzU}xizpI%N>A3l%^Aqh~(}-B9bo$5|Ml*kci}~fkY(t1omS2 zI^gvz-vA_{H2r`?B;N!iBDp`1h~!&=L?jOY5|Nw*9LVxuAQ9Pz0&iz|Byb$d6M&Oh zz7u#4%Ts|ww5<<#AIlE_7qYwv_#(?M0rOd23Ve&@K*pBg5GbrJaQrLY@A3SuZw>6p z@-X0ZmY)Sa52Ejc7l1E;sBBma%xAd(xC}(s`3i6)%SFJSSPsJXKro2XgaRX2js{j| zxh61)<@Uf1EO!ESW!Vet!SZFmt3Xt*yc(Fn_L0C@Eaw8(u>2|THu7#XL&F11j|YIW@ydd4}F27S)LEfWBGlc!1qL3d{@|j=5fY2-E%It89sYO zj!#f7eukPFc{LmYKZ+WfP1JPNbm6&jwFdXPyx>{JDBmfrsgNIZJp^9l`U-rHYY+H7 z*AL*oxDJ5Z-FCdUL*1d^5$HQY78$~fNH+DAxPjRP$w{WL{w{o`!@9gdd zeyK4w;R*LMke_wG0KUlm5Ac7wUjm0qDtNy8W$;4xa`08|H^A4rw}BVAw}bC+?*RYI zy&L>%_t)UxyZ3>I)r+i$cpddtg1=Gk4e+9RMc{ku?FG+mum~^dR~oDWU)#VB{%(UU z;O{s10Q}xJwPa;N2T`2hVIc2z*q-(cqgKz1s-i zS@5n#ydHR0!@CCF)$j#watb`_bQap?r04sv)59QlF71qO^sc2{&wQz$^Y1G&rfnbI zRVp^&8oZa@kX;RPhqYua1b)lOLV( zrL*#r>s}pq0z~OA0}_$E3HUb48*1PjKxFS$6Yp3M$*X`W&K|`iQ*p^!<9$U$X($dY z5y=$ejmX#!-dj`$Be^?#Oo&MC0lbvugSR7(gUD`NBl^n3(pci^A!X5Zn4E-#@E(vT{PK7^I>_^I z+9lHcrA49WB<+dZFs3l4m$Y9llcT5f|L=0<{QljCwV@;g(f;XMl)gXdJDTFQ()Z_n zyyWQnvm3|-yLs#$#hIpW(6O-7=-)pzHRS<$!XTX^=@^7C*0F7@IWS8dS~AW!6H zp4z3~W0~5dcY~@P(=L)b~t_%isdfqZAavVaQlidVw<1P6X== zU?HeEI?e{;7Sms*#K1EQYk1Kn{zqY64;7m`mQPlQOnRC z;9j=d@hzpsK+ss~Zv2gv4yYdsQ09IDxh=3Ah|bLmOlSF0U?FG( z?8||#v3(8jEfC#@wZQdk-w34f+P}kI2wV=LeNt@Yw^&{e^q?Fc2YVW@C711;fSpvfrXxOt%=+Wny=s z6}H4#g8XRirLY@52f;@F#EzsLxC48V_JIGC^eg!9Nk_o{Ncsz0xFTJ6w;T1iA+8+A z6O7uNAi8c}S7yMY_^daj+xdRb3cdhwX12O!UPH^gWrZ~d#lGwVygpYhGmd(rF( z@TZ%t0bkc_EBO1(J^}x=*#U6T{L1F|s*&?Uc!E3_JL?GnSL3$YLGJA71}?`R%JIP^ z@HfU0(_To!QO7o-PHqmAX*#>V)x)`1g%yV{#Oxj15t2 z#Pr!;`a9MhFFkdhI-0qE(QCV~{>60Um~+MBGyC-axAj4)_|JiiPcZjWR(7m{>f@xb z3aXFO9JjJed=un&6%WyB+=5k~sChhtxqmbR-m zM<0RQHMSedw`sA>oXbRtGtC(++B-Wr9ip?duQNgPa}IPyib2l7PV_c9M}g0D-U~j* zIS>3{=K`!<=zJFPs<`!WVPZqvX1q_f#BBwy7vB&)QB62Z6d@$JDWLIB$068a_B zMOMNf@WBbA62iskgmJJ3CALXS6P*&fB-%yS#B_LWU6R-Xd`9A;#ALBJaS7yt#2*v8 zh@TUGh5UQs;lvq&DDtk`5f!zn-5aR_4E%A1vO=Uzcwe>+}8jXeBG~6yVGYT7&m2xVxaa zm{Rad!D#V&!2wwQDEMya@8bKVKPrAb>JVX z7F#10E{(BqkP~8)!CS|+hh=^2X2@GO8t})Ei*Jp2JhBpR z$6cHt=d*I#@SbtFL%_q_;o#NXHQja*=dR-p70K>;ur$Cs%8r=~I=Z7oC-+tESaFS8 zzNzkacM=b{pKwQrCoNkLsqOI9GVO(*VQ)L#M_?EALhIoMsV{zAK;Fxvle8V*Iv-{Xxofb$$`%~~`={k7R; z;60mF{N~)3@-xOtmXG%+-=#sGs-7584WplCGwQgEeh= zIo_NZEi+p>MOMpckne9<2=3?T(P!VNa_*2K)w{Jr!fM%$e#^FEa0OVw!7Z}8@T+WhfZu7G0zTa~ z!-mm!w%L&9*zN;D$!JoA~2mS)0LQpHjVx(MR%LiX(dj zgsr9BE;`yf;j6BTy(@US{SsJu*e`>8h5cH{mpXDB&BQpzBu5ZN56*Uko*6Otki#w> zc02<9D32Rl>cF>%W0xabeChZd_M?u!(2sfCaRT;Jjuz-m$qe!Z;i`lD;2VRsffofe z54HB_T7<0Hk`B%utjOgDD-A&y=@SJLiCvT=(q`)7^nCUGuWnhY3 z3`!Y{yg4*w6y(tDUu~NVewS@3_%uWt!Mnva3-VmsJmi~4 zZBN)7;u+fl*q^s8g8ZWGC9KV}y=)7{gmteY%_`d(*w@)Mz)pU6xSR05L%bz;-XRvc z?JL;7wjBT`4?L9Cw&U2U#FmUaRL|ZDytBQV-66d8?vO9H_X5wb-++A9*WM5OCVPKa zZn0=vY^BBgmfQcHmxPjO=w79cvev$wOhuF-F7|CNGD)KDj8ls@R@foE$6mB=1WO z!Z`AK-EDcM!K{?*lprxAWh8Rg_>@V=dDBzAgZyjC@8Cx;*ayA`9Lqa3HAJK@9)+AY zeepu@dP`a$Z?#%72K=ri_kd5scyzm%x8#u}!Q!zcPb~=&&n(G@WhKU@+ePoCIZJDc zvBunN=NO5uWyW)re}7lWw+*@T<1(e)QCR*AxSY#zj6u%<5T3PU4Lt7w>tSPmLw=j zO9*1I(9BUA&|a~@KF4u$@D5vlV^*L0g8jkm#M{B|20O$NBSL$4NQBXH5e=S*I9Cq% zVBQ`APvwwNuzZSGOz`G3=F$4Aj1T7E@DN*ecushvI2C>{;)pmJ-7BWQkp7rKh9_qA z*jce-MUiRNh^w5v@HJoJ%&eAeKfj0OF2h4p7%d>7Mnon#_Cn5enwzi~U-Nm!EKoNm zX5t%D&WYM7>8!V=-()nK9>{=_0Z|zeF|s5`xX_24gz+VV zF#^XNvE5ums{Dz11bl#HEFtOtE8E>De(37$6IJUa;*LTTaN7*+Yr&!x}at=E4C>1YO(#ixmQ#lRdbYmbj^h|Q^m72f2r9^ z{94Y}Smk7jD)z7DVW+E$qYCF<%CVPnj=c@C*?U8JLdY3bh9M4IMV<=h%I7p<$z#f^ zVTclkx|$Im&g1EV8c8m4Z~y;L?(KT2_jTP)IZtmFRm!yJNso$Qr-Q}qM!TMk^J)Zg zY*cA1xblp4LGFZjb!b&C?T;LLDN(5VB77$lWdcsrdLQspmc=oY@*uK1ftRzK0lbOjOyC@r=L276Sscf{2W<}l z)MsS0A9H=C{LC_i9P{UmWUA+ELfz(Vj{ma-mLiS<^f~0>r3CK{F)Lbe#h+6=z}|N zeaW*V4YlxAOU9x`G;YZqkjvJc9)kUmC69qWzT^q4eacjWIchPRGWFw9dg{xiK3bBgpVmD0E%nQi{VSlF2bcP3sV|o7ULX<4OMxp{ zrg=b{g2--5Kpq2;+y+Q}u_Si_c4hfGV1JhH2U7nmrJ?>=^L${`S4(!Qc`noNd<7D7 zKAnB|E&&qJJ}C~pibe0iyR$97N2woJ_5VJP_bCyj7scp#1d$vDti^I5^DbTf6OIL< zZ9Rw3F9RYu19+kH7fS!zOrw7}4{==49*rN`m+^xKBqF+^zutZ=o`4KTKk!=}1HmUa zCW6bj&-WTV%QJ)KpmpY^;G0l_JrcYK{KepP;2VNBfNu=m1pZF&JK$G`T#s5>zmOZj zzYQq{m+_;14*3} zgYY!u<-&w@;H?u|qt@8N==~XyI399A;!^Ooh@^?{IU}Z4QMu^fe;LugWvs1tYJZGe z`iBt-{FD(1d|mQ7l;P)(!+Rj*5X$Ek&0F9ryQwkH$1}!^%_ogM@iB|XEQa_0;wj+M z5Pb_@jYdT8gNqMAj^p0&xhR2Ubnkca-pRvzB<~P-Goxp`x6$9UB7a3bTK|ncrhWPQ zAm3dur2ywvunGLnfp@U^Th?m<6QfcpNQ5|15g6jF`zX#M<(0Erod_>M zNq#6ICvuuNV$Aa~Evi>^k+?IuTgpye^1ZyWH`g5bsC>+$|5Tr@ z^o;s<_3Vmr5#Ci_FL_T*HhOvcFKtrtQLopGnC~Zz`3DA`9*kDNY$JY?obAjU#Yx76 zm$978zEUU4^bH%&z^7%$kki;N`j(>cUo`fMNKIUAF@6yMdtFd1Cm~%Dm zOCNKt#(?Q#&S|VUotxS3=;Am_c~AZ)jI%^Xv`}LP%)Wx=?;>-XBDE)~cEmks6H#$V zXbxC4_v`uE3x{l%*G}z)YJS*&+6#A_Z5PahtYUl!JX7Wv5dW>u?>V2Nf75rAIW|h5 z?VY7u=Y+#brP@CKH-2AHn09?-Tw9}ZYXHUrYu>2UZAN;qF-@vPcRzdC96WR@2)|6|8#i4eb7*-Fw zL0BX3CSfVypN4%4{$1El;0MDT_@?Rze@@g=O`e>6!u!E8FnkdBboT0e2Xa}jPMT?Q zuQA7@oRO~V40G3@mMv$P%ZV5Zd7?45oSb`deVKVBKg8O-#*C6DB2Ge<^Ye8rGxy## zsG-Zb_XZlX?#+mt0ZZB0;G|zD&8~;Q4XC+yEi=>SHL%E;J_j0ed`@S7QS&^AA0mzP z6y0mgfGF5s)I58-^cSVM`UV~ypVJQ({F_%pF0Min==!9ApB0r<8&DK zSmy-r8P3_@&pMw2A5d)oTI#CCRYMy>%eYqH*T?k+9~U zA5`)_jY~*G+d`j&+rYCDCV-2?Ahas9NxTHympB7_QDQ#$vc#9ce@r|CUf=L3J#2XO z|5*DdS`}6q-iLC&`w5HhUW}G5W7hk6d5!Y0pSMq; zoiPI=&A&*qFVd576Xsl`85gHxuGO_@kD|F%X=YWLSM_*AR3yz!Sq<}0(kzrT2PMrw zNzcG7cminF$r+eI@gdBd_zdPsq?rn@YE7=?t5r~|u+|?4x7;k=gBCL{em&wx#plFNi1)=W zjW3LUFTN=LOL+DsCe%S&+B8JRpP%qdLM}WoXx2QDcy(e1ynKC$M-opawoB@SDEU2; z<|dI3MiJV~MC}^MGh=9O(RoiTuNsB@y%#73XMfshcPHL0!5&2<9JFViW{HmNgbLxCN(E@8s^2wO}(k5RD-^!^xg+yd&8q2ch;f#$v$KXlr^ zIfeoC)0q2as841Q($M%j70qlfck7y`~$tuM#VzV`)thpG%79v_0y=h2L7r z)PF-``PBT)=COQ%#A)dM3EJ^Nv`_IRTJS+6cLI)Nc_Q#$5RENd2z-I{=X=j-$nlyWjX)2^zL|sTd_96yLl6QIWWoyHEFw0?N_qpYG=q0@ww4*^{uJp z>Su1b@`NUfv@$*(N6?B@)9~>q+kTabHmjRZ3#V2rIV-h2oZ7EChucMZ_!W?QhF=#R zB(5*hezi8dlh}y*IeCNZ4X+`-3%5no5q8waJBoDF$4F8bpB61><#aPtK zgT(ZR8L+R7*ccHkHgTI3dB<*x*c*ZUM4X6-6D5WZi4C=Mj9-XMkF<;KM#~k|)x(8q zt@1~9728k?Z-rJVFIt%{i@E~*D#OpDY^&7TD2$JedN(Rmd}w%{sFo-jTAZX8N_sT4 zNA-*z7+pgQLcM&b_$2yMSawFIVeEozYr5RUvElXr@_4eH(4ROe99-04BxsHMqYrHj!Xb)%_0O0_}F!pP{^@MZ}W&pB_c=C|L* zEm74`GiVvt8r<9x^?KY&Sl)5TYe6&d8(Im%#lEHcmpa66OY4-R^%AFJF)~B?-l+27K6roq z-Q|O9-zE>Su11W4>&lc2GhtsFz8t=5v%MO@!?n zZQVW3+19-_YJC*W%7~5dF2_dLcFq=WSt7+*r%9sF^bx5g5w1grAMQph_X>O($b7 zPru|gcJkIHe>~M%PVMABpk0yL$;mfQ zl@I|*^te-{L~oP{ zvekVBd)zfC=pmY#$^;qP;U2>Wudv{?0=rmIrUhP>55Hgzr(d}po;>tqJG}DH6Q}pu zZRWCqDihlBpKAZ3c0Y>kNc}11Hoxi6sXns;xQb1{%Pq9yXuuN@#D?p=Qe;v4*?Qa4%fT%9#2U2WQIyY(`e60rFRghNz zm0$N-$m>9~e{<|qb9-Sc_;Gh+U+ykIim7U}!vV?9djz(98CVFSHl^i2<@-%-i`1?- z1ok?>WDp&zF0cX1jezEM#}GV?lqMXge8s5^vOU|)zT$ctWM58m6VTiq`6Sz)2CBA6 z51v`GKY17EbX;nqr1r_)kRJmsVS53vkmc8aD_C9$T*b2PbN=sWzqD$z9J>8%o8^CF zJ0))aITn09<6p`E(O6C&=mbdly(pg-@^_)JMY&Am=R#$Z4}BBQf^tDcAnL0ipBDlD z_j;gI5XH#K0MR%RALwn+RuDzArugK~fmF<_LdZKnyFeu%0WW|?AP;B|h~lym3H0-~ zMa{?y>I2FI4Fw6*hk`+2p!T57Ox=LbgVfwV#gP91(fmI&=MTkr?Es=UuS7o38zAzV zr@4PV0fpd8B?{CEr21C6L8du?R9x6h$Tas4#dM;67MlNu;=&4iqqG50Y}h`a8$shh zcYq!NsW}VOTtEWfB2+8#fnEdoL5D$VexOYBl}`mxKMu`rK=T7pY%c1zp?S@UK>I-l zLB~N=uS(;D$C`h18rLxusqS&!CmpzrSDsC6`#YBN(K{SVbHBbi- zmATXxb2;b=(A6O7gUMi0<7IAzPGehi*sy1q}zOc^ir#{|2Hy7@E^fjKFz-XwLP?pedk-K>~d)JwO>C z8b70AWEVr;0}?qHA2|m&7uS71un<1B%i%c#DtQh1TEuh&<$&%1`9O0)^FhymXlyIR z+*Q76YFsOgX&ncm@{z`>(m2&iL1KsWKg$G??-}`>k$c^oz92)yW<9?cg#D0vE0c`>81W`PF>YK>`QG7NP6PSD` zX&#D3piUq)pTy;mX+8*=2ZH*_o#$GNI8t{Kp2e5)GUO5<9|w@Zy|pua5sYwgS$eyp|nm9xe9!A z$QbakAv?f73;8`HUK|KH5fX{6^OLZYhB!kpUvy{-l;k}_Ujknq`hIA<_#pIWqinAc zh9@SBVk&0B_XJOM*z4e{;Dds%zHlnPyM$+8E}I*}vmlQPr&4@Y_&jj(Pr)eX@I3H> z@Ir9%RS7~FzXtL;c&bzpUxj}S`P=Y4;0MDGfu9TyjtED&9}$6)51uP1_aYjDH;wRs zw~U}SL7RxS;O*hlf*cre3;1mj<07hy@evas&x&{u{Nad4z#ogq122du1b;1J1^7yM zyWr^Xb*UzbBlbW(jtP9CFdI-*B#sE57aTFNF?iER4|vPSR^V;m2ZJkx?@O|{HF5yz z%$bo{u#AnI8Cg}#f+q~dyGPCke-s`u^ln)I{%qv)NJ-u?L4y2afSiU+XnXy{x%r>6}=pMMf6+XY4ob+_dEmm?)QPu>Gs9U$T<7cq`DW*>;91UWC!!oVM?1stF3y2v zqLb=V55d!?yV&kL0r`~kimE8Fs`doWsM;6&#;P}0wTu4n1H!$nIv4zY_yeINu1fFV z*Wed~+DO&);Qp$ckn(MK3E}&x>Q3-oRq0(^wOS2u@*2YbQte@ITXlLjj;T2fe0t3V z;JGz_20sK3BAkC*6yAt2aaF)wacSV*xa+_(<3@syj>`e}#jODUD$Z4_hj7>Gj<;qH zcoo53>t66VwerBp!ze^7hnErd8DA$JR|F3ul!fpyLP~fU;X49;Mp&Cr6Yo@4!VTcT ziLFrczZCvOc=IOag1?gZD)^g;`@ls~y(B#QNxi^tPMQp^BIF)|{72HC;BAaKNXR<~ zM}cn;t}b;kcwXu&;DxEHz*ncP2luDG3%)h=Bk-cso#4AtzX1>NboR6r>F^=KTgtP@ zV;BGMoPbRJL?I#sK1HaJrac6n3*RD?25F_>qV>w2;Me~A0(frqT(rx>(+GS1=U?FB zrO3tjHd>qv-gI#@@X?EN7q=9P7Vm@nFg%ckiIOF@Jk;s(g26-cBEX~a>VkXo#)Hqu zTLFGJ?=SEY_$Jv!M1F&OeEa5K2A&BoCFJn@*T9SOzX$&@{{Z-j{L|o~pm72AQ_vl} zN5R$LnFV9O#}-TizpG$s0b<`3yb5`F!Ai)h3f2@j#G3`{VDZD($u4#md0WU1gTg(BCltNEOTft+3V8tjQ1}Wf zNiM-VrKE8Q?ny~o@b)F0!Mm1tVNWl)1iVMdW#E^WEQftZNtaW|Mev(Kc>>QV-1$=_ z;5nx!pha~O`A&%;rDIBQU8T=~=faZ;b#nMpVI(TNsqmc+e=5ux2#+e9iLv0lC}cg9 z<~M7|a${gemOBHxu-pwu^?)rm+}^&&$?{f6p1B&&K4$yDb_29Zql9xuz?fmgCj z^&lD>Z0ra49Lu>tsuz)+>P8>4oDn4JN5=l~E~UB<$*MkN*h6Gph-6hCB3admj5J}g zUPLn0jUqtQ<`4;N10s7nAk~>jrg{_AnW+823+%)8OyDGzeL$*HQTk%wVU~{qscuDf z5sqkGAd+1`dPkE?Z)U1rk-Q$bfn^baXNU;)D!^(WN>c+E$8tQ7-n?X|`qn)x`+(1~ zO!coVEWZc*o@G@JqiwyBvMy%q2Qt;iNcI7#PDZk-hmq_+`AwsgNuCLOm1R}eGSXB* z9s`j~b**tMQ=Mxji2A7S1-{1ijlg$U{u1~Vi2fIOR^j&~d z*CQVxs^ev`o$7V1L6qil;8iSBJ?~YP-vMrBnd*L2=UapH(}AymXxm-D-7Hfbumyrp+=28aZVWsLtm=Ver@CMm zh~x-hbC$ipaiEs4j|Woy&`4h!Zx|5CVZd;fBY_^4F9Qk}-g@+D4Wv3FrC0SuV?Q2Q zXEbE4HyZLVJg2wI^i+SmjpZUB)g6sABXF-kBo6{o-H~Kfe>Bpdo=9~_lBpg^b;d%- zHXzj>rp+A+H9``(%gV#zZPgUM2tz2&6h9ZA{7stm5KWxvmkG*;l@|_~nO}jeRalvw!fw@AW3vefH9W`Rz*YB*3?49WP6N%}+q1ECX6f4Zx@q=4 z4c~lZ-mGh%yjioao7yC&{^aA|PSETRuim%%g>~O9eps_VK4rq(j|b!*%h&85FU))R zv78=j-qGwMYQ%0hb};PzZ#Da>w6yF#4GOm%*X+xt-CgwV-fquSmzR6yxeMvktm!+> zTfT3u*>^lTe)u!>>la+9*<;=pGY_wvb83)g|0SWyql3ftt(~gbKb)H}Cj7&-^PbV{ zDcSLRZ;X3m`wGo2^PTtEkIrFtzVzw-{pIWfmu>##j+dz{H81yuKYr|v4&A2H95Lo) zMUBw+?*8a0e|xhLa?UM3-~Ca`sSi`1k$D;B`)TPv+kf&I_0yS`OY1I(YuKpQ3v%8{a-xRLX?7OpZ+pA%K5}!-r>AT7j-KSFzFL{PccEr~a>aMI^xpnO-YU)BxyO=! zesRyjk|NFS?D5Yf*IYB{Eo#~{FA2|jR~>k0=>rJ5QD#ZGx8Gvtf#f2WW^a=A$WQzJ z>0H=Jvp*O0?4pu|&L8_|_9jO=4gA8@cv+5SpY`#QyKcL>hwlN+p8EANhxdE;hL<$^ z!=*iLYw_&Rhy0p7ef7L&pNnht>6e;4Kk>WQAKe}Q{2!YAi^<=~mgis@Y9R%7`+4c9s#vy+Eu)UHeVphqv=au@m1ryEvlzM%0$ywQmP6wto-) ztNnMx6wEQ^Uz+5YjJob!Mzr&DMdx3HwJ#d8-Tvj6j@t13h-Z%0nc%+QHw9+}hlzo~ z6c><2ES_y~F5lL?FT^Q+K#OzO1++LHH6m=$h{q6N3!~QOgs}Qy5u$OJH>{RO54a_I z?8TPo{}V0Im&N?MTcY2^$kCeO>wj~5bgr|iSmfO7L>mdlp@yKhnA)aK{(otkma}5W znF#(}?bM&geH9lhXxuAW1uES_y(A$x@iq~10qxXNl5kv%jy+dvwWz&;>iTtRB%_TU zBWBTxYRse{=lqcIN9D{PnZ|qyK8`j@vwf6}HcB&psA!|RAr~9-f2e4qCt(@un&yfT z^D*8QHBUE1%~vyfpF3VYMI=QVifK0QYuz*5ZN$BpK?JR8M(lh~%55oVO-}h9{1?pE zfc5~4(Zv;+;*ZL<_%W%z)GA{7ztke%qIs}qr!B;j=4mEcdfIr>)J88`=T8}}^HFGpH@D7v7mtQznh}jJ*Jz`UTavs4a|tb>XnYUw zTqbWo{_Ee=YX5P5aefuC$7s9%p@O#iz+3IVE;!q2e-tu}ShjAp_ZU%KZ{et}6}RBi zm}a!@VN~;frZwMf%n&1^$qosiHNVWqhoSuMZr>NC_I>61M4#Ns|H=6L#&^xdc7Jp+ zM*KbP{>lf7{tM-YMe@b|0NJMbzokEbe89d1(SLWbZ$RD$FZu;8;l6=D`w1RHU%@4~ zhmQka;Qj;UcV?7VIsf7QgHHpCX+}P0RP9|1B)>C~3xHNJ2`LsKH4Cf<{-??^e6o1AVWr9#>GAxIDFtfBu8D zgJ^LXT!<{-U7%vLy^@J=C6MSxZr398L*EIa#bt1OR{+0ZS=oxA{|Peh?-=ycnvD)d zbSH=w18s;S4>CuYz~?!+yBbReKF2iZM73BaYze)ck?w4SiVo1P&}>&hm+hZt)7=3Z z5iN6gpLar^YS_-En+BceQ?$QRT0$RmqPNL_g>X7_B3eF?AZ9?HW!TQ{Z#HxyT0WA1 zc`yw(TWDv~-3QzKoNgPZdjR@8!*({^L(qw6=_Nrt44sIU_a%t=(1~dIK!SJ#x@`YF zyT8X^Bceqe_i^Y%w0tZ9^Mwe5vREg45;h`Qc1RFUK_{YRy9Dtx^k)p)+2byNPV^$D zA$%4(5iM$e&p{`mZFKg*uflg#j_bT*4E!}eHuW`CBWOA_r`YJ8m8_+&`fc(eeTx`!DE3v}|SDG3W?&jpb|}azQ7eWh$qu3%!+= zt|N3JTADSL*a>=PEnOGrM6}$>`|Apwh?d5@Z8zv%P&cIm)1h}a(w#lOOP~|c(uMce z13D2cmr4+qLMNi-atY!x=)DZv+5KfeCraky5?%+LD2Z)^*Fz_&!#2X+(21xYlMX?6 z19YO6Y$NOoy&tF*+X!!hPSlodg#DotwP73KEzpU2y(BM!FcUhFd4E~ZiLT~!goB_H zrSbj#5Tft(23Ttjqp+EL=D(R_#AYi8hl*B=b;l-WgFq8 z4oNr2TPg^tpKUzI{QORWK3TKf4Sfn|25(C^4LT7m6C_|hRlN7v){t#;q2JH825g%L zoro5%!SQ~F{xI9RvF#D)i`eF3+l$ZFBsF-bp zo1hczVH@Gw&_B@9eF&Y%&*=!aLH`J}k!^$@Lnoqz;uXm)t z5*S1vT5$iXi)|k0y+Fmhf5Hst13>2M&4fN3WWHDTL*Jm;{LqPLp}0M?Y=r)imhNNd zM2GmeghkL>1k)C0??D>$zJ?9o{6NSq|8@L+!8W1h&T;7EK7TZ>F-d_uyh?c+D zwjMgs?(a%V$xiq-bRzRHwnF~^M2mW_d=Y#Xi@K;BhZP=*LDp0aZcysZBIZi zVcQJ0orF$Ai@M&1ZhYr~XnB##lBUp!Xi5K8-q&W(iD-F~Z7rb_(Sjn+SgwUm^gSPw zFcbP1PRGT*$bmi$bdb{#&V+uik?w36wyHkfQ6O3pB#0vDKY@PWZ4FF8Jp@FHM}qJ` zzYRq1CNdFbLeDbNojt}t=tQ)H@04i=K_{Xm0WU_v!O)3liRX0L(1||#Or{|m0-b0V z+X#n39|Ee<}$80bW_?BR4d(1~c-%eHaQ#~Ze@*ESLQB+WJz z`ZU9a?*iV|2mL!O-7nC8*KCKOAJJ?g72o!tTz&=#8$l;B+ftzunQh+1xCbEfeepqG zsM+c+!FvW|PS+Cp7|nJk^m{bh6zD{>oSZ2Cr>W5AXz3n+K2Nhf2%U(QT{p{Z=RorspTyzNrxM6@ht+cM}xw5(*?%g~8v*}%3}pcB!e_E!jXp5=jL>(50}z;iD6NFxqjXs zA@vb%0MX(yIMP9EV=C*<}%Mn9t{k#;YmkI~O4PV#z2pF%#4U#&mI z`)BksiqmWl;`35EqhINva=kxXqA2C#GWr-rG^bO#;Pi-kMvIqqzp~pnJ)f^|b2_&B z7`<%wvo7qMj?u^HSLs+64o=7DWArl$b={19MiI>0G5Q$&jH0ewFG4sS>pn)mT3?UX zb2=ZRpHc9A^(y4_e%3`8Z_nss^fLDeFVe~PI6t*+^)qPOu`F{Br{p$Wz+mDl@Jx|%Y*PhBUY;QQrc^s01> zezuF|a(h4T&#QD!=jH#&$LMDiaeN(&LX}sn`x$+_Khc=4OXW*OKcnFM=VSD%?OO10 z6!L!joK7V0dPX0kpHZlM&*)}|$6qhIN2e~ey!PJE1h_1tiJqmT#lIr>Yn z@lXT1N6Ysp#q6hLVY1X!-QxPsZ!+v52B$VIxqaaQ`JPdFk){`GdakDXHT@v#6K{~m zPvhf-L`c0R>s5}*^HFu0J`TB_=1ia^y+p33IT&bJRzt3jm#VnpnAD?LFXrpXIW)+y zDE$si_bEMAUN<+98RN8hR|dFh$@OSvG8Tobij0%;ILfY2t#@4~w~t_hLbX1Z_ir>i z!KhHJ&*A+myF#^IN0<7ZQY$!LD773`u-=;{^DBSa3WaJv-ga_5%`rfWLaCv?cu?LS zI~x?L_2Lh?K8Otp)%qabUVcf+{#>=rzzWWPR|Us+R&e~{3bsF1!Fs{@OkKA^bq!$^ ztj|3q?~l1XL~To$QNj5;E7-rkg8e)HlE+u&g+g@>MSq@uy|04(AFN<|R|VI%zk>Vk zsbG6=1?vx1uzz1Gng1jChA33`r?`z=&rQ_ETs3MPq0b@9ho)>)sM75FT9$vRt?JK- zuQ=2?LO;J>)N`XyrOD*$Q{}5dwO+J5|MPwDsQiCb`KnND?+=#eudZ96TJNnV*Q@uD zLbbk_^M^Vwg=)R4t=xVd8x*Sbez#n2Zu?d12s3BP_2k1ri$bYk{XcmByk7brskOik z^8R#TlR}lk8!fk2|BphozB=chAT}se>+QVWs(hQv&#%?<@2FsV!R5C)u0nNuQ8d^P zl|Pz7wch`iTp!E^g=&3n2f6-6HYilsnf_WJ*#PA7x6%eC14mlv+`1lH0 zJp|3v}bKdbvcKfv=ZuH^ZP zD)N6)<%dH3IxTgJ`|k~r`wij^73%xR;rFwHS18oeS?Rtz<#DER0)@Uw(%zNr3ibMj z)&2KzzEtOWQ|gN##C&xb;*^DSq$`2U@}--^mVtNULV zVE(t-z9_)$4{|z{KNXg{P7A~QO6FfzADK^Y;r%Q0O_TO3*sd^CJCDLj9^dNz`2u`? z=X3pBy?+&Ir+G$I`QePgBLC#_ccpqCC_J+r3w}ONtM5ZSeL3CY_^tr+uk)CEepJ3u zSnfDh28-i+!{z_4?x(_XyRq2b6X5w<{r}zoxA!%W=jq}TQ&{ditqh@Z`+xQS%i!}= z<(5KUCGTHmfY)zz|9o}jb*b-gh2?HTBbm<4D-`+zeE(!t^8M|qA&;->oeImHr-i{) zRc^1o8x;E3uF5Ng>i0enVe&dvy;-4JUlHb5Cu-M}N5A^ss0UqD# z`I{f$_C=L^e->KY|8m#KhI0Rd#r^jMSpImc$@{OKJB3!q*X@38e^bw=!ubInKlcK! zUq8Op|L5a!siOBnA^*=oykmvsj$>u8xc@}~<{zu`KN#Tlt|K{^Qb-Cs2_2u@-yh35QbZpSe;e(ZZelGC)vD|rC{D0m6%U`SiFPEPy^*kulkAKB~ z=>6dj@cFYkf9G-e{!!(PLjC-$wl50s_})r>zZF-q{K&Al|2dVseorO0_f<0giR$vX zRQXP!{{K0-|44mbDAdzg>K4yWMkTM`U&-q~7-0Uh%D>9aN5P^!MGFwo=sc)#kr73$mHXp#T*be6is^@|9Z|5UwGp}rr_e<=SI26+FA zI9(8*m_q$LEp?0Q_Xl|YGyg;P|6qXEUpz|wPZRk96^gFXuIiHt_4_ldlINdO$?Ywl zUzN=iMu69!+eGG*#=L)pKK?FI`CVbT)37*xPJqX^%D>(K%TKHAGXlK-R@-L=xcwdf zq33sgW$#~r&wmbIr+U5>miwPA48BTkKfjXO=T>ritN*_+!26$@C!ZfoYho-4y{}69 zlYHM4mivyeczz1`x>S0F<^BeX?TZ3Df2-^F1(<*Q&&cb1jZaLWxL?|PvR$Em{+ZlP zsr(-l`YU<;2P=8~{s6DPsFL~5SIP3jSt8%lL40Es>i5}7FRtYIXH@e0^dr2(&tt-|5x(0W?AX|9o6eQGQVhwaw0`dO4m^$^83tC7(aP#sBY$ zllN8irz$*iAuQxp^7w-NztwwNp?(}IJ=5a;c^k^}SN-t{_5Bpjkm(-d6$*26q`ec{ z73$l2EzaK`VEx(8^?&8xpwP?xQWf>z9jxT{OVI_EKl<}?@B+8j?_aJ(|F@n_>H6~{ zu8{xJWt>2v?;dIQvR$Em{y7%;#~0}Lk4qj$jgM2vhb;%{|5YoTJbpp74=Ysbb=1or zC;$IJoKT@J!16oy33>j7yuCv4ytMacyF&eZaxd`vPo1CIPvHf=Kh<_;(Bk`fet_36 z+Q{on;T!N9w(&kWafD1ZkGMO1?ibZ&8>-j&oo4n5Myn({Ze$wt?yTTxC#~GEpe*Xo2ziWpV#o6*aNAU`U-d@t)i0um1 z_n6vGW*wO>o(&4sdL8xq=i>3>s@+qezMazTN?`O`)PFquU7*q{)bn3%nmm71zf$Pq z^9tePC{*R>nbWvMZZEm=8bEBH5z^9;6Dm~aab|n=pHur+cxJsO==aZG$?{`sCAYU~ zKk)K(sr#)^yBq{v8d}a|7y|TPeqpa1zo*{2)%Qs~lJ{EpqtsQt%Gc8Ow8`;_%I|cl zQ|jt_=fp|5y^3$3pX|inErkTkDe;K-5qS7`KPo~WGhhT7o`3&FmiA-;K z|IVcR*Mwy4e$9HPRYdedb!If?aYZj_*KyPPH5}so#6lOZf6!j`{!RR~O?0gnFBzT3 z|7(yIFXQk|c|9thSN}rls@~h}8>y@Bh@HEouF9PPZTnT)di^dO)y9Wj`&HTfHTbgZ z@jGgIFHP6?U##t4U%yAY9)11vU1g7di5b9^a{-IO`8<`){u8KTI1RyZuwSKUI#d(DL_O?S9^?=|1iMPu1@48tr_Z z)XpbH)Ah?L8Y1&^F5@xAt&GKtg^c?dS2G@D^fT^a%ws$@RPO&EVknG~KBldITzfvA(e&pvy|H$EpK0soYX9Fun*O+^ zr)%Zg&D!;3Y5FehdE27>4?VQ=`AWOqUo|~Wiy!cle!n#R18x5~+VxD*^t%s~&2Reu zXQiv}tmn4N?UkieLFh5fdl<#AQJl1jf# zi)W#}D|TDh>uB*VRQg8_%l)hOp0aQKL)z8%o4QY5SlHbsWO~(pqS8le-&N{+$7Ny9 z(!Ntw`mciJ`$6@4sq*TOg?)dROt0cBsPxUFq+RtNDf^S!_pmA_U$L-%URCZVo*yig z{+623p1^kXJwD08-ZoyQPt?-?Ra@FsJA?ZFtJOKTeNtU%ug&{W>91=j?R7N!fF{ze z+AGw1YO003af(du;{B-f@3oM2H``Ua_sUb+z=i_R?OD?J7Ukx3KT&DAU*1 z(x2!e?F}^h<6dcR$aa;l-?XsL>@L$c($XL4A?=OXuJU{0rRTOkd%3hX(bA`0CGAbw zuIgR=G&`?818GOfKCi0$drQj~%AWOS+2>f5Lu<5h_`vj3))H|oCGwECX9 z57oov{ZZwLn(HpoBkgX!pelTT4DCLs>n+gA1C_q1wtr<$vDp7y?S9($xb51=^HI;4 zy51hz|Eughwf!smkJ|m5&KK~YW>?4kT+4@QKZ~?-SJ?}-|0Rg`zgkQ0XZu*~J4)H_ z((G#gGcD}%EbLEM*q^tsFR`!}TG-cE*f&|&Kd`WWYGMD{!v2GW{eXr2n1$U|EXx!1 zURL)x!opt7!k%PdZ(w0>Zeed{VfR|tue7jVZ(;9mVIOQ^A7x>mWMQ9XVV`SZe?+tM zV^I!9#mMJhedqY9OI>}hbUGyeNA;_fdI1 z>bqn^19^P)z4aW=F00<-3meJx>bt9vRzKUPy*K-6|5uLI-Y_Dx?De+M_SaXt-p1Pf zJy5Ug`F^k6j~}(~_xrT}Yjzd6y=pI>bXwjIb-m46%k}PZIZ+z6#>w>wyuO9D{{h;2 z=_BoUeYF3h(=YP)>O1!bEq$n49#4HgzoH%ga&3QU+W&i(mi~nHebQEIXW8SB$5ZX= z_h|p0ep^;*-xq_l`*~b@o<7&E_q2AsE4BYOUCS?vwChXJ*1x3H5AV^AcbE46EY$8_ zw)THss^urImOsbvcenaZe_XrYZ)owT_iFK=L(=8_R^RhIJ4wAh-)L_WsjK?Ukn5%1 zg4Yl4Ds^@I#WzSjjn`j)jntd5{)cvbmuS!5mc}x@dfumL=YO4c|Nqd+hg|LYR%!ZX zEuQtgT6xi=zC6Avk1o;bBVMh1-L2i9+FJQ{g?9WI9pwI1{cLM5Qvl>l2q4)*ws>dvlno*s9vR z5gkLi!BNLS23I7BLCtMxPPtBq;M%O*7;+Q(%R)%)6DO6>*1{2HkGRrFTZJ`(a-}l~ zof6?>?-YBb(+&@#h%*Yh<06$JL{w{$W+aF<1gwk+#%5PXsSRUID%LusdL=rcss!_< zRSf|vt3~36h7Dw%wwcL=~;Y#8=AqnPOuMg-N z85!xj9&SvDHr$0PosqGImQ)k#u5?14aXq%LT>~q5Rh^nfLL*u7nPk(icN!^Ov1&t% zogx|YJ6BU18!76YQH7{)k{if<;Y7ax*~L(Bo9$#o1i0a5U=uASjvv=zhlkY*8$PzTV`@gSBPu^^_>%GC>#cD_P9EqOQ^!%Q(RX3LI7W|7 zwOBu3& zE^Y(zN`)6UZsO<(!$)Pe$Qn7+^0LmPG48>DoH4^kPskouVe&yk#|#)XDk~?TgyV+~ z%^Eae_?Xdwq{g2!GN5$BvImW*IN`9Y0FIh7amdK5q2u{KwaNkNRt?ITm@{t7kP44I zID6p4if{bj>>=X^j2u3C;vE4VK6~^e{7vJB-%;TSWRDv+rsBUc`wsl!72l*GS>q>+ z95b|Kg-0DSa!l5Q3R4Zs8b1urPeu2dk_D2@INbm`%L;cod`Q+H>jz!^%_E15pO6(m z7UOgS+O{4zyy9b{^cgW=$ixZR75{ys@Fa{GRN>W(8kjY1-0%RN*-<#ZK3lpTBJSLEX56cdqR2~&j0XAy<@Vf%IEqF{OjIa2= zojfp*>l~duA!kegZvaj=AUmMcW4MT|@GMor0YkG#XOA0Raqh{<8XZWgad@p(cyRnF z0UQvooAO<3`}V%+3W0TqSgvqxoBcvO65p!AS$ z(TYn~PJJP7Ufu+NuZqDV11NHNi;1HHcv~aw*ooQa`2tlhrtu?(2k`m|?Bz9n0&-@> zA1RcLDt^Sr-#KbRR>j$E{Qu+ZZNQ>BueD+4S)(qKXf!dgsGw$4P&+Cp-6*JMgV^Sb zM$~Q<6VIsllg+;w{uDO^+gzh!<2M>?@@){3oC!jccSJnR8I87mCkC3lqmd>TiFi)0 z(P+;(B*FH4$WQd0?|z2Fwr@|r<~vbg820R+=ULBM_qx};p6qcs%aXqLH|JAg*Kc*u zw=!~ss&6rBk)yVKo3LNBr|?_c9S+LT^SA%a$0&y7!hCmZ|J#eJxM*k6-#lN*c2~i- z47#Py=|wx7z=~g=G-YdXQ9;E0Z7tfdJ@P+x{L8%QzAf&^-I4XTJsT3Hp;YOwpXvX| zss8$b+lzPZ{L}2~uOA8Ek(K-0w>~znpk&9lJNUVx`F{r4BU#{IzYl5ff9hxMCylos z8A=+h7Mk^_TFI_Nm0v&cXW!~;-3!u~CfwwrH&gisG`xzVp@WU7>%ucuRGLNX1{ zEBP4}G*g{u3g&((=7z=U@JT9fXrM7&s&xd@Ps`hbJdZ4Gx_dWS@?6FICC0OWCbb3Y z`T}pT7b8+Y4^;;*@;sjGXE)EDyYx2&1@!O$ufK7XEH~vzrw@o(;cgLq&j5KrM%?|E z)_9M?b__`;4bP;#w`h1W?cL5nb_Bb-nMw|er}}7nGrOsxeJMTON>ej0FHo~NYG*b( zoM$;Ice=XLizCZ!6Q|Rj3z~?h#|GG}xp*~q-YFVYN+tVQJIK=)rncA(ohq6$#6%i3 zkj*rzl}b9;vz6z5^BbmT_hbGm4ljiX(L<$7yW^}Q>3>(;W^+-?dm@R353J-ZOe3SU zUJUFtD$UQx%VCd*?_*8Oq!P@27UgH~3ZBJO8Ao%PVA+%Ik);Yo#!;9p?tGgj#DQaF>nhQ7tA({tCvyfBqqWBP{`(;XBVc9VY~?}O>7O*+#vE4Y`b zB93~8U155-{vD5twVdt(-id?8vx1SyAEcRWu*Fi7j58$Cz3im&d>V0{OH8WVpWca| zjeN46XEV2#MpWg|LzzrP`+MtN%CYnBd3yk>;=uvJJyB@}D{0+1b}{)Yo=PLn*R5yG zxJ`}@+)4%LD7rYTMy@>?brT<=0or$lCd86hjgg@dxI=3deZPbLmmC}3_RZjIPG!fO zRK@#3=l$Wyafh7x;rYD8y>1PE#8jSQ99d$PzvZJ@oiw@z3qg01CEbN=ae)8pKN!>F|RIK_6cdJi+ry*_sc9%z{@ zR{U_#7ZSF6WHehJp#N2Ii*=v-XUkQdu$)(|RI#jRN7%=ff4m4(Y+hRfQ2>aQ=rVKD` zxkcm7Gv&t#gGS@$=i+EW7Z$zzc^ZEHhvPH}X19HXsGz6;+BrxMw!*k$#7_7W%0^hk zkFcK~S;&UNpE*nq_fX-Wc+mpKz_d4x9>wW%<7gDVl_X#pb%}+z`&8b$jFhmLbVtnU z59b8LmOj?QIbvR4wovW`#S50ZYcw(UE0KbO@#5}UyxKQt9Ya&P#H9Y)Sz>A*cZ;}w zF*AHjxWZ1KQ^ev>@dN^+u*vb6iUq3jSP>(GA5hB@CZp9Y?z`AB~uCnpD+|Z(29||M-RW-mbfRIq0I&26~{BHg0D}X-O%K zSZx>vmBz?pkC-aH{`US?n$R{&^K10X1^HX~J{4aeN@pA9){bSi_d=Qz7kbmFx!?sZ z90sVMBt$v?PM4zCH4e#7mp9PJdb}s5+;u6zq4$giJeO*HgqBn-=lSg7bxo?G%xzKa zJdRyQRNj0j^3C=7x~ur@D_B@uLc?ncF1u-OhYN9v#+> zvd(!_xrr*y0L=ieOlV^BShtw@m6&yx9tmSOZN67&1R?^}SMj*_v^jh(o~+4aG>AzU z$}FnPV0WS?(Z0;HY%2UAo-KjvsWgS+;7*6v-?->ORI`hTVVFv<>bb~nEU}65TGmpW z!a0{pF0h{-0#*NPSwD{JFUY5|6rKc2ilce;e>_PP;=%B?|MDC-Hs+g?4ED3D|J4k6 zj-1Fj<~LzRW_Y-pqgeUl8L;U70x@-F%!?SW>6L&>{tv56x|hZxa(K2;{*E*Ryd1dYPbpR zy{0A?MU5>8D1m7(l#2EczPXlR`Z-`@x0ph!rH#YTm(Z_=VTHXZLs?lILdSm$tB#Dw zdmjFh7}jvHf50tAmDBa)V-70L&!)=TRC&SehXt0Ak;4AnRM`OI z%%=p*Y6i}WVSaBIoQ9>|11#oF^e=Le$O^+k9JfD#j=FtuW!fro#E<;|%69 zImh3v!Pq2U%u7&ws&arPr5~N+;xrCnifv(*?bQ_VyzF-`Gbaw!~#)#=}i zVhy*k=9$a?^g3QW4vW;VTycemVXj@m5#C16HqfGUAk@0bL7E8PluwocF&RjP z!#CcxzbmHn)9wqbaVG;)=E1vj7-1m=5ykN)hO{(B;x4BCVbN^%%jl{DaRH}z*asKa z;Y$p-gDx&t5w$7m3SjnUIxka-bjQ2^5Srp#dHq}`5PXh^xhvuae@lC>8JlHnc^DI^ z$5HHg0G+sdB#`##5Yj|KcQ1H9MYG6Nd-P7I}+?A#zDzLx? zYOOph7T`9-H2P@R0BueoX9iPN9DOkismDRLJ5_sy`)a?`KmUj-x6{4`s!U-ZnP{0_ zEmON^!i;HbD?Em_FxC4TglBf?g;nbFh*;@VvEBM8T_`!s^*3HRKo8}?Cn#dlSGXa| zO)9u0=7%FbqE|YAf&6?bO8Ek)C5~)8fA$I6&?lG*R{R$q;Z;MQssEGDXy588nD_hl zgWaeT581nKHeyn-r>5FABrc#MIX|LbdeI4YiGy@ zGJbXc;CM_V2Wa?BJkyze9Mb=jeAhuyp5xnJBV9fRCml^8WB>ohlU#wj?v$g2aa@A&dZ?$@ zIJFT0BAANNX`+c)9Ke`uyM|=2;5JPh3K<)}4jEB!%JDP_L8I#|(l6)HC5XF7J=a}; z{b1Y{(^8DvKi?m>cQ9@+WLyh#b3Zu1-ah5dG+y5yi3xB8or&ro?L7mZGc6JfO7dw2 zg285tKh_-L<)3>jSnrUdIw~ENf+vTgO1tlP0t%8jkKe`CLbW88TE&aT2S{LE!aZKlz&^z>~izs<90;Q*iTfH+Bi-RVIxpi}7zFVDB9vHfG* zb32W%VqK$g13`KYOB%Q^4{#*|%*1=wkvt>L#?g1KaGi&z;qzng`9gf&?nGorcPcW^ zF3qROS1<|&;*up^*8%*24-wWC?iIVkpke?(juVxNX?Hj%;*fnH;v0yhh~tu|8BqgE zhSwZ9;DiMgV6q>{1G+DWqwd`}QMqz(x10pkEGaMUbRC97&ZO057FT zzPfOA`BtlZ3+F36^T+ohhdCqbPQuOFv|~Jxld!PI|AGJyYSREeC75GH#WVk&9;)Kn z5)f}I#7mYz(0g24jQIl^uUvuOJ~%KG-gm34ANkcGPBY=_9l)$cjP)_PP+rES5$006 z0~jJj)WZKr_6OOcs@jW9pBt0X4w^%?J}x;WmFF$DPT*OfX7%ig$9n0(tgdbkyNZ#2 z^dhS;hhFE0$(k9;1wPLKvsy*^ekz8a&F45uMqudNx?DHvJkl~FhE^WOv*_V!RZC*C zH?_fAA%(;=dDc9 z^#C}?{74L(Wfj9o;2sw1m|fZ<(>X)aD#&t$Z8!~`tGp)1{W1J%gmB(7z08691PR;Ai2ct1IKg!pLBYO?X(z*kGBqTBOLBGpss&yd+ z2`g3yU7_N3dK6#~$;$fR(bJgnYk-nPNJHnd({+d`C!fYugW(v0+@*)Vez}ffN)b6u zzVoh{B&L4_x~W$0I!o4iy^iMEL-DP)P}y2}dYu1^= z#}weJi}vQz1DOa<6Z<%i7s0dG#0;b~cmdkt!p9gelW&AybCKnlZ@?<$y1|OsrK-i*ydJq##o(_cjn1wh zt0MX<+zwvLD=%%8$>SV2iz_Aed8(TpX=10|xmZE&v;iy&^5ImCrzu||bt~bYh{JFN zFuT#wth-ead#};Hh&}rAC^;Z_0o@c2stD^IJ}oXO;3I%vS7Bwnd ziUlE1WT~$1RM0bz*M$2TyKx4Q$wnARp!n-=7O7aE6R^hBhdp~i>yv_zMQnQ=&F|@gAym1Im%%PiyPFGE z!f(t`^tGr1fPzP@CKJQ5`TzzO>sWdStQ9!ve;d{WzOQ64M=75hcH9(5bsfR=xa4F0 z@5&STpdo4aXRp!N^h94TDu$~E zDI-O!p@#J^UTH4&&Q)KKue^4Lx;yCy&Pr4H6vaj{aEU|GZ-i+>0|dcTbeKc5 zxC@-$+BF$#GO%3Mpc=LRn@}{1-Uq>D`uX2d10Hb6Iq&oAb?esBNALOij3?}G}nm4BTaYi6FYumI=`O5tzFa$hjApEZJbGO}-Ko8gG ztrGdsSw|e&lWL^o0KG6MNw-jkgihaAc^WgFYUXPQR_luuh`t;xbj1OMtR%y|z~ldL>6R z0!5S;WHgaJcFaw{z5*0rW74mG>CR<+dB4j*t|1rWUJ2sh<-SwRMnLnkdBqKmz&aVc zUk7{b0Z+aJM72mI?_@W+QvK4!l=fmgXioZN3C?OvzxVkPWzhm9>Ev}PYq*U9D5W>x z5Hp08ZNgP%0+@eTXEvIwdZhGu$*eUuBBGv`mPacXH^jwB@3jGAw%x`xk29h+**{i%v z75inKW+d=ub+%?~{fHmQQgUQj3o|1PDT>r`q-28sw&+$wDS#ePXv zOp^6}gg+ezPGWm4>I=W1Q9bZO2CD%LD_Z8dy3fpUaZ%tZJ%HG8!B?T12y}gva}DdO zTG7Lv3A?Du(#6>oNeKJBV*UVDw@WH=Yybls6}r^666nKH-)%!s%!2sAaJ$_)Fey@> zM1Er-H!ctvGDjTIMo%9Wv+e@x1C) zrWu(?f*x(+C^0+ywy#y^KJi5V=OU)>z<~pOPI1RF$hqPV76b>RUogMjWmK^VX9Vpa zM+r|5%}Cl_YSg)|mu+DX_O`854jo4z7FY`i_a`78-*`3s>^v z;3dXmh`_rHAj3oiL3->8XVb_Y41*~a+;O9f za4wH5nI?B1O4FK9xDhwq^R*M2OFhcUM%uogkW`jm`!SRxGE3^-wh45UgMa~I?AV;yrd!8yf3&sLVHRk&?v5oU|5#0~ZYkLyoW*NASnS`IQL}Lwh zUB8-}8faN*`qrD8yYWL=}k`<`jkLo`TMzZvez!%vq7_Z?2-OdW4n;TD`qK8}4dbhx_QID>H*$-F3 zRcw4d{qz?oEWHMZa|WgMe1NJRF{#hc4w$um_%FW^l(dTI!5rhBw_T%USwS83T3F|vRT`royZ-(01On0_yZUV}g7K;-Yc3&rC8N^ctSYp^#=Cn~S) zr^sWV_A_*{PY~U}Do?mgf4luZsdibbNq6n4L=`{c}h<&1Sm*7@sX`%4oIA~!%?~<9aV{f+)pYG|Cs53 zC0dli(VWFHOoRP^eUZ#Pod%s@rtu`-8*tqX;n!#^oYjd>H>4T0D9(=_$ZSgGUT=SP z{pqHSSac?W==rpIvo<7wAcPv2g}t}o_SC|Z9YRLI`uG?nA(87JcLHo9oHP#_h;_=mNGd&?)pUbxMIa{%kW!-1rCh_-Su>m8F zoLv&C@j$ekjQsj~Gv*Hdjk2815LQMV?cNSAG6i|3Q_azlN`t0}1vn{d$DKb?in^R# z*HxRt8JXZ%+pd{r1fJo9IY`D{d4rEEd+mLR-RkBj%6iBX)Feesva5@fRK0d9rV0TO zfk}#U>JkFAlIiLB}f&v+jkbEcPNWv)7$F^eqp%i=v_vF2tK%$g_~dt!ZvX zts8~Ot`-@n707&OL@apP{r=t~0L;61y7437xAglk)n#I7B>SBe&4*k%Xg<_^X?Q%9 z@264m)r%@k`^Pr^@y)bKRHJBGyv(^aZlc(Dxu{D$-Sr6V9b`|JY221(NmF>BN0G2N z^5Ux>(~NXP9%*|2DjmJ$d;s)BQt$jQRdmEiU-MWMXZzfUQQ$;S7sU}FR=a(L(uUgU zaX5}qc_&@)MPeXOOxy~!4o?q(n?rsMpFXP(DjHBo--#u?4C#XeUH~6)6hw%xliG*j z)!m_NS-@Bi)!+Fy=He!4b`@zcvK&lLA~8ree>-<3z>ndjkw~9^&Fd1mxZ|xuD?T?4bPHqLLk}lTky0EG)ETTtv5gS{Vq*K0IQHV9#6i#4;e0AhLCH=7PGA+HH0-; z4$~+ojV$n1dh; zbhEe>3{#eTg@m}Z*Ndw>Gbj~K|ACW&OBQG4cURfa&-=IJ=YhRJ2Uy z_rX|`j{wL4^d-@lWP;6%OU6hAT7vX!K21obXFF&@o{JLGb>z}#sAE|2j9*f7ZcWFXmRrM%8_|R%fKjS;3#^c30eq$WeQZ5d7$Sp@Zj(k zUy*}oq(FUJ-cC;+XGmAb*d&Wc$ zkJ96)HPrJvTY;L>p%4OtAR#VPDuTn+xB@bvgz%i=8Kp4)O02vrt}22qTPj@KC(SgN^Tr15Ty|r7{&i zC@JaTZJfoq(61UBdu*?q0hJ8og7sVx2dZBz=)++FZUuwfiUpuiZj7D-O%2MwcOcB^ z#0o?PTP3F$>alnEz*RA`5Abg|N+T&jJNQjFCn%>US3^o6(^KMDKb3&Fma}h|AV_Bb zbc8vW~)Pmb|@+!FF7T9F{Rw~_24}t4n4O-+KjprrvT5yD*o~dAe29@4|-X6;Q zuvkRfps0*TUWk|*BbGyalTWWzUSK+88CVAoD@t&qyEX|aS-`rBgK8%7maNI+r%M<- zeV}O|coHVH0q!yk#<(gZ>z(@s4;NUc(?6lYll>u7b2IYr3@|`ce)8(f^lW>bbVHUJ zokk?f;W+AapP-d1XhF3?IhfO&3ra7{l_>=juwTIf7}#tN+P}Xl$%z~eicA%`P94W9 zy<45x0MXSFM}qaJj46$!!VHeH&LB6kgkK@QQ~i3eQc#8r18oese!~u&)D0I0vbFe@ zZbJH;FvSE%fTWTpxB2a$F@CTi1Zr@?RV!4qJ!u+(FG?&Um1zAQ}$ne;Hg zb50!DZ-VCUEs!fN@*SmNHRRGikrlVGS*(Zh0m8Egl*&UNhyzIi!bQQ?JTL$d;_|%) zd9x(mqI^r|QsHsyOgdRnrdFy>#Yp`ZsQJa@K6v29vuxD)p&ag87h*h&Lst1=qs=mx z8lAo_qPHlEqZxRnBkHe8a00NyLG^Wh1O?xZQKNx2we5Vem zly$a8A^ez<^(I4d%+6aAej; zs4zjn2JXHtJk24dg_{6pkob7Tcf)Fvsp^%^5*>M?yl4sZ0n=qomuH;D*&2f&@1?>T z6c+@cPo$RsI-u;{0lRf3=p0ZCJKNlfLy==njyOt;wP^wElt)4tB-^z6Ho6?VP{Hpw zlTT4iv;p-&`UI;C-VkFB{xjw4e+lU*S3vVSJhnI+^vj-hh#=VXEks?f6RRy7_*8K^ z6sMCxO_i;XBLX@8G~}6}q-pe(V|{YTJyf%WJ8g+h&hec40?ueWnDP10xH#OHb|2M1w7_oNAB zNEZV}co{fNdZdkV^Jyv`IWcu_S~u_;8dU&JAWt-tb$$t&>1Fhw6sx$PtALV4K_mlR zwUwP7c7E=0J57y`L5{Rh2N-lKy@uPw6T0Qku@HuwLgKk+5bVQqP$Pk}dQ*ZR7#VCK ze^CD=ElHPX1(9}VhUk_4TRLrPe1zz`GVG=-47aEro_GU2VE`FdD7zzQz&gCxJ|x{P znZXA$4}Rj5vcHOUSr%SGlR>O;d=T{C*GKlH%|$s`E|RFCPn>t(rMyivKApBUyic}j z&W(gc6u&CUK!Ir@=AQk?MVPDkuk+0u(IM`N?c{>UJ#1`}eM`9PBF&kUq$yjJqnHj$ zypam`>wHSjLNX@<>|u1T=)1%@%N>rR@t|p?66la)YqDJds97O&q)n3H$D7XVcm;2k zrwqM022AdND>17Vg?AU(SmzeUmz z180F($h~P4f+{kr$57ZsE7Fq)Pwd*Iil}>7n|_YE;moyj!h21ery&Td&Rpb2r!lmx zC*V}ihwFVsxSi+5`!swj9*>rj4w}>?%l)(f(Sw)C(L@)>`D~@mG~GVX4cj4tKs>RY zH|}iPNV^-5Cl&ue0v~HE-E?)RCRj|Gh{AHNV+E)KBolUBfhR?xo-iHLHl~VQy&~$a z%o)*lroD)0f!+| z8RLS($VTr1n4A>ab&Gp{g^rL23ZVFb?sKMDhhl6U`sG+X%?9Xq_~nNX%_O&?f|&p- z;_`oJOJp~Iu5ku;gx^Bt(CYFBZJW`w#bV9Xs+-eO>RZf z3p}1dS0ObWndy?~ba)k2#2^oXS;Rgu3y=dykX}@9E zNE0rQ4R4`ye+X9qs&YgcW@hxsly`iMsvgx`+t}Q&0T2#u31z&o(ekEH4*wlI2pm%d zkY02&Z-tiwcm$09liyGjO!+h@{ID!YTVu7kFumBHAuqvaF9V(a998u3=-ASbH-Q3} z$*Xe^@5p(Jro}R(SK-e(`3OCmLXV`=<_!8Sh#1oi(nn-L^J(nT-=8j{;S5QU2j(o z`6rMA;uDj@?scj9oZ^Ej?_R%9Jp^Q112HGk?DHt(pdrjrh>V-Fo6z|w=JcUyk)xWB zyd% zZ=nu`Cc`oY5!`U zI}?G_!HYvTWONkCE-fVU^rthZ4Y{$(KBYMFSDiN%ZBnuSPCORZQV@%(vnLGncpaXCCpeu&z+R6SyXrzh_ zAH|}9?j-yMp5}Rs)@6PSJbUje$7mMBgc-CZBf~m}UIsF>tL6@j3i4tAS+;$YcKK69 zQMPElx*!6i?DIs|7iyyi8yh`r102Pl*)*!2UFE562%uyU2u>?eHH(dHzeas2|5dQ2 zvRVk$HAIfRpnsaExcyc7Er!tKsDp~4!<&29^A10vH+p}Ag+Ja0zNK=|1MAs4h!8Rn zEAv-LV9@WF{CPUp?R`Q;~X(&#Srpt<{8gg0M8&m z<8I(r4da8V0>rEx*6-3MfaH4jqHZM;h(YvWt;(!;H1JNqxh73LvmBZVfey96&r|s| zEFi3;|BP+)gBEeHecLhTT;~B8QP~11qniq1uo10(8yRxIyE6^oi@nD^eY=RZ;l*}+-son1Pv_=10umH^GFgJRg1jr`B1*R@evkWmdifRI+t$mYgPgALKD7_ zNUwKSi>g`Y(e?zH3V6i==oE1+SmbMH;{5h6BW8s|8+$7QORktlZo?G z9zW!0+Mj+=&LY#?ELAPy#rT3G?uRRCg=WLn0S5jphGXX>jEwfow+AdO2$M zPa)efcP05v8dr+7WUwCOX>!E)qg2YvM?6F$rni@apQ7>S7XPRiFk#&~8V^177hm`r z@o+Kd&2l7ZD;GMIE$|&5pho3(y+}Gttg@jA4!rpnG*{NwXJ^ST&^epBL`H3pFQIKW zsuMO-@D{8!PGyuY(vjk!7qdJiAN@r@d0ayx7C>7U$#;Z$_vl(NyI-h-FKvXeyd`DM z=1UBgf2+3u(fP2>Ka<*O2wz8Z)k`iHNjO%l9&mr4aMG!u6K+WNBsmh@&Vc^Ro30}u z$CE){!GBH-zmj{b+u4H>fbSH~^Yi9;FtiR-ZT{7@oA!e8FZC?H?(yj4tGq?YZRiyq zs$Bdqj)UDuAkkANzx%{YgpY52LP`V&DkZg%V=CfMrmn69)m5r1FKzb#aaxj1-^Dr| znYDgQ`nVUMHGdM}u~RpTR9p>(=LU zWvlr=im18M(@ZuFfy8pL;W;ZO<6mf&lzI=Z@7Db-hjc)XvsI@eqV}lRnQp-S3_K1x z4M$YD0A7#(z7u!-+^s+m-L~R?Sni5ha#rT`oY3?9Z}?v4iyqg<7)$GX+P}A*KMy)l zG(rG>1Pd_rl*Xw6E}ZS+*X=M5F%XHJScH$kpgj)XBJEWO@Su9cPd@Jjsg*bDkYrYg zM5tO{%zzihsfL$E^799z79gtd^w(eAHn@{GsndAlG9>ku`ar5LKoc_+^_h9@{U{&n zXC4~puMR{ELM82Oen%ckt(;8WdoEotb3s_a>N)+ zcezZ@@dx{aK>ZWR)-Qu9sRC1OhFCv^K~$aMq~&gC_wwtf&E8ON9}Ed?ru#+EBK`a9 zwKO6#cf7)eYXMawPbE=={6qG5 zQ_ShdfX=}k41IYLC4F2UxS>^k!+GKL+5F50RBifR6&Zu@RO4@QPJNN8sxA+{6b0VJ z*HoVigfnqA%^v4o|<8QZBiC5IeGBLTK^PWyP0 zrWFM{-2ipq)`BS#9_i+rz@@RD$cY}23H;!OkK3Xjb4XRy zQ!)p}P7iV>VXu>boxxZEZ=b1k0npdDp&H%|Sy!7Xb4cV&?{pr#6^n3=J1B`8`VJ5D zST!F7cA7NAJH}x@i~t}d%p850G$!+H_>kjr32|$2=rmY{eJiN+Hq)bBDs4t}v9cW< zS{W*MqSC!@`cd*m4`^|E)jG59$=0zB~rHgPXn3e8N{G}@jIrtdj4&Xhi{ z)3kJrGSTc&v^`zcy&q4PogShTt}Fw0oF7LYq8kRfY|jQA zwO!>Mj{>g1Nx6p+=OK`Um50@mtEcOhtJn$W40iR&xFstaaTFKk)GzgXEkiaL={Rz1 zt6E&cN(htW!~n-4lSC#{>eE#EJ4x)v&Olw%OIi6t8zhn-6OvC0K6=*)lCmWWPiQ~I`ryv33o2=GiQGpHd z@<}u*wG0hj$C`$t;F9+Z%0d~K@^*$bL}STIP=2Ko^*ySR{WM_wT%ZY5@FYHj4fek+ z``T5*<=AyIXrkl=wzvk?ori68LWSxRtEr}O{bIH$lfAgM(~5@v9c&5{3uwnPL&(a3=#%|1aG)7eLP+<6$iO0s z$WF%g4K%(>M>6Xx4u+&e1inJa0=Phqui;_jdk!ZOf&IKCO=NeX=jJ|O#IOBaG&k?F<#gghZ&aXZAu3}Haw zP+o?@EH)ZK{MFw|MT!PNtTnF@tuhHTId-Na>;+_k=)Z#=j?E3ii=znu$N;4wh+4m- zu~LdNOWt|J+r^znK-JA+xz782UQNy{{Ge12l=5m+C@S(%ASoaE;`5d)A=hma>=xV6 zF^7Pc5Di2*DNs;A!!Zun2c)m{th{43o7n3GfrPR{DTB=iX@?=1062MxZO3@I+YBO@ z%ECv)1w4$JbatJ0!92_J;AR55&5RONVK54BMqIItj1P{ugN|IU2oI^z`5NpZ8_dZzA@rvj;Z}8Z@hyIjaWSntRBLT zRFqJNjyg<7KQIU-+@!U~U9Rak=XtN5mm`%cmP}a3N&MDBdm@=Ml+K-infnH@vje0I zl_{u4hhbkaL8KC>gGZsK2Z+5W-vzR6ChW_JyWS%w_YyWRP-{^Zk5CNA%v}0wk}1_| zuZD}T=K(fyKy6I}U~_We3`YyAU;X9yIPpL@ib)4-%thg4vU@bii z<|gt2$^G(5I4ca#h|Gu`1+Jqx4wmjp^f@^Y*0eRjiZ$t9hTK(J(R(gHvmJv{yEr0N zZGfq4iE+7{YnHv{S|_R22@s6nxwgd7(s1p9<#0fYT{OMR1QIND$`;uN(y2HBUpvw8 zqGIkbQq*(>-+MTyp@%CXyl_f1iF*6B5K8ZmcnyArbR9aMcr#ptPnG!cVboahNL*L? z*z>&ost?HlYOed5F>7Y63q`b`#>s$f&QnQ3LBMp&>1p9qaKb1Il)-{CS$*D>uOjJq zs0|;%`3wNbi9N>Y-B{l7H!82A^Vcc+d`qy5cgN6M0BVch43UCeL@9(IX950fuxM<{AN1F16@cpCMQA9v4|G|x7FrCBER}GGyvwk$1@KgL!gJUP@m8kpvmd> z`nV!+9jVcWexo9|vGNihi~u{iu<|cZ4nh<5xd6X{Gj{_Q*dufm?M&$gOchsTEQHYl z{vmKlq<(7{D&IW+D5SMW=4>X=ID*NZ6`f8u9^4rc6YqqCY!Z`+mXstKhif4TISgR? z1iW{N_w;rC5Xy9vG^Gf`+U0{22>N{`L#f`_CfT&@>foRo+knWuPu8Z(<8t=XEJ?=> zT`cHy{}v+nudg;Ga$J_e494g~u@1YVr9&(PD;GVbBJNHn zqF5c=9ay;B+ofvm67&w`VON8`pU_FHog;u0TO*m-aV*ByELY3}wmCp2#|%^;W`Ha( zKjoe8Zb3BQa;3yH-%SM#WGm%f z2hVH5Rz7n#K&dKsMj4S#8;;YgSm-`iV(i;F$}R=NZtV2v!%hnzt0y$!ARV%7*?|L3 zC;OR3+&sZK&@_gT;_TVVXG2c^d4pD#b;CnRTj-#>2^?iXw zK2%b%d1kJ$8^G*@6vJCqhtQYOyWX4jW7ll#T^54igQIl4SO=o<0;|a^;=831DZ+H( zdtn54VPu3v2F_g=C;Jv7==2h{Z|nsY!g$yE(#X=*SIn(S;r&1-9a9V4~tg^p6HxDoqMtFWF zT7Kk4ZM~*@gHP0wT)H^SvKl)Du}wBO(&+e=`T=oSd<9`Pw)9ZLd=2oR8HK0SP(wtE zUMqS^!tC*KDg+!Y>IYCWjfxG>$TsnckU_)lcH>tE@e5S8fFqv)j@;2KZdnjjfUpr6 zf*}kwrUp9|sUg)|FBcS0x@oxi9kw|zZAb}LWKYps4 zv~~dNm0@}qF`+0x*ld%~gAq&43zPs=lIzE*&!l<|aj@8R5xir>@mR4WOQoLF)RP=M zpc{wa`z@vF^F!aN0Gb@HbESyx1Fzg>y0MiWHvK{g1$SR@_f_2c+3A~ozvHx5x=_9M z+q!qvsg+L0lZ&hg^!v%{gHtl4!WSI_;Hgp< zLui_YAQYnnWgdiWBii^-<5g_?Bo>81-9Z%jtKGY);IJ%2OEM!02jN~8MPOJuKZ^RQ z$(~}_2rivH)M=>N0Jk7F96D|)JY#wfV7$ql$7IExl9jCxhoStFz9RA&B*Dcoa{mex zJ#D3s>*ah#!+XRN2%D%vp)C*4Z`pJ#9efv4?qOp5xg74kBld-DyWG6R2A_y01T8Z) z% z5S|E_7xj%q50C4X3-Qqb+)^$?cR^Zkh2ar4purQDg5Ms!`ZTP&PE5Qj7GN;~X`}OX z0o-h$)!pgC&D93P)1cC^&68gJC3K(o*k#ZvxH0SbnRe+QqoiZSv!cffDL!^v*+k`M zmQeaFsVEKsWa_ zThaLm7^LO+G`KPp-aAlZHiIe}{?)nUx`y0Cmb#*9#DGOQ!vRZw&}mIXyRN=6Xv~*X zsz`=&dr}evpyOvnzD5EDf`&Z3j8S+O1AYIi z3@1{Ec?HoGAsheD^jFsyoH@1lR@Hv34;O`uD84_jh}~iX-0gi!u-kY z6Ju_ka)s=dJ?hF@6+O%Kx~sO*xdxF8-Lc#g$1QyVlRkLl z=Wtozq0uuaC*=Qlf&Eu?wH1p6kpUdr1(yzZB{$~VmmwLA*Wdmp>-Vset1(`MD5s?b z+MV<0=ya8~peYXYP^y#`-#jf&~6NGaxzDM71NvG)u}87V_@>*k^U|zbXzznF&^_mJI+sl4XFm8z z!JFuxPW&A`T664ON1}7HXWiaH20ejJu`hx-@O%iwmH97FYJ1Rd{S=80{OLbqYmW|D z-B!%r7FWCRVzjcu<#H_nI?0y%tZg|SjNFd;Ios6F z^)D4iqOvZq3=wZVuCl-I#lqb$0<=irQknq9sv3PbPWP!!bm4p+kbl4EPW5onIfVEz{TweV(j;(G*C!@d1cJ#(w>oKmWj+@3-UVpQC#&a`*+X2c{2h0doP! zi4ATSkx%aZgXr+z{~I61reNqGM$pC*@QFnLp=0V{)mK2NARb8zuguqgimt)S4j+(p z`0NzxV%l{Wkr5yH|M7QGznCSja{n(})fW65zS{g4}X+ zJsUT9Tggcib!_b#&5d`6MFZmJs1(HZzu`#)X9K>>jjbvjG$x$EKtKSIy3pX(`%At; zLF5do(NO~X-C)BRAHRG-%#f+p5BJj?Z2Fhd0UHk?Uj7`;V}(PKm=Jk7sPNi9VU|T! zm=@GPjR8+&P5Cttc44kBjsNW|O{~VAG$9bpQhH0V+dL=_hB@g5r!D1Z6Fm}ngS&@* zCE?NVYUK6k5sQ`l?3dqvQpV>B?2nGP@*ki%Jz$Mw&N>hR3Zk+UiAD4yNeWhFCW@35 z`5)_nPWV`KRbkdcMcB>>`Ys8%b~~3jQS=#E|M_O8pGJo}*#Rrv`_$?`1Fp)y;2t|18cgS(SEE}@kogXVT29l?j7LQ0ZCq9@Tl zfS4?`-QT>HRnTu${4}s2r?m-IRWC3|(tFLpF@k%9#vzO`ddK3L>Y z`L^or?zJwVfj#Y@ZWdp}^k&FUBR(BM!n6vLKPnndRB|-fe{5uIFC=-;{mZv>X<67J zSk`8cJ2s(`+xxL~DK;)>R~={hAfB|o`{YTGgJsxZRThqB1BqJeX?U0WT&wyqdKs|! zkEOKuu2*q&N-!Cd1?z(^Wc8dT67S&zvG^PkjB@O)&Bb5`cMam<7q~GfFCZ%)zZ|qW zD4=r4*4w`VBbE++P;MUi+zMKU0I+9;br~hK@kVUKQmH}hX|!Xzt@Sqh?5AYAB$OBr z*X!uzv$^^aU$U=pAm<`)McxYnT3f);5W7niK!otFh+9WfkrY7HdJ!R8Qw)0WCU-lb zHwU{lb^zjGsqCJH@&dLPz#R=w)&d}bRv<-1J;PZj|K9`SuJDPNKest{1Ka<=rEUb($nI9?c|$8`f|j_QgDq$%=%ybIhS&i7$NwZkVte309b+W7+K8ab zYr{Yp`knS;vH@#E{Y9H?cD>;b1$YW}X6|FKok)d6EZbfx#`i0|A?V@PT{{6$1z@O{ zgQ?vUM>`^%0`OMPu&&f?@C9w>rYz!Rb2xjOU&KjlE#}?B9{t$%A>50tyb<2qst<4- z)DU#FG%h}sui~bV(Ve*gNyqCl>(}FG))gQ(B^NIFiYl{a@SW#)%y+F-=#7rGMR=w`r(3Zx>V@kv7_*O+&vhHVt*@ zZH}WGh}Eug6+)EOUZBDPz(2X=NIzOGq!D6dL0fY1o-}mA1BDcuocL=PT6B798y;vB3dnZ5YG7PLzT`B*GI z^+j~i1Z!GEkpvtN>_qXqX*jHzs~X(=!F9RIJOC- zpW9sxPi?#C`Rw4L>^$1ifqf3ZkwCnNF%{=QDxv_6eMHUA*!hlz?wc^|`gy47>1j~6 ziKgrFN9b;Z`iXxanh1Fg;z{NpPJ94wefAUfp^p(eyW?a}M{LN5L$8xqho5Dq_NHr{ zIP6t$69WbuKT~$u|I6OHz(-k~`@hUu11zsaq1pfm;N*hhgo~F+AV4QDhz)8Wd$@QT z?mAqOz$7GGl89~hK%r%i3fu0%g7pjtt!F^2?$JWsdqAi*D74){v35s`+y0}~)}6_X zanAR7CqcZs|7V~7|9n2@^yy9`zUAW~&61iqJk>KWOW4-v`Lz{Kg8V z@m_6+r^D%^7L*@;VdE;|wN8Xa%4D*2FkEUH#R7^n2bpUCK9oUQGh7#y49>PPXSzkJ$rjod=>2drJ?42pf}E#pEyRSqK?KEhRJ$| z>l)*HOy#yi_QsH4MJNi&M}H=)2t2nFlNUG7IJW>9IwrBnQp(3!a^JK`_2zeyD>ZDr zyTLq?c>F=J3JNp@;uEI6Z@iCZ3j!Z8OC%wGI(ZjN#YPjJ9NDSzi1pjS>dQPpcydZ` zGcnj-BbXjx3?pumz^5D$5=5A4xS>hintByN`l9Y@1$0Vii243QgpmzP!gSm_HkipF zSM}ooe=*$vV*Z}OUm%(|Mv{cJ6LP(D0AytNZB|^3>o6vi1MWGF5pAGhuOhIFL>spq zUi0U5!vi``0*6M3xdk;MfEV;2ymRB7-f+olkK9p+QMXt>YFi)RVeG|vZ#@|P#&M^G zgNI)C*bMs99dUY>m$s`GqjdaZ$X0*Q!!{B2d*7Q#U?Wli z@WtoUqy3ui^3Dq5SosEbj*WZ4e#X`}XC##V4g{y?P*LlF-ls+bAErab3;cWd9yH2( zeD3XVTDqRGRD|^0sit?}k9{2=0^LEhvwu5e5@g;g2K=)OmYoFsGI&UVdtU#0uY;JI zuaS@?I@KTFFV7Pos#=vLe?(r>MIEidxY^o_LHk2oTg!Ox!c5|Le*9;I7B#eEM84(8 z3qNZ@40t&Af7((_YFZK*a51xsaIp+?s|xZC%2Mhdg~rznFPlk2htGsAF!RcI4)sIX zL5Ru-%r#WaL5-ZG^3Fc5FJBN0$8SMSK=7x<=z(K5`wGqV4N%!&0b_p7FjENsRax9? zVU`|pR17E9g+)-oUN!n44}=OE!G7h6Q7A$bnHJw0@0olE8aBg4oJFLp^c_Jr+0wUD z-qwYAX1?kqnj zSF%pL#|?fe(m8G*WjOh`*?xU-TdR5Wun6K93|R#f;D)nirfkpz53-?<2q~49nr7Z; zXFggk0Kn294i}Q42PPD8MoCoSweE??;*GP+Q1vStS;}YdUVS)=T###+(;``gTuI(> z{KJpipAnW^u^f1+*@~>P+;5x4dJ@$UmZUZL8?Ip6*>wo-w)7$a0vs=Ap>l> zzY?e`2hOP3>d1Ys*o;YfuZjwexKX_s7pZk1GsHk1vPZ!mKAOl_=N~Y8dP$NWP_W%@83x6W7$Ua4HvTce+3A9*m z+F+QO`_Oo9i#5c=jx;DmOhzh(J=F8iFT7~S4OC_s(N$;xG31NY81=Dp93jPSPjnxl zPM8xRa4j%{=jQH%kXH?-(ovL!YW#Zt)(b2aGYG5Kp2-AtkoHrE;DW0qN@U9>|GvpE zOY);$+m8?~O4Uu9n!PyRXS`mAv9)p=ZM-oZe!?W1I-JWfuT)3WOk*l@Dte72Yv}`# znN}vyKDZwC%xh99R}1*INh*@rI--z0AV`OkF)8sg+)zvj=0G$40rRMyvzIi&jAk|W zEQ%RccycM$zV2;~61j|cK+H9=efOb6i48feXO{wdhaMJusVy3ezS!O$UrQqE+U;B52i>%cb4ix=3#illG*P3 z_*~#r)*e&NE*Lu82~yOLyNaEDJI@Te)V-nZlUgdC7q2ZK0kMXqZ_=SPJi)wHB9;B94i=IF#(;wHS0PHb+Rg$r2}OM)PfSuQ?k z0>tY6csb(n?|mlqv_!bKsOn^~x;~j*LfsujpdbayBxlH!yfF{zBRDM-aZs~UOaZP+ zNAs!~UsOQD1Wp|KBgBt6Rkg5Bsu=-@kMCqe3W?o{Pg_|FIcg!X6VrdcW`J$?N-tcE zKeQ@D>~m{~h@zR$i<~4wf3ma^vYOpNy(cJ65Il#HmyI_y*Tl<;u^J$Zp5PeF+qhpb zyf|a-Xs*~eLbsUtE zFkV)*{eA8gcrYeIayXG-bYCF!oS9$_=?hY_^591AqZFcY4?(eI#CBi9x)Om<`9Rfh zUx}iEdlFj__2QF`sT&hsD%HR}*4y|L147T4{g~k zT_{w$kCc5Y@^>dBhC606eTEDU1r(+uwr#Es;)+S`(+fl!v$cKXy%iV8@2PmyiL*^ z`zGC4|CIhd7rpQ{=oBusjYPD)RtN42413fLb6iDiyrr$SBysx)DtPelciwhL-@RE? zx9eIw8zoslDSB(562HJ^{bH}t4M-oLlnp@+!Y~V>I>82c1oo^0o%tuQYu+;4qfX68 zVfKluM{S8F92x*hnIC~Kys}fI@6lMMgkj{S)V&FM(uLU*zF;4R)_#|8Z8b|fJM@g( zby*fYu{28$$ZIeSA%Yp+0xr$rbWUITq?;2bgP}5pPT(AoYmwI%bUwj~VB7KTUzcpo z$8Bd&GZq!m;cO+#rn)Gq?xb!>Eb_9vn9KL*&5+?Ydm-Ve^{@R*P=G13wa=0d-xf`( zc3V1n2P(6A8?w4z+CJ#)D1^g%$mz|~<;RHV#@a&rU#Gc;=q&mKAZB$yNdPqK+h2q2N?HD`pVJTpJkqetv>0}%WfyL z+@-5_Qb7=tJ(V`N-EckZASBItVE6Yc;z*r zc-Nx=n0gm}8{WsukNAIP{-EZaR{`#etIL4?1fku_aOKg+EYL%R?S;q8Ca4!_ zAS0#9+nRQ~fcS>bnD9-~N7hG4V{h_{w#96uXx>i>OyLVNe?I3=!BacbZr;(v44^62}35Q zq|zeWPeK^X!^Z<;k4re-FW?ObefFvPkFXEI==|p0-%V$Rx*ya74(g@Y&`0ab^F%X+ zb_c+q$f%JBoS`0%*h(;buLP6gk{W@;cth}D453>DLP~^&EQB8PhEpZWPP?IkE8wB? z(O2gukLsnM;+=p%ZdTjfYx$_6YSChYzf(~$bHD}b}D1appvC|MW{iD25V{E0^9 z9!7a=n<_sMJ&H2tQGc-E$YApxU3rX*hV>|C9cqaQ1Br$ z#J^M*g|;XvLv`n(+8V49lEdnR7`0|el^+th+Kqy~C~0npi{FHAAV2oYUC z&EcCnuR{-fL{05f#DaC|u?se;f_BPN{i>nUMfzWAKba-zs|vNEGL))>f3&H4+l;fT ziwP_j13t3zhY00P?2+nP5xGeF?qsrd>c90$l_A%bNdOz~@5NqoL?+Wo@DpyJP`q7FVR`+-&=+X-&n8vBc(1MaDBleq-zi`&cXx2yz}GN7wE;?AQzd_DHgdu z5RC_eA%FY_u}Bbi#-T^(KVKxwGF}C4hpua%rsn}xH;nf?Vr~KUdby6n5qLPajrj9J zr{JOJ9aL?+E;@JmY=|TJaKjWw$%}I<3mwPq!^Y#`$^z}+*K3Y3-U7I^k6}DpW$t@u ziPP)ieGl`&UoluX@dK32aX5Kx!~AMOl81(}Zv#`~82(^VCW^F^oVt3tV_~U8LpI_h z3*QI!4B=x)2kS9?=vu0DokrioK?|+LS|U~X?gKn}D+X2xs7;yIh6V5+!T5>x1silu zJJ#%q*D-KqVq-{}GNFk)YKP>Q>ac%{@8g)^L!nB&lQN>#rS!ksPRNq4(w?DzxaK6XO0^fYfSNmX zT=McW3|2%U7B4$vAEbuszabIC!%!{=A;=*$tmY`;j7C0ir8PGYQ-?sJ9yoHUP zKk4O%DEKGR^|TU!x*LG1-$@w#>fr!sKC<#8WNSZRP{f>UJ{pWkZgtHuIA{!|FblqZ zLl*>Sr+yXdbLf$B{WRgnp^5JBPr-ouh%O7qDgpdT5EK)pD=YOskiX&I;tOwT=CotK z%MN*Wf*kc*Ok5cLf|VebZ8C?4M&rb6V&}&Fb#F_14Wq(vg}Mydg$SA9JIdeSL-AJ zC4Pp?HM)gzYHrh5Np|}q#QMdtJy(*I-5~(I#0XGU&*xJIZD)NeU`}=AWRXKa2@T6g z{mTrUwF&t+BhlL2ILSPH2a@LKW(K$~`oj>lHM68x%lFCi5M$6a`qxC*$GFcZy$_6R z|6NtQW9FGJuUar%Bn9v03iPiVryZB$Vb1!uqM8KF4Tv?s=^3> zpdW|=GXlTp0$%^NOJ&DDSvy{5Knzc*XRHv(+j ziofVJALwC6vEo;Sgb{C%CJSK4rF*alIx4~3g+`e`B>_9HETXfbGu^MmBPh%ndQ~ls z&{o_@GP`clzXm>*sXise8`^8zG)4mgFk*tU7QddnAuVj`;at&KgAA# z*loX6@2lTz?+0zy9TMbp6{?k;w8=lJ1MK@?-7$M9`-XJbr>l2tB&bqRD&A|3Q?Ots zdny0yEOa{@&f7{H^6Q=Lb^wBQ-tX*^*-nSson129+BnPWD0VuX+1}Y+XSUOu<8_v} z3K`|z$suovW46~nE59J47%We%j!2 z75ZIHr^D;;yE6FB@H&u)o-FnkxSZ4EM$Q~>9^hCUR^akFM>?IkewWt|fP*d5>C_j_ z;cUOdo#Wyo*A2d{zmS9Fvf%Z&gA}&BX#IXlL&U$}$E!2uYtJJ-yKq>VwV&td) z|Cw$79se;~TY!_fo*-;+}uj1PrpSPEz5E`U(-sQw{-9N z^w+eWJ8a}Y;0sC~ahG`ITVJ)+%g}f0>~Zui^P|}PkSw@Zz=KI1e2F)gPq`I8>4&sF zOB~L@^ddXDH?qMwXq1ajHaOWUuWOJi+n4Xkapt&XPqurcGoNxp@&ibjvqX-a#_KEL zoycWMXdf&4TW;-92YBrXVHf&ZjF_wXQ(7TiYkQ9M(bfj?DKg+pW^JKsAH5>qm+gz@RdhHM*sE3W`*2nV_k;2QbPGLF?8?q}O>^fK z6cjkK3+Pua2z|mhPu~ruZ{-beGp5oYZowB<8|3BE+?q~fO}?GpLDoBRR?}My9vL)! z7Ff6G(|wznJ@+ZdFW|blUZ>YNiupr7rEio=8w;f!h0cNA!Lobgw1R?>u5-7e9WGiZ zZ7diCCIrn6U=}h@^7G%4Np{Mv!(o>YLNt3Jz&QkivK0|-F z?0$J@ffE1k`q)pixZeMF|LtY^ZBTC9qZr!OuY6b}1X}C8t(%6B6`1~ks5vH$^=8X< zSr(!N!QhQJ^Sig@Inx^*u7Lxc?tu>1Ag6r0N(M5{#@NquIy0TF0xI5Tu+2G17{$PWOaR9~AuF^% z+8~Xvn$g{mPc-H;jpEs?AK4*4J?jI#JzJo}*3YNcIkH6Bm7jl$S6_LXE8p#&3G12W z%rBXiUvf)4pZ8 zky28UpYL|&dmS@578pGF@IrMs%evg4ncnoq^d1M7>DguX>igGCvCl-QgE~wurUluK zS``jF8J&LBiY`4gmV_l8|9MHhPpIg;{Ar%;B~@S zTpMYUJ3|&Tc5RpA+>>cLyJ;d-4=v^-0~;Sxe_p^bc&I51oA`f@z5KDRdm*+N#m zTF>#)BXZedxvA6XcMo*?2Xjk$N$x5SVFs}%OvOnr+>S!;WS4(3&*6Bk`vTQ4@dBqzE(e+AZpTt-xWjd;^z`8LhuqhJ5G8GLxS+6J zw?k*m;nQs&pnth`v;9_-_3z=Gxc4Jf`;mMOw?0|eVEafNTU$AkWx4bxk7i?S&zZ7!>Un>1P5D26+f->( z`lR$`migoI>C3RdpPa>3xgcM|t!)Ktrm^kmhw0uswWpP=r~l4vNnfw~66t-?be7n| zJ^uOQdvveyB}ecz-1ZN+!$fwPA z_QDJKwAo~(*U0L?%8|<|7R*~V@}7#3ft3qJE?T*=Vr8KFEB~Gf4&&zn^~$wVtR=fm z>;7n$rAfj^d|YV`n^mvze17G%{pD4QR+e+GmGc%X8d+W;U+3Lor}Nw`uINw32p8OH zmy5T4or?!nmoFH(d|qYu1wZ>_=>w{(7S(?FArtK|4pg36m?FzoTnkU+Hp>zz zxAW9p1g7=%H0tVOGaXjF0m+2`=mP`TKiIlr7+|X<$@d=oXM@KPJQ?1Nt#21la3mK~ z%8vm4fXYrv;0)DND^aj8Ju zBumV)>N>aEAIhOf+~eLj@DW+LI6dpihLL8PR~306M%saKKl@0;MJU}38p19h#w+5-;6w>QV}xHIV#wcOF&VAqVXNkNQ^>)0z-4b8I|4UrmEid#&}F+ zC(K|9s!22hiQ5cAwNLyG1W*b9KeYjH8*~8LEtXp;0|_D^4z6TmiRHtvbr7Y#38UX? z94<%o+J(CBCQSNI0LdVhVd4$1q|Zr7X^ms#eU2CjcQjOinRgwwyIe!|JfKI|sQh{4 zXo`PDwjA3cc?5t1`1AG=3lRg>cFh$1P4SHpu;>iRaV|6BHQXilVVe(gu^jCC~i_e+lpCt~QQ0cGiAs#|} zKbU)tGKN)uEcO(pCtN+Dwx~`FUliC%sh``D`r`N1m^PsS|3W$+U%#*={DS0|vHmYI zsUny!0UMkNTrH{Dl&Zm`mlI2ws`11#S9Q*sP^1d*gc!ilLFnAWD{5BPCm{9%6U<=K z^rH#91iLc&___gB|6D<;dmHJaywPjh)wPoNz865;5h*(ZS^w+0PP^j!-cCmEl?UM* z2lc^c^zW%y+i>zEu}HsQuKAz(4UCZ<-Di>dU(l~#$wvaK18}TmBNvsg@>Q4nzl@%D zA6UC+!Ph%7P_^<)D0}*9-M9a`$N84|%d5Y-jonvGAMKmBeBq7Pf9*Sbtxj0;#dr8x z!?LH>=LMF3qLG0fR#m$4+$hcJk6$gI9)yqs^oP+I(`bhpnGj$QN;wm(p|0;V@94g# znbS~}tfT6hlyNng=i=DWr=_H{3TKAZU1!ORk)+S3h#&&FwTAHr_6 z#{CTA#LSH|4^~ad83{cdXHiTN@>s7g zpQy2$bH918MyY|UFOu8m$hvJDNg{TmKP2EcpBmfurXu?ws-d7c{4>FsxJXDsP*t%x z*lKp&LQ@&9=aG%~Ph~1F?^yS|p(=)=?t?YynO(7W8-kXr2KlkadUx3H9b_#Fp{=PzU zwyl1O#b}!?@&3C>-@Z7r#gm+jZ_Yu zy0njQfsZ~*v%c1Zs^}K8zi^s!z+(Th9!Bt4M$x_BS6LDG5+c}h)Rz(o4yv42b?=uG zNjV6{^c4h6j#^o<;LDyWP&KdWtFK}m^;NB|T3xv)@MZV14*1IFS_Frvu9`Q0*&?4b ze({%HQVu@p zFj%e--%haRJo7+MO>EmtR67Niim0>l@l9r>UXG{$Vm|S>VG{gt>|JE-{s4-Wb5thX z11#aCKYt1L`;TpB^(C8P!Fu4WfJlf*4aA#sB#1Rz{j063Vuj8=hzI^&{Q~mdC}e#e z?N=%lSMN!1-Vbbufrx!b=|I9j96-`P&hN|RLxv0$1n(x1%#YdH`UdvC(G z>uY_QhYhQ1lL(JIXNamlTqO?&rbGjnnp}c>T>@_*`G9MS8dOF{Ddm^4G$quKZonS~ zd=irO#0q#$Cf(qs5B-h+?*NK=P`zc7K!yt>{mp&`8p4V{OXsXNp^eIMmT)Ijnb>Y0 z+>4xwY~-zWm*QNa4- zMW~1<%0oR2K!kDxUu0+ki?`_UZi)kItW>K#U4gsmfzYO^-ro~u7O*<`U1 zVR5aL^feyR1z?-V_5=cvR_Oz>^>MfiQ71`?U!)YnoiVTU!mi5-8)bH zA2wb!biS&bId{7RjgCN}`1sF$snMD5bKVF_2orkG^CF!tBnDcJ%1gmM@(3+rPTu(b zGtbyB(RZAbQ-;(aXv{zP%dz=PFEeZiz?f;KO5zU1hGv-;4>KMCWaPCo z^?yH%281t_2IPO1C4H8Ch=pS0;)<2a=T&_n0kw_^EL*hbi|s1uLskdQr-)y0voE!Y zSl3xoQNGAuzPLwU`hx599OYZNXx_pvZ~M7J&toK?!=0tahyL_jkFU4&Z;b6Sjt|jB zh2)|x>oFbr7C_0zQTj5Z{sTegs4ZEjK4RfTSs<(;z3^{63xwv%7Dn$F0| zL%P(!E>+xxpf|W9B(t-L0pMr*9kwyi+X5m+$L2`#*-ZNPwY6;%cGxa zxIY9kID;@Ll&xjN4;e0@`MmfyBou*(Rc6FBWd%CwnRvX=QOAY zC0*IV%6BMnv7e*g>p8;;ty3pBx|6!*WP@q@oDkvGBD(#q=b$<5aEkQPRb)vd;dtx# zAxSi9tQAC&TjkJ6`nFm|Pw6RHB+!yX6#Dp9U!(7KFuX|+Jiq6z)<2^^a%ukf1d47- z{a(8NH$3Ri_3SL2caVVTkOWZ6UENuF!a@0LBO7}p999U-3@Gix-M`z;TPiJ^K7BfS zAD%v=>>oKukX_3Dkvys&6LTwH56{rQk#g3lP#g;MLaRR#Cwo7BKYXPb&Kt*&0kyrR zO@XgMy2jN>sY+G{AQWk_PEjmEsa8=)Oa*5&Qt+F_VTBRu6G?i*E}bNJr>=A2coOMOx z#fk^~)o?_GL@P3MQ`+8d*#P&XD z?>zXZw;gs>+QvknY6{PPSf%tvEJv-q_IOCiG0*J@es04>T#(A%C-nGdOExqe$mLS?l6wY>bukNJS zcM-Y%>tn?H>n$AJ#}g%V^{`5F5Q6UiK?@V-fl0Eu-~>i$;g}aOA4;vXCJzY>DLEcQ zdtM9`XD^u(rQ0>7OE2|2E{B1WHNX>Xlok<7Pezio2kaN4GnUJK09O^)qkv1NI~oAv zX(dc=(s(_{+Pi>?ot}N@KHlvddne`7Jh|sqy`4JGBO`Q#)v~fq!1?5o(-BmxOWxM0 zM{kmA-_ohClSVwGn4T2k%~t=U66y8Hoq8PWAnFx&BHt~mbOWTow%wsum)c(Y& z=0_ULlhO2OV%;>5xgqSQ3l*^0Pr4N_X3MbhCDE_$#Io~*suwfUAZ6Fe)%{XTg_vahT4+Tn(q6N$MMB z2CgOb^jKFW?U5eJw@D+`xdh5bnoY z7-hx^=tQoXLGbxF!M3T?BWn`!QnK_!)D|qYZbH;ar`7!>Hv0)6$$3#NOG0t)?j!zq z>M#ZAPmw6sN&2~JkVMTZJE)Y913?n4=FXy1OHgM-QiHTPvi*_;)X&pQ!cv16DosPA zS^OF1KKU+@lJz3T-=3w%dZt4_%dCIK^^ECdGb|C21JHkdLsDjaKxCg#3nd|UF(}I0 zh7pBFRAGTxm@UGjRFDjY$YQvSU{j!^x~XpwprIr(_FW)}e63_Hj2Ppa-gJnx(Xd8 z;2^I6WZNB(=6Ok-U8AM~5sB4BfYqdac?k())E@#%jq**cW?Jh5zx0`gH@i?th-eM? zn^K!nNPK&7?c9Gj2uceT1(|q7`UcXXbN zl*dF;3{zlUZ?lQE(n9eoLo&VdRV$S?YZ&6P8^g(iHvQXG#4T@ZG}C@N6tmn`>|L2j zJ>Y%eEYmn4=!=t>KN(YUW4r}q?!A()?nQz&C5`boc|gDXxn6sc*kW>ZB5{A9iNWH> zp8=%18n=Zx=HwB8$No4B-EQZ~zQPjA)pPCvhaL?li2&w59zy+Cv_X0lPH6S^P==KE68$j&? z=q{P5P+fvdO6jG8kPKPJhavL_C6tL!rxuDuaBP>-;M(;%)Z`&(ui72<&taK3=|NXIoLF8 zb0mw-G?^`^XE9APsntX%0oAD0t7oxDjxRDqZkZDTI4L+ z@rbP*U8KE5{4d`YqZ&RIA*eIqqM|yJtqLR#EdoZd>X;fXtX2ObT$R_-*=qr_3*aD` zWF-4r4j1x`Ng=^`+|@<} zY3$P>oru%&F)#*4wNaQ{lECgvFd!IN=x!u|_dmeH>ij%)(nii)_aS#%2c|zckdvs1 zI2YqFuuTIojr`WeTR`*yXM$^YiuknB`s0%7ZtMC!>-tbUM1p%4PS6X+=w(HuPrpB% zG2~|;SwDKd3@M6ILx?k_?a%Ur|0hqVexbU|GZ?Go308}&n>vUuNz`b}0A*$~cHs8$ ztd5R=>J1~}HD0z}9E?~}VXfv=q;-h{QH8Y+R3ePT$E2%69&)H<@PQ=&a};6JhVP>F ze-F%Y8AN3Xt$=?cW72Y(<(bG60;7?Wdcv$xHw*jFNR&@G)W6$Ok4|Pg2mI08$5g@^ z>_3Jw>(lMxjsas#J|DKr=je_Yz(b$@yDO#c;)-Hz*VJh z%UA=Ylo>W=2L{!s?KG7`#sJv|pKP+5VmXYm*7d7nPecfMQwvl`@Q4wviuzRF|&IcI&D(e*Qj zf3^DAZ_iDZCxtG)vcIoRz`lEL5^oy`^I=EKx69ukd3Za8Ur3SXMNcj)m}YE3u4 z17mZ&ldH>jO4=OAetIDCfFss`I~*Ffa2n`f7m9@7^oRYR;pwA|T=G<-;Jt@py99pJ zERXy^FU#_Siv&o31?jVFiooN4;w-bQsp*udi|sRuB=ZwwyIMJg69OE36%GwE>vv*6 zla&7uRBK4$qz3pdW;V)Zp|x&k?e&t#{poE8SNj1GBS!scSl!IFuJ5O`P*$ zRn$2lkI+uK1Ir?Mk;NH{9Y#YJ@+sFOe>3??_h$?+lEk@vr_b9*w1N12V*!60ycf`L z<+5;b!O~eX1-b0;hNpZc=MOP+B_ki$=x34R9=K2Cch!-v^@h}He~yY@ktQ+UiF8OZ zjD5~un8Y=&n49#9x8+eOVwi7KgP55IiH(RzMj}!nWD@B&M|7Nq ztVsr7Io0d**Lix3aQBX!@2~n|$Afi5b$MXk;vSFC*XkDQv_-X*KK!;{XezS~ zTD+{HV&zvKW?jp2PoL}D`?Ah;?xp>y&KdkAC<_hbTlLArC_7bBIOk(Tm~YqMP2X;} z1CrMphux*JzUC<=TkiwRd_ph!P@%*fa5@9dX>K2NzUfu3eEM7kbA5b%@|V@_zj=g& z5>-l+XUU)d!RzjURqjDPx35^>|I*>K-KY9_Ed!Lima$*wwLrEPR4iOHvbt(y!<*IW}y~WnwcmqDE!x6s~6ajE0GJx^U$7W+jJQy^`jXPlSgXGwfR1BDfle~99 z;y@kcI~REQ{~IT#eIpzEbUZ+;fqJHBPAVr-uWALKbHnEAnBe9PvFmd$f_xB%9ik23 zP+3A2p#S~!IagWd+$-lSDgT^vG7l^X210>IpoxD$Q$lEuOzZlrhpsbC^+O<5C#NHyqWle7-^zup3rGOd=`X!N(|H#t@4i6hLCS?J zD2|jgO@YTSF7hsyaZ&blgcIUK7F3k48ac13V*VG-s`Cz7{B;iUf8{|77cKBFpZ6uf zhuzIuR9nSt{t~->Ptz7xRm}U!yZXP*K`W{&s=kCU$%N^38P+#+o`Llb;j2lFc*`kwP^NfIj{7rXF0s|{B6OC@YukKxF?Ya-Z z!QO?0;txDvN&*iM#kCVIoJkF9Owl_5EfYANSQRQ0`O2kr7ywj8Qsn}3BtaGD)6~v{ITau#(tA}9_`faCS0(w4hSj+J~|3s zsSHu`Fv?EIB^VJuDUp~(RVH?Ph{RS<;Q8{r>_;g0M^=2?WbC6BD0PgVz;9cJsuNK| z&zg*3dCGzNcYlDJpQO56oP=z-c>gn^#t`rJD0P~}*4`Kc0bEb0nNgbdp{?Lcp!<;t z*6_847N0`3nVMK?Tr~I%tHIDjm|(<5qkGzn-OtfHG`AU%&4kfxVpT&(U6sZT=x*!{ zU(#3ux7;5)<)ct&*5@_%Q2|U}4IDK#f!B#yJFmH7ORS}(YfHt{k6hzlijdk!!Z}q= zDLLJ$#wNhFe7b=eWdz~n`4Q6QiZPfPHI3kLxOtHKM95t?#v_$6z1F&u^#LABM8b;I zx8o?3$;nZJAdZt6F35gZv2#)$)BNUmc~B}v1c0IduVhiFeitYT%3@U+Z}`>iyWfi9 zdaO77D$V6M5f~_TPa~B)F&k5ma9R0XV7!dBYPsTx>p3edXXT{o6O{r{(0$HG!Ke8~ z9;K2w=d$i|=7@&rX3)3oQ%>*wDC#+ugP_>&;LYD(2U;oc zi8|Sin}fKgCwsn{)iVkR3+qe5qQD#OKYgFQ}RP6Zh?97Oyi&a}73p;zBeMj;WxE2JFHveN0ev@B!OnO>XJnD$n+ z4p)X7yVx}ZrTIm-pPIr4Ct#g7ls$tR+tv|(#MJG_kQc(G7O1N-Kg${tkrhi+X7*7Y z(~ZGfKoWZAnYH}lJ(ubqfFxesK_7%%+Zk_B4Bk<-UbTzct}e`yn2QH!0t@PPQG2>N z3wO$89TLrXa|#V3=j@NgLBGl}S|b*{QoorYgmukn9^n5$%qB}%JC5Dj9XEAx$?;a3 zwCD{3u)7j2{a7{ebMGhsl$vo)%V@jA^D`U<|E5wvF##VK z-nTlaq%F7t#9xm*dk8`mZFwy* z5Fo_l{W?aTaZJPMLJ$ZU{-I64%A0r56F@OFbheLuv`jG3TN!gt*ivmRW+b8yFVHxf z0HJz~C|na{)3&Dc$7Q@ev*g2#;YjNn zxz?=BvlcOm4_~3b(Gd&(Sl}@eP21Hd+JeRinR%P9>2iS!G3bU@0NfHmvk*$(mn?hP z(5P?U&W5wXa@86Llcji#`tU42mOWsp&H1gCnYLx1vfYQj>vFbExr&jHSswD zsOAWkZI}UiO+?|6vgMtE)-ahz!lKFnR1h2erRxFiXu$;09DDQpzj*W%10e|fRaOx{ za`_=Ol)Xa7Z3_$5)rMzLl|s4?u|#eDXWN?NZ<%OwvKk;Bv0+|~Sa(7K%TK+`Wwc)2 z>SrXMV4|Fk#gaK-u)yCc0J<&8i!lT-mJzECOb=ZPd%#S5q}O^(%-B;g68o}=H4i*L zY;53Ao+FRgQgc>>TeMe#*y9q90#(fib$}?#-kA`uChXKs)9*ygY;S=n4@@l$1^p)8 z7|r!jrv+|ERAr05skylsGm97&#K1i!2@{~Wd5I~GnB|OHq~q<>X$Wu&rhrqg{(#^W zeVKrO%toQ4KpogWoq!VF)oGPaliDp{1O2+!R)OZKg&D7jpIG_{0` zr89cqnNXZ2hg-ztIE+c+Sy2;e=GxI_gyq2H(d4Vz>GpLx5Mdpr~@9aF;hJ zpb1(n7O~$JuOfO77mQv}R2?<0KVc{X!4jx7n`bP1Nk0j1e65#fx@Bz;Cp=GX1@}#t zxnHJdl$xm$zPP_(fXwb6hzJ>@N0Ll`Or~(80I)n!I4!b9CsNK5E3s`&7Va-x8bx@M zlC&w0{;lsEk8Oj2ZvF^)vEwc~8h^#OTZr}4eRgzjDCypCiGavOpEdoCn_b$VZ3qqxVX4lfnrT%OB?Ly* zs3+OVpl9n%%=><=623D{q%u8D=&h961CsZ;9cFuImtWe2iK~;y*N(OK+ciNhjFjPR z_6wM;gotY0_uwzsxpv!1E74bCH}O-Q`@?%B5uQChtDbKO^OUGh`F=;1tvJu--eM~H z@qfjHc+bjtmG}C;I4-`&)VOR>6=|~FMwzc=;_W%pieK+BAX^s7n{VzhdS8oOHUyy> ziF79!88;z8J%2)UtLqQaeRa{t?iu^&pP5mxk`FMe%-`GCDq@yDzN?XxZ*iHQPKQ<1 z1%&Ny%(kw{wPQKn%2u5;=x!icQXUr3+HP*B5?IpI&9 zdFb4kC&U#m4&CdLImkj(98c!_HES{wVBZ~*78DeJa$Wa6nqY=xO(raaqG~OnBYql3 z=mO626hV#@U#&i=FU||=iu?8Db9CiSfubzuUX&6Fse&Z2-gV(;f5k)B9b>MazxIM8 zu7r+6%q#w2Bb1|g^|)D>Yp@-OWaxwCt|@njIn(0&r3|m9>W*RBE|Z+nR#lz)^xw+Q zZ29al|1(<%Yc~4Tfy+PlrvEb+{O@ZgkNhQn|9ji^zwPe2O+bSmqwM?VjgtRInx*G) z$4&i~$38tKB^Ji$e^BT4tmqP-%uADsus%Fh)bU8!3jG&>(LT6Be)NX~G-aiitqouF zp-)$q>kZv!ebGVxi?{2@qH^+~zs6me%hl!h1Kzn(i2OTWhsdv7w0PN~1yukLEM2s? zy8Mf&e9wV_nt7F#=g9nLy_r47N&3i(~YhD7l>JRMT_5ynlb= z9M5P1a3DW*x>1aeMDtGWh=B5H<=@|!RNv_m8ERodf&Si^qQhVuwFq^JF!geTK@qGF zA1d15%czL~gULP*aDvpU-YBMvH3^hezK|hfgaN){6Kks4F$+SAOxMtv{+7h?ao7Vs zv9(LzQ$)lQWERW=-qm(}z~4q->d?_Bw9e$8#IGM$Ymg}82n@#&jT*#M(-%E^W0fD( zBvJ{Bsubf)Eag2+0?W8mv{Ia7eGiB*`fadVF<6C6{yY^$qQaCg>MZ(E(L~+vpy)m$ z&3xk%~Rm{^or6oLQ9c{8#46I=Ew!u<%u?{!(0@#PyHaeiSTEJFrU)m^Ik zEI{nsU*#nz|1d)jmB=+}xKW4(H0n*<{rkG#93JQzia>?UhxesF;<4kP;ihVgRADeu zdi#znmImapMbFC8gBP$iPd4G#UtG%fiY#Iqa`ON>*Ef);ig}&9*Myl0Msp1nx#GK1 z8_fiwbUF!ry)ea=^zVGpN^4VLiGnSumZ-I-dA!(JbHts~8b)91+aaKmYN_Zqq|jP> z35vVD%e*Ce6ry7l0uFq3MNjmHtC5##!t7QHu%TJ)hCW ztDud87%?#)#&9b$_aSamn@mZ#Kh#n35W3e^Bf}7$7ggm6(1MSlssNzemtnD@7NZYg z8N*VjN>~S?D7n~pc#I%o%Z*}iaY{9yg-8#)cQZCid^T5MKUIznu_6I+Ku}3)mMY}M zAEIuE6=M|0B=rUEiAW?t|Q^d2tD0E^V3n95)i!L>0EqQB)sBp@}1 zrO~o#eT+8wJQBGTA43hDxQ$wOdCK2rV$}gNJz!=@91V4Z^3>g8j0b|dFco_Ooc>nP8k;c+EWp+S zeCl3CdjyXLHpDE7eVvvRh$)mg5GfwX46p)XW`~+-xmXIGluP~(Z1)VaY(a+>9SuRS z1cUgX6e&>l1S#v(_EG_eyB*RwR;7x zn2g2_WXfCUJINUjGEiI${HSF9kX;g`xFKd01N@&T7>u1$~_(j$=mTY57{G8+92DPK(QuCRT05!sZUS{h3!MT zEG(4^=TL5Q3|1=s>&8$at%WqyKkml79o|hg+HGgW7zq=QRks?Sr0kpwd=uWt>cI3W zb+Z)rWiEtGzj(90lt~e#atc&_xD1Gh`YzhBCiEAde8lh)$Ixk9`-4VL$uNg(tb?;rlh&8Ii%WciH9U^G?_=S zb2*(9k!aMolhg=+s_nt5F%8c7@uH-W9P2fXM1@X0cs<9mFNqQ&aSY@xomAw_=&R?% zu-heMFOS;~XqO~*M&UnA5+gAuy}GoG!A*E_8-syFO8aRH5E!IkrneMnm#2(M(53pX zy##wXpX{vDLe`JjC`9-iefA3KJn>`=Z8+X$OCVq)#uH|jUd{l%4Ah)9Vou5b$phkf z0+FZ8FQz^8(Nez2g*q*Rd2I(g)j=vmFcBxTFM0?QD#5-!9A;uKyCns8?=q)|rH+xx ziU{!89i}h}7Ktf|QEu|P%zfc3O!Azw%)4=zt3JI^jYFyI-9dY~haBnVNH$J@xlQ8z zP6V(SETh%MOtVq~{iqWB`qp zIZ(?#*}o#@pt~CFo8o1_zl(vAPAE>P%}U-iWNoX$mX}hGu->LD3EP%8abhkrzH`Dv zOwno4sApbc%H;Sr6{Y_sO!P2uu?Ci$#fd|70`dzRxZZ?p_!Oo}%uSbjNP(cd7RhQe zbiD7umW}3naM?VOJdQv|nHQ4&CnWUdHk_FL7a#}y7(l{D(deVp`RgAI0!}UdJy3`>`qF1faO`6?wejd&KF7%%}5q!-A=o?D1w>2^&~y zrMM!lnW2J1@j#>+2Rg5W7s(4?#sWCr+AJs|zd%%@t`X6yND3Kek#I9N@sL4mNN4po z))O5{!rXgs#M^0##LjoApIR2^E2ve`dR9p8K)A-W?dA`VQd;|o)eAD|wVi4-gE~Z2 zPo&OPHMuRmyOHT|E%E*^JfRG4$&@AKa6&3--kCwN{n;7ZamsX-I=k0o&=QyHC-N+S zG-O^iZy2hS0P;~IMzF*$bNB_g=3C1AFR(6?ITx=XF#1*bbii57;{dIr2#Smp4pA$Kx@>>Z(KfI|vySJ`eF_n$2Sp;7I9xMx2^eucHar zI;=;wvPk067_^PypK44Uw(CLQ^1m;S^z9VE2M@9#8Wv;H7+ID5a?h|@pzrR~xD;uL zXveGRy%qce)PFS|+Ekz!oR@&XxjT+$sq3VyptXTwESQ3+{37`rlu8Ivm5BOQ5T?Gw zL6d~@1E_q2np5=1!A3Qs&HO|pih=EwSSktqpC&nq^+gkyDsVG~pOf63R5Tpg6$ouG zLvooVjMxk1rWl0n2lX1N*vS1j?OQ3gLHi-$$94j)>Wl_Ls5M9f#59dV#97yXwX_;m0P5TOoD-ePOlfn#vw^@kw=mLQl1z5+anF*iU`{qHRmWhA~agf;`B z3ia;>63tFL-w>*9>@gH3jRv57Axd=x@kq9LH~dvx!2`xI4N1}LcMv(Ve9RUu#`D9@ z|Fak6HKI|h+BcYa@T``gU4gqM$K>1k1 zSgGQ_mj4fcqiZ$9NJaxj$eH@3)g75f844jUV_~jA{oNjtXe3q8##U=Ok0Fqn9H*2u ztoaB`(Sy$$dW=3w+UpCt#~;D#vZa1_x{0?<#p-tRj5mkN$R9B~pfPDZ$MLFK z+^$G?WzLhE6qWSPdpfJ26l*#e!fOa#ALNk?Rt_QAM}qDMi1iT%!k?uj4AnPESd*D8 z;WtyrYDZcq%cZ-hL`XRR`fr84t^AYcNKCvi=0xv9-F)WpIg#8?@p(|4;pG{jcSI*#EED{L zO{hR`Bh?Wq_rY{>BjlKHVEVYmfR})pFy6qa7EteKJZCfa)<1A#g;rE z-zU4*t#5qe_bE=qtM-^m?h#-4C)SArhDq*&IR+AtzPe6|v|502&?Yzw+E!LOzF=N0 zVN_UAtSdo7g><1RB*?s%?K0$?;(Hv11t__+R1ms|v0_|ZxQAS@b`?`Xi|IYMlXGAbti(s>wONgmSL^+p;(KV3$85*j{ukyLEb-3PMZ4h zV$Wvr{~Qya4`@$m*O0mUGru1JvHI~w!CU38K>j}7`V~w6?U(rc`=$L{pR@13dVlGR zcH8GY-~WH5^Sk%|js0|f`#!n+zhvlIQx5;>CvR=7{a44%f5Rm#PcI{x7lp0=&zFd; zf8*D&^)Fst^;NJra!BP$GMfG?Zd^GDgvs*mbHsuB?T1Tx9Jr(Vw=RLA0xwXeZF8W# z!1WEJs4v#4IVl1m+uW_Q#M+65_x;_1dR026XcFo!j(;jle^ZM7O8vnqB!1*imSFq$ zb2#$)MB-#+w~17Vv;aC_#M&vWo_!Q_tplrLGsXoW`GYzLHaresfx59{H})3-QIT8J z-7vJeX7gZkv&HI2sqlz_=FKqse+xt8ivXT@m^A;mUb$DV9D_fAps1T9_~@GD2!$9b zdj2F_fdZB{ZsLX};(bd3(*~kTP-+>TQP+ChWKbR|7?%jni}QBt3l7pJp*S%qDI&#z zr1@4}vm`-h(x@L1=hRBJK4s>l>|->JrnR-Z9jo>>v8uPCyt2So7WVSVAY%c2isX-L z8xto$jP=<5tIE4?aFc|Jf765QC?v6hJnl|0j<6y;?51LZIc4|EA~9w~8xdlUikAG2 zgnQMZH`PsIFtUn4CC7`Q91rf(Xv+3`{$?PL8@J$fhuIo zwI7hn4VW6Q27%X5u8o)08aj6oAqJ>&H ziA90ydovZW?V7zCJ`GouB6#xc3$WWyI-q7?LkyZ?L%vJ%JZAAw__l zm6+7GXLGAsksvX-jzcdzO6VEZHZfYF+kaam2zQ5x$G3~Ucqjv@VJ}9>B%NxG0$oxU ze4q|tmVp&A$BVJhMSK&6nZb1`tYx?_ocvgW14IW*FjRiNI`ucN%4<{q0?OVX8X_SF zw0KR5^2;~_h-TW0Qf?05bkKpl3;|Vz?lGRYteS=8U;?}HfTBk0WzOIYQvJMC za?4Bkqx?EiyGn1a+evr)Sf!qL>7kDo{K(c@h@kj^X>CF^dr93$U_A*WDktg1_9K4t zsi~LBTvkYVF3(Kc!eJ9XX>P#aEg;tp6ul;ss(k5tlt@F|;;(UQLBM=dr0ciQPRSJx znEzl+W%7Y`dNCjf{4VBrzJe^E_zlI!!;>#dS&`ht)*^~Mk6>wzMYuiZ?1a6{)D2O0n`RBRD;i2x;tO)M7u0Lw!en-E;$czc)zSCqVE#z92FeKC0W z^~LKY7XHG~y7wI7ftQdmy~$4;A>mn93~>{2OEP*p z@r5{~`7Ugk3$9=CIQEwBv5C+|f)lgQLd?>-mPj+2Y@KN3AzpV7^bfGJ-{y(!qruwM zml42&8a~61UPhbDCK|npT&6rq{G!n0u}YB^f6V;KV_uMhc(MxtrYRDS)WDP;w1ZQ} zVXlq`3AcYih6w<%raX?Zx;hK$2CznYW46lgl&zZQpDjvXrpLuNBJu!Q=vQ}8R?w}l z;L*kjtWu^&&nwanfV%z6w3x~~i*l^{?Z8?@9(un`hh0W58-T7|u$U%dm6foaF~O#0 zsn`mV?c)yf!RiNoLd$D4ap581cA-ikoA2R4qWO=L*w9?4E(_fbiukH^d%Z38$=*C&@b^% zIjCDbyTif2<>g=F5mfcEV*s{+SFB4r(SWkp`E8|6mh4)545R*~vQ+st{tbsr9ywqk zL>kka17{`StK&)R=8%=gtbIGNy9|`|g~y0UH?p+qBw4&y$kM%stYd1ev>n#Vk702d z%!RTDm+U?rHBb(%I!UJukfkNZxJ4gXs!iVlgL=j0I=}gyJWj*jB@Q!Y$e&jLn=^rUL?89Vf^`Hjj@O)c-+$Uf>CXx`9RRB5MIt z89Q|g!OoYg4}gn;P3jvSS)eei>{;IcVqKNw$@IsJP=y`l+zJ2I>-`W2C-Ai!U}$9D z1Jl+4<|l49u_+Q;+?%k=B1z`qrT+va{c@sX_2uJQE$_u5h9bO>SBb=Q81N8`n3&4H zNAo{`e;ALLCF4Zf_w1Qn^nGs<8%dZK@-B}e&3m?CVMvDPAjlQ>7a%;Ck97kB&fp@NCkcNZ)p-L!%2o}_5Cn$+C;{Q> zS!6~>gYFm*PcdN6QXhhb<4+{?-y=3KG!V1f1##i<5eN#b3A3Ad-yEW^p_xTg6E1qI z6*6TaCE7lRwr?A12KgR_78N49Z#Pc<_DL9ovsp}6!#ooq*W>VW5l=$u+d45HOMGK^ zAE(2to5RO}@57~R?M%h@nL%)`7n)3T63jFz7Q39#S^f8nv$G72YzU(m4;8f#REj%dOd|-TNGV3<$Ewy_I_MOdO#N-14 z>`P1yU|oeC9su3Ou75+-0SV7sx9y)r14Y^MStvA5e;s8nP_YuNY|*@x3-0~GFs$@3 z%luzfH^#aQA76fvH4;0vYSGGozoJJC{G50$(F{MBIju)CyrI82(%5*|Ou~yhmb42< zGh*lM+Zgi07`zPs?;uo_i%Ct3;8-X510R|zO~&^$aO(bmqb0b3ZKu-abJ8c_;^jHx zMH>YuQ10V7$xygDp37R|15{cD#RXz@LBb6=Q3(002L)uV2XFzq3C2vmP zO~T_N{+~ICfJ1VN?3@_1d;W&6OQ7Hi&_FAc8;hpjof^pm>5@aCwosiYgYP zI|!b~TzCyr5h2PtWbFqiY4urXK-RZ_1NNL*1nHfiPvYA58#j$l!9Z>XT#xB0HA~DK z7Is^4Vi5AIvt~3dHE6<0BB%~<3X%F06;NAS{Vl&gB9%WjA@@p*`bIpue&{|U?Oy#m zP_?8xNj+TgXX#Bl=caqpMQ;uvET*S3t-Nmnujxz)dKgM(pA%tUI+Hl13-E$s_@0S8 z@8=t2UM|1Z+c1)!_){{Qei zsHeMz)x05ufL%<}j7ktt>4qgrc+2{Ni260pXZ$O1vHym==XNcbDr~@=kj?zm-pxMAz24}1d=#1 z;-rZabwM0s!$d&uj5OF`&Xn2xW)Y!Jc278Cr?@QqiF!iMP6LQ!I*qtPeeF(@HlbvA z)bttqQc)QC*RHbNqG1%}DT-5;*xP1_tvIbER)nTxQQ>G}63FMNS4i)2y>eunVwF^v z2Uf}1f@QR7n~DT7Qpxa3s4k=%+jVCcVqI`agTl}o)k!KG_Cjlzh;XYm63@m;@i0I~ zKCJaue)4e>Oyf#2YtNCRo)eB@IQygtt+-F}6M5PijK?a3HI>{fjQ2PkPu}G-#wg-{ zDs~}a-qFM3@jZ^R#t|)m)isvL8iMmk^01c->-vy*tyV&m1^9>k306a-;e;@2%zRT; zl>jOfhkvLjI)TXT)+Z93wplImd%sbU9W8LhD*kw+F8y4;8MLOATU5NYf49hfSFlZO zu2EUE6XVmhehhho=#xU`6}?12y;Lz(#32g5Cr+4R;y*i?FdjO8qrPs91hqd*G|4Ex zb?ppdi(;{Z;hMm%!wiY-vZ57+A3Ssc-(mb#esBz()&~ibF$KZ^ERTCwR@B4lN#jIw zelpm|RQ}{Xu-jFVu0tQhXjjiudv@X4!$owS35W+vjZ@Q5s6pAWXq=A3(*+j*Htc$F zSUGn)0%|7B_(omUVT9rRVig7AgZ=L?E?OL$L7H^{|KbXB(H7}dw!yAr5YiQ=HO^ufx<4{|y4B=+Qa z5^MFxZU&6j3jrdUgP?U%*cz{~28-hsGp6Ycx?>)Pu=*~;?|P0z4;u&@l0H^n9)o!p zh#ng*aWz4bU|VMPj9Y`&q>xUDQlpPY&S2a8P5I|6=hY#i9!)--<-Bld=AzN^<}Bn> zEX~8e3oOmOL7{k<523M>VDGZE-o%E0wIAtu0OLK6S^7hibSBnPuiqX2z65HHs<+eD z0yeICISs^xrO&VdboEl_yUOdiLGCiaiCXA|fkQX)=ayM0s}lXBLGUxXEZCXUoqMSW`4pccwq`ZpZ@CyBa4_Q`t!P z8w5O9*^z35$ZcpjMj~gc4%r8Xw?|qd5zuT2OG@A zA*D)IEj$wZ>gJ>M&$7)CXIL~Zvr*^uJILaRWM%xzFc5&^jB>||-kFoKvLe~6{-p)_ zrzUqHPIKXRom(i^b(rfW+1E9zv@VTN)xM5MZ*HCV7MnWzY!NB&0an^rf0a*`;!&sv zM#;6hqaA0a=Vk!k6@E43T>+dWh_(Z<{$dkY8z4~8JnM{LQ{iwbf6YiR1d81m;LdooWCYXI=60XR7Bv6HM zEqrW-ib9%1zEWRnWM>J|(l6!(iJ3|<0CsWeLxTmIlmt`|fMuKv6O4O3><8h~xFasY z?-4pq;wi8`Q5kJSfk=b~p>3(tMYH%PBB3|=&{zr6?hB881}J4k;e*1c^@|a;-{Ejk zsuz4Luw>&qZ*UTGm+%`Plao%i_&d zr7(J{l|SjNmvw#lR?!1~$=v^8FoN&OqXNN!?7f3ZccjRn@1oiiHb#)JpD_K(=N73W zJPopNmdtS9+ASvIDV6RmiOC^fj2mQ#v zK*?uHkBJ=?8uW*=g$8|jG7b6`3UjhUg=ATPy%;?|J11-Lvdn_JPRVDuC9`tj#Q$xn z?9$9-dEb$w$E}SJYu_P4xTTy==dQx&=MB8YVf0O68L_z@Rf zZfros99z}MZZ*gcW|`14#bMh-Z|XQAuWrq#u_||>Hyi~AJCwbme^m{E6&f2Fg}<;g z2~SWuWU#zYui_7gzS#1$s%GU!d`PiIwOW&4*Xz~abXz9~y)j|K0gC|B)7fD_BS)aN z!+5VcKCwPUt&*nCE|Kc+n^z+Ui|n`7uCw|tZ()%Yi8ziaN=<9y8B5}(pje0zCj^NX zT$VEx?nd|_z-|o{d&o5=VImuv}q zmk1lb4PpKksTXA7F8WD+yzak7#2DEFxK|fD(>D^SSR!rzRJU2^l1ey_Z~OosF>otp zw2DFiZ~o5Tv26W9|_dd!C~WvztbZd0uU-}Th1PGZtj!QKeyEh6j4#5UF|I`-lPx%tqLmG^w{ zp7ol(0$fRZL#_1{+tVm?5pA2$!mlCTFhUdiRV?~b5Jq>lp|1&-mY-4b#4A=(9ZCu7 zZNStMbKlPscN3a#T@RG|a~-|O^8T|DGv({`uP`XUf3ElALF8lbMED_#3udjBhFbMM z%KzF{Yg9z^bYY6L-o_2dq3|XQPFg|)mC_j%%&=`7Na30MR(q2NWt@Q&0dB0tPEzl+ zu7Txh`>RpJ9pqf2?o>;$qm|6%Ig(X$mvt|r%dO8Rw5IX%zvy33aOVLUPIQ>3!BccRuH;s7iKrI)tgAJWlspsPh@cC~OXiIYv(zTOf#+Y_$NvkfPKAlPx_TYJZ6*?6(Xq)w34D((TI*}s zx607i2#-JMn3{i#RCTf!p!Jbtv1c6;&hE4~Fl!)~H@nj;Lc|kY`XYv};F!wI6^&1? zoDlaVF-CsL5Fu|ZXOgSA{^#MoNSKN!L<-G9W(h`SB5>iM!RQFBTKOPc8!BujPe>N< zyp`AMVSD9BLZKDM7D}=zU{+(; zQDF$+^x!YV=4a9&g^{DQ)<;CADTFp@VSBbBpQSa=V#$L`As7P_%O^16Unz2ba4!=r zrf=4B5_R(4+Qytmkp*{;HPD3){A$%!Twf^J~|uqs#;P{#T?U7Yc1^02rPe9e(R!pTO_Cgyknxj3ac~fBO-F~4Y{e1TCKEgdH^zu zgl)*ACo%VB`z?VaW6ORmNI1OF`ic%cD@R`r79p8HB75q0`LM;LlcQvsp8jAy#Y&(j zxI3|!eb)IbVGViPhr%361DOdIxux$0aPn=9R$Z-PL1SWv4p3SwePuZl^Kyy}A&S>m zA0(MW88r3SM>3F%7%DJPv!pZ?@v@R|<2DFgF8~1Nn1^FJVY_uuRj6Y};hjbD+^%9e ztoy&F&yfu)2x=88f}fRNJdkvND-YAl=1gc?t6H&w{(=tF=Ztd?drvF9`*LZ#ZI+f=m{9ojb8_SiRA+%1YMTeVa?ne;FudHyOb+H54w z&prHny!-5)G`)O>bx{I7B`U`6VS6GlRwE9Z`z0b<6?kI+`LfB3ktag_FGg+XJb@n8;?&wufXKGsXZ5zi;Ss> z`9Mc+My((n{tTM)nXNOso3uH}G>H;*2>ot*Hlvl6-s?U{+sr{Cp>3|KI>P;}@xv3y zXnVaiK3+Z-SmWaoCdS7@_2cQp1bxlBteX~)^5?!QHy_ld-q*(sb%g0^VS099L*8U1 zHClH>$P8symTjpveYmv=y-Ab0SDL*c_&COmLrjorF@%suuMyrYO8*UMCCT#T{oP8= zv-(eMWpYjx-apKk$)93WdHL1x;I84=?MzYqK(NV-xq`-YSzYXgulPPk6M3VUb6U~g zu;l7%a6RR=SNOLk47VZ#%SpSE+UsTIwfPWaMac>Wwh|HrR8;#PVO?yr?)|V?y`|db z?Zdi*Ga124j#`hzt0&1xWK75BZ^zI#7g~7?DT+{T;)!<~URu^@Ap)5w5pKTu50HDpYfu{5-E(+ zf&YP@_LrXT>4baH%^@8jk_PS}P@{eIp}yPolPh0*d0+3p_wg!uAMvE7?7e;aXiDLG z|IfNp()#_eUlvz?Kzd_D72s{I7F0b7TLr7`vh0X3yNp)!)ssb-ljW89E06_7&tJZ9 z;dcljP9Z6JNQ{7T>ss(QOEuxjNK3wDqCHl%xOw^di>VJjSsna5hWhzcF z>e{39>%b25XFl?IGvg5H`vdPsK4+)B!YX`6)aH|k_GBfqH(@(%V6{Pe;*dimj|OBy z#Qo^@c%uD=HHy#cc+|0R`UlInMXEJVR9Eb7BiJMFM?^@^pAOi#uP`oEC@-I<*E>S`#&W}NQL!Az4dh?Ce)EpI95x9SgYO#L-4Rw_y?;@nsA1!YBp?Zi5!0( z002AcU$3&BNQ1Q!0wWcc)i+t21<8Z`&}t%`{3YQgnBmxA%xNnF^;jF!8ue|J;I3if z>d9;@wHtNe7#tRveqH(($mFOU$GK|9cF`ZBU2b;Nj?D&YZ`6*r+Gv(XCS@tO*m20BEo8wC)HKO$!720!w#GFcuyWr0ek`gx&sSnWTSDA%_mz zC;;*pJxk)+k394;Cud9gcZnUYf+9~|$ER8^DeihXm#Zty)zePC`s5|)q@@^LRFIjK zZNEDU{SAPTyJh_Ur38~!$ZJ^K9yEF&YWJCFm6 zj~^*!u5FjiMdaganV}EMW(?8t02nDfmQ^Ian^w~oV&FIG z;h^n0l?{(UctP*ie`CJf{2rN4sWw;UM$ivrx?g=dfzb=H7m*-fbm5AeqRi6KS-H#d zzAJ-u@6vgh1%+-Hlar^<31tfc*;4;qQTxJE?vK6eE%c(z^DbQRpG@rDH4xQhsb^6^ z?(#f$PnI2XZ%JUDpBLI<)l_`&9)ep{1>XuWU`-J1?Lf9Kd~tRazV3;%JSj0&ZN&pS zweOzmVVMM%7$IT)09}C&z;h#0-ZF zQSk?G9K&J1h*;r6Y>=3g{Wp2wRC$S4-M~2>X0P)?6#?SQ{Q-_EY?=JG;9{>}Y@V9ZI5x~-- zOCKD6(@o>YFZxoHg6D67_7IAO|B~&Lzz*!4jgoD-0ZG5hI;7U=_F5Ejq;x=gCB6nU zOqY%%LV!aDC4?iS^#|yXaSo{cstS&KU+Qp;qPf0ym&XH(lIM@sD|YC!$kc;I1P~^% z1JPzkr2jJnA=e-QG1y-}ctbs|+M5fp5F<3AzyXfkP}O1rkVg<)wM}5`QOAj**k#>_ zB6Mj^bX){hR;a%emwJ15Ut3|lP_vQt3>9q=ps#ASD@%c8hk~Nc z|JdB)tPTLg+@n^4kl)p5jd(=@+V-wKH;4GOB>a#@ zY~$J_o=PN*@83a4W%6heP8{{qkB}2y2<}SynjCsRR8({Ba3Pv|8*igmNJqDK(PH+(e?_44f>?bI> zOnLrH=zS<5x^PMVnNUiM9j!_adO*BmE8Dq{t>=b*wA&pf!y(PdrY}js~15%5iFj`Pr&u zm`mfl6_f6&z-=Q;51XSG-4i?UN_19UpsjH+y!+xJ!CdeKHXUz{2Go*C?Cg4eoPH;0 zljMLmJ!1s-f>iyb_}W)&mObE(sZU3!;T?jq0bN~Fm9Jus;un_>ZUE)zu&!CH86u_5 ztno(YF(*C;R<-eC-G72~uh{rgNiHk;InRyPTH{}PO{-~*t%m7>CyMt!9brY@Ds`VZ z0eaxE26=QzHF*a}8&Jv2u%^7PSAoYh+cn0}GBhTyMO7M8*qgz2%ImZot}km*+a-mv z?wd6er8TIDSs6HTlvStSp$E(XWdH?4uL1_^)mj1&i74}d*w#k3`56YJof&}NaOZ16 zMe)3#GB96U;(tvv2qM-LzlY6OhaLis#WV537m7-UAB2H&W@4kTpD=oGL}7?>j|aC) z6fd0R2$f%mCl*!TVN68l333i@Xcb*EY&)Q62HB_n!Yr;9g!PjktTq6GC$iv`@G19^ zrJ|l-@It}J;D+bAgln-P**xPmHvx^0Q9BgNJosI!Xh0{{ps>24=J{p#NgpA4gNlytu#**WLP@ zifU*7!sYW}b&SPAtcDxp==h_|yNbF-1GAgK^_5sAB0;O*zsm~$3vZ0j{OD|9X_kJ zjKMJDP}lOf;FT_ex!^uPE8DsL4TGGMB(3R?dNmabim3BN78%U|!}`>y1>84F?pu-* zufCBzTQ6T=ul+jnB=9pL9=E>0q8u-BFJl(5K{D;g>XUgPR^c>Y5LknRnY4V3L`kav z_<%+a?ntE`C3N9GRm#y>YPw(jMhN?ewi>!x&@ls&)r;Lnrl?MCcMZvbJhft>rC^3! z#J*$ys)r5U&EL)brdr;< zU~4?cjsjI>Z(jv=I83G*%n;yB|24!!njr;FI1(a9JQTVf`}j?;1@~C}F`$&xsvFvf zTnM6jx<1RK-4(tejJ@DyKYU^Eeu96~TZhGBPp}XM`PslK8-5Hh->f1SLXP zi7@L!>8nxl^PX1h95@BJ6@5pMT6K%`lAa%?qePMH!6uICvyl}v%>N1ExwIZ|gZEWP zq#D&p^h@|Vh5-3!OC#3SdIEB^Bfld4fc(5q`SXdBQ;!7&b^?1oP|IN~f=Mwuh4SJh zd8t4|V3%K$Qfcki_>B{I(T z#;RD~#OU3+&otS2N3;v1ql?WG;>+~sP(cb{XG;iaxN-Fni*s%KnvJM1RAj^Ncs6^Nsce8DZ{}LzKwptjE5U#~LUECc{-`>4!7TleDF5#*lTX7M zC!0g!B%tN%Dr|13Yvc=f7Yp8UzcKHfk(KU4-?2f_zvXRm5Y0+x*7Q5_#Q&LmOasx7 z994RW_(f6=1&CjC5m!n6YnCYz+rrcYuqF?e-6Pz>{VeYO?2a)t(!Bm446KGukb*i& zxa|z9gMFY~@C{?T zz7lV@vYbd9y<-GNE&rK_2uFZ4#%z~99Ng})Hogh3S+)^>$Db12OeJI01$MF!uIE$6 zYq`lyj~8MHCsM4cNvKScP+GhI*m$6XX~GCNh!tx@MP~IEgO>}^f+l;%0`h1Xv~dU z^|de)H*D3{E#vs)jXGXRCP~SJWj++iC{)J4RE4vz{T8v~&7fof@J_?7A}3R<-&kf2 zpA$oEkCl%k`X*Y|Fpo7*nj+p)^7yiW(%@!5D<-#0zhulcVQf{v(8UY=MVQ9F2Ql0Q z*mk5=&<7$LJ;X}+Mx+s=9lOGs>(^_`!;(NoCPV%`h8JfHmZa*gpq_J^r3&uOj9g33 z!_ga3z1H1eTJh()yHq@R%S!3USfp;Tagd@2mjTHm2W||HcyU)QEd`2GO5^!XnlZve z(!&>{!|^um66|aGjCyijy*Cl3YZ-O)Cr!wfEGcgjQn-}ZXI+?|Gy^NhN4^Ky&tA78 z%gAp=1|7Usli<-?)z3pnamt>LMOx@|T-QYFrh9RdCm4T*$rLqP=<{EK@WHDupIgqj z=My$FjPMT4WF~ImC7I{ARijELe~|?JC2pa0he|${kAg48Y89YUg-IVzDIy1(QXPC_ zqeimi>fLM(mmJ3={k9eTI)VCX2rpX1%2ktaw^=op5z0rrWz${|r|*h;Y$BMe2|~;e zYY_L31Z}cjul#ywx}3pEj&^yB4WA^r5HK^@Ti{Pm~&WA@|QxZj-Txx6`n zMDAj{7H=MTnE)wk28s<}f1i^wIe&{GYV_jVW!aXe1vr6dFSzR%=ckeyTHUFZ#%_5Z83bl0-H$Q#H(``>iU zl3Z`j_kAX*!z4WUK7$>oq#(EC`_z+@T~w0W4JrR``sDlehg|=Cr)*wk)?F;a!mQlx zqyzuWv&ed0oSCzr8y5dx)w2BiU;ll3Rc5+t#`~U&#v|DN?=#7IufOkIoik^9)?@q) z&P{a;^2Xl)EAf}>&e3Oy%m82g_G{aLka|P(H~a>YQE07u8Lmd$iW^~`)~BUyXVaeS z&yRS3WJ3V0kQu;$M*5!$w+7#ccgh+r_Ymb7#J%Vb?qdU!XvGnz^w=?pc8kLlZZSu# zPE-R(*XO(dQ-P8Rn*&yPoUk@h`l?RrDM=fDLn-?!KE~;O>n(P441p4T7ezc8!l9&o z%+JGwXm0pCazw?SVO)xLa|v2l_Y$J?ilzv@uSUd=LTn$zEVv2q;&X7p`S1g0qPA-H zlBybXU)LK_n{2QdTj>C7{UiO>wj%;1@FJ%)R;|O$WGFQ-e2{ zT+IVeh7nx!0`|Sf7p86D;gD)`yY`ZcHoY?h3rY?=a+ALaxxk<2xgm5id{%a1c5yOcyKVYHQNxOt~zCMnZ^fLapc#_{1YTHMX+y z9i;f0a+pD8aNI%O&ge+ccvx0p5wR5A>aSWyCYbf;VR2bOuwvo>KKUU{`U$PP5A|dv zAWNdE%R3>4ND3<;Au>b8gOS5*4?l}B?u{N_?1}OZ9L>r&R-0Zmfjh)!d`fm94qasO= zZuU|?F!s}_KIBaO$FfppQIv=u>sVX3qqj(2KC3Ywt(e+w*5YE3Pe8=0_)hD!B9fWl z+sE+ct!^bqzBlA7J?}7eFc{RaNY-boYQ<0O$RqV3XXBudL^pmmLU;;6{CNpcn{Ch!XLUe~3}r&QUxb|+kXk$Ix*fA*yHO60 zA5OxFxTketj$|8oUbSJWJZ>$IGyyx_CcOIdCLC}`@^IV^)L(-;$H!}H*%V{h#vPg& zfe3!p{yNFTJ2`;SccYfR)V!{o9-*Fn%Jwx`g4t-PhJW973o#1O-$pt{>VQM7xFF-E!dLd zv#8I6*aB~I_ApTgQJ6*mMac0Vr|6Y3(fXR0J&;xC@Q;~N@8fEeAtd|(et-eVAc}#Q zLeb7=Cd(Y=S8<#AJ7;Axc#T&TUC8_fVTtj$tC(O+*a>jQus>vtK0)78;$^#pm@Lp* zV3WFZKTq#=jMG2w+vCJ-xb-Ya)YugPJ&dz1WiAX8TjY7-q*GDBJe6o#rlHOU+m|%^ z_Yk#1^h#u{Y#-1okC+X`B}A>wFGl7yC*|BI&S{bgoG~XYUo-;9m!Tz%D75`u+B??L zLrQpXS@Sd)#6DhXFQAC6tU&eDeP@saVnA#~bH&{mTC8=t1Vp6syA+G!GAg%-=UfYU zrtca}=l!pVL&P?b>0gd|UUHQ4A$KI)3Q!NGIny`G4D@o>jSQnFn;gHoN(52ZJ9hkEB2;sO>+T9Q598$%z zUzkSM1$Ta}JZ%DYkQJ~sxRc;qLZAR?U?t)C?q#9vq~+k?P_ZZ`VQ` zMWPBq0|_xJ^0K2DFbu$F1o3bs_J&OHP(&{jjCyG0#{dgugUQhxlo#xLAIalAhgC?Pm zX4`PM9E^6m)ZDhndKJw&kAdZZVda(xu8Rm_Hzd=xz##B>uyQ-sHn80h(eMiHY3Tja1U z^n*vd;#<|9*$>Ug!Y3Jvu){4eKsXDo!{{X$!R+3v87#`IM1fppceQ>6pEC>8NlsL1 z$axllrv^%>)0F&|Li!UUo=X}QrL9J>#l|yR+T4T|T&qo}+U}uOBt_ULLZJU$_mf~a z$^Iu9^gKu+0CoCilR(C&N?sUuaSZOn8bz!z*>+_&l~y|h>N+%2#nrmM>_y|4aAUi| zLk8$M51Q^59_+$Jg7sArRdcGSri&JpQVg&jdJ3GqjvxF#k)YlL=C<9g0!b$lp%6)W z17zA>Z^-$&rd;+`=GS$iuZQP{_wFm0K!%u&^cSkL&iXr#Xf4IaBnYkvEH4Smw+fTX zCJ!)QJ=ZvuW7n@yOW{__)X+}$@HOyw0`1`_&G-}U1S1}M!kUQrxDO~i2`2d`;cd&l znI1AwHdQzmjFtR{ec#VQ<{8b;MH1tTe7oBB@xKHa7;?dD)}*Scs>h`f+U#`47#4Zu zledNI^6U@i7vUOMUveBuh3A&g&~?FG@$qxl=j%{o$0Wi=&+EC6UYFYB0|t|;C=Pqc zE(sx%TgfHex9d^-c40UvZV2z$;?Zi5E`oMJn>nNlm~*@Ad_U#`pD8IUTIPuL`5_nT zWMUz;NtCZ?YUoi09ZJ*&XTjz>OS}b6c!yP_pNYKy6RQ?58GHPn-iGaz{a<@_1KZG7 zZGdFi1YTMsIYMAp;G_zT40bhbn7f|E-63Z`(H`~) z)@*<8@h)Mq??cy!i@*9Eq1Jo&%ea0s?k7OxvrPzq?FgvB!JTKG$vfa=egvHo13Vv< zhJfb-s=Vce1*4bdW@RpQRg(^SH@h_ZyRtSKRC8`lb{;B5SBO7VRlNYm-=ZEBF?&pZ zSIpLzgaU<_tYSL%FwbA$`+Rjnq4$hQz_A^D|}qYSrSS@T%5h zR!$L~jw{H-(=5O9x9H__3HNgD1_>u@qPh&;MD#qXK@ciWuh1cDth}?a&0gFe-K@n8qnNk!lOY#9t!__`jTzg}P`RR))gYhe~8hwo{47BmE zc|PrlBmc@WUzyc3NeyokdFd;BO5?C7!2!g0U>_#|Q%Nqjk^saJal(i72#%s5xb4+7 z_-OwIkOp*hYl5yU|2W|;9KY6heELSvOxO6Kys;qN=f;u9!TiR&6zRjJAwPWPdyy$v zneecjAQ-?sg2~F;Izuml#ln~Z%$PWHcct=>B*!7*iezHM%Kb}xjuC=!>y%)!{W~%E zl-ZlPwQmbKCDG2aWXFi2_?yM3?S7VKT#%ds(hdO9A6Z?T3h48?hBhz6)9|%6q{(6=`ZCZJrdI@#4 z?^Adxa=TB2vR>bo)L_j)(w#(p8-y7>XKQrZ2(gNe@uy?5lPJj7W6TPk#(cZp55y(1 zJ)W3iz;ct?a4QGh7XfAuOf@0H*uj0mcbO>O!t1d+6Rd5x$e+O0?lh>eKjK#-X>JQu zUMD>mM+h9KGR8uo8m+W_kiTZR5Zk2=_)|JK zpq2>Ed}J2oh6|RJFgb;)xYOFhfhiFC2PZ}o3qq`7xeq3hLX$7c4inAOi>U{bt5 zB=HD4Wh#3l7%qZ?AaM*zvm62ZszdBTmB=fLF*`*|7HfeG7B=F-9qx~aeX~M{IIb5! z;xXps&IQ8Hb5IJ=Vz5jB0y-3?GZH4t0ytOX3Ar%i8kYqesBy3rZWGof}XR(AHE-j~^BoV#9e_`<|(7 zfH@&njtuc^R8QpitQUdtl6K)SCYYp0=sAX95v(9#D2B``y+|gjATjWhn>SVuUgl{c zWp-Aa511WS4~7pvS1hZGS(byAc|hMHWUkn)qbCq)n1}amWi5CbeBcmLkLfFtwFkzM zWI7fGT95!mTTLi{xrA8taAD3?oNeOpe=$Ir2t04?$r7I|YjYI{(C$;G9nCvq>6#Q2o^z*JHv>0Z55_ln-R!RL}BzitjA2N!7M7S|k;zcSJK*tq~_|Q%mN(oxuZvx>kR~>6nLyskk8q7o%RsfKPaumlS zM9DLUQDh0k3t42~A^L%?A0^m{XOJToMOe!L86BUToqm6f&;$Yl!?SYNdy&i7kVJ$? z)#o4=993DJ4Qg`d3kDI3q%PwQ(g-o;nKB+tl(v_{Gappr+Ug`Q|G95Un1^Aahf8Xh zWC6Uv00+525yhF7JQt< z6fk`2{UZ0GvtdTghFX(;>jpB^Dt$bN3`;(!8Szr+q$`dut%>3jK6QFmLpl9u*wQtq zH7?Wl0So+ssrNMTAR-AWIZFi3G!=E!#3E>2=Bwn_5c&eNK;IINuW|%jdun8)4+Hmk z$<{4jk>v7OG6+b+WI~in%#bSIMwSqWB=j5)FquhhcBP2Gf(>TY@tQ5)g|ix<%MRlW z(M9FV@w<+GDrzBC;j`1@iX^=h$h}L&hkOLCD=j=|GridIK?^nekBBlsaubPbdZxc6 zPbGKi_;*E%mu^+25O!yIn>CM>)wd+694_LiWR^U z(T7K|Cwi@Y3nf7=D{`tId=xqo>KTMIVqHdBAQgA~E{NohaFoB(?~$#xv5t)n)k+oZ z2B7d*WUL3wZj64UocI^PG3hc@Cn3uH3eM+C5C7N4t|z>hXe>Pw@mbgB&k1irZV(re zIFKwpp{3WfsfR<1mJDf)ojL1cJtQk1EYl)VjDs1XNGvfAdltE!^l4e^i3=B8;sb=q z9(Lio`doapDy^hR^t?ynJ>>Zlknk^&MNTik`P9hsr$|c3)T5#$LoX*V=`TOkui~Mx zGl=Nlt9A%s10cUf%WA$kPQMQ5#Hm(f&_TVbBEQ3lRXt5r1YC`yF72TQ-qt@Q7VuY6$0PVEd>v#wiZ?GyEK!_TLvbeYN?nVd@brm)3h?x!LK`d9n6CL1m7qOw z=qdp(>FruwQQL|;0RK1y1FH5n*Gg>a2treKCB5uPOWR*j%QVMY!9Ve==AS8QilbuB zu;#lJQ%z*gnpsFv^`IVAXPThrr$t(vW7q<6D4ZY^vVC#J*g^D$P5lRZYU^PYuzI}Jw^9}^s{zXtIo{0}!u{W$ZFl3WfoYlQH_0bS?+ zi~Ud~3&a7sW>!6@W>l9Gc8Ds8{DLOKB?+>5$UJ-muD0D_Va5qfv>#LO7m%mH{&g$y zNU4HvrC9LhLdtjQ#bs{c0Wpg7v1i!1EJe;gY2gz$Hf^wNSKW-ya z3s7cK)X?FGV9n1x_8Isu>Fy5tAgt>%IKL*4@BR3JC=? zLJyw?x_dKjq)T# z;qW=&{L0MXmR%AdY;f{$y_h&k1a-&n>psrZ>Kk{1fy~VoEH{ZJ$RPAN91?-}46+SL zhcN4CPxrtyHLKDtX8Z1yyVdOf<3DqLhFPYur!(h?X1EvH_YNB4b2Ic zEGE3lvdkP058<(w@9Eymy^>8O05|)0ck|}_N6%A~dsp_qi2{G1ySG~*BGG;n&OcLd z#QaM^;VU03EabkAP+%hXeen@i+Xv;_TOV0l)I+EnI0>H<7ms2jY@%dlkw_r&Dqyij zZm^mVhCIajX^|jQ!;XyyKdQ?0e=cTMUsFcndI1YilrA23w#7iiexGYHW9~3lW_QODVo1YPa8B5z@(dF*h)c9P+}L0%124fQov1uBM(>+;FjBaaPpj_)Z3z7E+D8Y3n6Y6Pa8VDrG->w>4i^-NJbzll)12l z1JsD4JlQ7U_N@8}=-fx5(r?0VLb8a9PbPN6VQu+BB1hFDU$HHid+BOe#*c%R>8GK* zd*G%^P~M%lNhV+uE*0(Z`M67{gf_vn`;J9I#78kp=-5%%@iuVtDv1kIXFW{BVZE+= zw@HD*Uz*ww`26D>Uhr|0sxiIKoSCXJ5>!+G*5^bZqJ6ONC2PVmzbHW~lBStfmi z1bbO^y_q`9qaV^uphZ66My=Mwd+jiF$eeQLMQK*-4BZD%0fvSm(SeWhH>^u#Y8bzi zW_bI`7S>DwzS*%LDdvDwm$^AeU^`@vCD#XRxm7J$#Pza~kG;ISSwh^^aDO}$k{^3{ zRH7;!xwofA`7buN!Zm8Bx;sdv6&$BFjIH1VUHv;*sH)6a8D9szz3H1KJ4ju za3;SCYex~mFa?u}E`_Kr@H5RM8VN~;U#OJ`i89A3Fl1QXirU(G{X5jp4MB6X_wW|D zKcMG{WA)LXRQ#_}wIAmyZiAgAsjF2WA5j?RB$#8PmHw+*$c=i=IyN_WYwwdhla0-yX}WvtBt0~l@Qa+h9N^w^ub#D$z#9$Th7(HlNv?A* zD;I5jf1Ku+09%ov#s5~Kgo>Cd#%24H{y{RGft+eiH3f-KULigo5!b;2rl+T7@JhdC z{M58~o>cg}D>@5D%>%UvZ9jJl>f%w*md>P63!f&@ofUh3ll4=!8#JVZ-ToeZJ%qmY z7kTQ7(8x3~y1NkX$-up?wb&a;-!`!QUeZ=5Q5x_GBry0K^EI-N7B-+y6Q7SE$#~_m z)!Hn%jsYLnT;!=LlH}YTyos@rvTV+H6O;3DkB`If#$ozw*@=i$u2X7_FXJUwP&GW{KivP0!yFWrgh$jWtk@tOf8OqHMnVK^8X3i6Lu+T^D7t3+Sjex$6-(SPt-vfN9hte#@62P!SF|Q zSQY-C;-4gmP=tW=2xQMEBnT@#*iVcKF9ev(pVKaM7kQ6E$21Ui$ z4QlzwKanU0^5Kr*LBDuZ&++Pkw#|js!WG?s~O_b7IJ8FO?r~NMpOz)?fm3F zi;<1JONz5|BzL|3XOY#0wR)khDVR5#qTiPQ+E@BtXWFU>M_VKv!!($vTS!m=!BL}* zE#(UIpv_`tcu@B_7(u9=5^E2^CaSFMm4Hp?UF;;J_mt56FhJ0bA?UyHx+lgYLJvVG z2*O2QL;*H=@119Wf?!W6&4FaU9NE+okWy(HU`g+9o5Z zrej6AUVPw5;JFc?NgY&gM6&B0;JV6oE$j&$F#8i_eSWKO@Re8zd zCsSxT4X_6)iby)_{pr{TVBzZ3jgm$EXzyd}4LD4VJAN%wFL)E@+t>>)mIUeV`*sj3 z(<`AwGd%>=%|w-y!5y0_389PeY;fmvK*3wfcN0(4gQBm)N(t#_>7SQh&y+YAMCiMI zr~qI5hpT)upN=9f@SWcXdH-o}yo~>KMMepZH{B>+J~|yCba7_k;?Y?JS>wjq@28aA zPAvFu;$WUs9M7ce0oI7&&=w;GyZ=P|N#DY?6v@w<;cs zjHr>2>G~7g(3(K|jrC*w<{d^DY6%}!Sej%BhZt>D<-ZuqJk)OrO(T8~7GMB8lVJL$ zs+8lh%jpS1f-pi;2v99Xrk3~7p0nUR{>n$ceuA0KiVH!NbEu^d8eru=19%gbCQ^70 zc-UC0^8+A@YS{bJLBfO3Yc6cX1r@+`)l)tgjv2AB;n%9Gpudy$p>nye{u4CtVbjwj zgG00!yRYhE{#2GrY`PTO{z#l<>8Hv!a{4bQ6+`&`BD1q9_)tpHq}ZgSq*(r@X7DyC zB_-vSq?DAYe9n~LoSYTyS;g%<;|H^r#0*+T)jM}`+Cui>^$gBb$xXxR2_dwqFzNJri-UG z>BCwZ3*i##pq5yKbO)P~l|7MY8jNtOgd3Yr3Nn%2cft3<{_HT8yy&(h)KnxZ?J7@) z%Fhl1TB|#%#MHbLXNj&toSP*ELH5xa@*d0HyK1YNCrZ4*;${z*(ze9Xmy4B0;Jkvf z9n+;}q~f)TykP&wRw+rGtwDQXyY~}!2nPp|a`zo#cF+dXh?W>WpsBXywR*hDPQ(lDNV-i5ZjWH%a2t#F2j{?pcJ7VFx90 z@?wmVSGQRoE)nZ5J8Z2wh6r>&h(XwY955CRga#EZ$L&LgY93jE680qI(zBDQxfssC6{Ho>+)p1VDFNq=F_Z|Mik}#CuNCg`kxeEmc7g= z{w>o{1}g6ici!mpLWR>t;45MzE^R|yC5Y!sk3v*qy3_&!fslBDz+y3E6fE}%hNcV& zcw`%qy#e2zE2#iQnkJX{D&wyLqLX|tE1=!NVu`+80M(pM6LQXfR0>H%Jrck+;Cc!C zKxITzk}}OoD)qjHW=YV;gfa-l*iOJLpZMeiZd`OMDU!phj(J2CW4?ov@x6yD^daG6 z*cjlQWKWvTml=}N2X##?3u>LY<)gv%~B3$_ms#Kig% z0uYIrH36$Ojy!Y&N(({|i(JBS>jNQX8Eqm|@(!DLF>)p7mqb6zPYXiRJa{;w5q8bU z13w3)T33W{zCQmaMZ^iDg%V;0QKUcsA~>l@hMW;qLY)HtQ4Ad;*++TYCj3PpaygQ# z9Fvp2XqjYkPr+ocP4~zBjPL>yv(_5YtY(tP9K{JB3*g5@g=%Xj9-ZJ8J{NB;f%~kA zV@Dsd2?lx&L__cgjVKa$VBNllQq}1^|n`sZwHRyLOF^f)YODSzRV?S-lATG;c05gP`m?;w8cF|+} z4(5Bf$%%tQM+H26EY+_BS^y}qf?77)#*FE{Qu?Pgdy-ObZZLg%^TC8 zirVp%ldfjwY(`#1er!g%s^=e}Nc33}YwZv4@e-QUEW^U%YI27Z`$P@x5-^FJ_y|t| z^7o)<2fy28jRF$S4lHdse;*{YR(^iT$$y^k`Wp4uiO1x(9RCYlBrh^3DLhQf>oCXN z@89WGL?%g<#Gpy0 z>}atZ*j)`Ap8l9+Qr?v?$pN*T+kuq0o%|+yD?u5ildqN%YFwu-*~-*K;;HpS)|e;? zdF{TawIrwlST^fkbkd@^#<2vgBdc@Q!(2KDt-`b0Bng)L2+k+#PclhF%Am){phwGq zH?VuPGwN{!Ws|`TcR~O!$<^*22}uAgFeZMXT{d+LClim@kF1)SlfS& z=%Nv)quzYex|Q{Ins-~njsCMc#^pP=956?Qj-HM|ThdCbCAjJ86$>PwCUMaO;yu&9 z;c$y_%bC)p63}aI!PE%-MDy{|jpE<{d{jKU;!$D>{ldN}M^JM|`77?T>Z>@20tqc) zpj1000P;$p)oI_IqYA`}aBR$K~DxBGDJ3Td~-Arili zh%X_=FiIeM_)urH3PCAkVodXk?;&TNQ03yJ&?h+>Me;8}z>5>%;CAbuCfSD1*kh$M zR#`&oAl`*lju9}qG*5#7q*vM z9*5mA4mM&a5i?E*(7bpkd^_6&TOfX@ka9g}7*n6w)TJ(N=Xs}+KpdM!hB#JLG(dI| z+o2;x)|V|HXjPU(*f}d%<_^lP^00X33z)WPBbx~=WncX+#&5uza>Hne+Lku`vB!wR zm5(x#+)QHKh#e-|Ac=mbGLxCB{1?|ZKE|{Q5k_chWW%l?Ba{%!TD(wHN6lv08zh_U z8N&dj%Ht4<$E2AGzLL<%gOf%1#hw^Rgq@MMqd7H3va1DGkiZt?e|rXr7s2?q<2G=n z052~FZ97)n3jjI_uh1`9@{OQ%`aknN#J_o>^($O*I7EInJPz4luI`XEL#A-%S_)!E zF0mI?y9q!&>P^|9z_iNR!Ibl{#NihahiS4-T_c){pRKwF^(5E+HA%wyUFTnqB+OW9 z)RUG6$3>Lpv3|ms7)49}MjR2Xgijl-=0^1ftBWKsY8RfL_rARaNSO7SCF*tnq58aG zR#F8@RGIk%c}^;{3Ug7hbRQDE&3{x~t1Pa^xz}5$@=?RFHIO@P7-N%Sy+du4rcp1` zpig+crr@}?-L!rQ?Exs*mssqFWp0TN4c%`KG4BAj674-7N2yWt%~9b)*ypU!=p_vA z2pGD%30G((Tahus$uTV?MA?mP!dqlPvxXkZ8$WUUIGqb*bod;W$I6k9p&+_5RbRJ( zF{-l0Klw09I3S;ZHDMoMc@BYqWP=u$m5asnq2fAQ#}huBj*qxjqRZ_JTEiwT% zkm?BT!g@MRESofaDtSSkb1=jK zWD?zV7wyKX!Y!}sN=l1TLyt!kiHX4{{qrC`kgXMJR96JLsGCtCfXWp32ss-1v9y*8 zl;=!Rz4z0_8%61$OE_Mtzs837E8t4Mb0XU8b`WIp>3W;U`7gAAH6gx?G1`K;rz3_* zOa}p#0R$sV3r;0+U_o|XA>f5T{P$Dv`a6fEaSJ1q{U8)dhrzG`&^wlpF?933m$8aS z+P_LZS4-N4+0NG{<-tg|0mc$LoA%p{^y=-uv$Kaml-54v<%xs({L^} zgj%Q`n?Nn|UtnRFFJIDU;@T})yecSu@3w8ob8B(L1l#4NvH>b0eI|X!(Z%N7U`PHR z52At!ct25e{SQNbpg~!dn=^Wugfo$p;MtkWik5dL*gREVjtcbUg`~ZJ>3}<4_qITk zaNk2y1_Vz$`0B+*a8u_4^fX`MIuQoUw}j5mWbtwgKLz$O@z7qM_6}cuC|{oE3vScv z)1KfZs07OR1f zigS)yncfB*efnL=114>gQ27lt3?r-!PhMyx{W*o)qY@{-G?FFo0=`WJgJ3*NE%dE0krhKgmm1FxunsFRfB9 z69&CmeM0&*gsrP)l(9jt&kle9^aC4OjKpd9U!a!v!?ao+2c`)i<_jI%w!xn`JiLd$q` zw|w0N_KgnoD(^8RdzN?G@z8~K`J6!cw9sOe7HEfCF6g4~E_ao4eV$Y9sUGuevDzT_ zU#zB?4=R@*6*4`8-0Q*z+4Vo!>$=KQUgyq}orCS``a1UyElW1v-1B>#^QW4phn@3o z`Ovc4dtKjuh+Y4nK>f3Oogd?zKh^cA&UtryLd)*zb^U7R{KbL#Gkcw{bIwcdEBOm8 zn{Phd`yFu3Uu%xa`O;qJ!-poC-xC7oMOPmbn1>n8dAn6|J@c?wy1i&_Xxa6Fa(hp+ zT;i}qQ~#;zbH}^RDYvS}crR4zin?o5-wJQ*|Gu77o}>wbF5>)d{?3%9S! z>2Y1LP56q{ugwxN?RMvT_{Dbl6mwMiWlCtNfnm2boK!9~(!PPOM}2v!G*Hfcp1n@F{2tFUU%esyFyByz`D%$gmHRxYQFgsQ3Dlbw zS|ImN3N6T_pj+;MQ||FVy;ImAicJ6Ni-QC8t&Xwl`zBD|6zY4~)VG{t?)+Rm(k?eJ zP|oHU-1}8K=f!v-f6~tj)&Dd1E9AF3U-n*-D5rypF;~l<>|YuBv0y2fk#63XnRiuU z=!#o^>g9>1{95xa*QJG)E|8L?>LyCM^&fD`4e8O2rRqy5xb)WE@1{cc`GT&n>pyXQ z#yR&J(&1ZOs`QW-6LT4jn%7wGl1Li}Ia(P$>VQ+O!eN*~%jSmOsWRpMciLp;PW`y)wmbC#`%{@Gx|7PJ%v*L7-RH?m zN;G#I8R)-R%o`XOW{J8l@Hm0zsh(t)zu#PCKTmg(-##|Q2Y-;>wfPq#V84>;ux2KvbpI#>Ngs-3HUVLE26o^5|B zL)Pb-4w|ceXv*+JlvLSn1KjrRooe6z554+pik0NdYWC|kzVD^+FqsR1co7S|LRn`d}g4$%8;*aef_5= z%Fm#`^mv}d>L+sN#p*-*<2G90_W$7-c6ms&`4jrMShbjfkjFr|@Z0QieFNpD!0#QD za>c^=xc48EZkLM=j6d^PU*OE#uL2$F-aliOT|SOFo zNF__bBBp78|9QwM_f4SOEI5VdrCcdeRJVMbZ`XHLpq$;XKz}>u&k3BLYQDMiqs}@1 zV@GhY`I%cnT!Cr~lylp6*eQ1^ z`8()tyPnWMJu}QVw>{y``5Oc0;j7@loaqvvb9qj=6X%`Vj)zWh-n~zqbN==o_bFD1 zfg8Ey`manhB?^0$n=0K|%zJ<@dB`az8btXMI#moG4d4K?W zvpbG?s}fC#l)$_dy~-m}ZXqtPC$EPxyZn2BdKt%j(@e#_5}?!7<#xF{0_9{qP6{;C zy?@YZyWFEa=uDn^P%6w*{|MYZz)x1MvCDteqrD6C{}?i}K-POw=mJ$`K7_prT=&qA z?CbXTy6#2iI>R9^km<+Q!Sx*b}3{!rA`ph&8u{OXju71QW|IZ%%Rs>fe z>a%~^9}ubz9ZFk3RH-ecjJ`+-IpRPc1dn-P!Qm z=NPleE`M#0{#vSUGX1qw?Frn#ou_%fu*-YxX4&&}fjmIz5|Q8Cb#}lhcUhp^67$VH z-~XvZ^ZQimbc}O;pebe6jrr!bBh@*d(|w-#YWVbGxW&Nw+v${Z9@}njPGDLG=ve=! z6HQIO4Adv|&gij<2k$BBD-4iJO%1TQ*KI+=icj-%k5FFNWsB5?F`)a*LHnz zJ<9duqulXY?UXw=P_8%s@{n_WSm69T^Ud9F>zwmH>M_q2t5>Amiq*L8oiuY=F!ay{u=t~>C8eO*Pb>x^jko%hRo$-Zt=uj>rIxLEk=6YqDM zX&vhEdi%OR_MjiLb)jk5>^tSU+4@7XdS|Qo=BN4}^I_J`Z1uSLVHcDRaO*wr2fJR? zgAUBrEA4s>-!WS^JN2>vMQHxB`G7Xak6)S(yP(v2xzpamZ`k#Y4Y3c{^lPc9cY*#T zHQMvjEkEXMyL@b~^0`j=%_o)5d&e$+>$jHQep30J@7v{7Ku#0>|5H={o%+~G<@bJI zmrvwFuKT!Xy&>Ynu*XOW&0({KgHoM$K zJ?Pz1HATuT753NeMpx{Q;&L=sNV({1b6;q{GVN}rbm015QoTZsavjpP?D*s7awKRD8)Qnw|+a%DOVA= zujog;C*3zF*sia$7d&Mwwn-)g7a%AV!mGvP0w1l{F=)M?h|lzS>rA9f(4UMMnp zO!s-mXuG~`J+gV5t|xz6AbM+u5p(Oy3$g3_aiBiz14vdp2hV#`V4el_ zvCC}^l%stsO}Sp}t8>ad*Rz~mpUj2;Jwmgu{bDDhBg|@)cW&VJ+e2sA<<|wu z%Y1_3!$0(#@8M_K<<|Eqmme4h_x^QGxh1{YYq}y&c9;{_XZYE6y*=gKVwnNRLo=w| zr9X8}xq;pHH{aazd!6%Pf%EoCa?c-j&W8rh+j63NzW+IPJ5H37c*T^F#Jbxzz2KG3VL!e;v5L$m=K`MZu4P-R&2*J>mWB@~4u^$2jLd z?NMK`GRo~@v8cH9x#Ju@z^<>ip7w8Y|AIvnciWkIzFjUX@SI`?_^TZf>~hcaXm61;lzA!T-22x#<@N^lbCF+eH~aH#I>lAT z1mwNdL+pCj2HHD|jljsiOYQDms;dLkwtGDKhuPPC)uTPdHrFHb&Y6AP<%5RX5#>GOyCYvocdNr*yX49;Nxf7Gi0XDGmkSeubx3cZ9oZf<`ZxWuk! zez0A!S^xS>b73Cz(k<`ybLwSw`Lw`uK?h3%?0aC{*E!{`?@_K4rS%QYZ}rh zTQ1`YyT0lk<#NOh;p`JvImd%W+vQ&CF^+k<#O_a9f0n0u^sBob_g-mVcOw08?|;}i zzcz4xdG18hP8n9W-0HD*JwFYU!`@|h?mU&jx^nwHe4JfwcaL%fwp>|Yt3L`%7rW&T zJLS9Aq1b_MHTS1pdTWlUDbS9q?E33^)jw5A7N|SB_lGg#?Q*tO+0jESKY5<4cFGk5 z+ZD<@%~8g#k)xhDd7cKv+VyViasOg_-4>T}%zdtEr<`CfQ|`oZchB$qH|GyH=g$q? zr?>oj_!Q^e=MTEhZs*5=>&3nX#g=iB4bnO8KGy-Ke9z}9>hWA|xx@)}y$rSaGx|kY z*A4-QbBx6d`l_hi}`CmtlR#)iFUn}f%0kaMTLP5am)2ju*)s#QO@*-^PKKDu6D|u zsE2URS3Bo_5U6LC`R1PA>6|~+IPZ1NpC7p19wE1#hn@3Tf%9phs|>%nik|J(k7Xp< z&oeGiuE2bA=S#J7{>osxfULs;Wq6VT^@r|t=#KA!B)h)IzR!S$D1Max>ztt{R64;lf(A8g?9yd2y zqE|>2Zuy5&?ebZHdWGJ8A?0&?=G|V3Zuy`YcKN^mf7-r0FtVcff0(zNatwr9z(JQo zK^#Q^amCqX39u|UE^%3xWtj~j#1O(5LI@!wBXS=yBA2K*i;CO~r(D8_h@zkaq9~${ zf_MQA0s@NgtEx{`y`I^G$@h=n{;~7&>eJQL)z#hA)yIPv)Gsn#{ItO&ZMbXgM;eMR zIG*c^@LFHSH-9krzUesT`(=?pCRF;~^qTJi{>-&O;P_@|w2_!rXSChwoUoGyO!{A} zzf<<&btbP@)3tmXhk?(1UO$IWprJ<`sqW~bq+fVOW;c};z;C^p<;#BJPdTFj^1|K! ztaStT*1Cr4?znX_dP}O89)7V7IGY{Sjs?DT-eCFGl^)JFN0O(y!VOm{fDdLRI?mF# zrRw&2ZzyiBpd7==2e=%=JbbqLgP9@lZI^#taZ9znWL=zkcFW)Vr|fayx6dro57l?{ zH&G8Zu<%UiX&1!mt8@a=y)%$|1P=NL_cJE9ZtgE-UdBV3$A?Pe!0i(9<+uySoxT~%Z{@9? z41CJ*^9d*e-$3#k*81c2T$=zslzS+7E%P7ZWoy%q*-O3%JbHh%7l}KJkUt*BZR(2K z&&$Vt;odHQ@ymQ3{0?^-9wL8~3^ zhu0N%bciFrpI~HUvHo&rg5Uey-Z{R!_AINBg_^#2E^r(9e6PFXo=LHfdnTjxMc#WR zr8B!{5*w}P{T=!~>NJLPlwbKm@T;zd7c%cdJXN+?^0MirEN(%RS0Gyz6EQvHFGl)1 zy?nech+Hn(5k$*}-ruD!0q&Tld}JO_z&4_S4EWaHl`aK-Tj!gXC(F00^e`?unmk1m z%MI1qNBJw@bF1^=dF3GEhACu*|H*!gZ~iji&vrcdkwF~L_%;-G&}sleZ_pGkcUwEM z@?^gTzVn@LQv3kpS5o@f!8kJzT`%=E5Zz6`;As5PW$;_u^N-fzN8^`S3Hr)eCLi0= zt1|vA55@(vgZj6D|G4vGJv}EH62BncS4ld<8*i0kVI{Am4C?k3Q~`WlSpi(fDpZ7h5TxntM#CP)2Nq?;ee zF%o@1@FTp>Qxh0I^IhPdUI&b5zkA2IlhJPE9?YeOWKktI{iW{*{zsZG?DE^r*UHyW z+$N6ek|)bI^#kzP%>8xRmlmBa_OmoPisXgDOdpva1Anvg<9XEqEafPv!T+a~J}IH) z)6jJ5H`)C%_yzXXR?hh|4#E9SXc{g{|!{PGtJ1JOcc80=dSLr@1~1 zm;V`X&;rJ}aaooeKbZSzleen4u^|639rwp1XJlmfYz=(Z3-A_?WP?_-b9r-*0)K|f zOYt4C|51q+nY?wyJ?Xgl)bCbV1LIrm}-zz=|e%}vpHm)4vD)(EvY?yjSWgdePPD_L%r1@N>QWP(Ppi zHRG2{cclDX?q`7ic_8;-@)E`mlEdnLX!_5daQ|ABOmdwF?ZulPHC(S4G6b+NNw!O( zUCqmj%T;;{>0S!-)*tmtg8t~JRmRIwG*2?PZ#Bz@{DCME{4j59YqhJ@f&XuS&qu!@ zJQQH^=hjrdE(eb*J4t`PG&)H5FZJkNEPv`9NVjdsAM2BO?#@)4gy}iEF7Q8eJ@Y)^ z_pGM@2_z1P4>OQ#$gAjV)qu&F+7Rh?_wkSS&;3uvznZ0k>IGd0sJfIWE6BaQ}6Fyr1$ntKznpA#O|H)GSFp+LwN; zPrp3hisE_#`G%ssseYho(^GmY@GE$Il6=uJsmEU3`P7@d(ULj;(z}q3b;AE7Ud!hw zbRKBqP$CW7hJX!FLM{sBnDf-pT0fH8>?b z9H~vn>MOl7@CyTdj3i%V^vEdHhVd=DAGkjTxW1?$`jCFArQPH|0Q}WnK9V2ev-jld z(y0t^t@~n3zKqge3HXd~z<}iAxRdEK^Fi=Y@kpOpNnw1lN?+l8$Ua1#F%l>CgDQ%9 z&2jAS(QbV5^!PZJwMWxS_5#FQDj~?j{iHAZgLehUAuftpM~9# zPiUWG9GIEgF-8+2_j_`C0N3nKEMeRz*)IK0WuZoE^_co7@K&CViQ`04%QvTVtRA3! zjjk|uaonNO2503??TP$0NkSp)$MhxdW`=#y9#kzYcAc36{1VqM`LPtf(dHfZ12e=m z6nAXEZv^KRM#vEMH9h1%j(pb)`lABx6Xrn1nEZ$q=ni@j+VoY(BHgXdpZk$>IKeP? z@P^CG1@4;xZWMOL_2l(gSKJ>0+z{_q=Qtb5Vg0YwXLTOXt_^gWz@f;@Ts z?2YuRCjsF48K4hQaE1fC;@eU`>3x8g#R^Ko{VzMNRzH?+L2>JPz6Z#Y(Thq?IsKFJ zWc5)}dTiIUj?&Fi*=}eo`;2Hqvvj#m$X9c|tE8ha8*!1z<%sX6PVb9!Bc6`RCEdU% zO%LPqFJ;Ar@yc=OzQ#oo(`!R<`#0$|E&htlnK&!6kL&{EANMPiKgZKlvBtNmxMy8X zwzuoJNIGIk_vU_n{D>cf z&Xi+)`bCWx-_jAl zf5Q1v`6NDpc4PfQO>rM}+^9TR`BOdMvwvWpvR=-CzB^a}!&0lmIz={l^G71xBTP?um{SyXgr6FeZ1pANVepJ1K@^^rF&R<7P%LD}7y0kN(o+ zsVMz|P=4$`7>c&`X6562ZUFMM+OhE~D1D12el(Nk_H>2SUfixr$AI5EoABai4Dqza z+Mm^TWf1sye-Qh5$20#?@+;l%EglQpWWaYoH}VbiGkk!DNAF(}p94OeUkoH)6X0MB zcYI+8_%MFkkA3w0dMdP^hE;Eem0{p7@p|AmK23Dczk0kUcRX-+IF8%NNrDT<^VA4% z+Xv+v0&XZ=S2Vq4PXHdX6^ySVq&6F04OdcJpXbl(-{cavj2DSf@H^9S)b42Ambw)Q z9+F4rL)V_26T zqh87Vp~6Yv7mjy!??bqc)Y?&g9Qam$$K)$3{pUfxLsMjH*Fz_(i1(K|t}p?9r#0bZ9ZUA1TK|@q1a9x3-|3Ccm42!hM__sp9+%rh z{N9b+DM&Z${CWHk{>>95rCT{`nhw1z>}@PK_iVV_7r_50=TGyy2`=UsSwcK7s46bp zk6B3G>Enm-E1wE};l7MQ@+rqzKUF>rxa-{>yF0F!q`fAb^%avk%Ek7VI|KL|T~40Q z(K?Mf&X#}TOyI(G*s;$_d01<12W7>LIX|jjiL#B5*AdTa(`SL-IRQ@ZN5N!_C(}f; za#a)`mg}0y=;KnZYbJT*moC-q`W*1z#rYp2Pgb6i(pNg2`Xe%E9u~WZm*ty17kvKc zI9>tsC&;+lE0JTow{oYx1bjG89)kbK`y;&E4aME;c0}{gXhPK8M;?QXUnWQ8 z%i!A%Z*mka0{${DC%3<|xdO&nrN%dPF>wD&TKNv|SQLF<%C{(x!}zAY0{rdHm*aS3 z&$Pjdjod~gN(lC+H1TE6;a;M?zVu%5rH{mgBiz~sn%9r*bH|3SLQ$PC7C z<;#JK?Tg#reFpP+M(G>Cbp-rI_&O5mH)T3CLct(<#v86lD8R~FDI@(Ou9wk{$N6Hq zx%49~7zC56xDxn+<9S>se@6!8=D33k?-z&j4_B(0bNX^*BTWhCf#r@j(CZFI( zD|hib;B$r3Y2JK{R6+9C0GGK6xT`~)&Obt2U2$RmI*K?3FH8D-t9Ui|t>^p}%9F`m zQu?k=$BrV?S54_pJDv9rQAeTiMEW7;E$i*CdJXc+`uKWy$Kt3W^|lxrw-%FCc>kZc z0rCQe+K0m zO#X-UKbZW5H(SSNZUw$Gzz?y1!1E4vz%*a0|Ke@Hx9*d*^4FA(*%Ic>W9)xr!N_pw z+rbB?4B+RCQg>nP#L81tTvI=XiRB>KEtQIED_&QueHVPga{^OeU_X#8r5TNvQR$h@o_S-VaD82PT@xP9cw_+*rxbvmtgk{a!A!xa=4uXnOt z{GIjJe-x!PTAyzc;(R{~MBr@zMB{lzt`H5FfHH9gnHC zdA(&HMt-a_{wMQtju((G!T*e3Wrn!)BQxVynjtRnvzhTL%n(;sTpVvCzlfYkEv}b0 z{d45|G4B`nJn02o4@1ds#`ch}0rzgGi}bfbo~)iqO5eumhsl%mKNY1HJRjayfH*fF z>$Gvt>LL9Y^22Nj_Azo4i^VQ+*5xlMF6_rgc|DxkE%h%Re@OfS{Ql?p&+q8QdSW-- z6X^X*;+MeXlPCzaY}#k>LFw+7CjZBLmvYR>{SESe&dbH;EbbC{yQ!DfVb9B#`7Q9TdOfj!CH{>pw^%)xJej9}zgIgl zUhiKLJr=P*OQQS9Z$t{yN8?Yx$Neq&qvY3QH9xbL^wYpuzu7U76w@?51*Pxka?bA< zBS+2n7_O{1SR3qQh!0vQ&~S-okYDJ>a(tq-J!BPkVUX`=@_grKdZ{ZejKhlq$L;1G z(@N|oW}Zd<4+ix#!uL^-L1TGogm>w?ADs9r@ICIQ^FEr5q+WZXjO*R}g2F$5e>jv| zhUHOl3PusT8(pR)F}-A;L%IP^$LnsN@&V0o6~%2C$UlUA95UTATE*IZ_SqkL`=fMc#`2 zZMgglaTUcK9mqQ>^78U^EN}WnS+K+KB~XR&jc;xrEROXD;-DlZ}3c_IJc;FsaD zF9Wx(*Tdl*%aT85O)R5XpO1IBSAgpY`BB7%y8u(oq;6{Ue_=Zco4%|=@K-POt zl%sZn9?;rt@xQ?B8nmna=m5fzqePnF_0TJ*+}2N3-vnOOP2*lxGO&DWO1FMwx_E#3 zE%4dH>y6_jd|^X#|CvwhE&SW{dnLs!2>1=-p4MURM@+u@YT(DQ3I50O71%FqsgE3v zRV0=HCb;lB0>-bRxV-C+#?j~#qQCCwovRrxwFdGnHQ{zv+;Yd2*9LB`=Sy)Hp77}3 z#;>8ccH?Yj9q?<1D<}@NqW&PQ>{vZozE#E9IEsCV`?!FHOT7d6PKPsl$}8@@L3u}e z;&Gy+^nHSQwShm_4(#K3eO=_cUJ?x1?)!C`)=zOT8_!M7#`?g2HQ+xa%L$O&aHSM* zH@lqVSCi+(!xe`qtUcv61n$pG{I-#CYA6W28a~?rd~bjsPQJ>FhuIx(mAA4n@Zo%Z z6mingaGfH%32^cJhsQCRm{EsE8(v)Ay5i0b@-N6nEZXR#YZP2$(?el1@VzR~!)Wq! z>sEZel-?Y;l;b`rPgd@X(tj4@JBpKdqtW5s4S2mKwgkVYoFDsX^QAoD{TC)@UGWX) zPxmlH=Se;EB@c7HwXMLn5ZKF5^r+Ss_Z-O3_-5Y)d^wbZhAvX8tg9HVuDCGXIfl61 zSXfWRH28hd`Ek5X9Q&qq&(yZS-5bc+D;xRwfcLd7r?sovdw}2E`C?{i`fn)xYXQH3 z=ucAa0X}HeQts0B;1}0B&x&5)`qWl?F6{_>d>)_5 zKg4jXBO2d^;`R#U9YH(c4VlC}T9d8D@R^;!cRYduxF6_^z9ao@FK={eDPLt5;2(?i zMKvmNvqR(Y(9#Eh`+gutA$n8vRtPpem>uLl1pLXa54t}k+FkMSysf?~aG}35jQIF4 zh26beg%1N4&TodIYlYv?;a)6nule18|7%dLVg8dfcG_d^B)6l&w%f6v;~`rq%?WUbASu?xsB*~tdStlZtW|#7x1n7 zcYOY#^w-0DasI|WGlbi2LI@`@TUZPt(|lCY4|QJPh&sew^#=+cs|}0{g8Iu(><2hSKP(-`S7il zYfEXmqP_5o>q5F@I@o1*y;=RG4hG+_-HhVgI9~uv>Z521{uh;-UJ_kMcZ=(r$0_nJ z2^-F5tBRvgi~Pg92>RwJoYkZ0CA}DYzaGVS--i?RQojbxm>kv50Drd2!TV6iW|Xnv za!Y}m8{oJ-+q}^7FDvevK;B`#%7EIlYzNYcalXsI_vg-+^>C`_fveGQ>BE4F*K?@< z7t;VR1Vhz%};sBF^d=0UTTWl!f{LF$>2bTIb$<#7SwCsG z!VGcc8RBYfaMr#O6Oez0SttOnPd+mdeO?TT9*z)8d1iuRSd1`vGp8Wk7rfkj&QLnc zVVf@cOltFIJ3Ll7r^fU=f~|TFYQX)`eAB~WKzaAeJb#Y!0tw|{?E%Z z?)Roo18(IyV9euuE=sJ$jB~Od4DH4E*3Jh$j8BeX{a6M9lQ&xe?zSNRLEIN7pSiMr z!0?F+fd5v|uKJR{Vln!n>%7@|`6?H_y&Q8?Pw9(*i@)bW>lWggbVrv--F1^!K$lbF z|7bHE@vXd>uOR&|XT|zi7F|bvP_tfAmjn0FK>mVoYi{ScGH~%e29A@BT6f^}-B8@s zL4C{j{HURJv%sTdB*fbES6&Ie$Gsz#GaK!+4nwmvf1&@AZ<5efuJrZ5uWki5tN0CdaW~LggY!!*%Vd;?8g!=Su+@!jZG>^5t#>zpCSy-zko> za;GZ59U9o#P_%*6EMHJd!x;%1&uK*KxJn*8de1 z_YuGdH!M%qe^iyeb0A*zyzk%CRcSqx7HQC0=q|&+ccRZk) zJ`%ShUARB7fP0S$ybX@#dz5y8+fC}bz{l%=e2>!?1c>Fj0(Dvo+}|kN0sO{I<+)Ap z;tv#PS;Z!mqzTh!=}x4xcF*xmi8E!PIMZicajo+|L)leG%Ci@+nNi$MjuP`qw=lDwpWlqKBlFY^;a$-H_kPBl45^5k&sk`<&v^fu8%g z1*1E0y%z2PzvDDL^OFy_(f)=mdS`lVC~i8vroIP0vEM*^C_tjyB%Du9ajp9#ET6p6 zw|04!$&=BGN}q1ON2v|HtaRho`u*Fg(sei^`+&t6zSf3+UFp;LSBZO}r(L{!$265M zqx6lPey}`wy(;~)PAB`tI6lUlP0p&~V6`$Yn$Jnh?o#(bj^_jW=#S2jeot=d)P`pG z#P@*@=l_FpUjQx^q5q|yG<@lP;Hv@uLH%wBUutf6>#uST06(%i3Wj*WEfFKT5SZ#p z*6ihFX+SdK-} zZK9V&IA62~$Cwt$of30tSNLa?O0yJSkE=a~{IvKaS5_FBe(Fkpf1n?_Fq!3%<%rgC zC-pe^try@WUP@tR8V6h->0izam-!WN+o71~2lFOnY)5&;ecEwJ?iRfNs45-Y*v@F) zF%k1I{imJ)V$k{UxS5rTqV-^3Z!an?9Pb8`|74upg>U++{95@1`WV!Cu3wr@d0Bf% zKiOK|9+AZO<&@sz<0bhE(TAmGN9Eo03JeGxB8mC@KBspnUs9{ZhXD$gHh=W)Hc4K%RKsM{zI;Td8~G z_EnxCuF(c(a%P`HzMl{DT0pxl@Le$`rE4pHfb7)5)1v`gC#>+N3W|liuVkDSbLQ4Odm%^KM7n4}XuFjodwH zxXM4Fm(Mzm>_hyCkriRQE8PI@caCE}U(TeA^1YK*pT;-wBJk=4QGF8MV^_7i+zfGL z#l_<+^=lM}Znfiv;@a7J_Fs^1j+ckxq*RVpenrKF`r&m3Oqb#~WJ7V&mDk(FOUQRH z=>G;ZE@k(t@F9bAc__Xds59jTpvgAoM5v3>{GBA8TIbRrW9_V=#_j`@X^;;^B>A5%y`0si- z$-c!+7?m%-8!o##aBb~IabMMm4eMnjI&lpvm+?!l3x4g|b53!cE+5%H^-pF8R$pbs zt>ZWp)bgve;bZzstOvOc4D4x$&-t_8bd6~{9`|zV1HYN)yI7tq--6P^_Q5BSOn$>v zXNXH}fc)C=%g+#3nISHbnpwWN8RE(_#5EMBW-I;aD9!j=^_SgnX7ZJ0h^x;Km)U4$ z`4(r0tIZIX?wDD=1;y>|_DSPA1!S}O_iX*GDDFVVQ9C1CtDX}ZBj0wo+zfH$HaKhF z4aIHb@-?47PHh6YTIp84jMCf5T~yq3?Q5atUsK!(YvM)6gxt3+{^;`PJn8?JWB##x zA@?53ad>Pw?**g3%q2JZa$7_Ga9=j>^EUlk|5Q?3yl;l$AT%GMI7m+Z*N)Qlrg-hn zsQqL`VB?qA2KnZ_zoT`4Xh#{3^dcb%F&e-8yMRAS^+4;PXY!+!qoMSdolgBf?fBqr zK$lbZY56wN;PaONH;DP^Aa8@R_HKOZ+X9bS0nY=;KC#Rvt1&h%XWtFn!#=+5OX~z5 zmHNm2BkAOO7BCuJ)J-KXB2-kJ1|^LapwfMQC`1M#ThQY6Zo~m zHD-t_?hJnMe4WY&zal(iCZbXs`x7$pIph0Mlb9Nep#na%9F`eQTkC%XSt~1X@WWW zKK)_v`I_4a&#xY5k>q=Wm~YVkVjH}))Z8AcA3=KjkND8IEL=3-CzaX_h!46v1$mmS zf9I6`C#Q3Jm3=G2v<=(uSIO=UK7F$Q#QOc~(y#PHYg2#07rX9lVtTu-d<^N%cmBLD zhs<6F8q;fb4sh>u9G^yv>oKqNaD89C=gMKB_;amZ@72A)FW!g9<$9CFl>KO}{cJW1 z{HvZne`n-pjF*Kfx2L(lh4Dpv8G~A|b!x<8w(=>=1AbOe&xPb~nQwvN)^8T~2JR~F zzYpoiC!b_oKKh`jH_x|}nB0~wvoF$Z8l>yP`HjA)DAm&!-e+a{&MiQ?_j-A;qBdLq zE-L+Sr}O#+MHF@6!EklO#q)2*&2yaTEpq_!+t=mfdp~f0@glyzUkhgKqj(VTyRHQR zIIg!S8kYhsiY~>kthOzRp7K@SSnt)vNPn`+Pva)OU)Qu>GJ6Pck2{X-e%;yH4^;~Xv){@n@bNwDye@!sxNu#-?6)umd~2L{w#rphde~3M zJ~!(2!}8=$0-q2!#OK;*9HIG;mnS<8+*3ih<-?{lF7PoAF=C=wdD15XAKwGOdZJcM zdYbCq=r}Q`onBceNl1tUdD|n;&?U1Ay*j>_GXDp z?)0gkgmJ?$;-g}_9&?IowPPz+Ug_JQxYX|-CQn8$PLtl`C@K9$PtWK0PiKn~_s95T zPlLRdI4&t$tIXc&N9s2R1%8zEyM;5r?^t(%<%khhSd`m*V zDsv9fh5JYcqd~zBV&lzVc#k5`iA3oOM$ym9RV&ceHP%HS*#y=uuriV z-}>diFW3BJ-H7KAE&WLL8^B?G8tlxemA^1O7c|*FDMls&-ty9{78lFO8!|upEPY>c`K$3@ z?6!CZ`2IG)7Z6`6;J8PDzg|zZOU4tlyZl{9_g)`2_&%1kBFka6>ZkNQ;C}A<;(6y~ zVp;5IS$~wj7r1XYE_pdW8lS{{pug^P{*L6W%!jY(bAK~Y)%=3;j7PgleU1l#1>;-$ zKJe}8Gj%_3M|=K!uO!*A>fg#yR9tJnYV?xQ*LOZxu`qghn)KHGDoTHJ78oF2kdt9& z5%(KvuU5aMA3^R91@$x#?Jgbh0Qx`qF}ql(a@Br6PkNbZaiTJ)l?+*))gWp?$zmSjqAoi0-JS*QF*jwQV;O7M8 z8cbfqB^r!x%Zu%}@@wF4YLY_&JR>KN942q!H^3e4IO=D}U$J{*Os_S?{XWb;%80y! z!NF{^hstlk_w=BA{n1uZul;%E-!GfdZJe@vvcE^Vt%H09u;eumghRdl@_zsxyTG`g z=6;_v$DwE{(`+qQU2%JPISD7_Y>w}y>)_Ygu8dDc>GAx7_)r{C!+3rlZBcRSH^e4#C96xw~lMEdy|2O+5<)?w4&hIn6iD!WOvdbmk`{s>+ zbLqq19@jE_`B~s^aDQvGH+?|gW1b$ zrI%lsnVgA#&kUDWT)0j#nEa*Ji`iNJRqzYH1J;cZsXO_~s&eLDpIQDD#SM7*Iqt(V zFua(p@6-PQzjnBi;?8d3H&1$u{y^{P|AOED9LI5oFEDO^!}_gyPros<@)Q;KWE0;z zT%T5+{QtnOU3*Bq3EXt;(CVk8xbFw~N+6HN$9+~UPyQ|NT|MwuMxv)_d_qTHd<#i< z)*ti!^6-v>Fb_G1?<1Z|f3e-q(R?ejfZtyi-Z;+x=v4pg$XwW!*u#RxvJz$yePL_=8-&?vDCI^pbSA^$D6mqb3cXnhpGC9M9ir zm6_)9=vQ$DR!@cPfj`3a%J#Y6D)w1#gR}f|JAhw&KSg)Pm6OqXL|%H>RzC;#mzuc>jnY} zM2TV8xKw{X_+&jlL{hAr>0Lk%`;}o#(1&@s*4mfjKLC6fKN{3iw}a7EUNMHReGvE~ zf^zgme-SnI1_5ltXFm-5x&a;sPU+w&O}tDVD_?Fm;IH@cC9mU0qbGI;J@&hj!%VmQ zib{WXV8`9?=ezkhiw?Jruj~PSF9-O+XttDRFnNOuXm(NA6ZmsEQ~Jk#yM4Ebygwgb zvL6HPzkm^r>-!s0!Q%euBD2YzodbLrj~!wEfMyP|TC4FJ(?dh?*Es*An4rm7{y6x> zekb|Yj z?9%j^*bn@}dLO~LvZnj5ONwib2YiiX=J=+cf6DIXGvKW`Ng(sO$rYLr4Nubw#lZ|RYO|Z% z5zxcNUXM6OY4n29*LC^<@?`X)(p%?8a}-}vdVEhV&yUYw0r~m~`X9lWalVt6{vy|p z{y)(7ALfsQe4p}unbK1Lfk0xFT&9o%1Y+02(i}{=0HC^8L8W%lnR`YKL$=%MdmH_?&lR0Qhn5hxq#) zxW{gBG)K&Fu~_k9>b2wkv`|30I8MmlKfjOVT0-zRzVbQXu=`5KDcPsNTQ=jkYfW+O z=6$&#@SCsoEAgt&t~#ES71tBs!tXO$J*AEVzs($n6)EdCGD^SA>DN`)k!tPH|s!e!TBSdK|fk&+MhUd%sQxE6*9c4AQ`Z=X>5ZalZ}t7O+kw$3?aJ9A7yZd`E)*(fU)F$SNPJpUfoin>hb2d9rfllpfxXNH_XO z|D3E$QLZBR&3ArOt||Jjy5ic^XZG{pw_nKbm{seuq`1!pxFH_-TiR>l6!80n<2X)t z(<<$tuDGKe$MfQNoyP1p_XY6VF3|S??s*@ek<;U4#Zzadmzv^^5Aq$Q@{$?R|M7T| zI}QB8@($x3NEvvn{;G=mTp(XBZ53jUy~nOvf7vs@w_QJ9o*^!ACis0UD9>2ltHtN{ zS;c)Oz=hviGJTg7_btcqcy)@`pOrgz7V>=}D0g>sgOs~Fc2_$b|`m8Aqt9aVK(X_O+-%5WG z{K9n~J9kLd?)Fw!oTV2(g5}OEc zIUu0nvKP(_SG;J3<(MHZaq-OfWfgZ-Q+?3>vzBqcp}5dar@BWge+lxHLp;P&;xZ>s zVF!vk#Pg;3@c62@^rhezwtN08quHso=c3{sa(?VT&1FMv`kuShOYSQ((@#lpHw1ip zc)^v9XK*)R<7D&W>?Xqs2drU#<*( znjO`1R?2Jiywc+*75ByjaBob+xY|^>lNEPzj4NI>1;3LNcT$YYTn*fVF5hs+8z(64 zgcz5&W(wS~iaR#O)uzH7uDHWL<$iMc+9~+mr;2&wKGF>SS^FtmHwFGJQ_x$ap=Nyc z`l;}ysJBQ{&G^&}Q{hcvZ;{5D@%1V3rnt9AbIth5jZ^VA1-?ZZY{nNWQ{hdKZ;>XO z@wuC(!n;D>GL4!((>G6rj}>YfHGE@gyerf+YWV6cQ}TC(nnn#@x^+stE7UY<`21~C z;$5MpQNw3$pAzp1HH{iR@!cu$u|id&hObSH4-^WGTEAAl1N_(3dZdm+o{U~m`gHw& zRq0!M`ejY&>q`HK(}$br={u4CT~1Hl$&c1=)s?=Qk9R!2yx|?I$=SFId^T`C+)Kve zL*j1Gr|bVyO5eiM!?Q8|8KtM4ez-gtJ*V`IosJo;(F;o7)ajpYN?%g?KC1)JF%iw@ zM@wI?LjGMnJ$85+J^g*q%@66AIIf9*Ug^`-Q&H)r_l^lfvW$O4>F@FUyPNpemA;qP z6Md7O5SBju1IV|Hr$4MIeOBr5y*1Gu?4nru;t!F2TTh>FN?&P1uPgn9*zb8hq5g3sj z7x~fTXgsR)xE_jOJro}Y{cbG?rN4_GEq(U4ps((7&X*^n=afEOf0kGJJ3T!j_Ljb= z^zEH~WE202(x+=jHKp(1>3f^fC!T`*t^BPXGD=75V1JqWJ6f&Df}zP-QrvXqEGvBz zm!qpmj%pivUFp;HSBc-D9MkDBt#mRS`DgcjnEVx`i<3?WqF=~Bqvw7P{<}zOrXM3u zMlUKo?R50VrpL0<<9G(?krqs|iZ@(caqo6MU2?ZUD=zg1$P>o_`8&b1#aMwZm*Vbr zen}bHjGnE7&pVt>lR)}s^qkUd9!7fkB-2gaveM1(f_>1+ZkACopTr-LPpcgopN!J? zL^9YJ%?7gix5-mb+;sg?N$EQ}pQGf-(pQ!Kd8a3f{Al#tpWa^1qSEJidYV^JoFS`! z8=sou)^gmw@?`Y7(x^K3E@!F@J+1WV`h~30ryKwBO5f7+M}adBL3L^xZ1)i!(#mwgubjfQgZcO_`Q$9xK;8m_81=~M`dc{ZO^LPGPG^M3)q zUpqgJQ<6UTz!Gf)LX;rZU-qvvD{obCt>eAP*HHTRn)3bXD)LqShWxhh@mAxgEFZ1K zx5`)h`^@Ca{R6nT{huh3n0zIrAM5h*J{F2osGV57CY}Qyv=-^d#ql;iIi)}7d|3W( zc{vT2eja>&*MvJy>Vd+`9?wp^09+gg;&T^gIX|n{isHh1;D?i!pk7JMt$Ir}z;|U+ z{09<+3{xwP*9VG z&of<4tC!46;J2USl2UV)Us37kWwd<+@m9mt6}QCskX%#5d9yFSeK}38s^aE2zmw$2 z@=Lt(_IwOiRNR2`A-QBdWtax#SYM49@+)UR>%dJqUn)2Hlf_lvgl^NIg}-xr&3`-z;iy6fv* zCB+q+aI#^B*BSRk^fK-zQ~#Y=c}j|Fr}xYo;MdMxONwh}uZF#9RI34YD%VzSOKOkSMZS>~-7mRE7@B=nmSYk*&&DPP*p)6#Dg6nC5BcpdIk z?l<_G^1d%Fv*yg?t0?Zv&X4PtdKT(`!|^}27Whq9A7*zo#ZBkmq}B$XP#;tIH$}xw z=ik(n{*ll>ll5UVd>ao^??8Uxy5Ml~&lnGA8P@y;!HFLCdT*~6vzM~s z+V!{D^}(-QJ1Q%#T{}u_0DkT4w5Yffo63!GllJktf1gOrth`0VjW+RHb-s|;aAxJr zE3RF6tBRY>POUz&8zJA*o8-%jU4;EwO>yn?m+Jt(Z#X|1hiJW>-BViL^?tIUxOVat zHU__<^W%1jeu(7r`e-PwEx%2`FZADs`5pxlkhZzFeCbVrYggW~;?8N3Zy({u{)_23 zvl;kJ*WL|RR@@JrAD?HWeXz6-IqcU8o6k(XhT@vr8{`Z7wbB;g*RJ19Yzf?hfqX+a zAKujO))Y7BIIcIE(6;pB*>{59w;jj*_%N5ZiC;r;M>~%B#rxo_-_36Yejjn%uspeb zmEKOS)Yjnhcjv?PaOW!Zkk|&ecKiy8Ygf+nyJp6(q_}qcvgw)et0=A=zx=k~*UpY> zin}4u-w?h_+*Gf{ch5||hT_`ESANgT_@!n8*N$IRanp@MW_RiBz^^q9X?B-Wdb|Fq zrnq+FMQ(fKS8f{jCusj)^SIyG0k{Kwy@TfkFG~L;_ZaEGZ*pee2Yfp@tBO0z>zCUj z;#T3fk=yC*`I-J|iksG7+8KPNs|S<6ptyG9V@+|FG|5jJQE41lK>fDKRd_%0-PUn= zc`|xg=~@jG=gT(HD@yN>w;bodj&PH=ru2uL&h_(Uk%OXLe$Q-q7s$~!3qU@{bl0Sw zzrAa+n-kFA+tFDfsZsOeQoABu(fP~%D2DUrN$CH%Liw=LON!rP4ZPrdu9}R#M}{?* zA2y~8qZJA~rpoY!API{PQ*6^xojVTF_4VaNe(vw$1Yi zMB1FqKjI7OJFX!xFnwqC0iST6dOz+7>Bm+2{WNIpAIsYb{0}@|u2&iVs5Z4yRhRans5a~V^ z_Ag0wUDsQ{^eb{~DrvJuaBZ19w-LuS{P?@Hr(ptsC1} z!|*|Ubw;0+it3EEr(cYpR{qKn;2(dVmF}ZW{@q}H4^OQJxOWEq&gFKzlpb*LBt6_I zRZI^H#YoLQ8%HDkqi&ykUqX@fvOId1h{xaM@N(CV0e&;L9~w8L+`~~;vcTy%D@UUM ze9861<2B8AP+VW%Eq)HTA2^QN6)jj$yW;vWd2>U+{WZ|z!ssH&c_E#%G5ejbe6q&_ z|7?I?jFYm9qidvmi?NM*F-@E1b}Xil5B>)yD@R7YT9HT`t(^+~8@e92K4c*2;$M-3>=reI9a6fIrUF|s2Q%!L{ak=Q+FYYf~ocuYLV=;GUUheYQ;2YnY z!uP<)cyf?_e)AxTet3VHI1l)*2Xgc$Ph`gZ%-G~8EAFg7j-}{NmnOd{b|rGS9?l2f zX9B*x$*L$mYieKlBLdCuQ(iA#a| zPEejc+=nD`8m^+acI7F21^oUI@Eg(b?<~%JgwB3ap=`dAzYO@#IX)>NAEVcm9>-ad z{R}mF?yE@O9j9Nxk7j38rC;Q9_UBiyB>X*&1r*g0ij_0}HSi1L1O+~?qV3K2roIl` zI$m#a|IF7|Dcsv}`tq6attoD%eCyu;-&ehSd~Q?x&Owg%nY{HfaNiAZL)g#AzAtG5 zPM+zZyb}1hURZB5zaS*pPp9!sUjbZPKQy0{ZgG^nH*(5APRv$4l@x!r2E=$i_pR}0 zGRa_i_^((#Jv?X+zp#gjJ^VgWFeZNlK1WH(XOwcMy`Ygo_-GPxMCuzNI~e~>}`?6_?Op*=Y-u*9G@V2 z5&UWaUn80Q@7~4(HRHSO&5_Omq9-clW_U&AuBcq#F*>4J04qEC!NaOC-j% zofI|#ZjJS0`HPd$C#1l|NqiZXUc9O=qKm~c=*^S%=6!~hQIpb$k<#xAoE!bxdud(t zcQ>iT^ifPB|7%qqz7NFiuQ`Cz56=??TDdaY0)K|%`F?Lf^6@$6TUFdqUVfUd(-v)J zDHj!LdguEd@Wn2At{37f)9>bclq-r0=iA-Mhna2n3hEY(Uv)P4J?C=L{l3v|(ybdf4LTcXl zW_ALu`QB72B=y(0=x2!UJM#L^?hJew7aGL9zJtIIa&+AB#rFgMav-;GWd2`}Y(#LA zr??C7zjFIxKT{m##WJlWN0S|L(_cOVe7vsA_x0?}jAh1Rd@G7ec|DU~6LpE*%6v-u zALCp75cr1u$rxWmmqkGgm)#Y($Gm(TmwaRuT>8VnecEwU&lFe0+c@r2R9tc`0C}7{ zeG(rIJAE>GXOc@s4|mUk9hw}aIp7q3w}`&`7xhT_21q%EOYH^R|6C6K{>YKcmCPx& z>(nQJ+t_gj%aifRDE+HW=W^3LGm96d_nP8f@cN{AxPEtefc+`e`+Vh7or`>T_ILot zp=jPhaVUvjE|E3oB|7I^0vCG85!L~ie2w|wQ*rs&&-$XlJkD3%2e`im`E^Bm$S~7| z_1P}Tv5T!P_76+@Bi$FhTs*%NKY4kQ?onCAk2?VPF_(wFqsYf7vv^^0mlfCLIIM?ezU6$A-{eP^?;y~_ zdD>lfbQTKF$5w9DQF=+?%uTg8h|c$=`5&FXL-v=_h)JNV`nqyvssVzqO*IZ8a1McUH`^XW^I_?nQE_EEo zC#V6p`1ge+!1V|E9!8*M7}w$tOLWERr~Vn>-y7ijm@kLusUxKyagGe1Iu!T^U5-OY z4he+!OTek+yGnJ%4+Z>tl20(>9>h0##IVhd@`r(cc_XV2`tHm`w2@T7JrlTTon9QJ zFQTgSNc0BLI(p#XBYlfzf~=N*qAweCr9BK$=Ov^K=?yvn^UD$irDqSCRhWV1BVkGFue=P!zgIz8YcmSzQVIjOXiD0N*Z-+)&(KR6n>6^*)wf;>ff0 zef^u@ca-BeE=`^jUVHzU`xair^IpE^E9W>bkK)>u$ME@YgYPv#{)0Skqx+nxau#$9 zeo82YPhAQ8O!-$7e|f;afDVMeBf5Zg^$5lEk^2t#w~L1)t^%%I+``7E)YZW6<8ma$ zbd8=@`Zi8KOrEUVMWx^8blRswBMQ5tS^4P^OEi4;8ss+?^q&KQA4t9?R&bQyt=!dX zfe+VlY#$JFHonE{fO|3E+mDsSe)z@xa*MCyZvZ}CS2={PmfcU9Q(y4mZj&Ql0e)pr zPWiAOP1AVPGF<8=;KFgOfc?OQg9BVeaaT6++n+Vi#4mp{`1LrB+W}b!W}Lpyt){qk z{ED~Cj9)`>+c)X+kDPCRcTkS>ZQ$1*$jA5bQHM``;=bNaONv{zDvn%;7}ro-7#|yy z;dqc&-CfU>+mWx0Cp-@x?X0Z0)^mV1{#2D7p10x;ZIijs=@|MSD;IoYqxAd6w|*D$Khn#U6jQQt=k5mm zprD=$$^QsTsu*G{?v?Aad=Kzp{m4GQ7QZe3J>b?2_zqy*cYwQRh5I^9;a=beWQ`LVz1)w}<*8?ibANijM-fgX4PT$?B=1^jp0gJWu&L z^I4XB&a?G-N#PgZw?SYZ3$Q@7K$gtsb}WbvmI_*sT&azKa1zp91z_6r$~<+=T%uu-QZP zPvAFQKWzHUJ`LRSf!|G^+x)4tWxDX`an|KMlhnmSGUK253(`Fp=xGQa;2WZ-o{#^D zzXEq-Aa_sn84mCriaSkfZxse9xqQuZz#Pr4ry|w{*LtFx=i?e9xHcAasPC? zV>_2P@Uo~T;w|ImWA$PDi_e4qN|%qn19v=YX`DJ9FL&kzEqB0wY4ngVwsqWjgg3tR z2JoSOGKBA_53L|InH>~g1nynVm*PFq^}@H8*B4s)h4M?l|1prahwr0q!IfSH?!^Gt z&FciMxL0PzulR4^9u4^QCU10pR-gG-f!oA!UGijlDJuPNx1aeGhfhm+3v}qi>a+eD z_{8^!&^Q(SS#UY5-{-JRGe=5_`{BYHNH^^HbNmMD0TeKBKdzxT%q|e$9~1v)jH4(rUn+7u0)?&c=IiZ+j14 z@2kmNp7boFyU*p}@8TXKv)g&R0IK;~ebml?I{nj4^t95qb$VW& ztUhx}U+#3)_ulRwT0RxUZRt4dBsO|Y>C?&CP`c^Q?l&?yGHat8S?5o25emH8--atF zPA;t>ti1e3w4QJHn&QV@j@bUmh1$1cq-DOl5%}Na zc-{|q{wi{(I)Hn?+b!?ALS$$eO}I>M({p`e;LmWrEXQY^ui*-t0Jo*{C4D6S#flbn z7_PJ_aP9P)-we3x1Acwcu;{ms0=*`u*->f>;8%KkqW)KwZF-|mN-OW>-B+~TiFZ-P z@#gAQNVlVx?8jD=m}6 zp4aP#5GNQy*nSAJ*&)oEhOnmRSJYC^NFf`UN*dy`ESmAJZukF({~XSQ{#AAa|MR_k z?4K`Tsg@(GyF4mLYhgE?Uy{UR_m)1p6Vms4diH;5rbp}Dn5lK^UfXUOz4#^4Z-wj= zmG>ikyuOcnT#bKz7trHzk@|56Hir4>8273a_P|H^#*#~gv)N@MgY?T>K3W%vJ}fP{ zuRC53%IykVXMpREwh>%E;Y{B0M}V8oeyd71`8y^~5_znDN$iI7lWRf&h{IC<%KI*; zL*^fu9SuY;Qh3gGkwgw8o<8>$2jXIOR;xwJnZ-h}N_;yC8 z;7{4C(V2WmFdS)+uHX8FMh<+p4EQe5q)Vc~SysaNs<+f3z<)Tfml0{kBUo)6;V;kA zc0YV=$X>$nJR#xnm)vK-Clm1D`|W9`5BQAYGwJ@ulwBS~EY>Z@L%! z;_uhGFe!S1A{@4MIf z-yUc3){X}5bkCptGCm_fjtF&+mcQZi$IJ{rp!L-l1pdvn@PhY8&^p!~%%7ISUY3mL z#kqKailo0~E6*L15(1$=|EBoO6dzru4@b1rXX6CqcZ=IMt%pt~Yn-0q0rRCP(nGhT zC(3@H;2Z5k;4SY|XtV#y1o&+o@S_7#k4bWRxL+TVS27=ZahkpugkaVm)=mYV!`>0= z;ns=hKta<3-K|gQ>0x*cNoe(*DIwjts!twwhjBh*xRc%)uBy0u1Kb!E{bXg->aX}E z)lX30z0ns$j9yt;1_P5j`(@xC@_v)!on*)qnTkrH&82*?yV;4QOI?C=n|nSicNeFV z&sEURG4h9Y)Fxl%QlvX#7UYnAFItQGCF=2LJCEgK`1g1|H2QNAT4~>rEWnQ^KhN-S93mK}Jm`@!&s9WUbR&Y~o#D=u&0CZxN_`SZHlk%4AV zF(yyy7U0A49)rn0vLXjrk@K}4Gq(b_$jABaj%y|nc)w;cdS03@Jya#9YbIsy69K5B z6AfFrN_T@#_#MN*n!E2fi+SUsv2=>){2D3lB_0M@Tv7p;uHv54-7` zFKDre7+Sd!&w}4E0l$ZJgCqSGxeyPJN2^Jx9v&BG?cs58M2UPY$iauIteqA90lq(4 z+w{e8!INZXMJ{@``)!8LzM^{Yaf{CLL^p_a93V%=5cUMAmNY;HG&^?-ZK<)D6xKQN~4#`KU|AGqy;dR-EAiXALT-sbgW z_~ORE|IYQs{o%XCcKdj}6lC{z@|)tthcqzC?ebp}5-6O@56dJaJ^a=l-oGIi{a+Kw zOGEvacLtwPFAuMuQy@s=B&MJA`+s#oggJu9u5fk*|;ezv*x`PtJT0xD`Qt4B)$(1Kq4hKIKjSllw^j(?l6?x9jqU z@ZwIFhyAMS8QtU$=640|_9omlj_c9)=???* zF&}z?hzY%rhD*QFInu99v%LK#_C!A6y6!-~6p{^x%1U7qf|%Vl6d#^1&~<2dwZn%C z-s~eW2mIT`bE}G*X*{>I7x*6KdYpQnSmG1Fwd-#RifhNOK0|)FEb?u)FRre*2fQ47 zzFPeFf%&9K(_>{W_}viT!gFzk%g+NYjxX~5XmKR?I16(#}XT7}9n}a&vcDT2zT;~k=?hD+@O>#=$e1v_btiHGM zr}mo}-_61{!RkRs-xZ|q!+HNc zS&r;OJgzT!JM-+r(of%^HXf%xh4lA${qnryM~q#LKJhwmg zL&R6;0`Rv6d`A)K7|jvf+I{|?z=iR|{^UH?UO$ep(=V+y*I)V#;KKXnXqP=$H|^my6yz4KE0tCQzt;9(`BmD`Yf7K4 z-8Pgy-MVQiiTpSA^5C4@9Q`|^^at0(3-0ge_};$K@6f|O5i6b^Ff~5i4nmkd66=8f zv(BH-u~ElLe#epWKMm3ZvHrE8_{Y|c^ZUs}@@a?jQJzt(Li;FcB)?;zUnej`#7r1yH%j20?Xr~*Tmiz3>{-Zo>z7LJ2fno*w02xk zdYFG7wnFrY^P>Or7{;6|U40ku?G5aC309q#M2Cr$En%@>W$}7X?t@785w8#0FF^iX zR{ysCtfaVu0{-_-YV_vbNtv+SJ4quppYHPBA#U&WU6D^MNWVCmBfZOFK2M~AnLSl@ z1O9gbp7Ohe5(vS{UHT|+J?leY?q@HjsM6{z2|avVI=;&%tbtj168jT_^p&)SNxuiKT)2R>EG!?!0)QH@dD!{HpHAhIl+8s zko>M_g&wd2o*w>fZ@wfIbvj~8r%z(G6}O989{l5d9@NiCP^=fH-+Qs`p?4l+H@ix7 zBi){Ef4u%64pcYJo_9xBMDCXU;BhTawg>6{8`$HoCV0*}w}XC3JpMfzKe|3n?_bjw zDJc!TkhcCSTiB(Cj8bT7z#L?5Nw~9`{)|Q}z06yPJ`Or8fY>ZkK(yG9GU)zlLP8|J znot3$Lq^S;y=RX@xsKCvvb`6$-&Q}??5V1_(}Or5U0XU|+T+EOI*X3iO>!rK?|L0^ z{hvA!-MJ3SO%JeLO zhxl>HtX%o80S|8!`_abOkZFe$c`RA8hpOV12K9RJM0AyiM-Mx%%}MBC2{JJIPhSiE zt$w7*n^pRCL3_w+m!0(x$Jn=dKt%ty@yuK2*03WYk^Sxs) zu`=^KeFQbA-&^tKLg&x%nkyK;oUe^Bywz{wcD#xAGq9f{8@+mIOz?O}RdH_x%}byMT}Fi|+lU>|5uFWySq5$Y1(*T0;%} zn9|+gHyzIGu6Pe{n>oM5@?`Z|R{BjrJEdJGH8DJTxE0OH>nVRf_@!O%eEv(uiGIG0 zMvLwE;tzoT-7E;e{W>jnVp;jtNeVbDpud+mKL2Cj7X|zW5$w~@60gq1_dir00)BSD zpTy=cfNY;B=>uG#yC(T65th-)-*^P+w+Z4(2SuIIoDY%{xRBfMsYihi?`Ir@-#SRc zBuDI;)=YbCna z(=D%Pc(Qsh`z!qhd>&Q4JTDu@Cbp*UauuFbc^!8!WkGFQ4b}R=hT>ig?0;!=qZDE( zrb_6^-Rrg>r+@t%CEuYd0NdKDW!*Rx&IQ`&}B$4C(IYV3n zxO1EzpK}lkA6XFCPx?jRRtA1F?fZL56i*K)+XJ6@B)yp4Yp){RrEYgTUXY|TUeFYZ zM-B497x+A?@H+7C2By9v9nU$zUoFuUX$3U9B)H;^vAzl!5w(myseQ5;chdL>3? zddt5FKI4Ht3RA@S>xxTwxp-Wo@s%PcG`#r!u=-oz_m=>dk9L&yn&V7K4$#=ws@;kT87 z^Tp1s{e5~<;CBu1J<*1QhaI?`Z3bMpuGu5^-ZtaPo6iha-vYQW-rf`3yKMP3wwxI* zu@!LPx_uAdfC%wiKdG&O`*>4%6QZ9UGVfOXJrfEH) zKUPTt7v3Mx6Wp(E{Hogm_r8E%K3Z#)dTG2HxSa!Bx2&->*Gqc#%y7BwfNQ?rc#l=` zt!xk6Ii4@yE4*SATz1Eq@hiXg?fF^z$?ODNcyCru)QS3=7uTQR%R6iSP5#F{f?vu( zF|BaJCq4lDrUAZ7^gfs3y-%-F@97T$U(kMu=ef3}m)5)d{A)>Z^8(x`P1-y4Z>#sZ z;=*xxRNRhHGNbrAtGN#$-_2eB9EW*=`?Vojqw)DiVOQYdcr)L_8ATJjHZx!r*KVH57gRdDIufxEv6cb(&`ej18f8I-$6 z?2a4~ZfKg`he`fFh z8@{IaYXbSFx-ZN0R-6z1b;q;b?sdJHUh?|@cU)j^%cAYY-jk%vRR{oOG<8_c>K^ncL^y&21XhTmO@b=})D7|%_Yx(DtK3)C=rGM1R zH`*kBr47BI^tqlsndL{zKf4g}Pe(5)eLDFnZRjoQo>TgC@)wjo zUHz2W&?{}|wKnvI(x=l;>L8SVI{7n7pDul_4ZYBYUTQL;&sZXN()3xuc z(x+?RMWs)tzj7OTRq4~|x32W*>?e_X`}#{O{iEC{>0if0bNkM>p_i3DUHfgcp=Uq! z_VVT1(2Gi+PCw-~^lBS=y$wCF=LK*0u;b=@Qx!%Fw2zl-kTpW+qFMnPFyKj^_vdGN=D?^ZtZ!gsUJqzAv`!LMD*>93Tp`_X>k_pti} z|5^R~#f0ECSvdQ=m+lYM9`9jq;k(%*=n+2U!5{bF=RNq79{iFAzjmER`i1XSUqixo ztFIHnce7u?gI^TBTYXh0R4*?&f9r+s7Joqa3r#sGZ;x{x{DSb^+T+^wte+b{Bz!mh zavuB%;k((V;K46?@YRXamydekyXhYger0{u`4jTs=REimE`04T3c?>Z`CEN_9^Am` zblw}p@j1*Z!n!7G`Z4=3LSLQU9y#H=*<->Ze8GcX^x&(F)9Y97!4G)wLmvE`@ZJ1H zUifbQq9lAb`_*=F`rP<_;k(s`pzz(wW6DGRxbPR6@>K3GCO!DIA=cm3A9(Qd!govm zqzAtwd^h{lhNmw-eh+@ogP#(3Bh^!i+uO= z_M8;Hn>|V%;cKJQ%lCWmgC6{p2Y=jypZDNT3g69Ns2Hcu&0hqB?`FS{2R|o#xB4(4 ze7Evg@Q_~={z4ajQ6J~@x$#57ck>r{55DSQ`EK^9_uvPF?`EHn2S4Y*pYY%pgzu(* zQTT58qk1|0Zv1)=e!zvV{Y6OlUjCvaI4^&p`dME$d)Eu!%^m@d@F5R=&VxVU!7q65 ziynNn$s_&3ck>rP;k)^ZapAk!FYm#h6uw*jl!WhA-f9P?uaAD=FLd!2DGz>L_-_6} z4RZW${GjmN?341~j|<<;K6wxRqzAv`!LLnt*jM;&`4jZur#$%ME`04T^1}D>7l9X;Q;c)7cS`tf`8nB~pG2S4D! z4+-B*|D5pM@@K+>U+~};UHIBxs3A_jm%kVnoR`0t7@ppq1>w8dqv#P{ZJu6!y$3(w z!4G-xa~}K&4}L-TZvLVqd^dmLAK~=7*)Qn9PYK_xK8y?Btvu#EFwtizMFl59{iLCf82wg7rvYRlfrk)pOOc^_GC`KTX??< zU;B%o@V)#+QE*=VqO^_makF>rDIWPHe7EpH4}QvnKkmWLd+;Yc_$3d1?I@?;&0hqB z@8&OZ!gniA6CV76@ZIcN6uw(|Qe)HSU%l|%{6WZrKOuZKe^K(_2XfQv7xLiegzsjb z2@ih3gJ1OEt5c`fzh3xm`4jNqhdlT>5B`Mk-Rx5kzFYY$dW2V}ar)il*L&~-9{i98 zKj*=p5WZXb3LgBThkSK9r{67q>xJ(Ye?a(d^)KhaF9_eQ{?(qr`nmB#!gtdz=fR&4 zzFYnmJorTqzB<#Rd~a!gp(5YtQENyS1-D;k(%{<-s2pzFYp}h3{5=COzbrgul?GKkh$=)91!d z3E!>$7CiX1=dye^`}jThLE*dEC*{E(_u%I}_>;nS)4wEqxBRI+kJInQ_j~YzE_~e| zPYK`cyoScrp3i#N_ZtlG{h0%J9!Bq%m=v6wy-UJ(vq$X(9FJRgzXw0)!B2Vc$36IY z5B{VFza)G&e^LK6PQRPK2npZKemM{Rgz(+$TM)imc`JIzR~NGW3tjw0z=NL?zMH?8 z^x)TD#PPe?C*Z*k3E#~=IS>AX2fyIKFACpHe>KkObIYH44}QRdAM)Vmgzsjb3E{ix zU+@TD^x&(DIsI<&*L&~-9{i98Kj*=p5WZXeo)o@Y{jR-a`ts}d;0J~8W}lSs-OA6n zhy1+o-RgJAgYUnT)8|&da~}Lj;k%Wuk_W%`vgyl@--93Y;HNzJ@E|Bd&RUtrD)>T`%i!R`DJ2I74$aeQY0?<+sfz|Fbe(iI%< zxRG0ZpJVRpj4K!%?sNRclzonYZ!m5z2S;Bz9UP!g-hZO?C;50Orlezg6bTQJAr?jGdu1lB zXSvVp#DejDm>-V$UZsQG@PXHywIStlxG@Go>UeUua=3d;df|Q(y2k;pHX(a$TyE(f zxQ+4WTKcE){Ir}q)%r{d?&KY{KKPDH%?_wVr*Ah>tFtB`sc_Db2Yy* z+(V=7I4QW{G8|mkAULf@@NSm(n02mprSIbu|4O_MqOEQvK0iRl1XIV-nz$QiRfEjVEn$;yF+lV`1MpU;Ul*rss@q-rGgD{PpAO9F_9@Pci;I z%ibM)FL#G;e@a~kgm&t~Zy5i3OMar}JDAvnjjMl#aeuXNS-z8>Uf|H>qW)RN(cyT& z;yZO)@geU32h_Nf2TtcxL2%A_eUt|yBXBry-3t6qH(<**UCgjGLUfj(<10SLdYx_b z$8+y+S5)PB?dKV{)xu@*UaX4pUlN>sPfNVYJuUv@=@*rIJ)(GDt`uxN{w=)9`2CICjr67cOi}pGy@GhoA1WZ+8_l;6W%+&4 z>{^no%SG-rmfL3O(OXl(aJ}Gt&Heb3e_-4mraePHjTZ)jpY|QkP60I1a!YSA{tTn9 zybBNQ3|=Roak;k`_Z@@7`?yY>VlV&OjO#J+BW@i=pYXi{^;+zu?K|-fW~PGVF#9iR23I)Rp;o9mJ`O@}Z z#WwutCc*HLqoyA|jv$Hf@n=ohmpU~-*%dx2^0~H^<=<%RiRFF{Nwv%U5juSf7(Zz6 z@U9BqH@?<)CJ)*Bq!6E9UO#}!sHE?kx#=*dbur1kW7GVTx~5By#T?UUu}%PZc;3aw$h z*Sw@!%eXg8{5W2C00o!56IBx2y%sKo^OqHN&mYh74zTo$;HxbaxZ*m-eagc1*Zjrc z=14qhJ>$|AZUCzq=qJ}H(Mw|5?zs~gKVadbz7@vqT5f3r<1!X5O%4r45?u921C38@ zWc)Jg-a8oJ>_$}zK6b6}XN5T9|8B}R-eXBO8HRitsecxN-i>J=3Vn>X^DAN9`=iqr z>SvsDzcaoE2Q65D4W@BL!OaspQvVD(QUWvesdf{~d(_~ty@7m%_6EAdM!%xq_Ot9Y z!rp5He51jsL6$darr!d~W_i7tmptub>!$+BRG%vVsTK1pr-lyOw%e~pcSG#9P>#fEZ zf4PO9?*6L@!EZ6`I^I{eGt`#0x^?(?mb62<+}5AY@}2vOmq11T1k_(VsOj{(0%tI8 zf0HkW<8dS6w7#J;8P{oWc#kSM&L|EG@H$nv4$hR{!dZ;ZTIoySLn9UCH*hxN?EFaM z`(1+DAmQ_ZTWIDNy6RT&!zC-Y8?}P(hF?LMxdLwbYO7lz{frgzzRL<6zylesfERO_ zciBo8upaxDl_R<>GsM@ShVXGlygd{`!_V>TV&{l{wHI;tD@}Uw9ljof4#(+#Fedyv zLil;X@4p=r@V)M3JWgF!a}EYr<||QdE%WUUzgl0Nzmu18e2<7cocD@a?_V)jsfo+@ z>ov>YzBDSNxqkF>bA%qc zhH;NtxB*Vz0N$qxrI0!@8t=cB@ms9&74mV%FoXq5J^G_rJ6r#qThlPq*Uuz5Kmao!_DFGVUK% zKD<3zb2W;D-}Cv`*SVt*VCs^RtowPP?{oN5topxJo`+Z~#e6MIf31ZFt(^B)ig`(o zu~QFyZIi!iZ)1Mf4oqNw%Ma+?JH|B&e$PdHNcDxNpaKEL-$EfUE6MFD&mbZ(+aY5Al zknlfi_!0Wj`iu+zCBw(}H|{azS>saoa6FugT;9?(Fu^DKXk1ZnUh)d}vb>au7w?yZ zky}Oj{P!`=P5+Saz2XbpKYe^!|GeP5tFi-$M>A!qaXRvl>96SE@yCf4}ENk{ZkLJJU9DK2;aG< z6z8wLGbO&%LoCOw{LB$Oih_IB_?t-Gjbpw%bvSNx-Ze4yc$DQHW71JW1kLyVocV6` zF(mvKOnB_)-(~dIelYMD%Q?sB)m7KwJA^uV9X$Wg!R==Uw!Y>*{>jHVT%+-G7;c4x zdjZQqhwnA%T!IcJAD`xM_WNpSc8hrbr|IwH1@~p6Kdkd~n`|`xt|YkkO?w3I(epck zLEiyRL$Bj6J;U)|Z{(tXJl)tu<0hVEoc)gTK+Q!{aG~cIcb<`lD?z%EuZVJx-GUL3`TNIp^ohM$IKD~29AAgZ?`|ijDZ1*o5 z^F2cSK={BL^RWY>@xeD3|D;uJSNX1`hJTea{4n*V{w>CbBtGmHZH4J2Sb%_4n09rB z&~t)&;gd{2JzhM87bM}AMiwug;`(FS1V>;%DnP`5)Sj_1tRkxITIW;XBPM0S>;nJ>&1*i50<35JLL9a+;*0}y*2kD4&RrA|FP9`@?m^$#xJtuZarn{{8&+Ni)|eA z*aPynsaN~3ycUDY(wEjRCHy-KAII4jqGrSNcHuCl#u;b(_horcn0bRp-3?kf@H{EwX}kfr6<&*5;dTk`dMCrpnTyVQP>arS#G5q$r$ z4czO26ioyb*Zn&+kMZBO^y%dF^-i9E>GTb;mB8;UH|rqz1`cdGhQ?{PpwO`M# zS*)jECLMuh4ri|;hiZO~`h{S0VDt*KFzzm^-?_qf32CyzcRl|?HwtE#XtqMzNiF1X zZu4-u+}9t@xPy&7d+2MM{2dhjt~)XT{b_!T$La996=R0qZ}Be;i1TAxgn9%2%B%1I zds_(nqGNuHM&@~b)fCv&PSc;Je;aCJ{k9tYmeh^#d*LHcKmt0wMZr1sxBPuoUHqN%z4a>?_kCj*>^D-wJg^?b>hw+u&Y5R*+PC{qn~HU( z($Or}ZJtBNpXy-TcdhtSJPt~M8#eF#)gQw+XFn*+o2;4IUIqnsu93GI=0!G{hDyg< z6x^VNlX)1JA3e&{ui#3K*M8S4MZLNd?s72p2_DP1MaCb39c5n^ZS0qLbo>)R#(%-W zr)vI&mP|pjYUEBH$GE$Vp2yb>Q*9Zpft?M|YH;T3d>UWH_y;Vx{o5FZwXV0J zPR2WSt+;33g|E|55WeH5@txa8Ov8AD=smuM<4!YtJVPzXHDf%xea+YksS;Yxi47dSXvN=;j|@V40$*3Q78{|z3hULBF$N!I{I#}Rs%FDn+9e;gz9qpwV&hX}qM8h3 zqn2BUuv}+eXi43+1md<4dK-5qO#jH=Jv;n_;P)?^_w^mO14i-osPEh0g3$LEeKxWD zR%4g0x|P1akfU0OhL-ebyh6K67Rj_GgVKEw$0_S@<<@I%5Iz%Ls6 zPoBs4cdhysl73SNa-cH3lNT^PZKZdmZx`+dfy1lB2QOlL(8$Mq2DqXE4S?nMwS6YW z8UOw+PCBoIXLNVL09THJZ^IubAQF+b&&1Un?nKi+p!u@7Fczg8&^R^0xW7q0^197) zQ}RiDn{hX8=jZ{?wS9>UIf3(U_)Z-5w3x5$pTC~NrL6pYchtB2ju_zGQRvZe0PPp* zzt8yjCZ4Xkc3D?!=lu)qkY8K{Uk6Pgaz7|q5Kr4df`uXo#dO0M4gffLrv8pU!g8Ll{4Bh8 zehcXf51_vf=N&Y(R7ra(M@97@krVhWoIO1d`>uA=uP_GV|f9qzMeJa z3sAj>k6()e*LeTm8UItOJui&u5XucCzdjmzX+3A1RvD|M+JU9=V#?~G387qiR zS4nVRxAyP7dy4P;T138kO3f=GT9>==&#_#4-~2M37g@%|W0~(^(r_7!ik2gpM+?AG| zOMJ(YnoGi3OQ*k@o6m9+JF{S{kB^M{jw8ndA0=q`FlAbPsg3cW?KB?tz4+cF`O9d( zak(9W?TqLDdA)lCif?5;ObG6TGCSb@k#akzWh^Pc(L?@2xq^1QQeHrC_I&p`-*F(1 z_0#pBu$<*}oAkrDm8J&=eC^~E27JFYp`3Qs-^tt910Ew_FxF6ncUhFXC_ytuYLpL8%=$| zb!(b%3;E_k#=|_~$zTs{x6nq$_ZYjOKZY3#DS%6aJ}J2KwqpX;D>z0+9nT-*IY2yB z=L?dX13C=!kf29xjvotkb9`R&?s>uOWAtLrtIPSM@S~=mLH*-zk^<0C9;Kw-Fa`=z z1<~OGeH{NnQ!jB}ER+z~7t70P3EmZ!z}Y^hAD`>za0|-P?;9gW0VyA#_i(d9R!C16z5>fK;ZE3flm@61v@7Yzs^^cWjT+U@tm2j^Zk;P$kzM>LTLQt zX2v`Fz;N6F9VBR2pk0Iixy3n1>>C_m{AI@8h$rm`sqI%1+^&}W(lmHUQ*SVhdjX&= z*BuWnUu|XiZy5P_e)eJGA2hBcxQh)A&%@G0b{6{wKNI@+$t>^X-I##o@$aMjFem&{ z65$udmu*1z-9uId!u?F(tzr&<3o`so7P5Y+WLtlSMc`Mpz~76&U&`;VA&* z1>|pytNj|w`-8z@|M+fJQ9NEF{m!>XddCtCP*w^G+Px%%1{R@6M!?fJe6_x41^Du9l_;vfLzQMRpoA@`- zmzGm6{C^uhjzh0Q{c!r&vCB7Ej@|#;#s_Y2VSf1$5~)m8 z!FLonmJPHmNy?!PpPJx!zObDWei?7yUB)h8neS^9d>N0nmhmFKPS3=5Ied+ke+ga& zCC{sI`Rf_?gq?1xTf=zj!mKCcZeZMy!J*%PnIdV&v^@1a#$96J(m2U11-L`#6N0oilW-U=c{lY)A;|?9gM%;!mpF%<#oKyzAkR*SAQ4d_c85M zS6#r@LGlAWUY85_=27wk*g{Hq(CtFtUJn1Z36Jv%{4n($W4=S7+ua5PXel_L4(ET6 z!(Aa20sZ*R;W?n{af1$hkfxkZ<0t_0K|1*0scZPagY^`5 zr~E#JI(Le`fsY+aUr(C!VZRiHnb04li30eoSw(;HaeTA6^3+~JKUz+;2q^=(vH4JlV?xP<$4cT8qu1Nc*`pszyV2;g1p4hXA(7_k%rvZ4Ku3&hicKPXgHwV3DFPi|s-EM^nfTCe1`(Kr zN&R+RK1+gYHuWchom6VXq1So>lncrkU2puKXStu-js@eqX~cH`m7|Dn1KBtN4hY>J z_7!!z5;~N_Ei~b<-h>Fh-nWK-c|vD>&8wz3sPBsY6LUHI%^%b8M(VB}lYsEC14+1g zjC+be=pdDV@D3^izf0wJvHX$&fv*3C`8vYM-?9}DBz<0sramMPIF%3-23!~f`jRG{ z!7s6%*O~f&{+9Q<1!R6TAopwpbSDXtr0uJ|!r{MX$``E5aqo2{4`)~UK1&L%glt@H z%1yqF!~NXYm-4Y2HH7@6>H_(>l&k`Te=!4kcwF&xdC0Z1oQTmM_mlqx(NW*g5L)V~ z&ezg%#vf$##c|j75uduZ|-$Uf@;Nu?@0epOp+D461e!oHnWK zcjuFg-_7JpSKTV#3nX||%`F(uDo#14Q@%6@D=tp?{Il@D9O2|P{P9{%g{M#lScyE#K zKFCKhmDZz>W8B{@e+IZ;Qa|P1u|+!L4E`*}-(&Q^aoV9|k#)4QpB2!0vD0Js?&N-Tu_O_^%|a3=&y-l{IuU!;{z8m zzGUR0efj>I2yeb1Gw&z%^^Y@tvHV>(TC)g0H9sf(E{9K3ygI(3@J}~<=(kb3xRn|& znU*l=NnOnGIrj+Rxa=BCM;1?kN%b=E7cOD^@kV~lTKv@ZuD_J|e=vNUhoE|qLZ72? zwU;sO5rf12xT4@X%5c<%&;-6txBqgMcbO?aSih(qZ$uVkMmIOMxF%O2!mt% z1J)w(hZ?hu+<)3g8MU$3vQz$Qmgi;9iElA(2a_Hczu*CGabP;#`D+;GoGXmf-96@m zr9;r>ZfAX6r`vxW&Rcjs_G9h1s{%f^AA^OApctarU^vAUr7!d7>_c;_eCuL z8eJ4nbE*7N-}J*#>O6rCM~EK&*I18XQ;)&F4r7>pJ|5c7n~M775{rJEONYd0`{!Th z@H>2#1!6h8WQ^3iWX$($Jxan4Fa()Em*2upK?>mPL%?$0kMI;Uvn%RSJRUUpwKL<- z7QJy@X}HRLStY^Q_aH?19)u`1uR8wvkFmVVEO}{OU&VFX6?JKjU*ijczq|||Mt!T| zL%Xtko!_kQYQ*DCx})lb%jK5@pR?o-^LVlhukDfAjphHq;H%%?Sid{tyzXz*>Gan! z{$@)q-P-}}(<;=SX05IELhv88%6*ngZ#DyV)cTh8V7Z4IxzPR$IrFzB{*NcIrbaJa7zmo_zsa1Jc#8e(?7$0GIbF;lW5mY znbYs&Dg90!^mNvoN=%$mqxz%m89apL-C)vvJhbP~frj=R%9rt9>QKgAVQ_FxjQ3BM z;U)xkx51%28pse%+b?t&%d0i%zWFoJV;|gn|t1cLGg}f7-NXc)uk&C%V;goC*wVWP1!0L&Isi)qjb@`Ha8A zb0IjM;D%f4<3EydojbAO=+|Hw?v;;X1h8LinW&`w=y*Hh+YBD|p|A&SuelZjwfp)= zsdnGPP_eDXWV|)yn4|0cS=dHh8mS1UrP|=$-1C# zXE3Ij7bt}|{NdYaJ7WKgQnAsuBOswqCY5L-ZWz~g(fRC;armE!ov{7Q)`T%!wuk;d zj{e6oj}VvHJ-?`tO|Zd&}f(AUW+o({f6Jt6cBU z<*BxZ^?A&eL+$KVUeuFyLLF~W@Ph^q{a@;?W}ye`?9U1IvRvm}E%d9Y`!>RzI@woj z^yp*!^;SKL_)esZh+s!p;?wpo^fNwY>N}kWCok9GyMe-Wuw6T15M0OOPjR>dO}`BH z&HXo(n`-+gLm9?@+^Wz29`${52ekCRN2S-{kAaAs~NiJLUx!ws70< z5E^Vi#~QD3^(V7Dr+vrrGDi7~9Agi`?QQIV{dv6U1iSOTCFBQz05gtcN?rbnIhMc8 zvd3C(-`C>OI3AZ-P1anC%@F)anYfm(++9m^JD3Dr-a==vyaSL3|LR6V^sCb|F8tSs zihS%BKRBg+=gwp~^GyB5`31ZXlx`m3*>IRh&^ILG5fDt6&gcC39R5tJJPoiz7~mn- z0F+l}ekJfV#?QC#{kV$^JDg@#&arpyLdJ(Ie4M+aalX|hPLs*;m8_SxPw^rSC(Smb zpZ&YGPf7R}82jLP`Gkg+LIHLm_D*0W@Aunto58kJvc4>Yd!YQ|k=aBva zOa)<$g_{(d-M`qzj(1zc!j1ouHpX9sT=?1!IpKTRt0Xutdxd^AGkeueGS16hlY;ZISN*SNX0P1S)7xvp zg|EwdLHNI5bA!F1{*b@Qax^lvizCqKPmW`mWR+wv(vvMcxV0+#|t!1z7=|r zFv^6=6L9?A_{%K+%U1cUc7N5R;I1}))YL;+J71LCe+nz-x?m?mw3G5ZrzT)4>{V^!Nkg z%kM#f^<}vMMfZ;;UuT?szePVyoAu*t1&$i1KiMyFXnhKAa=4FL``xa9^B_B7m@9Dp zgf-Im$&$pg9UBJc9ov21rr}&W4ELw#{};yF^J@JyBU9`c`hamG#*Q%mA-nB19z9iV z@vn^EWby~f6>R5&0(BCSw7$U)8Rxu1f%CsG`v&vpI4z^~)%euk82?zA9>-9-yU{8r z8Xx!v<8L>3%+FU*{stPD&|zce+>!bhBSqbm z6fAbFaOR)>&2q0Z^8&Qb?erSd1lFDIp&YuO5A5DQ2J0o9a$U}HJJ9s6dX&|mb}f!) z)>be<v@pfL9*VDyU*khxVf;FSM}2o<5?=@~-kt}ddriQf^YFhH zX1Sbl7hJ^n_bmB(UdML`MH8oKG1+vZ+k@Z|4)=u}9lOBvRGdP<$4O*|d&j^>k%~G! zxuZGU{brnv_tD;nM(g#x51}PNI-bBv#-C>Cr`O|nm^RG&z=!eiALsn|k1_6YtN!0P=6jOL8|)`+Lyh2T01EE`m{apJ`TRJaV>6$ zkc`#w=XrTl+2WljkitnUw_xQ*d^09K?psGmj))6zdncFpoF^jR#Gj$S3fUuf{q9}@ghj85>nf0Mh2<&Rqa`{psqXgF4$BJT~y z%KwHz;rCXpp$@OU&f)*G9Y=`$^!qtH^akfpZHJHDD2cF9308V2i8_6yt2q2;%j@$U z6fsm=PMwp!`s*0)oO=L2K?A&vzSF2&Zltk<&VT>)jDN@21N-H-V{%g1S6d?V`WqOx zW+$BvTwf+Tz%uhsC>`*@w=BSVa|R{AFSr#-kB%>QE6Z{63-7mjhT=O0MDHi@rSgoc zm%qVo^rhvD3%}FwaXbrM@c>;kEfSAQ*rGRD*ZwE?5aS1}elzTY7^aMa58n9>8zOGwc(ne7pL4jgjQ+Tf zDUA6=H*9HK@G-`Dt#9T8H(=!9JTCc>vh~f9;3h5HR-X6XiX$u?Z{Zg+)bE$m>!10SM9gq$WAs`z9RB3sE0tE1zS)S1h5z82nR= zpXvPIgy3&B^6`Ek@@fefkgljpuskh4_-p>#YkfrHOM;)NKI2cb{F&+#_|5F}DGJ^h zzfQluG4~A1xBG$iy%4$_)<4U*BaI!m(U&eKA>sELzM02JV&%~GniSlN28aDln2>`0 zd<`I}kpZ5@=bz(vyG%WX{v6JCVTTU(@!=1w5SaDD+7}pqpphS@FRf=l_%|9p#t%JC zh#yLYX-7(e`-8Q~t0E>G$;#@}c3hRY2htd>{! z1M}^Bm)AG>Mh1!Y~}At-VwD@j%}>OVU9%O z%nSaJ!v(DN7e-sPWN`Snk1Potx=GAZ_yu2yWx7-BQu}8Pzi4MB;J6Xa_nwAmIN$pt zk^>*lQ-cnizc7cVis0{e1la&c5WGa>hW^2FKQQ@<`#j0#;yEbT=gFP3P8mXW)|^Sy zrL3N@d+0wb=P;8mW*vZESIR;gZ~5bK#rqxhuY#vt9diGz&e!}$7;o>ljQY-?WJhT* z8>PXb&Zlu7;~y~TqI=f(A~diQpM(UvL^-c?JfR&qT-MSnB8O}v&>@B$E)gVO+n38?eqDX*;F_ z_c=*O-RMZ&pAx>qNe}pV?+d69eBg~hporFE;#8Jz_tz6u`s=l)G49(&51jXdeVEb! zXn6&}>2`tc8KWK!2h?`1J)Pw#%l|~#A4jqKslyxm8H_*HYL8aQ`){l8khNr_#)r;g ze9*!NeS48Pf~FKYcF3Q@c&~a;6x@VmhXlXNiv4BNKgylU^6d5EUOo-f%ezv0F}KZq znf2#0{^Q2K(0bZ_q%h2vz>G4bP^T|&A>)5#=`qafjKknJVAP7abYt`QpNzlCx27A{ru zPn4HhWXTJDpK+@!T*BuE1Ed8=L&Brj4j`3R=_iULwbSexyLc*tRW84od zzy9}Ac;oipPw~a6y@ii?JE6+(kpv2q6m4JsgB<=%t3Gu2Iw@EO`Uxsk&N-g?M;LGW zVK`v%1WJcr8bH8#jV=-Z1SmhmxPX7@=2aA_5_`59sVUsVoZhH>Y1GsR<$rhte=zYf9>p3BOU$vrsrT)UW z)yB@a{s0YDfC)PONx^;A>StXs=6jg>c~$n&sDH5BU3YTw<)3g}50|@Bx^+R)@@uza z4V-&Lv0n*crW?7{M7Ho_#YViRX^W5DY)&8 zf57wUS783daa)p@(b&7VH_QEDncQ&$H0j%iaqk+rSdPx-a=g}JVZaZYVL~EcK?5X>Cp#wPH0}L)iUpl^V;oJS@bd~XuI*{df;Zifi6+Lh| zUxJ_IcrUc_CBgnSfsZc0ab6k@;Qv_WbU7*>#Nqy9(v9aWU_Tk`v@(mCz|(dpeva{f zvhZoX{s4#3<~;f2=NWf~!I^n(JaucvJE1Qy?sJyjQN9lnH^Qx`iC*G3MO^UJ85+ql{W#ywMpg9&b^4>qnOIA^~V&VxLSxLzAq+r;wTu;lgf zyL`Pk9?<%!X2zA5qaRQ?vT;Shy=cknugOor6*t@(xa%!>+jwD#?s?U?qTu=r4$C*>sFWw|Z-Wb1-aZC*0)1(IO86~?pQSGy zPhR*u3(tCw!1@I&PX+DwU~3n0JRO#Q8R6rzBhZU7_75G-xPO}Xus#0?3QUCor|Vbf zON?7<$=m9yq4sa9nN@ZCW#I_MpK0Xcd;~O*Ff&s&K0lIi=UTWFPGU<3L8rSYIJ@3t zt^0+wo~bXhyjDxk0nu{+ChBFlX!1Az6~?>C)$s=cjC;lCiRGT`)Vsv8XMG#vE;TqT z2h?vZyXUzeIJ=zo^ZHUhHeXs#ovy+nmiyNBEEvb*FrM$BaV3oB*GLeJ&o5&LFB$#O-uFx?7s2Bg=d5Sr`N|ARBztRlCBfmIQ2a;Z z{4EF@f##&$#2Z{Cc3>Fbus*=F1k?xHf2h@ryTIUZ{yH&5zj47WF*vZ#$tZ5?VkBw( zih{HE=jwYNvA$^kpw@7_dmDM>`!VW;@2qR#exVhnW31zwSj%$WGIH>qNSN<}4%0d- zzVYK3SJ@6}eG0;_^k+JrqVVl`wKU%=mxf-j@k51m9M4UbKKi{O8<#qPaTgjK_NT$_ zGViC$zgo|@i;N$@eY8iBzuIU<`_4X>@e@7LDf|OXykYv%@s)&s<#r6f`U%UA@6vo5 zEI&e)Q^nEv@es$CG~;2szoHq2|7~?0m|%%V+fj8hey&Lmj;G#5cpMihHO?Pp+$SvD zFyHq*d?Mg7)sHcby_NL0hEd;bEdZZi4xa}}DwA!cf(>dH*miz&pW~XOs zwLNoZFz!Iho*lA#qr=RiJMBRJ9L67R=KZOi-xV@|G}m@0ozJ)h<+xomPRH-Rh;iFY zImdZgMM+wZ^AKVWEjK^T_@^!W3c9|Lff_e{3F8(T9Qvbwp~c7_>G&rvW!xU7yyCpc zT@-&mOr+|1t?{MH8GrW9Y*;L>uzvpwS|<61xxUPd<8u>?KTR%FLHyODavv6a41)*3 zIJErQTNrQe7mM;e@lm{&Q|qJCnfkuqw{!G?eff=0ZO9;6?&NKZ8?@}v?>mgs31;RF zj#DoKx6cf(^~m2by&f7@`xC}JZf0(w?PzS>Aw&7uyXmP{p`AbRXlE8XWYSwxJ!l-=zITUT|)H zep2`k89CUWgLz!&FQ9+Y>6o~m<9o;CAD&OSh4OE$)gRaKju#l;WaOslOXtI+@C$~I z{`_o=H*5R-@dsFrJ%8NG?cpNWUMC;gJUtsPVy%Grr5h!@Xep5dQ8l?D7--AXY==_hkGXmVKA_&I4`OvRZCx zZ^mtB;a16OoU8gY+R;0=FXL}A^$hn7yn#^d6gU5К=fpK&P* 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

      *X}~w~go2 zu3zupwqCDp-JE~he65^Xf%^Y8K<#?9Yt{L;3F_9aQ@g>xjZiDIK6UH=+Yq$_4eQsc z`)^a!snxJfPT=3hXpqyOLA}7g&QY&^{ra_Q{o5cpId$vSsq^pWVEtNw%$ELblv+7; zYS;Sz*n8LJHj*4&*gvH|+KN83d68_#ev)O$t!+uxigNd!>6l=Y$P&dd$zroeOX?H* z-`@ww3y@hTO4`|dFY)fgxWxjH$SaUYWF`_>?J7*~;KQK5KiEAg`v)K1zx(*a*nHoA zKj?mFAE9^U`1|4Tyk+!bhPZj$lyzqiB|n1po1lYNi=zHszpC$Mx0~9lvTm>ZFc8)C zs(!3lu?+p+RAw`5-W>sbRou6wUyk1vn`$u|caEyXs#uqY%gOM*SbdutxMwC7!fAb&b%Y9W|pj~F}%kIH@Ew}gFY*J*S6y(6lDo*?Nt8Yru*UJ z!3UZ`fE&vf$}@QP!JL|JA#a2C2m8I7516*mshhjb@xv->3qOab**(rl{mFiRQXUky zz4sr=ae3g1+rQ1MhVo&xt|rB%c;#oD&Z6AhRg>R}`L;Z2&OT`Ca=k3(Z^x@`2mfZ1 ztVjvL!?fJo{KzQCvU$5HZtmAJ@an|T+Akuu>btk&^%(6pE6eie?EKqV#&+FX zcVDds>;9`X$;GOgKSuFbjps|`7{704%L&bx8n$^{mGjy1dnB3xn*hoNBs873;L7Zq zSk{qWnSL%S3^dpBA$b#&;*+nNTCvt2%tZrA12U0f<&9I;j|<%}({Yk-nSD>kNnY*~ zmTkRR&z9599$C3;o$SRdK{MObKn2s+=SWKFs@xz&UHm9t)#YY7nvTa97xf0b-o~*- zfJwQkJEx0PwccDpIwCI~R^HH|ys6V*Teoqz2IezZ{p?AYlmL<8U+C7Gq9!4nEg4bj-RLipCK4HIYX5-CbHF}o=x;da1^DgF7m~4fN zk^~ik0ybZ0|20785zx%C)N6UIXSwU&4tyh29HQ{yDk>xs^Xp;l}kvN%9Yw}MC~@B zhKp&SFV@BR?**v~>6S@(vzT<+NH9q~yoabj$KS1At%T1tDC-5AJpdV!8a0!nVr{DlPFoQVQ&Y z0s9zWBG@o4I-jvh-Oe{(s&!S#g7~(mH}h)RyQU!%5_U*7F;&HH$*?PHDFu|#pnz7d zccnWOrSv-*X*w@u?9;b2@a=3}Zx+>Li_y(BXJW#Qch`&2y%vj8mmMS_&6(- zM2+V~U5_@^sGj{LO|iOrtS^gcIn+Q^_JhcAI$lA(CFOMdX^vH7xjfF~@w{AII(fO$ z@-iuJ%k?$pw#bnRLIvWIsjk*wEdPW)N>^6~%XAJkJ3pL5Od}!Gz^B>fs6zWiSh-kj z9vAhhkk*eaf^MkB%N3eI+8D}XEJdA;kE-QnUCp5cBFkbjS%cxY9Q`OIb65$aG9nee z|I{BsiEM$?ILz&2h-*+WM{|&k;s*m#Oz(^DQZxscNlgimkRPo}-4TSp+Af!xjP&3F zI&`rWA-;!Nj(pJj<$7GI21V(`tx(^WrF@~0XXEdh|80fD%wtxGl1C2nNnI{rM*7;t zL4^^74pYXMr#OY4zlPr{ra&<;1XO#ks%k!-m&I}`1%f&}q6HI8yD=q3N#5RzA&Nf4 zFe@)G;mBZ60wKdO4xh>F$D)|dq+?MFSSp8W%v8eTW(#52K$c?x2$IaH3r8bq9&*4< z4uD3!O{&kb7qeJwVI31qK6~--P7;zdR=1<0(K$7MlwnzJU=`1);nk!|pmBlOKW63W z@(H(G-AadJ_J{r-q0eG66mERDeUNzqZN1)XS69VyQY}7fJ-A&nY&0oRh97?^8-ZicJbar&jC76edHaCvyr) zOJa9P@_`9U8zw9>a~YkJ*`(UsX^Kf%Op+d3!BpOSf)2(E7we{hr&RTbVbdpJfGHlu z9Dh${1Tv>E0@k{>7w?=Ux4DBVnUGg;y>XCbwVB;Mj(j&5X4tGQ*5kXGvO;EP{+o5N z7W33=hgee8cB1%hiV0d7b~g$+Hcb@k%@)R{H?Cr~x9hU} zOKUAmqQ4aNF&mPR%Gs6KWw^5Z{UsRz8&DGp)etOA# z#N@h=F2SM&>zgQ?%OCPVZX=Svh-Fp84(U{(fKXH_K14;(qHFZmff!p1cu;`J&Gbj1 z%znv3o21@(@%8priihb4wv;j!)nF7u4lSQjjn`QF=&^HqKf$1jmApf*!!0n?FfhST zt-t;nsQ`(;A)Lo^bXo+cf{6K}fMTi%urKnXfTEB9#Oek!rp{&j{S3`59TbTcV(zOE zHLCAtoAI3*G?@RU=a_1Lr-j{GA?ok5mHx!ACRM0G(Z=idb(#DsxG(D}h}-aSaZ|B< zWRa@g5~gM&$v{$#ByH?W@_Lw#T8MxeV7D;I5~%>E_2!~j%DQbjmNq%BCZ(ILtp4qb zk)a9{7e4TPl@GIxYPEkXR%~7?5G@(t;}9LN-n_$(j{?ZXjRHbsVITgfAU9Js?i9qU z>$`Fen^3{2{%e}g6-+5bzu%;g(;+%$Tjvb{4a?p_H`}n}fN$(#u)HSYJW;f(*>YJ< zXn6rwgKE>q#faP#F)?It`_Pk7K$C;>w*+N1Muv5{oUBCo5NKVFe*nZQTT0}9kp`t< zz~FSf6&Gn_ImP^4f3j~rgOQVGAj4Zaejp=J0j(i2umoLX)@C!aK4H*od3IzEYXgFuxcuw& z@D5JN-^rm*>7>njucFVE))`c`6D(kk=2cBoENB6tFPEVt9K=nk6HGm9w}AK%7Dv)l zv>F;h_52JZF=qD~ev2|_xY}|<0#3ga0!`-= z(68W3#(IK^S4Tkf8FQ5a*}S+ZVLm_*BcU|Ky9mQB(D?3?c%4IXc<;`rhxI6-*7zBN zi#tUM1O>R+JVuUOoNJ8r2pO}lC2)?#g)0aPBSnQ3Rr+=5)}|52g-yTCX$>1eHH{hh ziZgpp>fv@iKW%X)Wvwa`>*?@U=ud}tzYL!J2wQF!Ck5JO5_1W4rR62uu{xuGlFm4Y zjL^;J5a{@geNLJ}<`E!?-oLt?7t_wS8h)vW=Jw%k3%T53)8jboAq1ds`52Q53|`JA z;RgfOrQJ2&15wi1?O8x+D~w*>tL*;^3^3gC&StBehFoOG-u9eK0~40)-N>mm@C0@O zZ#INN&|g|DXWvN~r2LT#QoGu2KF?(Es>a-pWUT%n;DNiBFxWQ{bc<1Rf=MR=6eAfW z3i#flH2dmf9Gj%?gCw^OZnz~vIYsYzh`81XzRaqdb`Fx zMF%proD`>6C1Z1N{_~4cNR^(EjJ<{zF#n&MPfYNi%jOK5(=X1761^AlfNjN(4WuEqqwQb65-~1QASZU|F`G#a{2#H zSL&sDAkF9*Gwpo-3pqovz4np`n)KOoo7w5Ur;8%mSgjtOp(=_0OXk%;|Dp;zzt?lC zOrqtQ^+lObXFoR!tiwowT_MSTTON-2OM0&V;<8-4U?c@$c#aa5(KL7&`Tonu4_-$8 z{$=D?MZBc>v6Orf{fC#4V*&ai_E?*~j2^@1Mb&*_*LGjnx7`0+&Q-tJykYRWTpWpy}2AA%Q z24Tw5XM`-Pwcnyh2_#P%hq%)Q>Dw-+aM$t|Jgk@49_lm?Dh2zVM$|2E}IA( zzELy)9HWzWa8J`u41!O^#QnxqmKhh=eL});;51O3!S{@vj8kk{F=&hSJ;!lr&oG?% z_Bm`fbU?%*7dVD^hm8Ts4*N|0*POUV8iKp6IY-A>w@u(n2~xh<=kR~wB;2st;)I@O zhXXZy@QQQ6ai_^Oj>X(oCpeRL{TCli4IRZ4Se)?rQQbdTP2jMktfi?le7EnM-MIO) zz~TVLwX!kU4bI1|4hDR~oLAHHPk%;IC zJ~tS^4HB+1n*A4S#k&TS;rY%1q{(j=8|B3Z8sDqc2@XA}Mr7E}gc@RNrDP(?+6`rb z!;6aR3jRc07zcZ3)N!@^(Ye?z^e~_VQUbT}0{nGVxg#bHB8Be0a*%Spz9^??-ybE- zlpZQ{O6kGL-ud?r2Z4mPhv0BW51-+nb4!6kRSp9stm=fY-)j=~dnq9<2Enwz(e98^T1&X0 z?T3%J)btRk0Krj9>@bC7^YRw^g*Y*cIouc4jo~yqeBt23*V}8fa-gdhtZ5B|+NO)d z5e6&6XCy_&PHqE+`MUwb>>pt6?qd9m>|w8Xl39I;{@uXH76^8>n?Q!(tWlFJ0e)E* zt2>c?l4J!NQE#lg@VSCeK24j4VW4Z!VpWNCP%D)44~C;xT;`K#AynYMuIL4G|Ttp+k2dT7zst zzZ^*j{RgV?by;5G@<_9II53o#7)OwD5C)Y4@ujqj3Q9HdT2|bhF|P|8{tD#MSU9cZ znPO2jE9PAPSQC%K2rW(39dchj>bwf~3HiS`3+x+xC`G_|tGr$~4wZu-sZCfsLG*UD z!f^+0CLW*3`5mnDDl!P)-e6d+A3O8eW-~93v8Prnf6kFq`Q&)8u)U~0(ZS|Z*#pQE z-^`{jWJ^VcfrlP*cQkx%Q(Uy#;(lcr_TN*qJG(cAC~sOQ=*o$mR`2&`x$4b*>Hnl) z=rWvgc7^^qEfk+pvGbIUj%Vn6MFj(2PT1~B`O*o)PmiqPauXGl2~Z$wJ;U{v;vhu2xh$vga8pnQY?*HPnV` z?@hSoT2sZ-kHWGvR+;_z@ehusZHc+Sy+B$fBLLyIHv^%iFL6W*yL zwKUu_OAK+I*SH^#o5OU%=!ruRj`}cAKet(2G91!<#*V~< zq40Y+JjRXXSk(}iU`_YuF;#M4oQ}JH=s#rO)6DA#aBFSi?|wC?Ffu*NMLRfh_n#Vc zG%Gr8k^{|wIgF;t97Z!;4ue=WU^E@&bTl93FrJVaK&h{k5T>J%+^P#qM}yL;AuMf! z(yC9_s!-Re5nZQhR1==c0K}a>8ZqHhDhS`P0kkUEv+B~bYE37}n}x9|*tdAnLH|bb zzSNo`Ae>dz_wA~6teJUPrRq!h=fxxD?lzVO(w5SA2Ue>eSS@;BwdjFW`va?(@1^#V z`+KXM5E>;k9xYHmw8-`cskbovU={L%Rhk4;GHZz!^K7EJR=o#7Xci3AQ;Sw(vYk}?vm)0|~q8`s?ClxNo z2cj{EzOE5pBtoQef#xXUj>huyR(@9UQ;K;OfoF61xsacd>P&J!+eU(O`6bz&Oa0E5 zk>EmpN!2bS+Y8C-GLl{j*GoxtDH&W!6)$I!O!1Q14kLajY=_?kDit3JgTF=m-=wgA zTM1M$IKp}YOAkE|4kzSGBv{2Ys@(Jwf5R!!%RD>%jlci_vezoq}u%sXLC~Pr*U3cg5^`z5T8r)v8)l z@CZhh>*8*q@N1kNh7lOi+i6yy?Hw+M1oTm{#5Myee|Uoy11WYO1h>{zJu0>k0j9+T z)}$4V?h4$SmxOn!v2m$>4Ihe>=!lJ8AttA!wD1vD=rddyk~TYfT&-t_*Aa{LxJC_C zlFw*woNb8&pG&x63WT?jR{0FWxO!CmbUb}Twk@50jQZ7@cyl+aRw~CYWwoB_w)uin z_!vaGB42PdTPIl;*p-ns`U0Q2^!u0XKXAx@BL)7lT`#uZ?*xE@JF$ZJJ>5z_;d8sJ zWJLT@z@Mb3%bRt1FNtSsw9QgV0wcRbN6$wX>$pfHU2(cZ*U?6FOnbVl$LkpyuEIDO zT^4uq;)fJ5$F9);svQ4Xpj)(P{EE0cs)oN7ON>P+*RQzO|6LdK*W$6b8=Xmpzupz= zs=iB@+~IyIS5W;;XsORJrgE8J)i!WjZDsIO&NtVf4A z;FAK>iu@4{DaHDcBrcMk7k9-!AWsSam2)Cx@Yh`L+KE}LM6f#dUz^>l$>J( zqd!Mq=n%C)OPm3ljn0clHTcePti2rlI{RLH-)hozMFVUAvYr)2*uIZU5tk=v0|+Z3aYXz@af9KYD|}JjkFJZ^=xi%R15CQ?;=8cF z*w%N_A77_hL4BQ%5LZeKldtp1=of4aLxHFUCg|w~%2@=2Ko{5(x+BrK$%**_TuKZO zDbQFeVt`%M76#Yb^>>}ydiE%_`^r~-Li2saw#rgrA)3Yg-RKv17p5_{%VNHG#2H{| zughXPIznWuUkYiQ%ew+WVJ{`8%exA5UNV9&>w*s1H#ERiiGeH`UQzL4_CshFs1sHF zVS)Wo$nRP*8$zVD62SKl-D1PpWTHC{cjcGlCxc^DGMX*#AU^V4tqU|W)SM=&9xL1o z7osghEKyIx$2kI5j4q0)nn;G2dVK+e}Sf z*Tr|0-F2~;iK4$Iy)X0JbvfTorQ^U?YfF5J@LkVtO5O6;IAf0~Qp}L++4>G=H)BP< z(dgIH*>8&5@93lD6eCAXSpQox`x}aJQ~U)5Is9{rD>RYmkS7miTn&#-Pe;F$52Md8 zJ)~lvW|P^-2UCiPKT~A6(9Q^>hs)Nm^JH%NjDAUrjG!E(*+hvnbNU6wo>H2ppNFGQ z>|P&z#iR(sP!bdF8e4<#y&K3$iKA6+r!SQ8UvWCWQ43sILd4|xh@JDJ;|E&DU^a~v zyQmQP56z2XVQVCYUcLkyX_~JT?`3p2N1QG6sFE&+@Da{Nr_&_{Jx=1b^Wcri64#Vn zGsO}(MyH>-=(q_l-4`)Kj_nfa>A+{S*Go&JjL|toP)td`ty57II4MnkbA^J-7z{+CO9}zH<*Z{a-%`{xhVM(#M zRsQhZqp^}j?QJ!moW->KB~{^187}-ODuO=GlcZ{8cmkwfIz1*dnZZs>QtOow;!-{iY*@--!^w{NzuDOo*JvU&&BIO~~` z)q8K2_r2NkreyW7n`N|vDOo+7cC~2D8k>^UGbO8MN>(3RP|e)>@2nd4O&RLHvr5*- zhO@!foSpi(=VZ{DHf7(Gp}r|YcxJ`oYsyd`M?Z}+U|ZFKntV+e>YFmuM}#heFG6q` zP;(x_ntCB zN=x6AmcA)1c;v(2iwCw$sMV5qSjeI^XPkjK;|xq08kjPK7kI4POuKJjF4qR8Wa053 zi?1nJ19Nu5J31x@Yy1vOyKi9HeRv4S;%my=z?8RvIcE*_tx=7qfUMli*=b-d)&`~& z4$L`gU@pxD@2xp@U`iogkFm;Y&RKXI$f7l66K_SCv}SL9Fvq019vk2+MN<6 zOgD!)+nJM{IoFv}ow;T+r$5s&H4Rg9Ml_Apk5<2%lcZ_CniD6TLTpNG*PKqf=5&h3 zB25mKCcw*;Cat9ly0*@Bnx^BE7OmM1rbg_VDzR(oM7;ND zN_HI0Iac5? z3SCq8bxkwAYihr)X+U&wGTInBrg7gj)neDwi(OMOcFl!A*Hn#o|I{j(xhK;#RVLmv zwK$kd_pYfRyC2Llm@2Ys?(E=gUaJhI>g<}jvx{@xMh}_VvuA40p1JAMGqq>WT#WQg zJ&8wmjgs}uB}&iKlXw`|9YniM)Q&09)Ga{sXcq9_UxJ3vuA40 zo~b>1ruOWa%cP!ZR`$%rQ_r*^@d&fkzUGpuXKK%$xuMlFwP(-V=IWX5#hz(P;yrGw zyr%Z-ncA~wYEL|$YwBGqq>W)}H3ZUf;Am`=(CC1LH>B`le3p zn>w{`ZUW+ob&IcQk@ihzP~X(FebXZCn;Z0f(<1GgI<;@QZ~LZB?VCEaZ#s4RrcUjf zI<;>)neaZl)sm)8?VA>9-_)snQ>Wq?cPlqjr}j-dwQuUwzGG7G@#wu(3v)ZQZ|dK^X}$JMP1`rE*S@*3 z@0gF!kiXT+a{8;~@i6 zPYz6rbYN=4fvFJ(rtLW}HR8ZrxerW@I52I`fvFJ(rbZl?w&%dqeFIbX4NToPFm2C) zxpE(vR_4IeeFM|GIWTqKz}%o8n7VIZx;+P`?%Ow4?)#?h+c$OJzIh;K-_&yZ=E{BF zv?upX-M4S*zI{{o?VGx9-?S(9O?z_Rv?uq?;{f}nM%*{;$$e8J?wcBM-_(fvrbgU1 zt;~IMjlOT{$$e8#?weNTzUlYeH#OqExfQ4ag-&cu)!q`&gRQCZ zwrCr>6uKhMoM3qk36@ulV0oPgmY0}dc~J?Lmyuw3PZ2EdBZ4)0hk;g2Ftens#FSQD zFxwcER&5$B&ph+`h?rw;N;xZ3UOPd-Zf{G4sj3x~*FrJO>!MJ32Pss0y)fUk7Nfmh zIPlPx3X@_hD(^=rA)GL+soGlu(`Rd{_SV4k+M24pHQ>%^ONBYO4Fxl$71eu{he8bR zmNyV0`v}!bar-*-)bJE@$a3;3fHu{niXUTsixihTml1^E8)IDflLW#w3k4|sLND?? zY~CVH@I7w3mg^3FAtZMS6LNZ-F(LnPgIo0(3{TEe)RG*0XOKRlg!J@dlBJZUkE$Rq+Tt(b703N(OMoPAY=QxE(*;VuoS2Mr}^X8$3F@^c0%&w_ZG)Y2$D%|yU` z4VFGx;d1+1ihN5yNSonl2Y+e(Rf-#apRMR^*c2+9C|V^V`)=yay8Q70I-N6Ax+=sW zjjJcTXwvc5{l6g2ZbgCnq1h41va=VWmUc)J*SJxR>+J}qnLJYQdXJw9c+)w<;|(@u zh@0pL`mSEKID+~{M2ExWWO!e!zM(J(rT7E|oM4&-Go_SRmYeW4ddkVSLI>~fQpaMN zcR^hP0MJgF8#k|HxCtr&yQpsHdFLR;n_lr6$gF(wnh!5{^oY8e3OQwYVarBX*G zT(J}ss+&SfdZxGhQxaz1#T)_25Emp=0g)LNvvEzMautD4GU>qP0me}?5c)%puypVS ziohI?iU`ximgObj)|*j1oBDU618_X07geO%h(-aBl*^&nEHr^IMEx+_F7FXib`ih9 z=CuA&0-D4CB(?QaInER|F`peg=uV1g@=Pk?|M28Z! z>|b=Dd0vR{coH5Yy2Zmu3EWR{kOCyQHVny1D za}Nf5#gk)b&ilxC0gVIBBYt2ehpKM`e#IkJwKNhEIgs>I1pS&MtT73Ql>j1yZO35M zG_-I?<}6SV3~&8O7f?|@ao2EY^q8JH0p4b*6SV=EXD66?)NtQa6(RsWh9+>VnoSIE z1m+bP->>&v+}y&k#a{2tZ?Om; ze}B_iA%dp~uVi*s5JEsJRFP`IsK2c+;VIiJ$iiWpK==!eCykr)AFbzPkAbo8rDdx68V?Ek}sI z8uP#u&d-EIz{DiJ{0#eA>*u@^I*dN%St?}Flg7KxvGq_CZulLZ< zr%QO3(I{H3r(;AR{eFKllI0dV(&6i-#S%1A(JVFJs|t-pL@ zJqd)11+l4RTzILW)n>AgV@52XW4($C{Ks<{nW7HlheYTfobcikDZ(&qVKA$c zF&vX&l%^@D?|oSgClhI&m_iYxDJ=^2N{3+^Qg&wbIdW0PbHu<%eiDV1-XXg=bTj!< z(@1u&%!N!z=&kxFI9@ssn^nQTdI|PT9WbmBV%`yoi0lw&5y*(R(QBCq#Vd*wwp1;Z z6pEb>)F`TrA{uRE;cvF{@7}+G$e+K{)>kZ~!l5lN5F>^pftu%&q>i|lBPmj|k$CQp zg|SI^2C!-3h~X#2cyu>1AC{YIl(7x|*?f4;k%r3C?vHuiVs^+tD7v2?oM=W`jlkSj)5MS%$=%r(y|dX+$V z-9U4$h9>)rC2tT*O+e^ueE%6U!yr0oFapIS6Y8{P)Ik%DWd;kI0O{=jsV2sH&S%PUUhAtAiG|s*B7#skyJVc)$R4 z{&JM1pqJH8Xj^gn1w5tSO(LvLsF<^3gay~ICxe|moWhkf8Y%g z-TNJfQyhl(+9;lmNm_VvNyyJCKar><3p zO`%Z;z~d*9%{Eo>c_w~FJVSjkJgm=$k>d3pJ*v05z?N-8U>4VeA}}q0RFZ&Lmt?9@ z+E!#MBGGX5xcGs+^^(hCC7}@4|QIM#QC z8UoXG`Ir$Po9$wiscN@MAuniZrMbSlo-H4_$)pX)h>2#1fK&5C;80hy`mQD+R12Y- zDMO?>gijPn#RLY3p0tkXFr#DYh3#K1>+QOPtgmG3L}Z#5BDrtJ8<3W(tj9>-GW^r_ zw&4dzP>Cx*W0u_Nhsw-%==2nR3L5^dIx zU+@@2(6o}ZL$ol2axjuiW;M<)>s|>3xWVff^j38yLN9nP@&`ROLTJL zSIG%dEQ*x^&`=G)Exqp@3W8c9Q3A@pu{}4znV^^ii*=fuNas)|p|H})WU=7gkMj2f z8VBG_YH|26>mELg{IOG5ttT{`BZ%Dhq{oit@O4EfkuaoF!=f9v;Fc9>jhF~8m0%iC zpTFSqD;Ye>CqT7*>P5^N!!&q?%FQ7je<>0}Hl~4DOjVNVL<|}L zduR)K?KPG&M>Y{`Mk>`3Q!t{19-A8a96_3fig$(`X|u-l8F{IQq=hx(o491j5c8Y! zbDB+R2gl<{H(GXl`|@Bj3qY8*s)bZ-H6y&;^f%j@pWuzf#K2-hfi087@CX<(W+Qp5 zZNO>_jj`rH3edbj+%@3cOd%L&59vGvH>-~`wX#sihJ}^mK51b=wFHj`#F7;%7KOB3 zo%+_uPRpSn#1xsW&0H0la9FP1x9N7iVkN2ba?YgE$tCfobAvb9Mf4SXwrPS{bl-HK zAbH}CS-?afn~gYX7!zf{2ez*PWM0RsO3grE!%&RvZ>gIb?O86aSPAliDfOlzo!fnk znPaDzcQ$FcCT?|sE_B@j>5Tx9Dr?2^&vXxB0iVb%T+I*JTF}AS>WigOuuQ@n=CZ1t z&5@-zPt}xXgYJxUSg^54L50Bqz~o>Dn-e(utJb&-!p18c-O8;1fi-Bs8n1en51w^c zIl4tr%J%ghPZh3z-gMG=uxji6gvy<~1VhM@)yEFH7j zV)A1ila$F~_*hf9PM6}D39!%V&<*Uk8@8@-G(WK=Js76sSbKjXJuJG!CT03m1O?QU ztAFGXiR*j%7?Q6H`vYpFwuD**hFTjk6E@4;EKbNlWOr9FGofuQ#@w)LwL|_lq3}%`BF8`+U3EV*Bwavl;N+S!JkiAD2#rwbR0bXDp+cS5>BmxFY%xs)kS1t>a21+_w;~Fh z5DyS<*8-zuDIF)|)KW4FHm%YuFjz3m#Udg>A5&w;LSWde9p?y|cqVk-xxt?QxL3L(_)hODiw3nJhpf*X|%<>xxVo2i6zL8>?p;Q_tDctIMf}y0-R(i zUIPBUfzLOk$VMKuKHXE9>)}nnE&in`8-Tk!%_`f?k0gQ z@KsEt_3-TS@>PYqAI@nwx+$^WS|(crJc0+24e;YKIJP773%m<=%D|+dr4Ht~&%E)o zd88Qsxy3o_;dY{S9@jYN2yaOWyv1(arR-1Kz~V^`#0P+wN`R=d46)cmh*GdNoW8Tc zP<2q48b^9Eeq$0Nd z?ZB}FwCA7(r?|8I(h?BOlqVRttHBDGFsTnPa`BR*&WHGNd3mF89Qg4)D z*dN1oyNu}}F+-d@;?o8vqJG(?ky(d&NPpd9@d%H9DC-oo^W<(mSBWo`VPR5VV%4l~g)ffHQA zHxVshOh66;cM!WBVy>{jF(=2*Y_7nA#n_k@3FNnw*O;g1E^kaUIs;oM7rwxqV$}9N zCO9;fa`U_I^yZHzVxXFOY0aUQ5%|RYA>szl4_>jBqEL50Ftsc1@YgtzO_QIoC_Wjm-ClIfH9^_AL_$P zwJ%NIIK>PI8~;c^=Rj~4)sZ!TLi^TTsElT(PGaFCWlRDSW7x=gQZdYz1c!O(EN`{o z@B?pf!C9|PGbB$)v7-a>7#p~6I@unxW9m?JKC~qj#c;~byvP`UBLq_74qU$J;3ifZ z3NxZQ0h1=f4b+%7u{)(DbqDv`i=#w&2oAU6Qxt2N;a0j`Xkl40ZGDI%OLX4+WL_vI zh${*^?{I?ZYrZB|Xp+5Mc)F2=3&_h&g9v*267@nRv7U-Ah5(<(yh=U22F3M#<%p!c z6jiziR0zJ*Y31zPNYJcnu|`!ovaMsXs=g}cg&aQK%y2w~4h5mqIB4!?%{Z>vNu*X} z?05=9$Cx@J<7vgpiSGGE7^r{Xc)#|nFoPfw(<=L<>_&JjK*|TmY^_WNa&@?Is&8)4 z7I4fJh7t}bmN(le?z6`xe(O%&4e5Xx9NtPdHJ7@Ekdqj=Nf52(SiY)L%J%4E2X;cqy&6!l$&B&(FAGC(VQTdTFej zhHH_9Hu`c={r)Kuu57LWoN`^*!~vWVBdhZyE7Te%5uKYd<`1oRzaY;kbR|u(Cc@b( zsmiBinOnWLxTO_AGA#h^d%BGwZ_%`K4XMKb=LIvZ{jM>>V)<(+Umz5am>YoQ`~;Me z=|gF>SZ-zjmnwjCrtM89y{D})`5kVJ!=>8h0Xt#p(C&y`(x|wAGP~t1dUn31ExRlT zt=fj%6FBG?#TK(Bj6LTICaX`DJ}E-^$x?*6{GkxH$uVVxK<0FgCUxH_d|TPpN~db_ z8&PE%Jhs7;7Vs>N9nGY+!D<&dp%%MUgFl$8KblaAjOSI5W0Ui1TpH^$bt3}bOUxvZn*E^CHuMRSh{o?z@|n%&U#ed`*U*m07-kGz{+56YQg~q9 z($YjWONCh<4n;YsO$ASU-;)_mrB#bz>bS&_(i)TS1mxPEIbQ1v_{++r8Ya6)NODna zzAhV3F#5cu>!9$z%WToi1PpHPaZW$B)&h{k!9f7b--)pcQ0j;9yNQ4cBkG4CS4lJ& zZ=E?{T4aStZn5~?www>=PRGfCh&iIWzSNvy%G;OT#5wvpZ`3r-2=g4_zcBd4d|VU{ zLvrmOsndFN4lvy-qvK>LgfmNg$qY%NLNJ8Ru(Q`vYDncIaB@I_iFvO-yPBgB?5s~2 zagq_A2Tl=2rb&jy*<}$5Hu&dY%)r^Gs%q>OT&jfM0sP`lj0G@i1s`pJv@x$ttMZf z+77JmAd279l+>a_>Q6eGCWVc=9>No=kulDU@a=9P)KDp)hW2BrG*spm4HfmtgOzsj zHmC||kjTY-($;ydaMlHyg=jbGESHB=kzvLN{7HPEiba)2NK59!%^F9-mAQ{0=<SF4)ju8*Ix-A7FV6BMJNRkptW(GS28jT^*-!xYTEx zLw3t5xvYx`H1Z>j%??FBc>ID2`{%g3dr{HZV1KPrN=1%Y@`OYt0U+MyWmUpot>#o0 zr8G>7w3FJ?TcAf6&sZ4a+!><#M825ZX#gKTU<6Ofb>pn3BY)GOgL&#sz2{&gwx`Qmnfd54){pYMnqc2S>bP*rZGia;zP1ba7NpP(udR}Njm<5vroE@ApgK(Fjgf1 zGFCN$I&HJk5^Pf4FRwWdN*Ekk7g%q)Ixf{ES@vAPt#_GS6Q-MQ@D({s>cUV6rJ0A^ zkd!7Cdx$;6>jXkgm_nEtfmKT~jO_Fi%$boJLrdtLE`4p80^*lkC`@s*yPTHiO9k_ugivMzE)R`DR9M~bRNw}aBZKtIQF0NN zlYcoHUjJ^=0&bGir8qkQV=x_N^*Q4EauzSTEZ}ZRnhXVPB(B_H>@2U!VpJWgk>qM! z4U?lu9(d_Cwj(OC#ZKShdYTv_kq8FOkqads$@%ygHf7*`oZQId6O%;32*HnR zY*T?{rlzF|OuLwHngi;)>i(7tzes}1IS;F9jUrsR={{mgSSJX@Q9!}1sSot>A#E z|6@{D*ixKr5YGf>1F=q=VLdY{@09nmy)Syi#YfJ7I!bbef*g^Mk>F3eO&o8K@Fud4@zX*=qua$Lejb5xmH3#) zbSExP3-2?z01YrsHUG%KxDUs!Gzp!S5#R(AW|-+X1JhPBu1I25V0?zTj9Z%yWZ z2FKdi2XV*|{&Ic>W=CbXGN0k%jbUp9MLlnfQ;aoF4BX?PA=8WZMc zqtTLrXkngg7nX&zA2-2%jv42o1(4JTCs!dBzRxpib9jNR8qR zp=aTxMC!C^o>C6NGNwI^R6}tie``BOJ~xXtbsmO%j-k`CSz3%8T8U;*J{p_B@?#@9P@rtts?e_*fJkF~@U6gPi&!}ry@7>4uT|7XlVT10+ zK_8FbYS@SVhYtsMrO&qnK>mCV!;}2Ri&eH%281abxJDc`9F`p3!Ot*6*?9*qgN5F= zC{HQ%Cvuqvega9-5o6BR<=_^6Aq^kGWiBC26XLFUzKf0Gu8YX$Sg&FtcmmHd+zO(` z2#{<8qRra(_<-cNzoI)wSjlc5B7d5Wl8@|?L~?VRkJH|YqnFnnF*VKq@nf6b-G=*W zqc)`YC`+W}FA|mr!Dz#&)+l7Mh-^SSdVMrH~o3w#IM z$Q;OkQxX8m1+qY;k(br3A_#4EOD>I9yqa7II;ydw=y zQb5z-LLnIg&3qcTG^G!K)yOPFs&a@V4LKypbJol&!=$O$QD&sk@Fc|@Ki$+es*DDOT6SkD8w@i{QV86B zrc3{r3t;IqQ#P?_YUn~rWTZ?nNvei#ubI4!B;ylecv=iHT$b|MMMky~rl=$xcAngm z1dW!;Flj1!iKLm+odPHHF+^}PO^!;^g^1|QWEqB2{n7h-7-ACFRFdvObNZ;91DBA= zPdVUUB4kxee){9X-)S!SU``f!(wH(Ze}b2*85V6acl@&~X(stEWtjAt*N_lV9~rPQ zvRWwYKA>y3ajFhA$GelXe-Nka2q!n8hwki+f1a!BmxK4sZu#eV^Hl%e!a3P(ANGsR+C+dNJ#B`Mh}IpnoZBV_~El9CTMH8Ymvnjqof zcTw$vH<6t7kM1C<>fC5(#c|4pj;1Z3s8hqJ$#m-A-7^&Qe~OW-O>bXW_Caj^Q~XHs z|LeH<(vgayV<&NNU?xmJj;`PTJ4c6tdrK%AKu;UH&+U|_z5hqQ9Uamq1)ys8tupbf zmzF&PIezy_?q0o(5$j^vH0)s3F+ApjhX5s4_d*FjFu?~Dv(dKX!^TGxw)6_KLQMsc#LLe( zLvO)94Tlz(n$?8UL~9^Id(ruetYF+ML)@@#v;rlf1Sax^U!9>{cj;|hh*k~aC^*O9 z%?d3CPg@zg3#UBrDQ&TZ2M0%s#|;tY5R&9<&O4Djq=*Pkhd|G8Cxk1ahZ5Z&s5gA> z{AFXEjy_5`sTQlvT1txBMIx3VWC4xzP)|O&K|yA*d-`9Hvu4d2wK?|d4R1d4@X|67 zjz?3|X}F7>O-E$%M&(%JCeWnDap9bJRiG;`h9(#mVN)1sRsXKhdj##8(4GnXQWbNL z*f}Ttk5bLXLQoR_qf}Gf%&V9GQL1Q^|52)m^8Ej~QnhW>UA7~RMlIsx7y|6bbV;@t z)>D~*LNTIZqSVB=!;Q}CB=ROJ{(f(#;{ShF318ldd$0rmzVmb zrITpi|0k@V^CI6DI4cMTbra)5H&`e-&uzhB#i9Ut7PzbldyxDK4HQma)Xo&%IadC^ zT`%6ssyi2W)Z70wx3{3#IkM$jx^&Yyc41*N;Cfx*vJZzxf)pR)u=u00IJUjwf({F9@ZY0B{^+NRcNKU<}>(de0>TK&UAA2!$M^2RAtxEtNX&!f%|pLTM41 zQgGeC{_1yMpDSGEnkxuoh76DpEwAkS>p3xL(SN#g$@MmQs1T)fDMC$VFa`)j%QAe?zR|2457Q9%gHGsB=aW@C@hMN#2UD! z2n1eAnScRs&w$FMV1TaJbL(7I5f#0xS3H*64tm4 zhCre^GrFCg0VuxbFbCo!TmuG=>TGphg@_H;vAiw={{F;IL@0ne%gW_L(#>7c?!RN? zPm0F`#%$(L;5RteLs{U5r;FbZQaWKoSSF{gMZG9uDg#4ed1nSYPiTxXroLduqGQ(@se}i|l$?!IyaCO4hoGwMtxjt6M zUMYLVv>SW+m4Yfm&tuo=(IIK3Bk#E`JQ+Blt~9S)|BbxLj+O!=U;jm$$%Mi6 z*strqA-Es&L96&&@T+IhM^a%UWFbojp=z8^uo-4es+aI{`Eij&V2Xv$iLrla3rqa5?sv#_2DRamy~{V zZZw6fiX!0}sBb3l8W~`=De?CRje~i|Ls-G%H$v%gWP6>%J;I4lKT{%J$>0Z-i`Omq z?G10*^CKEwHR$wwU+OoGn+7y;IIET}4g@~Y79nqVA;Qm+?0t973N&Q=5#|EVjZ8IX zHzq)Vh!@;^PJ$Ug3H%(az9Cu|5(;p?#$r?hYFdzh;IyVb9g;*Nj3C#Z9w+B07eLd} zjL2vRVEK(-9NPweQIa`jj!g#GTnIM%SQDCNFgl8OUC{(ZkkIEuNZ*u*8Zk_t8gxnl zFcP9m?C?1*$dEJBqeoydrE4R~45J*DOP)CNpQZy$N-I2lmFQ89j=yt?!m>1|6+A$c zL>BG=fWNNplx3WgGpH+f$}UP-!ZU}mApvfQjKwoXHX@`6bxbmdz$pDVm>zmQh1FQ) ziN*+{oz0g)WBfjc85oVZ(s3`i{irlIDu)0%?(^-sT060 z#$*OO7&HJW6?ZMOho9^f%yAWp9(^;>#*$R5$~X_qRx}TGc%l0Vo;kYf$Pl-1PC$H7 z^OR#4^YT5AhC3eN`}H0ys2PqNyI^VoOp1%UH0GfQ`6M0R1_|WQS+@s+@&W7dn1EpY zo&`%ta3U{o;fm$CUThc1j24LDa@-UTjEW3k#ZTQDDS}9grbHNgaVZtaIO}g1>?!0_ z0S+nFYsg)-#?U+Fd|yn+ZSgm{>KSN(U&IfUkfN|n@n(-th${|vo8cV-I7SfW4@_XE zSdG^8HGm)vv z74Jl&rj2w6LWljU@d-PbeLKJ_kbK{=H;xM^?aUE^L#ZM=_;QZ*$BF*<6hCmwSAOGp zh|q?v)QOlmke0JkUDlc|L&Ws1q{+DXbBmor@>E75hLFNcpw3VEZ$v$5MYNfaCd=Jo{&GunMArcIl7AS23;e| zQ=?md#YM9Qp_aASX=wYFG6Q3=2KXyQKh0~N0x5)rlPc&np%pM3^bw97!1&wuMN?KW zF_&u!Nb1-&21bUr##Bb)STx3v(|#_OylEP^W}g}))UjFLwNS-opzJBSeBR19KaVLP zfNz&S5U?btp*NuU8B0UdoZU|zX2m8Bh9l=@p0%PE-7Y&KsUiK#ky0bE=|^`9{xc%pWl?CN!*awd&k}L;VTopLqW(6p30|H60B(P{f=z zRl=0eXT_r)jx!o%5Y8xi3^96%W1@EHHL?!EWXrf_2*p40y~T4d z-VEn48KE#Dz8|}&yPj@~nBO_<^&Y2A+ya5`f+w*=m2lPzJYn=n+FQYlesYCle@fu% zLD({to_4S`mKr$>COCGjrO{of@bqa(=LutV`5fBS_=iB~WKkyCE*Dx2&r1Y+O4=_` z!j3Q%Sr6kiVVX46#g6V2#(zqMiU8;oPljs3e~?|(1bg1~#SFoOlH$|+!VjDz&}80q ziM8r>5FEX;Y5*{Ff8HHUW$2gyZZ(&GVkavse64ioD(4O3kQQe-7}S|ux-9uTSI#*V zU`LopsRQxCIkaH0UKfwpoC)7dqU4@kPyva3kCf2AilUStEeNp1_7(B1d~cA!tVaZW zJCB2=$Ok4-Wi=;I+H?d-TW$PD&f>~A@ z&#ejNDmK9mlGYzQxcY6>jkbY#2JIWW2I;SRr~30$ePZ_+{t6k9euWd_b^7kr&|YMv zp?woNFrj!+%;1Z_kOuU<3H@L~KSpR+myuw+!S)qaXU=xX>gDFY3@FoFhDeu)vOGK@ zzf&mu>pgFl#qA@(khY584a(N>(ae0sF~7z&mUt0D`UD$*etvt?Ni<=EYGoLKO%ow6qZ&g}2%6F-xJ>v2an$g)2+kBeL1&UDh+Ob8Omm&yAZRUayVMDf zL=ps?%ACMckrTKfaS5^|n})neqEy@jZc3Y=TZByr+GI^r7@}6QYq?d}G`b;f2{IKp zjmkt$qjI6sI8*F2K_++_)et?6w1iLN8{*eQHUvC~8) z;Z3JSpoK%5U<<`g0T;3sK^KBnfftfpf-h993LqzGRRluVt`LN#MbdIiXKihba+I(UYb0?iXjiYRvJDByRhDByQ%C=fhDL4j(g zegggp^#qEiwG)V*P)?EJ3Ec#`r&JS_NFRGKI`ApQ1QyTGOQ71RmVn=_l>)(2N=fLR zs@QW?5;*Qq1;Fl7NI>t>M?gQRjvUSIB0Qmtz+#s!0{R)M2vpC~M4)?~B4Wa4>mhxn z(9}a92=Hte_j^z4A)?u>hnQlA9wPiMJw*7OdWZ;~)EI? zPb(pi?AAdh|nGosn8 zYM5e&t|9y`WkdL#+J*?8RyRboOWzQFx56QcCp8Wc?N&KLv0LX5-4jZOOSD7l5dI0Z zLnKe@9irN$cnH5!^F)FtRF9(DrF$~6UCM_@+r^)-JJb)McjzBNKc#>?&F&)XRzbvK zhYlk2(@Kb_o}-0`?pbQc5k6ZFJ>{jAQ)rnM`tY<80?BS2L|o>n^`+NE`X-l=o~{t2BU zWKUFyGke2XcqL0d|MF0rn|v<4AU9zgyRU!46de?9-YCM9)z)AbXacVY;Yh z#8jDufhHXk~ zMqmnSMw7{EhRsFQLN_JVrrKSZb~(+sC32GCQYp=lsgPz!Lq?kvyRvRbXqG$`&Ji?Q*oZP^HDWU2K8Ve-Sf@~fZr!WUmp#A6J8 z5x)yZ>|#Xy(NBNK+hRU3JOe-d(S1k%`}=lT7q?}{U&^wh?fYc^4;3g>8POSWJ+~Vl#ac;kmv^5lwxQWYAs&67V)i)7r=o^i*vuNm>m?YIV5p3$4 zh;7j~5#Oe76hYKCB1<(zzHgN#g>A@_#-$RaFqup#OfFRlWy+PpXOg8b4cSsSOS%-g zAzuw#L&6kAQ^piB8@vkBlrx1iB~9ToSyPx?+HzD=-kKEcg>IKQ#U&951yAKpAyUax z2t)Q#Y+F_h`BTYK2~-GE1{Jyd=`PQnVaMVofe$HLi zoYMbxN&N>xsXCoTh2B)vS+SnF8sVND0{Cths;qc5V~Kc*G^r`*O)>ecT;C`NcyaIv zX$bs!_kb<31ag`TuoX2Y9$rx0=|M+6c0`g`H4*Gqm)2cUPslxu7sK}dSq@Z0JFtnT_RVAV4m>}MP zpR3!Xx|FU@Kq{pt3LUG$4O4wsmnfY`F#MC#69G^o*b@SKQ?$KT?l&1S?&l1TgskZ; zi6Mv_s7Am~7i5#t(PO28Ta=SovHW^_eMe%#%uvYLkIJY0b2}S<7jpT9mjyqUD+-Fp zh2dK-Ln;RQbq!#R$d5TGg#s|3vE*(9`GiT$Mzpi*l7{LjP^hBREbj%r|e^J}$&^z`41e;tpY=A?@i^3_)J+(ag;N?7$(KABX=* zp`3+*IIW49n{+&P=-q@lR9KU@0w$~hKNy(g0#*r78H#U`zdR5`qgiz`V4`X@7$SvN=RJ5Jt>uimao`_HD_LR=?E+P5fCtJneigZwmw?_N6dD zh)*91HgU8zH#e+dQD(Ui+yUj6;(1Errfb7{bbK>;z&Qr#mttWR`4-Qt!@A4bY-{!xn;&1vV$H}e$(pmFSUV80qTrt%>{S?( zkP`rg(%EcTst&%uOB7%b^^$(*pivAmW5Rgyp=_8&)SqgN*NoCb>dpVUU3im)*K160 zg4~rILzPh=ru(D!cVz71fMMMx)HcGU>9?iH5H}Q73rgC(+lWID9A+zr`azR}@qF** z@l_6`HG2-bDr;z<9E#UCzpShKPme&ep_UU&fo-IsBn_Zjisb1T+ERFW*o3TK&x&Jb znT22`i2GqO|3phK6T{X+j-gB&Ql6({z}e87j4)?tgwqKj(9$y}>sJ}PS{Yy-v<7@x<4G0-YfE^b zR2LCKLD&Np>w);2BfHaz1HtVBo?(dQAHKPy;MQ$GQoyA8R4%d3Nz%;=SpNcJibb*h zj;df5>|A6e&+&^J)Qn$5^v#wNU@ET0PcUJc5&^AAt! ztO17@@fBW761FPYd|TgLSD1?ta5W|+MMe{ucX0)dSiDD@BEW=IQcVG4I=O;3ut|Rc zi=zchFFH1AK@Uwep+1;egW9{RnhB_l@J>NwMZrYNby9c=JBSw{A_-m#OvfJj{2Dp> z5v%AZyEu;NDK|7M@z)hiXx1fsrSx#DufRFhz)qhA2%c0-Gw0IaSvx&?=XiPG8Z45L zngW>5jeic+=f<#to_LJ~U=R$*9k(^Rt_(@5bbnZTblh10hg2HZs-WjLiIGK?sJ z-H2W+m>*D0@@wid2Uhh%1_}|mNN;6~7$KdIVYAs4Lpd4j+&U?Q8V;VZ8Yfgqk2%%O zHgOy`vDw^wMKd<}f3(#cn*WHj?I(WN(s>54FJj>$@1T8xhh6y-aR{Qt!Zs1k!hYYD zCPTm@k9W*r8L_Ffb~aZEd*x_e?>Q2jX(j#HfFAz9VCE3mIlZ&qp?-1%Gqj;JKie4J z=s7R4?3}^OrFT{wPdRVhgHW9n^(zS>{6G&BF_q?{CX&fou`$H8PE2ledSNbQ$4$f9caXa-HF|maWUV8#k9V&*d)I@vR+}J?LfN) zUVF8nQks3jb0m&!^Yis-T816Lu^R+CMPqk>G&y<`Q{k1fX4)v$o_uR?S1{VT&0lrFO{$0dw zEg8afyt_siN9B4+_FBrEwr`h*#vltt8q>S5s~wGvpz-u>X3AE|1^%?@>$OT7)w*0j zqn|yC)^ogiYJ@0`Cav!$6L-l@r?%2^+rZXu_LbC|X|Mod2X-!HGuN3%#cH!%mpoDK z%vHz zTN; zl5;bBb1!P{&WI^35|&j9;fiK!EQiBt2|n_4pg0VvGrX|Yif_-)rEJGvlvC&%vsGQz zR)aZ=Pihe?nX8p6zd>2U)zuJ*h{;EvNATuj+Bp0u@we*}x|OColuR2#xMp=RrxOP$ z@pgH~4@YPQr^_0l{_Pp?m^`XqS78a5GCQq*uhtWD++$TWjn;CS3Qtxuvc?Ma!kBre zICyF+AJQS2-?FrM@|qHbrME_O6<_Z;u59QVjn%+kR=E-jB<8enLr*H`3@6*gimI7` zw;aeNC#14vgyhg9GMFuYZX0txFI*i#YdW)>?lpkcH0Q+wCV*BnaHN%*EfdNLvvM~9 zzsoker3aaGBh)i^qyVM|d^Fs&p`*_E6t&9p_}ACm+e@ z{5^oNz3mCa1B?P9?Bkf8yuH)Rl@ggrm& zl{2rFc;r3K4WPgBLO-ckQwe`T96yv}JcS!jU~vf+iB{}*;Ds!DJF}=zkqs7U+w!YR z`KF^6RSl$CI5%)v8>C!e{kxMcu}xF_jE_HsYUE{d*b9EDCXDreqf&;9#isjK(sRDPiP+_;wXs&?TS*$?RR<3}z3KnpU<_a>-qMwcyQd&6s z^J++$CEL|tDP?P$rDVIBEv0O2xs+^I#4cKmiV!E(&qQfVy+Z&!MEUn^0$oIFJW zS2-O0$nU7c;nR$hyKv#IVPq)jR=0)MioWF?zE(63jyzwO=WfvC2-bqiXvo-(xeg)H zq=iJs++jk-rZG;`m)L^A>`EpwhF<*^=M*<325bOSbO2>GpW`SbVRjSp_;TB{A$=>u z-(kMtXx|fb%ZXd7?Ih!DhuD?CIV9dA&Zx--8ss_MZ0Fy9u@Gn;>}$evopDLq1k=!P zRbmr%Cl&S8vsw;EN1tw9>Ibgo?o9cy+h>scf;(ut$+%&6lkjNV*#=Id?Ian`E9^LB zBq9JA;`JWhN#8i(2>bgDl4qiitei!zuh^ZFF~?c^3Ctt|3cDwR($LFcaIE+-!Dm&n z6sVmW3xtIKfGp@n+mwqLPL;WR$KC1MM{|-*OD8pHaNf_T4-F+8xP}|ewR=wTdhafy z2(Ge5>6?U3dp?7(WwLcgKwMYfOI$g7Zq;pa(xPxdJeLVqi)1cOafjnL)fC4#a|G&4dIYW_xV*A# z;Qzib`W$MCvVd40vkq!>Zy4&_FolRZcP~gMmZYhb$LQR@wxq0-BfL5l1m04jUnosBNK*FKYie;}# zhQ;FV@nl{8*_JKg>^wcj%8*f3Zfj>VTG|uG7U)mONFN znE$J_t7&c6~{Vv)@H`0WIDXYV{8i$LBRXYV@E({b@s|QAIA4O>VYck)? z|9p9}EDHSGJ<@0!-`>f2ula>(-7W_5^8EZfZX}7+cS9;}kyp^-8rQTY%AnD=X}T>p zd%EBq&20vm#00~UCTZH*NcGt5C2@m`l$6e{uHRjGo&NN5xjAue@f8+g!xxiA@3{&& zH#1SA+3iA?CIw<|OS;Ism%M_m*2uLM`oa|=LZ2oK3+Epulyg~5mgT-$uJC}0QUmZY z{TE(XUoafhf3iSUi2l2f$K|x-8g1}Yxi~3Si&y*Q>hbcmOC>T52-QLR^$gF^tFrvU zD4zh4z5s#@$S0n_f~Nhtr|~L9OyVm@sxG4&&}+1cNcpqV0RS#K95Z6cdz$f%&8O)A zUn)V!zIgTn?}~Onh&$bGyKDc-=I6e>uDGKHzi`kkam_t)-}q2 zV=a@;?+icb;aX;GV?r?^fRt+B+qOEpeV6WGW5g_CNBmtN960ftPDGK6@xDVWX}HE4 z`}c^P3^#?bLv~Fe4QU<)F-1`jr6G-oqkm;v&c8BsDdcp4co8sm{lHR8*UNa1PF9~M~z^mB<_KRVHaz8x`2Ftmjqdx=*Q+ZKt zTY|*t!ph!Ca*zu3MKM*5A{f+wLIAKk5D|GJyS z$|alIs#-5dCAh4mBy4MyB%$2kAb~0^rG(qoX8S0dV87tvN(&(z>DCTkJ?I?^TwZ~_}8mk|@@ z2|gkd@nniWn3CAcNW|lslnm$t$7}>Hk_sj8jT9K~d6u%aNIY9Dj#W>b859^OZHb$y zTb#RkppTA8>54JtZ;Gl|f7QX>Y_LAMd1z3DKwXM%Lhud9v!_R&DBKSUgV59aw4QsJ zO#Tn!)4`)!#2~m>u)eX^{Rio=J^dp=+j7i!1FimrSV|RaB4ub{3r(YlO(dl*MsnbP z`=_qi2hS)DL+rm8v*Qmete#fwy3j>E)}loBMK6eM9QgZ)$H(eWMFrq$G) z>i{w7t6kmSNpfpf>n)O%nbTqYQL#r+H>R>hZGpNQQ@E@M&btif?MCQzd;bFARqT%D z>U6uE;q?M3R2qb%<5>-l--N=3oQ-5Y#z_dqS)nQ;sWIyIX5aq zOxjzx|G14%fioLt<9C&js0hH!?M**pjzPWD9ZHryIV|vYoi9J#EN$UZGr8;(56O?S z%c^)Dkenc3WjS~=Tc1!9X+P*hbR&xkGNg+&vyy)+uYUbnZ@Zf_vzp*uu^a_{(v3Kx z0tep_!5!t7QACX8KAaAu9}rOq5)p)|Q zO$imKKaZvcxm~il*mAK%)KQ*F)}8-;(wxK5MD)`@St5CQ$*Sba?rQLc44a=uQ3+(H zRoiB-BDnLfv(;O(rJmV#yjRdZUM18w>BJ%hTXD zovdOyJD8esjX-VHibXBxh>sj$$GFB)+&p|P<_MNlOf}?|yE2zGN7a=qWF*~H7qUr{8_j^lHZLP-HJgXw#Vf;ZNTo0cQ-V@uJ6KuLcj#)@|j)hC>}b zl?iHdZt}SVMq-8zpo5+0t^90{%G&xWap6^_iXU>oB@#ci;iTEKS=X-Ipj7)iNLAN&`2gu)fXlS4(DP_W<3c0+ z9I15Z=s3hydLJGMjt9Brevr$pUir9O?qQ2jQJkG+k1X3ahR=~dDt{TpXR{o9!n>uM zCraNb&A}5xB~^RR+D@ABFmkUYLTvp+8g=tLFL&bWQ&wZyK48d~ruWfO+ic6tjCC?I z&MKU0BnPVI$W$wEGo*<#;7)_>w~5Ifji;nHDNTInmAYrZ-fT_uz3DQ4pfD)Tv4op` zS1j^r7;i5kqqG{$p+|HyjL_j%zvOHIX{MKxL_GZzdE7TS4-WyF_OKZsIlo0I&;U7m zd-B*gzqqb7GlAXPi5E)cd(K79F>k(0L|mN8g;{QQm`;Oup?X=sR&|GQ>P;wFB(zby z+`)Ow>`m27h$jGJLC!VunWG{hE+I=XL=wi3jitP8zzL4G^HbB65{Xf1dHNM%ykmrh zW<(3^#WF5yalFuwte%$0%u?(}3q+J5)KmeRKoe3ag3TZY Z|GH5M&NBjBC3_ubGx?kUn7?Fe_#apDz^niO diff --git a/rete/factory.go b/rete/factory.go index 6023444..2d1eb01 100644 --- a/rete/factory.go +++ b/rete/factory.go @@ -2,37 +2,33 @@ package rete import ( "github.com/project-flogo/rules/common/model" -) - -type StoreProvider int - -const ( - Memory = iota - Redis + "github.com/project-flogo/rules/rete/internal/memimpl" + "github.com/project-flogo/rules/rete/internal/types" ) type TypeFactory struct { + nw *reteNetworkImpl config map[string]string } -func NewFactory(config map[string]string) TypeFactory { - tf := TypeFactory{config} - return tf +func NewFactory(nw *reteNetworkImpl, config map[string]string) *TypeFactory { + tf := TypeFactory{nw, config} + return &tf } -func (f TypeFactory) getJoinTable(nw Network, rule model.Rule, identifiers []model.TupleType) joinTable { +func (f *TypeFactory) getJoinTable(rule model.Rule, identifiers []model.TupleType) types.JoinTable { jtStore := f.config["jtstore"] if jtStore == "" || jtStore == "memory" { - jt := newJoinTable(nw, rule, identifiers) + jt := memimpl.NewJoinTable(f.nw, rule, identifiers) return jt } return nil } -func (f TypeFactory) getJoinTableRefs() joinTableRefsInHdl { +func (f *TypeFactory) getJoinTableRefs() types.JoinTableRefsInHdl { jtType := f.config["jtstore"] if jtType == "" || jtType == "memory" { - jtRef := newJoinTableRefsInHdlImpl() + jtRef := memimpl.NewJoinTableRefsInHdlImpl() return jtRef } return nil diff --git a/rete/filternode.go b/rete/filternode.go index 1d424db..2399eab 100644 --- a/rete/filternode.go +++ b/rete/filternode.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/internal/types" ) //filter node holds the filter condition @@ -18,13 +19,13 @@ type filterNodeImpl struct { convert []int } -func newFilterNode(nw Network, rule model.Rule, identifiers []model.TupleType, conditionVar model.Condition) filterNode { +func newFilterNode(nw *reteNetworkImpl, rule model.Rule, identifiers []model.TupleType, conditionVar model.Condition) filterNode { fn := filterNodeImpl{} fn.initFilterNodeImpl(nw, rule, identifiers, conditionVar) return &fn } -func (fn *filterNodeImpl) initFilterNodeImpl(nw Network, rule model.Rule, identifiers []model.TupleType, conditionVar model.Condition) { +func (fn *filterNodeImpl) initFilterNodeImpl(nw *reteNetworkImpl, rule model.Rule, identifiers []model.TupleType, conditionVar model.Condition) { fn.nodeImpl.initNodeImpl(nw, rule, identifiers) fn.conditionVar = conditionVar fn.setConvert() @@ -60,23 +61,23 @@ func (fn *filterNodeImpl) String() string { switch fn.nodeLinkVar.getChild().(type) { case *joinNodeImpl: if fn.nodeLinkVar.isRightNode() { - linkTo += "j" + strconv.Itoa(fn.nodeLinkVar.getChild().getID()) + "R" + linkTo += "j" + strconv.Itoa(fn.nodeLinkVar.getChild().GetID()) + "R" } else { - linkTo += "j" + strconv.Itoa(fn.nodeLinkVar.getChild().getID()) + "L" + linkTo += "j" + strconv.Itoa(fn.nodeLinkVar.getChild().GetID()) + "L" } case *filterNodeImpl: - linkTo += "f" + strconv.Itoa(fn.nodeLinkVar.getChild().getID()) + linkTo += "f" + strconv.Itoa(fn.nodeLinkVar.getChild().GetID()) case *ruleNodeImpl: - linkTo += "r" + strconv.Itoa(fn.nodeLinkVar.getChild().getID()) + linkTo += "r" + strconv.Itoa(fn.nodeLinkVar.getChild().GetID()) } - return "\t[FilterNode id(" + strconv.Itoa(fn.nodeImpl.getID()) + ") link(" + linkTo + "):\n" + + return "\t[FilterNode id(" + strconv.Itoa(fn.nodeImpl.GetID()) + ") link(" + linkTo + "):\n" + "\t\tIdentifier = " + model.IdentifiersToString(fn.identifiers) + " ;\n" + "\t\tCondition Identifiers = " + cond + ";\n" + "\t\tCondition = " + fn.conditionVar.String() + "]" } -func (fn *filterNodeImpl) assertObjects(ctx context.Context, handles []reteHandle, isRight bool) { +func (fn *filterNodeImpl) assertObjects(ctx context.Context, handles []types.ReteHandle, isRight bool) { if fn.conditionVar == nil { fn.nodeLinkVar.propagateObjects(ctx, handles) } else { @@ -88,7 +89,7 @@ func (fn *filterNodeImpl) assertObjects(ctx context.Context, handles []reteHandl } else { tuples = make([]model.Tuple, len(fn.convert)) for i := 0; i < len(fn.convert); i++ { - tuples[i] = handles[fn.convert[i]].getTuple() + tuples[i] = handles[fn.convert[i]].GetTuple() // tupleMap[tuples[i].GetTupleType()] = tuples[i] } } diff --git a/rete/idelem.go b/rete/idelem.go deleted file mode 100644 index a483736..0000000 --- a/rete/idelem.go +++ /dev/null @@ -1,18 +0,0 @@ -package rete - -type nwElemId interface { - setID(nw Network) - getID() int -} -type nwElemIdImpl struct { - ID int - nw Network -} - -func (ide *nwElemIdImpl) setID(nw Network) { - ide.nw = nw - ide.ID = nw.incrementAndGetId() -} -func (ide *nwElemIdImpl) getID() int { - return ide.ID -} diff --git a/rete/internal/memimpl/jointablecollectionimpl.go b/rete/internal/memimpl/jointablecollectionimpl.go new file mode 100644 index 0000000..47410a1 --- /dev/null +++ b/rete/internal/memimpl/jointablecollectionimpl.go @@ -0,0 +1,7 @@ +package memimpl + +import "github.com/project-flogo/rules/rete/internal/types" + +type joinTableCollectionImpl struct { + allJoinTables map[int]types.JoinTable +} diff --git a/rete/internal/memimpl/jointablememimpl.go b/rete/internal/memimpl/jointablememimpl.go new file mode 100644 index 0000000..ff4ffc8 --- /dev/null +++ b/rete/internal/memimpl/jointablememimpl.go @@ -0,0 +1,66 @@ +package memimpl + +import ( + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/internal/types" +) + +type joinTableImpl struct { + types.NwElemIdImpl + table map[int]types.JoinTableRow + idr []model.TupleType + rule model.Rule +} + +func NewJoinTable(nw types.Network, rule model.Rule, identifiers []model.TupleType) types.JoinTable { + jT := joinTableImpl{} + jT.initJoinTableImpl(nw, rule, identifiers) + + //add it to all join tables collection before returning + nw.AddToAllJoinTables(&jT) + return &jT +} + +func (jt *joinTableImpl) initJoinTableImpl(nw types.Network, rule model.Rule, identifiers []model.TupleType) { + jt.SetID(nw) + jt.idr = identifiers + jt.table = map[int]types.JoinTableRow{} + jt.rule = rule +} + +func (jt *joinTableImpl) AddRow(handles []types.ReteHandle) types.JoinTableRow { + + row := newJoinTableRow(handles, jt.Nw) + + jt.table[row.GetID()] = row + for i := 0; i < len(row.GetHandles()); i++ { + handle := row.GetHandles()[i] + handle.AddJoinTableRowRef(row, jt) + } + return row +} + +func (jt *joinTableImpl) RemoveRow(rowID int) types.JoinTableRow { + row, found := jt.table[rowID] + if found { + delete(jt.table, rowID) + return row + } + return nil +} + +func (jt *joinTableImpl) GetRowCount() int { + return len(jt.table) +} + +func (jt *joinTableImpl) GetRule() model.Rule { + return jt.rule +} + +func (jt *joinTableImpl) GetRowIterator() types.RowIterator { + return newRowIterator(jt.table) +} + +func (jt *joinTableImpl) GetRow(rowID int) types.JoinTableRow { + return jt.table[rowID] +} diff --git a/rete/jointablerefsinhandlememimpl.go b/rete/internal/memimpl/jointablerefsinhandlememimpl.go similarity index 64% rename from rete/jointablerefsinhandlememimpl.go rename to rete/internal/memimpl/jointablerefsinhandlememimpl.go index 05e3609..0de0048 100644 --- a/rete/jointablerefsinhandlememimpl.go +++ b/rete/internal/memimpl/jointablerefsinhandlememimpl.go @@ -1,19 +1,22 @@ -package rete +package memimpl -import "container/list" +import ( + "container/list" + "github.com/project-flogo/rules/rete/internal/types" +) type joinTableRefsInHdlImpl struct { //keys are jointable-ids and values are lists of row-ids in the corresponding join table tablesAndRows map[int]*list.List } -func newJoinTableRefsInHdlImpl() joinTableRefsInHdl { +func NewJoinTableRefsInHdlImpl() types.JoinTableRefsInHdl { hdlJt := joinTableRefsInHdlImpl{} hdlJt.tablesAndRows = make(map[int]*list.List) return &hdlJt } -func (h *joinTableRefsInHdlImpl) addEntry(jointTableID int, rowID int) { +func (h *joinTableRefsInHdlImpl) AddEntry(jointTableID int, rowID int) { rowsForJoinTable := h.tablesAndRows[jointTableID] if rowsForJoinTable == nil { rowsForJoinTable = list.New() @@ -22,13 +25,19 @@ func (h *joinTableRefsInHdlImpl) addEntry(jointTableID int, rowID int) { rowsForJoinTable.PushBack(rowID) } -func (h *joinTableRefsInHdlImpl) removeEntry(jointTableID int) { +func (h *joinTableRefsInHdlImpl) RemoveEntry(jointTableID int) { delete(h.tablesAndRows, jointTableID) } -type hdlTblIterator interface { - hasNext() bool - next() (int, *list.List) +func (h *joinTableRefsInHdlImpl) GetIterator() types.HdlTblIterator { + ri := hdlTblIteratorImpl{} + ri.hdlJtImpl = h + ri.kList = list.List{} + for k, _ := range ri.hdlJtImpl.tablesAndRows { + ri.kList.PushBack(k) + } + ri.curr = ri.kList.Front() + return &ri } type hdlTblIteratorImpl struct { @@ -37,24 +46,13 @@ type hdlTblIteratorImpl struct { curr *list.Element } -func (ri *hdlTblIteratorImpl) hasNext() bool { +func (ri *hdlTblIteratorImpl) HasNext() bool { return ri.curr != nil } -func (ri *hdlTblIteratorImpl) next() (int, *list.List) { +func (ri *hdlTblIteratorImpl) Next() (int, *list.List) { id := ri.curr.Value.(int) lst := ri.hdlJtImpl.tablesAndRows[id] ri.curr = ri.curr.Next() return id, lst } - -func (hdl *reteHandleImpl) newHdlTblIterator() hdlTblIterator { - ri := hdlTblIteratorImpl{} - ri.hdlJtImpl = hdl.jtRefs.(*joinTableRefsInHdlImpl) - ri.kList = list.List{} - for k, _ := range ri.hdlJtImpl.tablesAndRows { - ri.kList.PushBack(k) - } - ri.curr = ri.kList.Front() - return &ri -} diff --git a/rete/rowiterator.go b/rete/internal/memimpl/jointablerowiterator.go similarity index 53% rename from rete/rowiterator.go rename to rete/internal/memimpl/jointablerowiterator.go index 905afed..e25fe94 100644 --- a/rete/rowiterator.go +++ b/rete/internal/memimpl/jointablerowiterator.go @@ -1,19 +1,17 @@ -package rete +package memimpl -import "container/list" - -type rowIterator interface { - hasNext() bool - next() joinTableRow -} +import ( + "container/list" + "github.com/project-flogo/rules/rete/internal/types" +) type rowIteratorImpl struct { - table map[int]joinTableRow + table map[int]types.JoinTableRow kList list.List curr *list.Element } -func newRowIterator(jTable map[int]joinTableRow) rowIterator { +func newRowIterator(jTable map[int]types.JoinTableRow) types.RowIterator { ri := rowIteratorImpl{} ri.table = jTable ri.kList = list.List{} @@ -24,11 +22,11 @@ func newRowIterator(jTable map[int]joinTableRow) rowIterator { return &ri } -func (ri *rowIteratorImpl) hasNext() bool { +func (ri *rowIteratorImpl) HasNext() bool { return ri.curr != nil } -func (ri *rowIteratorImpl) next() joinTableRow { +func (ri *rowIteratorImpl) Next() types.JoinTableRow { id := ri.curr.Value.(int) val := ri.table[id] ri.curr = ri.curr.Next() diff --git a/rete/internal/memimpl/jointablerowmemimpl.go b/rete/internal/memimpl/jointablerowmemimpl.go new file mode 100644 index 0000000..0c63091 --- /dev/null +++ b/rete/internal/memimpl/jointablerowmemimpl.go @@ -0,0 +1,23 @@ +package memimpl + +import "github.com/project-flogo/rules/rete/internal/types" + +type joinTableRowImpl struct { + types.NwElemIdImpl + handles []types.ReteHandle +} + +func newJoinTableRow(handles []types.ReteHandle, nw types.Network) types.JoinTableRow { + jtr := joinTableRowImpl{} + jtr.initJoinTableRow(handles, nw) + return &jtr +} + +func (jtr *joinTableRowImpl) initJoinTableRow(handles []types.ReteHandle, nw types.Network) { + jtr.SetID(nw) + jtr.handles = append([]types.ReteHandle{}, handles...) +} + +func (jtr *joinTableRowImpl) GetHandles() []types.ReteHandle { + return jtr.handles +} diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go new file mode 100644 index 0000000..7d8ffc6 --- /dev/null +++ b/rete/internal/types/types.go @@ -0,0 +1,77 @@ +package types + +import ( + "container/list" + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/common" +) + +type Network interface { + common.Network + IncrementAndGetId() int + GetJoinTable(joinTableID int) JoinTable + AddToAllJoinTables(jT JoinTable) +} + +type NwElemId interface { + SetID(nw Network) + GetID() int +} +type NwElemIdImpl struct { + ID int + Nw Network +} + +func (ide *NwElemIdImpl) SetID(nw Network) { + ide.Nw = nw + ide.ID = nw.IncrementAndGetId() +} +func (ide *NwElemIdImpl) GetID() int { + return ide.ID +} + +type JoinTable interface { + NwElemId + GetRule() model.Rule + + AddRow(handles []ReteHandle) JoinTableRow + RemoveRow(rowID int) JoinTableRow + GetRow(rowID int) JoinTableRow + GetRowIterator() RowIterator + + GetRowCount() int +} +type JoinTableRow interface { + NwElemId + GetHandles() []ReteHandle +} + +type ReteHandle interface { + NwElemId + SetTuple(tuple model.Tuple) + GetTuple() model.Tuple + AddJoinTableRowRef(joinTableRowVar JoinTableRow, joinTableVar JoinTable) + RemoveJoinTableRowRefs(changedProps map[string]bool) + RemoveJoinTable(joinTableID int) + GetTupleKey() model.TupleKey + GetRefTableIterator() HdlTblIterator +} + +type RowIterator interface { + HasNext() bool + Next() JoinTableRow +} + +type JoinTableRefsInHdl interface { + AddEntry(jointTableID int, rowID int) + RemoveEntry(jointTableID int) + GetIterator() HdlTblIterator +} + +type HdlTblIterator interface { + HasNext() bool + Next() (int, *list.List) +} + +type JoinTableCollection interface { +} diff --git a/rete/joinnode.go b/rete/joinnode.go index 096a6f2..8fb2c3d 100644 --- a/rete/joinnode.go +++ b/rete/joinnode.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/internal/types" ) //joinNode holds the join tables for unmatched entries @@ -26,23 +27,23 @@ type joinNodeImpl struct { joinIndexForLeft []int joinIndexForRight []int - leftTable joinTable - rightTable joinTable + leftTable types.JoinTable + rightTable types.JoinTable } -func newJoinNode(nw Network, rule model.Rule, leftIdrs []model.TupleType, rightIdrs []model.TupleType, conditionVar model.Condition) joinNode { +func newJoinNode(nw *reteNetworkImpl, rule model.Rule, leftIdrs []model.TupleType, rightIdrs []model.TupleType, conditionVar model.Condition) joinNode { jn := joinNodeImpl{} jn.initjoinNodeImplVar(nw, rule, leftIdrs, rightIdrs, conditionVar) return &jn } -func (jn *joinNodeImpl) initjoinNodeImplVar(nw Network, rule model.Rule, leftIdrs []model.TupleType, rightIdrs []model.TupleType, conditionVar model.Condition) { +func (jn *joinNodeImpl) initjoinNodeImplVar(nw *reteNetworkImpl, rule model.Rule, leftIdrs []model.TupleType, rightIdrs []model.TupleType, conditionVar model.Condition) { jn.initNodeImpl(nw, rule, nil) jn.leftIdrs = leftIdrs jn.rightIdrs = rightIdrs jn.conditionVar = conditionVar - jn.leftTable = nw.getFactory().getJoinTable(nw, rule, leftIdrs) - jn.rightTable = nw.getFactory().getJoinTable(nw, rule, rightIdrs) + jn.leftTable = nw.getFactory().getJoinTable(rule, leftIdrs) + jn.rightTable = nw.getFactory().getJoinTable(rule, rightIdrs) jn.setJoinIdentifiers() } @@ -128,12 +129,12 @@ func (jn *joinNodeImpl) String() string { switch jn.nodeLinkVar.getChild().(type) { case *joinNodeImpl: if jn.nodeLinkVar.isRightNode() { - linkTo += strconv.Itoa(jn.nodeLinkVar.getChild().getID()) + "R" + linkTo += strconv.Itoa(jn.nodeLinkVar.getChild().GetID()) + "R" } else { - linkTo += strconv.Itoa(jn.nodeLinkVar.getChild().getID()) + "L" + linkTo += strconv.Itoa(jn.nodeLinkVar.getChild().GetID()) + "L" } default: - linkTo += strconv.Itoa(jn.nodeLinkVar.getChild().getID()) + linkTo += strconv.Itoa(jn.nodeLinkVar.getChild().GetID()) } joinConditionStr := "nil" @@ -152,9 +153,9 @@ func (jn *joinNodeImpl) String() string { "\t\tCondition = " + joinConditionStr + "]\n" } -func (jn *joinNodeImpl) assertObjects(ctx context.Context, handles []reteHandle, isRight bool) { +func (jn *joinNodeImpl) assertObjects(ctx context.Context, handles []types.ReteHandle, isRight bool) { //TODO: - joinedHandles := make([]reteHandle, jn.totalIdrLen) + joinedHandles := make([]types.ReteHandle, jn.totalIdrLen) if isRight { jn.assertFromRight(ctx, handles, joinedHandles) } else { @@ -162,17 +163,17 @@ func (jn *joinNodeImpl) assertObjects(ctx context.Context, handles []reteHandle, } } -func (jn *joinNodeImpl) assertFromRight(ctx context.Context, handles []reteHandle, joinedHandles []reteHandle) { +func (jn *joinNodeImpl) assertFromRight(ctx context.Context, handles []types.ReteHandle, joinedHandles []types.ReteHandle) { //TODO: other stuff. right now focus on tuple table jn.joinRightObjects(handles, joinedHandles) //tupleTableRow := newJoinTableRow(handles, jn.nw.incrementAndGetId()) - jn.rightTable.addRow(handles) + jn.rightTable.AddRow(handles) //TODO: rete listeners etc. - rIterator := jn.leftTable.getRowIterator() - for rIterator.hasNext() { - tupleTableRowLeft := rIterator.next() - success := jn.joinLeftObjects(tupleTableRowLeft.getHandles(), joinedHandles) + rIterator := jn.leftTable.GetRowIterator() + for rIterator.HasNext() { + tupleTableRowLeft := rIterator.Next() + success := jn.joinLeftObjects(tupleTableRowLeft.GetHandles(), joinedHandles) if !success { //TODO: handle it continue @@ -191,10 +192,10 @@ func (jn *joinNodeImpl) assertFromRight(ctx context.Context, handles []reteHandl } } -func (jn *joinNodeImpl) joinLeftObjects(leftHandles []reteHandle, joinedHandles []reteHandle) bool { +func (jn *joinNodeImpl) joinLeftObjects(leftHandles []types.ReteHandle, joinedHandles []types.ReteHandle) bool { for i := 0; i < jn.leftIdrLen; i++ { handle := leftHandles[i] - if handle.getTuple() == nil { + if handle.GetTuple() == nil { return false } joinedHandles[jn.joinIndexForLeft[i]] = handle @@ -202,10 +203,10 @@ func (jn *joinNodeImpl) joinLeftObjects(leftHandles []reteHandle, joinedHandles return true } -func (jn *joinNodeImpl) joinRightObjects(rightHandles []reteHandle, joinedHandles []reteHandle) bool { +func (jn *joinNodeImpl) joinRightObjects(rightHandles []types.ReteHandle, joinedHandles []types.ReteHandle) bool { for i := 0; i < jn.rightIdrLen; i++ { handle := rightHandles[i] - if handle.getTuple() == nil { + if handle.GetTuple() == nil { return false } joinedHandles[jn.joinIndexForRight[i]] = handle @@ -213,16 +214,16 @@ func (jn *joinNodeImpl) joinRightObjects(rightHandles []reteHandle, joinedHandle return true } -func (jn *joinNodeImpl) assertFromLeft(ctx context.Context, handles []reteHandle, joinedHandles []reteHandle) { +func (jn *joinNodeImpl) assertFromLeft(ctx context.Context, handles []types.ReteHandle, joinedHandles []types.ReteHandle) { jn.joinLeftObjects(handles, joinedHandles) //TODO: other stuff. right now focus on tuple table //tupleTableRow := newJoinTableRow(handles, jn.nw.incrementAndGetId()) - jn.leftTable.addRow(handles) + jn.leftTable.AddRow(handles) //TODO: rete listeners etc. - rIterator := jn.rightTable.getRowIterator() - for rIterator.hasNext() { - tupleTableRowRight := rIterator.next() - success := jn.joinRightObjects(tupleTableRowRight.getHandles(), joinedHandles) + rIterator := jn.rightTable.GetRowIterator() + for rIterator.HasNext() { + tupleTableRowRight := rIterator.Next() + success := jn.joinRightObjects(tupleTableRowRight.GetHandles(), joinedHandles) if !success { //TODO: handle it continue diff --git a/rete/jointable.go b/rete/jointable.go deleted file mode 100644 index a949d27..0000000 --- a/rete/jointable.go +++ /dev/null @@ -1,17 +0,0 @@ -package rete - -import ( - "github.com/project-flogo/rules/common/model" -) - -type joinTable interface { - nwElemId - getRule() model.Rule - - addRow(handles []reteHandle) joinTableRow - removeRow(rowID int) joinTableRow - getRow(rowID int) joinTableRow - getRowIterator() rowIterator - - getRowCount() int -} diff --git a/rete/jointablememimpl.go b/rete/jointablememimpl.go deleted file mode 100644 index b03f2cf..0000000 --- a/rete/jointablememimpl.go +++ /dev/null @@ -1,64 +0,0 @@ -package rete - -import "github.com/project-flogo/rules/common/model" - -type joinTableImpl struct { - nwElemIdImpl - table map[int]joinTableRow - idr []model.TupleType - rule model.Rule -} - -func newJoinTable(nw Network, rule model.Rule, identifiers []model.TupleType) joinTable { - jT := joinTableImpl{} - jT.initJoinTableImpl(nw, rule, identifiers) - - //add it to all join tables collection before returning - reteNw := nw.(*reteNetworkImpl) - reteNw.allJoinTables[jT.getID()] = &jT - return &jT -} - -func (jt *joinTableImpl) initJoinTableImpl(nw Network, rule model.Rule, identifiers []model.TupleType) { - jt.setID(nw) - jt.idr = identifiers - jt.table = map[int]joinTableRow{} - jt.rule = rule -} - -func (jt *joinTableImpl) addRow(handles []reteHandle) joinTableRow { - - row := newJoinTableRow(handles, jt.nw) - - jt.table[row.getID()] = row - for i := 0; i < len(row.getHandles()); i++ { - handle := row.getHandles()[i] - handle.addJoinTableRowRef(row, jt) - } - return row -} - -func (jt *joinTableImpl) removeRow(rowID int) joinTableRow { - row, found := jt.table[rowID] - if found { - delete(jt.table, rowID) - return row - } - return nil -} - -func (jt *joinTableImpl) getRowCount() int { - return len(jt.table) -} - -func (jt *joinTableImpl) getRule() model.Rule { - return jt.rule -} - -func (jt *joinTableImpl) getRowIterator() rowIterator { - return newRowIterator(jt.table) -} - -func (jt *joinTableImpl) getRow(rowID int) joinTableRow { - return jt.table[rowID] -} diff --git a/rete/jointablerefsinhandle.go b/rete/jointablerefsinhandle.go deleted file mode 100644 index e78c7c3..0000000 --- a/rete/jointablerefsinhandle.go +++ /dev/null @@ -1,6 +0,0 @@ -package rete - -type joinTableRefsInHdl interface { - addEntry(jointTableID int, rowID int) - removeEntry(jointTableID int) -} diff --git a/rete/jointablerow.go b/rete/jointablerow.go deleted file mode 100644 index 790bf7c..0000000 --- a/rete/jointablerow.go +++ /dev/null @@ -1,6 +0,0 @@ -package rete - -type joinTableRow interface { - nwElemId - getHandles() []reteHandle -} diff --git a/rete/jointablerowmemimpl.go b/rete/jointablerowmemimpl.go deleted file mode 100644 index bbf18fc..0000000 --- a/rete/jointablerowmemimpl.go +++ /dev/null @@ -1,21 +0,0 @@ -package rete - -type joinTableRowImpl struct { - nwElemIdImpl - handles []reteHandle -} - -func newJoinTableRow(handles []reteHandle, nw Network) joinTableRow { - jtr := joinTableRowImpl{} - jtr.initJoinTableRow(handles, nw) - return &jtr -} - -func (jtr *joinTableRowImpl) initJoinTableRow(handles []reteHandle, nw Network) { - jtr.setID(nw) - jtr.handles = append([]reteHandle{}, handles...) -} - -func (jtr *joinTableRowImpl) getHandles() []reteHandle { - return jtr.handles -} diff --git a/rete/network.go b/rete/network.go index 5fb4b31..de5a2cd 100644 --- a/rete/network.go +++ b/rete/network.go @@ -8,49 +8,13 @@ import ( "github.com/project-flogo/rules/common/model" "container/list" + "github.com/project-flogo/rules/rete/internal/types" "sync" "time" + //"github.com/project-flogo/rules/rete/common" + "github.com/project-flogo/rules/rete/common" ) -type RtcOprn int - -const ( - ADD RtcOprn = 1 + iota - RETRACT - MODIFY - DELETE -) - -//Network ... the rete network -type Network interface { - AddRule(model.Rule) error - String() string - RemoveRule(string) model.Rule - GetRules() []model.Rule - //changedProps are the properties that changed in a previous action - Assert(ctx context.Context, rs model.RuleSession, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) - //mode can be one of retract, modify, delete - Retract(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) - - retractInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) - - assertInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) - getOrCreateHandle(ctx context.Context, tuple model.Tuple) reteHandle - getHandle(tuple model.Tuple) reteHandle - - incrementAndGetId() int - GetAssertedTuple(key model.TupleKey) model.Tuple - GetAssertedTupleByStringKey(key string) model.Tuple - //RtcTransactionHandler - RegisterRtcTransactionHandler(txnHandler model.RtcTransactionHandler, txnContext interface{}) - - getJoinTable(joinTableID int) joinTable - SetConfig(config map[string]string) - GetConfigValue(key string) string - GetConfig() map[string]string - getFactory() TypeFactory -} - type reteNetworkImpl struct { //All rules in the network allRules map[string]model.Rule //(Rule) @@ -64,7 +28,7 @@ type reteNetworkImpl struct { //Holds the Rule name as key and a pointer to a slice of NodeLinks as value ruleNameClassNodeLinksOfRule map[string]*list.List //*list.List of ClassNodeLink - allHandles map[string]reteHandle + allHandles map[string]types.ReteHandle currentId int @@ -73,14 +37,15 @@ type reteNetworkImpl struct { txnHandler model.RtcTransactionHandler txnContext interface{} - allJoinTables map[int]joinTable - config map[string]string + allJoinTables map[int]types.JoinTable + //allJoinTables types.JoinTableCollection + config map[string]string - factory TypeFactory + factory *TypeFactory } //NewReteNetwork ... creates a new rete network -func NewReteNetwork() Network { +func NewReteNetwork() types.Network { reteNetworkImpl := reteNetworkImpl{} reteNetworkImpl.initReteNetwork() return &reteNetworkImpl @@ -92,9 +57,10 @@ func (nw *reteNetworkImpl) initReteNetwork() { nw.allClassNodes = make(map[string]classNode) nw.ruleNameNodesOfRule = make(map[string]*list.List) nw.ruleNameClassNodeLinksOfRule = make(map[string]*list.List) - nw.allHandles = make(map[string]reteHandle) - nw.allJoinTables = make(map[int]joinTable) - nw.factory = TypeFactory{} + nw.allHandles = make(map[string]types.ReteHandle) + nw.allJoinTables = make(map[int]types.JoinTable) + //nw.allJoinTables = + nw.factory = NewFactory(nw, nil) } func (nw *reteNetworkImpl) AddRule(rule model.Rule) (err error) { @@ -207,15 +173,15 @@ func (nw *reteNetworkImpl) GetRules() []model.Rule { return rules } -func removeRefsFromReteHandles(joinTableVar joinTable) { +func removeRefsFromReteHandles(joinTableVar types.JoinTable) { if joinTableVar == nil { return } - rIterator := joinTableVar.getRowIterator() - for rIterator.hasNext() { - tableRow := rIterator.next() - for _, handle := range tableRow.getHandles() { - handle.removeJoinTable(joinTableVar.getID()) + rIterator := joinTableVar.GetRowIterator() + for rIterator.HasNext() { + tableRow := rIterator.Next() + for _, handle := range tableRow.GetHandles() { + handle.RemoveJoinTable(joinTableVar.GetID()) } } } @@ -542,7 +508,7 @@ func (nw *reteNetworkImpl) printClassNode(ruleName string, classNodeImpl *classN return "\t[ClassNode Class(" + classNodeImpl.getName() + ")" + links + "]\n" } -func (nw *reteNetworkImpl) Assert(ctx context.Context, rs model.RuleSession, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) { +func (nw *reteNetworkImpl) Assert(ctx context.Context, rs model.RuleSession, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) { if ctx == nil { ctx = context.Background() @@ -579,11 +545,11 @@ func (nw *reteNetworkImpl) removeTupleFromRete(tuple model.Tuple) { reteHandle, found := nw.allHandles[tuple.GetKey().String()] if found && reteHandle != nil { delete(nw.allHandles, tuple.GetKey().String()) - reteHandle.removeJoinTableRowRefs(nil) + reteHandle.RemoveJoinTableRowRefs(nil) } } -func (nw *reteNetworkImpl) Retract(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) { +func (nw *reteNetworkImpl) Retract(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) { if ctx == nil { ctx = context.Background() @@ -593,7 +559,7 @@ func (nw *reteNetworkImpl) Retract(ctx context.Context, tuple model.Tuple, chang nw.crudLock.Lock() defer nw.crudLock.Unlock() nw.retractInternal(ctx, tuple, changedProps, mode) - if nw.txnHandler != nil && mode == DELETE { + if nw.txnHandler != nil && mode == common.DELETE { rtcTxn := newRtcTxn(reteCtxVar.getRtcAdded(), reteCtxVar.getRtcModified(), reteCtxVar.getRtcDeleted()) nw.txnHandler(ctx, reteCtxVar.getRuleSession(), rtcTxn, nw.txnContext) } @@ -602,7 +568,7 @@ func (nw *reteNetworkImpl) Retract(ctx context.Context, tuple model.Tuple, chang } } -func (nw *reteNetworkImpl) retractInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) { +func (nw *reteNetworkImpl) retractInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) { if ctx == nil { ctx = context.Background() @@ -611,10 +577,10 @@ func (nw *reteNetworkImpl) retractInternal(ctx context.Context, tuple model.Tupl reteHandle := nw.allHandles[tuple.GetKey().String()] if reteHandle != nil { - reteHandle.removeJoinTableRowRefs(changedProps) + reteHandle.RemoveJoinTableRowRefs(changedProps) //add it to the delete list - if mode == DELETE { + if mode == common.DELETE { rCtx.addToRtcDeleted(tuple) } delete(nw.allHandles, tuple.GetKey().String()) @@ -624,7 +590,7 @@ func (nw *reteNetworkImpl) retractInternal(ctx context.Context, tuple model.Tupl func (nw *reteNetworkImpl) GetAssertedTuple(key model.TupleKey) model.Tuple { reteHandle, found := nw.allHandles[key.String()] if found { - return reteHandle.getTuple() + return reteHandle.GetTuple() } return nil } @@ -632,12 +598,12 @@ func (nw *reteNetworkImpl) GetAssertedTuple(key model.TupleKey) model.Tuple { func (nw *reteNetworkImpl) GetAssertedTupleByStringKey(key string) model.Tuple { reteHandle, found := nw.allHandles[key] if found { - return reteHandle.getTuple() + return reteHandle.GetTuple() } return nil } -func (nw *reteNetworkImpl) assertInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) { +func (nw *reteNetworkImpl) assertInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) { tupleType := tuple.GetTupleType() listItem := nw.allClassNodes[string(tupleType)] if listItem != nil { @@ -646,7 +612,7 @@ func (nw *reteNetworkImpl) assertInternal(ctx context.Context, tuple model.Tuple } td := model.GetTupleDescriptor(tuple.GetTupleType()) if td != nil { - if td.TTLInSeconds != 0 && mode == ADD { + if td.TTLInSeconds != 0 && mode == common.ADD { rCtx := getReteCtx(ctx) if rCtx != nil { rCtx.addToRtcAdded(tuple) @@ -655,7 +621,7 @@ func (nw *reteNetworkImpl) assertInternal(ctx context.Context, tuple model.Tuple } } -func (nw *reteNetworkImpl) getOrCreateHandle(ctx context.Context, tuple model.Tuple) reteHandle { +func (nw *reteNetworkImpl) getOrCreateHandle(ctx context.Context, tuple model.Tuple) types.ReteHandle { h := nw.allHandles[tuple.GetKey().String()] if h == nil { h = newReteHandleImpl(nw, tuple) @@ -664,12 +630,12 @@ func (nw *reteNetworkImpl) getOrCreateHandle(ctx context.Context, tuple model.Tu return h } -func (nw *reteNetworkImpl) getHandle(tuple model.Tuple) reteHandle { +func (nw *reteNetworkImpl) getHandle(tuple model.Tuple) types.ReteHandle { h := nw.allHandles[tuple.GetKey().String()] return h } -func (nw *reteNetworkImpl) incrementAndGetId() int { +func (nw *reteNetworkImpl) IncrementAndGetId() int { nw.currentId++ return nw.currentId } @@ -679,13 +645,13 @@ func (nw *reteNetworkImpl) RegisterRtcTransactionHandler(txnHandler model.RtcTra nw.txnContext = txnContext } -func (nw *reteNetworkImpl) getJoinTable(joinTableID int) joinTable { +func (nw *reteNetworkImpl) GetJoinTable(joinTableID int) types.JoinTable { return nw.allJoinTables[joinTableID] } func (nw *reteNetworkImpl) SetConfig(config map[string]string) { nw.config = config - nw.factory = TypeFactory{config} + nw.factory = &TypeFactory{nw, config} } @@ -698,6 +664,10 @@ func (nw *reteNetworkImpl) GetConfig() map[string]string { return nw.config } -func (nw *reteNetworkImpl) getFactory() TypeFactory { +func (nw *reteNetworkImpl) getFactory() *TypeFactory { return nw.factory } + +func (nw *reteNetworkImpl) AddToAllJoinTables(joinTable types.JoinTable) { + nw.allJoinTables[joinTable.GetID()] = joinTable +} diff --git a/rete/node.go b/rete/node.go index fcb9f21..8522ea8 100644 --- a/rete/node.go +++ b/rete/node.go @@ -7,26 +7,27 @@ import ( "strconv" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/internal/types" ) //node a building block of the rete network type node interface { abstractNode - nwElemId + types.NwElemId getIdentifiers() []model.TupleType addNodeLink(nodeLink) - assertObjects(ctx context.Context, handles []reteHandle, isRight bool) + assertObjects(ctx context.Context, handles []types.ReteHandle, isRight bool) } type nodeImpl struct { - nwElemIdImpl + types.NwElemIdImpl identifiers []model.TupleType nodeLinkVar nodeLink rule model.Rule } -func (n *nodeImpl) initNodeImpl(nw Network, rule model.Rule, identifiers []model.TupleType) { - n.setID(nw) +func (n *nodeImpl) initNodeImpl(nw *reteNetworkImpl, rule model.Rule, identifiers []model.TupleType) { + n.SetID(nw) n.identifiers = identifiers n.rule = rule } @@ -40,7 +41,7 @@ func (n *nodeImpl) addNodeLink(nl nodeLink) { } func (n *nodeImpl) String() string { - str := "id:" + strconv.Itoa(n.getID()) + ", idrs:" + str := "id:" + strconv.Itoa(n.GetID()) + ", idrs:" for _, nodeIdentifier := range n.identifiers { str += string(nodeIdentifier) + "," } @@ -74,6 +75,6 @@ func findSimilarNodes(nodeSet *list.List) []node { return similarNodes } -func (n *nodeImpl) assertObjects(ctx context.Context, handles []reteHandle, isRight bool) { +func (n *nodeImpl) assertObjects(ctx context.Context, handles []types.ReteHandle, isRight bool) { fmt.Println("Abstract method here.., see filterNodeImpl and joinNodeImpl") } diff --git a/rete/nodelink.go b/rete/nodelink.go index fde3b4f..0a36be0 100644 --- a/rete/nodelink.go +++ b/rete/nodelink.go @@ -5,22 +5,23 @@ import ( "strconv" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/internal/types" ) //nodelink connects 2 nodes, a rete building block type nodeLink interface { - nwElemId + types.NwElemId String() string getChild() node isRightNode() bool setChild(child node) setIsRightChild(isRight bool) - propagateObjects(ctx context.Context, handles []reteHandle) + propagateObjects(ctx context.Context, handles []types.ReteHandle) } type nodeLinkImpl struct { - nwElemIdImpl + types.NwElemIdImpl convert []int numIdentifiers int parent node @@ -30,14 +31,14 @@ type nodeLinkImpl struct { isRight bool } -func newNodeLink(nw Network, parent node, child node, isRight bool) nodeLink { +func newNodeLink(nw types.Network, parent node, child node, isRight bool) nodeLink { nl := nodeLinkImpl{} nl.initNodeLink(nw, parent, child, isRight) return &nl } -func (nl *nodeLinkImpl) initNodeLink(nw Network, parent node, child node, isRight bool) { - nl.setID(nw) +func (nl *nodeLinkImpl) initNodeLink(nw types.Network, parent node, child node, isRight bool) { + nl.SetID(nw) nl.child = child nl.isRight = isRight @@ -58,8 +59,8 @@ func (nl *nodeLinkImpl) initNodeLink(nw Network, parent node, child node, isRigh } //initialize node link : for use with ClassNodeLink -func (nl *nodeLinkImpl) initClassNodeLink(nw Network, child node) { - nl.setID(nw) +func (nl *nodeLinkImpl) initClassNodeLink(nw types.Network, child node) { + nl.SetID(nw) nl.child = child nl.childIds = child.getIdentifiers() } @@ -107,12 +108,12 @@ func (nl *nodeLinkImpl) String() string { switch nl.child.(type) { case *joinNodeImpl: if nl.isRight { - nextNode += "j" + strconv.Itoa(nl.child.getID()) + "R" + nextNode += "j" + strconv.Itoa(nl.child.GetID()) + "R" } else { - nextNode += "j" + strconv.Itoa(nl.child.getID()) + "L" + nextNode += "j" + strconv.Itoa(nl.child.GetID()) + "L" } case *filterNodeImpl: - nextNode += "f" + strconv.Itoa(nl.child.getID()) + nextNode += "f" + strconv.Itoa(nl.child.GetID()) } return "link (" + nextNode + ")" } @@ -128,9 +129,9 @@ func (nl *nodeLinkImpl) setIsRightChild(isRight bool) { nl.isRight = isRight } -func (nl *nodeLinkImpl) propagateObjects(ctx context.Context, handles []reteHandle) { +func (nl *nodeLinkImpl) propagateObjects(ctx context.Context, handles []types.ReteHandle) { if nl.convert != nil { - convertedHandles := make([]reteHandle, nl.numIdentifiers) + convertedHandles := make([]types.ReteHandle, nl.numIdentifiers) for i := 0; i < nl.numIdentifiers; i++ { convertedHandles[nl.convert[i]] = handles[i] } diff --git a/rete/opsList.go b/rete/opsList.go index 8679bb6..97669e6 100644 --- a/rete/opsList.go +++ b/rete/opsList.go @@ -4,6 +4,7 @@ import ( "context" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/common" ) type opsEntry interface { @@ -23,10 +24,10 @@ type assertEntry interface { type assertEntryImpl struct { opsEntryImpl - mode RtcOprn + mode common.RtcOprn } -func newAssertEntry(tuple model.Tuple, changeProps map[string]bool, mode RtcOprn) assertEntry { +func newAssertEntry(tuple model.Tuple, changeProps map[string]bool, mode common.RtcOprn) assertEntry { aEntry := assertEntryImpl{} aEntry.tuple = tuple aEntry.changeProps = changeProps @@ -59,8 +60,8 @@ func newModifyEntry(tuple model.Tuple, changeProps map[string]bool) modifyEntry func (me *modifyEntryImpl) execute(ctx context.Context) { reteCtx := getReteCtx(ctx) reteCtx.getConflictResolver().deleteAgendaFor(ctx, me.tuple, me.changeProps) - reteCtx.getNetwork().Retract(ctx, me.tuple, me.changeProps, MODIFY) - reteCtx.getNetwork().Assert(ctx, reteCtx.getRuleSession(), me.tuple, me.changeProps, MODIFY) + reteCtx.getNetwork().Retract(ctx, me.tuple, me.changeProps, common.MODIFY) + reteCtx.getNetwork().Assert(ctx, reteCtx.getRuleSession(), me.tuple, me.changeProps, common.MODIFY) } //Delete Entry @@ -71,10 +72,10 @@ type deleteEntry interface { type deleteEntryImpl struct { opsEntryImpl - mode RtcOprn + mode common.RtcOprn } -func newDeleteEntry(tuple model.Tuple, mode RtcOprn, changeProps map[string]bool) deleteEntry { +func newDeleteEntry(tuple model.Tuple, mode common.RtcOprn, changeProps map[string]bool) deleteEntry { dEntry := deleteEntryImpl{} dEntry.tuple = tuple dEntry.mode = mode diff --git a/rete/retehandle.go b/rete/retehandle.go index ca8336d..2ac6232 100644 --- a/rete/retehandle.go +++ b/rete/retehandle.go @@ -4,72 +4,64 @@ import ( "context" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/internal/types" ) //Holds a tuple reference and related state -type reteHandle interface { - nwElemId - setTuple(tuple model.Tuple) - getTuple() model.Tuple - addJoinTableRowRef(joinTableRowVar joinTableRow, joinTableVar joinTable) - removeJoinTableRowRefs(changedProps map[string]bool) - removeJoinTable(joinTableID int) - getTupleKey() model.TupleKey -} type reteHandleImpl struct { - nwElemIdImpl + types.NwElemIdImpl tuple model.Tuple tupleKey model.TupleKey - jtRefs joinTableRefsInHdl + jtRefs types.JoinTableRefsInHdl } -func newReteHandleImpl(nw *reteNetworkImpl, tuple model.Tuple) reteHandle { +func newReteHandleImpl(nw *reteNetworkImpl, tuple model.Tuple) types.ReteHandle { h1 := reteHandleImpl{} h1.initHandleImpl(nw, tuple) return &h1 } -func (hdl *reteHandleImpl) setTuple(tuple model.Tuple) { +func (hdl *reteHandleImpl) SetTuple(tuple model.Tuple) { hdl.tuple = tuple } func (hdl *reteHandleImpl) initHandleImpl(nw *reteNetworkImpl, tuple model.Tuple) { - hdl.setID(nw) - hdl.setTuple(tuple) + hdl.SetID(nw) + hdl.SetTuple(tuple) hdl.jtRefs = nw.getFactory().getJoinTableRefs() } -func (hdl *reteHandleImpl) getTuple() model.Tuple { +func (hdl *reteHandleImpl) GetTuple() model.Tuple { return hdl.tuple } -func (hdl *reteHandleImpl) getTupleKey() model.TupleKey { +func (hdl *reteHandleImpl) GetTupleKey() model.TupleKey { return hdl.tupleKey } -func getOrCreateHandle(ctx context.Context, tuple model.Tuple) reteHandle { +func getOrCreateHandle(ctx context.Context, tuple model.Tuple) types.ReteHandle { reteCtxVar := getReteCtx(ctx) return reteCtxVar.getNetwork().getOrCreateHandle(ctx, tuple) } -func (hdl *reteHandleImpl) addJoinTableRowRef(joinTableRowVar joinTableRow, joinTableVar joinTable) { - hdl.jtRefs.addEntry(joinTableVar.getID(), joinTableRowVar.getID()) +func (hdl *reteHandleImpl) AddJoinTableRowRef(joinTableRowVar types.JoinTableRow, joinTableVar types.JoinTable) { + hdl.jtRefs.AddEntry(joinTableVar.GetID(), joinTableRowVar.GetID()) } -func (hdl *reteHandleImpl) removeJoinTableRowRefs(changedProps map[string]bool) { +func (hdl *reteHandleImpl) RemoveJoinTableRowRefs(changedProps map[string]bool) { tuple := hdl.tuple alias := tuple.GetTupleType() - hdlTblIter := hdl.newHdlTblIterator() + hdlTblIter := hdl.GetRefTableIterator() - for hdlTblIter.hasNext() { - joinTableID, rowIDs := hdlTblIter.next() - joinTable := hdl.nw.getJoinTable(joinTableID) + for hdlTblIter.HasNext() { + joinTableID, rowIDs := hdlTblIter.Next() + joinTable := hdl.Nw.GetJoinTable(joinTableID) toDelete := false if changedProps != nil { - rule := joinTable.getRule() + rule := joinTable.GetRule() depProps, found := rule.GetDeps()[alias] if found { // rule depends on this type for changedProp := range changedProps { @@ -94,23 +86,29 @@ func (hdl *reteHandleImpl) removeJoinTableRowRefs(changedProps map[string]bool) ////Remove rows from corresponding join tables for e := rowIDs.Front(); e != nil; e = e.Next() { rowID := e.Value.(int) - row := joinTable.removeRow(rowID) + row := joinTable.RemoveRow(rowID) if row != nil { //Remove other refs recursively. - for _, otherHdl := range row.getHandles() { + for _, otherHdl := range row.GetHandles() { //if otherHdl != nil { - otherHdl.removeJoinTableRowRefs(nil) + otherHdl.RemoveJoinTableRowRefs(nil) //} } } } //Remove the reference to the table itself - hdl.jtRefs.removeEntry(joinTableID) + hdl.jtRefs.RemoveEntry(joinTableID) } } //Used when a rule is deleted. See Network.RemoveRule -func (hdl *reteHandleImpl) removeJoinTable(joinTableID int) { - hdl.jtRefs.removeEntry(joinTableID) +func (hdl *reteHandleImpl) RemoveJoinTable(joinTableID int) { + hdl.jtRefs.RemoveEntry(joinTableID) +} + +func (hdl *reteHandleImpl) GetRefTableIterator() types.HdlTblIterator { + refTblIteator := hdl.jtRefs.GetIterator() + return refTblIteator + } diff --git a/rete/rulenode.go b/rete/rulenode.go index 1112ff4..56aec0f 100644 --- a/rete/rulenode.go +++ b/rete/rulenode.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/internal/types" ) //ruleNode the leaf node of the rule network for a Rule @@ -26,12 +27,12 @@ func newRuleNode(rule model.Rule) ruleNode { } func (rn *ruleNodeImpl) String() string { - return "\t[RuleNode id(" + strconv.Itoa(rn.getID()) + "): \n" + + return "\t[RuleNode id(" + strconv.Itoa(rn.GetID()) + "): \n" + "\t\tIdentifier = " + model.IdentifiersToString(rn.identifiers) + " ;\n" + "\t\tRule = " + rn.rule.GetName() + "]\n" } -func (rn *ruleNodeImpl) assertObjects(ctx context.Context, handles []reteHandle, isRight bool) { +func (rn *ruleNodeImpl) assertObjects(ctx context.Context, handles []types.ReteHandle, isRight bool) { tupleMap := copyIntoTupleMap(handles) diff --git a/rete/utils.go b/rete/utils.go index 62e22ef..a0b34d3 100644 --- a/rete/utils.go +++ b/rete/utils.go @@ -4,21 +4,22 @@ package rete import ( "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/internal/types" ) -func copyIntoTupleArray(handles []reteHandle) []model.Tuple { +func copyIntoTupleArray(handles []types.ReteHandle) []model.Tuple { tuples := make([]model.Tuple, len(handles)) for i := 0; i < len(handles); i++ { - tuples[i] = handles[i].getTuple() + tuples[i] = handles[i].GetTuple() } return tuples } -func copyIntoTupleMap(handles []reteHandle) map[model.TupleType]model.Tuple { +func copyIntoTupleMap(handles []types.ReteHandle) map[model.TupleType]model.Tuple { tupleMap := map[model.TupleType]model.Tuple{} tuples := make([]model.Tuple, len(handles)) for i := 0; i < len(handles); i++ { - tuples[i] = handles[i].getTuple() + tuples[i] = handles[i].GetTuple() tupleMap[tuples[i].GetTupleType()] = tuples[i] //assuming no self-joins! need to correct this! } return tupleMap diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index ac524ad..e9ab00d 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -11,6 +11,7 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/config" "github.com/project-flogo/rules/rete" + "github.com/project-flogo/rules/rete/common" ) var ( @@ -19,7 +20,7 @@ var ( type rulesessionImpl struct { name string - reteNetwork rete.Network + reteNetwork common.Network timers map[interface{}]*time.Timer startupFn model.StartupRSFunction @@ -100,16 +101,16 @@ func (rs *rulesessionImpl) Assert(ctx context.Context, tuple model.Tuple) (err e if ctx == nil { ctx = context.Context(context.Background()) } - rs.reteNetwork.Assert(ctx, rs, tuple, nil, rete.ADD) + rs.reteNetwork.Assert(ctx, rs, tuple, nil, common.ADD) return nil } func (rs *rulesessionImpl) Retract(ctx context.Context, tuple model.Tuple) { - rs.reteNetwork.Retract(ctx, tuple, nil, rete.RETRACT) + rs.reteNetwork.Retract(ctx, tuple, nil, common.RETRACT) } func (rs *rulesessionImpl) Delete(ctx context.Context, tuple model.Tuple) { - rs.reteNetwork.Retract(ctx, tuple, nil, rete.DELETE) + rs.reteNetwork.Retract(ctx, tuple, nil, common.DELETE) } func (rs *rulesessionImpl) printNetwork() { @@ -180,7 +181,7 @@ func (rs *rulesessionImpl) SetConfig(config map[string]string) { if rs.config == nil { rs.config = config } - if rs.reteNetwork != nil && rs.reteNetwork.GetConfig() != nil { + if rs.reteNetwork != nil && rs.reteNetwork.GetConfig() == nil { rs.reteNetwork.SetConfig(config) } } From e9a97388a46738e1effba7fe4ad247eb647c74a1 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Thu, 13 Dec 2018 11:34:18 +0530 Subject: [PATCH 016/125] refactor interfaces in order to separate out and organize in-mem implementation --- rete/common/types.go | 14 +---- rete/factory.go | 46 ++++++++++++++-- rete/internal/memimpl/handlecollectionimpl.go | 37 +++++++++++++ .../memimpl/jointablecollectionimpl.go | 14 +++++ rete/internal/types/types.go | 9 ++++ rete/network.go | 53 +++++++++---------- rete/retehandle.go | 1 + ruleapi/rulesession.go | 2 +- 8 files changed, 129 insertions(+), 47 deletions(-) create mode 100644 rete/internal/memimpl/handlecollectionimpl.go diff --git a/rete/common/types.go b/rete/common/types.go index 2818dc6..611e750 100644 --- a/rete/common/types.go +++ b/rete/common/types.go @@ -14,7 +14,7 @@ const ( DELETE ) -//Network ... the rete network +//Network ... these are used by RuleSession type Network interface { AddRule(model.Rule) error String() string @@ -23,20 +23,8 @@ type Network interface { Assert(ctx context.Context, rs model.RuleSession, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) Retract(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) GetAssertedTuple(key model.TupleKey) model.Tuple - GetAssertedTupleByStringKey(key string) model.Tuple RegisterRtcTransactionHandler(txnHandler model.RtcTransactionHandler, txnContext interface{}) SetConfig(config map[string]string) GetConfigValue(key string) string GetConfig() map[string]string - - //private - //retractInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) - //assertInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) - //getOrCreateHandle(ctx context.Context, tuple model.Tuple) ReteHandle - //getHandle(tuple model.Tuple) ReteHandle - //IncrementAndGetId() int - //GetJoinTable(joinTableID int) JoinTable - //getFactory() TypeFactory - - //AddToAllJoinTables (jT JoinTable) } diff --git a/rete/factory.go b/rete/factory.go index 2d1eb01..5231f3c 100644 --- a/rete/factory.go +++ b/rete/factory.go @@ -17,19 +17,57 @@ func NewFactory(nw *reteNetworkImpl, config map[string]string) *TypeFactory { } func (f *TypeFactory) getJoinTable(rule model.Rule, identifiers []model.TupleType) types.JoinTable { - jtStore := f.config["jtstore"] - if jtStore == "" || jtStore == "memory" { + if f.config == nil { jt := memimpl.NewJoinTable(f.nw, rule, identifiers) return jt + } else { + jtStore := f.config["jtstore"] + if jtStore == "" || jtStore == "memory" { + jt := memimpl.NewJoinTable(f.nw, rule, identifiers) + return jt + } } return nil } func (f *TypeFactory) getJoinTableRefs() types.JoinTableRefsInHdl { - jtType := f.config["jtstore"] - if jtType == "" || jtType == "memory" { + if f.config == nil { jtRef := memimpl.NewJoinTableRefsInHdlImpl() return jtRef + } else { + jtType := f.config["jtstore"] + if jtType == "" || jtType == "memory" { + jtRef := memimpl.NewJoinTableRefsInHdlImpl() + return jtRef + } + } + return nil +} + +func (f *TypeFactory) getJoinTableCollection() types.JoinTableCollection { + if f.config == nil { + jtRef := memimpl.NewJoinTableCollection() + return jtRef + } else { + jtType := f.config["jtstore"] + if jtType == "" || jtType == "memory" { + jtRef := memimpl.NewJoinTableCollection() + return jtRef + } + } + return nil +} + +func (f *TypeFactory) getHandleCollection() types.HandleCollection { + if f.config == nil { + hc := memimpl.NewHandleCollection() + return hc + } else { + jtType := f.config["jtstore"] + if jtType == "" || jtType == "memory" { + hc := memimpl.NewHandleCollection() + return hc + } } return nil } diff --git a/rete/internal/memimpl/handlecollectionimpl.go b/rete/internal/memimpl/handlecollectionimpl.go new file mode 100644 index 0000000..b6717f0 --- /dev/null +++ b/rete/internal/memimpl/handlecollectionimpl.go @@ -0,0 +1,37 @@ +package memimpl + +import ( + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/internal/types" +) + +type handleCollectionImpl struct { + allHandles map[string]types.ReteHandle +} + +func NewHandleCollection() types.HandleCollection { + hc := handleCollectionImpl{} + hc.allHandles = make(map[string]types.ReteHandle) + return &hc +} + +func (hc *handleCollectionImpl) AddHandle(hdl types.ReteHandle) { + hc.allHandles[hdl.GetTupleKey().String()] = hdl +} + +func (hc *handleCollectionImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle { + rh, found := hc.allHandles[tuple.GetKey().String()] + if found { + delete(hc.allHandles, tuple.GetKey().String()) + return rh + } + return nil +} + +func (hc *handleCollectionImpl) GetHandle(tuple model.Tuple) types.ReteHandle { + return hc.allHandles[tuple.GetKey().String()] +} + +func (hc *handleCollectionImpl) GetHandleByKey(key model.TupleKey) types.ReteHandle { + return hc.allHandles[key.String()] +} diff --git a/rete/internal/memimpl/jointablecollectionimpl.go b/rete/internal/memimpl/jointablecollectionimpl.go index 47410a1..b621a0c 100644 --- a/rete/internal/memimpl/jointablecollectionimpl.go +++ b/rete/internal/memimpl/jointablecollectionimpl.go @@ -5,3 +5,17 @@ import "github.com/project-flogo/rules/rete/internal/types" type joinTableCollectionImpl struct { allJoinTables map[int]types.JoinTable } + +func NewJoinTableCollection() types.JoinTableCollection { + jtc := joinTableCollectionImpl{} + jtc.allJoinTables = make(map[int]types.JoinTable) + return &jtc +} + +func (jtc *joinTableCollectionImpl) GetJoinTable(joinTableID int) types.JoinTable { + return jtc.allJoinTables[joinTableID] +} + +func (jtc *joinTableCollectionImpl) AddJoinTable(joinTable types.JoinTable) { + jtc.allJoinTables[joinTable.GetID()] = joinTable +} diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index 7d8ffc6..ab0dadd 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -74,4 +74,13 @@ type HdlTblIterator interface { } type JoinTableCollection interface { + GetJoinTable(joinTableID int) JoinTable + AddJoinTable(joinTable JoinTable) +} + +type HandleCollection interface { + AddHandle(hdl ReteHandle) + RemoveHandle(tuple model.Tuple) ReteHandle + GetHandle(tuple model.Tuple) ReteHandle + GetHandleByKey(key model.TupleKey) ReteHandle } diff --git a/rete/network.go b/rete/network.go index de5a2cd..3eb407b 100644 --- a/rete/network.go +++ b/rete/network.go @@ -28,7 +28,8 @@ type reteNetworkImpl struct { //Holds the Rule name as key and a pointer to a slice of NodeLinks as value ruleNameClassNodeLinksOfRule map[string]*list.List //*list.List of ClassNodeLink - allHandles map[string]types.ReteHandle + //allHandles map[string]types.ReteHandle + allHandles types.HandleCollection currentId int @@ -37,30 +38,33 @@ type reteNetworkImpl struct { txnHandler model.RtcTransactionHandler txnContext interface{} - allJoinTables map[int]types.JoinTable - //allJoinTables types.JoinTableCollection - config map[string]string + //allJoinTables map[int]types.JoinTable + allJoinTables types.JoinTableCollection + config map[string]string factory *TypeFactory } //NewReteNetwork ... creates a new rete network -func NewReteNetwork() types.Network { +func NewReteNetwork(config map[string]string) types.Network { reteNetworkImpl := reteNetworkImpl{} - reteNetworkImpl.initReteNetwork() + reteNetworkImpl.initReteNetwork(config) return &reteNetworkImpl } -func (nw *reteNetworkImpl) initReteNetwork() { +func (nw *reteNetworkImpl) initReteNetwork(config map[string]string) { nw.currentId = 0 nw.allRules = make(map[string]model.Rule) nw.allClassNodes = make(map[string]classNode) nw.ruleNameNodesOfRule = make(map[string]*list.List) nw.ruleNameClassNodeLinksOfRule = make(map[string]*list.List) - nw.allHandles = make(map[string]types.ReteHandle) - nw.allJoinTables = make(map[int]types.JoinTable) + //nw.allHandles = make(map[string]types.ReteHandle) //nw.allJoinTables = - nw.factory = NewFactory(nw, nil) + nw.factory = NewFactory(nw, config) + + nw.allJoinTables = nw.factory.getJoinTableCollection() + nw.allHandles = nw.factory.getHandleCollection() + } func (nw *reteNetworkImpl) AddRule(rule model.Rule) (err error) { @@ -542,9 +546,8 @@ func (nw *reteNetworkImpl) Assert(ctx context.Context, rs model.RuleSession, tup } func (nw *reteNetworkImpl) removeTupleFromRete(tuple model.Tuple) { - reteHandle, found := nw.allHandles[tuple.GetKey().String()] - if found && reteHandle != nil { - delete(nw.allHandles, tuple.GetKey().String()) + reteHandle := nw.allHandles.RemoveHandle(tuple) + if reteHandle != nil { reteHandle.RemoveJoinTableRowRefs(nil) } } @@ -575,7 +578,7 @@ func (nw *reteNetworkImpl) retractInternal(ctx context.Context, tuple model.Tupl } rCtx, _, _ := getOrSetReteCtx(ctx, nw, nil) - reteHandle := nw.allHandles[tuple.GetKey().String()] + reteHandle := nw.allHandles.RemoveHandle(tuple) if reteHandle != nil { reteHandle.RemoveJoinTableRowRefs(changedProps) @@ -588,16 +591,8 @@ func (nw *reteNetworkImpl) retractInternal(ctx context.Context, tuple model.Tupl } func (nw *reteNetworkImpl) GetAssertedTuple(key model.TupleKey) model.Tuple { - reteHandle, found := nw.allHandles[key.String()] - if found { - return reteHandle.GetTuple() - } - return nil -} - -func (nw *reteNetworkImpl) GetAssertedTupleByStringKey(key string) model.Tuple { - reteHandle, found := nw.allHandles[key] - if found { + reteHandle := nw.allHandles.GetHandleByKey(key) + if reteHandle != nil { return reteHandle.GetTuple() } return nil @@ -622,16 +617,16 @@ func (nw *reteNetworkImpl) assertInternal(ctx context.Context, tuple model.Tuple } func (nw *reteNetworkImpl) getOrCreateHandle(ctx context.Context, tuple model.Tuple) types.ReteHandle { - h := nw.allHandles[tuple.GetKey().String()] + h := nw.allHandles.GetHandle(tuple) if h == nil { h = newReteHandleImpl(nw, tuple) - nw.allHandles[tuple.GetKey().String()] = h + nw.allHandles.AddHandle(h) //[tuple.GetKey().String()] = h } return h } func (nw *reteNetworkImpl) getHandle(tuple model.Tuple) types.ReteHandle { - h := nw.allHandles[tuple.GetKey().String()] + h := nw.allHandles.GetHandleByKey(tuple.GetKey()) return h } @@ -646,7 +641,7 @@ func (nw *reteNetworkImpl) RegisterRtcTransactionHandler(txnHandler model.RtcTra } func (nw *reteNetworkImpl) GetJoinTable(joinTableID int) types.JoinTable { - return nw.allJoinTables[joinTableID] + return nw.allJoinTables.GetJoinTable(joinTableID) } func (nw *reteNetworkImpl) SetConfig(config map[string]string) { @@ -669,5 +664,5 @@ func (nw *reteNetworkImpl) getFactory() *TypeFactory { } func (nw *reteNetworkImpl) AddToAllJoinTables(joinTable types.JoinTable) { - nw.allJoinTables[joinTable.GetID()] = joinTable + nw.allJoinTables.AddJoinTable(joinTable) } diff --git a/rete/retehandle.go b/rete/retehandle.go index 2ac6232..225db64 100644 --- a/rete/retehandle.go +++ b/rete/retehandle.go @@ -29,6 +29,7 @@ func (hdl *reteHandleImpl) SetTuple(tuple model.Tuple) { func (hdl *reteHandleImpl) initHandleImpl(nw *reteNetworkImpl, tuple model.Tuple) { hdl.SetID(nw) hdl.SetTuple(tuple) + hdl.tupleKey = tuple.GetKey() hdl.jtRefs = nw.getFactory().getJoinTableRefs() } diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index e9ab00d..95635e1 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -70,7 +70,7 @@ func GetOrCreateRuleSessionFromConfig(name string, jsonConfig string) (model.Rul } func (rs *rulesessionImpl) initRuleSession(name string) { - rs.reteNetwork = rete.NewReteNetwork() + rs.reteNetwork = rete.NewReteNetwork(nil) rs.name = name rs.timers = make(map[interface{}]*time.Timer) rs.started = false From 61efc06f0e7d3db8ee69170e2a0482492bbb8294 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Wed, 19 Dec 2018 14:56:59 +0530 Subject: [PATCH 017/125] configuration based impl --- common/model/types.go | 2 +- common/services/services.go | 9 + examples/rulesapp/main.go | 8 +- examples/rulesapp/rsconfig.json | 29 +++ examples/trackntrace/trackntrace_test.go | 3 - redisutils/redisutil_test.go | 185 ++++++++++++++++++ redisutils/redisutils.go | 48 +++++ rete/common/types.go | 9 +- rete/factory.go | 120 ++++++++---- .../mhandlecollectionimpl.go} | 11 +- rete/internal/mem/midgenimpl.go | 32 +++ .../mjointablecollectionimpl.go} | 2 +- .../mjointableimpl.go} | 2 +- .../mjointablerefsinhandleimpl.go} | 2 +- .../mjointablerowimpl.go} | 2 +- .../mjointablerowiterator.go} | 2 +- rete/internal/mem/mretehandle.go | 55 ++++++ rete/internal/redis/ridgenimpl.go | 60 ++++++ rete/internal/types/types.go | 10 +- rete/network.go | 105 +++++++--- rete/retehandle.go | 115 ----------- ruleapi/internal/store/mem/mstore.go | 26 +++ ruleapi/rulesession.go | 86 +++++--- 23 files changed, 710 insertions(+), 213 deletions(-) create mode 100644 common/services/services.go create mode 100644 examples/rulesapp/rsconfig.json create mode 100644 redisutils/redisutil_test.go create mode 100644 redisutils/redisutils.go rename rete/internal/{memimpl/handlecollectionimpl.go => mem/mhandlecollectionimpl.go} (75%) create mode 100644 rete/internal/mem/midgenimpl.go rename rete/internal/{memimpl/jointablecollectionimpl.go => mem/mjointablecollectionimpl.go} (97%) rename rete/internal/{memimpl/jointablememimpl.go => mem/mjointableimpl.go} (98%) rename rete/internal/{memimpl/jointablerefsinhandlememimpl.go => mem/mjointablerefsinhandleimpl.go} (98%) rename rete/internal/{memimpl/jointablerowmemimpl.go => mem/mjointablerowimpl.go} (97%) rename rete/internal/{memimpl/jointablerowiterator.go => mem/mjointablerowiterator.go} (97%) create mode 100644 rete/internal/mem/mretehandle.go create mode 100644 rete/internal/redis/ridgenimpl.go delete mode 100644 rete/retehandle.go create mode 100644 ruleapi/internal/store/mem/mstore.go diff --git a/common/model/types.go b/common/model/types.go index 45169bd..3438291 100644 --- a/common/model/types.go +++ b/common/model/types.go @@ -72,7 +72,7 @@ type RuleSession interface { //RtcTransactionHandler RegisterRtcTransactionHandler(txnHandler RtcTransactionHandler, handlerCtx interface{}) - SetConfig(config map[string]string) + //SetConfig(config map[string]string) } //ConditionEvaluator is a function pointer for handling condition evaluations on the server side diff --git a/common/services/services.go b/common/services/services.go new file mode 100644 index 0000000..3fafc0e --- /dev/null +++ b/common/services/services.go @@ -0,0 +1,9 @@ +package services + +import "github.com/project-flogo/rules/common/model" + +type TupleStore interface { + GetTupleByStringKey(key string) model.Tuple + SaveTuple(tuple model.Tuple) + DeleteTupleByStringKey(key string) +} diff --git a/examples/rulesapp/main.go b/examples/rulesapp/main.go index d6ffe24..ea4bf0f 100644 --- a/examples/rulesapp/main.go +++ b/examples/rulesapp/main.go @@ -25,8 +25,9 @@ func main() { return } + content := getFileContent("src/github.com/project-flogo/rules/examples/rulesapp/rsconfig.json") + rs, _ := ruleapi.GetOrCreateRuleSessionFromConfig("asession", string(content)) //Create a RuleSession - rs, _ := ruleapi.GetOrCreateRuleSession("asession") //// check for name "Bob" in n1 rule := ruleapi.NewRule("n1.name == Bob") @@ -123,3 +124,8 @@ func checkSameNamesAction(ctx context.Context, rs model.RuleSession, ruleName st name2, _ := t2.GetString("name") fmt.Printf("n1.name = [%s], n2.name = [%s]\n", name1, name2) } + +func getFileContent(filePath string) string { + absPath := common.GetAbsPathForResource(filePath) + return common.FileToString(absPath) +} diff --git a/examples/rulesapp/rsconfig.json b/examples/rulesapp/rsconfig.json new file mode 100644 index 0000000..f3d07f0 --- /dev/null +++ b/examples/rulesapp/rsconfig.json @@ -0,0 +1,29 @@ +{ + "rs" : { + "store-ref" : "mem" + }, + "rete": { + "jt": "redis", + "idgen" : "redis" + }, + "store": [ + { + "name": "memory" + }, + { + "name": "redis", + "network": "tcp", + "address": ":6379" + } + ], + "idgen-ref": [ + { + "name": "memory" + }, + { + "name": "redis", + "network": "tcp", + "address": ":6379" + } + ] +} \ No newline at end of file diff --git a/examples/trackntrace/trackntrace_test.go b/examples/trackntrace/trackntrace_test.go index acdbd88..6fd19b3 100644 --- a/examples/trackntrace/trackntrace_test.go +++ b/examples/trackntrace/trackntrace_test.go @@ -199,9 +199,6 @@ func TestSameTupleInstanceAssert(t *testing.T) { func createRuleSessionAndRules(t *testing.T) (model.RuleSession, error) { rs, _ := ruleapi.GetOrCreateRuleSession("asession") - props := make(map[string]string) - props["jtstore"] = "memory" - rs.SetConfig(props) tupleDescFileAbsPath := common.GetAbsPathForResource("src/github.com/project-flogo/rules/examples/trackntrace/trackntrace.json") diff --git a/redisutils/redisutil_test.go b/redisutils/redisutil_test.go new file mode 100644 index 0000000..853818f --- /dev/null +++ b/redisutils/redisutil_test.go @@ -0,0 +1,185 @@ +package redisutils + +import ( + "encoding/json" + "fmt" + "github.com/gomodule/redigo/redis" + "testing" +) + +func Test_first(t *testing.T) { + InitService("") + //fmt.Printf("redisutils test...") + rd := GetRedisHdl() + pool := rd.GetPool() + conn := pool.Get() + defer conn.Close() + //err := ping(conn) + //if err != nil { + // fmt.Println(err) + //} + + mset(conn) + mget(conn) +} + +// ping tests connectivity for redisutils (PONG should be returned) +func ping(c redis.Conn) error { + // Send PING command to Redis + pong, err := c.Do("PING") + if err != nil { + return err + } + + // PING command returns a Redis "Simple String" + // Use redisutils.String to convert the interface type to string + s, err := redis.String(pong, err) + if err != nil { + return err + } + + fmt.Printf("PING Response = %s\n", s) + // Output: PONG + + set(c) + get(c) + setStruct(c) + getStruct(c) + return nil +} + +// set executes the redisutils SET command +func set(c redis.Conn) error { + _, err := c.Do("SET", "Favorite Movie", "Repo Man") + if err != nil { + fmt.Printf("Error") + return nil + } + _, err = c.Do("SET", "Release Year", 1984) + if err != nil { + fmt.Printf("Error") + return nil + } + return nil +} + +// get executes the redisutils GET command +func get(c redis.Conn) error { + + // Simple GET example with String helper + key := "Favorite Movie" + s, err := redis.String(c.Do("GET", key)) + if err != nil { + return (err) + } + fmt.Printf("%s = %s\n", key, s) + + // Simple GET example with Int helper + key = "Release Year" + i, err := redis.Int(c.Do("GET", key)) + if err != nil { + return (err) + } + fmt.Printf("%s = %d\n", key, i) + + // Example where GET returns no results + key = "Nonexistent Key" + s, err = redis.String(c.Do("GET", key)) + if err == redis.ErrNil { + fmt.Printf("%s does not exist\n", key) + } else if err != nil { + return err + } else { + fmt.Printf("%s = %s\n", key, s) + } + + return nil +} + +type User struct { + Username string + MobileID int + Email string + FirstName string + LastName string +} + +func setStruct(c redis.Conn) error { + + const objectPrefix string = "user:" + + usr := User{ + Username: "otto", + MobileID: 1234567890, + Email: "ottoM@repoman.com", + FirstName: "Otto", + LastName: "Maddox", + } + + // serialize User object to JSON + json, err := json.Marshal(usr) + if err != nil { + return err + } + + // SET object + _, err = c.Do("SET", objectPrefix+usr.Username, json) + if err != nil { + return err + } + + return nil +} + +func getStruct(c redis.Conn) error { + + const objectPrefix string = "user:" + + username := "otto" + s, err := redis.String(c.Do("GET", objectPrefix+username)) + if err == redis.ErrNil { + fmt.Println("User does not exist") + } else if err != nil { + return err + } + + usr := User{} + err = json.Unmarshal([]byte(s), &usr) + + fmt.Printf("%+v\n", usr) + + return nil + +} + +func mset(c redis.Conn) error { + + vals, error := c.Do("HMSET", "key1", "f1", 1, "f2", "Hi", "f3", 3.14, "f4", true) + if error != nil { + fmt.Printf("error [%v]\n", error) + } else { + fmt.Printf("ret: [%v]\n", vals) + } + return error +} + +func mget(c redis.Conn) { + + vals, error := c.Do("HGETALL", "key1") + if error != nil { + fmt.Printf("error [%v]\n", error) + } else { + vals, err2 := redis.Values(vals, error) + if err2 != nil { + fmt.Printf("error [%v]\n", err2) + } else { + for _, val := range vals { + ba := val.([]byte) + s := string(ba) + fmt.Printf("Value [%s]\n", s) + } + } + + } + //return error +} diff --git a/redisutils/redisutils.go b/redisutils/redisutils.go new file mode 100644 index 0000000..a4c9b7c --- /dev/null +++ b/redisutils/redisutils.go @@ -0,0 +1,48 @@ +package redisutils + +import ( + "github.com/gomodule/redigo/redis" +) + +var rd RedisHdl + +type RedisHdl = *RedisHandle + +type RedisHandle struct { + config string + pool *redis.Pool +} + +func InitService(config string) { + if rd == nil { + rd = &RedisHandle{} + rd.config = config + rd.newPool("tcp", ":6379") + } +} + +func GetRedisHdl() RedisHdl { + return rd +} + +func (rh *RedisHandle) newPool(network string, address string) { + rh.pool = &redis.Pool{ + // Maximum number of idle connections in the pool. + MaxIdle: 80, + // max number of connections + MaxActive: 12000, + // Dial is an application supplied function for creating and + // configuring a connection. + Dial: func() (redis.Conn, error) { + c, err := redis.Dial(network, address) + if err != nil { + panic(err.Error()) + } + return c, err + }, + } +} + +func (rh *RedisHandle) GetPool() *redis.Pool { + return rh.pool +} diff --git a/rete/common/types.go b/rete/common/types.go index 611e750..3904048 100644 --- a/rete/common/types.go +++ b/rete/common/types.go @@ -3,6 +3,7 @@ package common import ( "context" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/common/services" ) type RtcOprn int @@ -24,7 +25,9 @@ type Network interface { Retract(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) GetAssertedTuple(key model.TupleKey) model.Tuple RegisterRtcTransactionHandler(txnHandler model.RtcTransactionHandler, txnContext interface{}) - SetConfig(config map[string]string) - GetConfigValue(key string) string - GetConfig() map[string]string + //SetConfig(config map[string]string) + //GetConfigValue(key string) string + //GetConfig() map[string]string + + SetTupleStore(tupleStore services.TupleStore) } diff --git a/rete/factory.go b/rete/factory.go index 5231f3c..af22f2a 100644 --- a/rete/factory.go +++ b/rete/factory.go @@ -1,73 +1,119 @@ package rete import ( + "encoding/json" "github.com/project-flogo/rules/common/model" - "github.com/project-flogo/rules/rete/internal/memimpl" + "github.com/project-flogo/rules/rete/internal/mem" + "github.com/project-flogo/rules/rete/internal/redis" "github.com/project-flogo/rules/rete/internal/types" ) type TypeFactory struct { - nw *reteNetworkImpl - config map[string]string + nw *reteNetworkImpl + config string + parsedJson map[string]interface{} } -func NewFactory(nw *reteNetworkImpl, config map[string]string) *TypeFactory { - tf := TypeFactory{nw, config} +func NewFactory(nw *reteNetworkImpl, config string) *TypeFactory { + tf := TypeFactory{} + tf.config = config + json.Unmarshal([]byte(config), &tf.parsedJson) + tf.nw = nw + return &tf } func (f *TypeFactory) getJoinTable(rule model.Rule, identifiers []model.TupleType) types.JoinTable { - if f.config == nil { - jt := memimpl.NewJoinTable(f.nw, rule, identifiers) - return jt + var jt types.JoinTable + if f.parsedJson == nil { + jt = mem.NewJoinTable(f.nw, rule, identifiers) + } else { - jtStore := f.config["jtstore"] - if jtStore == "" || jtStore == "memory" { - jt := memimpl.NewJoinTable(f.nw, rule, identifiers) - return jt + rete := f.parsedJson["rete"].(map[string]interface{}) + if rete != nil { + idgen := rete["jt"].(string) + if idgen == "" || idgen == "mem" { + jt = mem.NewJoinTable(f.nw, rule, identifiers) + } else if idgen == "redis" { + jt = mem.NewJoinTable(f.nw, rule, identifiers) + } } } - return nil + return jt } func (f *TypeFactory) getJoinTableRefs() types.JoinTableRefsInHdl { - if f.config == nil { - jtRef := memimpl.NewJoinTableRefsInHdlImpl() - return jtRef + var jtRefs types.JoinTableRefsInHdl + if f.parsedJson == nil { + jtRefs = mem.NewJoinTableRefsInHdlImpl() + } else { - jtType := f.config["jtstore"] - if jtType == "" || jtType == "memory" { - jtRef := memimpl.NewJoinTableRefsInHdlImpl() - return jtRef + rete := f.parsedJson["rete"].(map[string]interface{}) + if rete != nil { + idgen := rete["jt"].(string) + if idgen == "" || idgen == "mem" { + jtRefs = mem.NewJoinTableRefsInHdlImpl() + } else if idgen == "redis" { + jtRefs = mem.NewJoinTableRefsInHdlImpl() + } } } - return nil + return jtRefs } func (f *TypeFactory) getJoinTableCollection() types.JoinTableCollection { - if f.config == nil { - jtRef := memimpl.NewJoinTableCollection() - return jtRef + var allJt types.JoinTableCollection + if f.parsedJson == nil { + allJt = mem.NewJoinTableCollection() + } else { - jtType := f.config["jtstore"] - if jtType == "" || jtType == "memory" { - jtRef := memimpl.NewJoinTableCollection() - return jtRef + rete := f.parsedJson["rete"].(map[string]interface{}) + if rete != nil { + idgen := rete["jt"].(string) + if idgen == "" || idgen == "mem" { + allJt = mem.NewJoinTableCollection() + } else if idgen == "redis" { + allJt = mem.NewJoinTableCollection() + } } } - return nil + return allJt } func (f *TypeFactory) getHandleCollection() types.HandleCollection { - if f.config == nil { - hc := memimpl.NewHandleCollection() - return hc + var hc types.HandleCollection + if f.parsedJson == nil { + hc = mem.NewHandleCollection() + } else { + rete := f.parsedJson["rete"].(map[string]interface{}) + if rete != nil { + idgen := rete["jt"].(string) + if idgen == "" || idgen == "mem" { + hc = mem.NewHandleCollection() + } else if idgen == "redis" { + hc = mem.NewHandleCollection() + } + } + } + return hc +} + +func (f *TypeFactory) getIdGen() types.IdGen { + var idg types.IdGen + if f.parsedJson == nil { + idg = mem.NewIdImpl(f.config) + return idg } else { - jtType := f.config["jtstore"] - if jtType == "" || jtType == "memory" { - hc := memimpl.NewHandleCollection() - return hc + rete := f.parsedJson["rete"].(map[string]interface{}) + if rete != nil { + + idgen := rete["idgen"].(string) + if idgen == "" || idgen == "mem" { + idg = mem.NewIdImpl(f.config) + } else if idgen == "redis" { + idg = redis.NewIdImpl(f.config) + } } } - return nil + return idg } diff --git a/rete/internal/memimpl/handlecollectionimpl.go b/rete/internal/mem/mhandlecollectionimpl.go similarity index 75% rename from rete/internal/memimpl/handlecollectionimpl.go rename to rete/internal/mem/mhandlecollectionimpl.go index b6717f0..8cd0f94 100644 --- a/rete/internal/memimpl/handlecollectionimpl.go +++ b/rete/internal/mem/mhandlecollectionimpl.go @@ -1,4 +1,4 @@ -package memimpl +package mem import ( "github.com/project-flogo/rules/common/model" @@ -35,3 +35,12 @@ func (hc *handleCollectionImpl) GetHandle(tuple model.Tuple) types.ReteHandle { func (hc *handleCollectionImpl) GetHandleByKey(key model.TupleKey) types.ReteHandle { return hc.allHandles[key.String()] } + +func (hc *handleCollectionImpl) GetOrCreateHandle(nw types.Network, tuple model.Tuple) types.ReteHandle { + h, found := hc.allHandles[tuple.GetKey().String()] + if !found { + h = newReteHandleImpl(nw, tuple) + hc.allHandles[tuple.GetKey().String()] = h //[tuple.GetKey().String()] = h + } + return h +} diff --git a/rete/internal/mem/midgenimpl.go b/rete/internal/mem/midgenimpl.go new file mode 100644 index 0000000..25f17d2 --- /dev/null +++ b/rete/internal/mem/midgenimpl.go @@ -0,0 +1,32 @@ +package mem + +import ( + "github.com/project-flogo/rules/rete/internal/types" + "sync/atomic" +) + +type idGenImpl struct { + config string + currentId int32 +} + +func NewIdImpl(config string) types.IdGen { + idg := idGenImpl{} + idg.config = config + idg.currentId = 0 + return &idg +} + +func (id *idGenImpl) Init() { + id.currentId = int32(id.GetMaxID()) +} + +func (id *idGenImpl) GetNextID() int { + i := atomic.AddInt32(&id.currentId, 1) + return int(i) +} + +func (id *idGenImpl) GetMaxID() int { + i := atomic.LoadInt32(&id.currentId) + return int(i) +} diff --git a/rete/internal/memimpl/jointablecollectionimpl.go b/rete/internal/mem/mjointablecollectionimpl.go similarity index 97% rename from rete/internal/memimpl/jointablecollectionimpl.go rename to rete/internal/mem/mjointablecollectionimpl.go index b621a0c..1131527 100644 --- a/rete/internal/memimpl/jointablecollectionimpl.go +++ b/rete/internal/mem/mjointablecollectionimpl.go @@ -1,4 +1,4 @@ -package memimpl +package mem import "github.com/project-flogo/rules/rete/internal/types" diff --git a/rete/internal/memimpl/jointablememimpl.go b/rete/internal/mem/mjointableimpl.go similarity index 98% rename from rete/internal/memimpl/jointablememimpl.go rename to rete/internal/mem/mjointableimpl.go index ff4ffc8..e645337 100644 --- a/rete/internal/memimpl/jointablememimpl.go +++ b/rete/internal/mem/mjointableimpl.go @@ -1,4 +1,4 @@ -package memimpl +package mem import ( "github.com/project-flogo/rules/common/model" diff --git a/rete/internal/memimpl/jointablerefsinhandlememimpl.go b/rete/internal/mem/mjointablerefsinhandleimpl.go similarity index 98% rename from rete/internal/memimpl/jointablerefsinhandlememimpl.go rename to rete/internal/mem/mjointablerefsinhandleimpl.go index 0de0048..be5b658 100644 --- a/rete/internal/memimpl/jointablerefsinhandlememimpl.go +++ b/rete/internal/mem/mjointablerefsinhandleimpl.go @@ -1,4 +1,4 @@ -package memimpl +package mem import ( "container/list" diff --git a/rete/internal/memimpl/jointablerowmemimpl.go b/rete/internal/mem/mjointablerowimpl.go similarity index 97% rename from rete/internal/memimpl/jointablerowmemimpl.go rename to rete/internal/mem/mjointablerowimpl.go index 0c63091..8afa750 100644 --- a/rete/internal/memimpl/jointablerowmemimpl.go +++ b/rete/internal/mem/mjointablerowimpl.go @@ -1,4 +1,4 @@ -package memimpl +package mem import "github.com/project-flogo/rules/rete/internal/types" diff --git a/rete/internal/memimpl/jointablerowiterator.go b/rete/internal/mem/mjointablerowiterator.go similarity index 97% rename from rete/internal/memimpl/jointablerowiterator.go rename to rete/internal/mem/mjointablerowiterator.go index e25fe94..be4271b 100644 --- a/rete/internal/memimpl/jointablerowiterator.go +++ b/rete/internal/mem/mjointablerowiterator.go @@ -1,4 +1,4 @@ -package memimpl +package mem import ( "container/list" diff --git a/rete/internal/mem/mretehandle.go b/rete/internal/mem/mretehandle.go new file mode 100644 index 0000000..dd4ac95 --- /dev/null +++ b/rete/internal/mem/mretehandle.go @@ -0,0 +1,55 @@ +package mem + +import ( + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/internal/types" +) + +//Holds a tuple reference and related state + +type reteHandleImpl struct { + types.NwElemIdImpl + tuple model.Tuple + tupleKey model.TupleKey + jtRefs types.JoinTableRefsInHdl +} + +func newReteHandleImpl(nw types.Network, tuple model.Tuple) types.ReteHandle { + h1 := reteHandleImpl{} + h1.initHandleImpl(nw, tuple) + return &h1 +} + +func (hdl *reteHandleImpl) SetTuple(tuple model.Tuple) { + hdl.tuple = tuple + hdl.tupleKey = tuple.GetKey() +} + +func (hdl *reteHandleImpl) initHandleImpl(nw types.Network, tuple model.Tuple) { + hdl.SetID(nw) + hdl.SetTuple(tuple) + hdl.tupleKey = tuple.GetKey() + hdl.jtRefs = NewJoinTableRefsInHdlImpl() +} + +func (hdl *reteHandleImpl) GetTuple() model.Tuple { + return hdl.tuple +} + +func (hdl *reteHandleImpl) GetTupleKey() model.TupleKey { + return hdl.tupleKey +} + +func (hdl *reteHandleImpl) AddJoinTableRowRef(joinTableRowVar types.JoinTableRow, joinTableVar types.JoinTable) { + hdl.jtRefs.AddEntry(joinTableVar.GetID(), joinTableRowVar.GetID()) +} + +//Used when a rule is deleted. See Network.RemoveRule +func (hdl *reteHandleImpl) RemoveJoinTable(joinTableID int) { + hdl.jtRefs.RemoveEntry(joinTableID) +} + +func (hdl *reteHandleImpl) GetRefTableIterator() types.HdlTblIterator { + refTblIteator := hdl.jtRefs.GetIterator() + return refTblIteator +} diff --git a/rete/internal/redis/ridgenimpl.go b/rete/internal/redis/ridgenimpl.go new file mode 100644 index 0000000..1054a8c --- /dev/null +++ b/rete/internal/redis/ridgenimpl.go @@ -0,0 +1,60 @@ +package redis + +import ( + "fmt" + "github.com/gomodule/redigo/redis" + "github.com/project-flogo/rules/redisutils" + "github.com/project-flogo/rules/rete/internal/types" +) + +const ( + hget = "HGET" + hincrby = "HINCRBY" +) + +type ridImpl struct { + config string + //current int + rh redisutils.RedisHdl +} + +func NewIdImpl(config string) types.IdGen { + r := ridImpl{} + r.config = config + return &r +} + +func (ri *ridImpl) Init() { + redisutils.InitService(ri.config) + j := ri.GetMaxID() + fmt.Printf("maxid : [%d]\n ", j) +} + +func (ri *ridImpl) GetMaxID() int { + ri.rh = redisutils.GetRedisHdl() + c := ri.rh.GetPool().Get() + defer c.Close() + + i, err := c.Do(hget, "IDGEN", "ID") + if err == nil { + j, _ := redis.Int(i, err) + return j + } + return -1 +} + +func (ri *ridImpl) GetNextID() int { + ri.rh = redisutils.GetRedisHdl() + c := ri.rh.GetPool().Get() + defer c.Close() + + i, err := c.Do(hincrby, "IDGEN", "ID", 1) + + if err != nil { + fmt.Printf("error: [%s]", err) + return -1 + } + current := int(i.(int64)) + return current + +} diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index ab0dadd..213522d 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -8,6 +8,7 @@ import ( type Network interface { common.Network + IncrementAndGetId() int GetJoinTable(joinTableID int) JoinTable AddToAllJoinTables(jT JoinTable) @@ -51,7 +52,7 @@ type ReteHandle interface { SetTuple(tuple model.Tuple) GetTuple() model.Tuple AddJoinTableRowRef(joinTableRowVar JoinTableRow, joinTableVar JoinTable) - RemoveJoinTableRowRefs(changedProps map[string]bool) + //removeJoinTableRowRefs(changedProps map[string]bool) RemoveJoinTable(joinTableID int) GetTupleKey() model.TupleKey GetRefTableIterator() HdlTblIterator @@ -83,4 +84,11 @@ type HandleCollection interface { RemoveHandle(tuple model.Tuple) ReteHandle GetHandle(tuple model.Tuple) ReteHandle GetHandleByKey(key model.TupleKey) ReteHandle + GetOrCreateHandle(nw Network, tuple model.Tuple) ReteHandle +} + +type IdGen interface { + Init() + GetMaxID() int + GetNextID() int } diff --git a/rete/network.go b/rete/network.go index 3eb407b..31d7cab 100644 --- a/rete/network.go +++ b/rete/network.go @@ -12,6 +12,7 @@ import ( "sync" "time" //"github.com/project-flogo/rules/rete/common" + "github.com/project-flogo/rules/common/services" "github.com/project-flogo/rules/rete/common" ) @@ -31,8 +32,6 @@ type reteNetworkImpl struct { //allHandles map[string]types.ReteHandle allHandles types.HandleCollection - currentId int - assertLock sync.Mutex crudLock sync.Mutex txnHandler model.RtcTransactionHandler @@ -42,29 +41,36 @@ type reteNetworkImpl struct { allJoinTables types.JoinTableCollection config map[string]string - factory *TypeFactory + factory *TypeFactory + idGen types.IdGen + tupleStore services.TupleStore } //NewReteNetwork ... creates a new rete network -func NewReteNetwork(config map[string]string) types.Network { +func NewReteNetwork(jsonConfig string) types.Network { reteNetworkImpl := reteNetworkImpl{} - reteNetworkImpl.initReteNetwork(config) + reteNetworkImpl.initReteNetwork(jsonConfig) return &reteNetworkImpl } -func (nw *reteNetworkImpl) initReteNetwork(config map[string]string) { - nw.currentId = 0 +func (nw *reteNetworkImpl) initReteNetwork(config string) { + //nw.currentId = 0 nw.allRules = make(map[string]model.Rule) nw.allClassNodes = make(map[string]classNode) nw.ruleNameNodesOfRule = make(map[string]*list.List) nw.ruleNameClassNodeLinksOfRule = make(map[string]*list.List) - //nw.allHandles = make(map[string]types.ReteHandle) - //nw.allJoinTables = + nw.factory = NewFactory(nw, config) + nw.idGen = nw.factory.getIdGen() nw.allJoinTables = nw.factory.getJoinTableCollection() nw.allHandles = nw.factory.getHandleCollection() + nw.initNwServices() +} + +func (nw *reteNetworkImpl) initNwServices() { + nw.idGen.Init() } func (nw *reteNetworkImpl) AddRule(rule model.Rule) (err error) { @@ -548,7 +554,7 @@ func (nw *reteNetworkImpl) Assert(ctx context.Context, rs model.RuleSession, tup func (nw *reteNetworkImpl) removeTupleFromRete(tuple model.Tuple) { reteHandle := nw.allHandles.RemoveHandle(tuple) if reteHandle != nil { - reteHandle.RemoveJoinTableRowRefs(nil) + nw.removeJoinTableRowRefs(reteHandle, nil) } } @@ -580,7 +586,7 @@ func (nw *reteNetworkImpl) retractInternal(ctx context.Context, tuple model.Tupl reteHandle := nw.allHandles.RemoveHandle(tuple) if reteHandle != nil { - reteHandle.RemoveJoinTableRowRefs(changedProps) + nw.removeJoinTableRowRefs(reteHandle, changedProps) //add it to the delete list if mode == common.DELETE { @@ -617,11 +623,7 @@ func (nw *reteNetworkImpl) assertInternal(ctx context.Context, tuple model.Tuple } func (nw *reteNetworkImpl) getOrCreateHandle(ctx context.Context, tuple model.Tuple) types.ReteHandle { - h := nw.allHandles.GetHandle(tuple) - if h == nil { - h = newReteHandleImpl(nw, tuple) - nw.allHandles.AddHandle(h) //[tuple.GetKey().String()] = h - } + h := nw.allHandles.GetOrCreateHandle(nw, tuple) return h } @@ -631,8 +633,7 @@ func (nw *reteNetworkImpl) getHandle(tuple model.Tuple) types.ReteHandle { } func (nw *reteNetworkImpl) IncrementAndGetId() int { - nw.currentId++ - return nw.currentId + return nw.idGen.GetNextID() } func (nw *reteNetworkImpl) RegisterRtcTransactionHandler(txnHandler model.RtcTransactionHandler, txnContext interface{}) { @@ -644,12 +645,6 @@ func (nw *reteNetworkImpl) GetJoinTable(joinTableID int) types.JoinTable { return nw.allJoinTables.GetJoinTable(joinTableID) } -func (nw *reteNetworkImpl) SetConfig(config map[string]string) { - nw.config = config - nw.factory = &TypeFactory{nw, config} - -} - func (nw *reteNetworkImpl) GetConfigValue(key string) string { val, _ := nw.config[key] return val @@ -666,3 +661,65 @@ func (nw *reteNetworkImpl) getFactory() *TypeFactory { func (nw *reteNetworkImpl) AddToAllJoinTables(joinTable types.JoinTable) { nw.allJoinTables.AddJoinTable(joinTable) } + +func (nw *reteNetworkImpl) SetTupleStore(tupleStore services.TupleStore) { + nw.tupleStore = tupleStore +} + +func getOrCreateHandle(ctx context.Context, tuple model.Tuple) types.ReteHandle { + reteCtxVar := getReteCtx(ctx) + return reteCtxVar.getNetwork().getOrCreateHandle(ctx, tuple) +} + +func (nw *reteNetworkImpl) removeJoinTableRowRefs(hdl types.ReteHandle, changedProps map[string]bool) { + + tuple := hdl.GetTuple() + alias := tuple.GetTupleType() + + hdlTblIter := hdl.GetRefTableIterator() + + for hdlTblIter.HasNext() { + joinTableID, rowIDs := hdlTblIter.Next() + joinTable := nw.GetJoinTable(joinTableID) + toDelete := false + if changedProps != nil { + rule := joinTable.GetRule() + depProps, found := rule.GetDeps()[alias] + if found { // rule depends on this type + for changedProp := range changedProps { + _, foundProp := depProps[changedProp] + if foundProp { + toDelete = true + break + } + } + } + } else { + toDelete = true + } + + if !toDelete { + continue + } + //this can happen if some other handle removed a row as a result of retraction + if rowIDs == nil { + continue + } + ////Remove rows from corresponding join tables + for e := rowIDs.Front(); e != nil; e = e.Next() { + rowID := e.Value.(int) + row := joinTable.RemoveRow(rowID) + if row != nil { + //Remove other refs recursively. + for _, otherHdl := range row.GetHandles() { + if otherHdl != nil { + nw.removeJoinTableRowRefs(otherHdl, nil) + } + } + } + } + + //Remove the reference to the table itself + hdl.RemoveJoinTable(joinTableID) + } +} diff --git a/rete/retehandle.go b/rete/retehandle.go deleted file mode 100644 index 225db64..0000000 --- a/rete/retehandle.go +++ /dev/null @@ -1,115 +0,0 @@ -package rete - -import ( - "context" - - "github.com/project-flogo/rules/common/model" - "github.com/project-flogo/rules/rete/internal/types" -) - -//Holds a tuple reference and related state - -type reteHandleImpl struct { - types.NwElemIdImpl - tuple model.Tuple - tupleKey model.TupleKey - jtRefs types.JoinTableRefsInHdl -} - -func newReteHandleImpl(nw *reteNetworkImpl, tuple model.Tuple) types.ReteHandle { - h1 := reteHandleImpl{} - h1.initHandleImpl(nw, tuple) - return &h1 -} - -func (hdl *reteHandleImpl) SetTuple(tuple model.Tuple) { - hdl.tuple = tuple -} - -func (hdl *reteHandleImpl) initHandleImpl(nw *reteNetworkImpl, tuple model.Tuple) { - hdl.SetID(nw) - hdl.SetTuple(tuple) - hdl.tupleKey = tuple.GetKey() - hdl.jtRefs = nw.getFactory().getJoinTableRefs() -} - -func (hdl *reteHandleImpl) GetTuple() model.Tuple { - return hdl.tuple -} - -func (hdl *reteHandleImpl) GetTupleKey() model.TupleKey { - return hdl.tupleKey -} - -func getOrCreateHandle(ctx context.Context, tuple model.Tuple) types.ReteHandle { - reteCtxVar := getReteCtx(ctx) - return reteCtxVar.getNetwork().getOrCreateHandle(ctx, tuple) -} - -func (hdl *reteHandleImpl) AddJoinTableRowRef(joinTableRowVar types.JoinTableRow, joinTableVar types.JoinTable) { - hdl.jtRefs.AddEntry(joinTableVar.GetID(), joinTableRowVar.GetID()) -} - -func (hdl *reteHandleImpl) RemoveJoinTableRowRefs(changedProps map[string]bool) { - - tuple := hdl.tuple - alias := tuple.GetTupleType() - - hdlTblIter := hdl.GetRefTableIterator() - - for hdlTblIter.HasNext() { - joinTableID, rowIDs := hdlTblIter.Next() - joinTable := hdl.Nw.GetJoinTable(joinTableID) - toDelete := false - if changedProps != nil { - rule := joinTable.GetRule() - depProps, found := rule.GetDeps()[alias] - if found { // rule depends on this type - for changedProp := range changedProps { - _, foundProp := depProps[changedProp] - if foundProp { - toDelete = true - break - } - } - } - } else { - toDelete = true - } - - if !toDelete { - continue - } - //this can happen if some other handle removed a row as a result of retraction - if rowIDs == nil { - continue - } - ////Remove rows from corresponding join tables - for e := rowIDs.Front(); e != nil; e = e.Next() { - rowID := e.Value.(int) - row := joinTable.RemoveRow(rowID) - if row != nil { - //Remove other refs recursively. - for _, otherHdl := range row.GetHandles() { - //if otherHdl != nil { - otherHdl.RemoveJoinTableRowRefs(nil) - //} - } - } - } - - //Remove the reference to the table itself - hdl.jtRefs.RemoveEntry(joinTableID) - } -} - -//Used when a rule is deleted. See Network.RemoveRule -func (hdl *reteHandleImpl) RemoveJoinTable(joinTableID int) { - hdl.jtRefs.RemoveEntry(joinTableID) -} - -func (hdl *reteHandleImpl) GetRefTableIterator() types.HdlTblIterator { - refTblIteator := hdl.jtRefs.GetIterator() - return refTblIteator - -} diff --git a/ruleapi/internal/store/mem/mstore.go b/ruleapi/internal/store/mem/mstore.go new file mode 100644 index 0000000..18586e1 --- /dev/null +++ b/ruleapi/internal/store/mem/mstore.go @@ -0,0 +1,26 @@ +package mem + +import ( + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/common/services" +) + +type store struct { + allTuples map[string]model.Tuple +} + +func NewStore() services.TupleStore { + ms := store{} + ms.allTuples = make(map[string]model.Tuple) + return &ms +} + +func (ms *store) GetTupleByStringKey(key string) model.Tuple { + return ms.allTuples[key] +} +func (ms *store) SaveTuple(tuple model.Tuple) { + ms.allTuples[tuple.GetKey().String()] = tuple +} +func (ms *store) DeleteTupleByStringKey(key string) { + delete(ms.allTuples, key) +} diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index 95635e1..8919302 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -9,9 +9,11 @@ import ( "time" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/common/services" "github.com/project-flogo/rules/config" "github.com/project-flogo/rules/rete" "github.com/project-flogo/rules/rete/common" + "github.com/project-flogo/rules/ruleapi/internal/store/mem" ) var ( @@ -22,10 +24,12 @@ type rulesessionImpl struct { name string reteNetwork common.Network - timers map[interface{}]*time.Timer - startupFn model.StartupRSFunction - started bool - config map[string]string + timers map[interface{}]*time.Timer + startupFn model.StartupRSFunction + started bool + config map[string]string + tupleStore services.TupleStore + jsonConfig map[string]interface{} } func GetOrCreateRuleSession(name string) (model.RuleSession, error) { @@ -39,14 +43,17 @@ func GetOrCreateRuleSession(name string) (model.RuleSession, error) { } func GetOrCreateRuleSessionFromConfig(name string, jsonConfig string) (model.RuleSession, error) { - rs, err := GetOrCreateRuleSession(name) - - if err != nil { - return nil, err + if name == "" { + return nil, errors.New("RuleSession name cannot be empty") } + rs := rulesessionImpl{} + rs.initRuleSessionWithConfig(name, jsonConfig) + //existing, _ := sessionMap.LoadOrStore(name, &rs) + //rs1 := existing.(*rulesessionImpl) + //rs = *rs1 ruleSessionDescriptor := config.RuleSessionDescriptor{} - err = json.Unmarshal([]byte(jsonConfig), &ruleSessionDescriptor) + err := json.Unmarshal([]byte(jsonConfig), &ruleSessionDescriptor) if err != nil { return nil, err } @@ -66,16 +73,49 @@ func GetOrCreateRuleSessionFromConfig(name string, jsonConfig string) (model.Rul rs.SetStartupFunction(config.GetStartupRSFunction(name)) - return rs, nil + return &rs, nil } func (rs *rulesessionImpl) initRuleSession(name string) { - rs.reteNetwork = rete.NewReteNetwork(nil) + rs.reteNetwork = rete.NewReteNetwork("") rs.name = name rs.timers = make(map[interface{}]*time.Timer) rs.started = false } +func (rs *rulesessionImpl) initRuleSessionWithConfig(name string, jsonConfig string) error { + + err := json.Unmarshal([]byte(jsonConfig), &rs.jsonConfig) + if err != nil { + return err + } + + rs.name = name + rs.timers = make(map[interface{}]*time.Timer) + + //TODO: Configure it from jconsonfig + rs.tupleStore = getTupleStore(rs.jsonConfig) + + rs.reteNetwork = rete.NewReteNetwork(jsonConfig) + rs.reteNetwork.SetTupleStore(rs.tupleStore) + + rs.started = false + return nil +} + +func getTupleStore(jsonConfig map[string]interface{}) services.TupleStore { + rsCfg := jsonConfig["rs"].(map[string]interface{}) + + storeRef := rsCfg["store-ref"].(string) + + if storeRef == "" || storeRef == "mem" { + return mem.NewStore() + } else if storeRef == "redisutils" { + return mem.NewStore() + } + return nil +} + func (rs *rulesessionImpl) AddRule(rule model.Rule) (err error) { return rs.reteNetwork.AddRule(rule) } @@ -93,9 +133,10 @@ func (rs *rulesessionImpl) Assert(ctx context.Context, tuple model.Tuple) (err e return fmt.Errorf("Cannot assert tuple. Rulesession [%s] not started", rs.name) } assertedTuple := rs.GetAssertedTuple(tuple.GetKey()) - if assertedTuple == tuple { - return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) - } else if assertedTuple != nil { + //if assertedTuple == tuple { + // return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) + //} else + if assertedTuple != nil { return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) } if ctx == nil { @@ -177,11 +218,12 @@ func (rs *rulesessionImpl) RegisterRtcTransactionHandler(txnHandler model.RtcTra rs.reteNetwork.RegisterRtcTransactionHandler(txnHandler, txnContext) } -func (rs *rulesessionImpl) SetConfig(config map[string]string) { - if rs.config == nil { - rs.config = config - } - if rs.reteNetwork != nil && rs.reteNetwork.GetConfig() == nil { - rs.reteNetwork.SetConfig(config) - } -} +// +//func (rs *rulesessionImpl) SetConfig(config map[string]string) { +// if rs.config == nil { +// rs.config = config +// } +// if rs.reteNetwork != nil && rs.reteNetwork.GetConfig() == nil { +// rs.reteNetwork.SetConfig(config) +// } +//} From 448b0b6e2d4d1f05cda058c8d132ce2c72fe20db Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Thu, 20 Dec 2018 13:55:00 +0530 Subject: [PATCH 018/125] need the condition name to get correponding join tables --- rete/factory.go | 9 ++++----- rete/internal/mem/mjointableimpl.go | 2 +- rete/joinnode.go | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/rete/factory.go b/rete/factory.go index af22f2a..e2f67aa 100644 --- a/rete/factory.go +++ b/rete/factory.go @@ -23,19 +23,18 @@ func NewFactory(nw *reteNetworkImpl, config string) *TypeFactory { return &tf } -func (f *TypeFactory) getJoinTable(rule model.Rule, identifiers []model.TupleType) types.JoinTable { +func (f *TypeFactory) getJoinTable(rule model.Rule, conditionVar model.Condition, identifiers []model.TupleType) types.JoinTable { var jt types.JoinTable if f.parsedJson == nil { - jt = mem.NewJoinTable(f.nw, rule, identifiers) - + jt = mem.CreateOrGetJoinTable(f.nw, rule, conditionVar, identifiers) } else { rete := f.parsedJson["rete"].(map[string]interface{}) if rete != nil { idgen := rete["jt"].(string) if idgen == "" || idgen == "mem" { - jt = mem.NewJoinTable(f.nw, rule, identifiers) + jt = mem.CreateOrGetJoinTable(f.nw, rule, conditionVar, identifiers) } else if idgen == "redis" { - jt = mem.NewJoinTable(f.nw, rule, identifiers) + jt = mem.CreateOrGetJoinTable(f.nw, rule, conditionVar, identifiers) } } } diff --git a/rete/internal/mem/mjointableimpl.go b/rete/internal/mem/mjointableimpl.go index e645337..0576f1d 100644 --- a/rete/internal/mem/mjointableimpl.go +++ b/rete/internal/mem/mjointableimpl.go @@ -12,7 +12,7 @@ type joinTableImpl struct { rule model.Rule } -func NewJoinTable(nw types.Network, rule model.Rule, identifiers []model.TupleType) types.JoinTable { +func CreateOrGetJoinTable(nw types.Network, rule model.Rule, conditionVar model.Condition, identifiers []model.TupleType) types.JoinTable { jT := joinTableImpl{} jT.initJoinTableImpl(nw, rule, identifiers) diff --git a/rete/joinnode.go b/rete/joinnode.go index 8fb2c3d..388636f 100644 --- a/rete/joinnode.go +++ b/rete/joinnode.go @@ -42,8 +42,8 @@ func (jn *joinNodeImpl) initjoinNodeImplVar(nw *reteNetworkImpl, rule model.Rule jn.leftIdrs = leftIdrs jn.rightIdrs = rightIdrs jn.conditionVar = conditionVar - jn.leftTable = nw.getFactory().getJoinTable(rule, leftIdrs) - jn.rightTable = nw.getFactory().getJoinTable(rule, rightIdrs) + jn.leftTable = nw.getFactory().getJoinTable(rule, conditionVar, leftIdrs) + jn.rightTable = nw.getFactory().getJoinTable(rule, conditionVar, rightIdrs) jn.setJoinIdentifiers() } From 204eec43d817a7a82afaac3a9eb741cceb80cdad Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Sun, 23 Dec 2018 17:15:05 +0530 Subject: [PATCH 019/125] refactor --- common/services/services.go | 5 ++ redisutils/redisutils.go | 4 +- rete/factory.go | 36 +++++------ rete/internal/mem/mhandlecollectionimpl.go | 7 ++- rete/internal/mem/midgenimpl.go | 4 +- rete/internal/mem/mjointablecollectionimpl.go | 9 ++- .../mem/mjointablerefsinhandleimpl.go | 12 ++-- rete/internal/mem/mretehandle.go | 11 ++-- rete/internal/redis/rhandlecollectionimpl.go | 53 ++++++++++++++++ rete/internal/redis/ridgenimpl.go | 4 +- .../redis/rjointablecollectionimpl.go | 28 +++++++++ .../redis/rjointablerefsinhandleimpl.go | 62 +++++++++++++++++++ rete/internal/redis/rretehandle.go | 54 ++++++++++++++++ rete/internal/types/types.go | 23 ++++--- rete/network.go | 44 +++++++++---- ruleapi/internal/store/mem/mstore.go | 4 ++ 16 files changed, 304 insertions(+), 56 deletions(-) create mode 100644 rete/internal/redis/rhandlecollectionimpl.go create mode 100644 rete/internal/redis/rjointablecollectionimpl.go create mode 100644 rete/internal/redis/rjointablerefsinhandleimpl.go create mode 100644 rete/internal/redis/rretehandle.go diff --git a/common/services/services.go b/common/services/services.go index 3fafc0e..ccef011 100644 --- a/common/services/services.go +++ b/common/services/services.go @@ -2,7 +2,12 @@ package services import "github.com/project-flogo/rules/common/model" +type Service interface { + Init() +} + type TupleStore interface { + Service GetTupleByStringKey(key string) model.Tuple SaveTuple(tuple model.Tuple) DeleteTupleByStringKey(key string) diff --git a/redisutils/redisutils.go b/redisutils/redisutils.go index a4c9b7c..d95b335 100644 --- a/redisutils/redisutils.go +++ b/redisutils/redisutils.go @@ -9,11 +9,11 @@ var rd RedisHdl type RedisHdl = *RedisHandle type RedisHandle struct { - config string + config map[string]interface{} pool *redis.Pool } -func InitService(config string) { +func InitService(config map[string]interface{}) { if rd == nil { rd = &RedisHandle{} rd.config = config diff --git a/rete/factory.go b/rete/factory.go index e2f67aa..7458888 100644 --- a/rete/factory.go +++ b/rete/factory.go @@ -41,56 +41,56 @@ func (f *TypeFactory) getJoinTable(rule model.Rule, conditionVar model.Condition return jt } -func (f *TypeFactory) getJoinTableRefs() types.JoinTableRefsInHdl { - var jtRefs types.JoinTableRefsInHdl +func (f *TypeFactory) getJoinTableRefs() types.JtRefsService { + var jtRefs types.JtRefsService if f.parsedJson == nil { - jtRefs = mem.NewJoinTableRefsInHdlImpl() + jtRefs = mem.NewJoinTableRefsInHdlImpl(f.parsedJson) } else { rete := f.parsedJson["rete"].(map[string]interface{}) if rete != nil { idgen := rete["jt"].(string) if idgen == "" || idgen == "mem" { - jtRefs = mem.NewJoinTableRefsInHdlImpl() + jtRefs = mem.NewJoinTableRefsInHdlImpl(f.parsedJson) } else if idgen == "redis" { - jtRefs = mem.NewJoinTableRefsInHdlImpl() + jtRefs = redis.NewJoinTableRefsInHdlImpl(f.parsedJson) } } } return jtRefs } -func (f *TypeFactory) getJoinTableCollection() types.JoinTableCollection { - var allJt types.JoinTableCollection +func (f *TypeFactory) getJoinTableCollection() types.JtService { + var allJt types.JtService if f.parsedJson == nil { - allJt = mem.NewJoinTableCollection() + allJt = mem.NewJoinTableCollection(f.parsedJson) } else { rete := f.parsedJson["rete"].(map[string]interface{}) if rete != nil { idgen := rete["jt"].(string) if idgen == "" || idgen == "mem" { - allJt = mem.NewJoinTableCollection() + allJt = mem.NewJoinTableCollection(f.parsedJson) } else if idgen == "redis" { - allJt = mem.NewJoinTableCollection() + allJt = redis.NewJoinTableCollection(f.parsedJson) } } } return allJt } -func (f *TypeFactory) getHandleCollection() types.HandleCollection { - var hc types.HandleCollection +func (f *TypeFactory) getHandleCollection() types.HandleService { + var hc types.HandleService if f.parsedJson == nil { - hc = mem.NewHandleCollection() + hc = mem.NewHandleCollection(f.parsedJson) } else { rete := f.parsedJson["rete"].(map[string]interface{}) if rete != nil { idgen := rete["jt"].(string) if idgen == "" || idgen == "mem" { - hc = mem.NewHandleCollection() + hc = mem.NewHandleCollection(f.parsedJson) } else if idgen == "redis" { - hc = mem.NewHandleCollection() + hc = redis.NewHandleCollection(f.parsedJson) } } } @@ -100,7 +100,7 @@ func (f *TypeFactory) getHandleCollection() types.HandleCollection { func (f *TypeFactory) getIdGen() types.IdGen { var idg types.IdGen if f.parsedJson == nil { - idg = mem.NewIdImpl(f.config) + idg = mem.NewIdImpl(f.parsedJson) return idg } else { rete := f.parsedJson["rete"].(map[string]interface{}) @@ -108,9 +108,9 @@ func (f *TypeFactory) getIdGen() types.IdGen { idgen := rete["idgen"].(string) if idgen == "" || idgen == "mem" { - idg = mem.NewIdImpl(f.config) + idg = mem.NewIdImpl(f.parsedJson) } else if idgen == "redis" { - idg = redis.NewIdImpl(f.config) + idg = redis.NewIdImpl(f.parsedJson) } } } diff --git a/rete/internal/mem/mhandlecollectionimpl.go b/rete/internal/mem/mhandlecollectionimpl.go index 8cd0f94..eadf1f6 100644 --- a/rete/internal/mem/mhandlecollectionimpl.go +++ b/rete/internal/mem/mhandlecollectionimpl.go @@ -9,12 +9,16 @@ type handleCollectionImpl struct { allHandles map[string]types.ReteHandle } -func NewHandleCollection() types.HandleCollection { +func NewHandleCollection(config map[string]interface{}) types.HandleService { hc := handleCollectionImpl{} hc.allHandles = make(map[string]types.ReteHandle) return &hc } +func (hc *handleCollectionImpl) Init() { + +} + func (hc *handleCollectionImpl) AddHandle(hdl types.ReteHandle) { hc.allHandles[hdl.GetTupleKey().String()] = hdl } @@ -40,6 +44,7 @@ func (hc *handleCollectionImpl) GetOrCreateHandle(nw types.Network, tuple model. h, found := hc.allHandles[tuple.GetKey().String()] if !found { h = newReteHandleImpl(nw, tuple) + hc.allHandles[tuple.GetKey().String()] = h //[tuple.GetKey().String()] = h } return h diff --git a/rete/internal/mem/midgenimpl.go b/rete/internal/mem/midgenimpl.go index 25f17d2..ef51364 100644 --- a/rete/internal/mem/midgenimpl.go +++ b/rete/internal/mem/midgenimpl.go @@ -6,11 +6,11 @@ import ( ) type idGenImpl struct { - config string + config map[string]interface{} currentId int32 } -func NewIdImpl(config string) types.IdGen { +func NewIdImpl(config map[string]interface{}) types.IdGen { idg := idGenImpl{} idg.config = config idg.currentId = 0 diff --git a/rete/internal/mem/mjointablecollectionimpl.go b/rete/internal/mem/mjointablecollectionimpl.go index 1131527..aa736b9 100644 --- a/rete/internal/mem/mjointablecollectionimpl.go +++ b/rete/internal/mem/mjointablecollectionimpl.go @@ -6,11 +6,14 @@ type joinTableCollectionImpl struct { allJoinTables map[int]types.JoinTable } -func NewJoinTableCollection() types.JoinTableCollection { +func NewJoinTableCollection(config map[string]interface{}) types.JtService { jtc := joinTableCollectionImpl{} jtc.allJoinTables = make(map[int]types.JoinTable) return &jtc } +func (jtc *joinTableCollectionImpl) Init() { + +} func (jtc *joinTableCollectionImpl) GetJoinTable(joinTableID int) types.JoinTable { return jtc.allJoinTables[joinTableID] @@ -19,3 +22,7 @@ func (jtc *joinTableCollectionImpl) GetJoinTable(joinTableID int) types.JoinTabl func (jtc *joinTableCollectionImpl) AddJoinTable(joinTable types.JoinTable) { jtc.allJoinTables[joinTable.GetID()] = joinTable } + +func (jtc *joinTableCollectionImpl) RemoveJoinTable(joinTable types.JoinTable) { + delete (jtc.allJoinTables,joinTable.GetID()) +} \ No newline at end of file diff --git a/rete/internal/mem/mjointablerefsinhandleimpl.go b/rete/internal/mem/mjointablerefsinhandleimpl.go index be5b658..160bcdb 100644 --- a/rete/internal/mem/mjointablerefsinhandleimpl.go +++ b/rete/internal/mem/mjointablerefsinhandleimpl.go @@ -10,13 +10,17 @@ type joinTableRefsInHdlImpl struct { tablesAndRows map[int]*list.List } -func NewJoinTableRefsInHdlImpl() types.JoinTableRefsInHdl { +func NewJoinTableRefsInHdlImpl(config map[string]interface{}) types.JtRefsService { hdlJt := joinTableRefsInHdlImpl{} hdlJt.tablesAndRows = make(map[int]*list.List) return &hdlJt } -func (h *joinTableRefsInHdlImpl) AddEntry(jointTableID int, rowID int) { +func (h *joinTableRefsInHdlImpl) Init() { + +} + +func (h *joinTableRefsInHdlImpl) AddEntry(handle types.ReteHandle, jointTableID int, rowID int) { rowsForJoinTable := h.tablesAndRows[jointTableID] if rowsForJoinTable == nil { rowsForJoinTable = list.New() @@ -25,11 +29,11 @@ func (h *joinTableRefsInHdlImpl) AddEntry(jointTableID int, rowID int) { rowsForJoinTable.PushBack(rowID) } -func (h *joinTableRefsInHdlImpl) RemoveEntry(jointTableID int) { +func (h *joinTableRefsInHdlImpl) RemoveEntry(handle types.ReteHandle, jointTableID int) { delete(h.tablesAndRows, jointTableID) } -func (h *joinTableRefsInHdlImpl) GetIterator() types.HdlTblIterator { +func (h *joinTableRefsInHdlImpl) GetIterator(handle types.ReteHandle) types.HdlTblIterator { ri := hdlTblIteratorImpl{} ri.hdlJtImpl = h ri.kList = list.List{} diff --git a/rete/internal/mem/mretehandle.go b/rete/internal/mem/mretehandle.go index dd4ac95..022e401 100644 --- a/rete/internal/mem/mretehandle.go +++ b/rete/internal/mem/mretehandle.go @@ -11,7 +11,7 @@ type reteHandleImpl struct { types.NwElemIdImpl tuple model.Tuple tupleKey model.TupleKey - jtRefs types.JoinTableRefsInHdl + //jtRefs types.JtRefsService } func newReteHandleImpl(nw types.Network, tuple model.Tuple) types.ReteHandle { @@ -29,7 +29,6 @@ func (hdl *reteHandleImpl) initHandleImpl(nw types.Network, tuple model.Tuple) { hdl.SetID(nw) hdl.SetTuple(tuple) hdl.tupleKey = tuple.GetKey() - hdl.jtRefs = NewJoinTableRefsInHdlImpl() } func (hdl *reteHandleImpl) GetTuple() model.Tuple { @@ -41,15 +40,15 @@ func (hdl *reteHandleImpl) GetTupleKey() model.TupleKey { } func (hdl *reteHandleImpl) AddJoinTableRowRef(joinTableRowVar types.JoinTableRow, joinTableVar types.JoinTable) { - hdl.jtRefs.AddEntry(joinTableVar.GetID(), joinTableRowVar.GetID()) + hdl.Nw.GetJtRefService().AddEntry(hdl,joinTableVar.GetID(), joinTableRowVar.GetID()) } //Used when a rule is deleted. See Network.RemoveRule func (hdl *reteHandleImpl) RemoveJoinTable(joinTableID int) { - hdl.jtRefs.RemoveEntry(joinTableID) + hdl.Nw.GetJtRefService().RemoveEntry(hdl,joinTableID) } func (hdl *reteHandleImpl) GetRefTableIterator() types.HdlTblIterator { - refTblIteator := hdl.jtRefs.GetIterator() - return refTblIteator + refTblIterator := hdl.Nw.GetJtRefService().GetIterator(hdl) + return refTblIterator } diff --git a/rete/internal/redis/rhandlecollectionimpl.go b/rete/internal/redis/rhandlecollectionimpl.go new file mode 100644 index 0000000..16d9e0d --- /dev/null +++ b/rete/internal/redis/rhandlecollectionimpl.go @@ -0,0 +1,53 @@ +package redis + +import ( + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/internal/types" +) + +type handleCollectionImpl struct { + allHandles map[string]types.ReteHandle +} + +func NewHandleCollection(config map[string]interface{}) types.HandleService { + hc := handleCollectionImpl{} + hc.allHandles = make(map[string]types.ReteHandle) + return &hc +} + +func (hc *handleCollectionImpl) Init() { + +} + +func (hc *handleCollectionImpl) AddHandle(hdl types.ReteHandle) { + hc.allHandles[hdl.GetTupleKey().String()] = hdl +} + +func (hc *handleCollectionImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle { + rh, found := hc.allHandles[tuple.GetKey().String()] + if found { + delete(hc.allHandles, tuple.GetKey().String()) + return rh + } + return nil +} + +func (hc *handleCollectionImpl) GetHandle(tuple model.Tuple) types.ReteHandle { + return hc.allHandles[tuple.GetKey().String()] +} + +func (hc *handleCollectionImpl) GetHandleByKey(key model.TupleKey) types.ReteHandle { + return hc.allHandles[key.String()] +} + +func (hc *handleCollectionImpl) GetOrCreateHandle(nw types.Network, tuple model.Tuple) types.ReteHandle { + h, found := hc.allHandles[tuple.GetKey().String()] + if !found { + h = newReteHandleImpl(nw, tuple) + //hci := h.(*reteHandleImpl) + //hci.jtRefs = nw + hc.allHandles[tuple.GetKey().String()] = h //[tuple.GetKey().String()] = h + } + return h +} + diff --git a/rete/internal/redis/ridgenimpl.go b/rete/internal/redis/ridgenimpl.go index 1054a8c..f56e437 100644 --- a/rete/internal/redis/ridgenimpl.go +++ b/rete/internal/redis/ridgenimpl.go @@ -13,12 +13,12 @@ const ( ) type ridImpl struct { - config string + config map[string]interface{} //current int rh redisutils.RedisHdl } -func NewIdImpl(config string) types.IdGen { +func NewIdImpl(config map[string]interface{}) types.IdGen { r := ridImpl{} r.config = config return &r diff --git a/rete/internal/redis/rjointablecollectionimpl.go b/rete/internal/redis/rjointablecollectionimpl.go new file mode 100644 index 0000000..e866e97 --- /dev/null +++ b/rete/internal/redis/rjointablecollectionimpl.go @@ -0,0 +1,28 @@ +package redis + +import "github.com/project-flogo/rules/rete/internal/types" + +type joinTableCollectionImpl struct { + allJoinTables map[int]types.JoinTable +} + +func NewJoinTableCollection(config map[string]interface{}) types.JtService { + jtc := joinTableCollectionImpl{} + jtc.allJoinTables = make(map[int]types.JoinTable) + return &jtc +} +func (jtc *joinTableCollectionImpl) Init() { + +} + +func (jtc *joinTableCollectionImpl) GetJoinTable(joinTableID int) types.JoinTable { + return jtc.allJoinTables[joinTableID] +} + +func (jtc *joinTableCollectionImpl) AddJoinTable(joinTable types.JoinTable) { + jtc.allJoinTables[joinTable.GetID()] = joinTable +} + +func (jtc *joinTableCollectionImpl) RemoveJoinTable(joinTable types.JoinTable) { + delete(jtc.allJoinTables,joinTable.GetID()) +} \ No newline at end of file diff --git a/rete/internal/redis/rjointablerefsinhandleimpl.go b/rete/internal/redis/rjointablerefsinhandleimpl.go new file mode 100644 index 0000000..1be2505 --- /dev/null +++ b/rete/internal/redis/rjointablerefsinhandleimpl.go @@ -0,0 +1,62 @@ +package redis + +import ( + "container/list" + "github.com/project-flogo/rules/rete/internal/types" +) + +type joinTableRefsInHdlImpl struct { + //keys are jointable-ids and values are lists of row-ids in the corresponding join table + tablesAndRows map[int]*list.List +} + +func NewJoinTableRefsInHdlImpl(config map[string]interface{}) types.JtRefsService { + hdlJt := joinTableRefsInHdlImpl{} + hdlJt.tablesAndRows = make(map[int]*list.List) + return &hdlJt +} + +func (h *joinTableRefsInHdlImpl) Init() { + +} + +func (h *joinTableRefsInHdlImpl) AddEntry(handle types.ReteHandle, jointTableID int, rowID int) { + rowsForJoinTable := h.tablesAndRows[jointTableID] + if rowsForJoinTable == nil { + rowsForJoinTable = list.New() + h.tablesAndRows[jointTableID] = rowsForJoinTable + } + rowsForJoinTable.PushBack(rowID) +} + +func (h *joinTableRefsInHdlImpl) RemoveEntry(handle types.ReteHandle, jointTableID int) { + delete(h.tablesAndRows, jointTableID) +} + +func (h *joinTableRefsInHdlImpl) GetIterator(handle types.ReteHandle, ) types.HdlTblIterator { + ri := hdlTblIteratorImpl{} + ri.hdlJtImpl = h + ri.kList = list.List{} + for k, _ := range ri.hdlJtImpl.tablesAndRows { + ri.kList.PushBack(k) + } + ri.curr = ri.kList.Front() + return &ri +} + +type hdlTblIteratorImpl struct { + hdlJtImpl *joinTableRefsInHdlImpl + kList list.List + curr *list.Element +} + +func (ri *hdlTblIteratorImpl) HasNext() bool { + return ri.curr != nil +} + +func (ri *hdlTblIteratorImpl) Next() (int, *list.List) { + id := ri.curr.Value.(int) + lst := ri.hdlJtImpl.tablesAndRows[id] + ri.curr = ri.curr.Next() + return id, lst +} diff --git a/rete/internal/redis/rretehandle.go b/rete/internal/redis/rretehandle.go new file mode 100644 index 0000000..4b0cbe6 --- /dev/null +++ b/rete/internal/redis/rretehandle.go @@ -0,0 +1,54 @@ +package redis + +import ( + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/internal/types" +) + +//Holds a tuple reference and related state + +type reteHandleImpl struct { + types.NwElemIdImpl + tuple model.Tuple + tupleKey model.TupleKey + //jtRefs types.JtRefsService +} + +func newReteHandleImpl(nw types.Network, tuple model.Tuple) types.ReteHandle { + h1 := reteHandleImpl{} + h1.initHandleImpl(nw, tuple) + return &h1 +} + +func (hdl *reteHandleImpl) SetTuple(tuple model.Tuple) { + hdl.tuple = tuple + hdl.tupleKey = tuple.GetKey() +} + +func (hdl *reteHandleImpl) initHandleImpl(nw types.Network, tuple model.Tuple) { + hdl.SetID(nw) + hdl.SetTuple(tuple) + hdl.tupleKey = tuple.GetKey() +} + +func (hdl *reteHandleImpl) GetTuple() model.Tuple { + return hdl.tuple +} + +func (hdl *reteHandleImpl) GetTupleKey() model.TupleKey { + return hdl.tupleKey +} + +func (hdl *reteHandleImpl) AddJoinTableRowRef(joinTableRowVar types.JoinTableRow, joinTableVar types.JoinTable) { + hdl.Nw.GetJtRefService().AddEntry(hdl, joinTableVar.GetID(), joinTableRowVar.GetID()) +} + +//Used when a rule is deleted. See Network.RemoveRule +func (hdl *reteHandleImpl) RemoveJoinTable(joinTableID int) { + hdl.Nw.GetJtRefService().RemoveEntry(hdl, joinTableID) +} + +func (hdl *reteHandleImpl) GetRefTableIterator() types.HdlTblIterator { + refTblIteator := hdl.Nw.GetJtRefService().GetIterator(hdl) + return refTblIteator +} diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index 213522d..5a75e86 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -4,6 +4,7 @@ import ( "container/list" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/rete/common" + "github.com/project-flogo/rules/common/services" ) type Network interface { @@ -12,6 +13,10 @@ type Network interface { IncrementAndGetId() int GetJoinTable(joinTableID int) JoinTable AddToAllJoinTables(jT JoinTable) + + GetJtService() JtService + GetHandleService() HandleService + GetJtRefService() JtRefsService } type NwElemId interface { @@ -63,10 +68,11 @@ type RowIterator interface { Next() JoinTableRow } -type JoinTableRefsInHdl interface { - AddEntry(jointTableID int, rowID int) - RemoveEntry(jointTableID int) - GetIterator() HdlTblIterator +type JtRefsService interface { + services.Service + AddEntry(handle ReteHandle, jointTableID int, rowID int) + RemoveEntry(handle ReteHandle, jointTableID int) + GetIterator(handle ReteHandle) HdlTblIterator } type HdlTblIterator interface { @@ -74,12 +80,15 @@ type HdlTblIterator interface { Next() (int, *list.List) } -type JoinTableCollection interface { +type JtService interface { + services.Service GetJoinTable(joinTableID int) JoinTable AddJoinTable(joinTable JoinTable) + RemoveJoinTable(joinTable JoinTable) } -type HandleCollection interface { +type HandleService interface { + services.Service AddHandle(hdl ReteHandle) RemoveHandle(tuple model.Tuple) ReteHandle GetHandle(tuple model.Tuple) ReteHandle @@ -88,7 +97,7 @@ type HandleCollection interface { } type IdGen interface { - Init() + services.Service GetMaxID() int GetNextID() int } diff --git a/rete/network.go b/rete/network.go index 31d7cab..5ec314c 100644 --- a/rete/network.go +++ b/rete/network.go @@ -29,16 +29,19 @@ type reteNetworkImpl struct { //Holds the Rule name as key and a pointer to a slice of NodeLinks as value ruleNameClassNodeLinksOfRule map[string]*list.List //*list.List of ClassNodeLink - //allHandles map[string]types.ReteHandle - allHandles types.HandleCollection + //handleService map[string]types.ReteHandle + handleService types.HandleService assertLock sync.Mutex crudLock sync.Mutex txnHandler model.RtcTransactionHandler txnContext interface{} - //allJoinTables map[int]types.JoinTable - allJoinTables types.JoinTableCollection + //jtService map[int]types.JoinTable + jtService types.JtService + + jtRefsService types.JtRefsService + config map[string]string factory *TypeFactory @@ -63,14 +66,17 @@ func (nw *reteNetworkImpl) initReteNetwork(config string) { nw.factory = NewFactory(nw, config) nw.idGen = nw.factory.getIdGen() - nw.allJoinTables = nw.factory.getJoinTableCollection() - nw.allHandles = nw.factory.getHandleCollection() + nw.jtService = nw.factory.getJoinTableCollection() + nw.handleService = nw.factory.getHandleCollection() nw.initNwServices() } func (nw *reteNetworkImpl) initNwServices() { nw.idGen.Init() + nw.jtService.Init() + nw.handleService.Init() + nw.jtRefsService.Init() } func (nw *reteNetworkImpl) AddRule(rule model.Rule) (err error) { @@ -552,7 +558,7 @@ func (nw *reteNetworkImpl) Assert(ctx context.Context, rs model.RuleSession, tup } func (nw *reteNetworkImpl) removeTupleFromRete(tuple model.Tuple) { - reteHandle := nw.allHandles.RemoveHandle(tuple) + reteHandle := nw.handleService.RemoveHandle(tuple) if reteHandle != nil { nw.removeJoinTableRowRefs(reteHandle, nil) } @@ -584,7 +590,7 @@ func (nw *reteNetworkImpl) retractInternal(ctx context.Context, tuple model.Tupl } rCtx, _, _ := getOrSetReteCtx(ctx, nw, nil) - reteHandle := nw.allHandles.RemoveHandle(tuple) + reteHandle := nw.handleService.RemoveHandle(tuple) if reteHandle != nil { nw.removeJoinTableRowRefs(reteHandle, changedProps) @@ -597,7 +603,7 @@ func (nw *reteNetworkImpl) retractInternal(ctx context.Context, tuple model.Tupl } func (nw *reteNetworkImpl) GetAssertedTuple(key model.TupleKey) model.Tuple { - reteHandle := nw.allHandles.GetHandleByKey(key) + reteHandle := nw.handleService.GetHandleByKey(key) if reteHandle != nil { return reteHandle.GetTuple() } @@ -623,12 +629,12 @@ func (nw *reteNetworkImpl) assertInternal(ctx context.Context, tuple model.Tuple } func (nw *reteNetworkImpl) getOrCreateHandle(ctx context.Context, tuple model.Tuple) types.ReteHandle { - h := nw.allHandles.GetOrCreateHandle(nw, tuple) + h := nw.handleService.GetOrCreateHandle(nw, tuple) return h } func (nw *reteNetworkImpl) getHandle(tuple model.Tuple) types.ReteHandle { - h := nw.allHandles.GetHandleByKey(tuple.GetKey()) + h := nw.handleService.GetHandleByKey(tuple.GetKey()) return h } @@ -642,7 +648,7 @@ func (nw *reteNetworkImpl) RegisterRtcTransactionHandler(txnHandler model.RtcTra } func (nw *reteNetworkImpl) GetJoinTable(joinTableID int) types.JoinTable { - return nw.allJoinTables.GetJoinTable(joinTableID) + return nw.jtService.GetJoinTable(joinTableID) } func (nw *reteNetworkImpl) GetConfigValue(key string) string { @@ -659,7 +665,7 @@ func (nw *reteNetworkImpl) getFactory() *TypeFactory { } func (nw *reteNetworkImpl) AddToAllJoinTables(joinTable types.JoinTable) { - nw.allJoinTables.AddJoinTable(joinTable) + nw.jtService.AddJoinTable(joinTable) } func (nw *reteNetworkImpl) SetTupleStore(tupleStore services.TupleStore) { @@ -723,3 +729,15 @@ func (nw *reteNetworkImpl) removeJoinTableRowRefs(hdl types.ReteHandle, changedP hdl.RemoveJoinTable(joinTableID) } } + +func (nw *reteNetworkImpl) GetJtService() types.JtService { + return nw.jtService +} + +func (nw *reteNetworkImpl) GetJtRefService() types.JtRefsService { + return nw.jtRefsService +} + +func (nw *reteNetworkImpl) GetHandleService() types.HandleService { + return nw.handleService +} \ No newline at end of file diff --git a/ruleapi/internal/store/mem/mstore.go b/ruleapi/internal/store/mem/mstore.go index 18586e1..f87c2bc 100644 --- a/ruleapi/internal/store/mem/mstore.go +++ b/ruleapi/internal/store/mem/mstore.go @@ -15,6 +15,10 @@ func NewStore() services.TupleStore { return &ms } +func (ms *store) Init() { + +} + func (ms *store) GetTupleByStringKey(key string) model.Tuple { return ms.allTuples[key] } From 253adbed246d5046c8400e37e1a4f1239904a192 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Sun, 23 Dec 2018 23:22:16 +0530 Subject: [PATCH 020/125] refactor --- examples/rulesapp/rsconfig.json | 4 +- redisutils/redisutil_test.go | 4 +- rete/factory.go | 37 ++++++----- rete/internal/mem/mjointablecollectionimpl.go | 28 ++++++--- rete/internal/mem/mjointableimpl.go | 13 ++-- .../mem/mjointablerefsinhandleimpl.go | 18 +++--- rete/internal/mem/mretehandle.go | 4 +- .../redis/rjointablecollectionimpl.go | 27 +++++--- rete/internal/redis/rjointableimpl.go | 63 +++++++++++++++++++ .../redis/rjointablerefsinhandleimpl.go | 20 +++--- rete/internal/redis/rjointablerowimpl.go | 23 +++++++ rete/internal/redis/rjointablerowiterator.go | 34 ++++++++++ rete/internal/redis/rretehandle.go | 10 +-- rete/internal/types/types.go | 16 ++--- rete/joinnode.go | 4 +- rete/network.go | 14 ++--- 16 files changed, 232 insertions(+), 87 deletions(-) create mode 100644 rete/internal/redis/rjointableimpl.go create mode 100644 rete/internal/redis/rjointablerowimpl.go create mode 100644 rete/internal/redis/rjointablerowiterator.go diff --git a/examples/rulesapp/rsconfig.json b/examples/rulesapp/rsconfig.json index f3d07f0..1a1341f 100644 --- a/examples/rulesapp/rsconfig.json +++ b/examples/rulesapp/rsconfig.json @@ -3,8 +3,8 @@ "store-ref" : "mem" }, "rete": { - "jt": "redis", - "idgen" : "redis" + "jt": "mem", + "idgen" : "mem" }, "store": [ { diff --git a/redisutils/redisutil_test.go b/redisutils/redisutil_test.go index 853818f..4e82f4f 100644 --- a/redisutils/redisutil_test.go +++ b/redisutils/redisutil_test.go @@ -8,8 +8,8 @@ import ( ) func Test_first(t *testing.T) { - InitService("") - //fmt.Printf("redisutils test...") + + InitService(nil) rd := GetRedisHdl() pool := rd.GetPool() conn := pool.Get() diff --git a/rete/factory.go b/rete/factory.go index 7458888..078cd5e 100644 --- a/rete/factory.go +++ b/rete/factory.go @@ -2,7 +2,6 @@ package rete import ( "encoding/json" - "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/rete/internal/mem" "github.com/project-flogo/rules/rete/internal/redis" "github.com/project-flogo/rules/rete/internal/types" @@ -22,24 +21,24 @@ func NewFactory(nw *reteNetworkImpl, config string) *TypeFactory { return &tf } - -func (f *TypeFactory) getJoinTable(rule model.Rule, conditionVar model.Condition, identifiers []model.TupleType) types.JoinTable { - var jt types.JoinTable - if f.parsedJson == nil { - jt = mem.CreateOrGetJoinTable(f.nw, rule, conditionVar, identifiers) - } else { - rete := f.parsedJson["rete"].(map[string]interface{}) - if rete != nil { - idgen := rete["jt"].(string) - if idgen == "" || idgen == "mem" { - jt = mem.CreateOrGetJoinTable(f.nw, rule, conditionVar, identifiers) - } else if idgen == "redis" { - jt = mem.CreateOrGetJoinTable(f.nw, rule, conditionVar, identifiers) - } - } - } - return jt -} +// +//func (f *TypeFactory) getJoinTable(rule model.Rule, conditionVar model.Condition, identifiers []model.TupleType) types.JoinTable { +// var jt types.JoinTable +// if f.parsedJson == nil { +// jt = mem.CreateOrGetJoinTable(f.nw, rule, conditionVar, identifiers) +// } else { +// rete := f.parsedJson["rete"].(map[string]interface{}) +// if rete != nil { +// idgen := rete["jt"].(string) +// if idgen == "" || idgen == "mem" { +// jt = mem.CreateOrGetJoinTable(f.nw, rule, conditionVar, identifiers) +// } else if idgen == "redis" { +// jt = mem.CreateOrGetJoinTable(f.nw, rule, conditionVar, identifiers) +// } +// } +// } +// return jt +//} func (f *TypeFactory) getJoinTableRefs() types.JtRefsService { var jtRefs types.JtRefsService diff --git a/rete/internal/mem/mjointablecollectionimpl.go b/rete/internal/mem/mjointablecollectionimpl.go index aa736b9..c239f6d 100644 --- a/rete/internal/mem/mjointablecollectionimpl.go +++ b/rete/internal/mem/mjointablecollectionimpl.go @@ -1,28 +1,42 @@ package mem -import "github.com/project-flogo/rules/rete/internal/types" +import ( + "github.com/project-flogo/rules/rete/internal/types" + "github.com/project-flogo/rules/common/model" +) type joinTableCollectionImpl struct { - allJoinTables map[int]types.JoinTable + allJoinTables map[string]types.JoinTable } func NewJoinTableCollection(config map[string]interface{}) types.JtService { jtc := joinTableCollectionImpl{} - jtc.allJoinTables = make(map[int]types.JoinTable) + jtc.allJoinTables = make(map[string]types.JoinTable) return &jtc } func (jtc *joinTableCollectionImpl) Init() { } -func (jtc *joinTableCollectionImpl) GetJoinTable(joinTableID int) types.JoinTable { - return jtc.allJoinTables[joinTableID] +func (jtc *joinTableCollectionImpl) GetJoinTable(joinTableName string) types.JoinTable { + return jtc.allJoinTables[joinTableName] } func (jtc *joinTableCollectionImpl) AddJoinTable(joinTable types.JoinTable) { - jtc.allJoinTables[joinTable.GetID()] = joinTable + jtc.allJoinTables[joinTable.GetName()] = joinTable } func (jtc *joinTableCollectionImpl) RemoveJoinTable(joinTable types.JoinTable) { - delete (jtc.allJoinTables,joinTable.GetID()) + delete (jtc.allJoinTables,joinTable.GetName()) +} + +func (jtc *joinTableCollectionImpl) GetOrCreateJoinTable(nw types.Network, rule model.Rule, conditionVar model.Condition, identifiers []model.TupleType) types.JoinTable { + jT, found := jtc.allJoinTables[conditionVar.GetName()] + if !found { + jTn := joinTableImpl{} + jTn.initJoinTableImpl(nw, rule, identifiers) + jtc.allJoinTables[conditionVar.GetName()] = &jTn + jT = &jTn + } + return jT } \ No newline at end of file diff --git a/rete/internal/mem/mjointableimpl.go b/rete/internal/mem/mjointableimpl.go index 0576f1d..9a362c4 100644 --- a/rete/internal/mem/mjointableimpl.go +++ b/rete/internal/mem/mjointableimpl.go @@ -10,16 +10,9 @@ type joinTableImpl struct { table map[int]types.JoinTableRow idr []model.TupleType rule model.Rule + name string } -func CreateOrGetJoinTable(nw types.Network, rule model.Rule, conditionVar model.Condition, identifiers []model.TupleType) types.JoinTable { - jT := joinTableImpl{} - jT.initJoinTableImpl(nw, rule, identifiers) - - //add it to all join tables collection before returning - nw.AddToAllJoinTables(&jT) - return &jT -} func (jt *joinTableImpl) initJoinTableImpl(nw types.Network, rule model.Rule, identifiers []model.TupleType) { jt.SetID(nw) @@ -64,3 +57,7 @@ func (jt *joinTableImpl) GetRowIterator() types.RowIterator { func (jt *joinTableImpl) GetRow(rowID int) types.JoinTableRow { return jt.table[rowID] } + +func (jt *joinTableImpl) GetName() string { + return jt.name +} \ No newline at end of file diff --git a/rete/internal/mem/mjointablerefsinhandleimpl.go b/rete/internal/mem/mjointablerefsinhandleimpl.go index 160bcdb..4bbdbc3 100644 --- a/rete/internal/mem/mjointablerefsinhandleimpl.go +++ b/rete/internal/mem/mjointablerefsinhandleimpl.go @@ -7,12 +7,12 @@ import ( type joinTableRefsInHdlImpl struct { //keys are jointable-ids and values are lists of row-ids in the corresponding join table - tablesAndRows map[int]*list.List + tablesAndRows map[string]*list.List } func NewJoinTableRefsInHdlImpl(config map[string]interface{}) types.JtRefsService { hdlJt := joinTableRefsInHdlImpl{} - hdlJt.tablesAndRows = make(map[int]*list.List) + hdlJt.tablesAndRows = make(map[string]*list.List) return &hdlJt } @@ -20,17 +20,17 @@ func (h *joinTableRefsInHdlImpl) Init() { } -func (h *joinTableRefsInHdlImpl) AddEntry(handle types.ReteHandle, jointTableID int, rowID int) { - rowsForJoinTable := h.tablesAndRows[jointTableID] +func (h *joinTableRefsInHdlImpl) AddEntry(handle types.ReteHandle, jtName string, rowID int) { + rowsForJoinTable := h.tablesAndRows[jtName] if rowsForJoinTable == nil { rowsForJoinTable = list.New() - h.tablesAndRows[jointTableID] = rowsForJoinTable + h.tablesAndRows[jtName] = rowsForJoinTable } rowsForJoinTable.PushBack(rowID) } -func (h *joinTableRefsInHdlImpl) RemoveEntry(handle types.ReteHandle, jointTableID int) { - delete(h.tablesAndRows, jointTableID) +func (h *joinTableRefsInHdlImpl) RemoveEntry(handle types.ReteHandle, jtName string) { + delete(h.tablesAndRows, jtName) } func (h *joinTableRefsInHdlImpl) GetIterator(handle types.ReteHandle) types.HdlTblIterator { @@ -54,8 +54,8 @@ func (ri *hdlTblIteratorImpl) HasNext() bool { return ri.curr != nil } -func (ri *hdlTblIteratorImpl) Next() (int, *list.List) { - id := ri.curr.Value.(int) +func (ri *hdlTblIteratorImpl) Next() (string, *list.List) { + id := ri.curr.Value.(string) lst := ri.hdlJtImpl.tablesAndRows[id] ri.curr = ri.curr.Next() return id, lst diff --git a/rete/internal/mem/mretehandle.go b/rete/internal/mem/mretehandle.go index 022e401..dcfe026 100644 --- a/rete/internal/mem/mretehandle.go +++ b/rete/internal/mem/mretehandle.go @@ -40,11 +40,11 @@ func (hdl *reteHandleImpl) GetTupleKey() model.TupleKey { } func (hdl *reteHandleImpl) AddJoinTableRowRef(joinTableRowVar types.JoinTableRow, joinTableVar types.JoinTable) { - hdl.Nw.GetJtRefService().AddEntry(hdl,joinTableVar.GetID(), joinTableRowVar.GetID()) + hdl.Nw.GetJtRefService().AddEntry(hdl,joinTableVar.GetName(), joinTableRowVar.GetID()) } //Used when a rule is deleted. See Network.RemoveRule -func (hdl *reteHandleImpl) RemoveJoinTable(joinTableID int) { +func (hdl *reteHandleImpl) RemoveJoinTable(joinTableID string) { hdl.Nw.GetJtRefService().RemoveEntry(hdl,joinTableID) } diff --git a/rete/internal/redis/rjointablecollectionimpl.go b/rete/internal/redis/rjointablecollectionimpl.go index e866e97..c578346 100644 --- a/rete/internal/redis/rjointablecollectionimpl.go +++ b/rete/internal/redis/rjointablecollectionimpl.go @@ -1,28 +1,41 @@ package redis -import "github.com/project-flogo/rules/rete/internal/types" +import ( + "github.com/project-flogo/rules/rete/internal/types" + "github.com/project-flogo/rules/common/model" +) type joinTableCollectionImpl struct { - allJoinTables map[int]types.JoinTable + allJoinTables map[string]types.JoinTable } func NewJoinTableCollection(config map[string]interface{}) types.JtService { jtc := joinTableCollectionImpl{} - jtc.allJoinTables = make(map[int]types.JoinTable) + jtc.allJoinTables = make(map[string]types.JoinTable) return &jtc } func (jtc *joinTableCollectionImpl) Init() { } -func (jtc *joinTableCollectionImpl) GetJoinTable(joinTableID int) types.JoinTable { - return jtc.allJoinTables[joinTableID] +func (jtc *joinTableCollectionImpl) GetJoinTable(joinTableName string) types.JoinTable { + return jtc.allJoinTables[joinTableName] } +func (jtc *joinTableCollectionImpl) GetOrCreateJoinTable(nw types.Network, rule model.Rule, conditionVar model.Condition, identifiers []model.TupleType) types.JoinTable { + jT, found := jtc.allJoinTables[conditionVar.GetName()] + if !found { + jTn := joinTableImpl{} + jTn.initJoinTableImpl(nw, rule, identifiers) + jtc.allJoinTables[conditionVar.GetName()] = &jTn + jT = &jTn + } + return jT} + func (jtc *joinTableCollectionImpl) AddJoinTable(joinTable types.JoinTable) { - jtc.allJoinTables[joinTable.GetID()] = joinTable + jtc.allJoinTables[joinTable.GetName()] = joinTable } func (jtc *joinTableCollectionImpl) RemoveJoinTable(joinTable types.JoinTable) { - delete(jtc.allJoinTables,joinTable.GetID()) + delete(jtc.allJoinTables,joinTable.GetName()) } \ No newline at end of file diff --git a/rete/internal/redis/rjointableimpl.go b/rete/internal/redis/rjointableimpl.go new file mode 100644 index 0000000..ad81e92 --- /dev/null +++ b/rete/internal/redis/rjointableimpl.go @@ -0,0 +1,63 @@ +package redis + +import ( + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/internal/types" +) + +type joinTableImpl struct { + types.NwElemIdImpl + table map[int]types.JoinTableRow + idr []model.TupleType + rule model.Rule + name string +} + + +func (jt *joinTableImpl) initJoinTableImpl(nw types.Network, rule model.Rule, identifiers []model.TupleType) { + jt.SetID(nw) + jt.idr = identifiers + jt.table = map[int]types.JoinTableRow{} + jt.rule = rule +} + +func (jt *joinTableImpl) AddRow(handles []types.ReteHandle) types.JoinTableRow { + + row := newJoinTableRow(handles, jt.Nw) + + jt.table[row.GetID()] = row + for i := 0; i < len(row.GetHandles()); i++ { + handle := row.GetHandles()[i] + handle.AddJoinTableRowRef(row, jt) + } + return row +} + +func (jt *joinTableImpl) RemoveRow(rowID int) types.JoinTableRow { + row, found := jt.table[rowID] + if found { + delete(jt.table, rowID) + return row + } + return nil +} + +func (jt *joinTableImpl) GetRowCount() int { + return len(jt.table) +} + +func (jt *joinTableImpl) GetRule() model.Rule { + return jt.rule +} + +func (jt *joinTableImpl) GetRowIterator() types.RowIterator { + return newRowIterator(jt.table) +} + +func (jt *joinTableImpl) GetRow(rowID int) types.JoinTableRow { + return jt.table[rowID] +} + +func (jt *joinTableImpl) GetName() string { + return jt.name +} \ No newline at end of file diff --git a/rete/internal/redis/rjointablerefsinhandleimpl.go b/rete/internal/redis/rjointablerefsinhandleimpl.go index 1be2505..bb0bf0c 100644 --- a/rete/internal/redis/rjointablerefsinhandleimpl.go +++ b/rete/internal/redis/rjointablerefsinhandleimpl.go @@ -7,12 +7,12 @@ import ( type joinTableRefsInHdlImpl struct { //keys are jointable-ids and values are lists of row-ids in the corresponding join table - tablesAndRows map[int]*list.List + tablesAndRows map[string]*list.List } func NewJoinTableRefsInHdlImpl(config map[string]interface{}) types.JtRefsService { hdlJt := joinTableRefsInHdlImpl{} - hdlJt.tablesAndRows = make(map[int]*list.List) + hdlJt.tablesAndRows = make(map[string]*list.List) return &hdlJt } @@ -20,20 +20,20 @@ func (h *joinTableRefsInHdlImpl) Init() { } -func (h *joinTableRefsInHdlImpl) AddEntry(handle types.ReteHandle, jointTableID int, rowID int) { - rowsForJoinTable := h.tablesAndRows[jointTableID] +func (h *joinTableRefsInHdlImpl) AddEntry(handle types.ReteHandle, jtName string, rowID int) { + rowsForJoinTable := h.tablesAndRows[jtName] if rowsForJoinTable == nil { rowsForJoinTable = list.New() - h.tablesAndRows[jointTableID] = rowsForJoinTable + h.tablesAndRows[jtName] = rowsForJoinTable } rowsForJoinTable.PushBack(rowID) } -func (h *joinTableRefsInHdlImpl) RemoveEntry(handle types.ReteHandle, jointTableID int) { - delete(h.tablesAndRows, jointTableID) +func (h *joinTableRefsInHdlImpl) RemoveEntry(handle types.ReteHandle, jtName string) { + delete(h.tablesAndRows, jtName) } -func (h *joinTableRefsInHdlImpl) GetIterator(handle types.ReteHandle, ) types.HdlTblIterator { +func (h *joinTableRefsInHdlImpl) GetIterator(handle types.ReteHandle) types.HdlTblIterator { ri := hdlTblIteratorImpl{} ri.hdlJtImpl = h ri.kList = list.List{} @@ -54,8 +54,8 @@ func (ri *hdlTblIteratorImpl) HasNext() bool { return ri.curr != nil } -func (ri *hdlTblIteratorImpl) Next() (int, *list.List) { - id := ri.curr.Value.(int) +func (ri *hdlTblIteratorImpl) Next() (string, *list.List) { + id := ri.curr.Value.(string) lst := ri.hdlJtImpl.tablesAndRows[id] ri.curr = ri.curr.Next() return id, lst diff --git a/rete/internal/redis/rjointablerowimpl.go b/rete/internal/redis/rjointablerowimpl.go new file mode 100644 index 0000000..803a0d4 --- /dev/null +++ b/rete/internal/redis/rjointablerowimpl.go @@ -0,0 +1,23 @@ +package redis + +import "github.com/project-flogo/rules/rete/internal/types" + +type joinTableRowImpl struct { + types.NwElemIdImpl + handles []types.ReteHandle +} + +func newJoinTableRow(handles []types.ReteHandle, nw types.Network) types.JoinTableRow { + jtr := joinTableRowImpl{} + jtr.initJoinTableRow(handles, nw) + return &jtr +} + +func (jtr *joinTableRowImpl) initJoinTableRow(handles []types.ReteHandle, nw types.Network) { + jtr.SetID(nw) + jtr.handles = append([]types.ReteHandle{}, handles...) +} + +func (jtr *joinTableRowImpl) GetHandles() []types.ReteHandle { + return jtr.handles +} diff --git a/rete/internal/redis/rjointablerowiterator.go b/rete/internal/redis/rjointablerowiterator.go new file mode 100644 index 0000000..392ab10 --- /dev/null +++ b/rete/internal/redis/rjointablerowiterator.go @@ -0,0 +1,34 @@ +package redis + +import ( + "container/list" + "github.com/project-flogo/rules/rete/internal/types" +) + +type rowIteratorImpl struct { + table map[int]types.JoinTableRow + kList list.List + curr *list.Element +} + +func newRowIterator(jTable map[int]types.JoinTableRow) types.RowIterator { + ri := rowIteratorImpl{} + ri.table = jTable + ri.kList = list.List{} + for k, _ := range jTable { + ri.kList.PushBack(k) + } + ri.curr = ri.kList.Front() + return &ri +} + +func (ri *rowIteratorImpl) HasNext() bool { + return ri.curr != nil +} + +func (ri *rowIteratorImpl) Next() types.JoinTableRow { + id := ri.curr.Value.(int) + val := ri.table[id] + ri.curr = ri.curr.Next() + return val +} diff --git a/rete/internal/redis/rretehandle.go b/rete/internal/redis/rretehandle.go index 4b0cbe6..06aa21b 100644 --- a/rete/internal/redis/rretehandle.go +++ b/rete/internal/redis/rretehandle.go @@ -40,15 +40,15 @@ func (hdl *reteHandleImpl) GetTupleKey() model.TupleKey { } func (hdl *reteHandleImpl) AddJoinTableRowRef(joinTableRowVar types.JoinTableRow, joinTableVar types.JoinTable) { - hdl.Nw.GetJtRefService().AddEntry(hdl, joinTableVar.GetID(), joinTableRowVar.GetID()) + hdl.Nw.GetJtRefService().AddEntry(hdl, joinTableVar.GetName(), joinTableRowVar.GetID()) } //Used when a rule is deleted. See Network.RemoveRule -func (hdl *reteHandleImpl) RemoveJoinTable(joinTableID int) { - hdl.Nw.GetJtRefService().RemoveEntry(hdl, joinTableID) +func (hdl *reteHandleImpl) RemoveJoinTable(jtName string) { + hdl.Nw.GetJtRefService().RemoveEntry(hdl, jtName) } func (hdl *reteHandleImpl) GetRefTableIterator() types.HdlTblIterator { - refTblIteator := hdl.Nw.GetJtRefService().GetIterator(hdl) - return refTblIteator + refTblIterator := hdl.Nw.GetJtRefService().GetIterator(hdl) + return refTblIterator } diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index 5a75e86..ab2b257 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -11,8 +11,8 @@ type Network interface { common.Network IncrementAndGetId() int - GetJoinTable(joinTableID int) JoinTable - AddToAllJoinTables(jT JoinTable) + //GetJoinTable(jtName int) JoinTable + //AddToAllJoinTables(jT JoinTable) GetJtService() JtService GetHandleService() HandleService @@ -38,6 +38,7 @@ func (ide *NwElemIdImpl) GetID() int { type JoinTable interface { NwElemId + GetName() string GetRule() model.Rule AddRow(handles []ReteHandle) JoinTableRow @@ -58,7 +59,7 @@ type ReteHandle interface { GetTuple() model.Tuple AddJoinTableRowRef(joinTableRowVar JoinTableRow, joinTableVar JoinTable) //removeJoinTableRowRefs(changedProps map[string]bool) - RemoveJoinTable(joinTableID int) + RemoveJoinTable(jtName string) GetTupleKey() model.TupleKey GetRefTableIterator() HdlTblIterator } @@ -70,19 +71,20 @@ type RowIterator interface { type JtRefsService interface { services.Service - AddEntry(handle ReteHandle, jointTableID int, rowID int) - RemoveEntry(handle ReteHandle, jointTableID int) + AddEntry(handle ReteHandle, jtName string, rowID int) + RemoveEntry(handle ReteHandle, jtName string) GetIterator(handle ReteHandle) HdlTblIterator } type HdlTblIterator interface { HasNext() bool - Next() (int, *list.List) + Next() (string, *list.List) } type JtService interface { services.Service - GetJoinTable(joinTableID int) JoinTable + GetOrCreateJoinTable(nw Network, rule model.Rule, conditionVar model.Condition, identifiers []model.TupleType) JoinTable + GetJoinTable (name string) JoinTable AddJoinTable(joinTable JoinTable) RemoveJoinTable(joinTable JoinTable) } diff --git a/rete/joinnode.go b/rete/joinnode.go index 388636f..ee9e411 100644 --- a/rete/joinnode.go +++ b/rete/joinnode.go @@ -42,8 +42,8 @@ func (jn *joinNodeImpl) initjoinNodeImplVar(nw *reteNetworkImpl, rule model.Rule jn.leftIdrs = leftIdrs jn.rightIdrs = rightIdrs jn.conditionVar = conditionVar - jn.leftTable = nw.getFactory().getJoinTable(rule, conditionVar, leftIdrs) - jn.rightTable = nw.getFactory().getJoinTable(rule, conditionVar, rightIdrs) + jn.leftTable = nw.GetJtService().GetOrCreateJoinTable(nw, rule, conditionVar, leftIdrs) + jn.rightTable = nw.GetJtService().GetOrCreateJoinTable(nw, rule, conditionVar, rightIdrs) jn.setJoinIdentifiers() } diff --git a/rete/network.go b/rete/network.go index 5ec314c..e6ecf12 100644 --- a/rete/network.go +++ b/rete/network.go @@ -68,7 +68,7 @@ func (nw *reteNetworkImpl) initReteNetwork(config string) { nw.idGen = nw.factory.getIdGen() nw.jtService = nw.factory.getJoinTableCollection() nw.handleService = nw.factory.getHandleCollection() - + nw.jtRefsService = nw.factory.getJoinTableRefs() nw.initNwServices() } @@ -197,7 +197,7 @@ func removeRefsFromReteHandles(joinTableVar types.JoinTable) { for rIterator.HasNext() { tableRow := rIterator.Next() for _, handle := range tableRow.GetHandles() { - handle.RemoveJoinTable(joinTableVar.GetID()) + handle.RemoveJoinTable(joinTableVar.GetName()) } } } @@ -646,10 +646,10 @@ func (nw *reteNetworkImpl) RegisterRtcTransactionHandler(txnHandler model.RtcTra nw.txnHandler = txnHandler nw.txnContext = txnContext } - -func (nw *reteNetworkImpl) GetJoinTable(joinTableID int) types.JoinTable { - return nw.jtService.GetJoinTable(joinTableID) -} +// +//func (nw *reteNetworkImpl) GetJoinTable(joinTableID int) types.JoinTable { +// return nw.jtService.GetJoinTable(joinTableID) +//} func (nw *reteNetworkImpl) GetConfigValue(key string) string { val, _ := nw.config[key] @@ -686,7 +686,7 @@ func (nw *reteNetworkImpl) removeJoinTableRowRefs(hdl types.ReteHandle, changedP for hdlTblIter.HasNext() { joinTableID, rowIDs := hdlTblIter.Next() - joinTable := nw.GetJoinTable(joinTableID) + joinTable := nw.jtService.GetJoinTable(joinTableID) toDelete := false if changedProps != nil { rule := joinTable.GetRule() From bc213e8a608228d255e649912371282ea40d9370 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Mon, 24 Dec 2018 00:39:30 +0530 Subject: [PATCH 021/125] refactor --- examples/flogo/simple/functions.go | 4 +-- examples/rulesapp/main.go | 4 +-- rete/internal/mem/mjointablecollectionimpl.go | 14 +++++----- rete/internal/mem/mjointableimpl.go | 10 +++++-- rete/internal/mem/mretehandle.go | 6 ----- rete/internal/redis/rhandlecollectionimpl.go | 2 -- .../redis/rjointablecollectionimpl.go | 17 ++++++------ rete/internal/redis/rjointableimpl.go | 10 +++++-- rete/internal/types/types.go | 16 +++--------- rete/joinnode.go | 4 +-- rete/network.go | 26 ++++++++----------- 11 files changed, 51 insertions(+), 62 deletions(-) diff --git a/examples/flogo/simple/functions.go b/examples/flogo/simple/functions.go index ffc838c..070975d 100644 --- a/examples/flogo/simple/functions.go +++ b/examples/flogo/simple/functions.go @@ -34,7 +34,7 @@ func checkForBobAction(ctx context.Context, rs model.RuleSession, ruleName strin fmt.Printf("Context is [%s]\n", ruleCtx) t1 := tuples["n1"] if t1 == nil { - fmt.Println("Should not get nil tuples here in JoinCondition! This is an error") + fmt.Println("Should not get nil tuples here in JoinCondition3! This is an error") return } } @@ -43,7 +43,7 @@ func checkSameNamesCondition(ruleName string, condName string, tuples map[model. t1 := tuples["n1"] t2 := tuples["n2"] if t1 == nil || t2 == nil { - fmt.Println("Should not get nil tuples here in JoinCondition! This is an error") + fmt.Println("Should not get nil tuples here in JoinCondition4! This is an error") return false } name1, _ := t1.GetString("name") diff --git a/examples/rulesapp/main.go b/examples/rulesapp/main.go index ea4bf0f..1fc10b7 100644 --- a/examples/rulesapp/main.go +++ b/examples/rulesapp/main.go @@ -95,7 +95,7 @@ func checkForBobAction(ctx context.Context, rs model.RuleSession, ruleName strin fmt.Printf("Context is [%s]\n", ruleCtx) t1 := tuples["n1"] if t1 == nil { - fmt.Println("Should not get nil tuples here in JoinCondition! This is an error") + fmt.Println("Should not get nil tuples here in JoinCondition1! This is an error") return } } @@ -104,7 +104,7 @@ func checkSameNamesCondition(ruleName string, condName string, tuples map[model. t1 := tuples["n1"] t2 := tuples["n2"] if t1 == nil || t2 == nil { - fmt.Println("Should not get nil tuples here in JoinCondition! This is an error") + fmt.Println("Should not get nil tuples here in JoinCondition2! This is an error") return false } name1, _ := t1.GetString("name") diff --git a/rete/internal/mem/mjointablecollectionimpl.go b/rete/internal/mem/mjointablecollectionimpl.go index c239f6d..fb0bbd8 100644 --- a/rete/internal/mem/mjointablecollectionimpl.go +++ b/rete/internal/mem/mjointablecollectionimpl.go @@ -26,17 +26,15 @@ func (jtc *joinTableCollectionImpl) AddJoinTable(joinTable types.JoinTable) { jtc.allJoinTables[joinTable.GetName()] = joinTable } -func (jtc *joinTableCollectionImpl) RemoveJoinTable(joinTable types.JoinTable) { - delete (jtc.allJoinTables,joinTable.GetName()) +func (jtc *joinTableCollectionImpl) RemoveJoinTable(jtName string) { + delete (jtc.allJoinTables,jtName) } -func (jtc *joinTableCollectionImpl) GetOrCreateJoinTable(nw types.Network, rule model.Rule, conditionVar model.Condition, identifiers []model.TupleType) types.JoinTable { - jT, found := jtc.allJoinTables[conditionVar.GetName()] +func (jtc *joinTableCollectionImpl) GetOrCreateJoinTable(nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) types.JoinTable { + jT, found := jtc.allJoinTables[name] if !found { - jTn := joinTableImpl{} - jTn.initJoinTableImpl(nw, rule, identifiers) - jtc.allJoinTables[conditionVar.GetName()] = &jTn - jT = &jTn + jT = newJoinTableImpl(nw, rule, identifiers, name) + jtc.allJoinTables[name] = jT } return jT } \ No newline at end of file diff --git a/rete/internal/mem/mjointableimpl.go b/rete/internal/mem/mjointableimpl.go index 9a362c4..c72d6da 100644 --- a/rete/internal/mem/mjointableimpl.go +++ b/rete/internal/mem/mjointableimpl.go @@ -13,12 +13,18 @@ type joinTableImpl struct { name string } +func newJoinTableImpl (nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) types.JoinTable { + jt := joinTableImpl{} + jt.initJoinTableImpl(nw, rule, identifiers, name) + return &jt +} -func (jt *joinTableImpl) initJoinTableImpl(nw types.Network, rule model.Rule, identifiers []model.TupleType) { +func (jt *joinTableImpl) initJoinTableImpl(nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) { jt.SetID(nw) jt.idr = identifiers jt.table = map[int]types.JoinTableRow{} jt.rule = rule + jt.name = name } func (jt *joinTableImpl) AddRow(handles []types.ReteHandle) types.JoinTableRow { @@ -28,7 +34,7 @@ func (jt *joinTableImpl) AddRow(handles []types.ReteHandle) types.JoinTableRow { jt.table[row.GetID()] = row for i := 0; i < len(row.GetHandles()); i++ { handle := row.GetHandles()[i] - handle.AddJoinTableRowRef(row, jt) + jt.Nw.GetJtRefService().AddEntry(handle, jt.name, row.GetID()) } return row } diff --git a/rete/internal/mem/mretehandle.go b/rete/internal/mem/mretehandle.go index dcfe026..b1f9415 100644 --- a/rete/internal/mem/mretehandle.go +++ b/rete/internal/mem/mretehandle.go @@ -11,7 +11,6 @@ type reteHandleImpl struct { types.NwElemIdImpl tuple model.Tuple tupleKey model.TupleKey - //jtRefs types.JtRefsService } func newReteHandleImpl(nw types.Network, tuple model.Tuple) types.ReteHandle { @@ -47,8 +46,3 @@ func (hdl *reteHandleImpl) AddJoinTableRowRef(joinTableRowVar types.JoinTableRow func (hdl *reteHandleImpl) RemoveJoinTable(joinTableID string) { hdl.Nw.GetJtRefService().RemoveEntry(hdl,joinTableID) } - -func (hdl *reteHandleImpl) GetRefTableIterator() types.HdlTblIterator { - refTblIterator := hdl.Nw.GetJtRefService().GetIterator(hdl) - return refTblIterator -} diff --git a/rete/internal/redis/rhandlecollectionimpl.go b/rete/internal/redis/rhandlecollectionimpl.go index 16d9e0d..94e602b 100644 --- a/rete/internal/redis/rhandlecollectionimpl.go +++ b/rete/internal/redis/rhandlecollectionimpl.go @@ -44,8 +44,6 @@ func (hc *handleCollectionImpl) GetOrCreateHandle(nw types.Network, tuple model. h, found := hc.allHandles[tuple.GetKey().String()] if !found { h = newReteHandleImpl(nw, tuple) - //hci := h.(*reteHandleImpl) - //hci.jtRefs = nw hc.allHandles[tuple.GetKey().String()] = h //[tuple.GetKey().String()] = h } return h diff --git a/rete/internal/redis/rjointablecollectionimpl.go b/rete/internal/redis/rjointablecollectionimpl.go index c578346..5f1e721 100644 --- a/rete/internal/redis/rjointablecollectionimpl.go +++ b/rete/internal/redis/rjointablecollectionimpl.go @@ -22,20 +22,19 @@ func (jtc *joinTableCollectionImpl) GetJoinTable(joinTableName string) types.Joi return jtc.allJoinTables[joinTableName] } -func (jtc *joinTableCollectionImpl) GetOrCreateJoinTable(nw types.Network, rule model.Rule, conditionVar model.Condition, identifiers []model.TupleType) types.JoinTable { - jT, found := jtc.allJoinTables[conditionVar.GetName()] +func (jtc *joinTableCollectionImpl) GetOrCreateJoinTable(nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) types.JoinTable { + jT, found := jtc.allJoinTables[name] if !found { - jTn := joinTableImpl{} - jTn.initJoinTableImpl(nw, rule, identifiers) - jtc.allJoinTables[conditionVar.GetName()] = &jTn - jT = &jTn + jT = newJoinTableImpl(nw, rule, identifiers, name) + jtc.allJoinTables[name] = jT } - return jT} + return jT +} func (jtc *joinTableCollectionImpl) AddJoinTable(joinTable types.JoinTable) { jtc.allJoinTables[joinTable.GetName()] = joinTable } -func (jtc *joinTableCollectionImpl) RemoveJoinTable(joinTable types.JoinTable) { - delete(jtc.allJoinTables,joinTable.GetName()) +func (jtc *joinTableCollectionImpl) RemoveJoinTable(jtName string) { + delete(jtc.allJoinTables,jtName) } \ No newline at end of file diff --git a/rete/internal/redis/rjointableimpl.go b/rete/internal/redis/rjointableimpl.go index ad81e92..7beec87 100644 --- a/rete/internal/redis/rjointableimpl.go +++ b/rete/internal/redis/rjointableimpl.go @@ -13,12 +13,18 @@ type joinTableImpl struct { name string } +func newJoinTableImpl (nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) types.JoinTable { + jt := joinTableImpl{} + jt.initJoinTableImpl(nw, rule, identifiers, name) + return &jt +} -func (jt *joinTableImpl) initJoinTableImpl(nw types.Network, rule model.Rule, identifiers []model.TupleType) { +func (jt *joinTableImpl) initJoinTableImpl(nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) { jt.SetID(nw) jt.idr = identifiers jt.table = map[int]types.JoinTableRow{} jt.rule = rule + jt.name = name } func (jt *joinTableImpl) AddRow(handles []types.ReteHandle) types.JoinTableRow { @@ -28,7 +34,7 @@ func (jt *joinTableImpl) AddRow(handles []types.ReteHandle) types.JoinTableRow { jt.table[row.GetID()] = row for i := 0; i < len(row.GetHandles()); i++ { handle := row.GetHandles()[i] - handle.AddJoinTableRowRef(row, jt) + jt.Nw.GetJtRefService().AddEntry(handle, jt.name, row.GetID()) } return row } diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index ab2b257..7295a07 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -9,11 +9,7 @@ import ( type Network interface { common.Network - - IncrementAndGetId() int - //GetJoinTable(jtName int) JoinTable - //AddToAllJoinTables(jT JoinTable) - + GetIdGenService() IdGen GetJtService() JtService GetHandleService() HandleService GetJtRefService() JtRefsService @@ -30,7 +26,7 @@ type NwElemIdImpl struct { func (ide *NwElemIdImpl) SetID(nw Network) { ide.Nw = nw - ide.ID = nw.IncrementAndGetId() + ide.ID = nw.GetIdGenService().GetNextID() } func (ide *NwElemIdImpl) GetID() int { return ide.ID @@ -57,11 +53,7 @@ type ReteHandle interface { NwElemId SetTuple(tuple model.Tuple) GetTuple() model.Tuple - AddJoinTableRowRef(joinTableRowVar JoinTableRow, joinTableVar JoinTable) - //removeJoinTableRowRefs(changedProps map[string]bool) - RemoveJoinTable(jtName string) GetTupleKey() model.TupleKey - GetRefTableIterator() HdlTblIterator } type RowIterator interface { @@ -83,10 +75,10 @@ type HdlTblIterator interface { type JtService interface { services.Service - GetOrCreateJoinTable(nw Network, rule model.Rule, conditionVar model.Condition, identifiers []model.TupleType) JoinTable + GetOrCreateJoinTable(nw Network, rule model.Rule, identifiers []model.TupleType, name string) JoinTable GetJoinTable (name string) JoinTable AddJoinTable(joinTable JoinTable) - RemoveJoinTable(joinTable JoinTable) + RemoveJoinTable(name string) } type HandleService interface { diff --git a/rete/joinnode.go b/rete/joinnode.go index ee9e411..205f8cf 100644 --- a/rete/joinnode.go +++ b/rete/joinnode.go @@ -42,8 +42,8 @@ func (jn *joinNodeImpl) initjoinNodeImplVar(nw *reteNetworkImpl, rule model.Rule jn.leftIdrs = leftIdrs jn.rightIdrs = rightIdrs jn.conditionVar = conditionVar - jn.leftTable = nw.GetJtService().GetOrCreateJoinTable(nw, rule, conditionVar, leftIdrs) - jn.rightTable = nw.GetJtService().GetOrCreateJoinTable(nw, rule, conditionVar, rightIdrs) + jn.leftTable = nw.GetJtService().GetOrCreateJoinTable(nw, rule, leftIdrs, "L_" + conditionVar.GetName()) + jn.rightTable = nw.GetJtService().GetOrCreateJoinTable(nw, rule, rightIdrs, "R_" + conditionVar.GetName()) jn.setJoinIdentifiers() } diff --git a/rete/network.go b/rete/network.go index e6ecf12..c14e853 100644 --- a/rete/network.go +++ b/rete/network.go @@ -171,8 +171,8 @@ func (nw *reteNetworkImpl) RemoveRule(ruleName string) model.Rule { //case *classNodeImpl: //case *ruleNodeImpl: case *joinNodeImpl: - removeRefsFromReteHandles(nodeImpl.leftTable) - removeRefsFromReteHandles(nodeImpl.rightTable) + nw.removeRefsFromReteHandles(nodeImpl.leftTable) + nw.removeRefsFromReteHandles(nodeImpl.rightTable) } } } @@ -189,7 +189,7 @@ func (nw *reteNetworkImpl) GetRules() []model.Rule { return rules } -func removeRefsFromReteHandles(joinTableVar types.JoinTable) { +func (nw *reteNetworkImpl) removeRefsFromReteHandles(joinTableVar types.JoinTable) { if joinTableVar == nil { return } @@ -197,7 +197,7 @@ func removeRefsFromReteHandles(joinTableVar types.JoinTable) { for rIterator.HasNext() { tableRow := rIterator.Next() for _, handle := range tableRow.GetHandles() { - handle.RemoveJoinTable(joinTableVar.GetName()) + nw.jtRefsService.RemoveEntry(handle, joinTableVar.GetName()) } } } @@ -638,18 +638,11 @@ func (nw *reteNetworkImpl) getHandle(tuple model.Tuple) types.ReteHandle { return h } -func (nw *reteNetworkImpl) IncrementAndGetId() int { - return nw.idGen.GetNextID() -} - func (nw *reteNetworkImpl) RegisterRtcTransactionHandler(txnHandler model.RtcTransactionHandler, txnContext interface{}) { nw.txnHandler = txnHandler nw.txnContext = txnContext } -// -//func (nw *reteNetworkImpl) GetJoinTable(joinTableID int) types.JoinTable { -// return nw.jtService.GetJoinTable(joinTableID) -//} + func (nw *reteNetworkImpl) GetConfigValue(key string) string { val, _ := nw.config[key] @@ -682,7 +675,7 @@ func (nw *reteNetworkImpl) removeJoinTableRowRefs(hdl types.ReteHandle, changedP tuple := hdl.GetTuple() alias := tuple.GetTupleType() - hdlTblIter := hdl.GetRefTableIterator() + hdlTblIter := nw.jtRefsService.GetIterator(hdl) for hdlTblIter.HasNext() { joinTableID, rowIDs := hdlTblIter.Next() @@ -719,16 +712,19 @@ func (nw *reteNetworkImpl) removeJoinTableRowRefs(hdl types.ReteHandle, changedP //Remove other refs recursively. for _, otherHdl := range row.GetHandles() { if otherHdl != nil { - nw.removeJoinTableRowRefs(otherHdl, nil) + //nw.removeJoinTableRowRefs(otherHdl, nil) } } } } //Remove the reference to the table itself - hdl.RemoveJoinTable(joinTableID) + nw.GetJtService().RemoveJoinTable(joinTableID) } } +func (nw *reteNetworkImpl) GetIdGenService() types.IdGen { + return nw.idGen +} func (nw *reteNetworkImpl) GetJtService() types.JtService { return nw.jtService From 8c970dc43b597e7402f0f42973ee6cf16a2d183d Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Tue, 25 Dec 2018 17:02:30 +0530 Subject: [PATCH 022/125] refactor --- rete/factory.go | 1 + rete/internal/mem/mjointablecollectionimpl.go | 6 +- rete/internal/mem/mjointableimpl.go | 6 +- .../mem/mjointablerefsinhandleimpl.go | 58 ++++++++++++++----- rete/internal/mem/mretehandle.go | 4 +- rete/internal/redis/rhandlecollectionimpl.go | 1 - .../redis/rjointablecollectionimpl.go | 6 +- rete/internal/redis/rjointableimpl.go | 6 +- .../redis/rjointablerefsinhandleimpl.go | 58 ++++++++++++++----- rete/internal/types/types.go | 5 +- rete/joinnode.go | 4 +- rete/network.go | 21 ++++--- ruleapi/rulesession.go | 12 +--- 13 files changed, 121 insertions(+), 67 deletions(-) diff --git a/rete/factory.go b/rete/factory.go index 078cd5e..5ff204e 100644 --- a/rete/factory.go +++ b/rete/factory.go @@ -21,6 +21,7 @@ func NewFactory(nw *reteNetworkImpl, config string) *TypeFactory { return &tf } + // //func (f *TypeFactory) getJoinTable(rule model.Rule, conditionVar model.Condition, identifiers []model.TupleType) types.JoinTable { // var jt types.JoinTable diff --git a/rete/internal/mem/mjointablecollectionimpl.go b/rete/internal/mem/mjointablecollectionimpl.go index fb0bbd8..22e2664 100644 --- a/rete/internal/mem/mjointablecollectionimpl.go +++ b/rete/internal/mem/mjointablecollectionimpl.go @@ -1,8 +1,8 @@ package mem import ( - "github.com/project-flogo/rules/rete/internal/types" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/internal/types" ) type joinTableCollectionImpl struct { @@ -27,7 +27,7 @@ func (jtc *joinTableCollectionImpl) AddJoinTable(joinTable types.JoinTable) { } func (jtc *joinTableCollectionImpl) RemoveJoinTable(jtName string) { - delete (jtc.allJoinTables,jtName) + delete(jtc.allJoinTables, jtName) } func (jtc *joinTableCollectionImpl) GetOrCreateJoinTable(nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) types.JoinTable { @@ -37,4 +37,4 @@ func (jtc *joinTableCollectionImpl) GetOrCreateJoinTable(nw types.Network, rule jtc.allJoinTables[name] = jT } return jT -} \ No newline at end of file +} diff --git a/rete/internal/mem/mjointableimpl.go b/rete/internal/mem/mjointableimpl.go index c72d6da..12b073f 100644 --- a/rete/internal/mem/mjointableimpl.go +++ b/rete/internal/mem/mjointableimpl.go @@ -10,10 +10,10 @@ type joinTableImpl struct { table map[int]types.JoinTableRow idr []model.TupleType rule model.Rule - name string + name string } -func newJoinTableImpl (nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) types.JoinTable { +func newJoinTableImpl(nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) types.JoinTable { jt := joinTableImpl{} jt.initJoinTableImpl(nw, rule, identifiers, name) return &jt @@ -66,4 +66,4 @@ func (jt *joinTableImpl) GetRow(rowID int) types.JoinTableRow { func (jt *joinTableImpl) GetName() string { return jt.name -} \ No newline at end of file +} diff --git a/rete/internal/mem/mjointablerefsinhandleimpl.go b/rete/internal/mem/mjointablerefsinhandleimpl.go index 4bbdbc3..db4272d 100644 --- a/rete/internal/mem/mjointablerefsinhandleimpl.go +++ b/rete/internal/mem/mjointablerefsinhandleimpl.go @@ -7,12 +7,12 @@ import ( type joinTableRefsInHdlImpl struct { //keys are jointable-ids and values are lists of row-ids in the corresponding join table - tablesAndRows map[string]*list.List + tablesAndRows map[string]map[string]*list.List } func NewJoinTableRefsInHdlImpl(config map[string]interface{}) types.JtRefsService { hdlJt := joinTableRefsInHdlImpl{} - hdlJt.tablesAndRows = make(map[string]*list.List) + hdlJt.tablesAndRows = make(map[string]map[string]*list.List) return &hdlJt } @@ -21,33 +21,65 @@ func (h *joinTableRefsInHdlImpl) Init() { } func (h *joinTableRefsInHdlImpl) AddEntry(handle types.ReteHandle, jtName string, rowID int) { - rowsForJoinTable := h.tablesAndRows[jtName] - if rowsForJoinTable == nil { + + tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] + + if !found { + tblMap = make(map[string]*list.List) + h.tablesAndRows[handle.GetTupleKey().String()] = tblMap + } + + rowsForJoinTable, found := tblMap[jtName] + if !found { rowsForJoinTable = list.New() - h.tablesAndRows[jtName] = rowsForJoinTable + tblMap[jtName] = rowsForJoinTable } rowsForJoinTable.PushBack(rowID) } func (h *joinTableRefsInHdlImpl) RemoveEntry(handle types.ReteHandle, jtName string) { - delete(h.tablesAndRows, jtName) + tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] + if found { + delete(tblMap, jtName) + } +} + +func (h *joinTableRefsInHdlImpl) RemoveRowEntry(handle types.ReteHandle, jtName string, rowID int) { + tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] + if found { + rowIDs, fnd := tblMap[jtName] + if fnd { + for e:= rowIDs.Front(); e != nil; e = e.Next() { + rowIDInList := e.Value.(int) + if rowID == rowIDInList { + rowIDs.Remove(e) + return + } + } + } + } } func (h *joinTableRefsInHdlImpl) GetIterator(handle types.ReteHandle) types.HdlTblIterator { ri := hdlTblIteratorImpl{} - ri.hdlJtImpl = h + //ri.hdlJtImpl = h ri.kList = list.List{} - for k, _ := range ri.hdlJtImpl.tablesAndRows { - ri.kList.PushBack(k) + + tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] + if found { + ri.tblMap = tblMap + for k, _ := range tblMap { + ri.kList.PushBack(k) + } } ri.curr = ri.kList.Front() return &ri } type hdlTblIteratorImpl struct { - hdlJtImpl *joinTableRefsInHdlImpl - kList list.List - curr *list.Element + tblMap map[string]*list.List + kList list.List + curr *list.Element } func (ri *hdlTblIteratorImpl) HasNext() bool { @@ -56,7 +88,7 @@ func (ri *hdlTblIteratorImpl) HasNext() bool { func (ri *hdlTblIteratorImpl) Next() (string, *list.List) { id := ri.curr.Value.(string) - lst := ri.hdlJtImpl.tablesAndRows[id] + lst := ri.tblMap[id] ri.curr = ri.curr.Next() return id, lst } diff --git a/rete/internal/mem/mretehandle.go b/rete/internal/mem/mretehandle.go index b1f9415..ab4c79e 100644 --- a/rete/internal/mem/mretehandle.go +++ b/rete/internal/mem/mretehandle.go @@ -39,10 +39,10 @@ func (hdl *reteHandleImpl) GetTupleKey() model.TupleKey { } func (hdl *reteHandleImpl) AddJoinTableRowRef(joinTableRowVar types.JoinTableRow, joinTableVar types.JoinTable) { - hdl.Nw.GetJtRefService().AddEntry(hdl,joinTableVar.GetName(), joinTableRowVar.GetID()) + hdl.Nw.GetJtRefService().AddEntry(hdl, joinTableVar.GetName(), joinTableRowVar.GetID()) } //Used when a rule is deleted. See Network.RemoveRule func (hdl *reteHandleImpl) RemoveJoinTable(joinTableID string) { - hdl.Nw.GetJtRefService().RemoveEntry(hdl,joinTableID) + hdl.Nw.GetJtRefService().RemoveEntry(hdl, joinTableID) } diff --git a/rete/internal/redis/rhandlecollectionimpl.go b/rete/internal/redis/rhandlecollectionimpl.go index 94e602b..778d3d3 100644 --- a/rete/internal/redis/rhandlecollectionimpl.go +++ b/rete/internal/redis/rhandlecollectionimpl.go @@ -48,4 +48,3 @@ func (hc *handleCollectionImpl) GetOrCreateHandle(nw types.Network, tuple model. } return h } - diff --git a/rete/internal/redis/rjointablecollectionimpl.go b/rete/internal/redis/rjointablecollectionimpl.go index 5f1e721..a0e902f 100644 --- a/rete/internal/redis/rjointablecollectionimpl.go +++ b/rete/internal/redis/rjointablecollectionimpl.go @@ -1,8 +1,8 @@ package redis import ( - "github.com/project-flogo/rules/rete/internal/types" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/internal/types" ) type joinTableCollectionImpl struct { @@ -36,5 +36,5 @@ func (jtc *joinTableCollectionImpl) AddJoinTable(joinTable types.JoinTable) { } func (jtc *joinTableCollectionImpl) RemoveJoinTable(jtName string) { - delete(jtc.allJoinTables,jtName) -} \ No newline at end of file + delete(jtc.allJoinTables, jtName) +} diff --git a/rete/internal/redis/rjointableimpl.go b/rete/internal/redis/rjointableimpl.go index 7beec87..f52753e 100644 --- a/rete/internal/redis/rjointableimpl.go +++ b/rete/internal/redis/rjointableimpl.go @@ -10,10 +10,10 @@ type joinTableImpl struct { table map[int]types.JoinTableRow idr []model.TupleType rule model.Rule - name string + name string } -func newJoinTableImpl (nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) types.JoinTable { +func newJoinTableImpl(nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) types.JoinTable { jt := joinTableImpl{} jt.initJoinTableImpl(nw, rule, identifiers, name) return &jt @@ -66,4 +66,4 @@ func (jt *joinTableImpl) GetRow(rowID int) types.JoinTableRow { func (jt *joinTableImpl) GetName() string { return jt.name -} \ No newline at end of file +} diff --git a/rete/internal/redis/rjointablerefsinhandleimpl.go b/rete/internal/redis/rjointablerefsinhandleimpl.go index bb0bf0c..0cad7ed 100644 --- a/rete/internal/redis/rjointablerefsinhandleimpl.go +++ b/rete/internal/redis/rjointablerefsinhandleimpl.go @@ -7,12 +7,12 @@ import ( type joinTableRefsInHdlImpl struct { //keys are jointable-ids and values are lists of row-ids in the corresponding join table - tablesAndRows map[string]*list.List + tablesAndRows map[string]map[string]*list.List } func NewJoinTableRefsInHdlImpl(config map[string]interface{}) types.JtRefsService { hdlJt := joinTableRefsInHdlImpl{} - hdlJt.tablesAndRows = make(map[string]*list.List) + hdlJt.tablesAndRows = make(map[string]map[string]*list.List) return &hdlJt } @@ -21,33 +21,65 @@ func (h *joinTableRefsInHdlImpl) Init() { } func (h *joinTableRefsInHdlImpl) AddEntry(handle types.ReteHandle, jtName string, rowID int) { - rowsForJoinTable := h.tablesAndRows[jtName] - if rowsForJoinTable == nil { + + tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] + + if !found { + tblMap = make(map[string]*list.List) + h.tablesAndRows[handle.GetTupleKey().String()] = tblMap + } + + rowsForJoinTable, found := tblMap[jtName] + if !found { rowsForJoinTable = list.New() - h.tablesAndRows[jtName] = rowsForJoinTable + tblMap[jtName] = rowsForJoinTable } rowsForJoinTable.PushBack(rowID) } func (h *joinTableRefsInHdlImpl) RemoveEntry(handle types.ReteHandle, jtName string) { - delete(h.tablesAndRows, jtName) + tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] + if found { + delete(tblMap, jtName) + } +} + +func (h *joinTableRefsInHdlImpl) RemoveRowEntry(handle types.ReteHandle, jtName string, rowID int) { + tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] + if found { + rowIDs, fnd := tblMap[jtName] + if fnd { + for e:= rowIDs.Front(); e != nil; e = e.Next() { + rowIDInList := e.Value.(int) + if rowID == rowIDInList { + rowIDs.Remove(e) + return + } + } + } + } } func (h *joinTableRefsInHdlImpl) GetIterator(handle types.ReteHandle) types.HdlTblIterator { ri := hdlTblIteratorImpl{} - ri.hdlJtImpl = h + //ri.hdlJtImpl = h ri.kList = list.List{} - for k, _ := range ri.hdlJtImpl.tablesAndRows { - ri.kList.PushBack(k) + + tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] + if found { + ri.tblMap = tblMap + for k, _ := range tblMap { + ri.kList.PushBack(k) + } } ri.curr = ri.kList.Front() return &ri } type hdlTblIteratorImpl struct { - hdlJtImpl *joinTableRefsInHdlImpl - kList list.List - curr *list.Element + tblMap map[string]*list.List + kList list.List + curr *list.Element } func (ri *hdlTblIteratorImpl) HasNext() bool { @@ -56,7 +88,7 @@ func (ri *hdlTblIteratorImpl) HasNext() bool { func (ri *hdlTblIteratorImpl) Next() (string, *list.List) { id := ri.curr.Value.(string) - lst := ri.hdlJtImpl.tablesAndRows[id] + lst := ri.tblMap[id] ri.curr = ri.curr.Next() return id, lst } diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index 7295a07..07187a5 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -3,8 +3,8 @@ package types import ( "container/list" "github.com/project-flogo/rules/common/model" - "github.com/project-flogo/rules/rete/common" "github.com/project-flogo/rules/common/services" + "github.com/project-flogo/rules/rete/common" ) type Network interface { @@ -64,6 +64,7 @@ type RowIterator interface { type JtRefsService interface { services.Service AddEntry(handle ReteHandle, jtName string, rowID int) + RemoveRowEntry(handle ReteHandle, jtName string, rowID int) RemoveEntry(handle ReteHandle, jtName string) GetIterator(handle ReteHandle) HdlTblIterator } @@ -76,7 +77,7 @@ type HdlTblIterator interface { type JtService interface { services.Service GetOrCreateJoinTable(nw Network, rule model.Rule, identifiers []model.TupleType, name string) JoinTable - GetJoinTable (name string) JoinTable + GetJoinTable(name string) JoinTable AddJoinTable(joinTable JoinTable) RemoveJoinTable(name string) } diff --git a/rete/joinnode.go b/rete/joinnode.go index 205f8cf..06a772a 100644 --- a/rete/joinnode.go +++ b/rete/joinnode.go @@ -42,8 +42,8 @@ func (jn *joinNodeImpl) initjoinNodeImplVar(nw *reteNetworkImpl, rule model.Rule jn.leftIdrs = leftIdrs jn.rightIdrs = rightIdrs jn.conditionVar = conditionVar - jn.leftTable = nw.GetJtService().GetOrCreateJoinTable(nw, rule, leftIdrs, "L_" + conditionVar.GetName()) - jn.rightTable = nw.GetJtService().GetOrCreateJoinTable(nw, rule, rightIdrs, "R_" + conditionVar.GetName()) + jn.leftTable = nw.GetJtService().GetOrCreateJoinTable(nw, rule, leftIdrs, "L_"+conditionVar.GetName()) + jn.rightTable = nw.GetJtService().GetOrCreateJoinTable(nw, rule, rightIdrs, "R_"+conditionVar.GetName()) jn.setJoinIdentifiers() } diff --git a/rete/network.go b/rete/network.go index c14e853..46f4c03 100644 --- a/rete/network.go +++ b/rete/network.go @@ -42,7 +42,7 @@ type reteNetworkImpl struct { jtRefsService types.JtRefsService - config map[string]string + config map[string]string factory *TypeFactory idGen types.IdGen @@ -584,7 +584,6 @@ func (nw *reteNetworkImpl) Retract(ctx context.Context, tuple model.Tuple, chang } func (nw *reteNetworkImpl) retractInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) { - if ctx == nil { ctx = context.Background() } @@ -643,7 +642,6 @@ func (nw *reteNetworkImpl) RegisterRtcTransactionHandler(txnHandler model.RtcTra nw.txnContext = txnContext } - func (nw *reteNetworkImpl) GetConfigValue(key string) string { val, _ := nw.config[key] return val @@ -671,15 +669,17 @@ func getOrCreateHandle(ctx context.Context, tuple model.Tuple) types.ReteHandle } func (nw *reteNetworkImpl) removeJoinTableRowRefs(hdl types.ReteHandle, changedProps map[string]bool) { - tuple := hdl.GetTuple() alias := tuple.GetTupleType() hdlTblIter := nw.jtRefsService.GetIterator(hdl) for hdlTblIter.HasNext() { - joinTableID, rowIDs := hdlTblIter.Next() - joinTable := nw.jtService.GetJoinTable(joinTableID) + jtName, rowIDs := hdlTblIter.Next() + joinTable := nw.jtService.GetJoinTable(jtName) + if joinTable == nil { + continue + } toDelete := false if changedProps != nil { rule := joinTable.GetRule() @@ -712,16 +712,15 @@ func (nw *reteNetworkImpl) removeJoinTableRowRefs(hdl types.ReteHandle, changedP //Remove other refs recursively. for _, otherHdl := range row.GetHandles() { if otherHdl != nil { - //nw.removeJoinTableRowRefs(otherHdl, nil) + nw.jtRefsService.RemoveRowEntry(otherHdl, jtName, rowID) } } } } - - //Remove the reference to the table itself - nw.GetJtService().RemoveJoinTable(joinTableID) } } + + func (nw *reteNetworkImpl) GetIdGenService() types.IdGen { return nw.idGen } @@ -736,4 +735,4 @@ func (nw *reteNetworkImpl) GetJtRefService() types.JtRefsService { func (nw *reteNetworkImpl) GetHandleService() types.HandleService { return nw.handleService -} \ No newline at end of file +} diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index 8919302..d09ffab 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -216,14 +216,4 @@ func (rs *rulesessionImpl) GetAssertedTuple(key model.TupleKey) model.Tuple { func (rs *rulesessionImpl) RegisterRtcTransactionHandler(txnHandler model.RtcTransactionHandler, txnContext interface{}) { rs.reteNetwork.RegisterRtcTransactionHandler(txnHandler, txnContext) -} - -// -//func (rs *rulesessionImpl) SetConfig(config map[string]string) { -// if rs.config == nil { -// rs.config = config -// } -// if rs.reteNetwork != nil && rs.reteNetwork.GetConfig() == nil { -// rs.reteNetwork.SetConfig(config) -// } -//} +} \ No newline at end of file From 2dce1eb0ae21da8fe29b9deedef9496e8c817315 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Tue, 25 Dec 2018 17:03:04 +0530 Subject: [PATCH 023/125] refactor --- rete/internal/mem/mjointablerefsinhandleimpl.go | 2 +- rete/internal/redis/rjointablerefsinhandleimpl.go | 2 +- rete/network.go | 1 - ruleapi/rulesession.go | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/rete/internal/mem/mjointablerefsinhandleimpl.go b/rete/internal/mem/mjointablerefsinhandleimpl.go index db4272d..d8669d3 100644 --- a/rete/internal/mem/mjointablerefsinhandleimpl.go +++ b/rete/internal/mem/mjointablerefsinhandleimpl.go @@ -49,7 +49,7 @@ func (h *joinTableRefsInHdlImpl) RemoveRowEntry(handle types.ReteHandle, jtName if found { rowIDs, fnd := tblMap[jtName] if fnd { - for e:= rowIDs.Front(); e != nil; e = e.Next() { + for e := rowIDs.Front(); e != nil; e = e.Next() { rowIDInList := e.Value.(int) if rowID == rowIDInList { rowIDs.Remove(e) diff --git a/rete/internal/redis/rjointablerefsinhandleimpl.go b/rete/internal/redis/rjointablerefsinhandleimpl.go index 0cad7ed..2b5df8f 100644 --- a/rete/internal/redis/rjointablerefsinhandleimpl.go +++ b/rete/internal/redis/rjointablerefsinhandleimpl.go @@ -49,7 +49,7 @@ func (h *joinTableRefsInHdlImpl) RemoveRowEntry(handle types.ReteHandle, jtName if found { rowIDs, fnd := tblMap[jtName] if fnd { - for e:= rowIDs.Front(); e != nil; e = e.Next() { + for e := rowIDs.Front(); e != nil; e = e.Next() { rowIDInList := e.Value.(int) if rowID == rowIDInList { rowIDs.Remove(e) diff --git a/rete/network.go b/rete/network.go index 46f4c03..12b9bcb 100644 --- a/rete/network.go +++ b/rete/network.go @@ -720,7 +720,6 @@ func (nw *reteNetworkImpl) removeJoinTableRowRefs(hdl types.ReteHandle, changedP } } - func (nw *reteNetworkImpl) GetIdGenService() types.IdGen { return nw.idGen } diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index d09ffab..729920e 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -216,4 +216,4 @@ func (rs *rulesessionImpl) GetAssertedTuple(key model.TupleKey) model.Tuple { func (rs *rulesessionImpl) RegisterRtcTransactionHandler(txnHandler model.RtcTransactionHandler, txnContext interface{}) { rs.reteNetwork.RegisterRtcTransactionHandler(txnHandler, txnContext) -} \ No newline at end of file +} From 9f2b44d03d733b795c071ad34ec5a0949dd379a5 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Tue, 25 Dec 2018 17:11:20 +0530 Subject: [PATCH 024/125] refactor --- .../mem/mjointablerefsinhandleimpl.go | 22 +++++++------------ .../redis/rjointablerefsinhandleimpl.go | 22 +++++++------------ rete/internal/types/types.go | 3 +-- rete/network.go | 5 ++--- 4 files changed, 19 insertions(+), 33 deletions(-) diff --git a/rete/internal/mem/mjointablerefsinhandleimpl.go b/rete/internal/mem/mjointablerefsinhandleimpl.go index d8669d3..fc21e3b 100644 --- a/rete/internal/mem/mjointablerefsinhandleimpl.go +++ b/rete/internal/mem/mjointablerefsinhandleimpl.go @@ -7,12 +7,12 @@ import ( type joinTableRefsInHdlImpl struct { //keys are jointable-ids and values are lists of row-ids in the corresponding join table - tablesAndRows map[string]map[string]*list.List + tablesAndRows map[string]map[string]map[int]int } func NewJoinTableRefsInHdlImpl(config map[string]interface{}) types.JtRefsService { hdlJt := joinTableRefsInHdlImpl{} - hdlJt.tablesAndRows = make(map[string]map[string]*list.List) + hdlJt.tablesAndRows = make(map[string]map[string]map[int]int) return &hdlJt } @@ -25,16 +25,16 @@ func (h *joinTableRefsInHdlImpl) AddEntry(handle types.ReteHandle, jtName string tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] if !found { - tblMap = make(map[string]*list.List) + tblMap = make(map[string]map[int]int) h.tablesAndRows[handle.GetTupleKey().String()] = tblMap } rowsForJoinTable, found := tblMap[jtName] if !found { - rowsForJoinTable = list.New() + rowsForJoinTable = make(map[int]int) tblMap[jtName] = rowsForJoinTable } - rowsForJoinTable.PushBack(rowID) + rowsForJoinTable[rowID] = rowID } func (h *joinTableRefsInHdlImpl) RemoveEntry(handle types.ReteHandle, jtName string) { @@ -49,13 +49,7 @@ func (h *joinTableRefsInHdlImpl) RemoveRowEntry(handle types.ReteHandle, jtName if found { rowIDs, fnd := tblMap[jtName] if fnd { - for e := rowIDs.Front(); e != nil; e = e.Next() { - rowIDInList := e.Value.(int) - if rowID == rowIDInList { - rowIDs.Remove(e) - return - } - } + delete(rowIDs, rowID) } } } @@ -77,7 +71,7 @@ func (h *joinTableRefsInHdlImpl) GetIterator(handle types.ReteHandle) types.HdlT } type hdlTblIteratorImpl struct { - tblMap map[string]*list.List + tblMap map[string]map[int]int kList list.List curr *list.Element } @@ -86,7 +80,7 @@ func (ri *hdlTblIteratorImpl) HasNext() bool { return ri.curr != nil } -func (ri *hdlTblIteratorImpl) Next() (string, *list.List) { +func (ri *hdlTblIteratorImpl) Next() (string, map[int]int) { id := ri.curr.Value.(string) lst := ri.tblMap[id] ri.curr = ri.curr.Next() diff --git a/rete/internal/redis/rjointablerefsinhandleimpl.go b/rete/internal/redis/rjointablerefsinhandleimpl.go index 2b5df8f..a32de14 100644 --- a/rete/internal/redis/rjointablerefsinhandleimpl.go +++ b/rete/internal/redis/rjointablerefsinhandleimpl.go @@ -7,12 +7,12 @@ import ( type joinTableRefsInHdlImpl struct { //keys are jointable-ids and values are lists of row-ids in the corresponding join table - tablesAndRows map[string]map[string]*list.List + tablesAndRows map[string]map[string]map[int]int } func NewJoinTableRefsInHdlImpl(config map[string]interface{}) types.JtRefsService { hdlJt := joinTableRefsInHdlImpl{} - hdlJt.tablesAndRows = make(map[string]map[string]*list.List) + hdlJt.tablesAndRows = make(map[string]map[string]map[int]int) return &hdlJt } @@ -25,16 +25,16 @@ func (h *joinTableRefsInHdlImpl) AddEntry(handle types.ReteHandle, jtName string tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] if !found { - tblMap = make(map[string]*list.List) + tblMap = make(map[string]map[int]int) h.tablesAndRows[handle.GetTupleKey().String()] = tblMap } rowsForJoinTable, found := tblMap[jtName] if !found { - rowsForJoinTable = list.New() + rowsForJoinTable = make(map[int]int) tblMap[jtName] = rowsForJoinTable } - rowsForJoinTable.PushBack(rowID) + rowsForJoinTable[rowID] = rowID } func (h *joinTableRefsInHdlImpl) RemoveEntry(handle types.ReteHandle, jtName string) { @@ -49,13 +49,7 @@ func (h *joinTableRefsInHdlImpl) RemoveRowEntry(handle types.ReteHandle, jtName if found { rowIDs, fnd := tblMap[jtName] if fnd { - for e := rowIDs.Front(); e != nil; e = e.Next() { - rowIDInList := e.Value.(int) - if rowID == rowIDInList { - rowIDs.Remove(e) - return - } - } + delete(rowIDs, rowID) } } } @@ -77,7 +71,7 @@ func (h *joinTableRefsInHdlImpl) GetIterator(handle types.ReteHandle) types.HdlT } type hdlTblIteratorImpl struct { - tblMap map[string]*list.List + tblMap map[string]map[int]int kList list.List curr *list.Element } @@ -86,7 +80,7 @@ func (ri *hdlTblIteratorImpl) HasNext() bool { return ri.curr != nil } -func (ri *hdlTblIteratorImpl) Next() (string, *list.List) { +func (ri *hdlTblIteratorImpl) Next() (string, map[int]int) { id := ri.curr.Value.(string) lst := ri.tblMap[id] ri.curr = ri.curr.Next() diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index 07187a5..8efb502 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -1,7 +1,6 @@ package types import ( - "container/list" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/common/services" "github.com/project-flogo/rules/rete/common" @@ -71,7 +70,7 @@ type JtRefsService interface { type HdlTblIterator interface { HasNext() bool - Next() (string, *list.List) + Next() (string, map[int]int) } type JtService interface { diff --git a/rete/network.go b/rete/network.go index 12b9bcb..8d3ce7e 100644 --- a/rete/network.go +++ b/rete/network.go @@ -705,11 +705,10 @@ func (nw *reteNetworkImpl) removeJoinTableRowRefs(hdl types.ReteHandle, changedP continue } ////Remove rows from corresponding join tables - for e := rowIDs.Front(); e != nil; e = e.Next() { - rowID := e.Value.(int) + for rowID, _ := range rowIDs { row := joinTable.RemoveRow(rowID) if row != nil { - //Remove other refs recursively. + //Remove this (table+row) link from other handle refs of this row! for _, otherHdl := range row.GetHandles() { if otherHdl != nil { nw.jtRefsService.RemoveRowEntry(otherHdl, jtName, rowID) From 4e5097a15b5c73f3c1f6585af3551e6c84fd3431 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Tue, 25 Dec 2018 17:20:48 +0530 Subject: [PATCH 025/125] refactor --- rete/network.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rete/network.go b/rete/network.go index 8d3ce7e..63f8439 100644 --- a/rete/network.go +++ b/rete/network.go @@ -710,7 +710,7 @@ func (nw *reteNetworkImpl) removeJoinTableRowRefs(hdl types.ReteHandle, changedP if row != nil { //Remove this (table+row) link from other handle refs of this row! for _, otherHdl := range row.GetHandles() { - if otherHdl != nil { + if otherHdl.GetTupleKey().String() != hdl.GetTupleKey().String() { nw.jtRefsService.RemoveRowEntry(otherHdl, jtName, rowID) } } From 35a141dadc742c8260124617fbc03cbf39289133 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Tue, 25 Dec 2018 19:00:58 +0530 Subject: [PATCH 026/125] rename and refactor --- ...handlecollectionimpl.go => mhandleservice.go} | 16 ++++++++-------- .../mem/{midgenimpl.go => midgenservice.go} | 10 +++++----- ...ablerefsinhandleimpl.go => mjtrefsservice.go} | 14 +++++++------- ...mjointablecollectionimpl.go => mjtservice.go} | 14 +++++++------- ...handlecollectionimpl.go => rhandleservice.go} | 16 ++++++++-------- .../redis/{ridgenimpl.go => ridgenservice.go} | 10 +++++----- ...ablerefsinhandleimpl.go => rjtrefsservice.go} | 14 +++++++------- ...rjointablecollectionimpl.go => rjtservice.go} | 14 +++++++------- 8 files changed, 54 insertions(+), 54 deletions(-) rename rete/internal/mem/{mhandlecollectionimpl.go => mhandleservice.go} (62%) rename rete/internal/mem/{midgenimpl.go => midgenservice.go} (69%) rename rete/internal/mem/{mjointablerefsinhandleimpl.go => mjtrefsservice.go} (76%) rename rete/internal/mem/{mjointablecollectionimpl.go => mjtservice.go} (54%) rename rete/internal/redis/{rhandlecollectionimpl.go => rhandleservice.go} (62%) rename rete/internal/redis/{ridgenimpl.go => ridgenservice.go} (83%) rename rete/internal/redis/{rjointablerefsinhandleimpl.go => rjtrefsservice.go} (76%) rename rete/internal/redis/{rjointablecollectionimpl.go => rjtservice.go} (54%) diff --git a/rete/internal/mem/mhandlecollectionimpl.go b/rete/internal/mem/mhandleservice.go similarity index 62% rename from rete/internal/mem/mhandlecollectionimpl.go rename to rete/internal/mem/mhandleservice.go index eadf1f6..895cfb0 100644 --- a/rete/internal/mem/mhandlecollectionimpl.go +++ b/rete/internal/mem/mhandleservice.go @@ -5,25 +5,25 @@ import ( "github.com/project-flogo/rules/rete/internal/types" ) -type handleCollectionImpl struct { +type handleServiceImpl struct { allHandles map[string]types.ReteHandle } func NewHandleCollection(config map[string]interface{}) types.HandleService { - hc := handleCollectionImpl{} + hc := handleServiceImpl{} hc.allHandles = make(map[string]types.ReteHandle) return &hc } -func (hc *handleCollectionImpl) Init() { +func (hc *handleServiceImpl) Init() { } -func (hc *handleCollectionImpl) AddHandle(hdl types.ReteHandle) { +func (hc *handleServiceImpl) AddHandle(hdl types.ReteHandle) { hc.allHandles[hdl.GetTupleKey().String()] = hdl } -func (hc *handleCollectionImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle { +func (hc *handleServiceImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle { rh, found := hc.allHandles[tuple.GetKey().String()] if found { delete(hc.allHandles, tuple.GetKey().String()) @@ -32,15 +32,15 @@ func (hc *handleCollectionImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle return nil } -func (hc *handleCollectionImpl) GetHandle(tuple model.Tuple) types.ReteHandle { +func (hc *handleServiceImpl) GetHandle(tuple model.Tuple) types.ReteHandle { return hc.allHandles[tuple.GetKey().String()] } -func (hc *handleCollectionImpl) GetHandleByKey(key model.TupleKey) types.ReteHandle { +func (hc *handleServiceImpl) GetHandleByKey(key model.TupleKey) types.ReteHandle { return hc.allHandles[key.String()] } -func (hc *handleCollectionImpl) GetOrCreateHandle(nw types.Network, tuple model.Tuple) types.ReteHandle { +func (hc *handleServiceImpl) GetOrCreateHandle(nw types.Network, tuple model.Tuple) types.ReteHandle { h, found := hc.allHandles[tuple.GetKey().String()] if !found { h = newReteHandleImpl(nw, tuple) diff --git a/rete/internal/mem/midgenimpl.go b/rete/internal/mem/midgenservice.go similarity index 69% rename from rete/internal/mem/midgenimpl.go rename to rete/internal/mem/midgenservice.go index ef51364..148e158 100644 --- a/rete/internal/mem/midgenimpl.go +++ b/rete/internal/mem/midgenservice.go @@ -5,28 +5,28 @@ import ( "sync/atomic" ) -type idGenImpl struct { +type idGenServiceImpl struct { config map[string]interface{} currentId int32 } func NewIdImpl(config map[string]interface{}) types.IdGen { - idg := idGenImpl{} + idg := idGenServiceImpl{} idg.config = config idg.currentId = 0 return &idg } -func (id *idGenImpl) Init() { +func (id *idGenServiceImpl) Init() { id.currentId = int32(id.GetMaxID()) } -func (id *idGenImpl) GetNextID() int { +func (id *idGenServiceImpl) GetNextID() int { i := atomic.AddInt32(&id.currentId, 1) return int(i) } -func (id *idGenImpl) GetMaxID() int { +func (id *idGenServiceImpl) GetMaxID() int { i := atomic.LoadInt32(&id.currentId) return int(i) } diff --git a/rete/internal/mem/mjointablerefsinhandleimpl.go b/rete/internal/mem/mjtrefsservice.go similarity index 76% rename from rete/internal/mem/mjointablerefsinhandleimpl.go rename to rete/internal/mem/mjtrefsservice.go index fc21e3b..2611233 100644 --- a/rete/internal/mem/mjointablerefsinhandleimpl.go +++ b/rete/internal/mem/mjtrefsservice.go @@ -5,22 +5,22 @@ import ( "github.com/project-flogo/rules/rete/internal/types" ) -type joinTableRefsInHdlImpl struct { +type jtRefsServiceImpl struct { //keys are jointable-ids and values are lists of row-ids in the corresponding join table tablesAndRows map[string]map[string]map[int]int } func NewJoinTableRefsInHdlImpl(config map[string]interface{}) types.JtRefsService { - hdlJt := joinTableRefsInHdlImpl{} + hdlJt := jtRefsServiceImpl{} hdlJt.tablesAndRows = make(map[string]map[string]map[int]int) return &hdlJt } -func (h *joinTableRefsInHdlImpl) Init() { +func (h *jtRefsServiceImpl) Init() { } -func (h *joinTableRefsInHdlImpl) AddEntry(handle types.ReteHandle, jtName string, rowID int) { +func (h *jtRefsServiceImpl) AddEntry(handle types.ReteHandle, jtName string, rowID int) { tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] @@ -37,14 +37,14 @@ func (h *joinTableRefsInHdlImpl) AddEntry(handle types.ReteHandle, jtName string rowsForJoinTable[rowID] = rowID } -func (h *joinTableRefsInHdlImpl) RemoveEntry(handle types.ReteHandle, jtName string) { +func (h *jtRefsServiceImpl) RemoveEntry(handle types.ReteHandle, jtName string) { tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] if found { delete(tblMap, jtName) } } -func (h *joinTableRefsInHdlImpl) RemoveRowEntry(handle types.ReteHandle, jtName string, rowID int) { +func (h *jtRefsServiceImpl) RemoveRowEntry(handle types.ReteHandle, jtName string, rowID int) { tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] if found { rowIDs, fnd := tblMap[jtName] @@ -54,7 +54,7 @@ func (h *joinTableRefsInHdlImpl) RemoveRowEntry(handle types.ReteHandle, jtName } } -func (h *joinTableRefsInHdlImpl) GetIterator(handle types.ReteHandle) types.HdlTblIterator { +func (h *jtRefsServiceImpl) GetIterator(handle types.ReteHandle) types.HdlTblIterator { ri := hdlTblIteratorImpl{} //ri.hdlJtImpl = h ri.kList = list.List{} diff --git a/rete/internal/mem/mjointablecollectionimpl.go b/rete/internal/mem/mjtservice.go similarity index 54% rename from rete/internal/mem/mjointablecollectionimpl.go rename to rete/internal/mem/mjtservice.go index 22e2664..c02acec 100644 --- a/rete/internal/mem/mjointablecollectionimpl.go +++ b/rete/internal/mem/mjtservice.go @@ -5,32 +5,32 @@ import ( "github.com/project-flogo/rules/rete/internal/types" ) -type joinTableCollectionImpl struct { +type jtServiceImpl struct { allJoinTables map[string]types.JoinTable } func NewJoinTableCollection(config map[string]interface{}) types.JtService { - jtc := joinTableCollectionImpl{} + jtc := jtServiceImpl{} jtc.allJoinTables = make(map[string]types.JoinTable) return &jtc } -func (jtc *joinTableCollectionImpl) Init() { +func (jtc *jtServiceImpl) Init() { } -func (jtc *joinTableCollectionImpl) GetJoinTable(joinTableName string) types.JoinTable { +func (jtc *jtServiceImpl) GetJoinTable(joinTableName string) types.JoinTable { return jtc.allJoinTables[joinTableName] } -func (jtc *joinTableCollectionImpl) AddJoinTable(joinTable types.JoinTable) { +func (jtc *jtServiceImpl) AddJoinTable(joinTable types.JoinTable) { jtc.allJoinTables[joinTable.GetName()] = joinTable } -func (jtc *joinTableCollectionImpl) RemoveJoinTable(jtName string) { +func (jtc *jtServiceImpl) RemoveJoinTable(jtName string) { delete(jtc.allJoinTables, jtName) } -func (jtc *joinTableCollectionImpl) GetOrCreateJoinTable(nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) types.JoinTable { +func (jtc *jtServiceImpl) GetOrCreateJoinTable(nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) types.JoinTable { jT, found := jtc.allJoinTables[name] if !found { jT = newJoinTableImpl(nw, rule, identifiers, name) diff --git a/rete/internal/redis/rhandlecollectionimpl.go b/rete/internal/redis/rhandleservice.go similarity index 62% rename from rete/internal/redis/rhandlecollectionimpl.go rename to rete/internal/redis/rhandleservice.go index 778d3d3..f88b44e 100644 --- a/rete/internal/redis/rhandlecollectionimpl.go +++ b/rete/internal/redis/rhandleservice.go @@ -5,25 +5,25 @@ import ( "github.com/project-flogo/rules/rete/internal/types" ) -type handleCollectionImpl struct { +type handleServiceImpl struct { allHandles map[string]types.ReteHandle } func NewHandleCollection(config map[string]interface{}) types.HandleService { - hc := handleCollectionImpl{} + hc := handleServiceImpl{} hc.allHandles = make(map[string]types.ReteHandle) return &hc } -func (hc *handleCollectionImpl) Init() { +func (hc *handleServiceImpl) Init() { } -func (hc *handleCollectionImpl) AddHandle(hdl types.ReteHandle) { +func (hc *handleServiceImpl) AddHandle(hdl types.ReteHandle) { hc.allHandles[hdl.GetTupleKey().String()] = hdl } -func (hc *handleCollectionImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle { +func (hc *handleServiceImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle { rh, found := hc.allHandles[tuple.GetKey().String()] if found { delete(hc.allHandles, tuple.GetKey().String()) @@ -32,15 +32,15 @@ func (hc *handleCollectionImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle return nil } -func (hc *handleCollectionImpl) GetHandle(tuple model.Tuple) types.ReteHandle { +func (hc *handleServiceImpl) GetHandle(tuple model.Tuple) types.ReteHandle { return hc.allHandles[tuple.GetKey().String()] } -func (hc *handleCollectionImpl) GetHandleByKey(key model.TupleKey) types.ReteHandle { +func (hc *handleServiceImpl) GetHandleByKey(key model.TupleKey) types.ReteHandle { return hc.allHandles[key.String()] } -func (hc *handleCollectionImpl) GetOrCreateHandle(nw types.Network, tuple model.Tuple) types.ReteHandle { +func (hc *handleServiceImpl) GetOrCreateHandle(nw types.Network, tuple model.Tuple) types.ReteHandle { h, found := hc.allHandles[tuple.GetKey().String()] if !found { h = newReteHandleImpl(nw, tuple) diff --git a/rete/internal/redis/ridgenimpl.go b/rete/internal/redis/ridgenservice.go similarity index 83% rename from rete/internal/redis/ridgenimpl.go rename to rete/internal/redis/ridgenservice.go index f56e437..2962d50 100644 --- a/rete/internal/redis/ridgenimpl.go +++ b/rete/internal/redis/ridgenservice.go @@ -12,25 +12,25 @@ const ( hincrby = "HINCRBY" ) -type ridImpl struct { +type idGenServiceImpl struct { config map[string]interface{} //current int rh redisutils.RedisHdl } func NewIdImpl(config map[string]interface{}) types.IdGen { - r := ridImpl{} + r := idGenServiceImpl{} r.config = config return &r } -func (ri *ridImpl) Init() { +func (ri *idGenServiceImpl) Init() { redisutils.InitService(ri.config) j := ri.GetMaxID() fmt.Printf("maxid : [%d]\n ", j) } -func (ri *ridImpl) GetMaxID() int { +func (ri *idGenServiceImpl) GetMaxID() int { ri.rh = redisutils.GetRedisHdl() c := ri.rh.GetPool().Get() defer c.Close() @@ -43,7 +43,7 @@ func (ri *ridImpl) GetMaxID() int { return -1 } -func (ri *ridImpl) GetNextID() int { +func (ri *idGenServiceImpl) GetNextID() int { ri.rh = redisutils.GetRedisHdl() c := ri.rh.GetPool().Get() defer c.Close() diff --git a/rete/internal/redis/rjointablerefsinhandleimpl.go b/rete/internal/redis/rjtrefsservice.go similarity index 76% rename from rete/internal/redis/rjointablerefsinhandleimpl.go rename to rete/internal/redis/rjtrefsservice.go index a32de14..1d2480b 100644 --- a/rete/internal/redis/rjointablerefsinhandleimpl.go +++ b/rete/internal/redis/rjtrefsservice.go @@ -5,22 +5,22 @@ import ( "github.com/project-flogo/rules/rete/internal/types" ) -type joinTableRefsInHdlImpl struct { +type jtRefsServiceImpl struct { //keys are jointable-ids and values are lists of row-ids in the corresponding join table tablesAndRows map[string]map[string]map[int]int } func NewJoinTableRefsInHdlImpl(config map[string]interface{}) types.JtRefsService { - hdlJt := joinTableRefsInHdlImpl{} + hdlJt := jtRefsServiceImpl{} hdlJt.tablesAndRows = make(map[string]map[string]map[int]int) return &hdlJt } -func (h *joinTableRefsInHdlImpl) Init() { +func (h *jtRefsServiceImpl) Init() { } -func (h *joinTableRefsInHdlImpl) AddEntry(handle types.ReteHandle, jtName string, rowID int) { +func (h *jtRefsServiceImpl) AddEntry(handle types.ReteHandle, jtName string, rowID int) { tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] @@ -37,14 +37,14 @@ func (h *joinTableRefsInHdlImpl) AddEntry(handle types.ReteHandle, jtName string rowsForJoinTable[rowID] = rowID } -func (h *joinTableRefsInHdlImpl) RemoveEntry(handle types.ReteHandle, jtName string) { +func (h *jtRefsServiceImpl) RemoveEntry(handle types.ReteHandle, jtName string) { tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] if found { delete(tblMap, jtName) } } -func (h *joinTableRefsInHdlImpl) RemoveRowEntry(handle types.ReteHandle, jtName string, rowID int) { +func (h *jtRefsServiceImpl) RemoveRowEntry(handle types.ReteHandle, jtName string, rowID int) { tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] if found { rowIDs, fnd := tblMap[jtName] @@ -54,7 +54,7 @@ func (h *joinTableRefsInHdlImpl) RemoveRowEntry(handle types.ReteHandle, jtName } } -func (h *joinTableRefsInHdlImpl) GetIterator(handle types.ReteHandle) types.HdlTblIterator { +func (h *jtRefsServiceImpl) GetIterator(handle types.ReteHandle) types.HdlTblIterator { ri := hdlTblIteratorImpl{} //ri.hdlJtImpl = h ri.kList = list.List{} diff --git a/rete/internal/redis/rjointablecollectionimpl.go b/rete/internal/redis/rjtservice.go similarity index 54% rename from rete/internal/redis/rjointablecollectionimpl.go rename to rete/internal/redis/rjtservice.go index a0e902f..1c4b3c0 100644 --- a/rete/internal/redis/rjointablecollectionimpl.go +++ b/rete/internal/redis/rjtservice.go @@ -5,24 +5,24 @@ import ( "github.com/project-flogo/rules/rete/internal/types" ) -type joinTableCollectionImpl struct { +type jtServiceImpl struct { allJoinTables map[string]types.JoinTable } func NewJoinTableCollection(config map[string]interface{}) types.JtService { - jtc := joinTableCollectionImpl{} + jtc := jtServiceImpl{} jtc.allJoinTables = make(map[string]types.JoinTable) return &jtc } -func (jtc *joinTableCollectionImpl) Init() { +func (jtc *jtServiceImpl) Init() { } -func (jtc *joinTableCollectionImpl) GetJoinTable(joinTableName string) types.JoinTable { +func (jtc *jtServiceImpl) GetJoinTable(joinTableName string) types.JoinTable { return jtc.allJoinTables[joinTableName] } -func (jtc *joinTableCollectionImpl) GetOrCreateJoinTable(nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) types.JoinTable { +func (jtc *jtServiceImpl) GetOrCreateJoinTable(nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) types.JoinTable { jT, found := jtc.allJoinTables[name] if !found { jT = newJoinTableImpl(nw, rule, identifiers, name) @@ -31,10 +31,10 @@ func (jtc *joinTableCollectionImpl) GetOrCreateJoinTable(nw types.Network, rule return jT } -func (jtc *joinTableCollectionImpl) AddJoinTable(joinTable types.JoinTable) { +func (jtc *jtServiceImpl) AddJoinTable(joinTable types.JoinTable) { jtc.allJoinTables[joinTable.GetName()] = joinTable } -func (jtc *joinTableCollectionImpl) RemoveJoinTable(jtName string) { +func (jtc *jtServiceImpl) RemoveJoinTable(jtName string) { delete(jtc.allJoinTables, jtName) } From fa6c28130f3c6b153e76179a0fd59e777afb8c22 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Fri, 28 Dec 2018 21:58:32 +0530 Subject: [PATCH 027/125] added redis utility methods --- examples/rulesapp/rsconfig.json | 2 +- redisutils/redisutil_test.go | 52 ++++--------------- redisutils/redisutils.go | 72 ++++++++++++++++++++++++++- rete/internal/redis/rhandleservice.go | 3 +- rete/internal/redis/ridgenservice.go | 27 ++-------- 5 files changed, 87 insertions(+), 69 deletions(-) diff --git a/examples/rulesapp/rsconfig.json b/examples/rulesapp/rsconfig.json index 1a1341f..8b57081 100644 --- a/examples/rulesapp/rsconfig.json +++ b/examples/rulesapp/rsconfig.json @@ -4,7 +4,7 @@ }, "rete": { "jt": "mem", - "idgen" : "mem" + "idgen" : "redis" }, "store": [ { diff --git a/redisutils/redisutil_test.go b/redisutils/redisutil_test.go index 4e82f4f..d4e3833 100644 --- a/redisutils/redisutil_test.go +++ b/redisutils/redisutil_test.go @@ -11,16 +11,16 @@ func Test_first(t *testing.T) { InitService(nil) rd := GetRedisHdl() - pool := rd.GetPool() - conn := pool.Get() - defer conn.Close() - //err := ping(conn) - //if err != nil { - // fmt.Println(err) - //} - - mset(conn) - mget(conn) + + m := make(map[string]interface{}) + m["k1"] = "v1" + + rd.HSetAll("myhash", m) + x := rd.HGetAll("myhash") + + for k, v := range x { + fmt.Printf("key=[%s], value=[%s]\n", k, v) + } } // ping tests connectivity for redisutils (PONG should be returned) @@ -151,35 +151,3 @@ func getStruct(c redis.Conn) error { return nil } - -func mset(c redis.Conn) error { - - vals, error := c.Do("HMSET", "key1", "f1", 1, "f2", "Hi", "f3", 3.14, "f4", true) - if error != nil { - fmt.Printf("error [%v]\n", error) - } else { - fmt.Printf("ret: [%v]\n", vals) - } - return error -} - -func mget(c redis.Conn) { - - vals, error := c.Do("HGETALL", "key1") - if error != nil { - fmt.Printf("error [%v]\n", error) - } else { - vals, err2 := redis.Values(vals, error) - if err2 != nil { - fmt.Printf("error [%v]\n", err2) - } else { - for _, val := range vals { - ba := val.([]byte) - s := string(ba) - fmt.Printf("Value [%s]\n", s) - } - } - - } - //return error -} diff --git a/redisutils/redisutils.go b/redisutils/redisutils.go index d95b335..c667496 100644 --- a/redisutils/redisutils.go +++ b/redisutils/redisutils.go @@ -1,6 +1,7 @@ package redisutils import ( + "fmt" "github.com/gomodule/redigo/redis" ) @@ -43,6 +44,75 @@ func (rh *RedisHandle) newPool(network string, address string) { } } -func (rh *RedisHandle) GetPool() *redis.Pool { +func (rh *RedisHandle) getPool() *redis.Pool { return rh.pool } + +func (rh *RedisHandle) HSetAll(key string, kvs map[string]interface{}) error { + + var args = []interface{}{key} + for f, v := range kvs { + args = append(args, f, v) + } + c := rd.getPool().Get() + defer c.Close() + _, error := c.Do("HMSET", args...) + + return error +} + +func (rh *RedisHandle) HGetAll(key string) map[string]string { + hgetall := make(map[string]string) + c := rh.getPool() + defer c.Close() + vals, error := c.Get().Do("HGETALL", key) + if error != nil { + fmt.Printf("error [%v]\n", error) + } else { + vals, err2 := redis.Values(vals, error) + if err2 != nil { + fmt.Printf("error [%v]\n", err2) + } else { + i := 0 + key := "" + value := "" + for _, val := range vals { + ba := val.([]byte) + s := string(ba) + //fmt.Printf("Value [%s]\n", s) + if i%2 == 0 { + key = s + } else { + value = s + hgetall[key] = value + } + i++ + } + } + } + return hgetall +} + +func (rh *RedisHandle) HIncrBy(key string, field string, by int) int { + c := rh.getPool().Get() + defer c.Close() + i, err := c.Do("HINCRBY", key, field, 1) + + if err != nil { + fmt.Printf("error: [%s]", err) + return -1 + } + current := int(i.(int64)) + return current +} + +func (rh *RedisHandle) HGetAsInt(key string, field string) int { + c := rh.getPool().Get() + defer c.Close() + i, err := c.Do("HGET", key, field) + j := -1 + if err == nil { + j, _ = redis.Int(i, err) + } + return j +} diff --git a/rete/internal/redis/rhandleservice.go b/rete/internal/redis/rhandleservice.go index f88b44e..e0ba495 100644 --- a/rete/internal/redis/rhandleservice.go +++ b/rete/internal/redis/rhandleservice.go @@ -44,7 +44,8 @@ func (hc *handleServiceImpl) GetOrCreateHandle(nw types.Network, tuple model.Tup h, found := hc.allHandles[tuple.GetKey().String()] if !found { h = newReteHandleImpl(nw, tuple) - hc.allHandles[tuple.GetKey().String()] = h //[tuple.GetKey().String()] = h + hc.allHandles[tuple.GetKey().String()] = h } + return h } diff --git a/rete/internal/redis/ridgenservice.go b/rete/internal/redis/ridgenservice.go index 2962d50..df9a145 100644 --- a/rete/internal/redis/ridgenservice.go +++ b/rete/internal/redis/ridgenservice.go @@ -2,7 +2,6 @@ package redis import ( "fmt" - "github.com/gomodule/redigo/redis" "github.com/project-flogo/rules/redisutils" "github.com/project-flogo/rules/rete/internal/types" ) @@ -26,35 +25,15 @@ func NewIdImpl(config map[string]interface{}) types.IdGen { func (ri *idGenServiceImpl) Init() { redisutils.InitService(ri.config) + ri.rh = redisutils.GetRedisHdl() j := ri.GetMaxID() fmt.Printf("maxid : [%d]\n ", j) } func (ri *idGenServiceImpl) GetMaxID() int { - ri.rh = redisutils.GetRedisHdl() - c := ri.rh.GetPool().Get() - defer c.Close() - - i, err := c.Do(hget, "IDGEN", "ID") - if err == nil { - j, _ := redis.Int(i, err) - return j - } - return -1 + return ri.rh.HGetAsInt("IDGEN", "ID") } func (ri *idGenServiceImpl) GetNextID() int { - ri.rh = redisutils.GetRedisHdl() - c := ri.rh.GetPool().Get() - defer c.Close() - - i, err := c.Do(hincrby, "IDGEN", "ID", 1) - - if err != nil { - fmt.Printf("error: [%s]", err) - return -1 - } - current := int(i.(int64)) - return current - + return ri.rh.HIncrBy("IDGEN", "ID", 1) } From c686129fd132f53880550e8378bd3f650cfa74a4 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Sun, 30 Dec 2018 23:25:49 +0530 Subject: [PATCH 028/125] redis util methods --- common/services/services.go | 4 +- examples/rulesapp/rsconfig.json | 4 +- redisutils/redisutils.go | 17 ++++++-- rete/factory.go | 6 +-- rete/internal/mem/mhandleservice.go | 5 +-- rete/internal/mem/midgenservice.go | 5 ++- rete/internal/mem/mjtrefsservice.go | 1 + rete/internal/mem/mjtservice.go | 1 + rete/internal/redis/rhandleservice.go | 61 +++++++++++++++++++-------- rete/internal/redis/ridgenservice.go | 4 +- rete/internal/redis/rjtrefsservice.go | 2 + rete/internal/redis/rjtservice.go | 2 + rete/internal/types/types.go | 24 ++++++++--- rete/network.go | 3 ++ 14 files changed, 102 insertions(+), 37 deletions(-) diff --git a/common/services/services.go b/common/services/services.go index ccef011..a703cc9 100644 --- a/common/services/services.go +++ b/common/services/services.go @@ -1,6 +1,8 @@ package services -import "github.com/project-flogo/rules/common/model" +import ( + "github.com/project-flogo/rules/common/model" +) type Service interface { Init() diff --git a/examples/rulesapp/rsconfig.json b/examples/rulesapp/rsconfig.json index 8b57081..5785c2e 100644 --- a/examples/rulesapp/rsconfig.json +++ b/examples/rulesapp/rsconfig.json @@ -3,12 +3,12 @@ "store-ref" : "mem" }, "rete": { - "jt": "mem", + "jt": "redis", "idgen" : "redis" }, "store": [ { - "name": "memory" + "name": "mem" }, { "name": "redis", diff --git a/redisutils/redisutils.go b/redisutils/redisutils.go index c667496..d2ef6a0 100644 --- a/redisutils/redisutils.go +++ b/redisutils/redisutils.go @@ -55,7 +55,7 @@ func (rh *RedisHandle) HSetAll(key string, kvs map[string]interface{}) error { args = append(args, f, v) } c := rd.getPool().Get() - defer c.Close() + //defer c.Close() _, error := c.Do("HMSET", args...) return error @@ -64,7 +64,7 @@ func (rh *RedisHandle) HSetAll(key string, kvs map[string]interface{}) error { func (rh *RedisHandle) HGetAll(key string) map[string]string { hgetall := make(map[string]string) c := rh.getPool() - defer c.Close() + //defer c.Close() vals, error := c.Get().Do("HGETALL", key) if error != nil { fmt.Printf("error [%v]\n", error) @@ -95,7 +95,7 @@ func (rh *RedisHandle) HGetAll(key string) map[string]string { func (rh *RedisHandle) HIncrBy(key string, field string, by int) int { c := rh.getPool().Get() - defer c.Close() + //defer c.Close() i, err := c.Do("HINCRBY", key, field, 1) if err != nil { @@ -116,3 +116,14 @@ func (rh *RedisHandle) HGetAsInt(key string, field string) int { } return j } + +func (rh *RedisHandle) Del(key string) int { + c := rh.getPool().Get() + //defer c.Close() + i, err := c.Do("DEL", key) + j := -1 + if err == nil { + j, _ = redis.Int(i, err) + } + return j +} diff --git a/rete/factory.go b/rete/factory.go index 5ff204e..8172a9b 100644 --- a/rete/factory.go +++ b/rete/factory.go @@ -100,7 +100,7 @@ func (f *TypeFactory) getHandleCollection() types.HandleService { func (f *TypeFactory) getIdGen() types.IdGen { var idg types.IdGen if f.parsedJson == nil { - idg = mem.NewIdImpl(f.parsedJson) + idg = mem.NewIdGenImpl(f.nw, f.parsedJson) return idg } else { rete := f.parsedJson["rete"].(map[string]interface{}) @@ -108,9 +108,9 @@ func (f *TypeFactory) getIdGen() types.IdGen { idgen := rete["idgen"].(string) if idgen == "" || idgen == "mem" { - idg = mem.NewIdImpl(f.parsedJson) + idg = mem.NewIdGenImpl(f.nw, f.parsedJson) } else if idgen == "redis" { - idg = redis.NewIdImpl(f.parsedJson) + idg = redis.NewIdGenImpl(f.parsedJson) } } } diff --git a/rete/internal/mem/mhandleservice.go b/rete/internal/mem/mhandleservice.go index 895cfb0..2886843 100644 --- a/rete/internal/mem/mhandleservice.go +++ b/rete/internal/mem/mhandleservice.go @@ -6,6 +6,7 @@ import ( ) type handleServiceImpl struct { + types.NwServiceImpl allHandles map[string]types.ReteHandle } @@ -19,10 +20,6 @@ func (hc *handleServiceImpl) Init() { } -func (hc *handleServiceImpl) AddHandle(hdl types.ReteHandle) { - hc.allHandles[hdl.GetTupleKey().String()] = hdl -} - func (hc *handleServiceImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle { rh, found := hc.allHandles[tuple.GetKey().String()] if found { diff --git a/rete/internal/mem/midgenservice.go b/rete/internal/mem/midgenservice.go index 148e158..9afa066 100644 --- a/rete/internal/mem/midgenservice.go +++ b/rete/internal/mem/midgenservice.go @@ -6,14 +6,17 @@ import ( ) type idGenServiceImpl struct { + types.NwServiceImpl config map[string]interface{} currentId int32 + } -func NewIdImpl(config map[string]interface{}) types.IdGen { +func NewIdGenImpl(nw types.Network, config map[string]interface{}) types.IdGen { idg := idGenServiceImpl{} idg.config = config idg.currentId = 0 + idg.Nw = nw return &idg } diff --git a/rete/internal/mem/mjtrefsservice.go b/rete/internal/mem/mjtrefsservice.go index 2611233..2bcbd05 100644 --- a/rete/internal/mem/mjtrefsservice.go +++ b/rete/internal/mem/mjtrefsservice.go @@ -7,6 +7,7 @@ import ( type jtRefsServiceImpl struct { //keys are jointable-ids and values are lists of row-ids in the corresponding join table + types.NwServiceImpl tablesAndRows map[string]map[string]map[int]int } diff --git a/rete/internal/mem/mjtservice.go b/rete/internal/mem/mjtservice.go index c02acec..cac7605 100644 --- a/rete/internal/mem/mjtservice.go +++ b/rete/internal/mem/mjtservice.go @@ -6,6 +6,7 @@ import ( ) type jtServiceImpl struct { + types.NwServiceImpl allJoinTables map[string]types.JoinTable } diff --git a/rete/internal/redis/rhandleservice.go b/rete/internal/redis/rhandleservice.go index e0ba495..1f8cfc0 100644 --- a/rete/internal/redis/rhandleservice.go +++ b/rete/internal/redis/rhandleservice.go @@ -3,15 +3,18 @@ package redis import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/rete/internal/types" + "github.com/project-flogo/rules/redisutils" + "fmt" ) type handleServiceImpl struct { - allHandles map[string]types.ReteHandle + //allHandles map[string]types.ReteHandle + types.NwServiceImpl } func NewHandleCollection(config map[string]interface{}) types.HandleService { hc := handleServiceImpl{} - hc.allHandles = make(map[string]types.ReteHandle) + //hc.allHandles = make(map[string]types.ReteHandle) return &hc } @@ -19,33 +22,57 @@ func (hc *handleServiceImpl) Init() { } -func (hc *handleServiceImpl) AddHandle(hdl types.ReteHandle) { - hc.allHandles[hdl.GetTupleKey().String()] = hdl -} +//func (hc *handleServiceImpl) AddHandle(hdl types.ReteHandle) { +// //hc.allHandles[hdl.GetTupleKey().String()] = hdl +// +// redisutils.GetRedisHdl().HSetAll(key, m) +//} func (hc *handleServiceImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle { - rh, found := hc.allHandles[tuple.GetKey().String()] - if found { - delete(hc.allHandles, tuple.GetKey().String()) - return rh - } - return nil + + numDeleted := redisutils.GetRedisHdl().Del("h-"+tuple.GetKey().String()) + fmt.Printf("Deleted: [%d] keys\n", numDeleted) + + //TODO: Dummy handle + h := newReteHandleImpl(hc.GetNw(), tuple) + return h + } func (hc *handleServiceImpl) GetHandle(tuple model.Tuple) types.ReteHandle { - return hc.allHandles[tuple.GetKey().String()] + return hc.GetHandleByKey(tuple.GetKey()) } func (hc *handleServiceImpl) GetHandleByKey(key model.TupleKey) types.ReteHandle { - return hc.allHandles[key.String()] + rkey := "h-" + key.String() + + m := redisutils.GetRedisHdl().HGetAll(rkey) + if len (m) == 0 { + return nil + } else { + tuple := hc.Nw.GetTupleStore().GetTupleByStringKey(key.String()) + if tuple == nil { + //TODO: error handling + return nil + } + h := newReteHandleImpl(hc.GetNw(), tuple) + return h + } } func (hc *handleServiceImpl) GetOrCreateHandle(nw types.Network, tuple model.Tuple) types.ReteHandle { - h, found := hc.allHandles[tuple.GetKey().String()] - if !found { - h = newReteHandleImpl(nw, tuple) - hc.allHandles[tuple.GetKey().String()] = h + + key := "h-" + tuple.GetKey().String() + + m := redisutils.GetRedisHdl().HGetAll(key) + if len (m) == 0 { + m := make(map[string]interface{}) + m["k"] = "v" + redisutils.GetRedisHdl().HSetAll(key, m) } + h := newReteHandleImpl(nw, tuple) + //hc.allHandles[tuple.GetKey().String()] = h + return h } diff --git a/rete/internal/redis/ridgenservice.go b/rete/internal/redis/ridgenservice.go index df9a145..3ef77b1 100644 --- a/rete/internal/redis/ridgenservice.go +++ b/rete/internal/redis/ridgenservice.go @@ -12,12 +12,14 @@ const ( ) type idGenServiceImpl struct { + types.NwServiceImpl + config map[string]interface{} //current int rh redisutils.RedisHdl } -func NewIdImpl(config map[string]interface{}) types.IdGen { +func NewIdGenImpl(config map[string]interface{}) types.IdGen { r := idGenServiceImpl{} r.config = config return &r diff --git a/rete/internal/redis/rjtrefsservice.go b/rete/internal/redis/rjtrefsservice.go index 1d2480b..b18eccd 100644 --- a/rete/internal/redis/rjtrefsservice.go +++ b/rete/internal/redis/rjtrefsservice.go @@ -7,6 +7,8 @@ import ( type jtRefsServiceImpl struct { //keys are jointable-ids and values are lists of row-ids in the corresponding join table + types.NwServiceImpl + tablesAndRows map[string]map[string]map[int]int } diff --git a/rete/internal/redis/rjtservice.go b/rete/internal/redis/rjtservice.go index 1c4b3c0..6064657 100644 --- a/rete/internal/redis/rjtservice.go +++ b/rete/internal/redis/rjtservice.go @@ -6,6 +6,8 @@ import ( ) type jtServiceImpl struct { + types.NwServiceImpl + allJoinTables map[string]types.JoinTable } diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index 8efb502..7132630 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -12,6 +12,7 @@ type Network interface { GetJtService() JtService GetHandleService() HandleService GetJtRefService() JtRefsService + GetTupleStore() services.TupleStore } type NwElemId interface { @@ -60,8 +61,13 @@ type RowIterator interface { Next() JoinTableRow } -type JtRefsService interface { +type NwService interface { services.Service + GetNw() Network +} + +type JtRefsService interface { + NwService AddEntry(handle ReteHandle, jtName string, rowID int) RemoveRowEntry(handle ReteHandle, jtName string, rowID int) RemoveEntry(handle ReteHandle, jtName string) @@ -74,7 +80,7 @@ type HdlTblIterator interface { } type JtService interface { - services.Service + NwService GetOrCreateJoinTable(nw Network, rule model.Rule, identifiers []model.TupleType, name string) JoinTable GetJoinTable(name string) JoinTable AddJoinTable(joinTable JoinTable) @@ -82,8 +88,7 @@ type JtService interface { } type HandleService interface { - services.Service - AddHandle(hdl ReteHandle) + NwService RemoveHandle(tuple model.Tuple) ReteHandle GetHandle(tuple model.Tuple) ReteHandle GetHandleByKey(key model.TupleKey) ReteHandle @@ -91,7 +96,16 @@ type HandleService interface { } type IdGen interface { - services.Service + NwService GetMaxID() int GetNextID() int } + + +type NwServiceImpl struct { + Nw Network +} + +func (nws *NwServiceImpl) GetNw() Network { + return nws.Nw +} \ No newline at end of file diff --git a/rete/network.go b/rete/network.go index 63f8439..6e5b2e5 100644 --- a/rete/network.go +++ b/rete/network.go @@ -662,6 +662,9 @@ func (nw *reteNetworkImpl) AddToAllJoinTables(joinTable types.JoinTable) { func (nw *reteNetworkImpl) SetTupleStore(tupleStore services.TupleStore) { nw.tupleStore = tupleStore } +func (nw *reteNetworkImpl) GetTupleStore() services.TupleStore { + return nw.tupleStore +} func getOrCreateHandle(ctx context.Context, tuple model.Tuple) types.ReteHandle { reteCtxVar := getReteCtx(ctx) From 61297b63e7dd906f9858b266dca8667fef687175 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Wed, 2 Jan 2019 11:41:55 +0530 Subject: [PATCH 029/125] misc bug fixes --- examples/rulesapp/main.go | 6 +++--- redisutils/redisutils.go | 14 +++++++------- rete/factory.go | 18 +++++++++--------- rete/internal/mem/mhandleservice.go | 3 ++- rete/internal/mem/midgenservice.go | 1 - rete/internal/mem/mjtrefsservice.go | 3 ++- rete/internal/mem/mjtservice.go | 3 ++- rete/internal/redis/rhandleservice.go | 13 +++++++------ rete/internal/redis/rjtrefsservice.go | 3 ++- rete/internal/redis/rjtservice.go | 3 ++- rete/internal/types/types.go | 3 +-- 11 files changed, 37 insertions(+), 33 deletions(-) diff --git a/examples/rulesapp/main.go b/examples/rulesapp/main.go index 1fc10b7..5beeb1b 100644 --- a/examples/rulesapp/main.go +++ b/examples/rulesapp/main.go @@ -68,9 +68,9 @@ func main() { rs.Assert(nil, t3) //Retract tuples - rs.Retract(nil, t1) - rs.Retract(nil, t2) - rs.Retract(nil, t3) + //rs.Retract(nil, t1) + //rs.Retract(nil, t2) + //rs.Retract(nil, t3) //delete the rule rs.DeleteRule(rule.GetName()) diff --git a/redisutils/redisutils.go b/redisutils/redisutils.go index d2ef6a0..624ea57 100644 --- a/redisutils/redisutils.go +++ b/redisutils/redisutils.go @@ -54,8 +54,8 @@ func (rh *RedisHandle) HSetAll(key string, kvs map[string]interface{}) error { for f, v := range kvs { args = append(args, f, v) } - c := rd.getPool().Get() - //defer c.Close() + c := rh.getPool().Get() + defer c.Close() _, error := c.Do("HMSET", args...) return error @@ -63,9 +63,9 @@ func (rh *RedisHandle) HSetAll(key string, kvs map[string]interface{}) error { func (rh *RedisHandle) HGetAll(key string) map[string]string { hgetall := make(map[string]string) - c := rh.getPool() - //defer c.Close() - vals, error := c.Get().Do("HGETALL", key) + c := rh.getPool().Get() + defer c.Close() + vals, error := c.Do("HGETALL", key) if error != nil { fmt.Printf("error [%v]\n", error) } else { @@ -95,7 +95,7 @@ func (rh *RedisHandle) HGetAll(key string) map[string]string { func (rh *RedisHandle) HIncrBy(key string, field string, by int) int { c := rh.getPool().Get() - //defer c.Close() + defer c.Close() i, err := c.Do("HINCRBY", key, field, 1) if err != nil { @@ -119,7 +119,7 @@ func (rh *RedisHandle) HGetAsInt(key string, field string) int { func (rh *RedisHandle) Del(key string) int { c := rh.getPool().Get() - //defer c.Close() + defer c.Close() i, err := c.Do("DEL", key) j := -1 if err == nil { diff --git a/rete/factory.go b/rete/factory.go index 8172a9b..53abe37 100644 --- a/rete/factory.go +++ b/rete/factory.go @@ -44,16 +44,16 @@ func NewFactory(nw *reteNetworkImpl, config string) *TypeFactory { func (f *TypeFactory) getJoinTableRefs() types.JtRefsService { var jtRefs types.JtRefsService if f.parsedJson == nil { - jtRefs = mem.NewJoinTableRefsInHdlImpl(f.parsedJson) + jtRefs = mem.NewJoinTableRefsInHdlImpl(f.nw, f.parsedJson) } else { rete := f.parsedJson["rete"].(map[string]interface{}) if rete != nil { idgen := rete["jt"].(string) if idgen == "" || idgen == "mem" { - jtRefs = mem.NewJoinTableRefsInHdlImpl(f.parsedJson) + jtRefs = mem.NewJoinTableRefsInHdlImpl(f.nw, f.parsedJson) } else if idgen == "redis" { - jtRefs = redis.NewJoinTableRefsInHdlImpl(f.parsedJson) + jtRefs = redis.NewJoinTableRefsInHdlImpl(f.nw, f.parsedJson) } } } @@ -63,16 +63,16 @@ func (f *TypeFactory) getJoinTableRefs() types.JtRefsService { func (f *TypeFactory) getJoinTableCollection() types.JtService { var allJt types.JtService if f.parsedJson == nil { - allJt = mem.NewJoinTableCollection(f.parsedJson) + allJt = mem.NewJoinTableCollection(f.nw, f.parsedJson) } else { rete := f.parsedJson["rete"].(map[string]interface{}) if rete != nil { idgen := rete["jt"].(string) if idgen == "" || idgen == "mem" { - allJt = mem.NewJoinTableCollection(f.parsedJson) + allJt = mem.NewJoinTableCollection(f.nw, f.parsedJson) } else if idgen == "redis" { - allJt = redis.NewJoinTableCollection(f.parsedJson) + allJt = redis.NewJoinTableCollection(f.nw, f.parsedJson) } } } @@ -82,15 +82,15 @@ func (f *TypeFactory) getJoinTableCollection() types.JtService { func (f *TypeFactory) getHandleCollection() types.HandleService { var hc types.HandleService if f.parsedJson == nil { - hc = mem.NewHandleCollection(f.parsedJson) + hc = mem.NewHandleCollection(f.nw, f.parsedJson) } else { rete := f.parsedJson["rete"].(map[string]interface{}) if rete != nil { idgen := rete["jt"].(string) if idgen == "" || idgen == "mem" { - hc = mem.NewHandleCollection(f.parsedJson) + hc = mem.NewHandleCollection(f.nw, f.parsedJson) } else if idgen == "redis" { - hc = redis.NewHandleCollection(f.parsedJson) + hc = redis.NewHandleCollection(f.nw, f.parsedJson) } } } diff --git a/rete/internal/mem/mhandleservice.go b/rete/internal/mem/mhandleservice.go index 2886843..5dfa4ac 100644 --- a/rete/internal/mem/mhandleservice.go +++ b/rete/internal/mem/mhandleservice.go @@ -10,8 +10,9 @@ type handleServiceImpl struct { allHandles map[string]types.ReteHandle } -func NewHandleCollection(config map[string]interface{}) types.HandleService { +func NewHandleCollection(nw types.Network, config map[string]interface{}) types.HandleService { hc := handleServiceImpl{} + hc.Nw = nw hc.allHandles = make(map[string]types.ReteHandle) return &hc } diff --git a/rete/internal/mem/midgenservice.go b/rete/internal/mem/midgenservice.go index 9afa066..1aca2a0 100644 --- a/rete/internal/mem/midgenservice.go +++ b/rete/internal/mem/midgenservice.go @@ -9,7 +9,6 @@ type idGenServiceImpl struct { types.NwServiceImpl config map[string]interface{} currentId int32 - } func NewIdGenImpl(nw types.Network, config map[string]interface{}) types.IdGen { diff --git a/rete/internal/mem/mjtrefsservice.go b/rete/internal/mem/mjtrefsservice.go index 2bcbd05..ed0f2f9 100644 --- a/rete/internal/mem/mjtrefsservice.go +++ b/rete/internal/mem/mjtrefsservice.go @@ -11,8 +11,9 @@ type jtRefsServiceImpl struct { tablesAndRows map[string]map[string]map[int]int } -func NewJoinTableRefsInHdlImpl(config map[string]interface{}) types.JtRefsService { +func NewJoinTableRefsInHdlImpl(nw types.Network, config map[string]interface{}) types.JtRefsService { hdlJt := jtRefsServiceImpl{} + hdlJt.Nw = nw hdlJt.tablesAndRows = make(map[string]map[string]map[int]int) return &hdlJt } diff --git a/rete/internal/mem/mjtservice.go b/rete/internal/mem/mjtservice.go index cac7605..308fa11 100644 --- a/rete/internal/mem/mjtservice.go +++ b/rete/internal/mem/mjtservice.go @@ -10,8 +10,9 @@ type jtServiceImpl struct { allJoinTables map[string]types.JoinTable } -func NewJoinTableCollection(config map[string]interface{}) types.JtService { +func NewJoinTableCollection(nw types.Network, config map[string]interface{}) types.JtService { jtc := jtServiceImpl{} + jtc.Nw = nw jtc.allJoinTables = make(map[string]types.JoinTable) return &jtc } diff --git a/rete/internal/redis/rhandleservice.go b/rete/internal/redis/rhandleservice.go index 1f8cfc0..3d44b4c 100644 --- a/rete/internal/redis/rhandleservice.go +++ b/rete/internal/redis/rhandleservice.go @@ -1,10 +1,10 @@ package redis import ( + "fmt" "github.com/project-flogo/rules/common/model" - "github.com/project-flogo/rules/rete/internal/types" "github.com/project-flogo/rules/redisutils" - "fmt" + "github.com/project-flogo/rules/rete/internal/types" ) type handleServiceImpl struct { @@ -12,8 +12,9 @@ type handleServiceImpl struct { types.NwServiceImpl } -func NewHandleCollection(config map[string]interface{}) types.HandleService { +func NewHandleCollection(nw types.Network, config map[string]interface{}) types.HandleService { hc := handleServiceImpl{} + hc.Nw = nw //hc.allHandles = make(map[string]types.ReteHandle) return &hc } @@ -30,7 +31,7 @@ func (hc *handleServiceImpl) Init() { func (hc *handleServiceImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle { - numDeleted := redisutils.GetRedisHdl().Del("h-"+tuple.GetKey().String()) + numDeleted := redisutils.GetRedisHdl().Del("h-" + tuple.GetKey().String()) fmt.Printf("Deleted: [%d] keys\n", numDeleted) //TODO: Dummy handle @@ -47,7 +48,7 @@ func (hc *handleServiceImpl) GetHandleByKey(key model.TupleKey) types.ReteHandle rkey := "h-" + key.String() m := redisutils.GetRedisHdl().HGetAll(rkey) - if len (m) == 0 { + if len(m) == 0 { return nil } else { tuple := hc.Nw.GetTupleStore().GetTupleByStringKey(key.String()) @@ -65,7 +66,7 @@ func (hc *handleServiceImpl) GetOrCreateHandle(nw types.Network, tuple model.Tup key := "h-" + tuple.GetKey().String() m := redisutils.GetRedisHdl().HGetAll(key) - if len (m) == 0 { + if len(m) == 0 { m := make(map[string]interface{}) m["k"] = "v" redisutils.GetRedisHdl().HSetAll(key, m) diff --git a/rete/internal/redis/rjtrefsservice.go b/rete/internal/redis/rjtrefsservice.go index b18eccd..fac5232 100644 --- a/rete/internal/redis/rjtrefsservice.go +++ b/rete/internal/redis/rjtrefsservice.go @@ -12,8 +12,9 @@ type jtRefsServiceImpl struct { tablesAndRows map[string]map[string]map[int]int } -func NewJoinTableRefsInHdlImpl(config map[string]interface{}) types.JtRefsService { +func NewJoinTableRefsInHdlImpl(nw types.Network, config map[string]interface{}) types.JtRefsService { hdlJt := jtRefsServiceImpl{} + hdlJt.Nw = nw hdlJt.tablesAndRows = make(map[string]map[string]map[int]int) return &hdlJt } diff --git a/rete/internal/redis/rjtservice.go b/rete/internal/redis/rjtservice.go index 6064657..336efa2 100644 --- a/rete/internal/redis/rjtservice.go +++ b/rete/internal/redis/rjtservice.go @@ -11,8 +11,9 @@ type jtServiceImpl struct { allJoinTables map[string]types.JoinTable } -func NewJoinTableCollection(config map[string]interface{}) types.JtService { +func NewJoinTableCollection(nw types.Network, config map[string]interface{}) types.JtService { jtc := jtServiceImpl{} + jtc.Nw = nw jtc.allJoinTables = make(map[string]types.JoinTable) return &jtc } diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index 7132630..48af597 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -101,11 +101,10 @@ type IdGen interface { GetNextID() int } - type NwServiceImpl struct { Nw Network } func (nws *NwServiceImpl) GetNw() Network { return nws.Nw -} \ No newline at end of file +} From 88f334f56cee00cfa5fa5f86c9a7c24d0f0f8be0 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Wed, 2 Jan 2019 12:25:49 +0530 Subject: [PATCH 030/125] idgen fixes --- examples/rulesapp/main.go | 6 +++--- examples/rulesapp/rsconfig.json | 1 + redisutils/redisutils.go | 24 ++++++++++++++++++++++++ rete/factory.go | 2 +- rete/internal/mem/mhandleservice.go | 1 - rete/internal/redis/rhandleservice.go | 3 ++- rete/internal/redis/ridgenservice.go | 19 +++++++++++-------- rete/internal/types/types.go | 1 + rete/network.go | 15 ++++++++++++++- 9 files changed, 57 insertions(+), 15 deletions(-) diff --git a/examples/rulesapp/main.go b/examples/rulesapp/main.go index 5beeb1b..1fc10b7 100644 --- a/examples/rulesapp/main.go +++ b/examples/rulesapp/main.go @@ -68,9 +68,9 @@ func main() { rs.Assert(nil, t3) //Retract tuples - //rs.Retract(nil, t1) - //rs.Retract(nil, t2) - //rs.Retract(nil, t3) + rs.Retract(nil, t1) + rs.Retract(nil, t2) + rs.Retract(nil, t3) //delete the rule rs.DeleteRule(rule.GetName()) diff --git a/examples/rulesapp/rsconfig.json b/examples/rulesapp/rsconfig.json index 5785c2e..6cd068f 100644 --- a/examples/rulesapp/rsconfig.json +++ b/examples/rulesapp/rsconfig.json @@ -3,6 +3,7 @@ "store-ref" : "mem" }, "rete": { + "prefix" : "x", "jt": "redis", "idgen" : "redis" }, diff --git a/redisutils/redisutils.go b/redisutils/redisutils.go index 624ea57..de3dbe8 100644 --- a/redisutils/redisutils.go +++ b/redisutils/redisutils.go @@ -106,6 +106,19 @@ func (rh *RedisHandle) HIncrBy(key string, field string, by int) int { return current } +func (rh *RedisHandle) IncrBy(key string, by int) int { + c := rh.getPool().Get() + defer c.Close() + i, err := c.Do("INCRBY", key, 1) + + if err != nil { + fmt.Printf("error: [%s]", err) + return -1 + } + current := int(i.(int64)) + return current +} + func (rh *RedisHandle) HGetAsInt(key string, field string) int { c := rh.getPool().Get() defer c.Close() @@ -117,6 +130,17 @@ func (rh *RedisHandle) HGetAsInt(key string, field string) int { return j } +func (rh *RedisHandle) GetAsInt(key string) int { + c := rh.getPool().Get() + defer c.Close() + i, err := c.Do("GET", key) + j := -1 + if err == nil { + j, _ = redis.Int(i, err) + } + return j +} + func (rh *RedisHandle) Del(key string) int { c := rh.getPool().Get() defer c.Close() diff --git a/rete/factory.go b/rete/factory.go index 53abe37..e54c526 100644 --- a/rete/factory.go +++ b/rete/factory.go @@ -110,7 +110,7 @@ func (f *TypeFactory) getIdGen() types.IdGen { if idgen == "" || idgen == "mem" { idg = mem.NewIdGenImpl(f.nw, f.parsedJson) } else if idgen == "redis" { - idg = redis.NewIdGenImpl(f.parsedJson) + idg = redis.NewIdGenImpl(f.nw, f.parsedJson) } } } diff --git a/rete/internal/mem/mhandleservice.go b/rete/internal/mem/mhandleservice.go index 5dfa4ac..38e611b 100644 --- a/rete/internal/mem/mhandleservice.go +++ b/rete/internal/mem/mhandleservice.go @@ -18,7 +18,6 @@ func NewHandleCollection(nw types.Network, config map[string]interface{}) types. } func (hc *handleServiceImpl) Init() { - } func (hc *handleServiceImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle { diff --git a/rete/internal/redis/rhandleservice.go b/rete/internal/redis/rhandleservice.go index 3d44b4c..1ddcf5e 100644 --- a/rete/internal/redis/rhandleservice.go +++ b/rete/internal/redis/rhandleservice.go @@ -10,6 +10,7 @@ import ( type handleServiceImpl struct { //allHandles map[string]types.ReteHandle types.NwServiceImpl + prefix string } func NewHandleCollection(nw types.Network, config map[string]interface{}) types.HandleService { @@ -20,7 +21,7 @@ func NewHandleCollection(nw types.Network, config map[string]interface{}) types. } func (hc *handleServiceImpl) Init() { - + hc.prefix = hc.Nw.GetPrefix() + ":h:" } //func (hc *handleServiceImpl) AddHandle(hdl types.ReteHandle) { diff --git a/rete/internal/redis/ridgenservice.go b/rete/internal/redis/ridgenservice.go index 3ef77b1..1153a3d 100644 --- a/rete/internal/redis/ridgenservice.go +++ b/rete/internal/redis/ridgenservice.go @@ -6,26 +6,29 @@ import ( "github.com/project-flogo/rules/rete/internal/types" ) -const ( - hget = "HGET" - hincrby = "HINCRBY" -) - type idGenServiceImpl struct { types.NwServiceImpl config map[string]interface{} //current int rh redisutils.RedisHdl + + //key used to access idgen + key string + + //redis field in key + fld string } -func NewIdGenImpl(config map[string]interface{}) types.IdGen { +func NewIdGenImpl(nw types.Network, config map[string]interface{}) types.IdGen { r := idGenServiceImpl{} + r.Nw = nw r.config = config return &r } func (ri *idGenServiceImpl) Init() { + ri.key = ri.Nw.GetPrefix() + ":idgen" redisutils.InitService(ri.config) ri.rh = redisutils.GetRedisHdl() j := ri.GetMaxID() @@ -33,9 +36,9 @@ func (ri *idGenServiceImpl) Init() { } func (ri *idGenServiceImpl) GetMaxID() int { - return ri.rh.HGetAsInt("IDGEN", "ID") + return ri.rh.GetAsInt(ri.key) } func (ri *idGenServiceImpl) GetNextID() int { - return ri.rh.HIncrBy("IDGEN", "ID", 1) + return ri.rh.IncrBy(ri.key, 1) } diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index 48af597..0091eb0 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -8,6 +8,7 @@ import ( type Network interface { common.Network + GetPrefix() string GetIdGenService() IdGen GetJtService() JtService GetHandleService() HandleService diff --git a/rete/network.go b/rete/network.go index 6e5b2e5..dfc4c0d 100644 --- a/rete/network.go +++ b/rete/network.go @@ -17,6 +17,10 @@ import ( ) type reteNetworkImpl struct { + + //unique name of the network. used for namespacing in storage, etc + prefix string + //All rules in the network allRules map[string]model.Rule //(Rule) @@ -63,8 +67,13 @@ func (nw *reteNetworkImpl) initReteNetwork(config string) { nw.ruleNameNodesOfRule = make(map[string]*list.List) nw.ruleNameClassNodeLinksOfRule = make(map[string]*list.List) - nw.factory = NewFactory(nw, config) + factory := NewFactory(nw, config) + nw.factory = factory + + reteCfg := factory.parsedJson["rete"].(map[string]interface{}) + prefix := reteCfg["prefix"].(string) + nw.prefix = prefix nw.idGen = nw.factory.getIdGen() nw.jtService = nw.factory.getJoinTableCollection() nw.handleService = nw.factory.getHandleCollection() @@ -737,3 +746,7 @@ func (nw *reteNetworkImpl) GetJtRefService() types.JtRefsService { func (nw *reteNetworkImpl) GetHandleService() types.HandleService { return nw.handleService } + +func (nw *reteNetworkImpl) GetPrefix() string { + return nw.prefix +} From 607fa85a6e4b30acbb78c4c9c21c5cfd230545fa Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Wed, 2 Jan 2019 14:27:48 +0530 Subject: [PATCH 031/125] refactored interfaces and register a post-rtc in main --- common/model/services.go | 16 ++++++ common/model/tuple.go | 10 ++++ common/model/types.go | 4 +- common/services/services.go | 16 ------ examples/rulesapp/main.go | 13 +++++ examples/rulesapp/rsconfig.json | 4 +- redisutils/redisutils.go | 4 +- rete/common/types.go | 3 +- rete/internal/redis/rhandleservice.go | 6 +-- rete/internal/types/types.go | 5 +- rete/network.go | 9 ++-- ruleapi/internal/store/mem/mstore.go | 34 +++++++++---- ruleapi/internal/store/redis/rstore.go | 69 ++++++++++++++++++++++++++ ruleapi/rulesession.go | 17 ++++--- 14 files changed, 160 insertions(+), 50 deletions(-) create mode 100644 common/model/services.go delete mode 100644 common/services/services.go create mode 100644 ruleapi/internal/store/redis/rstore.go diff --git a/common/model/services.go b/common/model/services.go new file mode 100644 index 0000000..a1a2044 --- /dev/null +++ b/common/model/services.go @@ -0,0 +1,16 @@ +package model + + +type Service interface { + Init() +} + +type TupleStore interface { + Service + GetTupleByKey(key TupleKey) Tuple + SaveTuple(tuple Tuple) + SaveTuples(added map[string]map[string]Tuple) + SaveModifiedTuples(modified map[string]map[string]RtcModified) + DeleteTupleByStringKey(key TupleKey) + DeleteTuples(deleted map[string]map[string]Tuple) +} diff --git a/common/model/tuple.go b/common/model/tuple.go index bf0c421..0a64d7f 100644 --- a/common/model/tuple.go +++ b/common/model/tuple.go @@ -23,6 +23,7 @@ type Tuple interface { GetBool(name string) (val bool, err error) //GetDateTime(name string) time.Time GetKey() TupleKey + ToMap() map[string]interface{} } //MutableTuple mutable part of the tuple @@ -273,3 +274,12 @@ func (t *tupleImpl) isKeyProp(propName string) bool { } return found } + +func (t *tupleImpl) ToMap () map[string]interface{} { + m := make (map[string]interface{}) + // Copy from the original map to the target map + for key, value := range t.tuples { + m[key] = value + } + return m +} diff --git a/common/model/types.go b/common/model/types.go index 3438291..6a71635 100644 --- a/common/model/types.go +++ b/common/model/types.go @@ -72,7 +72,9 @@ type RuleSession interface { //RtcTransactionHandler RegisterRtcTransactionHandler(txnHandler RtcTransactionHandler, handlerCtx interface{}) - //SetConfig(config map[string]string) + //SetStore + GetStore() TupleStore + } //ConditionEvaluator is a function pointer for handling condition evaluations on the server side diff --git a/common/services/services.go b/common/services/services.go deleted file mode 100644 index a703cc9..0000000 --- a/common/services/services.go +++ /dev/null @@ -1,16 +0,0 @@ -package services - -import ( - "github.com/project-flogo/rules/common/model" -) - -type Service interface { - Init() -} - -type TupleStore interface { - Service - GetTupleByStringKey(key string) model.Tuple - SaveTuple(tuple model.Tuple) - DeleteTupleByStringKey(key string) -} diff --git a/examples/rulesapp/main.go b/examples/rulesapp/main.go index 1fc10b7..5463e92 100644 --- a/examples/rulesapp/main.go +++ b/examples/rulesapp/main.go @@ -46,6 +46,8 @@ func main() { rs.AddRule(rule2) fmt.Printf("Rule added: [%s]\n", rule2.GetName()) + //set a transaction handler + rs.RegisterRtcTransactionHandler(txHandler, nil) //Start the rule session rs.Start(nil) @@ -129,3 +131,14 @@ func getFileContent(filePath string) string { absPath := common.GetAbsPathForResource(filePath) return common.FileToString(absPath) } + +func txHandler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { + + store := rs.GetStore() + store.SaveTuples(rtxn.GetRtcAdded()) + + store.SaveModifiedTuples(rtxn.GetRtcModified()) + + store.DeleteTuples(rtxn.GetRtcDeleted()) + +} diff --git a/examples/rulesapp/rsconfig.json b/examples/rulesapp/rsconfig.json index 6cd068f..0251bdf 100644 --- a/examples/rulesapp/rsconfig.json +++ b/examples/rulesapp/rsconfig.json @@ -1,9 +1,9 @@ { "rs" : { - "store-ref" : "mem" + "prefix" : "x", + "store-ref" : "redis" }, "rete": { - "prefix" : "x", "jt": "redis", "idgen" : "redis" }, diff --git a/redisutils/redisutils.go b/redisutils/redisutils.go index de3dbe8..7214ee0 100644 --- a/redisutils/redisutils.go +++ b/redisutils/redisutils.go @@ -61,8 +61,8 @@ func (rh *RedisHandle) HSetAll(key string, kvs map[string]interface{}) error { return error } -func (rh *RedisHandle) HGetAll(key string) map[string]string { - hgetall := make(map[string]string) +func (rh *RedisHandle) HGetAll(key string) map[string]interface{} { + hgetall := make(map[string]interface{}) c := rh.getPool().Get() defer c.Close() vals, error := c.Do("HGETALL", key) diff --git a/rete/common/types.go b/rete/common/types.go index 3904048..5b73904 100644 --- a/rete/common/types.go +++ b/rete/common/types.go @@ -3,7 +3,6 @@ package common import ( "context" "github.com/project-flogo/rules/common/model" - "github.com/project-flogo/rules/common/services" ) type RtcOprn int @@ -29,5 +28,5 @@ type Network interface { //GetConfigValue(key string) string //GetConfig() map[string]string - SetTupleStore(tupleStore services.TupleStore) + SetTupleStore(tupleStore model.TupleStore) } diff --git a/rete/internal/redis/rhandleservice.go b/rete/internal/redis/rhandleservice.go index 1ddcf5e..6643b58 100644 --- a/rete/internal/redis/rhandleservice.go +++ b/rete/internal/redis/rhandleservice.go @@ -46,13 +46,13 @@ func (hc *handleServiceImpl) GetHandle(tuple model.Tuple) types.ReteHandle { } func (hc *handleServiceImpl) GetHandleByKey(key model.TupleKey) types.ReteHandle { - rkey := "h-" + key.String() + rkey := hc.prefix + key.String() m := redisutils.GetRedisHdl().HGetAll(rkey) if len(m) == 0 { return nil } else { - tuple := hc.Nw.GetTupleStore().GetTupleByStringKey(key.String()) + tuple := hc.Nw.GetTupleStore().GetTupleByKey(key) if tuple == nil { //TODO: error handling return nil @@ -64,7 +64,7 @@ func (hc *handleServiceImpl) GetHandleByKey(key model.TupleKey) types.ReteHandle func (hc *handleServiceImpl) GetOrCreateHandle(nw types.Network, tuple model.Tuple) types.ReteHandle { - key := "h-" + tuple.GetKey().String() + key := hc.prefix + tuple.GetKey().String() m := redisutils.GetRedisHdl().HGetAll(key) if len(m) == 0 { diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index 0091eb0..cb6aede 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -2,7 +2,6 @@ package types import ( "github.com/project-flogo/rules/common/model" - "github.com/project-flogo/rules/common/services" "github.com/project-flogo/rules/rete/common" ) @@ -13,7 +12,7 @@ type Network interface { GetJtService() JtService GetHandleService() HandleService GetJtRefService() JtRefsService - GetTupleStore() services.TupleStore + GetTupleStore() model.TupleStore } type NwElemId interface { @@ -63,7 +62,7 @@ type RowIterator interface { } type NwService interface { - services.Service + model.Service GetNw() Network } diff --git a/rete/network.go b/rete/network.go index dfc4c0d..6ec3af5 100644 --- a/rete/network.go +++ b/rete/network.go @@ -12,7 +12,6 @@ import ( "sync" "time" //"github.com/project-flogo/rules/rete/common" - "github.com/project-flogo/rules/common/services" "github.com/project-flogo/rules/rete/common" ) @@ -50,7 +49,7 @@ type reteNetworkImpl struct { factory *TypeFactory idGen types.IdGen - tupleStore services.TupleStore + tupleStore model.TupleStore } //NewReteNetwork ... creates a new rete network @@ -70,7 +69,7 @@ func (nw *reteNetworkImpl) initReteNetwork(config string) { factory := NewFactory(nw, config) nw.factory = factory - reteCfg := factory.parsedJson["rete"].(map[string]interface{}) + reteCfg := factory.parsedJson["rs"].(map[string]interface{}) prefix := reteCfg["prefix"].(string) nw.prefix = prefix @@ -668,10 +667,10 @@ func (nw *reteNetworkImpl) AddToAllJoinTables(joinTable types.JoinTable) { nw.jtService.AddJoinTable(joinTable) } -func (nw *reteNetworkImpl) SetTupleStore(tupleStore services.TupleStore) { +func (nw *reteNetworkImpl) SetTupleStore(tupleStore model.TupleStore) { nw.tupleStore = tupleStore } -func (nw *reteNetworkImpl) GetTupleStore() services.TupleStore { +func (nw *reteNetworkImpl) GetTupleStore() model.TupleStore { return nw.tupleStore } diff --git a/ruleapi/internal/store/mem/mstore.go b/ruleapi/internal/store/mem/mstore.go index f87c2bc..1294f04 100644 --- a/ruleapi/internal/store/mem/mstore.go +++ b/ruleapi/internal/store/mem/mstore.go @@ -2,29 +2,43 @@ package mem import ( "github.com/project-flogo/rules/common/model" - "github.com/project-flogo/rules/common/services" ) -type store struct { +type storeImpl struct { allTuples map[string]model.Tuple } -func NewStore() services.TupleStore { - ms := store{} +func NewStore(jsonConfig map[string]interface{}) model.TupleStore { + ms := storeImpl{} + ms.allTuples = make(map[string]model.Tuple) return &ms } -func (ms *store) Init() { +func (ms *storeImpl) Init() { } -func (ms *store) GetTupleByStringKey(key string) model.Tuple { - return ms.allTuples[key] +func (ms *storeImpl) GetTupleByKey(key model.TupleKey) model.Tuple { + return ms.allTuples[key.String()] } -func (ms *store) SaveTuple(tuple model.Tuple) { + +func (ms *storeImpl) SaveTuple(tuple model.Tuple) { ms.allTuples[tuple.GetKey().String()] = tuple } -func (ms *store) DeleteTupleByStringKey(key string) { - delete(ms.allTuples, key) + +func (ms *storeImpl) DeleteTupleByStringKey(key model.TupleKey) { + delete(ms.allTuples, key.String()) +} + +func (ms *storeImpl) SaveTuples(added map[string]map[string]model.Tuple) { + +} + +func (ms *storeImpl) SaveModifiedTuples(modified map[string]map[string]model.RtcModified) { + +} + +func (ms *storeImpl) DeleteTuples(deleted map[string]map[string]model.Tuple) { + } diff --git a/ruleapi/internal/store/redis/rstore.go b/ruleapi/internal/store/redis/rstore.go new file mode 100644 index 0000000..ec89aca --- /dev/null +++ b/ruleapi/internal/store/redis/rstore.go @@ -0,0 +1,69 @@ +package redis + +import ( + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/redisutils" +) + +type storeImpl struct { + //allTuples map[string]model.Tuple + prefix string + jsonConfig map[string]interface{} +} + +func NewStore(jsonConfig map[string]interface{}) model.TupleStore { + ms := storeImpl{} + ms.jsonConfig = jsonConfig + return &ms +} + +func (ms *storeImpl) Init() { + //ms.allTuples = make(map[string]model.Tuple) + reteCfg := ms.jsonConfig["rs"].(map[string]interface{}) + ms.prefix = reteCfg["prefix"].(string) + ms.prefix = ms.prefix + ":" + "s:" + +} + +func (ms *storeImpl) GetTupleByKey(key model.TupleKey) model.Tuple { + hdl := redisutils.GetRedisHdl() + + strKey := ms.prefix + key.GetTupleDescriptor().Name + ":" + key.String() + vals := hdl.HGetAll(strKey) + + + tuple, err := model.NewTuple(model.TupleType(key.GetTupleDescriptor().Name), vals) + + if err != nil { + return tuple + } + return nil +} + +func (ms *storeImpl) SaveTuple(tuple model.Tuple) { + m := tuple.ToMap() + + strKey := ms.prefix + tuple.GetTupleDescriptor().Name + ":" + tuple.GetKey().String() + + hdl := redisutils.GetRedisHdl() + hdl.HSetAll(strKey, m) +} + +func (ms *storeImpl) DeleteTupleByStringKey(key model.TupleKey) { + strKey := ms.prefix + key.GetTupleDescriptor().Name + ":" + key.String() + hdl := redisutils.GetRedisHdl() + hdl.Del(strKey) +} + +func (ms *storeImpl) SaveTuples(added map[string]map[string]model.Tuple) { + +} + +func (ms *storeImpl) SaveModifiedTuples(modified map[string]map[string]model.RtcModified) { + +} + +func (ms *storeImpl) DeleteTuples(deleted map[string]map[string]model.Tuple) { + +} + diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index 729920e..7da2042 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -9,11 +9,11 @@ import ( "time" "github.com/project-flogo/rules/common/model" - "github.com/project-flogo/rules/common/services" "github.com/project-flogo/rules/config" "github.com/project-flogo/rules/rete" "github.com/project-flogo/rules/rete/common" "github.com/project-flogo/rules/ruleapi/internal/store/mem" + "github.com/project-flogo/rules/ruleapi/internal/store/redis" ) var ( @@ -28,7 +28,7 @@ type rulesessionImpl struct { startupFn model.StartupRSFunction started bool config map[string]string - tupleStore services.TupleStore + tupleStore model.TupleStore jsonConfig map[string]interface{} } @@ -95,6 +95,7 @@ func (rs *rulesessionImpl) initRuleSessionWithConfig(name string, jsonConfig str //TODO: Configure it from jconsonfig rs.tupleStore = getTupleStore(rs.jsonConfig) + rs.tupleStore.Init() rs.reteNetwork = rete.NewReteNetwork(jsonConfig) rs.reteNetwork.SetTupleStore(rs.tupleStore) @@ -103,15 +104,15 @@ func (rs *rulesessionImpl) initRuleSessionWithConfig(name string, jsonConfig str return nil } -func getTupleStore(jsonConfig map[string]interface{}) services.TupleStore { +func getTupleStore(jsonConfig map[string]interface{}) model.TupleStore { rsCfg := jsonConfig["rs"].(map[string]interface{}) storeRef := rsCfg["store-ref"].(string) if storeRef == "" || storeRef == "mem" { - return mem.NewStore() - } else if storeRef == "redisutils" { - return mem.NewStore() + return mem.NewStore(jsonConfig) + } else if storeRef == "redis" { + return redis.NewStore(jsonConfig) } return nil } @@ -217,3 +218,7 @@ func (rs *rulesessionImpl) GetAssertedTuple(key model.TupleKey) model.Tuple { func (rs *rulesessionImpl) RegisterRtcTransactionHandler(txnHandler model.RtcTransactionHandler, txnContext interface{}) { rs.reteNetwork.RegisterRtcTransactionHandler(txnHandler, txnContext) } + +func (rs *rulesessionImpl) GetStore() model.TupleStore { + return rs.tupleStore +} From 1c8d3f02b7aef911d24fcf446ff80dc2de20ca7e Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Wed, 2 Jan 2019 15:43:23 +0530 Subject: [PATCH 032/125] working redis store impl and some bug fixes related to context --- common/model/services.go | 3 +- common/model/tuple.go | 4 +-- common/model/types.go | 1 - examples/rulesapp/main.go | 21 +++++++++---- redisutils/redisutils.go | 11 +++++++ rete/common/types.go | 2 +- rete/internal/redis/rhandleservice.go | 4 +-- rete/network.go | 4 +-- rete/opsList.go | 2 +- ruleapi/internal/store/mem/mstore.go | 24 ++++++++++++--- ruleapi/internal/store/redis/rstore.go | 42 +++++++++++++++++++------- ruleapi/rulesession.go | 4 +-- 12 files changed, 88 insertions(+), 34 deletions(-) diff --git a/common/model/services.go b/common/model/services.go index a1a2044..793edda 100644 --- a/common/model/services.go +++ b/common/model/services.go @@ -1,6 +1,5 @@ package model - type Service interface { Init() } @@ -11,6 +10,6 @@ type TupleStore interface { SaveTuple(tuple Tuple) SaveTuples(added map[string]map[string]Tuple) SaveModifiedTuples(modified map[string]map[string]RtcModified) - DeleteTupleByStringKey(key TupleKey) + DeleteTuple(key TupleKey) DeleteTuples(deleted map[string]map[string]Tuple) } diff --git a/common/model/tuple.go b/common/model/tuple.go index 0a64d7f..9f0ae1b 100644 --- a/common/model/tuple.go +++ b/common/model/tuple.go @@ -275,8 +275,8 @@ func (t *tupleImpl) isKeyProp(propName string) bool { return found } -func (t *tupleImpl) ToMap () map[string]interface{} { - m := make (map[string]interface{}) +func (t *tupleImpl) ToMap() map[string]interface{} { + m := make(map[string]interface{}) // Copy from the original map to the target map for key, value := range t.tuples { m[key] = value diff --git a/common/model/types.go b/common/model/types.go index 6a71635..bddcf13 100644 --- a/common/model/types.go +++ b/common/model/types.go @@ -74,7 +74,6 @@ type RuleSession interface { //SetStore GetStore() TupleStore - } //ConditionEvaluator is a function pointer for handling condition evaluations on the server side diff --git a/examples/rulesapp/main.go b/examples/rulesapp/main.go index 5463e92..0cdbad9 100644 --- a/examples/rulesapp/main.go +++ b/examples/rulesapp/main.go @@ -55,24 +55,33 @@ func main() { fmt.Println("Asserting n1 tuple with name=Tom") t1, _ := model.NewTupleWithKeyValues("n1", "Tom") t1.SetString(nil, "name", "Tom") - rs.Assert(nil, t1) + err = rs.Assert(nil, t1) + if err != nil { + fmt.Printf("Warn: [%s]\n", err) + } //Now assert a "n1" tuple fmt.Println("Asserting n1 tuple with name=Bob") t2, _ := model.NewTupleWithKeyValues("n1", "Bob") t2.SetString(nil, "name", "Bob") - rs.Assert(nil, t2) + err = rs.Assert(nil, t2) + if err != nil { + fmt.Printf("Warn: [%s]\n", err) + } //Now assert a "n2" tuple fmt.Println("Asserting n2 tuple with name=Bob") t3, _ := model.NewTupleWithKeyValues("n2", "Bob") t3.SetString(nil, "name", "Bob") - rs.Assert(nil, t3) + err = rs.Assert(nil, t3) + if err != nil { + fmt.Printf("Warn: [%s]\n", err) + } //Retract tuples - rs.Retract(nil, t1) - rs.Retract(nil, t2) - rs.Retract(nil, t3) + rs.Delete(nil, t1) + rs.Delete(nil, t2) + rs.Delete(nil, t3) //delete the rule rs.DeleteRule(rule.GetName()) diff --git a/redisutils/redisutils.go b/redisutils/redisutils.go index 7214ee0..3991b5a 100644 --- a/redisutils/redisutils.go +++ b/redisutils/redisutils.go @@ -151,3 +151,14 @@ func (rh *RedisHandle) Del(key string) int { } return j } + +func (rh *RedisHandle) HDel(key string) int { + c := rh.getPool().Get() + defer c.Close() + i, err := c.Do("HDEL", key) + j := -1 + if err == nil { + j, _ = redis.Int(i, err) + } + return j +} diff --git a/rete/common/types.go b/rete/common/types.go index 5b73904..5ce67a3 100644 --- a/rete/common/types.go +++ b/rete/common/types.go @@ -21,7 +21,7 @@ type Network interface { RemoveRule(string) model.Rule GetRules() []model.Rule Assert(ctx context.Context, rs model.RuleSession, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) - Retract(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) + Retract(ctx context.Context, rs model.RuleSession, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) GetAssertedTuple(key model.TupleKey) model.Tuple RegisterRtcTransactionHandler(txnHandler model.RtcTransactionHandler, txnContext interface{}) //SetConfig(config map[string]string) diff --git a/rete/internal/redis/rhandleservice.go b/rete/internal/redis/rhandleservice.go index 6643b58..72e1272 100644 --- a/rete/internal/redis/rhandleservice.go +++ b/rete/internal/redis/rhandleservice.go @@ -31,8 +31,8 @@ func (hc *handleServiceImpl) Init() { //} func (hc *handleServiceImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle { - - numDeleted := redisutils.GetRedisHdl().Del("h-" + tuple.GetKey().String()) + rkey := hc.prefix + tuple.GetKey().String() + numDeleted := redisutils.GetRedisHdl().Del(rkey) fmt.Printf("Deleted: [%d] keys\n", numDeleted) //TODO: Dummy handle diff --git a/rete/network.go b/rete/network.go index 6ec3af5..499f24f 100644 --- a/rete/network.go +++ b/rete/network.go @@ -572,12 +572,12 @@ func (nw *reteNetworkImpl) removeTupleFromRete(tuple model.Tuple) { } } -func (nw *reteNetworkImpl) Retract(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) { +func (nw *reteNetworkImpl) Retract(ctx context.Context, rs model.RuleSession, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) { if ctx == nil { ctx = context.Background() } - reteCtxVar, isRecursive, _ := getOrSetReteCtx(ctx, nw, nil) + reteCtxVar, isRecursive, ctx := getOrSetReteCtx(ctx, nw, rs) if !isRecursive { nw.crudLock.Lock() defer nw.crudLock.Unlock() diff --git a/rete/opsList.go b/rete/opsList.go index 97669e6..23ce989 100644 --- a/rete/opsList.go +++ b/rete/opsList.go @@ -60,7 +60,7 @@ func newModifyEntry(tuple model.Tuple, changeProps map[string]bool) modifyEntry func (me *modifyEntryImpl) execute(ctx context.Context) { reteCtx := getReteCtx(ctx) reteCtx.getConflictResolver().deleteAgendaFor(ctx, me.tuple, me.changeProps) - reteCtx.getNetwork().Retract(ctx, me.tuple, me.changeProps, common.MODIFY) + reteCtx.getNetwork().Retract(ctx, reteCtx.getRuleSession(), me.tuple, me.changeProps, common.MODIFY) reteCtx.getNetwork().Assert(ctx, reteCtx.getRuleSession(), me.tuple, me.changeProps, common.MODIFY) } diff --git a/ruleapi/internal/store/mem/mstore.go b/ruleapi/internal/store/mem/mstore.go index 1294f04..631c241 100644 --- a/ruleapi/internal/store/mem/mstore.go +++ b/ruleapi/internal/store/mem/mstore.go @@ -1,6 +1,7 @@ package mem import ( + "fmt" "github.com/project-flogo/rules/common/model" ) @@ -27,18 +28,33 @@ func (ms *storeImpl) SaveTuple(tuple model.Tuple) { ms.allTuples[tuple.GetKey().String()] = tuple } -func (ms *storeImpl) DeleteTupleByStringKey(key model.TupleKey) { +func (ms *storeImpl) DeleteTuple(key model.TupleKey) { delete(ms.allTuples, key.String()) } func (ms *storeImpl) SaveTuples(added map[string]map[string]model.Tuple) { - + for tupleType, tuples := range added { + for key, tuple := range tuples { + fmt.Printf("Saving tuple. Type [%s] Key [%s], Val [%v]\n", tupleType, key, tuple) + ms.SaveTuple(tuple) + } + } } func (ms *storeImpl) SaveModifiedTuples(modified map[string]map[string]model.RtcModified) { - + for tupleType, mmap := range modified { + for key, mdfd := range mmap { + fmt.Printf("Saving tuple. Type [%s] Key [%s], Val [%v]\n", tupleType, key, mdfd.GetTuple()) + ms.SaveTuple(mdfd.GetTuple()) + } + } } func (ms *storeImpl) DeleteTuples(deleted map[string]map[string]model.Tuple) { - + for tupleType, tuples := range deleted { + for key, tuple := range tuples { + fmt.Printf("Deleting tuple. Type [%s] Key [%s], Val [%v]\n", tupleType, key, tuple) + ms.DeleteTuple(tuple.GetKey()) + } + } } diff --git a/ruleapi/internal/store/redis/rstore.go b/ruleapi/internal/store/redis/rstore.go index ec89aca..dad21e5 100644 --- a/ruleapi/internal/store/redis/rstore.go +++ b/ruleapi/internal/store/redis/rstore.go @@ -1,13 +1,14 @@ package redis import ( + "fmt" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/redisutils" ) type storeImpl struct { //allTuples map[string]model.Tuple - prefix string + prefix string jsonConfig map[string]interface{} } @@ -28,13 +29,12 @@ func (ms *storeImpl) Init() { func (ms *storeImpl) GetTupleByKey(key model.TupleKey) model.Tuple { hdl := redisutils.GetRedisHdl() - strKey := ms.prefix + key.GetTupleDescriptor().Name + ":" + key.String() + strKey := ms.prefix + key.String() vals := hdl.HGetAll(strKey) - tuple, err := model.NewTuple(model.TupleType(key.GetTupleDescriptor().Name), vals) - if err != nil { + if err == nil { return tuple } return nil @@ -43,27 +43,47 @@ func (ms *storeImpl) GetTupleByKey(key model.TupleKey) model.Tuple { func (ms *storeImpl) SaveTuple(tuple model.Tuple) { m := tuple.ToMap() - strKey := ms.prefix + tuple.GetTupleDescriptor().Name + ":" + tuple.GetKey().String() + strKey := ms.prefix + tuple.GetKey().String() hdl := redisutils.GetRedisHdl() hdl.HSetAll(strKey, m) } -func (ms *storeImpl) DeleteTupleByStringKey(key model.TupleKey) { - strKey := ms.prefix + key.GetTupleDescriptor().Name + ":" + key.String() +func (ms *storeImpl) DeleteTuple(key model.TupleKey) { + strKey := ms.prefix + key.String() hdl := redisutils.GetRedisHdl() hdl.Del(strKey) } func (ms *storeImpl) SaveTuples(added map[string]map[string]model.Tuple) { - + hdl := redisutils.GetRedisHdl() + for tupleType, tuples := range added { + for key, tuple := range tuples { + fmt.Printf("Saving tuple. Type [%s] Key [%s], Val [%v]\n", tupleType, key, tuple) + strKey := ms.prefix + key + hdl.HSetAll(strKey, tuple.ToMap()) + } + } } func (ms *storeImpl) SaveModifiedTuples(modified map[string]map[string]model.RtcModified) { - + hdl := redisutils.GetRedisHdl() + for tupleType, mmap := range modified { + for key, mdfd := range mmap { + fmt.Printf("Saving tuple. Type [%s] Key [%s], Val [%v]\n", tupleType, key, mdfd.GetTuple()) + strKey := ms.prefix + key + hdl.HSetAll(strKey, mdfd.GetTuple().ToMap()) + } + } } func (ms *storeImpl) DeleteTuples(deleted map[string]map[string]model.Tuple) { - + hdl := redisutils.GetRedisHdl() + for tupleType, tuples := range deleted { + for key, tuple := range tuples { + fmt.Printf("Deleting tuple. Type [%s] Key [%s], Val [%v]\n", tupleType, key, tuple) + strKey := ms.prefix + key + hdl.Del(strKey) + } + } } - diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index 7da2042..187b4ee 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -148,11 +148,11 @@ func (rs *rulesessionImpl) Assert(ctx context.Context, tuple model.Tuple) (err e } func (rs *rulesessionImpl) Retract(ctx context.Context, tuple model.Tuple) { - rs.reteNetwork.Retract(ctx, tuple, nil, common.RETRACT) + rs.reteNetwork.Retract(ctx, rs, tuple, nil, common.RETRACT) } func (rs *rulesessionImpl) Delete(ctx context.Context, tuple model.Tuple) { - rs.reteNetwork.Retract(ctx, tuple, nil, common.DELETE) + rs.reteNetwork.Retract(ctx, rs, tuple, nil, common.DELETE) } func (rs *rulesessionImpl) printNetwork() { From 543f4e2484a794df93e5ed8042fd4ed614b3cc75 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Thu, 3 Jan 2019 16:29:55 +0530 Subject: [PATCH 033/125] redis store impl for join tables - wip --- common/model/tuplekey.go | 30 ++++ redisutils/redisutil_test.go | 41 ++++++ redisutils/redisutils.go | 147 +++++++++++++++++++ rete/internal/redis/rjointableimpl.go | 40 +++-- rete/internal/redis/rjointablerowimpl.go | 47 +++++- rete/internal/redis/rjointablerowiterator.go | 49 +++++-- rete/internal/redis/rjtservice.go | 6 +- rete/internal/types/types.go | 8 +- rete/network.go | 6 +- 9 files changed, 335 insertions(+), 39 deletions(-) diff --git a/common/model/tuplekey.go b/common/model/tuplekey.go index 996e2e3..852c78d 100644 --- a/common/model/tuplekey.go +++ b/common/model/tuplekey.go @@ -4,6 +4,7 @@ import ( "fmt" "reflect" "github.com/project-flogo/core/data/coerce" + "strings" ) // TupleKey primary key of a tuple @@ -118,3 +119,32 @@ func (tk *tupleKeyImpl) keysAsString() string { } return str } + +//TODO: Validations +func FromStringKey (strTupleKey string) TupleKey { + + keyComps := strings.Split(strTupleKey, ":") + + tupleType := TupleType(keyComps[0]) + + key := "" + + keyMap := make (map[string]interface{}) + + for i, v := range keyComps { + if i == 0 { + continue + } else if (i % 2 == 1) { + //this is a key + key = v + } else { + //this is a value + keyMap[key] = v + } + } + + tupleKey, _:= NewTupleKeyWithKeyValues(tupleType, keyMap) + + return tupleKey + +} diff --git a/redisutils/redisutil_test.go b/redisutils/redisutil_test.go index d4e3833..cab1d16 100644 --- a/redisutils/redisutil_test.go +++ b/redisutils/redisutil_test.go @@ -151,3 +151,44 @@ func getStruct(c redis.Conn) error { return nil } + + +func Test_three(t *testing.T) { + + InitService(nil) + + hdl := GetRedisHdl() + + iter := hdl.GetListIterator("x:jt:L_c2") + + for iter.HasNext() { + key := iter.Next() + fmt.Printf("KEY: [%s]\n", key) + } + + miter := hdl.GetMapIterator("a") + + for miter.HasNext() { + key, value := miter.Next() + fmt.Printf("KEY: [%s], Value=[%s]\n", key, value) + } + { + + miter := hdl.GetMapIterator("x:jt:L_c2") + + for miter.HasNext() { + key, value := miter.Next() + fmt.Printf("KEY: [%s], Value=[%s]\n", key, value) + } + } +} + +func Test_four(t *testing.T) { + InitService(nil) + + hdl := GetRedisHdl() + + v := hdl.HGet("a", "b") + fmt.Printf("[%v]\n", v) + +} diff --git a/redisutils/redisutils.go b/redisutils/redisutils.go index 3991b5a..09906ed 100644 --- a/redisutils/redisutils.go +++ b/redisutils/redisutils.go @@ -93,6 +93,23 @@ func (rh *RedisHandle) HGetAll(key string) map[string]interface{} { return hgetall } +func (rh *RedisHandle) HGet(key string, field string) interface{} { + c := rh.getPool().Get() + defer c.Close() + vals, error := c.Do("HGET", key, field) + if error != nil { + fmt.Printf("error [%v]\n", error) + } else { + vals2, err2 := redis.Values(vals, error) + if err2 != nil { + fmt.Printf("error [%v]\n", err2) + return nil + } + return vals2[0] + } + return nil +} + func (rh *RedisHandle) HIncrBy(key string, field string, by int) int { c := rh.getPool().Get() defer c.Close() @@ -162,3 +179,133 @@ func (rh *RedisHandle) HDel(key string) int { } return j } + + +type LIterator struct { + key string + scanIdx int //iterator index + keyIdx int //local array current index + keys []string //array of keys in the current call + valueMap map[string]interface{} //map of key/value of the current keys + rh RedisHdl +} + +func (iter *LIterator) HasNext() bool { + + if iter.scanIdx == 0 { // there is nothing more with redis + if len(iter.keys) == 0 { + return false + } else { + if iter.keyIdx == len(iter.keys) { + return false + } + } + } else { + if len(iter.keys) == 0 || iter.keyIdx >= len(iter.keys) { + c := iter.rh.getPool().Get() + defer c.Close() + if arr, err := redis.Values(c.Do("HSCAN", iter.key, iter.scanIdx)); err != nil { + panic(err) + } else { + // now we get the iter and the keys from the multi-bulk reply + iter.scanIdx, _ = redis.Int(arr[0], nil) + iter.keys, _ = redis.Strings(arr[1], nil) + iter.keyIdx = 0 + return iter.HasNext() + } + } + } + + return true + +} + +func (iter *LIterator) Next() string { + str := iter.keys[iter.keyIdx] + iter.keyIdx++ + return str +} + + +func (rh *RedisHandle) GetListIterator(key string) *LIterator { + + iter := LIterator{} + iter.key = key + iter.rh = rh + c := rh.getPool().Get() + defer c.Close() + + if arr, err := redis.Values(c.Do("HSCAN", key, 0, "COUNT", 1)); err != nil { + panic(err) + } else { + // now we get the iter and the keys from the multi-bulk reply + iter.scanIdx, _ = redis.Int(arr[0], nil) + iter.keys, _ = redis.Strings(arr[1], nil) + iter.keyIdx = 0 + } + + return &iter +} + +type MapIterator struct { + LIterator +} + +func (iter *MapIterator) HasNext() bool { + + if iter.scanIdx == 0 { // there is nothing more with redis + if len(iter.keys) == 0 { + return false + } else { + if iter.keyIdx > len(iter.keys) - 2 { + return false + } + } + } else { + if len(iter.keys) == 0 || iter.keyIdx >= len(iter.keys) - 1 { + c := iter.rh.getPool().Get() + defer c.Close() + if arr, err := redis.Values(c.Do("HSCAN", iter.key, iter.scanIdx)); err != nil { + panic(err) + } else { + iter.scanIdx, _ = redis.Int(arr[0], nil) + iter.keys, _ = redis.Strings(arr[1], nil) + iter.keyIdx = 0 + return iter.HasNext() + } + } + } + + return true + +} + +func (iter *MapIterator) Next() (string, interface{}) { + str := iter.keys[iter.keyIdx] + value := iter.keys[iter.keyIdx+1] + + iter.keyIdx += 2 + return str, value +} + + +func (rh *RedisHandle) GetMapIterator(key string) *MapIterator { + + iter := MapIterator{} + iter.key = key + iter.rh = rh + c := rh.getPool().Get() + defer c.Close() + + if arr, err := redis.Values(c.Do("HSCAN", key, 0, "COUNT", 1)); err != nil { + panic(err) + } else { + // now we get the iter and the keys from the multi-bulk reply + iter.scanIdx, _ = redis.Int(arr[0], nil) + iter.keys, _ = redis.Strings(arr[1], nil) + iter.keyIdx = 0 + } + + return &iter +} + diff --git a/rete/internal/redis/rjointableimpl.go b/rete/internal/redis/rjointableimpl.go index f52753e..c7661f0 100644 --- a/rete/internal/redis/rjointableimpl.go +++ b/rete/internal/redis/rjointableimpl.go @@ -3,14 +3,16 @@ package redis import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/rete/internal/types" + "github.com/project-flogo/rules/redisutils" ) type joinTableImpl struct { types.NwElemIdImpl - table map[int]types.JoinTableRow + //table map[int]types.JoinTableRow idr []model.TupleType rule model.Rule name string + jtKey string } func newJoinTableImpl(nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) types.JoinTable { @@ -22,16 +24,17 @@ func newJoinTableImpl(nw types.Network, rule model.Rule, identifiers []model.Tup func (jt *joinTableImpl) initJoinTableImpl(nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) { jt.SetID(nw) jt.idr = identifiers - jt.table = map[int]types.JoinTableRow{} + //jt.table = map[int]types.JoinTableRow{} jt.rule = rule jt.name = name + jt.jtKey = nw.GetPrefix() + ":" + "jt:" + name } func (jt *joinTableImpl) AddRow(handles []types.ReteHandle) types.JoinTableRow { - row := newJoinTableRow(handles, jt.Nw) + row := newJoinTableRow(jt.name, handles, jt.Nw) - jt.table[row.GetID()] = row + //jt.table[row.GetID()] = row for i := 0; i < len(row.GetHandles()); i++ { handle := row.GetHandles()[i] jt.Nw.GetJtRefService().AddEntry(handle, jt.name, row.GetID()) @@ -40,12 +43,16 @@ func (jt *joinTableImpl) AddRow(handles []types.ReteHandle) types.JoinTableRow { } func (jt *joinTableImpl) RemoveRow(rowID int) types.JoinTableRow { - row, found := jt.table[rowID] - if found { - delete(jt.table, rowID) - return row - } - return nil + + + hdl := newJoinTableRowLoadedFromStore () + + //row, found := jt.table[rowID] + //if found { + // delete(jt.table, rowID) + // return row + //} + //return nil } func (jt *joinTableImpl) GetRowCount() int { @@ -57,7 +64,7 @@ func (jt *joinTableImpl) GetRule() model.Rule { } func (jt *joinTableImpl) GetRowIterator() types.RowIterator { - return newRowIterator(jt.table) + return newRowIterator(jt) } func (jt *joinTableImpl) GetRow(rowID int) types.JoinTableRow { @@ -67,3 +74,14 @@ func (jt *joinTableImpl) GetRow(rowID int) types.JoinTableRow { func (jt *joinTableImpl) GetName() string { return jt.name } + +func (jt *joinTableImpl) loadRowFromStore(rowID int) { + + + hdl := redisutils.GetRedisHdl() + + row := hdl.HGetAll(jt.jtKey) + + + +} diff --git a/rete/internal/redis/rjointablerowimpl.go b/rete/internal/redis/rjointablerowimpl.go index 803a0d4..e7a11fe 100644 --- a/rete/internal/redis/rjointablerowimpl.go +++ b/rete/internal/redis/rjointablerowimpl.go @@ -1,23 +1,60 @@ package redis -import "github.com/project-flogo/rules/rete/internal/types" +import ( + "github.com/project-flogo/rules/rete/internal/types" + "strconv" + "github.com/project-flogo/rules/redisutils" +) type joinTableRowImpl struct { types.NwElemIdImpl handles []types.ReteHandle + jtName string } -func newJoinTableRow(handles []types.ReteHandle, nw types.Network) types.JoinTableRow { +func newJoinTableRow(jtName string, handles []types.ReteHandle, nw types.Network) types.JoinTableRow { jtr := joinTableRowImpl{} - jtr.initJoinTableRow(handles, nw) + jtr.SetID(nw) + jtr.initJoinTableRow(jtName, handles, nw) return &jtr } -func (jtr *joinTableRowImpl) initJoinTableRow(handles []types.ReteHandle, nw types.Network) { - jtr.SetID(nw) +func newJoinTableRowLoadedFromStore(jtName string, rowID int, handles []types.ReteHandle, nw types.Network) types.JoinTableRow { + jtr := joinTableRowImpl{} + jtr.jtName = jtName + jtr.Nw = nw + jtr.ID = rowID + jtr.handles = handles + + return &jtr +} + +func (jtr *joinTableRowImpl) initJoinTableRow(jtName string, handles []types.ReteHandle, nw types.Network) { + jtr.jtName = jtName jtr.handles = append([]types.ReteHandle{}, handles...) + + rowEntry := make (map[string]interface{}) + + str := "" + + for i, v := range handles { + str += v.GetTupleKey().String() + if i < len(handles) - 1 { + str += "," + } + } + + rowEntry[strconv.Itoa(jtr.ID)] = str + + + key := nw.GetPrefix() + ":jt:" + jtName + + hdl := redisutils.GetRedisHdl() + hdl.HSetAll(key, rowEntry) + } func (jtr *joinTableRowImpl) GetHandles() []types.ReteHandle { return jtr.handles } + diff --git a/rete/internal/redis/rjointablerowiterator.go b/rete/internal/redis/rjointablerowiterator.go index 392ab10..4e6240f 100644 --- a/rete/internal/redis/rjointablerowiterator.go +++ b/rete/internal/redis/rjointablerowiterator.go @@ -1,34 +1,51 @@ package redis import ( - "container/list" "github.com/project-flogo/rules/rete/internal/types" + "github.com/project-flogo/rules/redisutils" + "strings" + "github.com/project-flogo/rules/common/model" + "strconv" ) type rowIteratorImpl struct { - table map[int]types.JoinTableRow - kList list.List - curr *list.Element + iter *redisutils.MapIterator + jtName string + nw types.Network } -func newRowIterator(jTable map[int]types.JoinTableRow) types.RowIterator { +func newRowIterator(jTable types.JoinTable) types.RowIterator { + key := jTable.GetNw().GetPrefix() + "jt:" + jTable.GetName() ri := rowIteratorImpl{} - ri.table = jTable - ri.kList = list.List{} - for k, _ := range jTable { - ri.kList.PushBack(k) - } - ri.curr = ri.kList.Front() + ri.iter = redisutils.GetRedisHdl().GetMapIterator(key) + ri.nw = jTable.GetNw() + ri.jtName = jTable.GetName() return &ri } func (ri *rowIteratorImpl) HasNext() bool { - return ri.curr != nil + return ri.iter.HasNext() } func (ri *rowIteratorImpl) Next() types.JoinTableRow { - id := ri.curr.Value.(int) - val := ri.table[id] - ri.curr = ri.curr.Next() - return val + + rowId, value := ri.iter.Next() + rowID, _ := strconv.Atoi(rowId) + + strval := value.(string) + values := strings.Split(strval, ",") + + + handles := []types.ReteHandle{} + for _, key := range values { + tupleKey := model.FromStringKey(key) + tuple := ri.nw.GetTupleStore().GetTupleByKey(tupleKey) + handle := newReteHandleImpl(ri.nw, tuple) + handles = append (handles, handle) + } + + jtRow := newJoinTableRowLoadedFromStore(ri.jtName, rowID, handles, ri.nw) + + return jtRow + } diff --git a/rete/internal/redis/rjtservice.go b/rete/internal/redis/rjtservice.go index 336efa2..640bfde 100644 --- a/rete/internal/redis/rjtservice.go +++ b/rete/internal/redis/rjtservice.go @@ -9,20 +9,22 @@ type jtServiceImpl struct { types.NwServiceImpl allJoinTables map[string]types.JoinTable + prefix string } func NewJoinTableCollection(nw types.Network, config map[string]interface{}) types.JtService { jtc := jtServiceImpl{} jtc.Nw = nw jtc.allJoinTables = make(map[string]types.JoinTable) + jtc.prefix = nw.GetPrefix() + "jtc:" return &jtc } func (jtc *jtServiceImpl) Init() { } -func (jtc *jtServiceImpl) GetJoinTable(joinTableName string) types.JoinTable { - return jtc.allJoinTables[joinTableName] +func (jtc *jtServiceImpl) GetJoinTable(name string) types.JoinTable { + return jtc.allJoinTables[name] } func (jtc *jtServiceImpl) GetOrCreateJoinTable(nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) types.JoinTable { diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index cb6aede..6849fcf 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -18,6 +18,7 @@ type Network interface { type NwElemId interface { SetID(nw Network) GetID() int + GetNw() Network } type NwElemIdImpl struct { ID int @@ -31,6 +32,9 @@ func (ide *NwElemIdImpl) SetID(nw Network) { func (ide *NwElemIdImpl) GetID() int { return ide.ID } +func (ide *NwElemIdImpl) GetNw() Network { + return ide.Nw +} type JoinTable interface { NwElemId @@ -83,8 +87,8 @@ type JtService interface { NwService GetOrCreateJoinTable(nw Network, rule model.Rule, identifiers []model.TupleType, name string) JoinTable GetJoinTable(name string) JoinTable - AddJoinTable(joinTable JoinTable) - RemoveJoinTable(name string) + //AddJoinTable(joinTable JoinTable) + //RemoveJoinTable(name string) } type HandleService interface { diff --git a/rete/network.go b/rete/network.go index 499f24f..929d41b 100644 --- a/rete/network.go +++ b/rete/network.go @@ -663,9 +663,9 @@ func (nw *reteNetworkImpl) getFactory() *TypeFactory { return nw.factory } -func (nw *reteNetworkImpl) AddToAllJoinTables(joinTable types.JoinTable) { - nw.jtService.AddJoinTable(joinTable) -} +//func (nw *reteNetworkImpl) AddToAllJoinTables(joinTable types.JoinTable) { +// nw.jtService.AddJoinTable(joinTable) +//} func (nw *reteNetworkImpl) SetTupleStore(tupleStore model.TupleStore) { nw.tupleStore = tupleStore From 20a973237e13a9ae4850e348de66304fd4e9076a Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Thu, 3 Jan 2019 20:23:22 +0530 Subject: [PATCH 034/125] wip redis impl for jointables --- common/model/tuplekey.go | 8 ++-- redisutils/redisutil_test.go | 6 +-- redisutils/redisutils.go | 44 +++++++++++--------- rete/internal/redis/rjointableimpl.go | 43 +++++++------------ rete/internal/redis/rjointablerowimpl.go | 44 +++++++++++++------- rete/internal/redis/rjointablerowiterator.go | 32 +++----------- rete/internal/redis/rjtservice.go | 2 +- 7 files changed, 83 insertions(+), 96 deletions(-) diff --git a/common/model/tuplekey.go b/common/model/tuplekey.go index 852c78d..4bbb2ab 100644 --- a/common/model/tuplekey.go +++ b/common/model/tuplekey.go @@ -121,7 +121,7 @@ func (tk *tupleKeyImpl) keysAsString() string { } //TODO: Validations -func FromStringKey (strTupleKey string) TupleKey { +func FromStringKey(strTupleKey string) TupleKey { keyComps := strings.Split(strTupleKey, ":") @@ -129,12 +129,12 @@ func FromStringKey (strTupleKey string) TupleKey { key := "" - keyMap := make (map[string]interface{}) + keyMap := make(map[string]interface{}) for i, v := range keyComps { if i == 0 { continue - } else if (i % 2 == 1) { + } else if i%2 == 1 { //this is a key key = v } else { @@ -143,7 +143,7 @@ func FromStringKey (strTupleKey string) TupleKey { } } - tupleKey, _:= NewTupleKeyWithKeyValues(tupleType, keyMap) + tupleKey, _ := NewTupleKeyWithKeyValues(tupleType, keyMap) return tupleKey diff --git a/redisutils/redisutil_test.go b/redisutils/redisutil_test.go index cab1d16..5c9ad14 100644 --- a/redisutils/redisutil_test.go +++ b/redisutils/redisutil_test.go @@ -152,7 +152,6 @@ func getStruct(c redis.Conn) error { } - func Test_three(t *testing.T) { InitService(nil) @@ -188,7 +187,8 @@ func Test_four(t *testing.T) { hdl := GetRedisHdl() - v := hdl.HGet("a", "b") - fmt.Printf("[%v]\n", v) + //v := hdl.HGet("a", "d") + len := hdl.HLen("a") + fmt.Printf("[%d]\n", len) } diff --git a/redisutils/redisutils.go b/redisutils/redisutils.go index 09906ed..fc040cf 100644 --- a/redisutils/redisutils.go +++ b/redisutils/redisutils.go @@ -100,16 +100,26 @@ func (rh *RedisHandle) HGet(key string, field string) interface{} { if error != nil { fmt.Printf("error [%v]\n", error) } else { - vals2, err2 := redis.Values(vals, error) - if err2 != nil { - fmt.Printf("error [%v]\n", err2) - return nil - } - return vals2[0] + ba := vals.([]byte) + s := string(ba) + return s } return nil } +func (rh *RedisHandle) HLen(key string) int { + c := rh.getPool().Get() + defer c.Close() + vals, error := c.Do("HLEN", key) + if error != nil { + fmt.Printf("error [%v]\n", error) + } else { + len, _ := redis.Int(vals, error) + return len + } + return 0 +} + func (rh *RedisHandle) HIncrBy(key string, field string, by int) int { c := rh.getPool().Get() defer c.Close() @@ -169,10 +179,10 @@ func (rh *RedisHandle) Del(key string) int { return j } -func (rh *RedisHandle) HDel(key string) int { +func (rh *RedisHandle) HDel(key string, field string) int { c := rh.getPool().Get() defer c.Close() - i, err := c.Do("HDEL", key) + i, err := c.Do("HDEL", key, field) j := -1 if err == nil { j, _ = redis.Int(i, err) @@ -180,14 +190,13 @@ func (rh *RedisHandle) HDel(key string) int { return j } - type LIterator struct { - key string - scanIdx int //iterator index - keyIdx int //local array current index - keys []string //array of keys in the current call + key string + scanIdx int //iterator index + keyIdx int //local array current index + keys []string //array of keys in the current call valueMap map[string]interface{} //map of key/value of the current keys - rh RedisHdl + rh RedisHdl } func (iter *LIterator) HasNext() bool { @@ -226,7 +235,6 @@ func (iter *LIterator) Next() string { return str } - func (rh *RedisHandle) GetListIterator(key string) *LIterator { iter := LIterator{} @@ -257,12 +265,12 @@ func (iter *MapIterator) HasNext() bool { if len(iter.keys) == 0 { return false } else { - if iter.keyIdx > len(iter.keys) - 2 { + if iter.keyIdx > len(iter.keys)-2 { return false } } } else { - if len(iter.keys) == 0 || iter.keyIdx >= len(iter.keys) - 1 { + if len(iter.keys) == 0 || iter.keyIdx >= len(iter.keys)-1 { c := iter.rh.getPool().Get() defer c.Close() if arr, err := redis.Values(c.Do("HSCAN", iter.key, iter.scanIdx)); err != nil { @@ -288,7 +296,6 @@ func (iter *MapIterator) Next() (string, interface{}) { return str, value } - func (rh *RedisHandle) GetMapIterator(key string) *MapIterator { iter := MapIterator{} @@ -308,4 +315,3 @@ func (rh *RedisHandle) GetMapIterator(key string) *MapIterator { return &iter } - diff --git a/rete/internal/redis/rjointableimpl.go b/rete/internal/redis/rjointableimpl.go index c7661f0..7a49d7d 100644 --- a/rete/internal/redis/rjointableimpl.go +++ b/rete/internal/redis/rjointableimpl.go @@ -2,8 +2,9 @@ package redis import ( "github.com/project-flogo/rules/common/model" - "github.com/project-flogo/rules/rete/internal/types" "github.com/project-flogo/rules/redisutils" + "github.com/project-flogo/rules/rete/internal/types" + "strconv" ) type joinTableImpl struct { @@ -24,17 +25,13 @@ func newJoinTableImpl(nw types.Network, rule model.Rule, identifiers []model.Tup func (jt *joinTableImpl) initJoinTableImpl(nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) { jt.SetID(nw) jt.idr = identifiers - //jt.table = map[int]types.JoinTableRow{} jt.rule = rule jt.name = name jt.jtKey = nw.GetPrefix() + ":" + "jt:" + name } func (jt *joinTableImpl) AddRow(handles []types.ReteHandle) types.JoinTableRow { - - row := newJoinTableRow(jt.name, handles, jt.Nw) - - //jt.table[row.GetID()] = row + row := newJoinTableRow(jt.jtKey, handles, jt.Nw) for i := 0; i < len(row.GetHandles()); i++ { handle := row.GetHandles()[i] jt.Nw.GetJtRefService().AddEntry(handle, jt.name, row.GetID()) @@ -43,20 +40,16 @@ func (jt *joinTableImpl) AddRow(handles []types.ReteHandle) types.JoinTableRow { } func (jt *joinTableImpl) RemoveRow(rowID int) types.JoinTableRow { - - - hdl := newJoinTableRowLoadedFromStore () - - //row, found := jt.table[rowID] - //if found { - // delete(jt.table, rowID) - // return row - //} - //return nil + row := jt.GetRow(rowID) + hdl := redisutils.GetRedisHdl() + rowId := strconv.Itoa(rowID) + hdl.HDel(jt.jtKey, rowId) + return row } func (jt *joinTableImpl) GetRowCount() int { - return len(jt.table) + hdl := redisutils.GetRedisHdl() + return hdl.HLen(jt.name) } func (jt *joinTableImpl) GetRule() model.Rule { @@ -68,20 +61,12 @@ func (jt *joinTableImpl) GetRowIterator() types.RowIterator { } func (jt *joinTableImpl) GetRow(rowID int) types.JoinTableRow { - return jt.table[rowID] + hdl := redisutils.GetRedisHdl() + key := hdl.HGet(jt.jtKey, strconv.Itoa(rowID)) + rowId := strconv.Itoa(rowID) + return createRow(jt.name, rowId, key.(string), jt.Nw) } func (jt *joinTableImpl) GetName() string { return jt.name } - -func (jt *joinTableImpl) loadRowFromStore(rowID int) { - - - hdl := redisutils.GetRedisHdl() - - row := hdl.HGetAll(jt.jtKey) - - - -} diff --git a/rete/internal/redis/rjointablerowimpl.go b/rete/internal/redis/rjointablerowimpl.go index e7a11fe..1a526f8 100644 --- a/rete/internal/redis/rjointablerowimpl.go +++ b/rete/internal/redis/rjointablerowimpl.go @@ -1,27 +1,29 @@ package redis import ( + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/redisutils" "github.com/project-flogo/rules/rete/internal/types" "strconv" - "github.com/project-flogo/rules/redisutils" + "strings" ) type joinTableRowImpl struct { types.NwElemIdImpl handles []types.ReteHandle - jtName string + jtKey string } -func newJoinTableRow(jtName string, handles []types.ReteHandle, nw types.Network) types.JoinTableRow { +func newJoinTableRow(jtKey string, handles []types.ReteHandle, nw types.Network) types.JoinTableRow { jtr := joinTableRowImpl{} jtr.SetID(nw) - jtr.initJoinTableRow(jtName, handles, nw) + jtr.initJoinTableRow(jtKey, handles, nw) return &jtr } -func newJoinTableRowLoadedFromStore(jtName string, rowID int, handles []types.ReteHandle, nw types.Network) types.JoinTableRow { +func newJoinTableRowLoadedFromStore(jtKey string, rowID int, handles []types.ReteHandle, nw types.Network) types.JoinTableRow { jtr := joinTableRowImpl{} - jtr.jtName = jtName + jtr.jtKey = jtKey jtr.Nw = nw jtr.ID = rowID jtr.handles = handles @@ -29,28 +31,25 @@ func newJoinTableRowLoadedFromStore(jtName string, rowID int, handles []types.Re return &jtr } -func (jtr *joinTableRowImpl) initJoinTableRow(jtName string, handles []types.ReteHandle, nw types.Network) { - jtr.jtName = jtName +func (jtr *joinTableRowImpl) initJoinTableRow(jtKey string, handles []types.ReteHandle, nw types.Network) { + jtr.jtKey = jtKey jtr.handles = append([]types.ReteHandle{}, handles...) - rowEntry := make (map[string]interface{}) + rowEntry := make(map[string]interface{}) str := "" for i, v := range handles { str += v.GetTupleKey().String() - if i < len(handles) - 1 { + if i < len(handles)-1 { str += "," } } rowEntry[strconv.Itoa(jtr.ID)] = str - - key := nw.GetPrefix() + ":jt:" + jtName - hdl := redisutils.GetRedisHdl() - hdl.HSetAll(key, rowEntry) + hdl.HSetAll(jtKey, rowEntry) } @@ -58,3 +57,20 @@ func (jtr *joinTableRowImpl) GetHandles() []types.ReteHandle { return jtr.handles } +func createRow(jtKey string, rowID string, key string, nw types.Network) types.JoinTableRow { + + values := strings.Split(key, ",") + + handles := []types.ReteHandle{} + for _, key := range values { + tupleKey := model.FromStringKey(key) + tuple := nw.GetTupleStore().GetTupleByKey(tupleKey) + handle := newReteHandleImpl(nw, tuple) + handles = append(handles, handle) + } + + rowId, _ := strconv.Atoi(rowID) + jtRow := newJoinTableRowLoadedFromStore(jtKey, rowId, handles, nw) + + return jtRow +} diff --git a/rete/internal/redis/rjointablerowiterator.go b/rete/internal/redis/rjointablerowiterator.go index 4e6240f..b0c163b 100644 --- a/rete/internal/redis/rjointablerowiterator.go +++ b/rete/internal/redis/rjointablerowiterator.go @@ -1,17 +1,14 @@ package redis import ( - "github.com/project-flogo/rules/rete/internal/types" "github.com/project-flogo/rules/redisutils" - "strings" - "github.com/project-flogo/rules/common/model" - "strconv" + "github.com/project-flogo/rules/rete/internal/types" ) type rowIteratorImpl struct { - iter *redisutils.MapIterator + iter *redisutils.MapIterator jtName string - nw types.Network + nw types.Network } func newRowIterator(jTable types.JoinTable) types.RowIterator { @@ -28,24 +25,7 @@ func (ri *rowIteratorImpl) HasNext() bool { } func (ri *rowIteratorImpl) Next() types.JoinTableRow { - - rowId, value := ri.iter.Next() - rowID, _ := strconv.Atoi(rowId) - - strval := value.(string) - values := strings.Split(strval, ",") - - - handles := []types.ReteHandle{} - for _, key := range values { - tupleKey := model.FromStringKey(key) - tuple := ri.nw.GetTupleStore().GetTupleByKey(tupleKey) - handle := newReteHandleImpl(ri.nw, tuple) - handles = append (handles, handle) - } - - jtRow := newJoinTableRowLoadedFromStore(ri.jtName, rowID, handles, ri.nw) - - return jtRow - + rowId, key := ri.iter.Next() + tupleKeyStr := key.(string) + return createRow(ri.jtName, rowId, tupleKeyStr, ri.nw) } diff --git a/rete/internal/redis/rjtservice.go b/rete/internal/redis/rjtservice.go index 640bfde..4991f44 100644 --- a/rete/internal/redis/rjtservice.go +++ b/rete/internal/redis/rjtservice.go @@ -9,7 +9,7 @@ type jtServiceImpl struct { types.NwServiceImpl allJoinTables map[string]types.JoinTable - prefix string + prefix string } func NewJoinTableCollection(nw types.Network, config map[string]interface{}) types.JtService { From 21816ab61550d6758465d5ce970666f6e13bc26e Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Thu, 3 Jan 2019 22:00:27 +0530 Subject: [PATCH 035/125] wip redis impl for jointables --- common/model/tuplekey.go | 30 +++++++++++++++++++----- examples/rulesapp/main.go | 10 ++++---- rete/internal/redis/rjointablerowimpl.go | 3 +++ rete/internal/redis/rretehandle.go | 4 +++- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/common/model/tuplekey.go b/common/model/tuplekey.go index 4bbb2ab..4de9136 100644 --- a/common/model/tuplekey.go +++ b/common/model/tuplekey.go @@ -70,24 +70,42 @@ func NewTupleKeyWithKeyValues(tupleType TupleType, values ...interface{}) (tuple tk.td = *td tk.keys = make(map[string]interface{}) - if len(values) != len(td.GetKeyProps()) { - return nil, fmt.Errorf("Wrong number of key values in type [%s]. Expecting [%d], got [%d]", - td.Name, len(td.GetKeyProps()), len(values)) + switch vt := values[0].(type) { + case map[string]interface{}: + if len(vt) != len(td.GetKeyProps()) { + return nil, fmt.Errorf("Wrong number of key values in type [%s]. Expecting [%d], got [%d]", + td.Name, len(td.GetKeyProps()), len(vt)) + } + default: + if len(values) != len(td.GetKeyProps()) { + return nil, fmt.Errorf("Wrong number of key values in type [%s]. Expecting [%d], got [%d]", + td.Name, len(td.GetKeyProps()), len(values)) + } } - i := 0 - for _, keyProp := range td.GetKeyProps() { + for i, keyProp := range td.GetKeyProps() { tdp := td.GetProperty(keyProp) +<<<<<<< HEAD val := values[i] coerced, err := coerce.ToType(val, tdp.PropType) +======= + var val interface{} + switch vt := values[0].(type) { + case map[string]interface{}: + val = vt[keyProp] + default: + val = values[i] + } + coerced, err := data.CoerceToValue(val, tdp.PropType) +>>>>>>> wip redis impl for jointables if err == nil { tk.keys[keyProp] = coerced } else { return nil, fmt.Errorf("Type mismatch for field [%s] in type [%s] Expecting [%s], got [%v]", keyProp, td.Name, tdp.PropType.String(), reflect.TypeOf(val)) } - i++ } + tk.keyAsStr = tk.keysAsString() return &tk, err } diff --git a/examples/rulesapp/main.go b/examples/rulesapp/main.go index 0cdbad9..18fde7d 100644 --- a/examples/rulesapp/main.go +++ b/examples/rulesapp/main.go @@ -55,7 +55,7 @@ func main() { fmt.Println("Asserting n1 tuple with name=Tom") t1, _ := model.NewTupleWithKeyValues("n1", "Tom") t1.SetString(nil, "name", "Tom") - err = rs.Assert(nil, t1) + //err = rs.Assert(nil, t1) if err != nil { fmt.Printf("Warn: [%s]\n", err) } @@ -73,15 +73,15 @@ func main() { fmt.Println("Asserting n2 tuple with name=Bob") t3, _ := model.NewTupleWithKeyValues("n2", "Bob") t3.SetString(nil, "name", "Bob") - err = rs.Assert(nil, t3) + //err = rs.Assert(nil, t3) if err != nil { fmt.Printf("Warn: [%s]\n", err) } //Retract tuples - rs.Delete(nil, t1) - rs.Delete(nil, t2) - rs.Delete(nil, t3) + //rs.Delete(nil, t1) + //rs.Delete(nil, t2) + //rs.Delete(nil, t3) //delete the rule rs.DeleteRule(rule.GetName()) diff --git a/rete/internal/redis/rjointablerowimpl.go b/rete/internal/redis/rjointablerowimpl.go index 1a526f8..1880b52 100644 --- a/rete/internal/redis/rjointablerowimpl.go +++ b/rete/internal/redis/rjointablerowimpl.go @@ -1,6 +1,7 @@ package redis import ( + "fmt" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/redisutils" "github.com/project-flogo/rules/rete/internal/types" @@ -65,6 +66,8 @@ func createRow(jtKey string, rowID string, key string, nw types.Network) types.J for _, key := range values { tupleKey := model.FromStringKey(key) tuple := nw.GetTupleStore().GetTupleByKey(tupleKey) + ks := tupleKey.String() + fmt.Printf(ks) handle := newReteHandleImpl(nw, tuple) handles = append(handles, handle) } diff --git a/rete/internal/redis/rretehandle.go b/rete/internal/redis/rretehandle.go index 06aa21b..614713c 100644 --- a/rete/internal/redis/rretehandle.go +++ b/rete/internal/redis/rretehandle.go @@ -22,7 +22,9 @@ func newReteHandleImpl(nw types.Network, tuple model.Tuple) types.ReteHandle { func (hdl *reteHandleImpl) SetTuple(tuple model.Tuple) { hdl.tuple = tuple - hdl.tupleKey = tuple.GetKey() + if tuple == nil { + hdl.tupleKey = tuple.GetKey() + } } func (hdl *reteHandleImpl) initHandleImpl(nw types.Network, tuple model.Tuple) { From 4971fac2dc3e8e1f46f6bb31434354c9527bdf3c Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Thu, 3 Jan 2019 22:05:47 +0530 Subject: [PATCH 036/125] wip redis impl for jointables --- rete/internal/mem/mjtservice.go | 14 +++++++------- rete/internal/redis/rjtservice.go | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/rete/internal/mem/mjtservice.go b/rete/internal/mem/mjtservice.go index 308fa11..a27d0fe 100644 --- a/rete/internal/mem/mjtservice.go +++ b/rete/internal/mem/mjtservice.go @@ -24,13 +24,13 @@ func (jtc *jtServiceImpl) GetJoinTable(joinTableName string) types.JoinTable { return jtc.allJoinTables[joinTableName] } -func (jtc *jtServiceImpl) AddJoinTable(joinTable types.JoinTable) { - jtc.allJoinTables[joinTable.GetName()] = joinTable -} - -func (jtc *jtServiceImpl) RemoveJoinTable(jtName string) { - delete(jtc.allJoinTables, jtName) -} +//func (jtc *jtServiceImpl) AddJoinTable(joinTable types.JoinTable) { +// jtc.allJoinTables[joinTable.GetName()] = joinTable +//} +// +//func (jtc *jtServiceImpl) RemoveJoinTable(jtName string) { +// delete(jtc.allJoinTables, jtName) +//} func (jtc *jtServiceImpl) GetOrCreateJoinTable(nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) types.JoinTable { jT, found := jtc.allJoinTables[name] diff --git a/rete/internal/redis/rjtservice.go b/rete/internal/redis/rjtservice.go index 4991f44..e5d1a74 100644 --- a/rete/internal/redis/rjtservice.go +++ b/rete/internal/redis/rjtservice.go @@ -36,10 +36,10 @@ func (jtc *jtServiceImpl) GetOrCreateJoinTable(nw types.Network, rule model.Rule return jT } -func (jtc *jtServiceImpl) AddJoinTable(joinTable types.JoinTable) { - jtc.allJoinTables[joinTable.GetName()] = joinTable -} - -func (jtc *jtServiceImpl) RemoveJoinTable(jtName string) { - delete(jtc.allJoinTables, jtName) -} +//func (jtc *jtServiceImpl) AddJoinTable(joinTable types.JoinTable) { +// jtc.allJoinTables[joinTable.GetName()] = joinTable +//} +// +//func (jtc *jtServiceImpl) RemoveJoinTable(jtName string) { +// delete(jtc.allJoinTables, jtName) +//} From 4db3d556f739dfa3206d9bafb8772044b0cf1818 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Thu, 3 Jan 2019 22:29:18 +0530 Subject: [PATCH 037/125] wip redis impl for jointables --- examples/rulesapp/main.go | 4 ++-- rete/internal/redis/rjointablerowiterator.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/rulesapp/main.go b/examples/rulesapp/main.go index 18fde7d..0e12c29 100644 --- a/examples/rulesapp/main.go +++ b/examples/rulesapp/main.go @@ -64,7 +64,7 @@ func main() { fmt.Println("Asserting n1 tuple with name=Bob") t2, _ := model.NewTupleWithKeyValues("n1", "Bob") t2.SetString(nil, "name", "Bob") - err = rs.Assert(nil, t2) + //err = rs.Assert(nil, t2) if err != nil { fmt.Printf("Warn: [%s]\n", err) } @@ -73,7 +73,7 @@ func main() { fmt.Println("Asserting n2 tuple with name=Bob") t3, _ := model.NewTupleWithKeyValues("n2", "Bob") t3.SetString(nil, "name", "Bob") - //err = rs.Assert(nil, t3) + err = rs.Assert(nil, t3) if err != nil { fmt.Printf("Warn: [%s]\n", err) } diff --git a/rete/internal/redis/rjointablerowiterator.go b/rete/internal/redis/rjointablerowiterator.go index b0c163b..556752f 100644 --- a/rete/internal/redis/rjointablerowiterator.go +++ b/rete/internal/redis/rjointablerowiterator.go @@ -12,7 +12,7 @@ type rowIteratorImpl struct { } func newRowIterator(jTable types.JoinTable) types.RowIterator { - key := jTable.GetNw().GetPrefix() + "jt:" + jTable.GetName() + key := jTable.GetNw().GetPrefix() + ":jt:" + jTable.GetName() ri := rowIteratorImpl{} ri.iter = redisutils.GetRedisHdl().GetMapIterator(key) ri.nw = jTable.GetNw() From 1e034b36ae9b5c26c9ec3b72c2bcad1c11e38e6a Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Fri, 4 Jan 2019 16:09:03 +0530 Subject: [PATCH 038/125] wip mem impl for hdlrefs --- examples/rulesapp/main.go | 11 +++---- examples/rulesapp/rsconfig.json | 6 ++-- redisutils/redisutils.go | 8 +++++ rete/internal/mem/mjtrefsservice.go | 42 +++++++++++++++++++++++++++ rete/internal/redis/rjtrefsservice.go | 23 +++++++++++++++ rete/internal/redis/rjtservice.go | 11 ------- rete/internal/types/types.go | 6 ++++ rete/network.go | 24 +++++++-------- 8 files changed, 98 insertions(+), 33 deletions(-) diff --git a/examples/rulesapp/main.go b/examples/rulesapp/main.go index 0e12c29..4ac89d6 100644 --- a/examples/rulesapp/main.go +++ b/examples/rulesapp/main.go @@ -55,7 +55,7 @@ func main() { fmt.Println("Asserting n1 tuple with name=Tom") t1, _ := model.NewTupleWithKeyValues("n1", "Tom") t1.SetString(nil, "name", "Tom") - //err = rs.Assert(nil, t1) + err = rs.Assert(nil, t1) if err != nil { fmt.Printf("Warn: [%s]\n", err) } @@ -64,7 +64,7 @@ func main() { fmt.Println("Asserting n1 tuple with name=Bob") t2, _ := model.NewTupleWithKeyValues("n1", "Bob") t2.SetString(nil, "name", "Bob") - //err = rs.Assert(nil, t2) + err = rs.Assert(nil, t2) if err != nil { fmt.Printf("Warn: [%s]\n", err) } @@ -79,15 +79,16 @@ func main() { } //Retract tuples - //rs.Delete(nil, t1) - //rs.Delete(nil, t2) - //rs.Delete(nil, t3) + rs.Retract(nil, t1) + rs.Retract(nil, t2) + rs.Retract(nil, t3) //delete the rule rs.DeleteRule(rule.GetName()) //unregister the session, i.e; cleanup rs.Unregister() + } func checkForBob(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { diff --git a/examples/rulesapp/rsconfig.json b/examples/rulesapp/rsconfig.json index 0251bdf..189ca95 100644 --- a/examples/rulesapp/rsconfig.json +++ b/examples/rulesapp/rsconfig.json @@ -1,11 +1,11 @@ { "rs" : { "prefix" : "x", - "store-ref" : "redis" + "store-ref" : "mem" }, "rete": { - "jt": "redis", - "idgen" : "redis" + "jt": "mem", + "idgen" : "mem" }, "store": [ { diff --git a/redisutils/redisutils.go b/redisutils/redisutils.go index fc040cf..c3df1ce 100644 --- a/redisutils/redisutils.go +++ b/redisutils/redisutils.go @@ -315,3 +315,11 @@ func (rh *RedisHandle) GetMapIterator(key string) *MapIterator { return &iter } + +func Shutdown() { + if rd != nil { + + rd.pool.Close() + rd = nil + } +} diff --git a/rete/internal/mem/mjtrefsservice.go b/rete/internal/mem/mjtrefsservice.go index ed0f2f9..782231c 100644 --- a/rete/internal/mem/mjtrefsservice.go +++ b/rete/internal/mem/mjtrefsservice.go @@ -88,3 +88,45 @@ func (ri *hdlTblIteratorImpl) Next() (string, map[int]int) { ri.curr = ri.curr.Next() return id, lst } + +type RowIDIteratorImpl struct { + jtName string + rowIdMap map[int]int + kList list.List + curr *list.Element + nw types.Network +} + +func (ri *RowIDIteratorImpl) HasNext() bool { + return ri.curr != nil +} + +func (ri *RowIDIteratorImpl) Next() types.JoinTableRow { + rowID := ri.curr.Value.(int) + var jtRow types.JoinTableRow + jT := ri.nw.GetJtService().GetJoinTable(ri.jtName) + if jT != nil { + jtRow = jT.GetRow(rowID) + } + ri.curr = ri.curr.Next() + return jtRow +} + +func (h *jtRefsServiceImpl) GetRowIterator(handle types.ReteHandle, jtName string) types.RowIterator { + ri := RowIDIteratorImpl{} + ri.jtName = jtName + ri.kList = list.List{} + ri.nw = h.Nw + tblMap := h.tablesAndRows[handle.GetTupleKey().String()] + if tblMap != nil { + rowMap := tblMap[jtName] + if rowMap != nil { + ri.rowIdMap = rowMap + for k, _ := range ri.rowIdMap { + ri.kList.PushBack(k) + } + } + } + ri.curr = ri.kList.Front() + return &ri +} diff --git a/rete/internal/redis/rjtrefsservice.go b/rete/internal/redis/rjtrefsservice.go index fac5232..bc9b271 100644 --- a/rete/internal/redis/rjtrefsservice.go +++ b/rete/internal/redis/rjtrefsservice.go @@ -2,6 +2,7 @@ package redis import ( "container/list" + "github.com/project-flogo/rules/redisutils" "github.com/project-flogo/rules/rete/internal/types" ) @@ -89,3 +90,25 @@ func (ri *hdlTblIteratorImpl) Next() (string, map[int]int) { ri.curr = ri.curr.Next() return id, lst } + +type RowIDIteratorImpl struct { + key string + iter *redisutils.MapIterator +} + +func (r *RowIDIteratorImpl) HasNext() bool { + return false +} + +func (r *RowIDIteratorImpl) Next() types.JoinTableRow { + return nil +} + +func (h *jtRefsServiceImpl) GetRowIterator(handle types.ReteHandle, jtName string) types.RowIterator { + r := RowIDIteratorImpl{} + //ex: a:rf:n1:a:b1:L_tbl + r.key = h.Nw.GetPrefix() + ":rf:" + handle.GetTupleKey().String() + ":" + jtName + hdl := redisutils.GetRedisHdl() + r.iter = hdl.GetMapIterator(r.key) + return &r +} diff --git a/rete/internal/redis/rjtservice.go b/rete/internal/redis/rjtservice.go index e5d1a74..e4e753f 100644 --- a/rete/internal/redis/rjtservice.go +++ b/rete/internal/redis/rjtservice.go @@ -7,16 +7,13 @@ import ( type jtServiceImpl struct { types.NwServiceImpl - allJoinTables map[string]types.JoinTable - prefix string } func NewJoinTableCollection(nw types.Network, config map[string]interface{}) types.JtService { jtc := jtServiceImpl{} jtc.Nw = nw jtc.allJoinTables = make(map[string]types.JoinTable) - jtc.prefix = nw.GetPrefix() + "jtc:" return &jtc } func (jtc *jtServiceImpl) Init() { @@ -35,11 +32,3 @@ func (jtc *jtServiceImpl) GetOrCreateJoinTable(nw types.Network, rule model.Rule } return jT } - -//func (jtc *jtServiceImpl) AddJoinTable(joinTable types.JoinTable) { -// jtc.allJoinTables[joinTable.GetName()] = joinTable -//} -// -//func (jtc *jtServiceImpl) RemoveJoinTable(jtName string) { -// delete(jtc.allJoinTables, jtName) -//} diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index 6849fcf..caaabc4 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -65,6 +65,11 @@ type RowIterator interface { Next() JoinTableRow } +type RowIDIterator interface { + HasNext() bool + Next() JoinTableRow +} + type NwService interface { model.Service GetNw() Network @@ -76,6 +81,7 @@ type JtRefsService interface { RemoveRowEntry(handle ReteHandle, jtName string, rowID int) RemoveEntry(handle ReteHandle, jtName string) GetIterator(handle ReteHandle) HdlTblIterator + GetRowIterator(handle ReteHandle, jtName string) RowIterator } type HdlTblIterator interface { diff --git a/rete/network.go b/rete/network.go index 929d41b..d8aabce 100644 --- a/rete/network.go +++ b/rete/network.go @@ -16,7 +16,6 @@ import ( ) type reteNetworkImpl struct { - //unique name of the network. used for namespacing in storage, etc prefix string @@ -686,7 +685,7 @@ func (nw *reteNetworkImpl) removeJoinTableRowRefs(hdl types.ReteHandle, changedP hdlTblIter := nw.jtRefsService.GetIterator(hdl) for hdlTblIter.HasNext() { - jtName, rowIDs := hdlTblIter.Next() + jtName, _ := hdlTblIter.Next() joinTable := nw.jtService.GetJoinTable(jtName) if joinTable == nil { continue @@ -711,21 +710,18 @@ func (nw *reteNetworkImpl) removeJoinTableRowRefs(hdl types.ReteHandle, changedP if !toDelete { continue } - //this can happen if some other handle removed a row as a result of retraction - if rowIDs == nil { - continue - } + + rowIter := nw.jtRefsService.GetRowIterator(hdl, jtName) + ////Remove rows from corresponding join tables - for rowID, _ := range rowIDs { - row := joinTable.RemoveRow(rowID) - if row != nil { - //Remove this (table+row) link from other handle refs of this row! - for _, otherHdl := range row.GetHandles() { - if otherHdl.GetTupleKey().String() != hdl.GetTupleKey().String() { - nw.jtRefsService.RemoveRowEntry(otherHdl, jtName, rowID) - } + for rowIter.HasNext() { + row := rowIter.Next() + for _, otherHdl := range row.GetHandles() { + if otherHdl.GetTupleKey().String() != hdl.GetTupleKey().String() { + nw.jtRefsService.RemoveRowEntry(otherHdl, jtName, row.GetID()) } } + } } } From 7d523f168bc1209984b4ed092fcf012a09277970 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Fri, 4 Jan 2019 21:49:07 +0530 Subject: [PATCH 039/125] wip redis impl for hdlrefs --- examples/rulesapp/rsconfig.json | 6 +- rete/internal/mem/mjtrefsservice.go | 10 ++- rete/internal/redis/rjtrefsservice.go | 110 ++++++++++++++------------ rete/internal/types/types.go | 2 +- rete/network.go | 7 +- 5 files changed, 71 insertions(+), 64 deletions(-) diff --git a/examples/rulesapp/rsconfig.json b/examples/rulesapp/rsconfig.json index 189ca95..0251bdf 100644 --- a/examples/rulesapp/rsconfig.json +++ b/examples/rulesapp/rsconfig.json @@ -1,11 +1,11 @@ { "rs" : { "prefix" : "x", - "store-ref" : "mem" + "store-ref" : "redis" }, "rete": { - "jt": "mem", - "idgen" : "mem" + "jt": "redis", + "idgen" : "redis" }, "store": [ { diff --git a/rete/internal/mem/mjtrefsservice.go b/rete/internal/mem/mjtrefsservice.go index 782231c..9ca61df 100644 --- a/rete/internal/mem/mjtrefsservice.go +++ b/rete/internal/mem/mjtrefsservice.go @@ -58,6 +58,7 @@ func (h *jtRefsServiceImpl) RemoveRowEntry(handle types.ReteHandle, jtName strin func (h *jtRefsServiceImpl) GetIterator(handle types.ReteHandle) types.HdlTblIterator { ri := hdlTblIteratorImpl{} + ri.nw = h.Nw //ri.hdlJtImpl = h ri.kList = list.List{} @@ -76,17 +77,18 @@ type hdlTblIteratorImpl struct { tblMap map[string]map[int]int kList list.List curr *list.Element + nw types.Network } func (ri *hdlTblIteratorImpl) HasNext() bool { return ri.curr != nil } -func (ri *hdlTblIteratorImpl) Next() (string, map[int]int) { - id := ri.curr.Value.(string) - lst := ri.tblMap[id] +func (ri *hdlTblIteratorImpl) Next() types.JoinTable { + jtName := ri.curr.Value.(string) + jT := ri.nw.GetJtService().GetJoinTable(jtName) ri.curr = ri.curr.Next() - return id, lst + return jT } type RowIDIteratorImpl struct { diff --git a/rete/internal/redis/rjtrefsservice.go b/rete/internal/redis/rjtrefsservice.go index bc9b271..b9247fe 100644 --- a/rete/internal/redis/rjtrefsservice.go +++ b/rete/internal/redis/rjtrefsservice.go @@ -1,22 +1,22 @@ package redis import ( - "container/list" "github.com/project-flogo/rules/redisutils" "github.com/project-flogo/rules/rete/internal/types" + "strconv" ) type jtRefsServiceImpl struct { //keys are jointable-ids and values are lists of row-ids in the corresponding join table types.NwServiceImpl - tablesAndRows map[string]map[string]map[int]int + //tablesAndRows map[string]map[string]map[int]int } func NewJoinTableRefsInHdlImpl(nw types.Network, config map[string]interface{}) types.JtRefsService { hdlJt := jtRefsServiceImpl{} hdlJt.Nw = nw - hdlJt.tablesAndRows = make(map[string]map[string]map[int]int) + //hdlJt.tablesAndRows = make(map[string]map[string]map[int]int) return &hdlJt } @@ -26,88 +26,94 @@ func (h *jtRefsServiceImpl) Init() { func (h *jtRefsServiceImpl) AddEntry(handle types.ReteHandle, jtName string, rowID int) { - tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] + //format: prefix:rtbls:tkey ==> {jtname=jtname, ...} + //format: prefix:rrows:tkey:jtname ==> {rowid=rowid, ...} - if !found { - tblMap = make(map[string]map[int]int) - h.tablesAndRows[handle.GetTupleKey().String()] = tblMap - } - - rowsForJoinTable, found := tblMap[jtName] - if !found { - rowsForJoinTable = make(map[int]int) - tblMap[jtName] = rowsForJoinTable - } - rowsForJoinTable[rowID] = rowID + key := h.Nw.GetPrefix() + ":rtbls:" + handle.GetTupleKey().String() + hdl := redisutils.GetRedisHdl() + valMap := make(map[string]interface{}) + valMap[jtName] = jtName + hdl.HSetAll(key, valMap) + + rkey := h.Nw.GetPrefix() + ":rrows:" + handle.GetTupleKey().String() + ":" + jtName + rowMap := make(map[string]interface{}) + rowIdStr := strconv.Itoa(rowID) + rowMap[rowIdStr] = rowIdStr + hdl.HSetAll(rkey, rowMap) } func (h *jtRefsServiceImpl) RemoveEntry(handle types.ReteHandle, jtName string) { - tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] - if found { - delete(tblMap, jtName) - } + key := h.Nw.GetPrefix() + ":rtbls:" + handle.GetTupleKey().String() + hdl := redisutils.GetRedisHdl() + hdl.HDel(key, jtName) + + rkey := h.Nw.GetPrefix() + ":rrows:" + handle.GetTupleKey().String() + ":" + jtName + hdl.Del(rkey) + } func (h *jtRefsServiceImpl) RemoveRowEntry(handle types.ReteHandle, jtName string, rowID int) { - tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] - if found { - rowIDs, fnd := tblMap[jtName] - if fnd { - delete(rowIDs, rowID) - } - } + rowKey := h.Nw.GetPrefix() + ":rrows:" + handle.GetTupleKey().String() + ":" + jtName + hdl := redisutils.GetRedisHdl() + rowIdStr := strconv.Itoa(rowID) + hdl.HDel(rowKey, rowIdStr) + + //hkey := h.Nw.GetPrefix() + ":rtbls:" + handle.GetTupleKey().String() + //hdl.HDel(hkey, jtName) } func (h *jtRefsServiceImpl) GetIterator(handle types.ReteHandle) types.HdlTblIterator { ri := hdlTblIteratorImpl{} - //ri.hdlJtImpl = h - ri.kList = list.List{} - - tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] - if found { - ri.tblMap = tblMap - for k, _ := range tblMap { - ri.kList.PushBack(k) - } - } - ri.curr = ri.kList.Front() + //format: prefix:rtbls:tkey ==> {jtname=jtname, ...} + key := h.Nw.GetPrefix() + ":rtbls:" + handle.GetTupleKey().String() + hdl := redisutils.GetRedisHdl() + ri.iter = hdl.GetMapIterator(key) return &ri } type hdlTblIteratorImpl struct { - tblMap map[string]map[int]int - kList list.List - curr *list.Element + iter *redisutils.MapIterator + nw types.Network } func (ri *hdlTblIteratorImpl) HasNext() bool { - return ri.curr != nil + return ri.iter.HasNext() } -func (ri *hdlTblIteratorImpl) Next() (string, map[int]int) { - id := ri.curr.Value.(string) - lst := ri.tblMap[id] - ri.curr = ri.curr.Next() - return id, lst +func (ri *hdlTblIteratorImpl) Next() types.JoinTable { + jtName, _ := ri.iter.Next() + jT := ri.nw.GetJtService().GetJoinTable(jtName) + return jT } type RowIDIteratorImpl struct { - key string - iter *redisutils.MapIterator + key string + iter *redisutils.MapIterator + nw types.Network + jtName string } func (r *RowIDIteratorImpl) HasNext() bool { - return false + return r.iter.HasNext() } func (r *RowIDIteratorImpl) Next() types.JoinTableRow { - return nil + rowIdStr, _ := r.iter.Next() + rowID, _ := strconv.Atoi(rowIdStr) + jT := r.nw.GetJtService().GetJoinTable(r.jtName) + row := jT.GetRow(rowID) + return row } +//format: prefix:rtbls:tkey ==> {jtname=jtname, ...} +//format: prefix:rrows:tkey:jtname ==> {rowid=rowid, ...} + func (h *jtRefsServiceImpl) GetRowIterator(handle types.ReteHandle, jtName string) types.RowIterator { r := RowIDIteratorImpl{} - //ex: a:rf:n1:a:b1:L_tbl - r.key = h.Nw.GetPrefix() + ":rf:" + handle.GetTupleKey().String() + ":" + jtName + r.nw = h.Nw + r.jtName = jtName + //ex: a:rrows:n1:a:b1:L_tbl + r.key = h.Nw.GetPrefix() + ":rrows:" + handle.GetTupleKey().String() + ":" + jtName hdl := redisutils.GetRedisHdl() r.iter = hdl.GetMapIterator(r.key) return &r diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index caaabc4..3fdc55e 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -86,7 +86,7 @@ type JtRefsService interface { type HdlTblIterator interface { HasNext() bool - Next() (string, map[int]int) + Next() JoinTable } type JtService interface { diff --git a/rete/network.go b/rete/network.go index d8aabce..5d47eb6 100644 --- a/rete/network.go +++ b/rete/network.go @@ -685,8 +685,7 @@ func (nw *reteNetworkImpl) removeJoinTableRowRefs(hdl types.ReteHandle, changedP hdlTblIter := nw.jtRefsService.GetIterator(hdl) for hdlTblIter.HasNext() { - jtName, _ := hdlTblIter.Next() - joinTable := nw.jtService.GetJoinTable(jtName) + joinTable := hdlTblIter.Next() if joinTable == nil { continue } @@ -711,14 +710,14 @@ func (nw *reteNetworkImpl) removeJoinTableRowRefs(hdl types.ReteHandle, changedP continue } - rowIter := nw.jtRefsService.GetRowIterator(hdl, jtName) + rowIter := nw.jtRefsService.GetRowIterator(hdl, joinTable.GetName()) ////Remove rows from corresponding join tables for rowIter.HasNext() { row := rowIter.Next() for _, otherHdl := range row.GetHandles() { if otherHdl.GetTupleKey().String() != hdl.GetTupleKey().String() { - nw.jtRefsService.RemoveRowEntry(otherHdl, jtName, row.GetID()) + nw.jtRefsService.RemoveRowEntry(otherHdl, joinTable.GetName(), row.GetID()) } } From 02c5913dce92051d52f76e7bbf9baae1b1638f3a Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Fri, 4 Jan 2019 22:30:37 +0530 Subject: [PATCH 040/125] wip redis impl for hdlrefs --- rete/internal/redis/rjtrefsservice.go | 1 + rete/network.go | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/rete/internal/redis/rjtrefsservice.go b/rete/internal/redis/rjtrefsservice.go index b9247fe..95e7a8c 100644 --- a/rete/internal/redis/rjtrefsservice.go +++ b/rete/internal/redis/rjtrefsservice.go @@ -64,6 +64,7 @@ func (h *jtRefsServiceImpl) RemoveRowEntry(handle types.ReteHandle, jtName strin func (h *jtRefsServiceImpl) GetIterator(handle types.ReteHandle) types.HdlTblIterator { ri := hdlTblIteratorImpl{} + ri.nw = h.Nw //format: prefix:rtbls:tkey ==> {jtname=jtname, ...} key := h.Nw.GetPrefix() + ":rtbls:" + handle.GetTupleKey().String() hdl := redisutils.GetRedisHdl() diff --git a/rete/network.go b/rete/network.go index 5d47eb6..fc9d8e0 100644 --- a/rete/network.go +++ b/rete/network.go @@ -716,9 +716,9 @@ func (nw *reteNetworkImpl) removeJoinTableRowRefs(hdl types.ReteHandle, changedP for rowIter.HasNext() { row := rowIter.Next() for _, otherHdl := range row.GetHandles() { - if otherHdl.GetTupleKey().String() != hdl.GetTupleKey().String() { + //if otherHdl.GetTupleKey().String() != hdl.GetTupleKey().String() { nw.jtRefsService.RemoveRowEntry(otherHdl, joinTable.GetName(), row.GetID()) - } + //} } } From f8334169b8a743b0e5f19e7c9bd218ccdf224c78 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Sat, 5 Jan 2019 10:15:50 +0530 Subject: [PATCH 041/125] wip redis impl for hdlrefs --- examples/rulesapp/main.go | 8 ++--- redisutils/redisutil_test.go | 31 ++++++++++---------- redisutils/redisutils.go | 12 ++++++-- rete/internal/mem/mjointablerowiterator.go | 9 ++++-- rete/internal/mem/mjtrefsservice.go | 15 ++++++++-- rete/internal/redis/rhandleservice.go | 7 ++--- rete/internal/redis/rjointablerowimpl.go | 5 ++-- rete/internal/redis/rjointablerowiterator.go | 8 ++++- rete/internal/redis/rjtrefsservice.go | 6 ++++ rete/internal/types/types.go | 2 ++ rete/network.go | 11 +++---- 11 files changed, 74 insertions(+), 40 deletions(-) diff --git a/examples/rulesapp/main.go b/examples/rulesapp/main.go index 4ac89d6..d9a9cf7 100644 --- a/examples/rulesapp/main.go +++ b/examples/rulesapp/main.go @@ -79,12 +79,12 @@ func main() { } //Retract tuples - rs.Retract(nil, t1) - rs.Retract(nil, t2) - rs.Retract(nil, t3) + //rs.Retract(nil, t1) + rs.Delete(nil, t2) + rs.Delete(nil, t3) //delete the rule - rs.DeleteRule(rule.GetName()) + //rs.DeleteRule(rule.GetName()) //unregister the session, i.e; cleanup rs.Unregister() diff --git a/redisutils/redisutil_test.go b/redisutils/redisutil_test.go index 5c9ad14..b7a538b 100644 --- a/redisutils/redisutil_test.go +++ b/redisutils/redisutil_test.go @@ -158,28 +158,29 @@ func Test_three(t *testing.T) { hdl := GetRedisHdl() - iter := hdl.GetListIterator("x:jt:L_c2") - - for iter.HasNext() { - key := iter.Next() - fmt.Printf("KEY: [%s]\n", key) - } + //iter := hdl.GetListIterator("x:jt:L_c2") + // + //for iter.HasNext() { + // key := iter.Next() + // fmt.Printf("KEY: [%s]\n", key) + //} miter := hdl.GetMapIterator("a") for miter.HasNext() { key, value := miter.Next() fmt.Printf("KEY: [%s], Value=[%s]\n", key, value) + miter.Remove() } - { - - miter := hdl.GetMapIterator("x:jt:L_c2") - - for miter.HasNext() { - key, value := miter.Next() - fmt.Printf("KEY: [%s], Value=[%s]\n", key, value) - } - } + //{ + // + // miter := hdl.GetMapIterator("a") + // + // for miter.HasNext() { + // key, value := miter.Next() + // fmt.Printf("KEY: [%s], Value=[%s]\n", key, value) + // } + //} } func Test_four(t *testing.T) { diff --git a/redisutils/redisutils.go b/redisutils/redisutils.go index c3df1ce..3d1c1ba 100644 --- a/redisutils/redisutils.go +++ b/redisutils/redisutils.go @@ -257,6 +257,8 @@ func (rh *RedisHandle) GetListIterator(key string) *LIterator { type MapIterator struct { LIterator + currKey string + currValue interface{} } func (iter *MapIterator) HasNext() bool { @@ -287,13 +289,17 @@ func (iter *MapIterator) HasNext() bool { return true } +func (iter *MapIterator) Remove() { + hdl := GetRedisHdl() + hdl.HDel(iter.key, iter.currKey) +} func (iter *MapIterator) Next() (string, interface{}) { - str := iter.keys[iter.keyIdx] - value := iter.keys[iter.keyIdx+1] + iter.currKey = iter.keys[iter.keyIdx] + iter.currValue = iter.keys[iter.keyIdx+1] iter.keyIdx += 2 - return str, value + return iter.currKey, iter.currValue } func (rh *RedisHandle) GetMapIterator(key string) *MapIterator { diff --git a/rete/internal/mem/mjointablerowiterator.go b/rete/internal/mem/mjointablerowiterator.go index be4271b..e193e44 100644 --- a/rete/internal/mem/mjointablerowiterator.go +++ b/rete/internal/mem/mjointablerowiterator.go @@ -8,6 +8,7 @@ import ( type rowIteratorImpl struct { table map[int]types.JoinTableRow kList list.List + currKey int curr *list.Element } @@ -27,8 +28,12 @@ func (ri *rowIteratorImpl) HasNext() bool { } func (ri *rowIteratorImpl) Next() types.JoinTableRow { - id := ri.curr.Value.(int) - val := ri.table[id] + ri.currKey = ri.curr.Value.(int) + val := ri.table[ri.currKey] ri.curr = ri.curr.Next() return val } + +func (ri *rowIteratorImpl) Remove() { + delete (ri.table, ri.currKey) +} diff --git a/rete/internal/mem/mjtrefsservice.go b/rete/internal/mem/mjtrefsservice.go index 9ca61df..a9e0377 100644 --- a/rete/internal/mem/mjtrefsservice.go +++ b/rete/internal/mem/mjtrefsservice.go @@ -76,6 +76,7 @@ func (h *jtRefsServiceImpl) GetIterator(handle types.ReteHandle) types.HdlTblIte type hdlTblIteratorImpl struct { tblMap map[string]map[int]int kList list.List + currJtName string curr *list.Element nw types.Network } @@ -85,17 +86,22 @@ func (ri *hdlTblIteratorImpl) HasNext() bool { } func (ri *hdlTblIteratorImpl) Next() types.JoinTable { - jtName := ri.curr.Value.(string) - jT := ri.nw.GetJtService().GetJoinTable(jtName) + ri.currJtName = ri.curr.Value.(string) + jT := ri.nw.GetJtService().GetJoinTable(ri.currJtName) ri.curr = ri.curr.Next() return jT } +func (ri *hdlTblIteratorImpl) Remove() { + delete(ri.tblMap, ri.currJtName) +} + type RowIDIteratorImpl struct { jtName string rowIdMap map[int]int kList list.List curr *list.Element + currRowId int nw types.Network } @@ -105,6 +111,7 @@ func (ri *RowIDIteratorImpl) HasNext() bool { func (ri *RowIDIteratorImpl) Next() types.JoinTableRow { rowID := ri.curr.Value.(int) + ri.currRowId = rowID var jtRow types.JoinTableRow jT := ri.nw.GetJtService().GetJoinTable(ri.jtName) if jT != nil { @@ -114,6 +121,10 @@ func (ri *RowIDIteratorImpl) Next() types.JoinTableRow { return jtRow } +func (ri *RowIDIteratorImpl) Remove() { + delete (ri.rowIdMap, ri.currRowId) +} + func (h *jtRefsServiceImpl) GetRowIterator(handle types.ReteHandle, jtName string) types.RowIterator { ri := RowIDIteratorImpl{} ri.jtName = jtName diff --git a/rete/internal/redis/rhandleservice.go b/rete/internal/redis/rhandleservice.go index 72e1272..087e7d3 100644 --- a/rete/internal/redis/rhandleservice.go +++ b/rete/internal/redis/rhandleservice.go @@ -1,7 +1,6 @@ package redis import ( - "fmt" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/redisutils" "github.com/project-flogo/rules/rete/internal/types" @@ -32,8 +31,8 @@ func (hc *handleServiceImpl) Init() { func (hc *handleServiceImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle { rkey := hc.prefix + tuple.GetKey().String() - numDeleted := redisutils.GetRedisHdl().Del(rkey) - fmt.Printf("Deleted: [%d] keys\n", numDeleted) + redisutils.GetRedisHdl().Del(rkey) + //fmt.Printf("Deleted: [%d] keys\n", numDeleted) //TODO: Dummy handle h := newReteHandleImpl(hc.GetNw(), tuple) @@ -74,7 +73,5 @@ func (hc *handleServiceImpl) GetOrCreateHandle(nw types.Network, tuple model.Tup } h := newReteHandleImpl(nw, tuple) - //hc.allHandles[tuple.GetKey().String()] = h - return h } diff --git a/rete/internal/redis/rjointablerowimpl.go b/rete/internal/redis/rjointablerowimpl.go index 1880b52..bed501b 100644 --- a/rete/internal/redis/rjointablerowimpl.go +++ b/rete/internal/redis/rjointablerowimpl.go @@ -1,7 +1,6 @@ package redis import ( - "fmt" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/redisutils" "github.com/project-flogo/rules/rete/internal/types" @@ -66,8 +65,8 @@ func createRow(jtKey string, rowID string, key string, nw types.Network) types.J for _, key := range values { tupleKey := model.FromStringKey(key) tuple := nw.GetTupleStore().GetTupleByKey(tupleKey) - ks := tupleKey.String() - fmt.Printf(ks) + //ks := tupleKey.String() + //fmt.Printf(ks) handle := newReteHandleImpl(nw, tuple) handles = append(handles, handle) } diff --git a/rete/internal/redis/rjointablerowiterator.go b/rete/internal/redis/rjointablerowiterator.go index 556752f..914c5fb 100644 --- a/rete/internal/redis/rjointablerowiterator.go +++ b/rete/internal/redis/rjointablerowiterator.go @@ -9,6 +9,7 @@ type rowIteratorImpl struct { iter *redisutils.MapIterator jtName string nw types.Network + curr types.JoinTableRow } func newRowIterator(jTable types.JoinTable) types.RowIterator { @@ -27,5 +28,10 @@ func (ri *rowIteratorImpl) HasNext() bool { func (ri *rowIteratorImpl) Next() types.JoinTableRow { rowId, key := ri.iter.Next() tupleKeyStr := key.(string) - return createRow(ri.jtName, rowId, tupleKeyStr, ri.nw) + ri.curr = createRow(ri.jtName, rowId, tupleKeyStr, ri.nw) + return ri.curr +} + +func (ri *rowIteratorImpl) Remove() { + ri.iter.Remove() } diff --git a/rete/internal/redis/rjtrefsservice.go b/rete/internal/redis/rjtrefsservice.go index 95e7a8c..d289091 100644 --- a/rete/internal/redis/rjtrefsservice.go +++ b/rete/internal/redis/rjtrefsservice.go @@ -86,6 +86,9 @@ func (ri *hdlTblIteratorImpl) Next() types.JoinTable { jT := ri.nw.GetJtService().GetJoinTable(jtName) return jT } +func (ri *hdlTblIteratorImpl) Remove() { + ri.iter.Remove() +} type RowIDIteratorImpl struct { key string @@ -106,6 +109,9 @@ func (r *RowIDIteratorImpl) Next() types.JoinTableRow { return row } +func (r *RowIDIteratorImpl) Remove() { + r.iter.Remove() +} //format: prefix:rtbls:tkey ==> {jtname=jtname, ...} //format: prefix:rrows:tkey:jtname ==> {rowid=rowid, ...} diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index 3fdc55e..ed0bc51 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -63,6 +63,7 @@ type ReteHandle interface { type RowIterator interface { HasNext() bool Next() JoinTableRow + Remove() // remove underneath current element } type RowIDIterator interface { @@ -87,6 +88,7 @@ type JtRefsService interface { type HdlTblIterator interface { HasNext() bool Next() JoinTable + Remove() } type JtService interface { diff --git a/rete/network.go b/rete/network.go index fc9d8e0..afd7936 100644 --- a/rete/network.go +++ b/rete/network.go @@ -662,10 +662,6 @@ func (nw *reteNetworkImpl) getFactory() *TypeFactory { return nw.factory } -//func (nw *reteNetworkImpl) AddToAllJoinTables(joinTable types.JoinTable) { -// nw.jtService.AddJoinTable(joinTable) -//} - func (nw *reteNetworkImpl) SetTupleStore(tupleStore model.TupleStore) { nw.tupleStore = tupleStore } @@ -715,14 +711,19 @@ func (nw *reteNetworkImpl) removeJoinTableRowRefs(hdl types.ReteHandle, changedP ////Remove rows from corresponding join tables for rowIter.HasNext() { row := rowIter.Next() + //first, from jTable, remove row + joinTable.RemoveRow(row.GetID()) for _, otherHdl := range row.GetHandles() { //if otherHdl.GetTupleKey().String() != hdl.GetTupleKey().String() { nw.jtRefsService.RemoveRowEntry(otherHdl, joinTable.GetName(), row.GetID()) //} } - + //TODO: Delete the rowRef itself + rowIter.Remove() } } + //TODO: Remove the current entry from underneath + hdlTblIter.Remove() } func (nw *reteNetworkImpl) GetIdGenService() types.IdGen { From 3ab94e91bef0a60b3aa2da1d447cbab2ddb1bf00 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Sat, 5 Jan 2019 10:23:51 +0530 Subject: [PATCH 042/125] wip redis impl for hdlrefs --- examples/rulesapp/main.go | 4 ++-- ruleapi/internal/store/redis/rstore.go | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/rulesapp/main.go b/examples/rulesapp/main.go index d9a9cf7..d984bcc 100644 --- a/examples/rulesapp/main.go +++ b/examples/rulesapp/main.go @@ -79,12 +79,12 @@ func main() { } //Retract tuples - //rs.Retract(nil, t1) + rs.Delete(nil, t1) rs.Delete(nil, t2) rs.Delete(nil, t3) //delete the rule - //rs.DeleteRule(rule.GetName()) + rs.DeleteRule(rule.GetName()) //unregister the session, i.e; cleanup rs.Unregister() diff --git a/ruleapi/internal/store/redis/rstore.go b/ruleapi/internal/store/redis/rstore.go index dad21e5..089b44d 100644 --- a/ruleapi/internal/store/redis/rstore.go +++ b/ruleapi/internal/store/redis/rstore.go @@ -59,7 +59,7 @@ func (ms *storeImpl) SaveTuples(added map[string]map[string]model.Tuple) { hdl := redisutils.GetRedisHdl() for tupleType, tuples := range added { for key, tuple := range tuples { - fmt.Printf("Saving tuple. Type [%s] Key [%s], Val [%v]\n", tupleType, key, tuple) + fmt.Printf("Saving tuple. Type [%s] Key [%s]\n", tupleType, key) strKey := ms.prefix + key hdl.HSetAll(strKey, tuple.ToMap()) } @@ -70,7 +70,7 @@ func (ms *storeImpl) SaveModifiedTuples(modified map[string]map[string]model.Rtc hdl := redisutils.GetRedisHdl() for tupleType, mmap := range modified { for key, mdfd := range mmap { - fmt.Printf("Saving tuple. Type [%s] Key [%s], Val [%v]\n", tupleType, key, mdfd.GetTuple()) + fmt.Printf("Saving tuple. Type [%s] Key [%s]\n", tupleType, key) strKey := ms.prefix + key hdl.HSetAll(strKey, mdfd.GetTuple().ToMap()) } @@ -80,8 +80,8 @@ func (ms *storeImpl) SaveModifiedTuples(modified map[string]map[string]model.Rtc func (ms *storeImpl) DeleteTuples(deleted map[string]map[string]model.Tuple) { hdl := redisutils.GetRedisHdl() for tupleType, tuples := range deleted { - for key, tuple := range tuples { - fmt.Printf("Deleting tuple. Type [%s] Key [%s], Val [%v]\n", tupleType, key, tuple) + for key, _ := range tuples { + fmt.Printf("Deleting tuple. Type [%s] Key [%s]\n", tupleType, key) strKey := ms.prefix + key hdl.Del(strKey) } From 6e0f10aedcbfbcb1b772e57651f38be767eb2d20 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Mon, 7 Jan 2019 12:30:14 +0530 Subject: [PATCH 043/125] rule delete impl --- examples/rulesapp/main.go | 8 ++-- redisutils/redisutil_test.go | 14 +++++++ redisutils/redisutils.go | 4 +- rete/internal/mem/mjointableimpl.go | 14 +++++++ rete/internal/mem/mjointablerowiterator.go | 8 ++-- rete/internal/mem/mjtrefsservice.go | 27 ++++++++----- rete/internal/redis/rjointableimpl.go | 14 +++++++ rete/internal/redis/rjointablerowiterator.go | 2 +- rete/internal/redis/rjtrefsservice.go | 13 +++++++ rete/internal/types/types.go | 2 + rete/network.go | 41 +++++++++++--------- 11 files changed, 108 insertions(+), 39 deletions(-) diff --git a/examples/rulesapp/main.go b/examples/rulesapp/main.go index d984bcc..94623b9 100644 --- a/examples/rulesapp/main.go +++ b/examples/rulesapp/main.go @@ -79,12 +79,12 @@ func main() { } //Retract tuples - rs.Delete(nil, t1) - rs.Delete(nil, t2) - rs.Delete(nil, t3) + rs.Retract(nil, t1) + rs.Retract(nil, t2) + rs.Retract(nil, t3) //delete the rule - rs.DeleteRule(rule.GetName()) + rs.DeleteRule(rule2.GetName()) //unregister the session, i.e; cleanup rs.Unregister() diff --git a/redisutils/redisutil_test.go b/redisutils/redisutil_test.go index b7a538b..adb7772 100644 --- a/redisutils/redisutil_test.go +++ b/redisutils/redisutil_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "github.com/gomodule/redigo/redis" + "strconv" "testing" ) @@ -193,3 +194,16 @@ func Test_four(t *testing.T) { fmt.Printf("[%d]\n", len) } + +func Test_five(t *testing.T) { + InitService(nil) + + hdl := GetRedisHdl() + + for i := 0; i < 10; i++ { + m := make(map[string]interface{}) + m[""+strconv.Itoa(i)] = i + hdl.HSetAll("x", m) + } + +} diff --git a/redisutils/redisutils.go b/redisutils/redisutils.go index 3d1c1ba..bd89803 100644 --- a/redisutils/redisutils.go +++ b/redisutils/redisutils.go @@ -196,7 +196,7 @@ type LIterator struct { keyIdx int //local array current index keys []string //array of keys in the current call valueMap map[string]interface{} //map of key/value of the current keys - rh RedisHdl + rh *RedisHandle } func (iter *LIterator) HasNext() bool { @@ -257,7 +257,7 @@ func (rh *RedisHandle) GetListIterator(key string) *LIterator { type MapIterator struct { LIterator - currKey string + currKey string currValue interface{} } diff --git a/rete/internal/mem/mjointableimpl.go b/rete/internal/mem/mjointableimpl.go index 12b073f..476eeff 100644 --- a/rete/internal/mem/mjointableimpl.go +++ b/rete/internal/mem/mjointableimpl.go @@ -67,3 +67,17 @@ func (jt *joinTableImpl) GetRow(rowID int) types.JoinTableRow { func (jt *joinTableImpl) GetName() string { return jt.name } + +func (jt *joinTableImpl) RemoveAllRows() { + rowIter := jt.GetRowIterator() + for rowIter.HasNext() { + row := rowIter.Next() + //first, from jTable, remove row + jt.RemoveRow(row.GetID()) + for _, hdl := range row.GetHandles() { + jt.Nw.GetJtRefService().RemoveTableEntry(hdl, jt.GetName()) + } + //Delete the rowRef itself + rowIter.Remove() + } +} diff --git a/rete/internal/mem/mjointablerowiterator.go b/rete/internal/mem/mjointablerowiterator.go index e193e44..130d9e0 100644 --- a/rete/internal/mem/mjointablerowiterator.go +++ b/rete/internal/mem/mjointablerowiterator.go @@ -6,10 +6,10 @@ import ( ) type rowIteratorImpl struct { - table map[int]types.JoinTableRow - kList list.List + table map[int]types.JoinTableRow + kList list.List currKey int - curr *list.Element + curr *list.Element } func newRowIterator(jTable map[int]types.JoinTableRow) types.RowIterator { @@ -35,5 +35,5 @@ func (ri *rowIteratorImpl) Next() types.JoinTableRow { } func (ri *rowIteratorImpl) Remove() { - delete (ri.table, ri.currKey) + delete(ri.table, ri.currKey) } diff --git a/rete/internal/mem/mjtrefsservice.go b/rete/internal/mem/mjtrefsservice.go index a9e0377..03c3e31 100644 --- a/rete/internal/mem/mjtrefsservice.go +++ b/rete/internal/mem/mjtrefsservice.go @@ -56,6 +56,13 @@ func (h *jtRefsServiceImpl) RemoveRowEntry(handle types.ReteHandle, jtName strin } } +func (h *jtRefsServiceImpl) RemoveTableEntry(handle types.ReteHandle, jtName string) { + tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] + if found { + delete(tblMap, jtName) + } +} + func (h *jtRefsServiceImpl) GetIterator(handle types.ReteHandle) types.HdlTblIterator { ri := hdlTblIteratorImpl{} ri.nw = h.Nw @@ -74,11 +81,11 @@ func (h *jtRefsServiceImpl) GetIterator(handle types.ReteHandle) types.HdlTblIte } type hdlTblIteratorImpl struct { - tblMap map[string]map[int]int - kList list.List + tblMap map[string]map[int]int + kList list.List currJtName string - curr *list.Element - nw types.Network + curr *list.Element + nw types.Network } func (ri *hdlTblIteratorImpl) HasNext() bool { @@ -97,12 +104,12 @@ func (ri *hdlTblIteratorImpl) Remove() { } type RowIDIteratorImpl struct { - jtName string - rowIdMap map[int]int - kList list.List - curr *list.Element + jtName string + rowIdMap map[int]int + kList list.List + curr *list.Element currRowId int - nw types.Network + nw types.Network } func (ri *RowIDIteratorImpl) HasNext() bool { @@ -122,7 +129,7 @@ func (ri *RowIDIteratorImpl) Next() types.JoinTableRow { } func (ri *RowIDIteratorImpl) Remove() { - delete (ri.rowIdMap, ri.currRowId) + delete(ri.rowIdMap, ri.currRowId) } func (h *jtRefsServiceImpl) GetRowIterator(handle types.ReteHandle, jtName string) types.RowIterator { diff --git a/rete/internal/redis/rjointableimpl.go b/rete/internal/redis/rjointableimpl.go index 7a49d7d..59513ae 100644 --- a/rete/internal/redis/rjointableimpl.go +++ b/rete/internal/redis/rjointableimpl.go @@ -47,6 +47,20 @@ func (jt *joinTableImpl) RemoveRow(rowID int) types.JoinTableRow { return row } +func (jt *joinTableImpl) RemoveAllRows() { + rowIter := jt.GetRowIterator() + for rowIter.HasNext() { + row := rowIter.Next() + //first, from jTable, remove row + jt.RemoveRow(row.GetID()) + for _, hdl := range row.GetHandles() { + jt.Nw.GetJtRefService().RemoveTableEntry(hdl, jt.GetName()) + } + //Delete the rowRef itself + rowIter.Remove() + } +} + func (jt *joinTableImpl) GetRowCount() int { hdl := redisutils.GetRedisHdl() return hdl.HLen(jt.name) diff --git a/rete/internal/redis/rjointablerowiterator.go b/rete/internal/redis/rjointablerowiterator.go index 914c5fb..20d6a46 100644 --- a/rete/internal/redis/rjointablerowiterator.go +++ b/rete/internal/redis/rjointablerowiterator.go @@ -9,7 +9,7 @@ type rowIteratorImpl struct { iter *redisutils.MapIterator jtName string nw types.Network - curr types.JoinTableRow + curr types.JoinTableRow } func newRowIterator(jTable types.JoinTable) types.RowIterator { diff --git a/rete/internal/redis/rjtrefsservice.go b/rete/internal/redis/rjtrefsservice.go index d289091..a1f653f 100644 --- a/rete/internal/redis/rjtrefsservice.go +++ b/rete/internal/redis/rjtrefsservice.go @@ -62,6 +62,18 @@ func (h *jtRefsServiceImpl) RemoveRowEntry(handle types.ReteHandle, jtName strin //hdl.HDel(hkey, jtName) } +func (h *jtRefsServiceImpl) RemoveTableEntry(handle types.ReteHandle, jtName string) { + + //Delete all row entry refs for this table + rowKey := h.Nw.GetPrefix() + ":rrows:" + handle.GetTupleKey().String() + ":" + jtName + hdl := redisutils.GetRedisHdl() + hdl.Del(rowKey) + + //Delete the table entry for this key + hkey := h.Nw.GetPrefix() + ":rtbls:" + handle.GetTupleKey().String() + hdl.HDel(hkey, jtName) +} + func (h *jtRefsServiceImpl) GetIterator(handle types.ReteHandle) types.HdlTblIterator { ri := hdlTblIteratorImpl{} ri.nw = h.Nw @@ -112,6 +124,7 @@ func (r *RowIDIteratorImpl) Next() types.JoinTableRow { func (r *RowIDIteratorImpl) Remove() { r.iter.Remove() } + //format: prefix:rtbls:tkey ==> {jtname=jtname, ...} //format: prefix:rrows:tkey:jtname ==> {rowid=rowid, ...} diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index ed0bc51..cd95198 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -47,6 +47,7 @@ type JoinTable interface { GetRowIterator() RowIterator GetRowCount() int + RemoveAllRows() //used when join table needs to be deleted } type JoinTableRow interface { NwElemId @@ -80,6 +81,7 @@ type JtRefsService interface { NwService AddEntry(handle ReteHandle, jtName string, rowID int) RemoveRowEntry(handle ReteHandle, jtName string, rowID int) + RemoveTableEntry(handle ReteHandle, jtName string) RemoveEntry(handle ReteHandle, jtName string) GetIterator(handle ReteHandle) HdlTblIterator GetRowIterator(handle ReteHandle, jtName string) RowIterator diff --git a/rete/network.go b/rete/network.go index afd7936..074fcee 100644 --- a/rete/network.go +++ b/rete/network.go @@ -178,8 +178,10 @@ func (nw *reteNetworkImpl) RemoveRule(ruleName string) model.Rule { //case *classNodeImpl: //case *ruleNodeImpl: case *joinNodeImpl: - nw.removeRefsFromReteHandles(nodeImpl.leftTable) - nw.removeRefsFromReteHandles(nodeImpl.rightTable) + //nw.removeRefsFromReteHandles(nodeImpl.leftTable) + //nw.removeRefsFromReteHandles(nodeImpl.rightTable) + nodeImpl.leftTable.RemoveAllRows() + nodeImpl.rightTable.RemoveAllRows() } } } @@ -204,7 +206,7 @@ func (nw *reteNetworkImpl) removeRefsFromReteHandles(joinTableVar types.JoinTabl for rIterator.HasNext() { tableRow := rIterator.Next() for _, handle := range tableRow.GetHandles() { - nw.jtRefsService.RemoveEntry(handle, joinTableVar.GetName()) + nw.removeJoinTableRowRefs(handle, nil) } } } @@ -706,26 +708,29 @@ func (nw *reteNetworkImpl) removeJoinTableRowRefs(hdl types.ReteHandle, changedP continue } - rowIter := nw.jtRefsService.GetRowIterator(hdl, joinTable.GetName()) - - ////Remove rows from corresponding join tables - for rowIter.HasNext() { - row := rowIter.Next() - //first, from jTable, remove row - joinTable.RemoveRow(row.GetID()) - for _, otherHdl := range row.GetHandles() { - //if otherHdl.GetTupleKey().String() != hdl.GetTupleKey().String() { - nw.jtRefsService.RemoveRowEntry(otherHdl, joinTable.GetName(), row.GetID()) - //} - } - //TODO: Delete the rowRef itself - rowIter.Remove() - } + nw.removeJtRowsForTable(hdl, joinTable) } //TODO: Remove the current entry from underneath hdlTblIter.Remove() } +func (nw *reteNetworkImpl) removeJtRowsForTable(hdl types.ReteHandle, joinTable types.JoinTable) { + rowIter := nw.jtRefsService.GetRowIterator(hdl, joinTable.GetName()) + ////Remove rows from corresponding join tables + for rowIter.HasNext() { + row := rowIter.Next() + //first, from jTable, remove row + joinTable.RemoveRow(row.GetID()) + for _, otherHdl := range row.GetHandles() { + //if otherHdl.GetTupleKey().String() != hdl.GetTupleKey().String() { + nw.jtRefsService.RemoveRowEntry(otherHdl, joinTable.GetName(), row.GetID()) + //} + } + //TODO: Delete the rowRef itself + rowIter.Remove() + } +} + func (nw *reteNetworkImpl) GetIdGenService() types.IdGen { return nw.idGen } From acb964f987ff6b22d5eb75df5afe62b3746e6a2f Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Mon, 7 Jan 2019 14:28:40 +0530 Subject: [PATCH 044/125] refactor and cleanup --- rete/internal/mem/mjointableimpl.go | 36 ++++++++++- rete/internal/mem/mjointablerowiterator.go | 39 ------------ rete/internal/mem/mjtrefsservice.go | 25 ++++---- rete/internal/mem/mjtservice.go | 8 --- rete/internal/redis/rhandleservice.go | 8 --- rete/internal/redis/rjointableimpl.go | 33 +++++++++- rete/internal/redis/rjointablerowiterator.go | 37 ----------- rete/internal/redis/rjtrefsservice.go | 24 ++++---- rete/internal/redis/rretehandle.go | 4 +- rete/internal/types/nwelem.go | 23 +++++++ rete/internal/types/nwservice.go | 16 +++++ rete/internal/types/types.go | 64 ++++---------------- rete/network.go | 2 +- 13 files changed, 145 insertions(+), 174 deletions(-) delete mode 100644 rete/internal/mem/mjointablerowiterator.go delete mode 100644 rete/internal/redis/rjointablerowiterator.go create mode 100644 rete/internal/types/nwelem.go create mode 100644 rete/internal/types/nwservice.go diff --git a/rete/internal/mem/mjointableimpl.go b/rete/internal/mem/mjointableimpl.go index 476eeff..a88f627 100644 --- a/rete/internal/mem/mjointableimpl.go +++ b/rete/internal/mem/mjointableimpl.go @@ -1,6 +1,7 @@ package mem import ( + "container/list" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/rete/internal/types" ) @@ -56,7 +57,7 @@ func (jt *joinTableImpl) GetRule() model.Rule { return jt.rule } -func (jt *joinTableImpl) GetRowIterator() types.RowIterator { +func (jt *joinTableImpl) GetRowIterator() types.JointableRowIterator { return newRowIterator(jt.table) } @@ -81,3 +82,36 @@ func (jt *joinTableImpl) RemoveAllRows() { rowIter.Remove() } } + +type rowIteratorImpl struct { + table map[int]types.JoinTableRow + kList list.List + currKey int + curr *list.Element +} + +func newRowIterator(jTable map[int]types.JoinTableRow) types.JointableRowIterator { + ri := rowIteratorImpl{} + ri.table = jTable + ri.kList = list.List{} + for k, _ := range jTable { + ri.kList.PushBack(k) + } + ri.curr = ri.kList.Front() + return &ri +} + +func (ri *rowIteratorImpl) HasNext() bool { + return ri.curr != nil +} + +func (ri *rowIteratorImpl) Next() types.JoinTableRow { + ri.currKey = ri.curr.Value.(int) + val := ri.table[ri.currKey] + ri.curr = ri.curr.Next() + return val +} + +func (ri *rowIteratorImpl) Remove() { + delete(ri.table, ri.currKey) +} diff --git a/rete/internal/mem/mjointablerowiterator.go b/rete/internal/mem/mjointablerowiterator.go deleted file mode 100644 index 130d9e0..0000000 --- a/rete/internal/mem/mjointablerowiterator.go +++ /dev/null @@ -1,39 +0,0 @@ -package mem - -import ( - "container/list" - "github.com/project-flogo/rules/rete/internal/types" -) - -type rowIteratorImpl struct { - table map[int]types.JoinTableRow - kList list.List - currKey int - curr *list.Element -} - -func newRowIterator(jTable map[int]types.JoinTableRow) types.RowIterator { - ri := rowIteratorImpl{} - ri.table = jTable - ri.kList = list.List{} - for k, _ := range jTable { - ri.kList.PushBack(k) - } - ri.curr = ri.kList.Front() - return &ri -} - -func (ri *rowIteratorImpl) HasNext() bool { - return ri.curr != nil -} - -func (ri *rowIteratorImpl) Next() types.JoinTableRow { - ri.currKey = ri.curr.Value.(int) - val := ri.table[ri.currKey] - ri.curr = ri.curr.Next() - return val -} - -func (ri *rowIteratorImpl) Remove() { - delete(ri.table, ri.currKey) -} diff --git a/rete/internal/mem/mjtrefsservice.go b/rete/internal/mem/mjtrefsservice.go index 03c3e31..ed67fb1 100644 --- a/rete/internal/mem/mjtrefsservice.go +++ b/rete/internal/mem/mjtrefsservice.go @@ -63,10 +63,9 @@ func (h *jtRefsServiceImpl) RemoveTableEntry(handle types.ReteHandle, jtName str } } -func (h *jtRefsServiceImpl) GetIterator(handle types.ReteHandle) types.HdlTblIterator { - ri := hdlTblIteratorImpl{} +func (h *jtRefsServiceImpl) GetTableIterator(handle types.ReteHandle) types.JointableIterator { + ri := hdlRefsTableIterator{} ri.nw = h.Nw - //ri.hdlJtImpl = h ri.kList = list.List{} tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] @@ -80,7 +79,7 @@ func (h *jtRefsServiceImpl) GetIterator(handle types.ReteHandle) types.HdlTblIte return &ri } -type hdlTblIteratorImpl struct { +type hdlRefsTableIterator struct { tblMap map[string]map[int]int kList list.List currJtName string @@ -88,22 +87,22 @@ type hdlTblIteratorImpl struct { nw types.Network } -func (ri *hdlTblIteratorImpl) HasNext() bool { +func (ri *hdlRefsTableIterator) HasNext() bool { return ri.curr != nil } -func (ri *hdlTblIteratorImpl) Next() types.JoinTable { +func (ri *hdlRefsTableIterator) Next() types.JoinTable { ri.currJtName = ri.curr.Value.(string) jT := ri.nw.GetJtService().GetJoinTable(ri.currJtName) ri.curr = ri.curr.Next() return jT } -func (ri *hdlTblIteratorImpl) Remove() { +func (ri *hdlRefsTableIterator) Remove() { delete(ri.tblMap, ri.currJtName) } -type RowIDIteratorImpl struct { +type hdlRefsRowIterator struct { jtName string rowIdMap map[int]int kList list.List @@ -112,11 +111,11 @@ type RowIDIteratorImpl struct { nw types.Network } -func (ri *RowIDIteratorImpl) HasNext() bool { +func (ri *hdlRefsRowIterator) HasNext() bool { return ri.curr != nil } -func (ri *RowIDIteratorImpl) Next() types.JoinTableRow { +func (ri *hdlRefsRowIterator) Next() types.JoinTableRow { rowID := ri.curr.Value.(int) ri.currRowId = rowID var jtRow types.JoinTableRow @@ -128,12 +127,12 @@ func (ri *RowIDIteratorImpl) Next() types.JoinTableRow { return jtRow } -func (ri *RowIDIteratorImpl) Remove() { +func (ri *hdlRefsRowIterator) Remove() { delete(ri.rowIdMap, ri.currRowId) } -func (h *jtRefsServiceImpl) GetRowIterator(handle types.ReteHandle, jtName string) types.RowIterator { - ri := RowIDIteratorImpl{} +func (h *jtRefsServiceImpl) GetRowIterator(handle types.ReteHandle, jtName string) types.JointableRowIterator { + ri := hdlRefsRowIterator{} ri.jtName = jtName ri.kList = list.List{} ri.nw = h.Nw diff --git a/rete/internal/mem/mjtservice.go b/rete/internal/mem/mjtservice.go index a27d0fe..235ea7f 100644 --- a/rete/internal/mem/mjtservice.go +++ b/rete/internal/mem/mjtservice.go @@ -24,14 +24,6 @@ func (jtc *jtServiceImpl) GetJoinTable(joinTableName string) types.JoinTable { return jtc.allJoinTables[joinTableName] } -//func (jtc *jtServiceImpl) AddJoinTable(joinTable types.JoinTable) { -// jtc.allJoinTables[joinTable.GetName()] = joinTable -//} -// -//func (jtc *jtServiceImpl) RemoveJoinTable(jtName string) { -// delete(jtc.allJoinTables, jtName) -//} - func (jtc *jtServiceImpl) GetOrCreateJoinTable(nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) types.JoinTable { jT, found := jtc.allJoinTables[name] if !found { diff --git a/rete/internal/redis/rhandleservice.go b/rete/internal/redis/rhandleservice.go index 087e7d3..074f970 100644 --- a/rete/internal/redis/rhandleservice.go +++ b/rete/internal/redis/rhandleservice.go @@ -23,17 +23,9 @@ func (hc *handleServiceImpl) Init() { hc.prefix = hc.Nw.GetPrefix() + ":h:" } -//func (hc *handleServiceImpl) AddHandle(hdl types.ReteHandle) { -// //hc.allHandles[hdl.GetTupleKey().String()] = hdl -// -// redisutils.GetRedisHdl().HSetAll(key, m) -//} - func (hc *handleServiceImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle { rkey := hc.prefix + tuple.GetKey().String() redisutils.GetRedisHdl().Del(rkey) - //fmt.Printf("Deleted: [%d] keys\n", numDeleted) - //TODO: Dummy handle h := newReteHandleImpl(hc.GetNw(), tuple) return h diff --git a/rete/internal/redis/rjointableimpl.go b/rete/internal/redis/rjointableimpl.go index 59513ae..88f287f 100644 --- a/rete/internal/redis/rjointableimpl.go +++ b/rete/internal/redis/rjointableimpl.go @@ -70,7 +70,7 @@ func (jt *joinTableImpl) GetRule() model.Rule { return jt.rule } -func (jt *joinTableImpl) GetRowIterator() types.RowIterator { +func (jt *joinTableImpl) GetRowIterator() types.JointableRowIterator { return newRowIterator(jt) } @@ -84,3 +84,34 @@ func (jt *joinTableImpl) GetRow(rowID int) types.JoinTableRow { func (jt *joinTableImpl) GetName() string { return jt.name } + +type rowIteratorImpl struct { + iter *redisutils.MapIterator + jtName string + nw types.Network + curr types.JoinTableRow +} + +func newRowIterator(jTable types.JoinTable) types.JointableRowIterator { + key := jTable.GetNw().GetPrefix() + ":jt:" + jTable.GetName() + ri := rowIteratorImpl{} + ri.iter = redisutils.GetRedisHdl().GetMapIterator(key) + ri.nw = jTable.GetNw() + ri.jtName = jTable.GetName() + return &ri +} + +func (ri *rowIteratorImpl) HasNext() bool { + return ri.iter.HasNext() +} + +func (ri *rowIteratorImpl) Next() types.JoinTableRow { + rowId, key := ri.iter.Next() + tupleKeyStr := key.(string) + ri.curr = createRow(ri.jtName, rowId, tupleKeyStr, ri.nw) + return ri.curr +} + +func (ri *rowIteratorImpl) Remove() { + ri.iter.Remove() +} diff --git a/rete/internal/redis/rjointablerowiterator.go b/rete/internal/redis/rjointablerowiterator.go deleted file mode 100644 index 20d6a46..0000000 --- a/rete/internal/redis/rjointablerowiterator.go +++ /dev/null @@ -1,37 +0,0 @@ -package redis - -import ( - "github.com/project-flogo/rules/redisutils" - "github.com/project-flogo/rules/rete/internal/types" -) - -type rowIteratorImpl struct { - iter *redisutils.MapIterator - jtName string - nw types.Network - curr types.JoinTableRow -} - -func newRowIterator(jTable types.JoinTable) types.RowIterator { - key := jTable.GetNw().GetPrefix() + ":jt:" + jTable.GetName() - ri := rowIteratorImpl{} - ri.iter = redisutils.GetRedisHdl().GetMapIterator(key) - ri.nw = jTable.GetNw() - ri.jtName = jTable.GetName() - return &ri -} - -func (ri *rowIteratorImpl) HasNext() bool { - return ri.iter.HasNext() -} - -func (ri *rowIteratorImpl) Next() types.JoinTableRow { - rowId, key := ri.iter.Next() - tupleKeyStr := key.(string) - ri.curr = createRow(ri.jtName, rowId, tupleKeyStr, ri.nw) - return ri.curr -} - -func (ri *rowIteratorImpl) Remove() { - ri.iter.Remove() -} diff --git a/rete/internal/redis/rjtrefsservice.go b/rete/internal/redis/rjtrefsservice.go index a1f653f..b584ca3 100644 --- a/rete/internal/redis/rjtrefsservice.go +++ b/rete/internal/redis/rjtrefsservice.go @@ -74,8 +74,8 @@ func (h *jtRefsServiceImpl) RemoveTableEntry(handle types.ReteHandle, jtName str hdl.HDel(hkey, jtName) } -func (h *jtRefsServiceImpl) GetIterator(handle types.ReteHandle) types.HdlTblIterator { - ri := hdlTblIteratorImpl{} +func (h *jtRefsServiceImpl) GetTableIterator(handle types.ReteHandle) types.JointableIterator { + ri := hdlRefsTableIteratorImpl{} ri.nw = h.Nw //format: prefix:rtbls:tkey ==> {jtname=jtname, ...} key := h.Nw.GetPrefix() + ":rtbls:" + handle.GetTupleKey().String() @@ -84,36 +84,36 @@ func (h *jtRefsServiceImpl) GetIterator(handle types.ReteHandle) types.HdlTblIte return &ri } -type hdlTblIteratorImpl struct { +type hdlRefsTableIteratorImpl struct { iter *redisutils.MapIterator nw types.Network } -func (ri *hdlTblIteratorImpl) HasNext() bool { +func (ri *hdlRefsTableIteratorImpl) HasNext() bool { return ri.iter.HasNext() } -func (ri *hdlTblIteratorImpl) Next() types.JoinTable { +func (ri *hdlRefsTableIteratorImpl) Next() types.JoinTable { jtName, _ := ri.iter.Next() jT := ri.nw.GetJtService().GetJoinTable(jtName) return jT } -func (ri *hdlTblIteratorImpl) Remove() { +func (ri *hdlRefsTableIteratorImpl) Remove() { ri.iter.Remove() } -type RowIDIteratorImpl struct { +type hdlRefsRowIteratorImpl struct { key string iter *redisutils.MapIterator nw types.Network jtName string } -func (r *RowIDIteratorImpl) HasNext() bool { +func (r *hdlRefsRowIteratorImpl) HasNext() bool { return r.iter.HasNext() } -func (r *RowIDIteratorImpl) Next() types.JoinTableRow { +func (r *hdlRefsRowIteratorImpl) Next() types.JoinTableRow { rowIdStr, _ := r.iter.Next() rowID, _ := strconv.Atoi(rowIdStr) jT := r.nw.GetJtService().GetJoinTable(r.jtName) @@ -121,15 +121,15 @@ func (r *RowIDIteratorImpl) Next() types.JoinTableRow { return row } -func (r *RowIDIteratorImpl) Remove() { +func (r *hdlRefsRowIteratorImpl) Remove() { r.iter.Remove() } //format: prefix:rtbls:tkey ==> {jtname=jtname, ...} //format: prefix:rrows:tkey:jtname ==> {rowid=rowid, ...} -func (h *jtRefsServiceImpl) GetRowIterator(handle types.ReteHandle, jtName string) types.RowIterator { - r := RowIDIteratorImpl{} +func (h *jtRefsServiceImpl) GetRowIterator(handle types.ReteHandle, jtName string) types.JointableRowIterator { + r := hdlRefsRowIteratorImpl{} r.nw = h.Nw r.jtName = jtName //ex: a:rrows:n1:a:b1:L_tbl diff --git a/rete/internal/redis/rretehandle.go b/rete/internal/redis/rretehandle.go index 614713c..33b72a1 100644 --- a/rete/internal/redis/rretehandle.go +++ b/rete/internal/redis/rretehandle.go @@ -50,7 +50,7 @@ func (hdl *reteHandleImpl) RemoveJoinTable(jtName string) { hdl.Nw.GetJtRefService().RemoveEntry(hdl, jtName) } -func (hdl *reteHandleImpl) GetRefTableIterator() types.HdlTblIterator { - refTblIterator := hdl.Nw.GetJtRefService().GetIterator(hdl) +func (hdl *reteHandleImpl) GetRefTableIterator() types.JointableIterator { + refTblIterator := hdl.Nw.GetJtRefService().GetTableIterator(hdl) return refTblIterator } diff --git a/rete/internal/types/nwelem.go b/rete/internal/types/nwelem.go new file mode 100644 index 0000000..e81f4f5 --- /dev/null +++ b/rete/internal/types/nwelem.go @@ -0,0 +1,23 @@ +package types + +type NwElemId interface { + SetID(nw Network) + GetID() int + GetNw() Network +} + +type NwElemIdImpl struct { + ID int + Nw Network +} + +func (ide *NwElemIdImpl) SetID(nw Network) { + ide.Nw = nw + ide.ID = nw.GetIdGenService().GetNextID() +} +func (ide *NwElemIdImpl) GetID() int { + return ide.ID +} +func (ide *NwElemIdImpl) GetNw() Network { + return ide.Nw +} diff --git a/rete/internal/types/nwservice.go b/rete/internal/types/nwservice.go new file mode 100644 index 0000000..314a18c --- /dev/null +++ b/rete/internal/types/nwservice.go @@ -0,0 +1,16 @@ +package types + +import "github.com/project-flogo/rules/common/model" + +type NwService interface { + model.Service + GetNw() Network +} + +type NwServiceImpl struct { + Nw Network +} + +func (nws *NwServiceImpl) GetNw() Network { + return nws.Nw +} diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index cd95198..57dba46 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -15,27 +15,6 @@ type Network interface { GetTupleStore() model.TupleStore } -type NwElemId interface { - SetID(nw Network) - GetID() int - GetNw() Network -} -type NwElemIdImpl struct { - ID int - Nw Network -} - -func (ide *NwElemIdImpl) SetID(nw Network) { - ide.Nw = nw - ide.ID = nw.GetIdGenService().GetNextID() -} -func (ide *NwElemIdImpl) GetID() int { - return ide.ID -} -func (ide *NwElemIdImpl) GetNw() Network { - return ide.Nw -} - type JoinTable interface { NwElemId GetName() string @@ -44,11 +23,12 @@ type JoinTable interface { AddRow(handles []ReteHandle) JoinTableRow RemoveRow(rowID int) JoinTableRow GetRow(rowID int) JoinTableRow - GetRowIterator() RowIterator + GetRowIterator() JointableRowIterator GetRowCount() int RemoveAllRows() //used when join table needs to be deleted } + type JoinTableRow interface { NwElemId GetHandles() []ReteHandle @@ -61,44 +41,20 @@ type ReteHandle interface { GetTupleKey() model.TupleKey } -type RowIterator interface { - HasNext() bool - Next() JoinTableRow - Remove() // remove underneath current element -} - -type RowIDIterator interface { - HasNext() bool - Next() JoinTableRow -} - -type NwService interface { - model.Service - GetNw() Network -} - type JtRefsService interface { NwService AddEntry(handle ReteHandle, jtName string, rowID int) RemoveRowEntry(handle ReteHandle, jtName string, rowID int) RemoveTableEntry(handle ReteHandle, jtName string) RemoveEntry(handle ReteHandle, jtName string) - GetIterator(handle ReteHandle) HdlTblIterator - GetRowIterator(handle ReteHandle, jtName string) RowIterator -} - -type HdlTblIterator interface { - HasNext() bool - Next() JoinTable - Remove() + GetTableIterator(handle ReteHandle) JointableIterator + GetRowIterator(handle ReteHandle, jtName string) JointableRowIterator } type JtService interface { NwService GetOrCreateJoinTable(nw Network, rule model.Rule, identifiers []model.TupleType, name string) JoinTable GetJoinTable(name string) JoinTable - //AddJoinTable(joinTable JoinTable) - //RemoveJoinTable(name string) } type HandleService interface { @@ -115,10 +71,14 @@ type IdGen interface { GetNextID() int } -type NwServiceImpl struct { - Nw Network +type JointableIterator interface { + HasNext() bool + Next() JoinTable + Remove() } -func (nws *NwServiceImpl) GetNw() Network { - return nws.Nw +type JointableRowIterator interface { + HasNext() bool + Next() JoinTableRow + Remove() } diff --git a/rete/network.go b/rete/network.go index 074fcee..454a516 100644 --- a/rete/network.go +++ b/rete/network.go @@ -680,7 +680,7 @@ func (nw *reteNetworkImpl) removeJoinTableRowRefs(hdl types.ReteHandle, changedP tuple := hdl.GetTuple() alias := tuple.GetTupleType() - hdlTblIter := nw.jtRefsService.GetIterator(hdl) + hdlTblIter := nw.jtRefsService.GetTableIterator(hdl) for hdlTblIter.HasNext() { joinTable := hdlTblIter.Next() From 241fe376c0ddf3c071c8ae0cbc920ef1037c1fd1 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Mon, 7 Jan 2019 19:11:33 +0530 Subject: [PATCH 045/125] use in-memory implementation by default --- rete/factory.go | 19 ------------------- rete/network.go | 7 ++++--- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/rete/factory.go b/rete/factory.go index e54c526..be01bf7 100644 --- a/rete/factory.go +++ b/rete/factory.go @@ -22,25 +22,6 @@ func NewFactory(nw *reteNetworkImpl, config string) *TypeFactory { return &tf } -// -//func (f *TypeFactory) getJoinTable(rule model.Rule, conditionVar model.Condition, identifiers []model.TupleType) types.JoinTable { -// var jt types.JoinTable -// if f.parsedJson == nil { -// jt = mem.CreateOrGetJoinTable(f.nw, rule, conditionVar, identifiers) -// } else { -// rete := f.parsedJson["rete"].(map[string]interface{}) -// if rete != nil { -// idgen := rete["jt"].(string) -// if idgen == "" || idgen == "mem" { -// jt = mem.CreateOrGetJoinTable(f.nw, rule, conditionVar, identifiers) -// } else if idgen == "redis" { -// jt = mem.CreateOrGetJoinTable(f.nw, rule, conditionVar, identifiers) -// } -// } -// } -// return jt -//} - func (f *TypeFactory) getJoinTableRefs() types.JtRefsService { var jtRefs types.JtRefsService if f.parsedJson == nil { diff --git a/rete/network.go b/rete/network.go index 454a516..39b8a33 100644 --- a/rete/network.go +++ b/rete/network.go @@ -68,10 +68,11 @@ func (nw *reteNetworkImpl) initReteNetwork(config string) { factory := NewFactory(nw, config) nw.factory = factory - reteCfg := factory.parsedJson["rs"].(map[string]interface{}) - prefix := reteCfg["prefix"].(string) + if factory.parsedJson != nil { + reteCfg := factory.parsedJson["rs"].(map[string]interface{}) + nw.prefix = reteCfg["prefix"].(string) + } - nw.prefix = prefix nw.idGen = nw.factory.getIdGen() nw.jtService = nw.factory.getJoinTableCollection() nw.handleService = nw.factory.getHandleCollection() From 098bed9862923faca9ef9d7e75140b7202c99b67 Mon Sep 17 00:00:00 2001 From: "balamg@yahoo.com" Date: Thu, 2 May 2019 14:57:54 +0530 Subject: [PATCH 046/125] merged master (project-flogo refactor and later changes) --- examples/rulesapp/main.go | 4 +- rete/factory.go | 90 ++++++++++++++++++++++++--------------- rete/network.go | 54 +++++++++++++---------- ruleapi/rulesession.go | 55 ++++++++++++++++++------ 4 files changed, 130 insertions(+), 73 deletions(-) diff --git a/examples/rulesapp/main.go b/examples/rulesapp/main.go index 94623b9..515da74 100644 --- a/examples/rulesapp/main.go +++ b/examples/rulesapp/main.go @@ -25,8 +25,8 @@ func main() { return } - content := getFileContent("src/github.com/project-flogo/rules/examples/rulesapp/rsconfig.json") - rs, _ := ruleapi.GetOrCreateRuleSessionFromConfig("asession", string(content)) + //content := getFileContent("src/github.com/project-flogo/rules/examples/rulesapp/rsconfig.json") + rs, _ := ruleapi.GetOrCreateRuleSessionFromConfig("asession", "{}") //Create a RuleSession //// check for name "Bob" in n1 diff --git a/rete/factory.go b/rete/factory.go index be01bf7..6d533cd 100644 --- a/rete/factory.go +++ b/rete/factory.go @@ -13,31 +13,39 @@ type TypeFactory struct { parsedJson map[string]interface{} } -func NewFactory(nw *reteNetworkImpl, config string) *TypeFactory { +func NewFactory(nw *reteNetworkImpl, config string) (*TypeFactory, error) { tf := TypeFactory{} tf.config = config - json.Unmarshal([]byte(config), &tf.parsedJson) + err := json.Unmarshal([]byte(config), &tf.parsedJson) + if err != nil { + return nil, err + } tf.nw = nw - return &tf + return &tf, nil } func (f *TypeFactory) getJoinTableRefs() types.JtRefsService { var jtRefs types.JtRefsService if f.parsedJson == nil { jtRefs = mem.NewJoinTableRefsInHdlImpl(f.nw, f.parsedJson) - } else { - rete := f.parsedJson["rete"].(map[string]interface{}) - if rete != nil { - idgen := rete["jt"].(string) - if idgen == "" || idgen == "mem" { - jtRefs = mem.NewJoinTableRefsInHdlImpl(f.nw, f.parsedJson) - } else if idgen == "redis" { - jtRefs = redis.NewJoinTableRefsInHdlImpl(f.nw, f.parsedJson) + if rete, found := f.parsedJson["rete"].(map[string]interface{}); found { + if rete != nil { + if idgen, found2 := rete["jt"].(string); found2 { + if idgen == "" || idgen == "mem" { + jtRefs = mem.NewJoinTableRefsInHdlImpl(f.nw, f.parsedJson) + } else if idgen == "redis" { + jtRefs = redis.NewJoinTableRefsInHdlImpl(f.nw, f.parsedJson) + } + } } } } + if jtRefs == nil { + //default in-mem + jtRefs = mem.NewJoinTableRefsInHdlImpl(f.nw, f.parsedJson) + } return jtRefs } @@ -45,18 +53,22 @@ func (f *TypeFactory) getJoinTableCollection() types.JtService { var allJt types.JtService if f.parsedJson == nil { allJt = mem.NewJoinTableCollection(f.nw, f.parsedJson) - } else { - rete := f.parsedJson["rete"].(map[string]interface{}) - if rete != nil { - idgen := rete["jt"].(string) - if idgen == "" || idgen == "mem" { - allJt = mem.NewJoinTableCollection(f.nw, f.parsedJson) - } else if idgen == "redis" { - allJt = redis.NewJoinTableCollection(f.nw, f.parsedJson) + if rete, found := f.parsedJson["rete"].(map[string]interface{}); found { + if rete != nil { + if idgen, found2 := rete["jt"].(string); found2 { + if idgen == "" || idgen == "mem" { + allJt = mem.NewJoinTableCollection(f.nw, f.parsedJson) + } else if idgen == "redis" { + allJt = redis.NewJoinTableCollection(f.nw, f.parsedJson) + } + } } } } + if allJt == nil { + allJt = mem.NewJoinTableCollection(f.nw, f.parsedJson) + } return allJt } @@ -65,16 +77,21 @@ func (f *TypeFactory) getHandleCollection() types.HandleService { if f.parsedJson == nil { hc = mem.NewHandleCollection(f.nw, f.parsedJson) } else { - rete := f.parsedJson["rete"].(map[string]interface{}) - if rete != nil { - idgen := rete["jt"].(string) - if idgen == "" || idgen == "mem" { - hc = mem.NewHandleCollection(f.nw, f.parsedJson) - } else if idgen == "redis" { - hc = redis.NewHandleCollection(f.nw, f.parsedJson) + if rete, found := f.parsedJson["rete"].(map[string]interface{}); found { + if rete != nil { + if idgen, found2 := rete["jt"].(string); found2 { + if idgen == "" || idgen == "mem" { + hc = mem.NewHandleCollection(f.nw, f.parsedJson) + } else if idgen == "redis" { + hc = redis.NewHandleCollection(f.nw, f.parsedJson) + } + } } } } + if hc == nil { + hc = mem.NewHandleCollection(f.nw, f.parsedJson) + } return hc } @@ -82,18 +99,21 @@ func (f *TypeFactory) getIdGen() types.IdGen { var idg types.IdGen if f.parsedJson == nil { idg = mem.NewIdGenImpl(f.nw, f.parsedJson) - return idg } else { - rete := f.parsedJson["rete"].(map[string]interface{}) - if rete != nil { - - idgen := rete["idgen"].(string) - if idgen == "" || idgen == "mem" { - idg = mem.NewIdGenImpl(f.nw, f.parsedJson) - } else if idgen == "redis" { - idg = redis.NewIdGenImpl(f.nw, f.parsedJson) + if rete, found := f.parsedJson["rete"].(map[string]interface{}); found { + if rete != nil { + if idgen, found2 := rete["idgen"].(string); found2 { + if idgen == "" || idgen == "mem" { + idg = mem.NewIdGenImpl(f.nw, f.parsedJson) + } else if idgen == "redis" { + idg = redis.NewIdGenImpl(f.nw, f.parsedJson) + } + } } } } + if idg == nil { + idg = mem.NewIdGenImpl(f.nw, f.parsedJson) + } return idg } diff --git a/rete/network.go b/rete/network.go index 39b8a33..728793c 100644 --- a/rete/network.go +++ b/rete/network.go @@ -36,8 +36,8 @@ type reteNetworkImpl struct { assertLock sync.Mutex crudLock sync.Mutex - txnHandler model.RtcTransactionHandler - txnContext interface{} + txnHandler[] model.RtcTransactionHandler + txnContext[] interface{} //jtService map[int]types.JoinTable jtService types.JtService @@ -52,32 +52,38 @@ type reteNetworkImpl struct { } //NewReteNetwork ... creates a new rete network -func NewReteNetwork(jsonConfig string) types.Network { +func NewReteNetwork(sessionName string, jsonConfig string) types.Network { reteNetworkImpl := reteNetworkImpl{} - reteNetworkImpl.initReteNetwork(jsonConfig) + reteNetworkImpl.initReteNetwork(sessionName, jsonConfig) return &reteNetworkImpl } -func (nw *reteNetworkImpl) initReteNetwork(config string) { +func (nw *reteNetworkImpl) initReteNetwork(sessionName string, config string) error { //nw.currentId = 0 nw.allRules = make(map[string]model.Rule) nw.allClassNodes = make(map[string]classNode) nw.ruleNameNodesOfRule = make(map[string]*list.List) nw.ruleNameClassNodeLinksOfRule = make(map[string]*list.List) - - factory := NewFactory(nw, config) - nw.factory = factory - - if factory.parsedJson != nil { - reteCfg := factory.parsedJson["rs"].(map[string]interface{}) - nw.prefix = reteCfg["prefix"].(string) - } - - nw.idGen = nw.factory.getIdGen() - nw.jtService = nw.factory.getJoinTableCollection() - nw.handleService = nw.factory.getHandleCollection() - nw.jtRefsService = nw.factory.getJoinTableRefs() + nw.txnHandler = []model.RtcTransactionHandler{} + + factory, err := NewFactory(nw, config) + if err != nil { + return err + } + //nw.factory = factory + + //if factory.parsedJson != nil { + // reteCfg := factory.parsedJson["rs"].(map[string]interface{}) + // nw.prefix = reteCfg["prefix"].(string) + //} + nw.prefix = sessionName + nw.idGen = factory.getIdGen() + nw.jtService = factory.getJoinTableCollection() + nw.handleService = factory.getHandleCollection() + nw.jtRefsService = factory.getJoinTableRefs() nw.initNwServices() + return nil + } func (nw *reteNetworkImpl) initNwServices() { @@ -560,7 +566,9 @@ func (nw *reteNetworkImpl) Assert(ctx context.Context, rs model.RuleSession, tup } if nw.txnHandler != nil { rtcTxn := newRtcTxn(reteCtxVar.getRtcAdded(), reteCtxVar.getRtcModified(), reteCtxVar.getRtcDeleted()) - nw.txnHandler(ctx, rs, rtcTxn, nw.txnContext) + for i, txnHandler := range nw.txnHandler { + txnHandler(newCtx, rs, rtcTxn, nw.txnContext[i]) + } } } else { reteCtxVar.getOpsList().PushBack(newAssertEntry(tuple, changedProps, mode)) @@ -586,7 +594,9 @@ func (nw *reteNetworkImpl) Retract(ctx context.Context, rs model.RuleSession, tu nw.retractInternal(ctx, tuple, changedProps, mode) if nw.txnHandler != nil && mode == common.DELETE { rtcTxn := newRtcTxn(reteCtxVar.getRtcAdded(), reteCtxVar.getRtcModified(), reteCtxVar.getRtcDeleted()) - nw.txnHandler(ctx, reteCtxVar.getRuleSession(), rtcTxn, nw.txnContext) + for i, txnHandler := range nw.txnHandler { + txnHandler(ctx, rs, rtcTxn, nw.txnContext[i]) + } } } else { reteCtxVar.getOpsList().PushBack(newDeleteEntry(tuple, mode, changedProps)) @@ -648,8 +658,8 @@ func (nw *reteNetworkImpl) getHandle(tuple model.Tuple) types.ReteHandle { } func (nw *reteNetworkImpl) RegisterRtcTransactionHandler(txnHandler model.RtcTransactionHandler, txnContext interface{}) { - nw.txnHandler = txnHandler - nw.txnContext = txnContext + nw.txnHandler = append(nw.txnHandler, txnHandler) + nw.txnContext = append(nw.txnContext, txnContext) } func (nw *reteNetworkImpl) GetConfigValue(key string) string { diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index 187b4ee..65d2e43 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -77,7 +77,7 @@ func GetOrCreateRuleSessionFromConfig(name string, jsonConfig string) (model.Rul } func (rs *rulesessionImpl) initRuleSession(name string) { - rs.reteNetwork = rete.NewReteNetwork("") + rs.reteNetwork = rete.NewReteNetwork(name, "{}") rs.name = name rs.timers = make(map[interface{}]*time.Timer) rs.started = false @@ -96,25 +96,29 @@ func (rs *rulesessionImpl) initRuleSessionWithConfig(name string, jsonConfig str //TODO: Configure it from jconsonfig rs.tupleStore = getTupleStore(rs.jsonConfig) rs.tupleStore.Init() - - rs.reteNetwork = rete.NewReteNetwork(jsonConfig) - rs.reteNetwork.SetTupleStore(rs.tupleStore) - + rs.reteNetwork = rete.NewReteNetwork(rs.name, jsonConfig) + //TODO: Configure it from jconsonfig + tupleStore := getTupleStore(rs.jsonConfig) + if tupleStore != nil { + tupleStore.Init() + rs.SetStore(tupleStore) + } rs.started = false return nil } func getTupleStore(jsonConfig map[string]interface{}) model.TupleStore { - rsCfg := jsonConfig["rs"].(map[string]interface{}) - - storeRef := rsCfg["store-ref"].(string) - - if storeRef == "" || storeRef == "mem" { - return mem.NewStore(jsonConfig) - } else if storeRef == "redis" { - return redis.NewStore(jsonConfig) + if rsCfg, found := jsonConfig["rs"].(map[string]interface{}); found { + if storeRef, found2 := rsCfg["store-ref"].(string); found2 { + if storeRef == "" || storeRef == "mem" { + return mem.NewStore(jsonConfig) + } else if storeRef == "redis" { + return redis.NewStore(jsonConfig) + } + } } - return nil + //default to in-mem + return mem.NewStore(jsonConfig) } func (rs *rulesessionImpl) AddRule(rule model.Rule) (err error) { @@ -222,3 +226,26 @@ func (rs *rulesessionImpl) RegisterRtcTransactionHandler(txnHandler model.RtcTra func (rs *rulesessionImpl) GetStore() model.TupleStore { return rs.tupleStore } + +func (rs *rulesessionImpl) SetStore(store model.TupleStore) error { + if store == nil { + return fmt.Errorf("Cannot set nil store") + } + if rs.tupleStore != nil { + return fmt.Errorf("TupleStore already set") + } + if rs.started { + return fmt.Errorf("RuleSession already started") + } + rs.tupleStore = store + rs.reteNetwork.RegisterRtcTransactionHandler(internalTxnHandler, nil) + return nil +} + + +func internalTxnHandler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { + store := rs.GetStore() + store.DeleteTuples(rtxn.GetRtcDeleted()) + store.SaveTuples(rtxn.GetRtcAdded()) + store.SaveModifiedTuples(rtxn.GetRtcModified()) +} From b0f3f3d3a545c1c0d8fd9b49186fddd6d0694470 Mon Sep 17 00:00:00 2001 From: bg Date: Tue, 11 Jun 2019 12:46:05 -0500 Subject: [PATCH 047/125] fixed configuration issues --- common/model/tuplekey.go | 14 ++-------- examples/rulesapp/main.go | 4 +++ examples/rulesapp/rsconfig.json | 38 ++++++++++++++------------ redisutils/redisutils.go | 15 ++++++++-- rete/factory.go | 6 ++-- rete/internal/redis/rhandleservice.go | 7 +++++ rete/internal/redis/ridgenservice.go | 7 ++++- rete/network.go | 5 ++-- ruleapi/internal/store/redis/rstore.go | 8 +++--- ruleapi/rulesession.go | 6 +--- 10 files changed, 62 insertions(+), 48 deletions(-) diff --git a/common/model/tuplekey.go b/common/model/tuplekey.go index 4de9136..fab6092 100644 --- a/common/model/tuplekey.go +++ b/common/model/tuplekey.go @@ -2,8 +2,8 @@ package model import ( "fmt" - "reflect" "github.com/project-flogo/core/data/coerce" + "reflect" "strings" ) @@ -85,19 +85,9 @@ func NewTupleKeyWithKeyValues(tupleType TupleType, values ...interface{}) (tuple for i, keyProp := range td.GetKeyProps() { tdp := td.GetProperty(keyProp) -<<<<<<< HEAD val := values[i] coerced, err := coerce.ToType(val, tdp.PropType) -======= - var val interface{} - switch vt := values[0].(type) { - case map[string]interface{}: - val = vt[keyProp] - default: - val = values[i] - } - coerced, err := data.CoerceToValue(val, tdp.PropType) ->>>>>>> wip redis impl for jointables + if err == nil { tk.keys[keyProp] = coerced } else { diff --git a/examples/rulesapp/main.go b/examples/rulesapp/main.go index 515da74..b90e756 100644 --- a/examples/rulesapp/main.go +++ b/examples/rulesapp/main.go @@ -60,6 +60,10 @@ func main() { fmt.Printf("Warn: [%s]\n", err) } + t11 := rs.GetStore().GetTupleByKey(t1.GetKey()) + if t11 != nil { + fmt.Printf("Warn: Tuple already in store[%s]\n", t11.GetKey()) + } //Now assert a "n1" tuple fmt.Println("Asserting n1 tuple with name=Bob") t2, _ := model.NewTupleWithKeyValues("n1", "Bob") diff --git a/examples/rulesapp/rsconfig.json b/examples/rulesapp/rsconfig.json index 0251bdf..2919ad7 100644 --- a/examples/rulesapp/rsconfig.json +++ b/examples/rulesapp/rsconfig.json @@ -1,30 +1,34 @@ { - "rs" : { - "prefix" : "x", - "store-ref" : "redis" + "rs": { + "prefix": "x", + "store-ref": "redis" }, "rete": { - "jt": "redis", - "idgen" : "redis" + "jt-ref": "mem", + "idgen-ref": "mem" }, - "store": [ - { - "name": "mem" + "stores": { + "mem": { }, - { - "name": "redis", + "redis": { "network": "tcp", "address": ":6379" } - ], - "idgen-ref": [ - { - "name": "memory" + }, + "idgens": { + "mem": { + }, + "redis": { + "network": "tcp", + "address": ":6379" + } + }, + "jts": { + "mem": { }, - { - "name": "redis", + "redis": { "network": "tcp", "address": ":6379" } - ] + } } \ No newline at end of file diff --git a/redisutils/redisutils.go b/redisutils/redisutils.go index bd89803..d63efc1 100644 --- a/redisutils/redisutils.go +++ b/redisutils/redisutils.go @@ -10,15 +10,24 @@ var rd RedisHdl type RedisHdl = *RedisHandle type RedisHandle struct { - config map[string]interface{} - pool *redis.Pool + config map[string]interface{} + pool *redis.Pool + network string + address string } func InitService(config map[string]interface{}) { if rd == nil { rd = &RedisHandle{} rd.config = config - rd.newPool("tcp", ":6379") + if config != nil { + rd.network = rd.config["network"].(string) + rd.address = rd.config["address"].(string) + } else { + rd.network = "tcp" + rd.address = ":6379" + } + rd.newPool(rd.network, rd.address) } } diff --git a/rete/factory.go b/rete/factory.go index 6d533cd..c269cdf 100644 --- a/rete/factory.go +++ b/rete/factory.go @@ -17,7 +17,7 @@ func NewFactory(nw *reteNetworkImpl, config string) (*TypeFactory, error) { tf := TypeFactory{} tf.config = config err := json.Unmarshal([]byte(config), &tf.parsedJson) - if err != nil { + if err != nil { return nil, err } tf.nw = nw @@ -79,7 +79,7 @@ func (f *TypeFactory) getHandleCollection() types.HandleService { } else { if rete, found := f.parsedJson["rete"].(map[string]interface{}); found { if rete != nil { - if idgen, found2 := rete["jt"].(string); found2 { + if idgen, found2 := rete["jt-ref"].(string); found2 { if idgen == "" || idgen == "mem" { hc = mem.NewHandleCollection(f.nw, f.parsedJson) } else if idgen == "redis" { @@ -102,7 +102,7 @@ func (f *TypeFactory) getIdGen() types.IdGen { } else { if rete, found := f.parsedJson["rete"].(map[string]interface{}); found { if rete != nil { - if idgen, found2 := rete["idgen"].(string); found2 { + if idgen, found2 := rete["idgen-ref"].(string); found2 { if idgen == "" || idgen == "mem" { idg = mem.NewIdGenImpl(f.nw, f.parsedJson) } else if idgen == "redis" { diff --git a/rete/internal/redis/rhandleservice.go b/rete/internal/redis/rhandleservice.go index 074f970..6c3c853 100644 --- a/rete/internal/redis/rhandleservice.go +++ b/rete/internal/redis/rhandleservice.go @@ -10,17 +10,24 @@ type handleServiceImpl struct { //allHandles map[string]types.ReteHandle types.NwServiceImpl prefix string + config map[string]interface{} } func NewHandleCollection(nw types.Network, config map[string]interface{}) types.HandleService { hc := handleServiceImpl{} hc.Nw = nw + hc.config = config //hc.allHandles = make(map[string]types.ReteHandle) return &hc } func (hc *handleServiceImpl) Init() { hc.prefix = hc.Nw.GetPrefix() + ":h:" + reteCfg := hc.config["rete"].(map[string]interface{}) + jtRef := reteCfg["jt-ref"].(string) + jts := hc.config["jts"].(map[string]interface{}) + redisCfg := jts[jtRef].(map[string]interface{}) + redisutils.InitService(redisCfg) } func (hc *handleServiceImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle { diff --git a/rete/internal/redis/ridgenservice.go b/rete/internal/redis/ridgenservice.go index 1153a3d..6fcb989 100644 --- a/rete/internal/redis/ridgenservice.go +++ b/rete/internal/redis/ridgenservice.go @@ -29,7 +29,12 @@ func NewIdGenImpl(nw types.Network, config map[string]interface{}) types.IdGen { func (ri *idGenServiceImpl) Init() { ri.key = ri.Nw.GetPrefix() + ":idgen" - redisutils.InitService(ri.config) + reteCfg := ri.config["rete"].(map[string]interface{}) + idgenRef := reteCfg["idgen-ref"].(string) + idGens := ri.config["idgens"].(map[string]interface{}) + redisCfg := idGens[idgenRef].(map[string]interface{}) + redisutils.InitService(redisCfg) + ri.rh = redisutils.GetRedisHdl() j := ri.GetMaxID() fmt.Printf("maxid : [%d]\n ", j) diff --git a/rete/network.go b/rete/network.go index 728793c..a2cb99e 100644 --- a/rete/network.go +++ b/rete/network.go @@ -36,8 +36,8 @@ type reteNetworkImpl struct { assertLock sync.Mutex crudLock sync.Mutex - txnHandler[] model.RtcTransactionHandler - txnContext[] interface{} + txnHandler []model.RtcTransactionHandler + txnContext []interface{} //jtService map[int]types.JoinTable jtService types.JtService @@ -617,7 +617,6 @@ func (nw *reteNetworkImpl) retractInternal(ctx context.Context, tuple model.Tupl if mode == common.DELETE { rCtx.addToRtcDeleted(tuple) } - delete(nw.allHandles, tuple.GetKey().String()) } } diff --git a/ruleapi/internal/store/redis/rstore.go b/ruleapi/internal/store/redis/rstore.go index 089b44d..cd03752 100644 --- a/ruleapi/internal/store/redis/rstore.go +++ b/ruleapi/internal/store/redis/rstore.go @@ -19,11 +19,11 @@ func NewStore(jsonConfig map[string]interface{}) model.TupleStore { } func (ms *storeImpl) Init() { - //ms.allTuples = make(map[string]model.Tuple) reteCfg := ms.jsonConfig["rs"].(map[string]interface{}) - ms.prefix = reteCfg["prefix"].(string) - ms.prefix = ms.prefix + ":" + "s:" - + storeRef := reteCfg["store-ref"].(string) + storeCfg := ms.jsonConfig["stores"].(map[string]interface{}) + redisCfg := storeCfg[storeRef].(map[string]interface{}) + redisutils.InitService(redisCfg) } func (ms *storeImpl) GetTupleByKey(key model.TupleKey) model.Tuple { diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index 65d2e43..1e47204 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -92,11 +92,8 @@ func (rs *rulesessionImpl) initRuleSessionWithConfig(name string, jsonConfig str rs.name = name rs.timers = make(map[interface{}]*time.Timer) - - //TODO: Configure it from jconsonfig - rs.tupleStore = getTupleStore(rs.jsonConfig) - rs.tupleStore.Init() rs.reteNetwork = rete.NewReteNetwork(rs.name, jsonConfig) + //TODO: Configure it from jconsonfig tupleStore := getTupleStore(rs.jsonConfig) if tupleStore != nil { @@ -242,7 +239,6 @@ func (rs *rulesessionImpl) SetStore(store model.TupleStore) error { return nil } - func internalTxnHandler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { store := rs.GetStore() store.DeleteTuples(rtxn.GetRtcDeleted()) From 9d68b5c151fae4c2db2d8d60aab943f536883d86 Mon Sep 17 00:00:00 2001 From: bg Date: Tue, 11 Jun 2019 12:58:06 -0500 Subject: [PATCH 048/125] fix default rs config --- ruleapi/rulesession.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index 1e47204..efe14d0 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -77,10 +77,7 @@ func GetOrCreateRuleSessionFromConfig(name string, jsonConfig string) (model.Rul } func (rs *rulesessionImpl) initRuleSession(name string) { - rs.reteNetwork = rete.NewReteNetwork(name, "{}") - rs.name = name - rs.timers = make(map[interface{}]*time.Timer) - rs.started = false + rs.initRuleSessionWithConfig(name, "{}") } func (rs *rulesessionImpl) initRuleSessionWithConfig(name string, jsonConfig string) error { From 6af83a6fbae60c139401083db746c9cee0b50f7e Mon Sep 17 00:00:00 2001 From: bg Date: Thu, 4 Jul 2019 19:48:00 +0530 Subject: [PATCH 049/125] fixed compilation errors in tests --- ruleapi/exprcondition.go | 2 +- ruleapi/tests/expr_1_test.go | 2 +- ruleapi/tests/expr_2_test.go | 2 +- ruleapi/tests/expr_3_test.go | 2 +- ruleapi/tests/expr_4_test.go | 2 +- ruleapi/tests/expr_5_test.go | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ruleapi/exprcondition.go b/ruleapi/exprcondition.go index 26734b0..9b979ff 100644 --- a/ruleapi/exprcondition.go +++ b/ruleapi/exprcondition.go @@ -133,7 +133,7 @@ func (t *tuplePropertyResolver) Resolve(scope data.Scope, item string, field str // case data.TypeBoolean: // v, err = tuple.GetBool(aliasAndProp[1]) // } - // } + // }` //} //return v, err ts := scope.(*tupleScope) diff --git a/ruleapi/tests/expr_1_test.go b/ruleapi/tests/expr_1_test.go index 66c46c3..41f4435 100644 --- a/ruleapi/tests/expr_1_test.go +++ b/ruleapi/tests/expr_1_test.go @@ -1,9 +1,9 @@ package tests import ( + "context" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "golang.org/x/net/context" "testing" ) diff --git a/ruleapi/tests/expr_2_test.go b/ruleapi/tests/expr_2_test.go index 3be99f6..c27581e 100644 --- a/ruleapi/tests/expr_2_test.go +++ b/ruleapi/tests/expr_2_test.go @@ -1,9 +1,9 @@ package tests import ( + "context" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "golang.org/x/net/context" "testing" ) diff --git a/ruleapi/tests/expr_3_test.go b/ruleapi/tests/expr_3_test.go index 8012566..f8a49e1 100644 --- a/ruleapi/tests/expr_3_test.go +++ b/ruleapi/tests/expr_3_test.go @@ -1,9 +1,9 @@ package tests import ( + "context" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "golang.org/x/net/context" "testing" ) diff --git a/ruleapi/tests/expr_4_test.go b/ruleapi/tests/expr_4_test.go index 936942e..d4d7bfe 100644 --- a/ruleapi/tests/expr_4_test.go +++ b/ruleapi/tests/expr_4_test.go @@ -1,9 +1,9 @@ package tests import ( + "context" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "golang.org/x/net/context" "testing" ) diff --git a/ruleapi/tests/expr_5_test.go b/ruleapi/tests/expr_5_test.go index 5676e2b..53c8668 100644 --- a/ruleapi/tests/expr_5_test.go +++ b/ruleapi/tests/expr_5_test.go @@ -1,9 +1,9 @@ package tests import ( + "context" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "golang.org/x/net/context" "testing" ) From b45a266b02d3627460c668bce3a0fec6f41c61d2 Mon Sep 17 00:00:00 2001 From: bg Date: Wed, 31 Jul 2019 19:46:57 +0530 Subject: [PATCH 050/125] fix go mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index cfa514a..1e5d07f 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,5 @@ require ( github.com/aws/aws-sdk-go v1.18.3 github.com/gorilla/websocket v1.4.0 github.com/oklog/ulid v1.3.1 - github.com/project-flogo/core v0.9.0-alpha.6 + github.com/project-flogo/core v0.9.2 ) From f66fa9608d235089bd41cefdc06dc71daea93712 Mon Sep 17 00:00:00 2001 From: bg Date: Fri, 2 Aug 2019 12:59:02 +0530 Subject: [PATCH 051/125] flogo/flogo.json support for expression based conditions --- common/model/types.go | 10 ++--- config/config.go | 37 +++++++++++----- config/manager.go | 23 ---------- examples/flogo/simple/flogo.json | 71 ++++++++++++++++-------------- examples/flogo/simple/functions.go | 25 +++++++++++ rete/network.go | 6 +++ ruleapi/condition.go | 9 +++- ruleapi/exprcondition.go | 13 +++++- ruleapi/rule.go | 24 ++++++++++ ruleapi/rulesession.go | 14 +++++- ruleapi/tests/expr_1_test.go | 61 +++++-------------------- ruleapi/tests/expr_2_test.go | 10 +++++ ruleapi/tests/expr_3_test.go | 10 +++++ ruleapi/tests/expr_4_test.go | 10 +++++ ruleapi/tests/expr_5_test.go | 10 +++++ 15 files changed, 205 insertions(+), 128 deletions(-) diff --git a/common/model/types.go b/common/model/types.go index ca04954..eb2807d 100644 --- a/common/model/types.go +++ b/common/model/types.go @@ -26,7 +26,8 @@ type MutableRule interface { SetAction(actionFn ActionFunction) SetPriority(priority int) SetContext(ctx RuleContext) - AddExprCondition (conditionName string, cExpr string, ctx RuleContext) (error) + AddExprCondition(conditionName string, cExpr string, ctx RuleContext) error + AddIdrsToRule(idrs []TupleType) } //Condition interface to maintain/get various condition properties @@ -72,7 +73,6 @@ type RuleSession interface { //RtcTransactionHandler RegisterRtcTransactionHandler(txnHandler RtcTransactionHandler, handlerCtx interface{}) - } //ConditionEvaluator is a function pointer for handling condition evaluations on the server side @@ -93,10 +93,9 @@ type ValueChangeListener interface { type RtcTxn interface { //map of type and map of key/tuple - GetRtcAdded () map[string]map[string]Tuple + GetRtcAdded() map[string]map[string]Tuple GetRtcModified() map[string]map[string]RtcModified GetRtcDeleted() map[string]map[string]Tuple - } type RtcModified interface { @@ -104,5 +103,4 @@ type RtcModified interface { GetModifiedProps() map[string]bool } -type RtcTransactionHandler func (ctx context.Context, rs RuleSession, txn RtcTxn, txnContext interface{}) - +type RtcTransactionHandler func(ctx context.Context, rs RuleSession, txn RtcTxn, txnContext interface{}) diff --git a/config/config.go b/config/config.go index ec9429e..85836be 100644 --- a/config/config.go +++ b/config/config.go @@ -23,10 +23,11 @@ type RuleSessionDescriptor struct { // RuleDescriptor defines a rule type RuleDescriptor struct { - Name string - Conditions []*ConditionDescriptor - ActionFunc model.ActionFunction - Priority int + Name string + Conditions []*ConditionDescriptor + ActionFunc model.ActionFunction + Priority int + Identifiers []string } // ConditionDescriptor defines a condition in a rule @@ -34,6 +35,7 @@ type ConditionDescriptor struct { Name string Identifiers []string Evaluator model.ConditionEvaluator + Expression string } func (c *RuleDescriptor) UnmarshalJSON(d []byte) error { @@ -42,6 +44,7 @@ func (c *RuleDescriptor) UnmarshalJSON(d []byte) error { Conditions []*ConditionDescriptor `json:"conditions"` ActionFuncId string `json:"actionFunction"` Priority int `json:"priority"` + Identifiers []string `json:"identifiers"` }{} if err := json.Unmarshal(d, ser); err != nil { @@ -52,6 +55,7 @@ func (c *RuleDescriptor) UnmarshalJSON(d []byte) error { c.Conditions = ser.Conditions c.ActionFunc = GetActionFunction(ser.ActionFuncId) c.Priority = ser.Priority + c.Identifiers = ser.Identifiers return nil } @@ -59,6 +63,14 @@ func (c *RuleDescriptor) UnmarshalJSON(d []byte) error { func (c *RuleDescriptor) MarshalJSON() ([]byte, error) { buffer := bytes.NewBufferString("{") buffer.WriteString("\"name\":" + "\"" + c.Name + "\",") + if c.Identifiers != nil { + buffer.WriteString("\"identifiers\":[") + for _, id := range c.Identifiers { + buffer.WriteString("\"" + id + "\",") + } + buffer.Truncate(buffer.Len() - 1) + buffer.WriteString("],") + } buffer.WriteString("\"conditions\":[") for _, condition := range c.Conditions { @@ -82,6 +94,7 @@ func (c *ConditionDescriptor) UnmarshalJSON(d []byte) error { Name string `json:"name"` Identifiers []string `json:"identifiers"` EvaluatorId string `json:"evaluator"` + Expression string `json:"expression"` }{} if err := json.Unmarshal(d, ser); err != nil { @@ -91,6 +104,7 @@ func (c *ConditionDescriptor) UnmarshalJSON(d []byte) error { c.Name = ser.Name c.Identifiers = ser.Identifiers c.Evaluator = GetConditionEvaluator(ser.EvaluatorId) + c.Expression = ser.Expression return nil } @@ -98,15 +112,18 @@ func (c *ConditionDescriptor) UnmarshalJSON(d []byte) error { func (c *ConditionDescriptor) MarshalJSON() ([]byte, error) { buffer := bytes.NewBufferString("{") buffer.WriteString("\"name\":" + "\"" + c.Name + "\",") - buffer.WriteString("\"identifiers\":[") - for _, id := range c.Identifiers { - buffer.WriteString("\"" + id + "\",") + if c.Identifiers != nil { + buffer.WriteString("\"identifiers\":[") + for _, id := range c.Identifiers { + buffer.WriteString("\"" + id + "\",") + } + buffer.Truncate(buffer.Len() - 1) + buffer.WriteString("],") } - buffer.Truncate(buffer.Len() - 1) - buffer.WriteString("],") conditionEvaluatorID := GetConditionEvaluatorID(c.Evaluator) - buffer.WriteString("\"evaluator\":\"" + conditionEvaluatorID + "\"}") + buffer.WriteString("\"evaluator\":\"" + conditionEvaluatorID + "\",") + buffer.WriteString("\"expression\":\"" + c.Expression + "\"}") return buffer.Bytes(), nil } diff --git a/config/manager.go b/config/manager.go index ccb6054..25d43be 100644 --- a/config/manager.go +++ b/config/manager.go @@ -57,26 +57,3 @@ func (m *ResourceManager) GetRuleActionDescriptor(uri string) (*RuleActionDescri return nil, errors.New("cannot find RuleSession: " + uri) } - -//ioMetadata support -/* -type ActionResource struct { - IOMetadata *metadata.IOMetadata `json:"metadata"` -} - -type ResManager struct { - IOMetadata *metadata.IOMetadata -} - -func (m *ResManager) LoadResource(resConfig *resource.Config) (*resource.Resource, error) { - - var res *ActionResource - err := json.Unmarshal(resConfig.Data, &res) - if err != nil { - return nil, fmt.Errorf("error unmarshalling metadata resource with id '%s', %s", resConfig.ID, err.Error()) - } - - m.IOMetadata = res.IOMetadata - return resource.New("ruleaction", m.IOMetadata), nil -} -*/ diff --git a/examples/flogo/simple/flogo.json b/examples/flogo/simple/flogo.json index 41e8ca1..71beff9 100644 --- a/examples/flogo/simple/flogo.json +++ b/examples/flogo/simple/flogo.json @@ -4,6 +4,13 @@ "version": "0.0.1", "description": "Sample Flogo App", "appModel": "1.0.0", + "properties": [ + { + "name": "name", + "type": "string", + "value": "testprop" + } + ], "triggers": [ { "id": "receive_http_message", @@ -12,32 +19,17 @@ "port": "7777" }, "handlers": [ - { - "settings": { - "method": "GET", - "path": "/test/n1" - }, - "actions": [ - { - "id": "simple_rule", - "input": { - "tupletype": "n1", - "values": "=$.queryParams" - } - } - ] - }, { "settings": { "method": "GET", - "path": "/test/n2" + "path": "/test/:tupleType" }, "actions": [ { "id": "simple_rule", "input": { - "tupletype": "n2", - "values": "=$.queryParams" + "tupletype": "=$.pathParams.tupleType", + "values": "=$.queryParams" } } ] @@ -92,10 +84,10 @@ } ], "output": [ - { - "name": "outputData", - "type": "any" - } + { + "name": "outputData", + "type": "any" + } ] }, "rules": [ @@ -103,11 +95,7 @@ "name": "n1.name == Bob", "conditions": [ { - "name": "c1", - "identifiers": [ - "n1" - ], - "evaluator": "checkForBob" + "expression" : "$.n1.name == 'Bob'" } ], "actionFunction": "checkForBobAction" @@ -116,22 +104,37 @@ "name": "n1.name == Bob \u0026\u0026 n1.name == n2.name", "conditions": [ { - "name": "c1", "identifiers": [ "n1" ], "evaluator": "checkForBob" }, { - "name": "c2", - "identifiers": [ - "n1", - "n2" - ], - "evaluator": "checkSameNamesCondition" + "expression" : "($.n1.name == 'Bob') \u0026\u0026 ($.n1.name == $.n2.name)" } ], "actionFunction": "checkSameNamesAction" + }, + { + "name": "env variable example", + "conditions": [ + { + "expression" : "($.n1.name == $env['name'])" + } + ], + "actionFunction": "envVarExampleAction" + }, + { + "name": "flogo property example", + "identifiers": [ + "n1" + ], + "conditions": [ + { + "expression" : "('testprop' == $property['name'])" + } + ], + "actionFunction": "propertyExampleAction" } ] } diff --git a/examples/flogo/simple/functions.go b/examples/flogo/simple/functions.go index ffc838c..f23073f 100644 --- a/examples/flogo/simple/functions.go +++ b/examples/flogo/simple/functions.go @@ -12,6 +12,8 @@ import ( func init() { config.RegisterActionFunction("checkForBobAction", checkForBobAction) config.RegisterActionFunction("checkSameNamesAction", checkSameNamesAction) + config.RegisterActionFunction("envVarExampleAction", envVarExampleAction) + config.RegisterActionFunction("propertyExampleAction", propertyExampleAction) config.RegisterConditionEvaluator("checkForBob", checkForBob) config.RegisterConditionEvaluator("checkSameNamesCondition", checkSameNamesCondition) @@ -72,3 +74,26 @@ func StartupRSFunction(ctx context.Context, rs model.RuleSession, startupCtx map rs.Assert(nil, t3) return nil } + +func envVarExampleAction(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + fmt.Printf("Rule fired: [%s]\n", ruleName) + t1 := tuples["n1"] + if t1 == nil { + fmt.Println("Should not get nil tuples here in JoinCondition! This is an error") + return + } else { + nm, _ := t1.GetString("name") + fmt.Printf("n1.name is [%s]\n", nm) + } +} +func propertyExampleAction(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + fmt.Printf("Rule fired: [%s]\n", ruleName) + t1 := tuples["n1"] + if t1 == nil { + fmt.Println("Should not get nil tuples here ! This is an error") + return + } else { + nm, _ := t1.GetString("name") + fmt.Printf("n1.name is [%s]\n", nm) + } +} diff --git a/rete/network.go b/rete/network.go index 52dc416..4fef2d2 100644 --- a/rete/network.go +++ b/rete/network.go @@ -99,6 +99,7 @@ func (nw *reteNetworkImpl) AddRule(rule model.Rule) (err error) { classNodeLinksOfRule := list.New() conditions := rule.GetConditions() + noIdrConditionCnt := 0 if len(conditions) == 0 { identifierVar := pickIdentifier(rule.GetIdentifiers()) nw.createClassFilterNode(rule, nodesOfRule, classNodeLinksOfRule, identifierVar, nil, nodeSet) @@ -106,6 +107,7 @@ func (nw *reteNetworkImpl) AddRule(rule model.Rule) (err error) { for i := 0; i < len(conditions); i++ { if conditions[i].GetIdentifiers() == nil || len(conditions[i].GetIdentifiers()) == 0 { conditionSetNoIdr.PushBack(conditions[i]) + noIdrConditionCnt++ } else if len(conditions[i].GetIdentifiers()) == 1 && !contains(nodeSet, conditions[i].GetIdentifiers()[0]) { cond := conditions[i] @@ -115,6 +117,10 @@ func (nw *reteNetworkImpl) AddRule(rule model.Rule) (err error) { } } } + if len(rule.GetConditions()) != 0 && noIdrConditionCnt == len(rule.GetConditions()) { + idr := pickIdentifier(rule.GetIdentifiers()) + nw.createClassFilterNode(rule, nodesOfRule, classNodeLinksOfRule, idr, nil, nodeSet) + } nw.buildNetwork(rule, nodesOfRule, classNodeLinksOfRule, conditionSet, nodeSet, conditionSetNoIdr) diff --git a/ruleapi/condition.go b/ruleapi/condition.go index 33f9c8d..df49f43 100644 --- a/ruleapi/condition.go +++ b/ruleapi/condition.go @@ -2,6 +2,7 @@ package ruleapi import ( "github.com/project-flogo/rules/common/model" + "strconv" ) type conditionImpl struct { @@ -20,6 +21,10 @@ func newCondition(name string, rule model.Rule, identifiers []model.TupleType, c } func (cnd *conditionImpl) initConditionImpl(name string, rule model.Rule, identifiers []model.TupleType, cfn model.ConditionEvaluator, ctx model.RuleContext) { + if name == "" { + cndIdx := len(rule.GetConditions()) + 1 + name = "c_" + strconv.Itoa(cndIdx) + } cnd.name = name cnd.rule = rule cnd.identifiers = append(cnd.identifiers, identifiers...) @@ -53,10 +58,10 @@ func (cnd *conditionImpl) GetTupleTypeAlias() []model.TupleType { return cnd.identifiers } -func (cnd *conditionImpl) Evaluate (condName string, ruleNm string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) (bool, error) { +func (cnd *conditionImpl) Evaluate(condName string, ruleNm string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) (bool, error) { result := false if cnd.cfn != nil { - result = cnd.cfn (condName, ruleNm, tuples, ctx) + result = cnd.cfn(condName, ruleNm, tuples, ctx) } return result, nil diff --git a/ruleapi/exprcondition.go b/ruleapi/exprcondition.go index 9b979ff..872826c 100644 --- a/ruleapi/exprcondition.go +++ b/ruleapi/exprcondition.go @@ -7,6 +7,7 @@ import ( "github.com/project-flogo/core/data/resolve" "github.com/project-flogo/rules/common/model" "reflect" + "strconv" ) var td tuplePropertyResolver @@ -15,7 +16,13 @@ var factory expression.Factory func init() { td = tuplePropertyResolver{} - resolver = resolve.NewCompositeResolver(map[string]resolve.Resolver{".": &td}) + //resolver = resolve.NewCompositeResolver(map[string]resolve.Resolver{".": &td}) + resolver = resolve.NewCompositeResolver(map[string]resolve.Resolver{ + ".": &td, + "env": &resolve.EnvResolver{}, + "property": &resolve.PropertyResolver{}, + "loop": &resolve.LoopResolver{}, + }) factory = script.NewExprFactory(resolver) } @@ -34,6 +41,10 @@ func newExprCondition(name string, rule model.Rule, identifiers []model.TupleTyp } func (cnd *exprConditionImpl) initExprConditionImpl(name string, rule model.Rule, identifiers []model.TupleType, cExpr string, ctx model.RuleContext) { + if name == "" { + cndIdx := len(rule.GetConditions()) + 1 + name = "c_" + strconv.Itoa(cndIdx) + } cnd.name = name cnd.rule = rule cnd.identifiers = append(cnd.identifiers, identifiers...) diff --git a/ruleapi/rule.go b/ruleapi/rule.go index 18aee4d..9bd2d30 100644 --- a/ruleapi/rule.go +++ b/ruleapi/rule.go @@ -74,6 +74,30 @@ func (rule *ruleImpl) addCond(conditionName string, idrs []model.TupleType, cfn } } + +func (rule *ruleImpl) AddIdrsToRule(idrs []model.TupleType) { + for _, cidr := range idrs { + //TODO: configure the rulesession + if model.GetTupleDescriptor(cidr) == nil { + return + } + if len(rule.identifiers) == 0 { + rule.identifiers = append(rule.identifiers, cidr) + } else { + found := false + for _, ridr := range rule.identifiers { + if cidr == ridr { + found = true + break + } + } + if !found { + rule.identifiers = append(rule.identifiers, cidr) + } + } + } +} + func (rule *ruleImpl) addExprCond(conditionName string, idrs []model.TupleType, cExpr string, ctx model.RuleContext) { condition := newExprCondition(conditionName, rule, idrs, cExpr, ctx) rule.conditions = append(rule.conditions, condition) diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index 6df0fc9..68424f0 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -56,7 +56,19 @@ func GetOrCreateRuleSessionFromConfig(name string, jsonConfig string) (model.Rul rule.SetPriority(ruleCfg.Priority) for _, condCfg := range ruleCfg.Conditions { - rule.AddCondition(condCfg.Name, condCfg.Identifiers, condCfg.Evaluator, nil) + if condCfg.Expression == "" { + rule.AddCondition(condCfg.Name, condCfg.Identifiers, condCfg.Evaluator, nil) + } else { + rule.AddExprCondition(condCfg.Name, condCfg.Expression, nil) + } + } + //now add explicit rule identifiers if any + if ruleCfg.Identifiers != nil { + idrs := []model.TupleType{} + for _, idr := range ruleCfg.Identifiers { + idrs = append(idrs, model.TupleType(idr)) + } + rule.AddIdrsToRule(idrs) } rs.AddRule(rule) diff --git a/ruleapi/tests/expr_1_test.go b/ruleapi/tests/expr_1_test.go index 41f4435..478cfa6 100644 --- a/ruleapi/tests/expr_1_test.go +++ b/ruleapi/tests/expr_1_test.go @@ -10,10 +10,13 @@ import ( //1 condition, 1 expression func Test_1_Expr(t *testing.T) { + actionCount := map[string]int{"count": 0} rs, _ := createRuleSession() r1 := ruleapi.NewRule("r1") r1.AddExprCondition("c1", "$.t2.p2 > $.t1.p1", nil) r1.SetAction(a1) + r1.SetContext(actionCount) + rs.AddRule(r1) rs.Start(nil) @@ -36,60 +39,16 @@ func Test_1_Expr(t *testing.T) { ctx = context.WithValue(context.TODO(), TestKey{}, t) rs.Assert(ctx, t2) rs.Unregister() + count := actionCount["count"] + if count != 1 { + t.Errorf("expected [%d], got [%d]\n", 1, count) + } } func a1(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { t := ctx.Value(TestKey{}).(*testing.T) t.Logf("Test_1_Expr executed!") + actionCount := ruleCtx.(map[string]int) + count := actionCount["count"] + actionCount["count"] = count + 1 } - -// -// These standalone tests are not relevant anymore as the expression API has changed -// -//func Test_Eval (t *testing.T) { -// expr, _ := expression.ParseExpression("1 == 1.23") -// i, err := expr.Eval() -// if err != nil { -// t.Fatalf("error %s\n", err) -// } -// res := i.(bool) -// if res { -// t.Errorf("Expected false, got : %t\n ", res) -// } -//} -// -//func Test_Eval2 (t *testing.T) { -// expr, _ := expression.ParseExpression("1 < 1.23") -// i, err := expr.Eval() -// if err != nil { -// t.Fatalf("error %s\n", err) -// } -// res := i.(bool) -// if !res { -// t.Errorf("Expected true, got : %t\n ", res) -// } -//} -// -//func Test_Eval3 (t *testing.T) { -// expr, _ := expression.ParseExpression("1.23 == 1") -// i, err := expr.Eval() -// if err != nil { -// t.Fatalf("error %s\n", err) -// } -// res := i.(bool) -// if res { -// t.Errorf("Expected false, got : %t\n ", res) -// } -//} -// -//func Test_Eval4 (t *testing.T) { -// expr, _ := expression.ParseExpression("1.23 > 1") -// i, err := expr.Eval() -// if err != nil { -// t.Fatalf("error %s\n", err) -// } -// res := i.(bool) -// if !res { -// t.Errorf("Expected true, got : %t\n ", res) -// } -//} diff --git a/ruleapi/tests/expr_2_test.go b/ruleapi/tests/expr_2_test.go index c27581e..5f5d7fe 100644 --- a/ruleapi/tests/expr_2_test.go +++ b/ruleapi/tests/expr_2_test.go @@ -10,11 +10,14 @@ import ( //2 conditions, 1 expr each func Test_2_Expr(t *testing.T) { + actionCount := map[string]int{"count": 0} rs, _ := createRuleSession() r1 := ruleapi.NewRule("r1") r1.AddExprCondition("c1", "$.t1.p1 > $.t2.p1", nil) r1.AddExprCondition("c2", "$.t1.p1 == 2", nil) r1.SetAction(a2) + r1.SetContext(actionCount) + rs.AddRule(r1) rs.Start(nil) @@ -37,9 +40,16 @@ func Test_2_Expr(t *testing.T) { ctx = context.WithValue(context.TODO(), TestKey{}, t) rs.Assert(ctx, t2) rs.Unregister() + count := actionCount["count"] + if count != 1 { + t.Errorf("expected [%d], got [%d]\n", 1, count) + } } func a2(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { t := ctx.Value(TestKey{}).(*testing.T) t.Logf("Test_2_Expr executed!") + actionCount := ruleCtx.(map[string]int) + count := actionCount["count"] + actionCount["count"] = count + 1 } diff --git a/ruleapi/tests/expr_3_test.go b/ruleapi/tests/expr_3_test.go index f8a49e1..37d6361 100644 --- a/ruleapi/tests/expr_3_test.go +++ b/ruleapi/tests/expr_3_test.go @@ -10,10 +10,13 @@ import ( //1 conditions, 2 expr func Test_3_Expr(t *testing.T) { + actionCount := map[string]int{"count": 0} rs, _ := createRuleSession() r1 := ruleapi.NewRule("r1") r1.AddExprCondition("c1", "($.t1.p1 > $.t2.p1) && ($.t1.p2 > $.t2.p2)", nil) r1.SetAction(a3) + r1.SetContext(actionCount) + rs.AddRule(r1) rs.Start(nil) @@ -36,9 +39,16 @@ func Test_3_Expr(t *testing.T) { ctx = context.WithValue(context.TODO(), TestKey{}, t) rs.Assert(ctx, t2) rs.Unregister() + count := actionCount["count"] + if count != 1 { + t.Errorf("expected [%d], got [%d]\n", 1, count) + } } func a3(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { t := ctx.Value(TestKey{}).(*testing.T) t.Logf("Test_3_Expr executed!") + actionCount := ruleCtx.(map[string]int) + count := actionCount["count"] + actionCount["count"] = count + 1 } diff --git a/ruleapi/tests/expr_4_test.go b/ruleapi/tests/expr_4_test.go index d4d7bfe..c3cd7f8 100644 --- a/ruleapi/tests/expr_4_test.go +++ b/ruleapi/tests/expr_4_test.go @@ -10,10 +10,13 @@ import ( //1 conditions, 3 expr func Test_4_Expr(t *testing.T) { + actionCount := map[string]int{"count": 0} rs, _ := createRuleSession() r1 := ruleapi.NewRule("r1") r1.AddExprCondition("c1", "($.t1.p1 > $.t2.p1) && (($.t1.p2 > $.t2.p2) && ($.t1.p3 == $.t2.p3))", nil) r1.SetAction(a4) + r1.SetContext(actionCount) + rs.AddRule(r1) rs.Start(nil) @@ -36,9 +39,16 @@ func Test_4_Expr(t *testing.T) { ctx = context.WithValue(context.TODO(), TestKey{}, t) rs.Assert(ctx, t2) rs.Unregister() + count := actionCount["count"] + if count != 1 { + t.Errorf("expected [%d], got [%d]\n", 1, count) + } } func a4(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { t := ctx.Value(TestKey{}).(*testing.T) t.Logf("Test_4_Expr executed!") + actionCount := ruleCtx.(map[string]int) + count := actionCount["count"] + actionCount["count"] = count + 1 } diff --git a/ruleapi/tests/expr_5_test.go b/ruleapi/tests/expr_5_test.go index 53c8668..411a50b 100644 --- a/ruleapi/tests/expr_5_test.go +++ b/ruleapi/tests/expr_5_test.go @@ -10,10 +10,13 @@ import ( //1 arithmetic operation func Test_5_Expr(t *testing.T) { + actionCount := map[string]int{"count": 0} rs, _ := createRuleSession() r1 := ruleapi.NewRule("r1") r1.AddExprCondition("c1", "(($.t1.p1 + $.t2.p1) == 5) && (($.t1.p2 > $.t2.p2) && ($.t1.p3 == $.t2.p3))", nil) r1.SetAction(a5) + r1.SetContext(actionCount) + rs.AddRule(r1) rs.Start(nil) @@ -36,9 +39,16 @@ func Test_5_Expr(t *testing.T) { ctx = context.WithValue(context.TODO(), TestKey{}, t) rs.Assert(ctx, t2) rs.Unregister() + count := actionCount["count"] + if count != 1 { + t.Errorf("expected [%d], got [%d]\n", 1, count) + } } func a5(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { t := ctx.Value(TestKey{}).(*testing.T) t.Logf("Test_5_Expr executed!") + actionCount := ruleCtx.(map[string]int) + count := actionCount["count"] + actionCount["count"] = count + 1 } From 8c8015044fe9572fb0cd04774e1dc776a0cad1dc Mon Sep 17 00:00:00 2001 From: LakshmiMekala Date: Fri, 9 Aug 2019 12:38:03 +0530 Subject: [PATCH 052/125] add go tests for examples --- examples/flogo/creditcard/creditcard_test.go | 334 ++++++++++++++++++ .../flogo/simple-kafka/docker-compose.yml | 19 + examples/flogo/simple-kafka/kakfa_test.go | 104 ++++++ examples/flogo/simple/sanity.sh | 31 -- examples/flogo/simple/simple_test.go | 121 +++++++ .../flogo/trackntrace/trackntrace_test.go | 115 ++++++ examples/rulesapp/ruleapp_test.go | 21 ++ examples/rulesapp/sanity.sh | 21 -- go.mod | 7 +- ruleapi/tests/common.go | 72 ++++ scripts/README.md | 37 -- scripts/run_sanitytest.sh | 53 --- 12 files changed, 792 insertions(+), 143 deletions(-) create mode 100644 examples/flogo/creditcard/creditcard_test.go create mode 100644 examples/flogo/simple-kafka/docker-compose.yml create mode 100644 examples/flogo/simple-kafka/kakfa_test.go delete mode 100755 examples/flogo/simple/sanity.sh create mode 100644 examples/flogo/simple/simple_test.go create mode 100644 examples/flogo/trackntrace/trackntrace_test.go create mode 100644 examples/rulesapp/ruleapp_test.go delete mode 100644 examples/rulesapp/sanity.sh delete mode 100755 scripts/README.md delete mode 100755 scripts/run_sanitytest.sh diff --git a/examples/flogo/creditcard/creditcard_test.go b/examples/flogo/creditcard/creditcard_test.go new file mode 100644 index 0000000..2ca6e5b --- /dev/null +++ b/examples/flogo/creditcard/creditcard_test.go @@ -0,0 +1,334 @@ +package main + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + "path/filepath" + "strings" + "testing" + + "github.com/project-flogo/core/engine" + "github.com/project-flogo/rules/ruleapi/tests" + "github.com/stretchr/testify/assert" +) + +// Response is a reply form the server +type Response struct { + Status string `json:"status"` + Error string `json:"error"` +} + +var ( + payload = `{ + "Name": "Sam", + "Age": "26", + "Income": "50100", + "Address": "SFO", + "Id": "4" + }` + payload1 = `{ + "Id": "4", + "creditScore": "850" + }` + payload2 = `{ + "Name": "Sam1", + "Age": "17", + "Income": "50100", + "Address": "SFO", + "Id": "5" + }` + payload3 = `{ + "Name": "Sam2", + "Age": "26", + "Income": "5100", + "Address": "SFO", + "Id": "6" + }` + payload4 = `{ + "Name": "Sam3", + "Age": "26", + "Income": "5100", + "Address": "", + "Id": "7" + }` + payload5 = `{ + "Name": "Sam5", + "Age": "32", + "Income": "75000", + "Address": "SFO", + "Id": "8" + }` + payload6 = `{ + "Id": "8", + "creditScore": "760" + }` + payload7 = `{ + "Name": "Sam4", + "Age": "41", + "Income": "30000", + "Address": "SFO", + "Id": "9" + }` + payload8 = `{ + "Id": "9", + "creditScore": "720" + }` +) + +func testapplication(t *testing.T, e engine.Engine) { + tests.Drain("7777") + err := e.Start() + assert.Nil(t, err) + defer func() { + err := e.Stop() + assert.Nil(t, err) + }() + tests.Pour("7777") + transport := &http.Transport{ + MaxIdleConns: 1, + } + defer transport.CloseIdleConnections() + client := &http.Client{ + Transport: transport, + } + + payload, err := json.Marshal(payload) + if err != nil { + panic(err) + } + + payload1, err := json.Marshal(payload1) + if err != nil { + panic(err) + } + + payload2, err := json.Marshal(payload2) + if err != nil { + panic(err) + } + + payload3, err := json.Marshal(payload3) + if err != nil { + panic(err) + } + + payload4, err := json.Marshal(payload4) + if err != nil { + panic(err) + } + + payload5, err := json.Marshal(payload5) + if err != nil { + panic(err) + } + + payload6, err := json.Marshal(payload6) + if err != nil { + panic(err) + } + + payload7, err := json.Marshal(payload7) + if err != nil { + panic(err) + } + + payload8, err := json.Marshal(payload8) + if err != nil { + panic(err) + } + + // valid new user details + request := func() { + req, err := http.NewRequest(http.MethodPut, "http://localhost:7777/newaccount", bytes.NewBuffer(payload)) + assert.Nil(t, err) + req.Header.Set("Content-Type", "application/json") + response, err := client.Do(req) + assert.Nil(t, err) + _, err = ioutil.ReadAll(response.Body) + assert.Nil(t, err) + response.Body.Close() + assert.Nil(t, err) + } + outpt := tests.CaptureOutput(request) + var result string + if strings.Contains(outpt, "Rule fired") { + result = "success" + } + assert.Equal(t, "success", result) + outpt = "" + result = "" + + // user with credit score > 850 + request1 := func() { + req, err := http.NewRequest(http.MethodPut, "http://localhost:7777/credit", bytes.NewBuffer(payload1)) + assert.Nil(t, err) + req.Header.Set("Content-Type", "application/json") + response, err := client.Do(req) + assert.Nil(t, err) + _, err = ioutil.ReadAll(response.Body) + assert.Nil(t, err) + response.Body.Close() + assert.Nil(t, err) + } + outpt = tests.CaptureOutput(request1) + if strings.Contains(outpt, "Rule fired") { + result = "success" + } + assert.Equal(t, "success", result) + outpt = "" + result = "" + + // New user with age < 17 + request2 := func() { + req, err := http.NewRequest(http.MethodPut, "http://localhost:7777/newaccount", bytes.NewBuffer(payload2)) + assert.Nil(t, err) + req.Header.Set("Content-Type", "application/json") + response, err := client.Do(req) + assert.Nil(t, err) + _, err = ioutil.ReadAll(response.Body) + assert.Nil(t, err) + response.Body.Close() + assert.Nil(t, err) + } + outpt = tests.CaptureOutput(request2) + if strings.Contains(outpt, "Applicant is not eligible to apply for creditcard") { + result = "success" + } + assert.Equal(t, "success", result) + outpt = "" + result = "" + + // New user with income < 10k + request3 := func() { + req, err := http.NewRequest(http.MethodPut, "http://localhost:7777/newaccount", bytes.NewBuffer(payload3)) + assert.Nil(t, err) + req.Header.Set("Content-Type", "application/json") + response, err := client.Do(req) + assert.Nil(t, err) + _, err = ioutil.ReadAll(response.Body) + assert.Nil(t, err) + response.Body.Close() + assert.Nil(t, err) + } + outpt = tests.CaptureOutput(request3) + if strings.Contains(outpt, "Applicant is not eligible to apply for creditcard") { + result = "success" + } + assert.Equal(t, "success", result) + outpt = "" + result = "" + + // New user with empty address + request4 := func() { + req, err := http.NewRequest(http.MethodPut, "http://localhost:7777/newaccount", bytes.NewBuffer(payload4)) + assert.Nil(t, err) + req.Header.Set("Content-Type", "application/json") + response, err := client.Do(req) + assert.Nil(t, err) + _, err = ioutil.ReadAll(response.Body) + assert.Nil(t, err) + response.Body.Close() + assert.Nil(t, err) + } + outpt = tests.CaptureOutput(request4) + if strings.Contains(outpt, "Applicant is not eligible to apply for creditcard") { + result = "success" + } + assert.Equal(t, "success", result) + outpt = "" + result = "" + + request5 := func() { + req, err := http.NewRequest(http.MethodPut, "http://localhost:7777/newaccount", bytes.NewBuffer(payload5)) + assert.Nil(t, err) + req.Header.Set("Content-Type", "application/json") + response, err := client.Do(req) + assert.Nil(t, err) + _, err = ioutil.ReadAll(response.Body) + assert.Nil(t, err) + response.Body.Close() + assert.Nil(t, err) + } + outpt = tests.CaptureOutput(request5) + if strings.Contains(outpt, "Rule fired") { + result = "success" + } + assert.Equal(t, "success", result) + outpt = "" + result = "" + + // user with credit score 760 + request6 := func() { + req, err := http.NewRequest(http.MethodPut, "http://localhost:7777/credit", bytes.NewBuffer(payload6)) + assert.Nil(t, err) + req.Header.Set("Content-Type", "application/json") + response, err := client.Do(req) + assert.Nil(t, err) + _, err = ioutil.ReadAll(response.Body) + assert.Nil(t, err) + response.Body.Close() + assert.Nil(t, err) + } + outpt = tests.CaptureOutput(request6) + if strings.Contains(outpt, "Rule fired") { + result = "success" + } + assert.Equal(t, "success", result) + outpt = "" + result = "" + + request7 := func() { + req, err := http.NewRequest(http.MethodPut, "http://localhost:7777/newaccount", bytes.NewBuffer(payload7)) + assert.Nil(t, err) + req.Header.Set("Content-Type", "application/json") + response, err := client.Do(req) + assert.Nil(t, err) + _, err = ioutil.ReadAll(response.Body) + assert.Nil(t, err) + response.Body.Close() + assert.Nil(t, err) + } + outpt = tests.CaptureOutput(request7) + if strings.Contains(outpt, "Rule fired") { + result = "success" + } + assert.Equal(t, "success", result) + outpt = "" + result = "" + + // New user with credit score < 720 + request8 := func() { + req, err := http.NewRequest(http.MethodPut, "http://localhost:7777/credit", bytes.NewBuffer(payload8)) + assert.Nil(t, err) + req.Header.Set("Content-Type", "application/json") + response, err := client.Do(req) + assert.Nil(t, err) + _, err = ioutil.ReadAll(response.Body) + assert.Nil(t, err) + response.Body.Close() + assert.Nil(t, err) + } + outpt = tests.CaptureOutput(request8) + if strings.Contains(outpt, "Rule fired: Rejected") { + result = "success" + } + assert.Equal(t, "success", result) + outpt = "" + result = "" +} + +func TestCreditCardJSON(t *testing.T) { + if testing.Short() { + t.Skip("skipping Handler Routing JSON integration test in short mode") + } + + data, err := ioutil.ReadFile(filepath.FromSlash("./flogo.json")) + assert.Nil(t, err) + cfg, err := engine.LoadAppConfig(string(data), false) + assert.Nil(t, err) + e, err := engine.New(cfg) + assert.Nil(t, err) + testapplication(t, e) +} diff --git a/examples/flogo/simple-kafka/docker-compose.yml b/examples/flogo/simple-kafka/docker-compose.yml new file mode 100644 index 0000000..eacafc7 --- /dev/null +++ b/examples/flogo/simple-kafka/docker-compose.yml @@ -0,0 +1,19 @@ +version: '2' + +services: + + zookeeper: + image: wurstmeister/zookeeper:3.4.6 + expose: + - "2181" + + kafka: + image: wurstmeister/kafka:2.11-2.0.0 + depends_on: + - zookeeper + ports: + - "9092:9092" + environment: + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 + KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092 + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 \ No newline at end of file diff --git a/examples/flogo/simple-kafka/kakfa_test.go b/examples/flogo/simple-kafka/kakfa_test.go new file mode 100644 index 0000000..65370b7 --- /dev/null +++ b/examples/flogo/simple-kafka/kakfa_test.go @@ -0,0 +1,104 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/Shopify/sarama" + "github.com/project-flogo/core/engine" + "github.com/project-flogo/rules/ruleapi/tests" + "github.com/stretchr/testify/assert" +) + +const ( + kafkaConn = "localhost:9092" + topic = "orderinfo" +) + +func initProducer() (sarama.SyncProducer, error) { + // setup sarama log to stdout + sarama.Logger = log.New(os.Stdout, "", log.Ltime) + + // producer config + config := sarama.NewConfig() + config.Producer.Retry.Max = 5 + config.Producer.RequiredAcks = sarama.WaitForAll + config.Producer.Return.Successes = true + + // sync producer + prd, err := sarama.NewSyncProducer([]string{kafkaConn}, config) + + return prd, err +} + +func publish(message string, producer sarama.SyncProducer) { + // publish sync + msg := &sarama.ProducerMessage{ + Topic: topic, + Value: sarama.StringEncoder(message), + } + p, o, err := producer.SendMessage(msg) + if err != nil { + fmt.Println("Error publish: ", err.Error()) + } + + fmt.Println("Partition: ", p) + fmt.Println("Offset: ", o) +} + +func testApplication(t *testing.T, e engine.Engine) { + err := e.Start() + assert.Nil(t, err) + defer func() { + tests.Command("docker", "rm", "-f", "$(docker ps -aq)") + err := e.Stop() + assert.Nil(t, err) + }() + tests.Pour("9092") + + producer, err := initProducer() + if err != nil { + fmt.Println("Error producer: ", err.Error()) + os.Exit(1) + } + + request := func() { + publish(`{"type":"grocery","totalPrice":"2001.0"}`, producer) + } + outpt := tests.CaptureOutput(request) + + fmt.Println("********************", outpt, "888888888888888888888") + var result string + if strings.Contains(outpt, "Rule fired") { + result = "success" + } + assert.Equal(t, "success", result) + outpt = "" + result = "" + +} + +func TestSimpleKafkaJSON(t *testing.T) { + cmd := exec.Command("docker-compose") + err := cmd.Run() + if err == nil { + t.Skip("skipping test - docker-compose not found") + } + + data, err := ioutil.ReadFile(filepath.FromSlash("./flogo.json")) + assert.Nil(t, err) + tests.Command("docker-compose", "up", "-d") + time.Sleep(50 * time.Second) + cfg, err := engine.LoadAppConfig(string(data), false) + assert.Nil(t, err) + e, err := engine.New(cfg) + assert.Nil(t, err) + testApplication(t, e) +} diff --git a/examples/flogo/simple/sanity.sh b/examples/flogo/simple/sanity.sh deleted file mode 100755 index b43527e..0000000 --- a/examples/flogo/simple/sanity.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash - -function get_test_cases { - local my_list=( testcase1 ) - echo "${my_list[@]}" -} - -# This Testcase creates flogo rules binary and checks for name bob -function testcase1 { -pushd $GOPATH/src/github.com/project-flogo/rules/examples/flogo/simple -flogo create -f flogo.json -cp functions.go simplerules/src -cd simplerules -flogo build -./bin/simplerules > /tmp/testcase1.log 2>&1 & -pId=$! - -response=$(curl --request GET localhost:7777/test/n1?name=Bob --write-out '%{http_code}' --silent --output /dev/null) -response1=$(curl --request GET localhost:7777/test/n2?name=Bob --write-out '%{http_code}' --silent --output /dev/null) - -kill -9 $pId -if [ $response -eq 200 ] && [ $response1 -eq 200 ] && [[ "echo $(cat /tmp/testcase1.log)" =~ "Rule fired" ]] - then - echo "PASS" - else - echo "FAIL" -fi -cd .. -rm -rf simplerules -popd -} \ No newline at end of file diff --git a/examples/flogo/simple/simple_test.go b/examples/flogo/simple/simple_test.go new file mode 100644 index 0000000..1af0f87 --- /dev/null +++ b/examples/flogo/simple/simple_test.go @@ -0,0 +1,121 @@ +package main + +import ( + "io/ioutil" + "net/http" + "path/filepath" + "strings" + "testing" + + "github.com/project-flogo/core/engine" + "github.com/project-flogo/rules/ruleapi/tests" + "github.com/stretchr/testify/assert" +) + +func testApplication(t *testing.T, e engine.Engine) { + tests.Drain("7777") + err := e.Start() + assert.Nil(t, err) + defer func() { + err := e.Stop() + assert.Nil(t, err) + }() + tests.Pour("7777") + + transport := &http.Transport{ + MaxIdleConns: 1, + } + defer transport.CloseIdleConnections() + client := &http.Client{ + Transport: transport, + } + + // check for samename condition + request := func() { + req, err := http.NewRequest(http.MethodGet, "http://localhost:7777/test/n1?name=Bob", nil) + assert.Nil(t, err) + response, err := client.Do(req) + assert.Nil(t, err) + _, err = ioutil.ReadAll(response.Body) + assert.Nil(t, err) + response.Body.Close() + } + + outpt := tests.CaptureOutput(request) + + var result string + if strings.Contains(outpt, "Rule fired") { + result = "success" + } + assert.Equal(t, "success", result) + result = "" + + // check for tuples match n1 and n2 + request1 := func() { + req, err := http.NewRequest(http.MethodGet, "http://localhost:7777/test/n2?name=Bob", nil) + assert.Nil(t, err) + response, err := client.Do(req) + assert.Nil(t, err) + _, err = ioutil.ReadAll(response.Body) + assert.Nil(t, err) + response.Body.Close() + } + + outpt = tests.CaptureOutput(request1) + if strings.Contains(outpt, "Rule fired") { + result = "success" + } + assert.Equal(t, "success", result) + result = "" + + // check for name mismatch + request2 := func() { + req, err := http.NewRequest(http.MethodGet, "http://localhost:7777/test/n1?name=Tom", nil) + assert.Nil(t, err) + response, err := client.Do(req) + assert.Nil(t, err) + _, err = ioutil.ReadAll(response.Body) + assert.Nil(t, err) + response.Body.Close() + } + + outpt = tests.CaptureOutput(request2) + if !strings.Contains(outpt, "Rule fired") { + result = "success" + } + assert.Equal(t, "success", result) + result = "" + + // Already asserted tuple check + request3 := func() string { + req, err := http.NewRequest(http.MethodGet, "http://localhost:7777/test/n1?name=Bob", nil) + assert.Nil(t, err) + response, err := client.Do(req) + assert.Nil(t, err) + body, err := ioutil.ReadAll(response.Body) + assert.Nil(t, err) + response.Body.Close() + return string(body) + } + + body := request3() + if strings.Contains(body, "already asserted") { + result = "success" + } + assert.Equal(t, "success", result) + result = "" +} + +func TestSimpleJSON(t *testing.T) { + if testing.Short() { + t.Skip("skipping Handler Routing JSON integration test in short mode") + } + + data, err := ioutil.ReadFile(filepath.FromSlash("./flogo.json")) + assert.Nil(t, err) + cfg, err := engine.LoadAppConfig(string(data), false) + assert.Nil(t, err) + e, err := engine.New(cfg) + assert.Nil(t, err) + testApplication(t, e) +} diff --git a/examples/flogo/trackntrace/trackntrace_test.go b/examples/flogo/trackntrace/trackntrace_test.go new file mode 100644 index 0000000..2a92f8b --- /dev/null +++ b/examples/flogo/trackntrace/trackntrace_test.go @@ -0,0 +1,115 @@ +package main + +import ( + "io/ioutil" + "net/http" + "path/filepath" + "strings" + "testing" + + "github.com/project-flogo/core/engine" + "github.com/project-flogo/rules/ruleapi/tests" + "github.com/stretchr/testify/assert" +) + +func testApplication(t *testing.T, e engine.Engine) { + tests.Drain("7777") + err := e.Start() + assert.Nil(t, err) + defer func() { + err := e.Stop() + assert.Nil(t, err) + }() + tests.Pour("7777") + + transport := &http.Transport{ + MaxIdleConns: 1, + } + defer transport.CloseIdleConnections() + client := &http.Client{ + Transport: transport, + } + + request := func() { + req, err := http.NewRequest(http.MethodGet, "http://localhost:7777/moveevent?packageid=PACKAGE1&targetstate=sitting", nil) + assert.Nil(t, err) + response, err := client.Do(req) + assert.Nil(t, err) + _, err = ioutil.ReadAll(response.Body) + assert.Nil(t, err) + response.Body.Close() + } + + outpt := tests.CaptureOutput(request) + var result string + if strings.Contains(outpt, "target state [sitting]") { + result = "success" + } + assert.Equal(t, "success", result) + result = "" + + request1 := func() { + req, err := http.NewRequest(http.MethodGet, "http://localhost:7777/moveevent?packageid=PACKAGE1&targetstate=sitting", nil) + assert.Nil(t, err) + response, err := client.Do(req) + assert.Nil(t, err) + _, err = ioutil.ReadAll(response.Body) + assert.Nil(t, err) + response.Body.Close() + } + + outpt = tests.CaptureOutput(request1) + if strings.Contains(outpt, "target state [sitting]") { + result = "success" + } + assert.Equal(t, "success", result) + result = "" + + request2 := func() { + req, err := http.NewRequest(http.MethodGet, "http://localhost:7777/moveevent?packageid=PACKAGE1&targetstate=moving", nil) + assert.Nil(t, err) + response, err := client.Do(req) + assert.Nil(t, err) + _, err = ioutil.ReadAll(response.Body) + assert.Nil(t, err) + response.Body.Close() + } + + outpt = tests.CaptureOutput(request2) + if strings.Contains(outpt, "target state [moving]") { + result = "success" + } + assert.Equal(t, "success", result) + result = "" + + request3 := func() { + req, err := http.NewRequest(http.MethodGet, "http://localhost:7777/moveevent?packageid=PACKAGE2&targetstate=normal", nil) + assert.Nil(t, err) + response, err := client.Do(req) + assert.Nil(t, err) + _, err = ioutil.ReadAll(response.Body) + assert.Nil(t, err) + response.Body.Close() + } + + outpt = tests.CaptureOutput(request3) + if strings.Contains(outpt, "Tuple inserted successfully") { + result = "success" + } + assert.Equal(t, "success", result) + result = "" +} + +func TestTracknTraceJSON(t *testing.T) { + if testing.Short() { + t.Skip("skipping Handler Routing JSON integration test in short mode") + } + + data, err := ioutil.ReadFile(filepath.FromSlash("./flogo.json")) + assert.Nil(t, err) + cfg, err := engine.LoadAppConfig(string(data), false) + assert.Nil(t, err) + e, err := engine.New(cfg) + assert.Nil(t, err) + testApplication(t, e) +} diff --git a/examples/rulesapp/ruleapp_test.go b/examples/rulesapp/ruleapp_test.go new file mode 100644 index 0000000..3783ed5 --- /dev/null +++ b/examples/rulesapp/ruleapp_test.go @@ -0,0 +1,21 @@ +package main + +import ( + "strings" + "testing" + + "github.com/project-flogo/rules/ruleapi/tests" + "github.com/stretchr/testify/assert" +) + +func TestRuleApp(t *testing.T) { + request := func() { + tests.Command("go", "run", "main.go") + } + output := tests.CaptureOutput(request) + var result string + if strings.Contains(output, "Rule fired") && strings.Contains(output, "Loaded tuple descriptor") { + result = "success" + } + assert.Equal(t, "success", result) +} diff --git a/examples/rulesapp/sanity.sh b/examples/rulesapp/sanity.sh deleted file mode 100644 index aff0c8a..0000000 --- a/examples/rulesapp/sanity.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -function get_test_cases { - local my_list=( testcase1 ) - echo "${my_list[@]}" -} - -# This testcase checks for name bob -function testcase1 { -pushd $GOPATH/src/github.com/project-flogo/rules/examples/rulesapp -rm -rf /tmp/testcase1.log -go run main.go > /tmp/testcase1.log 2>&1 - -if [[ "echo $(cat /tmp/testcase1.log)" =~ "Rule fired" ]] - then - echo "PASS" - else - echo "FAIL" -fi -popd -} \ No newline at end of file diff --git a/go.mod b/go.mod index cfa514a..cbc1a58 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,13 @@ module github.com/project-flogo/rules require ( + github.com/Shopify/sarama v1.22.0 github.com/aws/aws-sdk-go v1.18.3 github.com/gorilla/websocket v1.4.0 github.com/oklog/ulid v1.3.1 - github.com/project-flogo/core v0.9.0-alpha.6 + github.com/project-flogo/contrib/trigger/kafka v0.9.0 + github.com/project-flogo/contrib/trigger/rest v0.9.0 + github.com/project-flogo/core v0.9.0 + github.com/square-it/jsonschema v1.9.1 // indirect + github.com/stretchr/testify v1.3.0 ) diff --git a/ruleapi/tests/common.go b/ruleapi/tests/common.go index c1cf504..93be6b8 100644 --- a/ruleapi/tests/common.go +++ b/ruleapi/tests/common.go @@ -1,10 +1,19 @@ package tests import ( + "bytes" "context" + "fmt" + "io" "io/ioutil" "log" + "net" + "os" + "os/exec" + "strings" + "sync" "testing" + "time" "github.com/project-flogo/rules/common" "github.com/project-flogo/rules/common/model" @@ -60,3 +69,66 @@ type txnCtx struct { Testing *testing.T TxnCnt int } + +func CaptureOutput(f func()) string { + reader, writer, err := os.Pipe() + if err != nil { + panic(err) + } + stdout := os.Stdout + stderr := os.Stderr + defer func() { + os.Stdout = stdout + os.Stderr = stderr + log.SetOutput(os.Stderr) + }() + os.Stdout = writer + os.Stderr = writer + log.SetOutput(writer) + out := make(chan string) + wg := new(sync.WaitGroup) + wg.Add(1) + go func() { + var buf bytes.Buffer + wg.Done() + io.Copy(&buf, reader) + out <- buf.String() + }() + wg.Wait() + f() + writer.Close() + return <-out +} + +func Command(name string, arg ...string) { + fmt.Printf("%s %v\n", name, arg) + output, err := exec.Command(name, arg...).CombinedOutput() + if err != nil { + os.Stderr.WriteString(err.Error()) + } + if len(output) > 0 { + fmt.Printf("%s", string(output)) + } +} + +func Drain(port string) { + for { + conn, err := net.DialTimeout("tcp", net.JoinHostPort("", port), time.Second) + if conn != nil { + conn.Close() + } + if err != nil && strings.Contains(err.Error(), "connect: connection refused") { + break + } + } +} + +func Pour(port string) { + for { + conn, _ := net.Dial("tcp", net.JoinHostPort("", port)) + if conn != nil { + conn.Close() + break + } + } +} diff --git a/scripts/README.md b/scripts/README.md deleted file mode 100755 index 6cdeff0..0000000 --- a/scripts/README.md +++ /dev/null @@ -1,37 +0,0 @@ -## Sanity Testing - -* There is a shell script file `run_sanitytest.sh` that performs sanity testing against rules/examples and generates html report. - -* This script file checks for all available `sanity.sh` files inside rules/examples and run tests against individual `sanity.sh` file - - -* To run sanity tests - -``` -cd $GOPATH/src/github.com/project-flogo/rules/scripts -./run_sanitytest.sh -``` - -* Testcase status of each example is updated in the html report and test report is made available in scripts folder. - - -### Contributing - -If you're adding a new rules example, optionally you can add sanity test file with name `sanity.sh`. Below is the template used for creating test file. - -``` -#!/bin/bash - -function get_test_cases { - local my_list=( testcase1 ) - echo "${my_list[@]}" -} - -function testcase1 { -# Add detailed steps to execute the test case -} -``` -Sample sanity test file can be found at -``` -$GOPATH/src/github.com/project-flogo/rules/examples/flogo/simple/sanity.sh -``` \ No newline at end of file diff --git a/scripts/run_sanitytest.sh b/scripts/run_sanitytest.sh deleted file mode 100755 index f10de76..0000000 --- a/scripts/run_sanitytest.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash - -RULESPATH=$GOPATH/src/github.com/project-flogo/rules -export FILENAME="RulesSanityReport.html" -HTML=" -

      Rules Sanity Report

      Summary

      Number of test cases passed
      Number of test cases failed
      Total test cases
      Recipe Testcase Status
      " - -echo $HTML >> $RULESPATH/scripts/$FILENAME -PASS_COUNT=0 FAIL_COUNT=0 - -# Fetch list of sanity.sh files in examples folder -function get_sanitylist() -{ - cd $RULESPATH/examples - find | grep sanity.sh > file.txt - readarray -t array < file.txt - for EXAMPLE in "${array[@]}" - do - echo "$EXAMPLE" - RECIPE=$(echo $EXAMPLE | sed -e 's/\/sanity.sh//g' | sed -e 's/\.\///g' | sed -e 's/\//-/g') - execute_testcase - done -} - -# Execute and obtain testcase status (pass/fail) -function execute_testcase() -{ - echo $RECIPE - source $EXAMPLE - TESTCASE_LIST=($(get_test_cases)) - sleep 10 - for ((i=0;i < ${#TESTCASE_LIST[@]};i++)) - do - TESTCASE=$(${TESTCASE_LIST[i]}) - sleep 10 - if [[ $TESTCASE == *"PASS"* ]]; then - echo "$RECIPE":"Passed" - PASS_COUNT=$((PASS_COUNT+1)) - sed -i "s/<\/tr> <\/table>/$RECIPE<\/td>${TESTCASE_LIST[i]}<\/td>PASS<\/td><\/tr><\/tr> <\/table>/g" $RULESPATH/scripts/$FILENAME - else - echo "$RECIPE":"Failed" - FAIL_COUNT=$((FAIL_COUNT+1)) - sed -i "s/<\/tr> <\/table>/$RECIPE<\/td>${TESTCASE_LIST[i]}<\/td>FAIL<\/td><\/tr><\/tr> <\/table>/g" $RULESPATH/scripts/$FILENAME - fi - done -} - -get_sanitylist - -# Update testcase count in html report -sed -i s/"passed <\/td> "/"passed <\/td> $PASS_COUNT"/g $RULESPATH/scripts/$FILENAME -sed -i s/"failed <\/td> "/"failed <\/td> $FAIL_COUNT"/g $RULESPATH/scripts/$FILENAME -sed -i s/"cases<\/td>"/"cases<\/td>$((PASS_COUNT+FAIL_COUNT))"/g $RULESPATH/scripts/$FILENAME \ No newline at end of file From a636339eabef9b9f0e1dcc3a2182e86b683791f1 Mon Sep 17 00:00:00 2001 From: LakshmiMekala Date: Fri, 9 Aug 2019 16:04:06 +0530 Subject: [PATCH 053/125] update kafka go test to kill running docker containers --- examples/flogo/simple-kafka/kakfa_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/flogo/simple-kafka/kakfa_test.go b/examples/flogo/simple-kafka/kakfa_test.go index 65370b7..74a6bf6 100644 --- a/examples/flogo/simple-kafka/kakfa_test.go +++ b/examples/flogo/simple-kafka/kakfa_test.go @@ -57,9 +57,9 @@ func testApplication(t *testing.T, e engine.Engine) { err := e.Start() assert.Nil(t, err) defer func() { - tests.Command("docker", "rm", "-f", "$(docker ps -aq)") err := e.Stop() assert.Nil(t, err) + tests.Command("docker-compose", "down") }() tests.Pour("9092") From eea81959f0401c4c7169203c119832cdbd3a9435 Mon Sep 17 00:00:00 2001 From: LakshmiMekala Date: Mon, 12 Aug 2019 12:37:31 +0530 Subject: [PATCH 054/125] incorporating PR review comments --- examples/flogo/simple-kafka/kakfa_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/flogo/simple-kafka/kakfa_test.go b/examples/flogo/simple-kafka/kakfa_test.go index 74a6bf6..ee722f0 100644 --- a/examples/flogo/simple-kafka/kakfa_test.go +++ b/examples/flogo/simple-kafka/kakfa_test.go @@ -74,7 +74,6 @@ func testApplication(t *testing.T, e engine.Engine) { } outpt := tests.CaptureOutput(request) - fmt.Println("********************", outpt, "888888888888888888888") var result string if strings.Contains(outpt, "Rule fired") { result = "success" @@ -86,9 +85,8 @@ func testApplication(t *testing.T, e engine.Engine) { } func TestSimpleKafkaJSON(t *testing.T) { - cmd := exec.Command("docker-compose") - err := cmd.Run() - if err == nil { + _, err := exec.LookPath("docker-compose") + if err != nil { t.Skip("skipping test - docker-compose not found") } From dda01ea256be7a11833879c734afa705fb726fbf Mon Sep 17 00:00:00 2001 From: LakshmiMekala Date: Tue, 13 Aug 2019 20:36:04 +0530 Subject: [PATCH 055/125] changing fn name captureout to capturestdout --- examples/flogo/creditcard/creditcard_test.go | 18 +++++++++--------- examples/flogo/simple-kafka/kakfa_test.go | 2 +- examples/flogo/simple/simple_test.go | 6 +++--- examples/flogo/trackntrace/trackntrace_test.go | 8 ++++---- examples/rulesapp/ruleapp_test.go | 2 +- ruleapi/tests/common.go | 2 +- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/examples/flogo/creditcard/creditcard_test.go b/examples/flogo/creditcard/creditcard_test.go index 2ca6e5b..8978961 100644 --- a/examples/flogo/creditcard/creditcard_test.go +++ b/examples/flogo/creditcard/creditcard_test.go @@ -151,7 +151,7 @@ func testapplication(t *testing.T, e engine.Engine) { response.Body.Close() assert.Nil(t, err) } - outpt := tests.CaptureOutput(request) + outpt := tests.CaptureStdOutput(request) var result string if strings.Contains(outpt, "Rule fired") { result = "success" @@ -172,7 +172,7 @@ func testapplication(t *testing.T, e engine.Engine) { response.Body.Close() assert.Nil(t, err) } - outpt = tests.CaptureOutput(request1) + outpt = tests.CaptureStdOutput(request1) if strings.Contains(outpt, "Rule fired") { result = "success" } @@ -192,7 +192,7 @@ func testapplication(t *testing.T, e engine.Engine) { response.Body.Close() assert.Nil(t, err) } - outpt = tests.CaptureOutput(request2) + outpt = tests.CaptureStdOutput(request2) if strings.Contains(outpt, "Applicant is not eligible to apply for creditcard") { result = "success" } @@ -212,7 +212,7 @@ func testapplication(t *testing.T, e engine.Engine) { response.Body.Close() assert.Nil(t, err) } - outpt = tests.CaptureOutput(request3) + outpt = tests.CaptureStdOutput(request3) if strings.Contains(outpt, "Applicant is not eligible to apply for creditcard") { result = "success" } @@ -232,7 +232,7 @@ func testapplication(t *testing.T, e engine.Engine) { response.Body.Close() assert.Nil(t, err) } - outpt = tests.CaptureOutput(request4) + outpt = tests.CaptureStdOutput(request4) if strings.Contains(outpt, "Applicant is not eligible to apply for creditcard") { result = "success" } @@ -251,7 +251,7 @@ func testapplication(t *testing.T, e engine.Engine) { response.Body.Close() assert.Nil(t, err) } - outpt = tests.CaptureOutput(request5) + outpt = tests.CaptureStdOutput(request5) if strings.Contains(outpt, "Rule fired") { result = "success" } @@ -271,7 +271,7 @@ func testapplication(t *testing.T, e engine.Engine) { response.Body.Close() assert.Nil(t, err) } - outpt = tests.CaptureOutput(request6) + outpt = tests.CaptureStdOutput(request6) if strings.Contains(outpt, "Rule fired") { result = "success" } @@ -290,7 +290,7 @@ func testapplication(t *testing.T, e engine.Engine) { response.Body.Close() assert.Nil(t, err) } - outpt = tests.CaptureOutput(request7) + outpt = tests.CaptureStdOutput(request7) if strings.Contains(outpt, "Rule fired") { result = "success" } @@ -310,7 +310,7 @@ func testapplication(t *testing.T, e engine.Engine) { response.Body.Close() assert.Nil(t, err) } - outpt = tests.CaptureOutput(request8) + outpt = tests.CaptureStdOutput(request8) if strings.Contains(outpt, "Rule fired: Rejected") { result = "success" } diff --git a/examples/flogo/simple-kafka/kakfa_test.go b/examples/flogo/simple-kafka/kakfa_test.go index ee722f0..db90410 100644 --- a/examples/flogo/simple-kafka/kakfa_test.go +++ b/examples/flogo/simple-kafka/kakfa_test.go @@ -72,7 +72,7 @@ func testApplication(t *testing.T, e engine.Engine) { request := func() { publish(`{"type":"grocery","totalPrice":"2001.0"}`, producer) } - outpt := tests.CaptureOutput(request) + outpt := tests.CaptureStdOutput(request) var result string if strings.Contains(outpt, "Rule fired") { diff --git a/examples/flogo/simple/simple_test.go b/examples/flogo/simple/simple_test.go index 1af0f87..89620be 100644 --- a/examples/flogo/simple/simple_test.go +++ b/examples/flogo/simple/simple_test.go @@ -41,7 +41,7 @@ func testApplication(t *testing.T, e engine.Engine) { response.Body.Close() } - outpt := tests.CaptureOutput(request) + outpt := tests.CaptureStdOutput(request) var result string if strings.Contains(outpt, "Rule fired") { @@ -61,7 +61,7 @@ func testApplication(t *testing.T, e engine.Engine) { response.Body.Close() } - outpt = tests.CaptureOutput(request1) + outpt = tests.CaptureStdOutput(request1) if strings.Contains(outpt, "Rule fired") { result = "success" } @@ -79,7 +79,7 @@ func testApplication(t *testing.T, e engine.Engine) { response.Body.Close() } - outpt = tests.CaptureOutput(request2) + outpt = tests.CaptureStdOutput(request2) if !strings.Contains(outpt, "Rule fired") { result = "success" } diff --git a/examples/flogo/trackntrace/trackntrace_test.go b/examples/flogo/trackntrace/trackntrace_test.go index 2a92f8b..3186dcd 100644 --- a/examples/flogo/trackntrace/trackntrace_test.go +++ b/examples/flogo/trackntrace/trackntrace_test.go @@ -40,7 +40,7 @@ func testApplication(t *testing.T, e engine.Engine) { response.Body.Close() } - outpt := tests.CaptureOutput(request) + outpt := tests.CaptureStdOutput(request) var result string if strings.Contains(outpt, "target state [sitting]") { result = "success" @@ -58,7 +58,7 @@ func testApplication(t *testing.T, e engine.Engine) { response.Body.Close() } - outpt = tests.CaptureOutput(request1) + outpt = tests.CaptureStdOutput(request1) if strings.Contains(outpt, "target state [sitting]") { result = "success" } @@ -75,7 +75,7 @@ func testApplication(t *testing.T, e engine.Engine) { response.Body.Close() } - outpt = tests.CaptureOutput(request2) + outpt = tests.CaptureStdOutput(request2) if strings.Contains(outpt, "target state [moving]") { result = "success" } @@ -92,7 +92,7 @@ func testApplication(t *testing.T, e engine.Engine) { response.Body.Close() } - outpt = tests.CaptureOutput(request3) + outpt = tests.CaptureStdOutput(request3) if strings.Contains(outpt, "Tuple inserted successfully") { result = "success" } diff --git a/examples/rulesapp/ruleapp_test.go b/examples/rulesapp/ruleapp_test.go index 3783ed5..7590694 100644 --- a/examples/rulesapp/ruleapp_test.go +++ b/examples/rulesapp/ruleapp_test.go @@ -12,7 +12,7 @@ func TestRuleApp(t *testing.T) { request := func() { tests.Command("go", "run", "main.go") } - output := tests.CaptureOutput(request) + output := tests.CaptureStdOutput(request) var result string if strings.Contains(output, "Rule fired") && strings.Contains(output, "Loaded tuple descriptor") { result = "success" diff --git a/ruleapi/tests/common.go b/ruleapi/tests/common.go index 93be6b8..720fae0 100644 --- a/ruleapi/tests/common.go +++ b/ruleapi/tests/common.go @@ -70,7 +70,7 @@ type txnCtx struct { TxnCnt int } -func CaptureOutput(f func()) string { +func CaptureStdOutput(f func()) string { reader, writer, err := os.Pipe() if err != nil { panic(err) From 33fa36da7e658d8a1063859f1d436360c4fb5517 Mon Sep 17 00:00:00 2001 From: nthota Date: Mon, 19 Aug 2019 15:28:13 +0530 Subject: [PATCH 056/125] assigned tuple store to rete & support for redis to rete --- common/model/tuplekey.go | 9 ++++++++- examples/rulesapp/main.go | 4 ++-- examples/rulesapp/rsconfig.json | 5 +++-- rete/internal/redis/rjointablerowimpl.go | 7 +++---- rete/internal/redis/rretehandle.go | 2 +- ruleapi/rulesession.go | 1 + 6 files changed, 18 insertions(+), 10 deletions(-) diff --git a/common/model/tuplekey.go b/common/model/tuplekey.go index fab6092..568dd92 100644 --- a/common/model/tuplekey.go +++ b/common/model/tuplekey.go @@ -2,9 +2,10 @@ package model import ( "fmt" - "github.com/project-flogo/core/data/coerce" "reflect" "strings" + + "github.com/project-flogo/core/data/coerce" ) // TupleKey primary key of a tuple @@ -86,6 +87,12 @@ func NewTupleKeyWithKeyValues(tupleType TupleType, values ...interface{}) (tuple for i, keyProp := range td.GetKeyProps() { tdp := td.GetProperty(keyProp) val := values[i] + + switch typ := val.(type) { + case map[string]interface{}: + val = typ[keyProp] + } + coerced, err := coerce.ToType(val, tdp.PropType) if err == nil { diff --git a/examples/rulesapp/main.go b/examples/rulesapp/main.go index b90e756..dd59bd4 100644 --- a/examples/rulesapp/main.go +++ b/examples/rulesapp/main.go @@ -25,8 +25,8 @@ func main() { return } - //content := getFileContent("src/github.com/project-flogo/rules/examples/rulesapp/rsconfig.json") - rs, _ := ruleapi.GetOrCreateRuleSessionFromConfig("asession", "{}") + content := getFileContent("src/github.com/project-flogo/rules/examples/rulesapp/rsconfig.json") + rs, _ := ruleapi.GetOrCreateRuleSessionFromConfig("asession", content) //Create a RuleSession //// check for name "Bob" in n1 diff --git a/examples/rulesapp/rsconfig.json b/examples/rulesapp/rsconfig.json index 2919ad7..2681c7d 100644 --- a/examples/rulesapp/rsconfig.json +++ b/examples/rulesapp/rsconfig.json @@ -4,8 +4,9 @@ "store-ref": "redis" }, "rete": { - "jt-ref": "mem", - "idgen-ref": "mem" + "jt-ref": "redis", + "idgen-ref": "redis", + "jt":"redis" }, "stores": { "mem": { diff --git a/rete/internal/redis/rjointablerowimpl.go b/rete/internal/redis/rjointablerowimpl.go index bed501b..af07241 100644 --- a/rete/internal/redis/rjointablerowimpl.go +++ b/rete/internal/redis/rjointablerowimpl.go @@ -1,11 +1,12 @@ package redis import ( + "strconv" + "strings" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/redisutils" "github.com/project-flogo/rules/rete/internal/types" - "strconv" - "strings" ) type joinTableRowImpl struct { @@ -65,8 +66,6 @@ func createRow(jtKey string, rowID string, key string, nw types.Network) types.J for _, key := range values { tupleKey := model.FromStringKey(key) tuple := nw.GetTupleStore().GetTupleByKey(tupleKey) - //ks := tupleKey.String() - //fmt.Printf(ks) handle := newReteHandleImpl(nw, tuple) handles = append(handles, handle) } diff --git a/rete/internal/redis/rretehandle.go b/rete/internal/redis/rretehandle.go index 33b72a1..bcb770e 100644 --- a/rete/internal/redis/rretehandle.go +++ b/rete/internal/redis/rretehandle.go @@ -22,7 +22,7 @@ func newReteHandleImpl(nw types.Network, tuple model.Tuple) types.ReteHandle { func (hdl *reteHandleImpl) SetTuple(tuple model.Tuple) { hdl.tuple = tuple - if tuple == nil { + if tuple != nil { hdl.tupleKey = tuple.GetKey() } } diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index efe14d0..e2a8b11 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -232,6 +232,7 @@ func (rs *rulesessionImpl) SetStore(store model.TupleStore) error { return fmt.Errorf("RuleSession already started") } rs.tupleStore = store + rs.reteNetwork.SetTupleStore(store) rs.reteNetwork.RegisterRtcTransactionHandler(internalTxnHandler, nil) return nil } From 64a8568ff9c60dbd2b3742567b8de8aae8bc517d Mon Sep 17 00:00:00 2001 From: Ramesh Polishetti Date: Tue, 20 Aug 2019 19:40:42 +0530 Subject: [PATCH 057/125] Merge branch feature-invoke-activity --- common/model/types.go | 10 +- config/config.go | 114 +++++++- config/config_test.go | 55 +++- config/manager.go | 3 +- examples/flogo/creditcard/README.md | 60 +++++ examples/flogo/creditcard/flogo.json | 313 ++++++++++++++++++++++ examples/flogo/creditcard/functions.go | 177 ++++++++++++ examples/flogo/creditcard/main.go | 68 +++++ examples/flogo/creditcard/sanity.sh | 72 +++++ examples/flogo/invokeservice/README.md | 62 +++++ examples/flogo/invokeservice/flogo.json | 230 ++++++++++++++++ examples/flogo/invokeservice/functions.go | 109 ++++++++ examples/flogo/invokeservice/main.go | 69 +++++ examples/flogo/simple-kafka/flogo.json | 20 +- examples/flogo/simple/flogo.json | 38 ++- examples/flogo/trackntrace/flogo.json | 110 ++++++-- examples/rulesapp/main.go | 17 +- examples/trackntrace/trackntrace_test.go | 43 ++- rete/conflict.go | 7 +- ruleaction/action.go | 2 + ruleapi/actionservice.go | 202 ++++++++++++++ ruleapi/actionservice_test.go | 52 ++++ ruleapi/rule.go | 26 +- ruleapi/rulesession.go | 20 +- ruleapi/servicecontext.go | 179 +++++++++++++ ruleapi/tests/common.go | 20 +- ruleapi/tests/expr_1_test.go | 5 +- ruleapi/tests/expr_2_test.go | 5 +- ruleapi/tests/expr_3_test.go | 5 +- ruleapi/tests/expr_4_test.go | 5 +- ruleapi/tests/expr_5_test.go | 5 +- ruleapi/tests/identifier_1_test.go | 3 +- ruleapi/tests/identifier_2_test.go | 8 +- ruleapi/tests/retract_1_test.go | 2 +- ruleapi/tests/rtctxn_10_test.go | 2 +- ruleapi/tests/rtctxn_11_test.go | 4 +- ruleapi/tests/rtctxn_12_test.go | 4 +- ruleapi/tests/rtctxn_13_test.go | 4 +- ruleapi/tests/rtctxn_14_test.go | 4 +- ruleapi/tests/rtctxn_15_test.go | 2 +- ruleapi/tests/rtctxn_16_test.go | 2 +- ruleapi/tests/rtctxn_1_test.go | 8 +- ruleapi/tests/rtctxn_2_test.go | 9 +- ruleapi/tests/rtctxn_3_test.go | 9 +- ruleapi/tests/rtctxn_4_test.go | 9 +- ruleapi/tests/rtctxn_5_test.go | 31 +-- ruleapi/tests/rtctxn_6_test.go | 35 +-- ruleapi/tests/rtctxn_7_test.go | 13 +- ruleapi/tests/rtctxn_8_test.go | 9 +- ruleapi/tests/rtctxn_9_test.go | 2 +- ruleapi/tests/rules_1_test.go | 9 +- ruleapi/tests/rules_2_test.go | 2 +- ruleapi/tests/rules_3_test.go | 4 +- 53 files changed, 2089 insertions(+), 189 deletions(-) create mode 100644 examples/flogo/creditcard/README.md create mode 100644 examples/flogo/creditcard/flogo.json create mode 100644 examples/flogo/creditcard/functions.go create mode 100644 examples/flogo/creditcard/main.go create mode 100644 examples/flogo/creditcard/sanity.sh create mode 100644 examples/flogo/invokeservice/README.md create mode 100644 examples/flogo/invokeservice/flogo.json create mode 100644 examples/flogo/invokeservice/functions.go create mode 100644 examples/flogo/invokeservice/main.go create mode 100644 ruleapi/actionservice.go create mode 100644 ruleapi/actionservice_test.go create mode 100644 ruleapi/servicecontext.go diff --git a/common/model/types.go b/common/model/types.go index eb2807d..580661b 100644 --- a/common/model/types.go +++ b/common/model/types.go @@ -12,7 +12,7 @@ type Rule interface { GetName() string GetIdentifiers() []TupleType GetConditions() []Condition - GetActionFn() ActionFunction + GetActionService() ActionService String() string GetPriority() int GetDeps() map[TupleType]map[string]bool @@ -23,7 +23,7 @@ type Rule interface { type MutableRule interface { Rule AddCondition(conditionName string, idrs []string, cFn ConditionEvaluator, ctx RuleContext) (err error) - SetAction(actionFn ActionFunction) + SetActionService(actionService ActionService) SetPriority(priority int) SetContext(ctx RuleContext) AddExprCondition(conditionName string, cExpr string, ctx RuleContext) error @@ -83,6 +83,12 @@ type ConditionEvaluator func(string, string, map[TupleType]Tuple, RuleContext) b //i.e part of the server side API type ActionFunction func(context.Context, RuleSession, string, map[TupleType]Tuple, RuleContext) +// ActionService action service +type ActionService interface { + SetInput(input map[string]interface{}) + Execute(context.Context, RuleSession, string, map[TupleType]Tuple, RuleContext) (done bool, err error) +} + //StartupRSFunction is called once after creation of a RuleSession type StartupRSFunction func(ctx context.Context, rs RuleSession, sessionCtx map[string]interface{}) (err error) diff --git a/config/config.go b/config/config.go index 85836be..88b8b7a 100644 --- a/config/config.go +++ b/config/config.go @@ -3,31 +3,43 @@ package config import ( "bytes" "encoding/json" + "fmt" "strconv" "github.com/project-flogo/core/data/metadata" "github.com/project-flogo/rules/common/model" ) +const ( + // TypeServiceFunction function based rule service + TypeServiceFunction = "function" + // TypeServiceActivity activity based rule service + TypeServiceActivity = "activity" + // TypeServiceAction action based rule service + TypeServiceAction = "action" +) + // RuleSessionDescriptor is a collection of rules to be loaded type RuleActionDescriptor struct { Name string `json:"name"` IOMetadata *metadata.IOMetadata `json:"metadata"` Rules []*RuleDescriptor `json:"rules"` + Services []*ServiceDescriptor `json:"services,omitempty"` } type RuleSessionDescriptor struct { - Rules []*RuleDescriptor `json:"rules"` + Rules []*RuleDescriptor `json:"rules"` + Services []*ServiceDescriptor `json:"services,omitempty"` } // RuleDescriptor defines a rule type RuleDescriptor struct { - Name string - Conditions []*ConditionDescriptor - ActionFunc model.ActionFunction - Priority int - Identifiers []string + Name string + Conditions []*ConditionDescriptor + ActionService *ActionServiceDescriptor + Priority int + Identifiers []string } // ConditionDescriptor defines a condition in a rule @@ -38,13 +50,30 @@ type ConditionDescriptor struct { Expression string } +// ActionServiceDescriptor defines rule action service +type ActionServiceDescriptor struct { + Service string `json:"service"` + Input map[string]interface{} `json:"input,omitempty"` +} + +// ServiceDescriptor defines a functional target that may be invoked by a rule +type ServiceDescriptor struct { + Name string + Description string + Type string + Function model.ActionFunction + Ref string + Settings map[string]interface{} +} + +// UnmarshalJSON unmarshals JSON into struct RuleDescriptor func (c *RuleDescriptor) UnmarshalJSON(d []byte) error { ser := &struct { - Name string `json:"name"` - Conditions []*ConditionDescriptor `json:"conditions"` - ActionFuncId string `json:"actionFunction"` - Priority int `json:"priority"` - Identifiers []string `json:"identifiers"` + Name string `json:"name"` + Conditions []*ConditionDescriptor `json:"conditions"` + ActionService *ActionServiceDescriptor `json:"actionService,omitempty"` + Priority int `json:"priority"` + Identifiers []string `json:"identifiers"` }{} if err := json.Unmarshal(d, ser); err != nil { @@ -53,13 +82,14 @@ func (c *RuleDescriptor) UnmarshalJSON(d []byte) error { c.Name = ser.Name c.Conditions = ser.Conditions - c.ActionFunc = GetActionFunction(ser.ActionFuncId) + c.ActionService = ser.ActionService c.Priority = ser.Priority c.Identifiers = ser.Identifiers return nil } +// MarshalJSON returns JSON encoding of RuleDescriptor func (c *RuleDescriptor) MarshalJSON() ([]byte, error) { buffer := bytes.NewBufferString("{") buffer.WriteString("\"name\":" + "\"" + c.Name + "\",") @@ -82,13 +112,16 @@ func (c *RuleDescriptor) MarshalJSON() ([]byte, error) { buffer.Truncate(buffer.Len() - 1) buffer.WriteString("],") - actionFunctionID := GetActionFunctionID(c.ActionFunc) - buffer.WriteString("\"actionFunction\":\"" + actionFunctionID + "\",") + jsonActionService, err := json.Marshal(c.ActionService) + if err == nil { + buffer.WriteString("\"actionService\":" + string(jsonActionService) + ",") + } buffer.WriteString("\"priority\":" + strconv.Itoa(c.Priority) + "}") return buffer.Bytes(), nil } +// UnmarshalJSON unmarshals JSON into struct ConditionDescriptor func (c *ConditionDescriptor) UnmarshalJSON(d []byte) error { ser := &struct { Name string `json:"name"` @@ -109,6 +142,7 @@ func (c *ConditionDescriptor) UnmarshalJSON(d []byte) error { return nil } +// MarshalJSON returns JSON encoding of ConditionDescriptor func (c *ConditionDescriptor) MarshalJSON() ([]byte, error) { buffer := bytes.NewBufferString("{") buffer.WriteString("\"name\":" + "\"" + c.Name + "\",") @@ -128,6 +162,58 @@ func (c *ConditionDescriptor) MarshalJSON() ([]byte, error) { return buffer.Bytes(), nil } +// UnmarshalJSON unmarshals JSON into struct ServiceDescriptor +func (sd *ServiceDescriptor) UnmarshalJSON(d []byte) error { + ser := &struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + Type string `json:"type"` + FunctionID string `json:"function,omitempty"` + Ref string `json:"ref"` + Settings map[string]interface{} `json:"settings,omitempty"` + }{} + + if err := json.Unmarshal(d, ser); err != nil { + return err + } + + sd.Name = ser.Name + sd.Description = ser.Description + if ser.Type == TypeServiceFunction || ser.Type == TypeServiceActivity || ser.Type == TypeServiceAction { + sd.Type = ser.Type + } else { + return fmt.Errorf("unsupported type - '%s' is referenced in the service '%s'", ser.Type, ser.Name) + } + if ser.FunctionID != "" { + fn := GetActionFunction(ser.FunctionID) + if fn == nil { + return fmt.Errorf("function - '%s' not found", ser.FunctionID) + } + sd.Function = fn + } + sd.Ref = ser.Ref + sd.Settings = ser.Settings + + return nil +} + +// MarshalJSON returns JSON encoding of ServiceDescriptor +func (sd *ServiceDescriptor) MarshalJSON() ([]byte, error) { + buffer := bytes.NewBufferString("{") + buffer.WriteString("\"name\":" + "\"" + sd.Name + "\",") + buffer.WriteString("\"description\":" + "\"" + sd.Description + "\",") + buffer.WriteString("\"type\":" + "\"" + sd.Type + "\",") + functionID := GetActionFunctionID(sd.Function) + buffer.WriteString("\"function\":" + "\"" + functionID + "\",") + buffer.WriteString("\"ref\":" + "\"" + sd.Ref + "\",") + jsonSettings, err := json.Marshal(sd.Settings) + if err == nil { + buffer.WriteString("\"settings\":" + string(jsonSettings) + "}") + } + + return buffer.Bytes(), nil +} + //metadata support type DefinitionConfig struct { Name string `json:"name"` diff --git a/config/config_test.go b/config/config_test.go index 983ff3e..ea42644 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -21,7 +21,9 @@ var testRuleSessionDescriptorJson = `{ "evaluator": "checkForBob" } ], - "actionFunction": "checkForBobAction" + "actionService": { + "service": "checkForBobService" + } }, { "name": "n1.name == Bob && n1.name == n2.name", @@ -39,8 +41,24 @@ var testRuleSessionDescriptorJson = `{ "evaluator": "checkSameNamesCondition" } ], - "actionFunction": "checkSameNamesAction" + "actionService": { + "service": "checkSameNamesService" + } } + ], + "services": [ + { + "name": "checkForBobService", + "description": "service checkForBobService", + "type": "function", + "function": "checkForBobAction" + }, + { + "name": "checkSameNamesService", + "description": "service checkSameNamesService", + "type": "function", + "function": "checkSameNamesAction" + } ] } ` @@ -59,35 +77,32 @@ func TestDeserialize(t *testing.T) { assert.Nil(t, err) assert.NotNil(t, ruleSessionDescriptor.Rules) assert.Equal(t, 2, len(ruleSessionDescriptor.Rules)) + assert.Equal(t, 2, len(ruleSessionDescriptor.Services)) + // rule-0 r1Cfg := ruleSessionDescriptor.Rules[0] assert.Equal(t, "n1.name == Bob", r1Cfg.Name) assert.NotNil(t, r1Cfg.Conditions) assert.Equal(t, 1, len(r1Cfg.Conditions)) - - sf1 := reflect.ValueOf(checkForBobAction) - sf2 := reflect.ValueOf(r1Cfg.ActionFunc) - assert.Equal(t, sf1.Pointer(), sf2.Pointer()) + assert.Equal(t, "checkForBobService", r1Cfg.ActionService.Service) r1c1Cfg := r1Cfg.Conditions[0] assert.Equal(t, "c1", r1c1Cfg.Name) assert.NotNil(t, r1c1Cfg.Identifiers) assert.Equal(t, 1, len(r1c1Cfg.Identifiers)) - sf1 = reflect.ValueOf(checkForBob) - sf2 = reflect.ValueOf(r1c1Cfg.Evaluator) + sf1 := reflect.ValueOf(checkForBob) + sf2 := reflect.ValueOf(r1c1Cfg.Evaluator) assert.Equal(t, sf1.Pointer(), sf2.Pointer()) + // rule-1 r2Cfg := ruleSessionDescriptor.Rules[1] assert.Equal(t, "n1.name == Bob && n1.name == n2.name", r2Cfg.Name) assert.NotNil(t, r2Cfg.Conditions) assert.Equal(t, 2, len(r2Cfg.Conditions)) - - sf1 = reflect.ValueOf(checkSameNamesAction) - sf2 = reflect.ValueOf(r2Cfg.ActionFunc) - assert.Equal(t, sf1.Pointer(), sf2.Pointer()) + assert.Equal(t, "checkSameNamesService", r2Cfg.ActionService.Service) r2c1Cfg := r2Cfg.Conditions[0] assert.Equal(t, "c1", r2c1Cfg.Name) @@ -106,6 +121,22 @@ func TestDeserialize(t *testing.T) { sf1 = reflect.ValueOf(checkSameNamesCondition) sf2 = reflect.ValueOf(r2c2Cfg.Evaluator) assert.Equal(t, sf1.Pointer(), sf2.Pointer()) + + // service-0 + s1Cfg := ruleSessionDescriptor.Services[0] + assert.Equal(t, "checkForBobService", s1Cfg.Name) + assert.Equal(t, "service checkForBobService", s1Cfg.Description) + sf1 = reflect.ValueOf(checkForBobAction) + sf2 = reflect.ValueOf(s1Cfg.Function) + assert.Equal(t, sf1.Pointer(), sf2.Pointer()) + + // service-1 + s2Cfg := ruleSessionDescriptor.Services[1] + assert.Equal(t, "checkSameNamesService", s2Cfg.Name) + assert.Equal(t, "service checkSameNamesService", s2Cfg.Description) + sf1 = reflect.ValueOf(checkSameNamesAction) + sf2 = reflect.ValueOf(s2Cfg.Function) + assert.Equal(t, sf1.Pointer(), sf2.Pointer()) } // TEST FUNCTIONS diff --git a/config/manager.go b/config/manager.go index 25d43be..2a99ec2 100644 --- a/config/manager.go +++ b/config/manager.go @@ -43,7 +43,8 @@ func (m *ResourceManager) GetResource(id string) interface{} { func (m *ResourceManager) GetRuleSessionDescriptor(uri string) (*RuleSessionDescriptor, error) { if strings.HasPrefix(uri, uriSchemeRes) { - return &RuleSessionDescriptor{m.configs[uri[len(uriSchemeRes):]].Rules}, nil + id := uri[len(uriSchemeRes):] + return &RuleSessionDescriptor{Rules: m.configs[id].Rules, Services: m.configs[id].Services}, nil } return nil, errors.New("cannot find RuleSession: " + uri) diff --git a/examples/flogo/creditcard/README.md b/examples/flogo/creditcard/README.md new file mode 100644 index 0000000..8486172 --- /dev/null +++ b/examples/flogo/creditcard/README.md @@ -0,0 +1,60 @@ +## Flogo Rules based Creditcard application + + +This example demonstrates rule based processing of credit card application. In this example three tuples are used, tuples description is given below. + + +* `UserAccount` tuple is always stored in network, while the other tuples `NewAccount` and `UpdateCreditScore` are removed after usage as ttl is given as 0. + + +## Usage + +Get the repo and in this example main.go, functions.go both are available. We can directly build and run the app or create flogo rule app and run it. + +#### Conditions + +``` +cBadUser : Check for new user input data - check if age <18 and >=45, empty address and salary less than 10k +cNewUser : Check for new user input data - check if age >=18 and <= 44, address and salary >= 10k +cUserIdMatch : Check for id match from 'UserAccount' and 'UpdateCreditScore' tuples +cUserCreditScore : Check for CreditScore >= 750 && < 820 +cUserLowCreditScore : Check for CreditScore < 750 +cUserHighCreditScore : Check for CreditScore >= 820 && <= 900 +``` +#### Actions +``` +aBadUser : Executes when age - < 18 and >=45, address empty, salary less than 10k +aNewUser : Add the newuser info to userAccount tuple +aApproveWithLowerLimit : Provides credit card application status approved with lower credit limit +aApproveWithHigherLimit : Provides credit card application status approved with higher credit limit +aUserReject : Rejects when lower Credit score provided and retracts NewAccount +``` +### Direct build and run +``` +cd $GOPATH/src/github.com/project-flogo/rules/examples/flogo/creditcard +go build +./creditcard +``` +### Create app using flogo cli +``` +cd $GOPATH/src/github.com/project-flogo/rules/examples/flogo/creditcard +flogo create -f flogo.json creditcard +cp functions.go creditcard/src +cd creditcard +flogo build +./bin/creditcard +``` + +* Input new user details + +``` +$ curl -X PUT http://localhost:7777/newaccount -H 'Content-Type: application/json' -d '{"Name":"Test","Age":"26","Income":"60100","Address":"TEt","Id":"12312","Gender":"male","maritalStatus":"single"}' +``` +* Update credit score details of the user + +``` +$ curl -X PUT http://localhost:7777/credit -H 'Content-Type: application/json' -d '{"Id":12312,"creditScore":680}' +``` + +* Application status will be printed on the console + \ No newline at end of file diff --git a/examples/flogo/creditcard/flogo.json b/examples/flogo/creditcard/flogo.json new file mode 100644 index 0000000..b4524c7 --- /dev/null +++ b/examples/flogo/creditcard/flogo.json @@ -0,0 +1,313 @@ +{ + "name": "cardapp", + "type": "flogo:app", + "version": "0.0.1", + "description": "Sample Flogo App", + "appModel": "1.0.0", + "imports": [ + "github.com/project-flogo/contrib/trigger/rest", + "github.com/project-flogo/rules/ruleaction", + "github.com/project-flogo/legacybridge" + ], + "triggers": [ + { + "id": "receive_http_message", + "ref": "github.com/project-flogo/contrib/trigger/rest", + "settings": { + "port": "7777" + }, + "handlers": [ + { + "settings": { + "method": "PUT", + "path": "/newaccount" + }, + "actions": [ + { + "id": "simple_rule", + "input": { + "tupletype": "NewAccount", + "values": "=$.content" + } + } + ] + }, + { + "settings": { + "method": "PUT", + "path": "/credit" + }, + "actions": [ + { + "id": "simple_rule", + "input": { + "tupletype": "UpdateCreditScore", + "values": "=$.content" + } + } + ] + } + ] + } + ], + "resources": [ + { + "id": "rulesession:simple", + "data": { + "metadata": { + "input": [ + { + "name": "values", + "type": "string" + }, + { + "name": "tupletype", + "type": "string" + } + ], + "output": [ + { + "name": "outputData", + "type": "any" + } + ] + }, + "rules": [ + { + "name": "UserData", + "conditions": [ + { + "name": "cBadUser", + "identifiers": [ + "NewAccount" + ], + "evaluator": "cBadUser" + } + ], + "actionService": { + "service": "FunctionService" + } + }, + { + "name": "NewUser", + "conditions": [ + { + "name": "cNewUser", + "identifiers": [ + "NewAccount" + ], + "evaluator": "cNewUser" + } + ], + "actionService": { + "service": "FunctionService1" + } + }, + { + "name": "NewUser1", + "conditions": [ + { + "name": "cUserIdMatch", + "identifiers": [ + "UpdateCreditScore", + "UserAccount" + ], + "evaluator": "cUserIdMatch" + }, + { + "name": "cUserCreditScore", + "identifiers": [ + "UpdateCreditScore" + ], + "evaluator": "cUserCreditScore" + } + ], + "actionService": { + "service": "FunctionService2" + } + }, + { + "name": "NewUser2", + "conditions": [ + { + "name": "cUserIdMatch", + "identifiers": [ + "UpdateCreditScore", + "UserAccount" + ], + "evaluator": "cUserIdMatch" + }, + { + "name": "cUserCreditScore", + "identifiers": [ + "UpdateCreditScore" + ], + "evaluator": "cUserHighCreditScore" + } + ], + "actionService": { + "service": "FunctionService3" + } + }, + { + "name": "Rejected", + "conditions": [ + { + "name": "cUserIdMatch", + "identifiers": [ + "UpdateCreditScore", + "UserAccount" + ], + "evaluator": "cUserIdMatch" + }, + { + "name": "cUserCreditScore", + "identifiers": [ + "UpdateCreditScore" + ], + "evaluator": "cUserLowCreditScore" + } + ], + "actionService": { + "service": "FunctionService4" + } + } + ], + "services": [ + { + "name": "FunctionService", + "description": "function service for aBadUser", + "function": "aBadUser" + }, + { + "name": "FunctionService1", + "description": "function service for aNewUser", + "function": "aNewUser" + }, + { + "name": "FunctionService2", + "description": "function service for aApproveWithLowerLimit", + "function": "aApproveWithLowerLimit" + }, + { + "name": "FunctionService3", + "description": "function service for aApproveWithHigherLimit", + "function": "aApproveWithHigherLimit" + }, + { + "name": "FunctionService4", + "description": "function service for aUserReject", + "function": "aUserReject" + } + ] + } + } + ], + "actions": [ + { + "ref": "github.com/project-flogo/rules/ruleaction", + "settings": { + "ruleSessionURI": "res://rulesession:simple", + "tds": [ + { + "name": "UserAccount", + "properties": [ + { + "name": "Id", + "pk-index": 0, + "type": "int" + }, + { + "name": "Name", + "type": "string" + }, + { + "name": "Gender", + "type": "string" + }, + { + "name": "Age", + "type": "int" + }, + { + "name": "Address", + "type": "string" + }, + { + "name": "Income", + "type": "int" + }, + { + "name": "maritalStatus", + "type": "string" + }, + { + "name": "creditScore", + "type": "int" + }, + { + "name": "approvedLimit", + "type": "int" + }, + { + "name": "appStatus", + "type": "string" + } + ] + }, + { + "name": "NewAccount", + "ttl": 0, + "properties": [ + { + "name": "Id", + "pk-index": 0, + "type": "int" + }, + { + "name": "Name", + "type": "string" + }, + { + "name": "Gender", + "type": "string" + }, + { + "name": "Age", + "type": "int" + }, + { + "name": "Address", + "type": "string" + }, + { + "name": "Income", + "type": "int" + }, + { + "name": "maritalStatus", + "type": "string" + } + ] + }, + { + "name": "UpdateCreditScore", + "properties": [ + { + "name": "Id", + "pk-index": 0, + "type": "int" + }, + { + "name": "creditScore", + "type": "int" + } + ], + "ttl": 0 + } + ] + }, + "id": "simple_rule" + } + ] +} \ No newline at end of file diff --git a/examples/flogo/creditcard/functions.go b/examples/flogo/creditcard/functions.go new file mode 100644 index 0000000..0587fb6 --- /dev/null +++ b/examples/flogo/creditcard/functions.go @@ -0,0 +1,177 @@ +package main + +import ( + "context" + "fmt" + + "github.com/project-flogo/rules/config" + + "github.com/project-flogo/rules/common/model" +) + +//add this sample file to your flogo project +func init() { + + // rule UserData + config.RegisterConditionEvaluator("cNewUser", cNewUser) + config.RegisterActionFunction("aNewUser", aNewUser) + + // rule NewUser + config.RegisterConditionEvaluator("cBadUser", cBadUser) + config.RegisterActionFunction("aBadUser", aBadUser) + + // rule NewUserApprove + config.RegisterConditionEvaluator("cUserIdMatch", cUserIdMatch) + config.RegisterConditionEvaluator("cUserCreditScore", cUserCreditScore) + config.RegisterActionFunction("aApproveWithLowerLimit", aApproveWithLowerLimit) + + // // rule NewUserReject + config.RegisterConditionEvaluator("cUserIdMatch", cUserIdMatch) + config.RegisterConditionEvaluator("cUserLowCreditScore", cUserLowCreditScore) + config.RegisterActionFunction("aUserReject", aUserReject) + + // // rule NewUserApprove1 + config.RegisterConditionEvaluator("cUserIdMatch", cUserIdMatch) + config.RegisterConditionEvaluator("cUserHighCreditScore", cUserHighCreditScore) + config.RegisterActionFunction("aApproveWithHigherLimit", aApproveWithHigherLimit) +} + +func cNewUser(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + newaccount := tuples["NewAccount"] + if newaccount != nil { + address, _ := newaccount.GetString("Address") + age, _ := newaccount.GetInt("Age") + income, _ := newaccount.GetInt("Income") + if address != "" && age >= 18 && income >= 10000 && age <= 44 { + return true + } + } + return false +} + +func aNewUser(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + fmt.Println("Rule fired:", ruleName) + newaccount := tuples["NewAccount"] + Id, _ := newaccount.GetInt("Id") + name, _ := newaccount.GetString("Name") + address, _ := newaccount.GetString("Address") + age, _ := newaccount.GetInt("Age") + income, _ := newaccount.GetInt("Income") + userInfo, _ := model.NewTupleWithKeyValues("UserAccount", Id) + userInfo.SetString(ctx, "Name", name) + userInfo.SetString(ctx, "Addresss", address) + userInfo.SetInt(ctx, "Age", age) + userInfo.SetInt(ctx, "Income", income) + fmt.Println(userInfo) + rs.Assert(ctx, userInfo) + fmt.Println("User information received") + +} + +func cBadUser(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + newaccount := tuples["NewAccount"] + if newaccount != nil { + address, _ := newaccount.GetString("Address") + age, _ := newaccount.GetInt("Age") + income, _ := newaccount.GetInt("Income") + if address == "" || age < 18 || income < 10000 || age >= 45 { + return true + } + } + return false +} + +func aBadUser(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + fmt.Println("Rule fired:", ruleName) + fmt.Println("Applicant is not eligible to apply for creditcard") +} + +func cUserIdMatch(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + userInfo := tuples["UserAccount"] + updateScore := tuples["UpdateCreditScore"] + if userInfo != nil || updateScore != nil { + userId, _ := userInfo.GetInt("Id") + newUserId, _ := updateScore.GetInt("Id") + if userId == newUserId { + fmt.Println("Userid match found") + return true + } + } + return false +} + +func cUserCreditScore(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + updateScore := tuples["UpdateCreditScore"] + if updateScore != nil { + CreditScore, _ := updateScore.GetInt("creditScore") + if CreditScore >= 750 && CreditScore < 820 { + fmt.Println("cUserCreditScore") + return true + } + } + return false +} + +func cUserLowCreditScore(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + updateScore := tuples["UpdateCreditScore"] + if updateScore != nil { + CreditScore, _ := updateScore.GetInt("creditScore") + if CreditScore < 750 { + fmt.Println("cUserLowCreditScore") + return true + } + } + return false +} + +func cUserHighCreditScore(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + updateScore := tuples["UpdateCreditScore"] + if updateScore != nil { + CreditScore, _ := updateScore.GetInt("creditScore") + if CreditScore >= 820 && CreditScore <= 900 { + fmt.Println("cUserHighCreditScore") + return true + } + } + return false +} + +func aApproveWithLowerLimit(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + fmt.Println("Rule fired:", ruleName) + userInfo := tuples["UserAccount"] + updateScore := tuples["UpdateCreditScore"] + CreditScore, _ := updateScore.GetInt("creditScore") + income, _ := userInfo.GetInt("Income") + var limit = 2 * income + userInfoMutable := userInfo.(model.MutableTuple) + userInfoMutable.SetInt(ctx, "creditScore", CreditScore) + userInfoMutable.SetString(ctx, "appStatus", "Approved") + userInfoMutable.SetInt(ctx, "approvedLimit", limit) + fmt.Println(userInfo) +} + +func aApproveWithHigherLimit(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + fmt.Println("Rule fired:", ruleName) + userInfo := tuples["UserAccount"] + updateScore := tuples["UpdateCreditScore"] + CreditScore, _ := updateScore.GetInt("creditScore") + income, _ := userInfo.GetInt("Income") + var limit = 3 * income + userInfoMutable := userInfo.(model.MutableTuple) + userInfoMutable.SetInt(ctx, "creditScore", CreditScore) + userInfoMutable.SetString(ctx, "appStatus", "Approved") + userInfoMutable.SetInt(ctx, "approvedLimit", limit) + fmt.Println(userInfo) +} + +func aUserReject(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + fmt.Println("Rule fired:", ruleName) + userInfo := tuples["UserAccount"] + updateScore := tuples["UpdateCreditScore"] + CreditScore, _ := updateScore.GetInt("creditScore") + userInfoMutable := userInfo.(model.MutableTuple) + userInfoMutable.SetInt(ctx, "creditScore", CreditScore) + userInfoMutable.SetString(ctx, "appStatus", "Rejected") + userInfoMutable.SetInt(ctx, "approvedLimit", 0) + fmt.Println(userInfo) +} diff --git a/examples/flogo/creditcard/main.go b/examples/flogo/creditcard/main.go new file mode 100644 index 0000000..132d1a2 --- /dev/null +++ b/examples/flogo/creditcard/main.go @@ -0,0 +1,68 @@ +package main + +import ( + "flag" + "fmt" + "os" + "runtime" + "runtime/pprof" + + _ "github.com/project-flogo/core/data/expression/script" + "github.com/project-flogo/core/engine" + "github.com/project-flogo/core/support/log" + + _ "github.com/project-flogo/contrib/trigger/rest" + _ "github.com/project-flogo/rules/ruleaction" +) + +var ( + cpuProfile = flag.String("cpuprofile", "", "Writes CPU profile to the specified file") + memProfile = flag.String("memprofile", "", "Writes memory profile to the specified file") + cfgJson string + cfgCompressed bool +) + +func main() { + + flag.Parse() + if *cpuProfile != "" { + f, err := os.Create(*cpuProfile) + if err != nil { + fmt.Println(fmt.Sprintf("Failed to create CPU profiling file due to error - %s", err.Error())) + os.Exit(1) + } + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + } + + cfg, err := engine.LoadAppConfig(cfgJson, cfgCompressed) + if err != nil { + log.RootLogger().Errorf("Failed to create engine: %s", err.Error()) + os.Exit(1) + } + + e, err := engine.New(cfg) + if err != nil { + log.RootLogger().Errorf("Failed to create engine: %s", err.Error()) + os.Exit(1) + } + + code := engine.RunEngine(e) + + if *memProfile != "" { + f, err := os.Create(*memProfile) + if err != nil { + fmt.Println(fmt.Sprintf("Failed to create memory profiling file due to error - %s", err.Error())) + os.Exit(1) + } + + runtime.GC() // get up-to-date statistics + if err := pprof.WriteHeapProfile(f); err != nil { + fmt.Println(fmt.Sprintf("Failed to write memory profiling data to file due to error - %s", err.Error())) + os.Exit(1) + } + f.Close() + } + + os.Exit(code) +} diff --git a/examples/flogo/creditcard/sanity.sh b/examples/flogo/creditcard/sanity.sh new file mode 100644 index 0000000..74e2bb5 --- /dev/null +++ b/examples/flogo/creditcard/sanity.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +function get_test_cases { + local my_list=( testcase1 testcase2 testcase3 ) + echo "${my_list[@]}" +} + +# Test cases performs credit card application status as approved if Creditscore > 750 +function testcase1 { +pushd $GOPATH/src/github.com/project-flogo/rules/examples/flogo/creditcard +go build +./creditcard > /tmp/testcase1.log 2>&1 & +pId=$! + +response=$(curl -X PUT http://localhost:7777/newaccount -H 'Content-Type: application/json' -d '{"Name":"Sam4","Age":"26","Income":"50100","Address":"SFO","Id":"4"}' --write-out '%{http_code}' --silent --output /dev/null) +response1=$(curl -X PUT http://localhost:7777/credit -H 'Content-Type: application/json' -d '{"Id":"4","creditScore":"850"}' --write-out '%{http_code}' --silent --output /dev/null) +kill -9 $pId + +if [ $response -eq 200 ] && [ $response1 -eq 200 ] && [[ "echo $(cat /tmp/testcase1.log)" =~ "Rule fired" ]] + then + echo "PASS" + else + echo "FAIL" +fi +cd .. +rm -rf /tmp/testcase1.log +popd +} + +# Test cases performs credit card application status rejected if Creditscore < 750 +function testcase2 { +pushd $GOPATH/src/github.com/project-flogo/rules/examples/flogo/creditcard +go build +./creditcard > /tmp/testcase2.log 2>&1 & +pId=$! + +response=$(curl -X PUT http://localhost:7777/newaccount -H 'Content-Type: application/json' -d '{"Name":"Sam4","Age":"26","Income":"50100","Address":"SFO","Id":"5"}' --write-out '%{http_code}' --silent --output /dev/null) +response1=$(curl -X PUT http://localhost:7777/credit -H 'Content-Type: application/json' -d '{"Id":"5","creditScore":"650"}' --write-out '%{http_code}' --silent --output /dev/null) + +kill -9 $pId +if [ $response -eq 200 ] && [ $response1 -eq 200 ] && [[ "echo $(cat /tmp/testcase2.log)" =~ "c" ]] + then + echo "PASS" + else + echo "FAIL" +fi +cd .. +rm -rf /tmp/testcase2.log +popd +} + + +# Test cases performs invalid applicant when age address or income data is not matching requirements +function testcase3 { +pushd $GOPATH/src/github.com/project-flogo/rules/examples/flogo/creditcard +go build +./creditcard > /tmp/testcase3.log 2>&1 & +pId=$! + +response=$(curl -X PUT http://localhost:7777/newaccount -H 'Content-Type: application/json' -d '{"Name":"Sam4","Age":"26","Income":"5010","Address":"SFO","Id":"6"}' --write-out '%{http_code}' --silent --output /dev/null) + +kill -9 $pId +if [ $response -eq 200 ] && [[ "echo $(cat /tmp/testcase3.log)" =~ "Applicant is not eligible to apply for creditcard" ]] + then + echo "PASS" + else + echo "FAIL" +fi +cd .. +rm -rf /tmp/testcase3.log +popd +} diff --git a/examples/flogo/invokeservice/README.md b/examples/flogo/invokeservice/README.md new file mode 100644 index 0000000..d853d88 --- /dev/null +++ b/examples/flogo/invokeservice/README.md @@ -0,0 +1,62 @@ +# Invoke service when rule fires + +This example demonstrates how a rule can invoke rule `service`. A rule `service` is a `go function` or a `flogo action` or a `flogo activity`. + +## Setup and build +Once you have the `flogo.json` file and a `functions.go` file, you are ready to build your Flogo App + +### Pre-requisites +* Go 1.11 +* Download and build the Flogo CLI 'flogo' and add it to your system PATH + +### Steps + +```sh +cd $GOPATH/src/github.com/project-flogo/rules/examples/flogo/invokeservice +flogo create -f flogo.json +cp functions.go ./invokeservice/src +cd invokeservice +flogo build +cd bin +./invokeservice +``` +### Testing + +#### #1 Invoke function based service + +Send a curl request +`curl http://localhost:7777/test/n1?name=function` +You should see following output: +``` +Rule[n1.name == function] fired. serviceFunctionAction() function got invoked. +``` + +#### #2 Invoke flogo async action (Ex: flow action) based service + +Send a curl request +`curl http://localhost:7777/test/n1?name=asyncaction` +You should see following output: +``` +2019-08-20T09:57:46.780+0530 INFO [flogo.activity.log] - asyncaction +2019-08-20T09:57:46.781+0530 INFO [flogo.rules] - service[ServiceFlowAction] outputs: map[] + +2019-08-20T09:57:46.781+0530 INFO [flogo.flow] - Instance [39470b3be53593aa827043a05086504f] Done +2019-08-20T09:57:46.781+0530 INFO [flogo.rules] - service[ServiceFlowAction] executed successfully asynchronously +``` + +#### #3 Invoke flogo sync action based service +Send a curl request +`curl http://localhost:7777/test/n1?name=syncaction` +You should see following output: +``` +2019-08-20T09:58:21.090+0530 INFO [flogo] - Input: syncaction +2019-08-20T09:58:21.090+0530 INFO [flogo.rules] - service[ServiceCoreAction] executed successfully. Service outputs: map[anOutput:syncaction] +``` + +#### #4 Invoke activity based service +Send a curl request +`curl http://localhost:7777/test/n1?name=activity` +You should see following output: +``` +2019-08-19T20:11:11.068+0530 INFO [flogo.test] - activity +``` \ No newline at end of file diff --git a/examples/flogo/invokeservice/flogo.json b/examples/flogo/invokeservice/flogo.json new file mode 100644 index 0000000..3129393 --- /dev/null +++ b/examples/flogo/invokeservice/flogo.json @@ -0,0 +1,230 @@ +{ + "name": "invokeservice", + "type": "flogo:app", + "version": "0.0.1", + "description": "Sample Flogo App", + "appModel": "1.0.0", + "imports": [ + "github.com/project-flogo/contrib/trigger/rest", + "github.com/project-flogo/core/examples/action", + "github.com/project-flogo/rules/ruleaction", + "github.com/project-flogo/contrib/activity/log", + "github.com/project-flogo/flow" + ], + "triggers": [ + { + "id": "receive_http_message", + "ref": "github.com/project-flogo/contrib/trigger/rest", + "settings": { + "port": "7777" + }, + "handlers": [ + { + "settings": { + "method": "GET", + "path": "/test/n1" + }, + "actions": [ + { + "id": "simple_rule", + "input": { + "tupletype": "n1", + "values": "=$.queryParams" + } + } + ] + } + ] + } + ], + "resources": [ + { + "id": "rulesession:simple", + "data": { + "metadata": { + "input": [ + { + "name": "values", + "type": "string" + }, + { + "name": "tupletype", + "type": "string" + } + ], + "output": [ + { + "name": "outputData", + "type": "any" + } + ] + }, + "rules": [ + { + "name": "n1.name == function", + "conditions": [ + { + "name": "c1", + "identifiers": [ + "n1" + ], + "evaluator": "cServiceFunction" + } + ], + "actionService": { + "service": "ServiceFunction", + "input": { + "message": "=$.n1.name" + } + } + }, + { + "name": "n1.name == activity", + "conditions": [ + { + "name": "c1", + "identifiers": [ + "n1" + ], + "evaluator": "cServiceActivity" + } + ], + "actionService": { + "service": "ServiceLogActivity", + "input": { + "message": "=$.n1.name" + } + } + }, + { + "name": "n1.name == syncaction", + "conditions": [ + { + "name": "c1", + "identifiers": [ + "n1" + ], + "evaluator": "cServiceSyncAction" + } + ], + "actionService": { + "service": "ServiceCoreAction", + "input": { + "anInput": "=$.n1.name" + } + } + }, + { + "name": "n1.name == asyncaction", + "conditions": [ + { + "name": "c1", + "identifiers": [ + "n1" + ], + "evaluator": "cServiceASyncAction" + } + ], + "actionService": { + "service": "ServiceFlowAction", + "input": { + "message": "=$.n1.name" + } + } + } + ], + "services": [ + { + "name": "ServiceFunction", + "description": "function service", + "type": "function", + "function": "serviceFunctionAction" + }, + { + "name": "ServiceLogActivity", + "description": "log activity service", + "type": "activity", + "ref": "github.com/project-flogo/contrib/activity/log" + }, + { + "name": "ServiceCoreAction", + "description": "synchronous action service", + "type": "action", + "ref": "github.com/project-flogo/core/examples/action", + "settings": { + "aSetting": "test setting" + } + }, + { + "name": "ServiceFlowAction", + "description": "Asynchronous flow action service", + "type": "action", + "ref": "github.com/project-flogo/flow", + "settings": { + "flowURI": "res://flow:sample_flow" + } + } + ] + } + }, + { + "id": "flow:sample_flow", + "data": { + "name": "SampleFlow", + "metadata": { + "input": [ + { + "name": "message", + "type": "string" + } + ] + }, + "tasks": [ + { + "id": "log_message", + "name": "Log Message", + "description": "Simple Log Activity", + "activity": { + "ref": "#log", + "input": { + "message": "=$flow.message" + } + } + } + ] + } + } + ], + "actions": [ + { + "ref": "github.com/project-flogo/rules/ruleaction", + "settings": { + "ruleSessionURI": "res://rulesession:simple", + "tds": [ + { + "name": "n1", + "properties": [ + { + "name": "name", + "pk-index": 0, + "type": "string" + } + ], + "ttl": 0 + }, + { + "name": "n2", + "properties": [ + { + "name": "name", + "pk-index": 0, + "type": "string" + } + ] + } + ] + }, + "id": "simple_rule" + } + ] +} \ No newline at end of file diff --git a/examples/flogo/invokeservice/functions.go b/examples/flogo/invokeservice/functions.go new file mode 100644 index 0000000..7459cf5 --- /dev/null +++ b/examples/flogo/invokeservice/functions.go @@ -0,0 +1,109 @@ +package main + +import ( + "context" + "fmt" + + "github.com/project-flogo/rules/config" + + "github.com/project-flogo/rules/common/model" +) + +//add this sample file to your flogo project +func init() { + config.RegisterActionFunction("serviceFunctionAction", serviceFunctionAction) + + config.RegisterConditionEvaluator("cServiceFunction", cServiceFunction) + config.RegisterConditionEvaluator("cServiceActivity", cServiceActivity) + config.RegisterConditionEvaluator("cServiceSyncAction", cServiceSyncAction) + config.RegisterConditionEvaluator("cServiceASyncAction", cServiceASyncAction) + + config.RegisterStartupRSFunction("simple", StartupRSFunction) +} + +func cServiceFunction(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + //This conditions filters on name="Tom" + t1 := tuples["n1"] + if t1 == nil { + fmt.Println("Should not get a nil tuple in FilterCondition! This is an error") + return false + } + name, _ := t1.GetString("name") + return name == "function" +} + +func cServiceActivity(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + //This conditions filters on name="Bob" + t1 := tuples["n1"] + if t1 == nil { + fmt.Println("Should not get a nil tuple in FilterCondition! This is an error") + return false + } + name, _ := t1.GetString("name") + return name == "activity" +} + +func cServiceSyncAction(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + //This conditions filters on name="Tom" + t1 := tuples["n1"] + if t1 == nil { + fmt.Println("Should not get a nil tuple in FilterCondition! This is an error") + return false + } + name, _ := t1.GetString("name") + return name == "syncaction" +} + +func cServiceASyncAction(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + //This conditions filters on name="Michael" + t1 := tuples["n1"] + if t1 == nil { + fmt.Println("Should not get a nil tuple in FilterCondition! This is an error") + return false + } + name, _ := t1.GetString("name") + return name == "asyncaction" +} + +func serviceFunctionAction(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + fmt.Printf("Rule[%s] fired. serviceFunctionAction() function got invoked. \n", ruleName) + t1 := tuples["n1"] + if t1 == nil { + fmt.Println("Should not get nil tuples here in JoinCondition! This is an error") + return + } +} + +func checkSameNamesCondition(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + t1 := tuples["n1"] + t2 := tuples["n2"] + if t1 == nil || t2 == nil { + fmt.Println("Should not get nil tuples here in JoinCondition! This is an error") + return false + } + name1, _ := t1.GetString("name") + name2, _ := t2.GetString("name") + return name1 == name2 +} + +func checkSameNamesAction(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + fmt.Printf("Rule fired: [%s]\n", ruleName) + t1 := tuples["n1"] + t2 := tuples["n2"] + if t1 == nil || t2 == nil { + fmt.Println("Should not get nil tuples here in Action! This is an error") + return + } + name1, _ := t1.GetString("name") + name2, _ := t2.GetString("name") + fmt.Printf("n1.name = [%s], n2.name = [%s]\n", name1, name2) +} + +func StartupRSFunction(ctx context.Context, rs model.RuleSession, startupCtx map[string]interface{}) (err error) { + + fmt.Printf("In startup rule function..\n") + t3, _ := model.NewTupleWithKeyValues("n1", "Bob") + t3.SetString(nil, "name", "Bob") + rs.Assert(nil, t3) + return nil +} diff --git a/examples/flogo/invokeservice/main.go b/examples/flogo/invokeservice/main.go new file mode 100644 index 0000000..b1f6c5b --- /dev/null +++ b/examples/flogo/invokeservice/main.go @@ -0,0 +1,69 @@ +package main + +import ( + "flag" + "fmt" + "os" + "runtime" + "runtime/pprof" + + _ "github.com/project-flogo/core/data/expression/script" + "github.com/project-flogo/core/engine" + "github.com/project-flogo/core/support/log" + + _ "github.com/project-flogo/contrib/trigger/rest" + _ "github.com/project-flogo/rules/ruleaction" + +) + +var ( + cpuProfile = flag.String("cpuprofile", "", "Writes CPU profile to the specified file") + memProfile = flag.String("memprofile", "", "Writes memory profile to the specified file") + cfgJson string + cfgCompressed bool +) + +func main() { + + flag.Parse() + if *cpuProfile != "" { + f, err := os.Create(*cpuProfile) + if err != nil { + fmt.Println(fmt.Sprintf("Failed to create CPU profiling file due to error - %s", err.Error())) + os.Exit(1) + } + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + } + + cfg, err := engine.LoadAppConfig(cfgJson, cfgCompressed) + if err != nil { + log.RootLogger().Errorf("Failed to create engine: %s", err.Error()) + os.Exit(1) + } + + e, err := engine.New(cfg) + if err != nil { + log.RootLogger().Errorf("Failed to create engine: %s", err.Error()) + os.Exit(1) + } + + code := engine.RunEngine(e) + + if *memProfile != "" { + f, err := os.Create(*memProfile) + if err != nil { + fmt.Println(fmt.Sprintf("Failed to create memory profiling file due to error - %s", err.Error())) + os.Exit(1) + } + + runtime.GC() // get up-to-date statistics + if err := pprof.WriteHeapProfile(f); err != nil { + fmt.Println(fmt.Sprintf("Failed to write memory profiling data to file due to error - %s", err.Error())) + os.Exit(1) + } + f.Close() + } + + os.Exit(code) +} diff --git a/examples/flogo/simple-kafka/flogo.json b/examples/flogo/simple-kafka/flogo.json index 806a19a..e0a6334 100644 --- a/examples/flogo/simple-kafka/flogo.json +++ b/examples/flogo/simple-kafka/flogo.json @@ -101,7 +101,9 @@ "evaluator": "checkForGrocery" } ], - "actionFunction": "groceryAction" + "actionService": { + "service": "FunctionService" + } }, { "name": "furnitureCheckRule", @@ -114,7 +116,21 @@ "evaluator": "checkForFurniture" } ], - "actionFunction": "furnitureAction" + "actionService": { + "service": "FunctionService1" + } + } + ], + "services": [ + { + "name": "FunctionService", + "description": "function service for groceryAction", + "function": "groceryAction" + }, + { + "name": "FunctionService1", + "description": "function service", + "function": "furnitureAction" } ] } diff --git a/examples/flogo/simple/flogo.json b/examples/flogo/simple/flogo.json index 71beff9..889b695 100644 --- a/examples/flogo/simple/flogo.json +++ b/examples/flogo/simple/flogo.json @@ -98,7 +98,9 @@ "expression" : "$.n1.name == 'Bob'" } ], - "actionFunction": "checkForBobAction" + "actionService": { + "service": "FunctionService" + } }, { "name": "n1.name == Bob \u0026\u0026 n1.name == n2.name", @@ -113,7 +115,9 @@ "expression" : "($.n1.name == 'Bob') \u0026\u0026 ($.n1.name == $.n2.name)" } ], - "actionFunction": "checkSameNamesAction" + "actionService": { + "service": "FunctionService1" + } }, { "name": "env variable example", @@ -122,7 +126,9 @@ "expression" : "($.n1.name == $env['name'])" } ], - "actionFunction": "envVarExampleAction" + "actionService": { + "service": "FunctionService2" + } }, { "name": "flogo property example", @@ -134,7 +140,31 @@ "expression" : "('testprop' == $property['name'])" } ], - "actionFunction": "propertyExampleAction" + "actionService": { + "service": "FunctionService3" + } + } + ], + "services": [ + { + "name": "FunctionService", + "description": "function service for checkForBobAction", + "function": "checkForBobAction" + }, + { + "name": "FunctionService1", + "description": "function service for checkSameNamesAction", + "function": "checkSameNamesAction" + }, + { + "name": "FunctionService2", + "description": "function service for envVarExampleAction", + "function": "envVarExampleAction" + }, + { + "name": "FunctionService3", + "description": "function service for propertyExampleAction", + "function": "propertyExampleAction" } ] } diff --git a/examples/flogo/trackntrace/flogo.json b/examples/flogo/trackntrace/flogo.json index dbaad3c..334e510 100644 --- a/examples/flogo/trackntrace/flogo.json +++ b/examples/flogo/trackntrace/flogo.json @@ -108,14 +108,13 @@ } ], "output": [ - { - "name": "outputData", - "type": "any" - } + { + "name": "outputData", + "type": "any" + } ] }, "rules": [ - { "name": "packageInSittingRule", "conditions": [ @@ -127,8 +126,10 @@ "evaluator": "cPackageInSitting" } ], - "actionFunction": "aPackageInSitting", - "priority":1 + "actionService": { + "service": "FunctionService" + }, + "priority": 1 }, { "name": "packageInDelayedRule", @@ -141,8 +142,10 @@ "evaluator": "cPackageInDelayed" } ], - "actionFunction": "aPackageInDelayed", - "priority":1 + "actionService": { + "service": "FunctionService1" + }, + "priority": 1 }, { "name": "packageInMovingRule", @@ -155,8 +158,10 @@ "evaluator": "cPackageInMoving" } ], - "actionFunction": "aPackageInMoving", - "priority":1 + "actionService": { + "service": "FunctionService2" + }, + "priority": 1 }, { "name": "packageInDroppedRule", @@ -169,8 +174,10 @@ "evaluator": "cPackageInDropped" } ], - "actionFunction": "aPackageInDropped", - "priority":1 + "actionService": { + "service": "FunctionService3" + }, + "priority": 1 }, { "name": "printPackageRule", @@ -183,8 +190,10 @@ "evaluator": "cPackageEvent" } ], - "actionFunction": "aPrintPackage", - "priority":2 + "actionService": { + "service": "FunctionService4" + }, + "priority": 2 }, { "name": "printMoveEventRule", @@ -197,8 +206,10 @@ "evaluator": "cMoveEvent" } ], - "actionFunction": "aPrintMoveEvent", - "priority":3 + "actionService": { + "service": "FunctionService5" + }, + "priority": 3 }, { "name": "joinMoveEventAndPackageEventRule", @@ -212,8 +223,10 @@ "evaluator": "cJoinMoveEventAndPackage" } ], - "actionFunction": "aJoinMoveEventAndPackage", - "priority":4 + "actionService": { + "service": "FunctionService6" + }, + "priority": 4 }, { "name": "aMoveTimeoutEventRule", @@ -226,8 +239,10 @@ "evaluator": "cMoveTimeoutEvent" } ], - "actionFunction": "aMoveTimeoutEvent", - "priority":5 + "actionService": { + "service": "FunctionService7" + }, + "priority": 5 }, { "name": "joinMoveTimeoutEventAndPackage", @@ -241,8 +256,57 @@ "evaluator": "cJoinMoveTimeoutEventAndPackage" } ], - "actionFunction": "aJoinMoveTimeoutEventAndPackage", - "priority":6 + "actionService": { + "service": "FunctionService8" + }, + "priority": 6 + } + ], + "services": [ + { + "name": "FunctionService", + "description": "function service for aPackageInSitting", + "function": "aPackageInSitting" + }, + { + "name": "FunctionService1", + "description": "function service for aPackageInDelayed", + "function": "aPackageInDelayed" + }, + { + "name": "FunctionService2", + "description": "function service for aPackageInMoving", + "function": "aPackageInMoving" + }, + { + "name": "FunctionService3", + "description": "function service for aPackageInDropped", + "function": "aPackageInDropped" + }, + { + "name": "FunctionService4", + "description": "function service for aPrintPackage", + "function": "aPrintPackage" + }, + { + "name": "FunctionService5", + "description": "function service for aPrintMoveEvent", + "function": "aPrintMoveEvent" + }, + { + "name": "FunctionService6", + "description": "function service for aJoinMoveEventAndPackage", + "function": "aJoinMoveEventAndPackage" + }, + { + "name": "FunctionService7", + "description": "function service for aMoveTimeoutEvent", + "function": "aMoveTimeoutEvent" + }, + { + "name": "FunctionService8", + "description": "function service for aJoinMoveTimeoutEventAndPackage", + "function": "aJoinMoveTimeoutEventAndPackage" } ] } diff --git a/examples/rulesapp/main.go b/examples/rulesapp/main.go index 0cfef18..9e77d74 100644 --- a/examples/rulesapp/main.go +++ b/examples/rulesapp/main.go @@ -4,9 +4,10 @@ import ( "context" "fmt" + "github.com/project-flogo/rules/common" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/config" "github.com/project-flogo/rules/ruleapi" - "github.com/project-flogo/rules/common" ) func main() { @@ -31,7 +32,12 @@ func main() { //// check for name "Bob" in n1 rule := ruleapi.NewRule("n1.name == Bob") rule.AddCondition("c1", []string{"n1"}, checkForBob, nil) - rule.SetAction(checkForBobAction) + serviceCfg := &config.ServiceDescriptor{ + Name: "checkForBobAction", + Function: checkForBobAction, + } + aService, _ := ruleapi.NewActionService(serviceCfg) + rule.SetActionService(aService) rule.SetContext("This is a test of context") rs.AddRule(rule) fmt.Printf("Rule added: [%s]\n", rule.GetName()) @@ -41,7 +47,12 @@ func main() { rule2 := ruleapi.NewRule("n1.name == Bob && n1.name == n2.name") rule2.AddCondition("c1", []string{"n1"}, checkForBob, nil) rule2.AddCondition("c2", []string{"n1", "n2"}, checkSameNamesCondition, nil) - rule2.SetAction(checkSameNamesAction) + serviceCfg2 := &config.ServiceDescriptor{ + Name: "checkSameNamesAction", + Function: checkSameNamesAction, + } + aService2, _ := ruleapi.NewActionService(serviceCfg2) + rule.SetActionService(aService2) rs.AddRule(rule2) fmt.Printf("Rule added: [%s]\n", rule2.GetName()) diff --git a/examples/trackntrace/trackntrace_test.go b/examples/trackntrace/trackntrace_test.go index b370547..581960b 100644 --- a/examples/trackntrace/trackntrace_test.go +++ b/examples/trackntrace/trackntrace_test.go @@ -1,16 +1,21 @@ package trackntrace import ( + "reflect" + "runtime" + + "github.com/project-flogo/rules/common" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/config" "github.com/project-flogo/rules/ruleapi" - "github.com/project-flogo/rules/common" + "github.com/stretchr/testify/assert" - "testing" - "time" - "io/ioutil" - "strconv" "context" "fmt" + "io/ioutil" + "strconv" + "testing" + "time" ) func TestPkgFlowNormal(t *testing.T) { @@ -197,8 +202,6 @@ func TestSameTupleInstanceAssert(t *testing.T) { rs.Unregister() } - - func createRuleSessionAndRules(t *testing.T) (model.RuleSession, error) { rs, _ := ruleapi.GetOrCreateRuleSession("asession") @@ -220,7 +223,7 @@ func loadPkgRulesWithDeps(t *testing.T, rs model.RuleSession) { //handle a package event, create a package in the packageAction rule := ruleapi.NewRule("packageevent") rule.AddCondition("truecondition", []string{"packageevent.none"}, truecondition, nil) - rule.SetAction(packageeventAction) + rule.SetActionService(createActionServiceFromFunction(t, packageeventAction)) rule.SetPriority(1) rs.AddRule(rule) t.Logf("Rule added: [%s]\n", rule.GetName()) @@ -228,7 +231,7 @@ func loadPkgRulesWithDeps(t *testing.T, rs model.RuleSession) { //handle a package, print package details in the packageAction rule1 := ruleapi.NewRule("package") rule1.AddCondition("packageCondition", []string{"package.none"}, packageCondition, nil) - rule1.SetAction(packageAction) + rule1.SetActionService(createActionServiceFromFunction(t, packageAction)) rule1.SetPriority(2) rs.AddRule(rule1) t.Logf("Rule added: [%s]\n", rule1.GetName()) @@ -237,7 +240,7 @@ func loadPkgRulesWithDeps(t *testing.T, rs model.RuleSession) { //for the next destination, etc in the scaneventAction rule2 := ruleapi.NewRule("scanevent") rule2.AddCondition("scaneventCondition", []string{"package.packageid", "scanevent.packageid", "package.curr", "package.next"}, scaneventCondition, nil) - rule2.SetAction(scaneventAction) + rule2.SetActionService(createActionServiceFromFunction(t, scaneventAction)) rule2.SetPriority(2) rs.AddRule(rule2) t.Logf("Rule added: [%s]\n", rule2.GetName()) @@ -245,7 +248,7 @@ func loadPkgRulesWithDeps(t *testing.T, rs model.RuleSession) { //handle a timeout event, triggered by scaneventAction, mark the package as delayed in scantimeoutAction rule3 := ruleapi.NewRule("scantimeout") rule3.AddCondition("scantimeoutCondition", []string{"package.packageid", "scantimeout.packageid"}, scantimeoutCondition, nil) - rule3.SetAction(scantimeoutAction) + rule3.SetActionService(createActionServiceFromFunction(t, scantimeoutAction)) rule3.SetPriority(1) rs.AddRule(rule3) t.Logf("Rule added: [%s]\n", rule3.GetName()) @@ -253,7 +256,7 @@ func loadPkgRulesWithDeps(t *testing.T, rs model.RuleSession) { //notify when a package is marked as delayed, print as such in the packagedelayedAction rule4 := ruleapi.NewRule("packagedelayed") rule4.AddCondition("packageDelayedCheck", []string{"package.status"}, packageDelayedCheck, nil) - rule4.SetAction(packagedelayedAction) + rule4.SetActionService(createActionServiceFromFunction(t, packagedelayedAction)) rule4.SetPriority(1) rs.AddRule(rule4) t.Logf("Rule added: [%s]\n", rule4.GetName()) @@ -392,4 +395,18 @@ func packagedelayedAction(ctx context.Context, rs model.RuleSession, ruleName st fmt.Printf("Package is now delayed id[%s]\n", pkgid) } -type TestKey struct {} \ No newline at end of file +type TestKey struct{} + +func createActionServiceFromFunction(t *testing.T, actionFunction model.ActionFunction) model.ActionService { + fname := runtime.FuncForPC(reflect.ValueOf(actionFunction).Pointer()).Name() + cfg := &config.ServiceDescriptor{ + Name: fname, + Description: fname, + Type: config.TypeServiceFunction, + Function: actionFunction, + } + aService, err := ruleapi.NewActionService(cfg) + assert.Nil(t, err) + assert.NotNil(t, aService) + return aService +} diff --git a/rete/conflict.go b/rete/conflict.go index 3aff2d1..4db539c 100644 --- a/rete/conflict.go +++ b/rete/conflict.go @@ -53,10 +53,11 @@ func (cr *conflictResImpl) resolveConflict(ctx context.Context) { if val != nil { item = val.(agendaItem) actionTuples := item.getTuples() - actionFn := item.getRule().GetActionFn() - if actionFn != nil { + // execute rule action service + aService := item.getRule().GetActionService() + if aService != nil { reteCtxV := getReteCtx(ctx) - actionFn(ctx, reteCtxV.getRuleSession(), item.getRule().GetName(), actionTuples, item.getRule().GetContext()) + aService.Execute(ctx, reteCtxV.getRuleSession(), item.getRule().GetName(), actionTuples, item.getRule().GetContext()) } } diff --git a/ruleaction/action.go b/ruleaction/action.go index 6ee587d..855395c 100644 --- a/ruleaction/action.go +++ b/ruleaction/action.go @@ -96,6 +96,8 @@ func (f *ActionFactory) New(cfg *action.Config) (action.Action, error) { } ruleAction := &RuleAction{} + + // create rule session ruleSessionDescriptor, err := manager.GetRuleSessionDescriptor(settings.RuleSessionURI) if err != nil { return nil, fmt.Errorf("failed to get RuleSessionDescriptor for %s\n%s", settings.RuleSessionURI, err.Error()) diff --git a/ruleapi/actionservice.go b/ruleapi/actionservice.go new file mode 100644 index 0000000..c6a71ba --- /dev/null +++ b/ruleapi/actionservice.go @@ -0,0 +1,202 @@ +package ruleapi + +import ( + "context" + "fmt" + + "github.com/project-flogo/core/action" + "github.com/project-flogo/core/activity" + "github.com/project-flogo/core/data" + "github.com/project-flogo/core/data/mapper" + "github.com/project-flogo/core/data/resolve" + "github.com/project-flogo/core/support" + "github.com/project-flogo/core/support/log" + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/config" +) + +var logger = log.ChildLogger(log.RootLogger(), "rules") + +// rule action service +type ruleActionService struct { + Name string + Type string + Function model.ActionFunction + Act activity.Activity + Action action.Action + Input map[string]interface{} +} + +// NewActionService creates new rule action service +func NewActionService(serviceCfg *config.ServiceDescriptor) (model.ActionService, error) { + + raService := &ruleActionService{ + Name: serviceCfg.Name, + Type: serviceCfg.Type, + Input: make(map[string]interface{}), + } + + switch serviceCfg.Type { + default: + return nil, fmt.Errorf("service type - '%s' is not supported", serviceCfg.Type) + case "": + return nil, fmt.Errorf("service type can't be empty") + case config.TypeServiceFunction: + if serviceCfg.Function == nil { + return nil, fmt.Errorf("service function can't empty") + } + raService.Function = serviceCfg.Function + case config.TypeServiceActivity: + // inflate activity from ref + if serviceCfg.Ref[0] == '#' { + var ok bool + activityRef := serviceCfg.Ref + serviceCfg.Ref, ok = support.GetAliasRef("activity", activityRef) + if !ok { + return nil, fmt.Errorf("activity '%s' not imported", activityRef) + } + } + + act := activity.Get(serviceCfg.Ref) + if act == nil { + return nil, fmt.Errorf("unsupported Activity:" + serviceCfg.Ref) + } + + f := activity.GetFactory(serviceCfg.Ref) + + if f != nil { + initCtx := newInitContext(serviceCfg.Name, serviceCfg.Settings, log.ChildLogger(log.RootLogger(), "ruleaction")) + pa, err := f(initCtx) + if err != nil { + return nil, fmt.Errorf("unable to create rule action service '%s' : %s", serviceCfg.Name, err.Error()) + } + act = pa + } + + raService.Act = act + + case config.TypeServiceAction: + if serviceCfg.Ref[0] == '#' { + var ok bool + actionRef := serviceCfg.Ref + serviceCfg.Ref, ok = support.GetAliasRef("action", actionRef) + if !ok { + return nil, fmt.Errorf("action - '%s' not imported", actionRef) + } + } + + actionFactory := action.GetFactory(serviceCfg.Ref) + if actionFactory == nil { + return nil, fmt.Errorf("factory not found for the action - '%s'", serviceCfg.Ref) + } + + actionCfg := &action.Config{Settings: serviceCfg.Settings} + var err error + raService.Action, err = actionFactory.New(actionCfg) + if err != nil { + return nil, fmt.Errorf("not able create action - %s", err) + } + } + + return raService, nil +} + +// SetInput sets input +func (raService *ruleActionService) SetInput(input map[string]interface{}) { + for k, v := range input { + raService.Input[k] = v + } +} + +func resolveExpFromTupleScope(tuples map[model.TupleType]model.Tuple, exprs map[string]interface{}) (map[string]interface{}, error) { + // resolve inputs from tuple scope + mFactory := mapper.NewFactory(resolve.GetBasicResolver()) + mapper, err := mFactory.NewMapper(exprs) + if err != nil { + return nil, err + } + + tupleScope := make(map[string]interface{}) + for tk, t := range tuples { + tupleScope[string(tk)] = t.GetMap() + } + + scope := data.NewSimpleScope(tupleScope, nil) + return mapper.Apply(scope) +} + +// Execute execute rule action service +func (raService *ruleActionService) Execute(ctx context.Context, rs model.RuleSession, rName string, tuples map[model.TupleType]model.Tuple, rCtx model.RuleContext) (done bool, err error) { + + switch raService.Type { + + default: + return false, fmt.Errorf("unsupported service type - '%s'", raService.Type) + + case config.TypeServiceFunction: + // invoke function and return, if available + if raService.Function != nil { + raService.Function(ctx, rs, rName, tuples, rCtx) + return true, nil + } + + case config.TypeServiceActivity: + // resolve inputs from tuple scope + resolvedInputs, err := resolveExpFromTupleScope(tuples, raService.Input) + if err != nil { + return false, err + } + // create activity context and set resolved inputs + sContext := newServiceContext(raService.Act.Metadata()) + for k, v := range resolvedInputs { + sContext.SetInput(k, v) + } + // run activities Eval + return raService.Act.Eval(sContext) + + case config.TypeServiceAction: + // resolve inputs from tuple scope + resolvedInputs, err := resolveExpFromTupleScope(tuples, raService.Input) + if err != nil { + return false, err + } + + // check whether the action is sync action + syncAction, syncOk := raService.Action.(action.SyncAction) + if syncOk && syncAction != nil { + // sync action + results, err := syncAction.Run(ctx, resolvedInputs) + if err != nil { + return false, fmt.Errorf("error while running the action service[%s] - %s", raService.Name, err) + } + logger.Infof("service[%s] executed successfully. Service outputs: %s \n", raService.Name, results) + return true, nil + } + + // check whether the action is async action + asyncAction, asyncOk := raService.Action.(action.AsyncAction) + if asyncOk && asyncAction != nil { + err := asyncAction.Run(ctx, resolvedInputs, &actionResultHandler{name: raService.Name}) + if err != nil { + return false, fmt.Errorf("error while running the action service[%s] - %s", raService.Name, err) + } + return true, nil + } + } + + return false, fmt.Errorf("service not executed, something went wrong") +} + +type actionResultHandler struct { + name string +} + +// HandleResult is invoked when there are results available +func (arh *actionResultHandler) HandleResult(results map[string]interface{}, err error) { + logger.Infof("service[%s] outputs: %s \n", arh.name, results) +} + +// Done indicates that the action has completed +func (arh *actionResultHandler) Done() { + logger.Infof("service[%s] executed successfully asynchronously\n", arh.name) +} diff --git a/ruleapi/actionservice_test.go b/ruleapi/actionservice_test.go new file mode 100644 index 0000000..2899e7b --- /dev/null +++ b/ruleapi/actionservice_test.go @@ -0,0 +1,52 @@ +package ruleapi + +import ( + "context" + "testing" + + _ "github.com/project-flogo/contrib/activity/log" + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/config" + "github.com/stretchr/testify/assert" +) + +func TestNewActionService(t *testing.T) { + cfg := &config.ServiceDescriptor{ + Name: "TestActionService", + Description: "test action service", + } + aService, err := NewActionService(cfg) + assert.NotNil(t, err) + assert.Equal(t, "service type can't be empty", err.Error()) + assert.Nil(t, aService) + + // unsupported service type + cfg.Type = "unknowntype" + aService, err = NewActionService(cfg) + assert.NotNil(t, err) + assert.Equal(t, "service type - 'unknowntype' is not supported", err.Error()) + + // action service with function + cfg.Function = emptyAction + cfg.Type = config.TypeServiceFunction + aService, err = NewActionService(cfg) + assert.Nil(t, err) + assert.NotNil(t, aService) + cfg.Function = nil //clear for next test scenario + + // action service with activity + cfg.Ref = "github.com/project-flogo/contrib/activity/log" + cfg.Type = config.TypeServiceActivity + aService, err = NewActionService(cfg) + assert.Nil(t, err) + assert.NotNil(t, aService) + + // set input + input := map[string]interface{}{"message": "=$.n1.name"} + aService.SetInput(input) + + // TODO: test aService.Execute() +} + +func emptyAction(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { +} diff --git a/ruleapi/rule.go b/ruleapi/rule.go index 9bd2d30..6d53a2f 100644 --- a/ruleapi/rule.go +++ b/ruleapi/rule.go @@ -9,13 +9,13 @@ import ( ) type ruleImpl struct { - name string - identifiers []model.TupleType - conditions []model.Condition - actionFn model.ActionFunction - priority int - deps map[model.TupleType]map[string]bool - ctx model.RuleContext + name string + identifiers []model.TupleType + conditions []model.Condition + actionService model.ActionService + priority int + deps map[model.TupleType]map[string]bool + ctx model.RuleContext } func (rule *ruleImpl) GetContext() model.RuleContext { @@ -44,16 +44,16 @@ func (rule *ruleImpl) GetName() string { return rule.name } -func (rule *ruleImpl) GetActionFn() model.ActionFunction { - return rule.actionFn +func (rule *ruleImpl) GetActionService() model.ActionService { + return rule.actionService } func (rule *ruleImpl) GetConditions() []model.Condition { return rule.conditions } -func (rule *ruleImpl) SetActionFn(actionFn model.ActionFunction) { - rule.actionFn = actionFn +func (rule *ruleImpl) SetActionService(actionService model.ActionService) { + rule.actionService = actionService } func (rule *ruleImpl) addCond(conditionName string, idrs []model.TupleType, cfn model.ConditionEvaluator, ctx model.RuleContext, setIdr bool) { @@ -141,10 +141,6 @@ func (rule *ruleImpl) GetIdentifiers() []model.TupleType { return rule.identifiers } -func (rule *ruleImpl) SetAction(actionFn model.ActionFunction) { - rule.actionFn = actionFn -} - func (rule *ruleImpl) AddCondition2(conditionName string, idrs []string, cFn model.ConditionEvaluator, ctx model.RuleContext) (err error) { typeDeps := []model.TupleType{} for _, idr := range idrs { diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index 68424f0..c742f46 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -49,10 +49,28 @@ func GetOrCreateRuleSessionFromConfig(name string, jsonConfig string) (model.Rul return nil, err } + // inflate action services + aServices := make(map[string]model.ActionService) + for _, s := range ruleSessionDescriptor.Services { + aService, err := NewActionService(s) + if err != nil { + return nil, err + } + aServices[s.Name] = aService + } + for _, ruleCfg := range ruleSessionDescriptor.Rules { rule := NewRule(ruleCfg.Name) rule.SetContext("This is a test of context") - rule.SetAction(ruleCfg.ActionFunc) + // set action service to rule, if exist + if ruleCfg.ActionService != nil { + aService, found := aServices[ruleCfg.ActionService.Service] + if !found { + return nil, fmt.Errorf("rule action service[%s] not found", ruleCfg.ActionService.Service) + } + aService.SetInput(ruleCfg.ActionService.Input) + rule.SetActionService(aService) + } rule.SetPriority(ruleCfg.Priority) for _, condCfg := range ruleCfg.Conditions { diff --git a/ruleapi/servicecontext.go b/ruleapi/servicecontext.go new file mode 100644 index 0000000..4b97a66 --- /dev/null +++ b/ruleapi/servicecontext.go @@ -0,0 +1,179 @@ +package ruleapi + +import ( + "github.com/project-flogo/core/activity" + "github.com/project-flogo/core/data" + "github.com/project-flogo/core/data/mapper" + "github.com/project-flogo/core/data/metadata" + "github.com/project-flogo/core/data/resolve" + "github.com/project-flogo/core/support/log" +) + +// activity init context +type initContext struct { + settings map[string]interface{} + mapperFactory mapper.Factory + logger log.Logger +} + +func newInitContext(name string, settings map[string]interface{}, l log.Logger) *initContext { + return &initContext{ + settings: settings, + mapperFactory: mapper.NewFactory(resolve.GetBasicResolver()), + logger: log.ChildLogger(l, name), + } +} + +func (i *initContext) Settings() map[string]interface{} { + return i.settings +} + +func (i *initContext) MapperFactory() mapper.Factory { + return i.mapperFactory +} + +func (i *initContext) Logger() log.Logger { + return i.logger +} + +// ServiceContext context +type ServiceContext struct { + TaskName string + activityHost activity.Host + + metadata *activity.Metadata + settings map[string]interface{} + inputs map[string]interface{} + outputs map[string]interface{} + + shared map[string]interface{} +} + +func newServiceContext(md *activity.Metadata) *ServiceContext { + input := map[string]data.TypedValue{"Input1": data.NewTypedValue(data.TypeString, "")} + output := map[string]data.TypedValue{"Output1": data.NewTypedValue(data.TypeString, "")} + + // TBD: rule action's details (like: metadata, name, etc) to be used here + sHost := &ServiceHost{ + HostId: "1", + HostRef: "github.com/project-flogo/rules", + IoMetadata: &metadata.IOMetadata{Input: input, Output: output}, + HostData: data.NewSimpleScope(nil, nil), + } + sContext := &ServiceContext{ + metadata: md, + activityHost: sHost, + TaskName: "Rule action service", + inputs: make(map[string]interface{}, len(md.Input)), + outputs: make(map[string]interface{}, len(md.Output)), + settings: make(map[string]interface{}, len(md.Settings)), + } + + for name, tv := range md.Input { + sContext.inputs[name] = tv.Value() + } + for name, tv := range md.Output { + sContext.outputs[name] = tv.Value() + } + + return sContext +} + +// ActivityHost gets the "host" under with the activity is executing +func (sc *ServiceContext) ActivityHost() activity.Host { + return sc.activityHost +} + +//Name the name of the activity that is currently executing +func (sc *ServiceContext) Name() string { + return sc.TaskName +} + +// GetInput gets the value of the specified input attribute +func (sc *ServiceContext) GetInput(name string) interface{} { + val, found := sc.inputs[name] + if found { + return val + } + return nil +} + +// SetOutput sets the value of the specified output attribute +func (sc *ServiceContext) SetOutput(name string, value interface{}) error { + sc.outputs[name] = value + return nil +} + +// GetInputObject gets all the activity input as the specified object. +func (sc *ServiceContext) GetInputObject(input data.StructValue) error { + err := input.FromMap(sc.inputs) + return err +} + +// SetOutputObject sets the activity output as the specified object. +func (sc *ServiceContext) SetOutputObject(output data.StructValue) error { + sc.outputs = output.ToMap() + return nil +} + +// GetSharedTempData get shared temporary data for activity, lifespan +// of the data dependent on the activity host implementation +func (sc *ServiceContext) GetSharedTempData() map[string]interface{} { + if sc.shared == nil { + sc.shared = make(map[string]interface{}) + } + return sc.shared +} + +// Logger the logger for the activity +func (sc *ServiceContext) Logger() log.Logger { + return logger +} + +// SetInput sets input +func (sc *ServiceContext) SetInput(name string, value interface{}) { + sc.inputs[name] = value +} + +// ServiceHost hosts service +type ServiceHost struct { + HostId string + HostRef string + + IoMetadata *metadata.IOMetadata + HostData data.Scope + ReplyData map[string]interface{} + ReplyErr error +} + +// ID returns the ID of the Activity Host +func (ac *ServiceHost) ID() string { + return ac.HostId +} + +// Name the name of the Activity Host +func (ac *ServiceHost) Name() string { + return "" +} + +// IOMetadata get the input/output metadata of the activity host +func (ac *ServiceHost) IOMetadata() *metadata.IOMetadata { + return ac.IoMetadata +} + +// Reply is used to reply to the activity Host with the results of the execution +func (ac *ServiceHost) Reply(replyData map[string]interface{}, err error) { + ac.ReplyData = replyData + ac.ReplyErr = err +} + +// Return is used to indicate to the activity Host that it should complete and return the results of the execution +func (ac *ServiceHost) Return(returnData map[string]interface{}, err error) { + ac.ReplyData = returnData + ac.ReplyErr = err +} + +// Scope returns the scope for the Host's data +func (ac *ServiceHost) Scope() data.Scope { + return ac.HostData +} diff --git a/ruleapi/tests/common.go b/ruleapi/tests/common.go index 8dfa92b..72eef91 100644 --- a/ruleapi/tests/common.go +++ b/ruleapi/tests/common.go @@ -4,11 +4,15 @@ import ( "context" "io/ioutil" "log" + "reflect" + "runtime" "testing" "github.com/project-flogo/rules/common" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/config" "github.com/project-flogo/rules/ruleapi" + "github.com/stretchr/testify/assert" ) func createRuleSession() (model.RuleSession, error) { @@ -62,4 +66,18 @@ type txnCtx struct { TxnCnt int } -type TestKey struct{} \ No newline at end of file +type TestKey struct{} + +func createActionServiceFromFunction(t *testing.T, actionFunction model.ActionFunction) model.ActionService { + fname := runtime.FuncForPC(reflect.ValueOf(actionFunction).Pointer()).Name() + cfg := &config.ServiceDescriptor{ + Name: fname, + Description: fname, + Type: config.TypeServiceFunction, + Function: actionFunction, + } + aService, err := ruleapi.NewActionService(cfg) + assert.Nil(t, err) + assert.NotNil(t, aService) + return aService +} diff --git a/ruleapi/tests/expr_1_test.go b/ruleapi/tests/expr_1_test.go index 478cfa6..9930f55 100644 --- a/ruleapi/tests/expr_1_test.go +++ b/ruleapi/tests/expr_1_test.go @@ -2,9 +2,10 @@ package tests import ( "context" + "testing" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "testing" ) //1 condition, 1 expression @@ -14,7 +15,7 @@ func Test_1_Expr(t *testing.T) { rs, _ := createRuleSession() r1 := ruleapi.NewRule("r1") r1.AddExprCondition("c1", "$.t2.p2 > $.t1.p1", nil) - r1.SetAction(a1) + r1.SetActionService(createActionServiceFromFunction(t, a1)) r1.SetContext(actionCount) rs.AddRule(r1) diff --git a/ruleapi/tests/expr_2_test.go b/ruleapi/tests/expr_2_test.go index 5f5d7fe..2d8d486 100644 --- a/ruleapi/tests/expr_2_test.go +++ b/ruleapi/tests/expr_2_test.go @@ -2,9 +2,10 @@ package tests import ( "context" + "testing" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "testing" ) //2 conditions, 1 expr each @@ -15,7 +16,7 @@ func Test_2_Expr(t *testing.T) { r1 := ruleapi.NewRule("r1") r1.AddExprCondition("c1", "$.t1.p1 > $.t2.p1", nil) r1.AddExprCondition("c2", "$.t1.p1 == 2", nil) - r1.SetAction(a2) + r1.SetActionService(createActionServiceFromFunction(t, a2)) r1.SetContext(actionCount) rs.AddRule(r1) diff --git a/ruleapi/tests/expr_3_test.go b/ruleapi/tests/expr_3_test.go index 37d6361..5b865b1 100644 --- a/ruleapi/tests/expr_3_test.go +++ b/ruleapi/tests/expr_3_test.go @@ -2,9 +2,10 @@ package tests import ( "context" + "testing" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "testing" ) //1 conditions, 2 expr @@ -14,7 +15,7 @@ func Test_3_Expr(t *testing.T) { rs, _ := createRuleSession() r1 := ruleapi.NewRule("r1") r1.AddExprCondition("c1", "($.t1.p1 > $.t2.p1) && ($.t1.p2 > $.t2.p2)", nil) - r1.SetAction(a3) + r1.SetActionService(createActionServiceFromFunction(t, a3)) r1.SetContext(actionCount) rs.AddRule(r1) diff --git a/ruleapi/tests/expr_4_test.go b/ruleapi/tests/expr_4_test.go index c3cd7f8..779b181 100644 --- a/ruleapi/tests/expr_4_test.go +++ b/ruleapi/tests/expr_4_test.go @@ -2,9 +2,10 @@ package tests import ( "context" + "testing" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "testing" ) //1 conditions, 3 expr @@ -14,7 +15,7 @@ func Test_4_Expr(t *testing.T) { rs, _ := createRuleSession() r1 := ruleapi.NewRule("r1") r1.AddExprCondition("c1", "($.t1.p1 > $.t2.p1) && (($.t1.p2 > $.t2.p2) && ($.t1.p3 == $.t2.p3))", nil) - r1.SetAction(a4) + r1.SetActionService(createActionServiceFromFunction(t, a4)) r1.SetContext(actionCount) rs.AddRule(r1) diff --git a/ruleapi/tests/expr_5_test.go b/ruleapi/tests/expr_5_test.go index 411a50b..ad49c6a 100644 --- a/ruleapi/tests/expr_5_test.go +++ b/ruleapi/tests/expr_5_test.go @@ -2,9 +2,10 @@ package tests import ( "context" + "testing" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "testing" ) //1 arithmetic operation @@ -14,7 +15,7 @@ func Test_5_Expr(t *testing.T) { rs, _ := createRuleSession() r1 := ruleapi.NewRule("r1") r1.AddExprCondition("c1", "(($.t1.p1 + $.t2.p1) == 5) && (($.t1.p2 > $.t2.p2) && ($.t1.p3 == $.t2.p3))", nil) - r1.SetAction(a5) + r1.SetActionService(createActionServiceFromFunction(t, a5)) r1.SetContext(actionCount) rs.AddRule(r1) diff --git a/ruleapi/tests/identifier_1_test.go b/ruleapi/tests/identifier_1_test.go index 7dfc736..937f0e3 100644 --- a/ruleapi/tests/identifier_1_test.go +++ b/ruleapi/tests/identifier_1_test.go @@ -17,7 +17,8 @@ func Test_I1(t *testing.T) { rule := ruleapi.NewRule("I1") rule.AddCondition("I1_c1", []string{"t1.none", "t3.none"}, trueCondition, nil) - rule.SetAction(i1_action) + rule.SetActionService(createActionServiceFromFunction(t, i1_action)) + rule.SetPriority(1) rs.AddRule(rule) t.Logf("Rule added: [%s]\n", rule.GetName()) diff --git a/ruleapi/tests/identifier_2_test.go b/ruleapi/tests/identifier_2_test.go index 7e82a51..e823fdf 100644 --- a/ruleapi/tests/identifier_2_test.go +++ b/ruleapi/tests/identifier_2_test.go @@ -19,7 +19,7 @@ func Test_I2(t *testing.T) { rule := ruleapi.NewRule("I21") rule.AddCondition("I2_c1", []string{"t1.none", "t2.none"}, trueCondition, nil) - rule.SetAction(i21_action) + rule.SetActionService(createActionServiceFromFunction(t, i21_action)) rule.SetPriority(1) //rule.SetContext(actionMap) rs.AddRule(rule) @@ -27,7 +27,7 @@ func Test_I2(t *testing.T) { rule1 := ruleapi.NewRule("I22") rule1.AddCondition("I2_c2", []string{"t1.none", "t3.none"}, trueCondition, nil) - rule1.SetAction(i22_action) + rule1.SetActionService(createActionServiceFromFunction(t, i22_action)) rule1.SetPriority(1) //rule.SetContext(actionMap) rs.AddRule(rule1) @@ -35,7 +35,7 @@ func Test_I2(t *testing.T) { rule2 := ruleapi.NewRule("I23") rule2.AddCondition("I2_c3", []string{"t2.none", "t3.none"}, trueCondition, nil) - rule2.SetAction(i23_action) + rule2.SetActionService(createActionServiceFromFunction(t, i23_action)) rule2.SetPriority(1) //rule.SetContext(actionMap) rs.AddRule(rule2) @@ -43,7 +43,7 @@ func Test_I2(t *testing.T) { rule3 := ruleapi.NewRule("I24") rule3.AddCondition("I2_c4", []string{"t1.none", "t2.none", "t3.none"}, trueCondition, nil) - rule3.SetAction(i24_action) + rule3.SetActionService(createActionServiceFromFunction(t, i24_action)) rule3.SetPriority(1) //rule.SetContext(actionMap) rs.AddRule(rule3) diff --git a/ruleapi/tests/retract_1_test.go b/ruleapi/tests/retract_1_test.go index 43d3f61..f83c762 100644 --- a/ruleapi/tests/retract_1_test.go +++ b/ruleapi/tests/retract_1_test.go @@ -22,7 +22,7 @@ func Test_Retract_1(t *testing.T) { } ruleActionCtx := make(map[string]string) rule.SetContext(ruleActionCtx) - rule.SetAction(assertAction) + rule.SetActionService(createActionServiceFromFunction(t, assertAction)) rule.SetPriority(1) err = rs.AddRule(rule) if err != nil { diff --git a/ruleapi/tests/rtctxn_10_test.go b/ruleapi/tests/rtctxn_10_test.go index ac95149..5c9c22b 100644 --- a/ruleapi/tests/rtctxn_10_test.go +++ b/ruleapi/tests/rtctxn_10_test.go @@ -15,7 +15,7 @@ func Test_T10(t *testing.T) { rule := ruleapi.NewRule("R10") rule.AddCondition("R10_c1", []string{"t1.none", "t3.none"}, trueCondition, nil) - rule.SetAction(r10_action) + rule.SetActionService(createActionServiceFromFunction(t, r10_action)) rule.SetPriority(1) rs.AddRule(rule) t.Logf("Rule added: [%s]\n", rule.GetName()) diff --git a/ruleapi/tests/rtctxn_11_test.go b/ruleapi/tests/rtctxn_11_test.go index aeb9b84..b153be6 100644 --- a/ruleapi/tests/rtctxn_11_test.go +++ b/ruleapi/tests/rtctxn_11_test.go @@ -15,14 +15,14 @@ func Test_T11(t *testing.T) { rule := ruleapi.NewRule("R11") rule.AddCondition("R11_c1", []string{"t1.none"}, trueCondition, nil) - rule.SetAction(r11_action) + rule.SetActionService(createActionServiceFromFunction(t, r11_action)) rule.SetPriority(1) rs.AddRule(rule) t.Logf("Rule added: [%s]\n", rule.GetName()) rule1 := ruleapi.NewRule("R112") rule1.AddCondition("R112_c1", []string{"t1.none"}, trueCondition, nil) - rule1.SetAction(r112_action) + rule1.SetActionService(createActionServiceFromFunction(t, r112_action)) rule1.SetPriority(1) rs.AddRule(rule1) t.Logf("Rule added: [%s]\n", rule1.GetName()) diff --git a/ruleapi/tests/rtctxn_12_test.go b/ruleapi/tests/rtctxn_12_test.go index 3404b62..e8f74bc 100644 --- a/ruleapi/tests/rtctxn_12_test.go +++ b/ruleapi/tests/rtctxn_12_test.go @@ -15,14 +15,14 @@ func Test_T12(t *testing.T) { rule := ruleapi.NewRule("R12") rule.AddCondition("R12_c1", []string{"t1.none", "t3.none"}, trueCondition, nil) - rule.SetAction(r122_action) + rule.SetActionService(createActionServiceFromFunction(t, r122_action)) rule.SetPriority(1) rs.AddRule(rule) t.Logf("Rule added: [%s]\n", rule.GetName()) rule1 := ruleapi.NewRule("R122") rule1.AddCondition("R122_c1", []string{"t1.none", "t3.none"}, trueCondition, nil) - rule1.SetAction(r12_action) + rule1.SetActionService(createActionServiceFromFunction(t, r12_action)) rule1.SetPriority(1) rs.AddRule(rule1) t.Logf("Rule added: [%s]\n", rule1.GetName()) diff --git a/ruleapi/tests/rtctxn_13_test.go b/ruleapi/tests/rtctxn_13_test.go index 0b2775b..17001a3 100644 --- a/ruleapi/tests/rtctxn_13_test.go +++ b/ruleapi/tests/rtctxn_13_test.go @@ -15,14 +15,14 @@ func Test_T13(t *testing.T) { rule := ruleapi.NewRule("R13") rule.AddCondition("R13_c1", []string{"t1.none", "t3.none"}, trueCondition, nil) - rule.SetAction(r13_action) + rule.SetActionService(createActionServiceFromFunction(t, r13_action)) rule.SetPriority(1) rs.AddRule(rule) t.Logf("Rule added: [%s]\n", rule.GetName()) rule1 := ruleapi.NewRule("R132") rule1.AddCondition("R132_c1", []string{"t3.none"}, trueCondition, nil) - rule1.SetAction(r132_action) + rule1.SetActionService(createActionServiceFromFunction(t, r132_action)) rule1.SetPriority(2) rs.AddRule(rule1) t.Logf("Rule added: [%s]\n", rule1.GetName()) diff --git a/ruleapi/tests/rtctxn_14_test.go b/ruleapi/tests/rtctxn_14_test.go index f2fe006..5766f36 100644 --- a/ruleapi/tests/rtctxn_14_test.go +++ b/ruleapi/tests/rtctxn_14_test.go @@ -15,14 +15,14 @@ func Test_T14(t *testing.T) { rule := ruleapi.NewRule("R14") rule.AddCondition("R14_c1", []string{"t1.none"}, trueCondition, nil) - rule.SetAction(r14_action) + rule.SetActionService(createActionServiceFromFunction(t, r14_action)) rule.SetPriority(1) rs.AddRule(rule) t.Logf("Rule added: [%s]\n", rule.GetName()) rule1 := ruleapi.NewRule("R142") rule1.AddCondition("R142_c1", []string{"t3.none"}, trueCondition, nil) - rule1.SetAction(r142_action) + rule1.SetActionService(createActionServiceFromFunction(t, r142_action)) rule1.SetPriority(2) rs.AddRule(rule1) t.Logf("Rule added: [%s]\n", rule1.GetName()) diff --git a/ruleapi/tests/rtctxn_15_test.go b/ruleapi/tests/rtctxn_15_test.go index 86e5aff..87031e5 100644 --- a/ruleapi/tests/rtctxn_15_test.go +++ b/ruleapi/tests/rtctxn_15_test.go @@ -18,7 +18,7 @@ func Test_T15(t *testing.T) { rule := ruleapi.NewRule("R15") rule.AddCondition("R15_c1", []string{"t1.none"}, trueCondition, nil) - rule.SetAction(r15_action) + rule.SetActionService(createActionServiceFromFunction(t, r15_action)) rule.SetPriority(1) rs.AddRule(rule) t.Logf("Rule added: [%s]\n", rule.GetName()) diff --git a/ruleapi/tests/rtctxn_16_test.go b/ruleapi/tests/rtctxn_16_test.go index b89dac7..6278fe0 100644 --- a/ruleapi/tests/rtctxn_16_test.go +++ b/ruleapi/tests/rtctxn_16_test.go @@ -18,7 +18,7 @@ func Test_T16(t *testing.T) { rule := ruleapi.NewRule("R16") rule.AddCondition("R16_c1", []string{"t1.none"}, trueCondition, nil) - rule.SetAction(r16_action) + rule.SetActionService(createActionServiceFromFunction(t, r16_action)) rule.SetPriority(1) rs.AddRule(rule) t.Logf("Rule added: [%s]\n", rule.GetName()) diff --git a/ruleapi/tests/rtctxn_1_test.go b/ruleapi/tests/rtctxn_1_test.go index 6333aaf..a5c6c06 100644 --- a/ruleapi/tests/rtctxn_1_test.go +++ b/ruleapi/tests/rtctxn_1_test.go @@ -15,7 +15,7 @@ func Test_T1(t *testing.T) { rule := ruleapi.NewRule("R1") rule.AddCondition("R1_c1", []string{"t1.none"}, trueCondition, t) - rule.SetAction(emptyAction) + rule.SetActionService(createActionServiceFromFunction(t, emptyAction)) rule.SetPriority(1) rs.AddRule(rule) t.Logf("Rule added: [%s]\n", rule.GetName()) @@ -29,8 +29,6 @@ func Test_T1(t *testing.T) { } - - func t1Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { t := handlerCtx.(*testing.T) @@ -38,7 +36,7 @@ func t1Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lA := len(rtxn.GetRtcAdded()) if lA != 1 { t.Errorf("RtcAdded: Expected [%d], got [%d]\n", 1, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } lM := len(rtxn.GetRtcModified()) if lM != 0 { @@ -48,6 +46,6 @@ func t1Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 0 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 0, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } diff --git a/ruleapi/tests/rtctxn_2_test.go b/ruleapi/tests/rtctxn_2_test.go index 1eb4415..af9891e 100644 --- a/ruleapi/tests/rtctxn_2_test.go +++ b/ruleapi/tests/rtctxn_2_test.go @@ -2,9 +2,10 @@ package tests import ( "context" + "testing" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "testing" ) //TTL = 0, asserted @@ -14,7 +15,7 @@ func Test_T2(t *testing.T) { rule := ruleapi.NewRule("R2") rule.AddCondition("R2_c1", []string{"t2.none"}, trueCondition, nil) - rule.SetAction(emptyAction) + rule.SetActionService(createActionServiceFromFunction(t, emptyAction)) rule.SetPriority(1) rs.AddRule(rule) t.Logf("Rule added: [%s]\n", rule.GetName()) @@ -35,7 +36,7 @@ func t2Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lA := len(rtxn.GetRtcAdded()) if lA != 0 { t.Errorf("RtcAdded: Expected [%d], got [%d]\n", 0, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } lM := len(rtxn.GetRtcModified()) if lM != 0 { @@ -46,6 +47,6 @@ func t2Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 0 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 0, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } diff --git a/ruleapi/tests/rtctxn_3_test.go b/ruleapi/tests/rtctxn_3_test.go index 2281e1f..1bfadfa 100644 --- a/ruleapi/tests/rtctxn_3_test.go +++ b/ruleapi/tests/rtctxn_3_test.go @@ -2,9 +2,10 @@ package tests import ( "context" + "testing" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "testing" ) //New asserted in action (forward chain) @@ -14,7 +15,7 @@ func Test_T3(t *testing.T) { rule := ruleapi.NewRule("R3") rule.AddCondition("R3_c1", []string{"t1.none"}, trueCondition, nil) - rule.SetAction(R3_action) + rule.SetActionService(createActionServiceFromFunction(t, R3_action)) rule.SetPriority(1) rs.AddRule(rule) t.Logf("Rule added: [%s]\n", rule.GetName()) @@ -39,7 +40,7 @@ func t3Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lA := len(rtxn.GetRtcAdded()) if lA != 1 { t.Errorf("RtcAdded: Types expected [%d], got [%d]\n", 1, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } else { //ok @@ -60,6 +61,6 @@ func t3Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 0 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 0, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } diff --git a/ruleapi/tests/rtctxn_4_test.go b/ruleapi/tests/rtctxn_4_test.go index dbbb303..0f12bea 100644 --- a/ruleapi/tests/rtctxn_4_test.go +++ b/ruleapi/tests/rtctxn_4_test.go @@ -2,9 +2,10 @@ package tests import ( "context" + "testing" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "testing" ) // modified in action (forward chain) @@ -14,7 +15,7 @@ func Test_T4(t *testing.T) { rule := ruleapi.NewRule("R4") rule.AddCondition("R4_c1", []string{"t1.none"}, trueCondition, nil) - rule.SetAction(r4_action) + rule.SetActionService(createActionServiceFromFunction(t, r4_action)) rule.SetPriority(1) rs.AddRule(rule) t.Logf("Rule added: [%s]\n", rule.GetName()) @@ -40,7 +41,7 @@ func t4Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lA := len(rtxn.GetRtcAdded()) if lA != 1 { t.Errorf("RtcAdded: Types expected [%d], got [%d]\n", 1, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } else { //ok @@ -59,6 +60,6 @@ func t4Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 0 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 0, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } diff --git a/ruleapi/tests/rtctxn_5_test.go b/ruleapi/tests/rtctxn_5_test.go index 5c65fa0..718ad24 100644 --- a/ruleapi/tests/rtctxn_5_test.go +++ b/ruleapi/tests/rtctxn_5_test.go @@ -2,9 +2,10 @@ package tests import ( "context" + "testing" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "testing" ) //3 rtcs, 1st rtc ->asserted, 2nd rtc ->modified the 1st one, 3rd rtc ->deleted the 2nd one @@ -14,7 +15,7 @@ func Test_T5(t *testing.T) { rule := ruleapi.NewRule("R5") rule.AddCondition("R5_c1", []string{"t1.none"}, trueCondition, nil) - rule.SetAction(r5_action) + rule.SetActionService(createActionServiceFromFunction(t, r5_action)) rule.SetPriority(1) rs.AddRule(rule) t.Logf("Rule added: [%s]\n", rule.GetName()) @@ -40,17 +41,17 @@ func r5_action(ctx context.Context, rs model.RuleSession, ruleName string, tuple t1 := tuples[model.TupleType("t1")].(model.MutableTuple) //t1.SetString(ctx, "p3", "v3") id, _ := t1.GetString("id") - if id == "t11" { - tk, _:= model.NewTupleKeyWithKeyValues("t1", "t10") - t10 := rs.GetAssertedTuple (tk).(model.MutableTuple) + if id == "t11" { + tk, _ := model.NewTupleKeyWithKeyValues("t1", "t10") + t10 := rs.GetAssertedTuple(tk).(model.MutableTuple) if t10 != nil { t10.SetString(ctx, "p3", "v3") t10.SetDouble(ctx, "p2", 11.11) } - } else if (id == "t13") { + } else if id == "t13" { //delete t11 - tk, _:= model.NewTupleKeyWithKeyValues("t1", "t11") - t11 := rs.GetAssertedTuple (tk).(model.MutableTuple) + tk, _ := model.NewTupleKeyWithKeyValues("t1", "t11") + t11 := rs.GetAssertedTuple(tk).(model.MutableTuple) if t11 != nil { rs.Delete(ctx, t11) } @@ -66,7 +67,7 @@ func t5Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lA := len(rtxn.GetRtcAdded()) if lA != 1 { t.Errorf("RtcAdded: Types expected [%d], got [%d]\n", 1, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } lM := len(rtxn.GetRtcModified()) if lM != 0 { @@ -76,13 +77,13 @@ func t5Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 0 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 0, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } else if txnCtx.TxnCnt == 2 { lA := len(rtxn.GetRtcAdded()) if lA != 1 { t.Errorf("RtcAdded: Types expected [%d], got [%d]\n", 1, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } lM := len(rtxn.GetRtcModified()) if lM != 1 { @@ -92,13 +93,13 @@ func t5Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 0 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 0, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } else if txnCtx.TxnCnt == 3 { lA := len(rtxn.GetRtcAdded()) if lA != 1 { t.Errorf("RtcAdded: Types expected [%d], got [%d]\n", 1, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } lM := len(rtxn.GetRtcModified()) if lM != 0 { @@ -108,7 +109,7 @@ func t5Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 1 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 1, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } -} \ No newline at end of file +} diff --git a/ruleapi/tests/rtctxn_6_test.go b/ruleapi/tests/rtctxn_6_test.go index 3fc6ad4..08759c3 100644 --- a/ruleapi/tests/rtctxn_6_test.go +++ b/ruleapi/tests/rtctxn_6_test.go @@ -2,9 +2,10 @@ package tests import ( "context" + "testing" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "testing" ) //Same as Test_T5, but in 3rd rtc, assert a TTL=0 based and a TTL=1 based @@ -14,7 +15,7 @@ func Test_T6(t *testing.T) { rule := ruleapi.NewRule("R6") rule.AddCondition("R6_c1", []string{"t1.none"}, trueCondition, nil) - rule.SetAction(r6_action) + rule.SetActionService(createActionServiceFromFunction(t, r6_action)) rule.SetPriority(1) rs.AddRule(rule) t.Logf("Rule added: [%s]\n", rule.GetName()) @@ -38,17 +39,17 @@ func Test_T6(t *testing.T) { func r6_action(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { t1 := tuples[model.TupleType("t1")].(model.MutableTuple) id, _ := t1.GetString("id") - if id == "t11" { - tk, _:= model.NewTupleKeyWithKeyValues("t1", "t10") - t10 := rs.GetAssertedTuple (tk).(model.MutableTuple) + if id == "t11" { + tk, _ := model.NewTupleKeyWithKeyValues("t1", "t10") + t10 := rs.GetAssertedTuple(tk).(model.MutableTuple) if t10 != nil { t10.SetString(ctx, "p3", "v3") t10.SetDouble(ctx, "p2", 11.11) } - } else if (id == "t13") { + } else if id == "t13" { //delete t11 - tk, _:= model.NewTupleKeyWithKeyValues("t1", "t11") - t11 := rs.GetAssertedTuple (tk).(model.MutableTuple) + tk, _ := model.NewTupleKeyWithKeyValues("t1", "t11") + t11 := rs.GetAssertedTuple(tk).(model.MutableTuple) if t11 != nil { rs.Delete(ctx, t11) } @@ -70,7 +71,7 @@ func t6Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lA := len(rtxn.GetRtcAdded()) if lA != 1 { t.Errorf("RtcAdded: Expected [%d], got [%d]\n", 1, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } lM := len(rtxn.GetRtcModified()) if lM != 0 { @@ -80,13 +81,13 @@ func t6Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 0 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 0, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } else if txnCtx.TxnCnt == 2 { lA := len(rtxn.GetRtcAdded()) if lA != 1 { t.Errorf("RtcAdded: Types expected [%d], got [%d]\n", 1, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } lM := len(rtxn.GetRtcModified()) if lM != 1 { @@ -96,18 +97,18 @@ func t6Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 0 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 0, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } else if txnCtx.TxnCnt == 3 { lA := len(rtxn.GetRtcAdded()) if lA != 1 { t.Errorf("RtcAdded: Types expected [%d], got [%d]\n", 1, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } else { - added, _ := rtxn.GetRtcAdded() ["t1"] + added, _ := rtxn.GetRtcAdded()["t1"] if len(added) != 2 { t.Errorf("RtcAdded: Tuples expected [%d], got [%d]\n", 2, len(added)) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } } lM := len(rtxn.GetRtcModified()) @@ -118,7 +119,7 @@ func t6Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 1 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 1, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } -} \ No newline at end of file +} diff --git a/ruleapi/tests/rtctxn_7_test.go b/ruleapi/tests/rtctxn_7_test.go index 6eb03ca..61b260b 100644 --- a/ruleapi/tests/rtctxn_7_test.go +++ b/ruleapi/tests/rtctxn_7_test.go @@ -2,9 +2,10 @@ package tests import ( "context" + "testing" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "testing" ) //add and delete in the same rtc @@ -14,7 +15,7 @@ func Test_T7(t *testing.T) { rule := ruleapi.NewRule("R7") rule.AddCondition("R7_c1", []string{"t1.none"}, trueCondition, nil) - rule.SetAction(r7_action) + rule.SetActionService(createActionServiceFromFunction(t, r7_action)) rule.SetPriority(1) rs.AddRule(rule) t.Logf("Rule added: [%s]\n", rule.GetName()) @@ -33,7 +34,7 @@ func Test_T7(t *testing.T) { func r7_action(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { t1 := tuples[model.TupleType("t1")].(model.MutableTuple) id, _ := t1.GetString("id") - if id == "t10" { + if id == "t10" { rs.Delete(ctx, t1) } } @@ -47,7 +48,7 @@ func t7Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lA := len(rtxn.GetRtcAdded()) if lA != 1 { t.Errorf("RtcAdded: Expected [%d], got [%d]\n", 1, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } lM := len(rtxn.GetRtcModified()) if lM != 0 { @@ -57,7 +58,7 @@ func t7Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 1 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 1, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } -} \ No newline at end of file +} diff --git a/ruleapi/tests/rtctxn_8_test.go b/ruleapi/tests/rtctxn_8_test.go index 350929c..48c9f81 100644 --- a/ruleapi/tests/rtctxn_8_test.go +++ b/ruleapi/tests/rtctxn_8_test.go @@ -16,7 +16,7 @@ func Test_T8(t *testing.T) { rule := ruleapi.NewRule("R1") rule.AddCondition("R1_c1", []string{"t1.none"}, trueCondition, nil) rule.AddCondition("R1_c2", []string{}, falseCondition, nil) - rule.SetAction(assertTuple) + rule.SetActionService(createActionServiceFromFunction(t, assertTuple)) rule.SetPriority(1) rs.AddRule(rule) t.Logf("Rule added: [%s]\n", rule.GetName()) @@ -30,9 +30,8 @@ func Test_T8(t *testing.T) { } - func assertTuple(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { - t, _:= model.NewTupleWithKeyValues("t1", "t2") + t, _ := model.NewTupleWithKeyValues("t1", "t2") rs.Assert(ctx, t) } @@ -43,7 +42,7 @@ func t8Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lA := len(m) if lA != 1 { t.Errorf("RtcAdded: Expected [%d], got [%d]\n", 1, lA) - printTuples(t,"Added", rtxn.GetRtcAdded()) + printTuples(t, "Added", rtxn.GetRtcAdded()) } } lM := len(rtxn.GetRtcModified()) @@ -54,6 +53,6 @@ func t8Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han lD := len(rtxn.GetRtcDeleted()) if lD != 0 { t.Errorf("RtcDeleted: Expected [%d], got [%d]\n", 0, lD) - printTuples(t,"Deleted", rtxn.GetRtcDeleted()) + printTuples(t, "Deleted", rtxn.GetRtcDeleted()) } } diff --git a/ruleapi/tests/rtctxn_9_test.go b/ruleapi/tests/rtctxn_9_test.go index b5a242c..694e5ba 100644 --- a/ruleapi/tests/rtctxn_9_test.go +++ b/ruleapi/tests/rtctxn_9_test.go @@ -15,7 +15,7 @@ func Test_T9(t *testing.T) { rule := ruleapi.NewRule("R9") rule.AddCondition("R9_c1", []string{"t1.none", "t3.none"}, trueCondition, nil) - rule.SetAction(r9_action) + rule.SetActionService(createActionServiceFromFunction(t, r9_action)) rule.SetPriority(1) rs.AddRule(rule) t.Logf("Rule added: [%s]\n", rule.GetName()) diff --git a/ruleapi/tests/rules_1_test.go b/ruleapi/tests/rules_1_test.go index ac28026..69ea6fd 100644 --- a/ruleapi/tests/rules_1_test.go +++ b/ruleapi/tests/rules_1_test.go @@ -3,9 +3,10 @@ package tests import ( "context" "fmt" + "testing" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "testing" ) /** @@ -23,7 +24,7 @@ func Test_One(t *testing.T) { //// rule 1 r1 := ruleapi.NewRule("R1") r1.AddCondition("C1", []string{"t1"}, checkC1, nil) - r1.SetAction(actionA1) + r1.SetActionService(createActionServiceFromFunction(t, actionA1)) r1.SetPriority(1) r1.SetContext(actionMap) @@ -32,7 +33,7 @@ func Test_One(t *testing.T) { // rule 2 r2 := ruleapi.NewRule("R2") r2.AddCondition("C2", []string{"t1"}, checkC2, nil) - r2.SetAction(actionA2) + r2.SetActionService(createActionServiceFromFunction(t, actionA2)) r2.SetPriority(2) r2.SetContext(actionMap) @@ -41,7 +42,7 @@ func Test_One(t *testing.T) { // rule 3 r3 := ruleapi.NewRule("R3") r3.AddCondition("C3", []string{"t1"}, checkC3, nil) - r3.SetAction(actionA3) + r3.SetActionService(createActionServiceFromFunction(t, actionA3)) r3.SetPriority(3) r3.SetContext(actionMap) diff --git a/ruleapi/tests/rules_2_test.go b/ruleapi/tests/rules_2_test.go index 4cca838..34fa7ce 100644 --- a/ruleapi/tests/rules_2_test.go +++ b/ruleapi/tests/rules_2_test.go @@ -35,7 +35,7 @@ func Test_Two(t *testing.T) { rule.AddCondition("c1", []string{"n1"}, checkForBob, nil) rule.AddCondition("c2", []string{"n1"}, checkForName, nil) - rule.SetAction(checkForBobAction) + rule.SetActionService(createActionServiceFromFunction(t, checkForBobAction)) rule.SetContext(actionFireCount) rs.AddRule(rule) fmt.Printf("Rule added: [%s]\n", rule.GetName()) diff --git a/ruleapi/tests/rules_3_test.go b/ruleapi/tests/rules_3_test.go index 0b54fe6..4ed5417 100644 --- a/ruleapi/tests/rules_3_test.go +++ b/ruleapi/tests/rules_3_test.go @@ -19,14 +19,14 @@ func Test_Three(t *testing.T) { rule := ruleapi.NewRule("R3") rule.AddCondition("R3c1", []string{"t1.id"}, trueCondition, nil) - rule.SetAction(r3action) + rule.SetActionService(createActionServiceFromFunction(t, r3action)) rule.SetPriority(1) rs.AddRule(rule) t.Logf("Rule added: [%s]\n", rule.GetName()) rule1 := ruleapi.NewRule("R32") rule1.AddCondition("R32c1", []string{"t1.p1"}, r3Condition, nil) - rule1.SetAction(r32action) + rule1.SetActionService(createActionServiceFromFunction(t, r32action)) rule1.SetPriority(1) rule1.SetContext(actionMap) rs.AddRule(rule1) From 398290a43f4d7aa00e2db64d4f0e9f2bab5fa924 Mon Sep 17 00:00:00 2001 From: nthota Date: Wed, 21 Aug 2019 17:04:11 +0530 Subject: [PATCH 058/125] fixing rsconfig for flogo action model --- examples/rulesapp/main.go | 3 +-- ruleapi/rulesession.go | 50 +++++++++++++++++++++++---------------- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/examples/rulesapp/main.go b/examples/rulesapp/main.go index dd59bd4..20b78af 100644 --- a/examples/rulesapp/main.go +++ b/examples/rulesapp/main.go @@ -25,9 +25,8 @@ func main() { return } - content := getFileContent("src/github.com/project-flogo/rules/examples/rulesapp/rsconfig.json") - rs, _ := ruleapi.GetOrCreateRuleSessionFromConfig("asession", content) //Create a RuleSession + rs, _ := ruleapi.GetOrCreateRuleSession("asession") //// check for name "Bob" in n1 rule := ruleapi.NewRule("n1.name == Bob") diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index e2a8b11..2064bb8 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -5,9 +5,11 @@ import ( "encoding/json" "errors" "fmt" + "os" "sync" "time" + utils "github.com/project-flogo/rules/common" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/config" "github.com/project-flogo/rules/rete" @@ -24,36 +26,37 @@ type rulesessionImpl struct { name string reteNetwork common.Network - timers map[interface{}]*time.Timer - startupFn model.StartupRSFunction - started bool - config map[string]string - tupleStore model.TupleStore - jsonConfig map[string]interface{} + timers map[interface{}]*time.Timer + startupFn model.StartupRSFunction + started bool + storeConfig string + tupleStore model.TupleStore + jsonConfig map[string]interface{} } +// GetOrCreateRuleSession returns rule session func GetOrCreateRuleSession(name string) (model.RuleSession, error) { if name == "" { return nil, errors.New("RuleSession name cannot be empty") } rs := rulesessionImpl{} + rs.loadStoreConfig() rs.initRuleSession(name) + rs1, _ := sessionMap.LoadOrStore(name, &rs) return rs1.(*rulesessionImpl), nil } +// GetOrCreateRuleSessionFromConfig returns rule session from created from config func GetOrCreateRuleSessionFromConfig(name string, jsonConfig string) (model.RuleSession, error) { - if name == "" { - return nil, errors.New("RuleSession name cannot be empty") + rs, err := GetOrCreateRuleSession(name) + + if err != nil { + return nil, err } - rs := rulesessionImpl{} - rs.initRuleSessionWithConfig(name, jsonConfig) - //existing, _ := sessionMap.LoadOrStore(name, &rs) - //rs1 := existing.(*rulesessionImpl) - //rs = *rs1 ruleSessionDescriptor := config.RuleSessionDescriptor{} - err := json.Unmarshal([]byte(jsonConfig), &ruleSessionDescriptor) + err = json.Unmarshal([]byte(jsonConfig), &ruleSessionDescriptor) if err != nil { return nil, err } @@ -73,23 +76,30 @@ func GetOrCreateRuleSessionFromConfig(name string, jsonConfig string) (model.Rul rs.SetStartupFunction(config.GetStartupRSFunction(name)) - return &rs, nil + return rs, nil } -func (rs *rulesessionImpl) initRuleSession(name string) { - rs.initRuleSessionWithConfig(name, "{}") +func (rs *rulesessionImpl) loadStoreConfig() { + storeConfigFileName := "rsconfig.json" + _, err := os.Stat(storeConfigFileName) + if err != nil { + // TO DO -- get the config from env or assign it as empty json + rs.storeConfig = "{}" + } else { + rs.storeConfig = utils.FileToString(storeConfigFileName) + } } -func (rs *rulesessionImpl) initRuleSessionWithConfig(name string, jsonConfig string) error { +func (rs *rulesessionImpl) initRuleSession(name string) error { - err := json.Unmarshal([]byte(jsonConfig), &rs.jsonConfig) + err := json.Unmarshal([]byte(rs.storeConfig), &rs.jsonConfig) if err != nil { return err } rs.name = name rs.timers = make(map[interface{}]*time.Timer) - rs.reteNetwork = rete.NewReteNetwork(rs.name, jsonConfig) + rs.reteNetwork = rete.NewReteNetwork(rs.name, rs.storeConfig) //TODO: Configure it from jconsonfig tupleStore := getTupleStore(rs.jsonConfig) From 9eea19e0c09f246da90559849eb7720d8e3d2707 Mon Sep 17 00:00:00 2001 From: LakshmiMekala Date: Mon, 26 Aug 2019 15:44:39 +0530 Subject: [PATCH 059/125] remove unused sanity sh files --- examples/flogo/creditcard/sanity.sh | 72 ----------------------------- 1 file changed, 72 deletions(-) delete mode 100644 examples/flogo/creditcard/sanity.sh diff --git a/examples/flogo/creditcard/sanity.sh b/examples/flogo/creditcard/sanity.sh deleted file mode 100644 index 74e2bb5..0000000 --- a/examples/flogo/creditcard/sanity.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/bash - -function get_test_cases { - local my_list=( testcase1 testcase2 testcase3 ) - echo "${my_list[@]}" -} - -# Test cases performs credit card application status as approved if Creditscore > 750 -function testcase1 { -pushd $GOPATH/src/github.com/project-flogo/rules/examples/flogo/creditcard -go build -./creditcard > /tmp/testcase1.log 2>&1 & -pId=$! - -response=$(curl -X PUT http://localhost:7777/newaccount -H 'Content-Type: application/json' -d '{"Name":"Sam4","Age":"26","Income":"50100","Address":"SFO","Id":"4"}' --write-out '%{http_code}' --silent --output /dev/null) -response1=$(curl -X PUT http://localhost:7777/credit -H 'Content-Type: application/json' -d '{"Id":"4","creditScore":"850"}' --write-out '%{http_code}' --silent --output /dev/null) -kill -9 $pId - -if [ $response -eq 200 ] && [ $response1 -eq 200 ] && [[ "echo $(cat /tmp/testcase1.log)" =~ "Rule fired" ]] - then - echo "PASS" - else - echo "FAIL" -fi -cd .. -rm -rf /tmp/testcase1.log -popd -} - -# Test cases performs credit card application status rejected if Creditscore < 750 -function testcase2 { -pushd $GOPATH/src/github.com/project-flogo/rules/examples/flogo/creditcard -go build -./creditcard > /tmp/testcase2.log 2>&1 & -pId=$! - -response=$(curl -X PUT http://localhost:7777/newaccount -H 'Content-Type: application/json' -d '{"Name":"Sam4","Age":"26","Income":"50100","Address":"SFO","Id":"5"}' --write-out '%{http_code}' --silent --output /dev/null) -response1=$(curl -X PUT http://localhost:7777/credit -H 'Content-Type: application/json' -d '{"Id":"5","creditScore":"650"}' --write-out '%{http_code}' --silent --output /dev/null) - -kill -9 $pId -if [ $response -eq 200 ] && [ $response1 -eq 200 ] && [[ "echo $(cat /tmp/testcase2.log)" =~ "c" ]] - then - echo "PASS" - else - echo "FAIL" -fi -cd .. -rm -rf /tmp/testcase2.log -popd -} - - -# Test cases performs invalid applicant when age address or income data is not matching requirements -function testcase3 { -pushd $GOPATH/src/github.com/project-flogo/rules/examples/flogo/creditcard -go build -./creditcard > /tmp/testcase3.log 2>&1 & -pId=$! - -response=$(curl -X PUT http://localhost:7777/newaccount -H 'Content-Type: application/json' -d '{"Name":"Sam4","Age":"26","Income":"5010","Address":"SFO","Id":"6"}' --write-out '%{http_code}' --silent --output /dev/null) - -kill -9 $pId -if [ $response -eq 200 ] && [[ "echo $(cat /tmp/testcase3.log)" =~ "Applicant is not eligible to apply for creditcard" ]] - then - echo "PASS" - else - echo "FAIL" -fi -cd .. -rm -rf /tmp/testcase3.log -popd -} From 051b0b544a2d42d401d5e88b05df3d3606cdc9b4 Mon Sep 17 00:00:00 2001 From: Ramesh Polishetti Date: Mon, 26 Aug 2019 20:45:08 +0530 Subject: [PATCH 060/125] Intial version of decision table activity --- activity/dtable/activity.go | 68 ++++++++++++++ activity/dtable/metadata.go | 52 +++++++++++ examples/flogo/dtable/flogo.json | 155 +++++++++++++++++++++++++++++++ ruleapi/actionservice.go | 6 ++ 4 files changed, 281 insertions(+) create mode 100644 activity/dtable/activity.go create mode 100644 activity/dtable/metadata.go create mode 100644 examples/flogo/dtable/flogo.json diff --git a/activity/dtable/activity.go b/activity/dtable/activity.go new file mode 100644 index 0000000..6dfbcff --- /dev/null +++ b/activity/dtable/activity.go @@ -0,0 +1,68 @@ +package dtable + +import ( + "context" + "fmt" + + "github.com/project-flogo/core/activity" + "github.com/project-flogo/rules/common/model" +) + +func init() { + _ = activity.Register(&Activity{}, New) +} + +// Activity decision table based rule action +type Activity struct { + tasks []*Task +} + +// New creates new decision table activity +func New(ctx activity.InitContext) (activity.Activity, error) { + // Read settings + settings := &Settings{} + err := settings.FromMap(ctx.Settings()) + if err != nil { + return nil, err + } + fmt.Println("settings: ", settings) + fmt.Println("settings: ", settings.Make[0]) + + // Read setting from init context + act := &Activity{ + tasks: settings.Make, + } + return act, nil +} + +// Metadata activity metadata +func (a *Activity) Metadata() *activity.Metadata { + return activity.ToMetadata(&Input{}) +} + +// Eval implements decision table action +func (a *Activity) Eval(ctx activity.Context) (done bool, err error) { + fmt.Println("DECISION TABLE ENTER") + + ctx1 := ctx.GetInput("ctx").(context.Context) + // rs := ctx.GetInput("rulesession").(model.RuleSession) + // rName := ctx.GetInput("rulename").(string) + tuples := ctx.GetInput("tuples").(map[model.TupleType]model.Tuple) + // rCtx := ctx.GetInput("rulecontext").(model.RuleContext) + + // Run tasks + for _, task := range a.tasks { + tuple := tuples[model.TupleType(task.Tuple)] + if tuple == nil { + fmt.Printf("tuple[%s] not found \n", task.Tuple) + continue + } + fmt.Printf("tuple = %v \n", tuple) + mutableTuple := tuple.(model.MutableTuple) + mutableTuple.SetString(ctx1, task.Field, task.To) + fmt.Printf("updated tuple = %v \n", tuple) + } + + fmt.Println("DECISION TABLE EXIT") + return false, nil +} diff --git a/activity/dtable/metadata.go b/activity/dtable/metadata.go new file mode 100644 index 0000000..4e39b6a --- /dev/null +++ b/activity/dtable/metadata.go @@ -0,0 +1,52 @@ +package dtable + +import ( + "github.com/project-flogo/core/data/coerce" + "github.com/project-flogo/core/data/metadata" +) + +// Input activity input +type Input struct { + TBDMessage string `md:"message"` +} + +// ToMap converts Input struct to map +func (i *Input) ToMap() map[string]interface{} { + return map[string]interface{}{ + "message": i.TBDMessage, + } +} + +// FromMap fills Input struct from map +func (i *Input) FromMap(values map[string]interface{}) (err error) { + i.TBDMessage, err = coerce.ToString(values["message"]) + return +} + +// Settings activity settings +type Settings struct { + Make []*Task `md:"make"` +} + +// Task task +type Task struct { + Tuple string `md:"tuple"` + Field string `md:"field"` + To string `md:"to"` +} + +// FromMap fills Input struct from map +func (s *Settings) FromMap(values map[string]interface{}) (err error) { + tasks, err := coerce.ToArray(values["make"]) + if err != nil { + return + } + s.Make = make([]*Task, len(tasks)) + for i, t := range tasks { + task := &Task{} + taskMap := t.(map[string]interface{}) + err = metadata.MapToStruct(taskMap, task, true) + s.Make[i] = task + } + return +} diff --git a/examples/flogo/dtable/flogo.json b/examples/flogo/dtable/flogo.json new file mode 100644 index 0000000..1c4d955 --- /dev/null +++ b/examples/flogo/dtable/flogo.json @@ -0,0 +1,155 @@ +{ + "name": "invokeservice", + "type": "flogo:app", + "version": "0.0.1", + "description": "Sample Flogo App", + "appModel": "1.0.0", + "imports": [ + "github.com/project-flogo/contrib/trigger/rest", + "github.com/project-flogo/contrib/activity/log", + "github.com/project-flogo/rules/ruleaction", + "github.com/project-flogo/rules/activity/dtable" + ], + "triggers": [ + { + "id": "receive_http_message", + "ref": "github.com/project-flogo/contrib/trigger/rest", + "settings": { + "port": "7777" + }, + "handlers": [ + { + "settings": { + "method": "GET", + "path": "/test/:tupleType" + }, + "actions": [ + { + "id": "simple_rule", + "input": { + "tupletype": "=$.pathParams.tupleType", + "values": "=$.queryParams" + } + } + ] + } + ] + } + ], + "resources": [ + { + "id": "rulesession:simple", + "data": { + "metadata": { + "input": [ + { + "name": "values", + "type": "string" + }, + { + "name": "tupletype", + "type": "string" + } + ], + "output": [ + { + "name": "outputData", + "type": "any" + } + ] + }, + "rules": [ + { + "name": "student.grade == GRADE-A", + "conditions": [ + { + "expression":"$.student.grade == 'GRADE-A'" + } + ], + "actionService": { + "service": "LogAGradeStudent", + "input": { + "message": "=$.student.name" + } + }, + "priority":2 + }, + { + "name": "student.name == changegrade.name", + "conditions": [ + { + "expression":"$.changegrade.name == $.student.name" + } + ], + "actionService": { + "service": "ChangeGrade", + "input": { + "message": "=$.changegrade.name" + } + }, + "priority":2 + } + ], + "services": [ + { + "name": "ChangeGrade", + "description": "Changes student grade", + "type": "activity", + "ref": "github.com/project-flogo/rules/activity/dtable", + "settings":{ + "make" : [ + {"tuple":"student", "field":"grade", "to": "GRADE-A"} + ] + } + }, + { + "name": "LogAGradeStudent", + "description": "Logs A Grade Student", + "type": "activity", + "ref": "github.com/project-flogo/contrib/activity/log" + } + ] + } + } + ], + "actions": [ + { + "ref": "github.com/project-flogo/rules/ruleaction", + "settings": { + "ruleSessionURI": "res://rulesession:simple", + "tds": [ + { + "name": "student", + "properties": [ + { + "name": "name", + "pk-index": 0, + "type": "string" + }, + { + "name": "grade", + "type": "string" + } + ] + }, + { + "name": "changegrade", + "properties": [ + { + "name": "name", + "pk-index": 0, + "type": "string" + }, + { + "name": "grade", + "type": "string" + } + ], + "ttl":0 + } + ] + }, + "id": "simple_rule" + } + ] +} \ No newline at end of file diff --git a/ruleapi/actionservice.go b/ruleapi/actionservice.go index c6a71ba..a660883 100644 --- a/ruleapi/actionservice.go +++ b/ruleapi/actionservice.go @@ -151,6 +151,12 @@ func (raService *ruleActionService) Execute(ctx context.Context, rs model.RuleSe for k, v := range resolvedInputs { sContext.SetInput(k, v) } + // set rule context + sContext.SetInput("ctx", ctx) + sContext.SetInput("rulesession", rs) + sContext.SetInput("rulename", rName) + sContext.SetInput("tuples", tuples) + sContext.SetInput("rulecontext", rCtx) // run activities Eval return raService.Act.Eval(sContext) From c2085016939dff1b1dc2a479ba0a0dd21b52e292 Mon Sep 17 00:00:00 2001 From: nthota Date: Tue, 27 Aug 2019 15:27:14 +0530 Subject: [PATCH 061/125] handling null pointer for condivar in joinnode --- rete/joinnode.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/rete/joinnode.go b/rete/joinnode.go index 06a772a..2bda0fb 100644 --- a/rete/joinnode.go +++ b/rete/joinnode.go @@ -42,8 +42,17 @@ func (jn *joinNodeImpl) initjoinNodeImplVar(nw *reteNetworkImpl, rule model.Rule jn.leftIdrs = leftIdrs jn.rightIdrs = rightIdrs jn.conditionVar = conditionVar - jn.leftTable = nw.GetJtService().GetOrCreateJoinTable(nw, rule, leftIdrs, "L_"+conditionVar.GetName()) - jn.rightTable = nw.GetJtService().GetOrCreateJoinTable(nw, rule, rightIdrs, "R_"+conditionVar.GetName()) + + jointTableName := "" + if conditionVar != nil { + jointTableName = conditionVar.GetName() + } else { + jointTableName = rule.GetName() + } + + jn.leftTable = nw.GetJtService().GetOrCreateJoinTable(nw, rule, leftIdrs, "L_"+jointTableName) + jn.rightTable = nw.GetJtService().GetOrCreateJoinTable(nw, rule, rightIdrs, "R_"+jointTableName) + jn.setJoinIdentifiers() } From d11ad33722c38f8731e95ac53571a72bc8f1d877 Mon Sep 17 00:00:00 2001 From: Ramesh Polishetti Date: Tue, 27 Aug 2019 17:53:44 +0530 Subject: [PATCH 062/125] Update app name in flogo.json --- examples/flogo/dtable/flogo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/flogo/dtable/flogo.json b/examples/flogo/dtable/flogo.json index 1c4d955..2a5d89a 100644 --- a/examples/flogo/dtable/flogo.json +++ b/examples/flogo/dtable/flogo.json @@ -1,8 +1,8 @@ { - "name": "invokeservice", + "name": "decisiontable", "type": "flogo:app", "version": "0.0.1", - "description": "Sample Flogo App", + "description": "Sample Rules App", "appModel": "1.0.0", "imports": [ "github.com/project-flogo/contrib/trigger/rest", From 6c16b51dd428f91e9f62d51bc4c35514937a7349 Mon Sep 17 00:00:00 2001 From: nthota Date: Wed, 28 Aug 2019 11:57:55 +0530 Subject: [PATCH 063/125] reverting conditionvar nil check change --- rete/joinnode.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/rete/joinnode.go b/rete/joinnode.go index 2bda0fb..f33ee4d 100644 --- a/rete/joinnode.go +++ b/rete/joinnode.go @@ -43,16 +43,9 @@ func (jn *joinNodeImpl) initjoinNodeImplVar(nw *reteNetworkImpl, rule model.Rule jn.rightIdrs = rightIdrs jn.conditionVar = conditionVar - jointTableName := "" - if conditionVar != nil { - jointTableName = conditionVar.GetName() - } else { - jointTableName = rule.GetName() - } - - jn.leftTable = nw.GetJtService().GetOrCreateJoinTable(nw, rule, leftIdrs, "L_"+jointTableName) - jn.rightTable = nw.GetJtService().GetOrCreateJoinTable(nw, rule, rightIdrs, "R_"+jointTableName) - + // TODO conditionVar nil check + jn.leftTable = nw.GetJtService().GetOrCreateJoinTable(nw, rule, leftIdrs, "L_"+conditionVar.GetName()) + jn.rightTable = nw.GetJtService().GetOrCreateJoinTable(nw, rule, rightIdrs, "R_"+conditionVar.GetName()) jn.setJoinIdentifiers() } From 3530432997ddee7c06d8c59c72b3e9e56e2e37e2 Mon Sep 17 00:00:00 2001 From: nthota Date: Fri, 30 Aug 2019 14:07:33 +0530 Subject: [PATCH 064/125] decision table information fields added --- activity/dtable/activity.go | 48 ++++++++++++++---- activity/dtable/metadata.go | 54 +++++++++++++++++--- examples/flogo/dtable/flogo.json | 84 +++++++++++++++++++++++++++----- ruleapi/exprcondition.go | 12 ++++- 4 files changed, 167 insertions(+), 31 deletions(-) diff --git a/activity/dtable/activity.go b/activity/dtable/activity.go index 6dfbcff..0d5aca4 100644 --- a/activity/dtable/activity.go +++ b/activity/dtable/activity.go @@ -6,6 +6,7 @@ import ( "github.com/project-flogo/core/activity" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/ruleapi" ) func init() { @@ -14,7 +15,7 @@ func init() { // Activity decision table based rule action type Activity struct { - tasks []*Task + tasks []*DecisionTable } // New creates new decision table activity @@ -52,17 +53,46 @@ func (a *Activity) Eval(ctx activity.Context) (done bool, err error) { // Run tasks for _, task := range a.tasks { - tuple := tuples[model.TupleType(task.Tuple)] - if tuple == nil { - fmt.Printf("tuple[%s] not found \n", task.Tuple) - continue + + conditionEval := false + for _, cond := range task.DtConditions { + + eval := evaluateCondition(cond, tuples) + if eval { + conditionEval = true + continue + } else { + conditionEval = false + break + } + } + + if conditionEval { + for _, act := range task.DtActions { + tuple := tuples[model.TupleType(act.Tuple)] + if tuple == nil { + continue + } + mutableTuple := tuple.(model.MutableTuple) + mutableTuple.SetString(ctx1, act.Field, act.Value) + } } - fmt.Printf("tuple = %v \n", tuple) - mutableTuple := tuple.(model.MutableTuple) - mutableTuple.SetString(ctx1, task.Field, task.To) - fmt.Printf("updated tuple = %v \n", tuple) + } fmt.Println("DECISION TABLE EXIT") return false, nil } + +func evaluateCondition(cond *DtCondition, tuples map[model.TupleType]model.Tuple) bool { + + condExprsn := "$." + cond.Tuple + "." + cond.Field + " " + cond.Expr + + condExprs := ruleapi.NewExprCondition(condExprsn) + res, err := condExprs.Evaluate("", "", tuples, "") + if err != nil { + return false + } + + return res +} diff --git a/activity/dtable/metadata.go b/activity/dtable/metadata.go index 4e39b6a..82f36d4 100644 --- a/activity/dtable/metadata.go +++ b/activity/dtable/metadata.go @@ -1,6 +1,8 @@ package dtable import ( + "strings" + "github.com/project-flogo/core/data/coerce" "github.com/project-flogo/core/data/metadata" ) @@ -25,14 +27,27 @@ func (i *Input) FromMap(values map[string]interface{}) (err error) { // Settings activity settings type Settings struct { - Make []*Task `md:"make"` + Make []*DecisionTable `md:"make"` +} + +// DecisionTable decision table rows +type DecisionTable struct { + DtConditions []*DtCondition `md:"condition"` + DtActions []*DtAction `md:"action"` +} + +// DtCondition decision row condition +type DtCondition struct { + Tuple string `md:"tuple"` + Field string `md:"field"` + Expr string `md:"expr"` } -// Task task -type Task struct { +// DtAction decision row action +type DtAction struct { Tuple string `md:"tuple"` Field string `md:"field"` - To string `md:"to"` + Value string `md:"value"` } // FromMap fills Input struct from map @@ -41,11 +56,36 @@ func (s *Settings) FromMap(values map[string]interface{}) (err error) { if err != nil { return } - s.Make = make([]*Task, len(tasks)) + s.Make = make([]*DecisionTable, len(tasks)) for i, t := range tasks { - task := &Task{} + task := &DecisionTable{} + + condArr := make([]*DtCondition, 0) + actArr := make([]*DtAction, 0) + taskMap := t.(map[string]interface{}) - err = metadata.MapToStruct(taskMap, task, true) + + for k, v := range taskMap { + if strings.Compare(k, "condition") == 0 { + tempCondArr := v.([]interface{}) + for _, cond := range tempCondArr { + tempMap := cond.(map[string]interface{}) + dtCondtion := &DtCondition{} + err = metadata.MapToStruct(tempMap, dtCondtion, true) + condArr = append(condArr, dtCondtion) + } + } else { + tempActArr := v.([]interface{}) + for _, act := range tempActArr { + tempMap := act.(map[string]interface{}) + dtAction := &DtAction{} + err = metadata.MapToStruct(tempMap, dtAction, true) + actArr = append(actArr, dtAction) + } + } + } + task.DtConditions = condArr + task.DtActions = actArr s.Make[i] = task } return diff --git a/examples/flogo/dtable/flogo.json b/examples/flogo/dtable/flogo.json index 2a5d89a..32d4f3b 100644 --- a/examples/flogo/dtable/flogo.json +++ b/examples/flogo/dtable/flogo.json @@ -60,10 +60,10 @@ }, "rules": [ { - "name": "student.grade == GRADE-A", + "name": "student.careRequired", "conditions": [ { - "expression":"$.student.grade == 'GRADE-A'" + "expression": "$.student.careRequired == 'YES'" } ], "actionService": { @@ -72,13 +72,13 @@ "message": "=$.student.name" } }, - "priority":2 + "priority": 2 }, { "name": "student.name == changegrade.name", "conditions": [ { - "expression":"$.changegrade.name == $.student.name" + "expression": "$.changegrade.name == $.student.name" } ], "actionService": { @@ -87,7 +87,7 @@ "message": "=$.changegrade.name" } }, - "priority":2 + "priority": 2 } ], "services": [ @@ -96,9 +96,55 @@ "description": "Changes student grade", "type": "activity", "ref": "github.com/project-flogo/rules/activity/dtable", - "settings":{ - "make" : [ - {"tuple":"student", "field":"grade", "to": "GRADE-A"} + "settings": { + "make": [ + { + "condition": [ + { + "tuple": "student", + "field": "grade", + "expr": "== 'GRADE-C'" + }, + { + "tuple": "student", + "field": "class", + "expr": "== 'X-A'" + } + ], + "action": [ + { + "tuple": "student", + "field": "careRequired", + "value": "YES" + }, + { + "tuple": "student", + "field": "extraTimeToStudy", + "value": "2 hours" + } + ] + }, + { + "condition": [ + { + "tuple": "student", + "field": "grade", + "expr": "== 'GRADE-A'" + }, + { + "tuple": "student", + "field": "class", + "expr": "== 'X-A'" + } + ], + "action": [ + { + "tuple": "student", + "field": "careRequired", + "value": "NO" + } + ] + } ] } }, @@ -129,11 +175,24 @@ { "name": "grade", "type": "string" + }, + { + "name": "class", + "type": "string" + }, + { + "name": "careRequired", + "type": "string" + }, + { + "name": "extraTimeToStudy", + "type": "string" } ] }, { "name": "changegrade", + "ttl":0, "properties": [ { "name": "name", @@ -141,11 +200,10 @@ "type": "string" }, { - "name": "grade", - "type": "string" - } - ], - "ttl":0 + "name": "grade", + "type": "string" + } + ] } ] }, diff --git a/ruleapi/exprcondition.go b/ruleapi/exprcondition.go index 872826c..7cf8650 100644 --- a/ruleapi/exprcondition.go +++ b/ruleapi/exprcondition.go @@ -1,13 +1,14 @@ package ruleapi import ( + "reflect" + "strconv" + "github.com/project-flogo/core/data" "github.com/project-flogo/core/data/expression" "github.com/project-flogo/core/data/expression/script" "github.com/project-flogo/core/data/resolve" "github.com/project-flogo/rules/common/model" - "reflect" - "strconv" ) var td tuplePropertyResolver @@ -157,3 +158,10 @@ func (t *tuplePropertyResolver) Resolve(scope data.Scope, item string, field str func (*tuplePropertyResolver) GetResolverInfo() *resolve.ResolverInfo { return resolve.NewResolverInfo(false, false) } + +// NewExprCondition creates exprsn condition +func NewExprCondition(cExpr string) model.Condition { + c := exprConditionImpl{} + c.initExprConditionImpl("InstantCondition", nil, nil, cExpr, nil) + return &c +} From 62dc8e04e8a16e5bfe9f78de685ed2c5f0dd0a2e Mon Sep 17 00:00:00 2001 From: nthota Date: Thu, 5 Sep 2019 18:42:49 +0530 Subject: [PATCH 065/125] added decision table readme --- activity/dtable/activity.go | 55 ++++++++++++++++---- activity/dtable/metadata.go | 6 +-- examples/flogo/dtable/README.md | 88 ++++++++++++++++++++++++++++++++ examples/flogo/dtable/flogo.json | 82 +++++++++++------------------ examples/flogo/dtable/imports.go | 9 ++++ examples/flogo/dtable/main.go | 67 ++++++++++++++++++++++++ 6 files changed, 242 insertions(+), 65 deletions(-) create mode 100644 examples/flogo/dtable/README.md create mode 100644 examples/flogo/dtable/imports.go create mode 100644 examples/flogo/dtable/main.go diff --git a/activity/dtable/activity.go b/activity/dtable/activity.go index 0d5aca4..47eaad5 100644 --- a/activity/dtable/activity.go +++ b/activity/dtable/activity.go @@ -3,8 +3,10 @@ package dtable import ( "context" "fmt" + "strconv" "github.com/project-flogo/core/activity" + "github.com/project-flogo/core/data" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" ) @@ -43,17 +45,12 @@ func (a *Activity) Metadata() *activity.Metadata { // Eval implements decision table action func (a *Activity) Eval(ctx activity.Context) (done bool, err error) { - fmt.Println("DECISION TABLE ENTER") - ctx1 := ctx.GetInput("ctx").(context.Context) - // rs := ctx.GetInput("rulesession").(model.RuleSession) - // rName := ctx.GetInput("rulename").(string) + context := ctx.GetInput("ctx").(context.Context) tuples := ctx.GetInput("tuples").(map[model.TupleType]model.Tuple) - // rCtx := ctx.GetInput("rulecontext").(model.RuleContext) // Run tasks for _, task := range a.tasks { - conditionEval := false for _, cond := range task.DtConditions { @@ -74,13 +71,51 @@ func (a *Activity) Eval(ctx activity.Context) (done bool, err error) { continue } mutableTuple := tuple.(model.MutableTuple) - mutableTuple.SetString(ctx1, act.Field, act.Value) + + tds := mutableTuple.GetTupleDescriptor() + strVal := fmt.Sprintf("%v", act.Value) + + switch tds.GetProperty(act.Field).PropType { + case data.TypeString: + mutableTuple.SetString(context, act.Field, strVal) + case data.TypeBool: + b, err := strconv.ParseBool(strVal) + if err == nil { + mutableTuple.SetBool(context, act.Field, b) + } + case data.TypeInt: + i, err := strconv.ParseInt(strVal, 10, 64) + if err == nil { + mutableTuple.SetInt(context, act.Field, int(i)) + } + case data.TypeInt32: + i, err := strconv.ParseInt(strVal, 10, 64) + if err == nil { + mutableTuple.SetInt(context, act.Field, int(i)) + } + case data.TypeInt64: + i, err := strconv.ParseInt(strVal, 10, 64) + if err == nil { + mutableTuple.SetLong(context, act.Field, i) + } + case data.TypeFloat32: + f, err := strconv.ParseFloat(strVal, 32) + if err == nil { + mutableTuple.SetDouble(context, act.Field, f) + } + case data.TypeFloat64: + f, err := strconv.ParseFloat(strVal, 64) + if err == nil { + mutableTuple.SetDouble(context, act.Field, f) + } + default: + mutableTuple.SetValue(context, act.Field, act.Value) + + } + } } - } - - fmt.Println("DECISION TABLE EXIT") return false, nil } diff --git a/activity/dtable/metadata.go b/activity/dtable/metadata.go index 82f36d4..62e5b04 100644 --- a/activity/dtable/metadata.go +++ b/activity/dtable/metadata.go @@ -45,9 +45,9 @@ type DtCondition struct { // DtAction decision row action type DtAction struct { - Tuple string `md:"tuple"` - Field string `md:"field"` - Value string `md:"value"` + Tuple string `md:"tuple"` + Field string `md:"field"` + Value interface{} `md:"value"` } // FromMap fills Input struct from map diff --git a/examples/flogo/dtable/README.md b/examples/flogo/dtable/README.md new file mode 100644 index 0000000..5a12de5 --- /dev/null +++ b/examples/flogo/dtable/README.md @@ -0,0 +1,88 @@ +# Decision Table Usage + +This example demonstrates how to use decision table activity with student analysis example. + +## Setup and build +Once you have the `flogo.json` file, you are ready to build your Flogo App + +### Pre-requisites +* Go 1.11 +* Download and build the Flogo CLI 'flogo' and add it to your system PATH + +### Steps + +```sh +cd $GOPATH/src/github.com/project-flogo/rules/examples/flogo/dtable +flogo create -f flogo.json +cd dtable +flogo build +cd bin +./dtable +``` +### Testing + +#### #1 Invoke student analysis decision table + +Store student information. +```sh +curl localhost:7777/test/student?grade=GRADE-C\&name=s1\&class=X-A\&careRequired=false +curl localhost:7777/test/student?grade=GRADE-B\&name=s2\&class=X-A\&careRequired=false +``` + +Send a curl student analysis event. +```sh +curl localhost:7777/test/studentanalysis?name=s1 +curl localhost:7777/test/studentanalysis?name=s2 + +``` +You should see following output: +``` +2019-09-05T18:35:12.142+0530 INFO [flogo.rules] - s1 +2019-09-05T18:35:12.142+0530 INFO [flogo.rules] - s2 +``` + +### Writing Decision Table in JSON + +Sample usage can be as below. +```json + { + "name": "AnalyseStudent", + "description": "Analysing student data", + "type": "activity", + "ref": "github.com/project-flogo/rules/activity/dtable", + "settings": { + "make": [ + { + "condition": [ + {"tuple": "student","field": "grade","expr": "== 'GRADE-C'"}, + {"tuple": "student","field": "class","expr": "== 'X-A'"} + ], + "action": [ + { "tuple": "student","field": "careRequired","value": true}, + { "tuple": "student","field": "comments","value": "additional study hours required"} + ] + }, + { + "condition": [ + {"tuple": "student","field": "grade","expr": "== 'GRADE-A'"}, + {"tuple": "student","field": "class","expr": "== 'X-A'"} + ], + "action": [ + {"tuple": "student","field": "careRequired","value": false} + ] + }, + { + "condition": [ + {"tuple": "student","field": "grade","expr": "== 'GRADE-B'"}, + {"tuple": "student","field": "class","expr": "== 'X-A'"} + ], + "action": [ + {"tuple": "student","field": "careRequired","value": true}, + {"tuple": "student","field": "comments","value": "little care can be taken to achieve grade-a "} + ] + } + ] + } + } +``` +Decision table will have condition and action included into decition table activity. \ No newline at end of file diff --git a/examples/flogo/dtable/flogo.json b/examples/flogo/dtable/flogo.json index 32d4f3b..a7aa0c4 100644 --- a/examples/flogo/dtable/flogo.json +++ b/examples/flogo/dtable/flogo.json @@ -63,11 +63,11 @@ "name": "student.careRequired", "conditions": [ { - "expression": "$.student.careRequired == 'YES'" + "expression": "$.student.careRequired" } ], "actionService": { - "service": "LogAGradeStudent", + "service": "LogCareRequiredStudents", "input": { "message": "=$.student.name" } @@ -75,16 +75,16 @@ "priority": 2 }, { - "name": "student.name == changegrade.name", + "name": "student.name == studentanalysis.name", "conditions": [ { - "expression": "$.changegrade.name == $.student.name" + "expression": "$.studentanalysis.name == $.student.name" } ], "actionService": { - "service": "ChangeGrade", + "service": "AnalyseStudent", "input": { - "message": "=$.changegrade.name" + "message": "=$.studentanalysis.name" } }, "priority": 2 @@ -92,65 +92,47 @@ ], "services": [ { - "name": "ChangeGrade", - "description": "Changes student grade", + "name": "AnalyseStudent", + "description": "Analysing student data", "type": "activity", "ref": "github.com/project-flogo/rules/activity/dtable", "settings": { "make": [ { "condition": [ - { - "tuple": "student", - "field": "grade", - "expr": "== 'GRADE-C'" - }, - { - "tuple": "student", - "field": "class", - "expr": "== 'X-A'" - } + {"tuple": "student","field": "grade","expr": "== 'GRADE-C'"}, + {"tuple": "student","field": "class","expr": "== 'X-A'"} ], "action": [ - { - "tuple": "student", - "field": "careRequired", - "value": "YES" - }, - { - "tuple": "student", - "field": "extraTimeToStudy", - "value": "2 hours" - } + { "tuple": "student","field": "careRequired","value": true}, + { "tuple": "student","field": "comments","value": "additional study hours required"} ] }, { "condition": [ - { - "tuple": "student", - "field": "grade", - "expr": "== 'GRADE-A'" - }, - { - "tuple": "student", - "field": "class", - "expr": "== 'X-A'" - } + {"tuple": "student","field": "grade","expr": "== 'GRADE-A'"}, + {"tuple": "student","field": "class","expr": "== 'X-A'"} ], "action": [ - { - "tuple": "student", - "field": "careRequired", - "value": "NO" - } + {"tuple": "student","field": "careRequired","value": false} + ] + }, + { + "condition": [ + {"tuple": "student","field": "grade","expr": "== 'GRADE-B'"}, + {"tuple": "student","field": "class","expr": "== 'X-A'"} + ], + "action": [ + {"tuple": "student","field": "careRequired","value": true}, + {"tuple": "student","field": "comments","value": "little care can be taken to achieve grade-a "} ] } ] } }, { - "name": "LogAGradeStudent", - "description": "Logs A Grade Student", + "name": "LogCareRequiredStudents", + "description": "Logs Care required student names", "type": "activity", "ref": "github.com/project-flogo/contrib/activity/log" } @@ -182,26 +164,22 @@ }, { "name": "careRequired", - "type": "string" + "type": "bool" }, { - "name": "extraTimeToStudy", + "name": "comments", "type": "string" } ] }, { - "name": "changegrade", + "name": "studentanalysis", "ttl":0, "properties": [ { "name": "name", "pk-index": 0, "type": "string" - }, - { - "name": "grade", - "type": "string" } ] } diff --git a/examples/flogo/dtable/imports.go b/examples/flogo/dtable/imports.go new file mode 100644 index 0000000..dab9b05 --- /dev/null +++ b/examples/flogo/dtable/imports.go @@ -0,0 +1,9 @@ +package main + +import ( + _ "github.com/project-flogo/contrib/activity/log" + _ "github.com/project-flogo/contrib/trigger/rest" + _ "github.com/project-flogo/legacybridge" + _ "github.com/project-flogo/rules/activity/dtable" + _ "github.com/project-flogo/rules/ruleaction" +) diff --git a/examples/flogo/dtable/main.go b/examples/flogo/dtable/main.go new file mode 100644 index 0000000..234f2eb --- /dev/null +++ b/examples/flogo/dtable/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "flag" + "fmt" + "os" + "runtime" + "runtime/pprof" + + _ "github.com/project-flogo/core/data/expression/script" + "github.com/project-flogo/core/engine" +) + +var ( + cpuProfile = flag.String("cpuprofile", "", "Writes CPU profile to the specified file") + memProfile = flag.String("memprofile", "", "Writes memory profile to the specified file") + cfgJson string + cfgCompressed bool +) + +func main() { + + flag.Parse() + if *cpuProfile != "" { + f, err := os.Create(*cpuProfile) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to create CPU profiling file: %v\n", err) + os.Exit(1) + } + if err = pprof.StartCPUProfile(f); err != nil { + fmt.Fprintf(os.Stderr, "Failed to start CPU profiling: %v\n", err) + os.Exit(1) + } + defer pprof.StopCPUProfile() + } + + cfg, err := engine.LoadAppConfig(cfgJson, cfgCompressed) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to create engine: %v\n", err) + os.Exit(1) + } + + e, err := engine.New(cfg) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to create engine: %v\n", err) + os.Exit(1) + } + + code := engine.RunEngine(e) + + if *memProfile != "" { + f, err := os.Create(*memProfile) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to create memory profiling file: %v\n", err) + os.Exit(1) + } + + runtime.GC() // get up-to-date statistics + if err := pprof.WriteHeapProfile(f); err != nil { + fmt.Fprintf(os.Stderr, "Failed to write memory profiling data: %v", err) + os.Exit(1) + } + _ = f.Close() + } + + os.Exit(code) +} From beee2c110f31553876719c9ba49cc15e9ecd0ee9 Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Thu, 12 Sep 2019 13:39:33 -0600 Subject: [PATCH 066/125] Added ClearSessions function --- common/model/td_test.go | 3 ++ examples/trackntrace/trackntrace_test.go | 17 ++++--- go.mod | 6 ++- go.sum | 65 ++++++++++++++++++++++++ ruleapi/rulesession.go | 7 +++ ruleapi/tests/common.go | 3 ++ ruleapi/tests/rules_2_test.go | 3 ++ ruleapi/tests/sessions_test.go | 12 +++++ 8 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 go.sum create mode 100644 ruleapi/tests/sessions_test.go diff --git a/common/model/td_test.go b/common/model/td_test.go index d982a5d..b97b1bc 100644 --- a/common/model/td_test.go +++ b/common/model/td_test.go @@ -38,6 +38,9 @@ func TestOne(t *testing.T) { func TestTwo(t *testing.T) { tupleDescAbsFileNm := common.GetAbsPathForResource("src/github.com/project-flogo/rules/examples/rulesapp/rulesapp.json") + if tupleDescAbsFileNm == "" { + tupleDescAbsFileNm = "../../examples/rulesapp/rulesapp.json" + } tupleDescriptor := common.FileToString(tupleDescAbsFileNm) t.Logf("Loaded tuple descriptor: \n%s\n", tupleDescriptor) diff --git a/examples/trackntrace/trackntrace_test.go b/examples/trackntrace/trackntrace_test.go index b370547..865192c 100644 --- a/examples/trackntrace/trackntrace_test.go +++ b/examples/trackntrace/trackntrace_test.go @@ -1,16 +1,16 @@ package trackntrace import ( + "github.com/project-flogo/rules/common" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "github.com/project-flogo/rules/common" - "testing" - "time" - "io/ioutil" - "strconv" "context" "fmt" + "io/ioutil" + "strconv" + "testing" + "time" ) func TestPkgFlowNormal(t *testing.T) { @@ -197,12 +197,13 @@ func TestSameTupleInstanceAssert(t *testing.T) { rs.Unregister() } - - func createRuleSessionAndRules(t *testing.T) (model.RuleSession, error) { rs, _ := ruleapi.GetOrCreateRuleSession("asession") tupleDescFileAbsPath := common.GetAbsPathForResource("src/github.com/project-flogo/rules/examples/trackntrace/trackntrace.json") + if tupleDescFileAbsPath == "" { + tupleDescFileAbsPath = "trackntrace.json" + } dat, err := ioutil.ReadFile(tupleDescFileAbsPath) if err != nil { @@ -392,4 +393,4 @@ func packagedelayedAction(ctx context.Context, rs model.RuleSession, ruleName st fmt.Printf("Package is now delayed id[%s]\n", pkgid) } -type TestKey struct {} \ No newline at end of file +type TestKey struct{} diff --git a/go.mod b/go.mod index cfa514a..3c6c791 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,9 @@ require ( github.com/aws/aws-sdk-go v1.18.3 github.com/gorilla/websocket v1.4.0 github.com/oklog/ulid v1.3.1 - github.com/project-flogo/core v0.9.0-alpha.6 + github.com/project-flogo/contrib/trigger/kafka v0.9.0 + github.com/project-flogo/contrib/trigger/rest v0.9.0 + github.com/project-flogo/core v0.9.0 + github.com/square-it/jsonschema v1.9.1 // indirect + github.com/stretchr/testify v1.3.0 ) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..574268a --- /dev/null +++ b/go.sum @@ -0,0 +1,65 @@ +github.com/DataDog/zstd v1.3.5 h1:DtpNbljikUepEPD16hD4LvIcmhnhdLTiW/5pHgbmp14= +github.com/DataDog/zstd v1.3.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/Shopify/sarama v1.22.0 h1:rtiODsvY4jW6nUV6n3K+0gx/8WlAwVt+Ixt6RIvpYyo= +github.com/Shopify/sarama v1.22.0/go.mod h1:lm3THZ8reqBDBQKQyb5HB3sY1lKp3grEbQ81aWSgPp4= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/aws/aws-sdk-go v1.18.3 h1:6BkQIKBFCXw0zVQl5KC7o+J/zYTyz+DKWxL3Uy0XUjg= +github.com/aws/aws-sdk-go v1.18.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41 h1:GeinFsrjWz97fAxVUEd748aV0cYL+I6k44gFJTCVvpU= +github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/project-flogo/contrib/trigger/kafka v0.9.0 h1:xeGFM73L0jSYPOd0ihOIpkdF/8ko6/sOZircJYOT5Ew= +github.com/project-flogo/contrib/trigger/kafka v0.9.0/go.mod h1:NqVLzyM1u4dMGp9q06YsI/JahK/oFNvpV+72tKIt8bk= +github.com/project-flogo/contrib/trigger/rest v0.9.0 h1:3e9uLWLmg8VG8fcf8Fi4MrLptPc6WI1ZpaylC8eQ3Pk= +github.com/project-flogo/contrib/trigger/rest v0.9.0/go.mod h1:vxFRTpssjn5eF1fXdCc+L6+PxZOKm5iqkTZPA4lUIsY= +github.com/project-flogo/core v0.9.0-alpha.6 h1:ugQmmE1WQ75gzHwFpIv7Wbt1wTqNmWj94J8t6sKdKPw= +github.com/project-flogo/core v0.9.0-alpha.6/go.mod h1:eB+hMcq51lOIeJ93K8Nvvm/vC3OI60ZaEgBbA4gtk8k= +github.com/project-flogo/core v0.9.0 h1:/iR4m5L0zj5SuqLtDDZIRyvrvG8TxwxdM0n8ZURo1I4= +github.com/project-flogo/core v0.9.0/go.mod h1:QGWi7TDLlhGUaYH3n/16ImCuulbEHGADYEXyrcHhX7U= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/square-it/jsonschema v1.9.1/go.mod h1:80WJHSuy3YnokzfFopfx+MAt5lVVnVpS6w2Avv+svHk= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index 6df0fc9..0f7261a 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -26,6 +26,13 @@ type rulesessionImpl struct { started bool } +func ClearSessions() { + sessionMap.Range(func(key, value interface{}) bool { + sessionMap.Delete(key) + return true + }) +} + func GetOrCreateRuleSession(name string) (model.RuleSession, error) { if name == "" { return nil, errors.New("RuleSession name cannot be empty") diff --git a/ruleapi/tests/common.go b/ruleapi/tests/common.go index c1cf504..cad40a9 100644 --- a/ruleapi/tests/common.go +++ b/ruleapi/tests/common.go @@ -15,6 +15,9 @@ func createRuleSession() (model.RuleSession, error) { rs, _ := ruleapi.GetOrCreateRuleSession("test") tupleDescFileAbsPath := common.GetAbsPathForResource("src/github.com/project-flogo/rules/ruleapi/tests/tests.json") + if tupleDescFileAbsPath == "" { + tupleDescFileAbsPath = "tests.json" + } dat, err := ioutil.ReadFile(tupleDescFileAbsPath) if err != nil { diff --git a/ruleapi/tests/rules_2_test.go b/ruleapi/tests/rules_2_test.go index 4cca838..4a681d0 100644 --- a/ruleapi/tests/rules_2_test.go +++ b/ruleapi/tests/rules_2_test.go @@ -16,6 +16,9 @@ func Test_Two(t *testing.T) { //Load the tuple descriptor file (relative to GOPATH) tupleDescAbsFileNm := common.GetAbsPathForResource("src/github.com/project-flogo/rules/examples/rulesapp/rulesapp.json") + if tupleDescAbsFileNm == "" { + tupleDescAbsFileNm = "../../examples/rulesapp/rulesapp.json" + } tupleDescriptor := common.FileToString(tupleDescAbsFileNm) // fmt.Printf("Loaded tuple descriptor: \n%s\n", tupleDescriptor) diff --git a/ruleapi/tests/sessions_test.go b/ruleapi/tests/sessions_test.go new file mode 100644 index 0000000..63ac21f --- /dev/null +++ b/ruleapi/tests/sessions_test.go @@ -0,0 +1,12 @@ +package tests + +import ( + "testing" + + "github.com/project-flogo/rules/ruleapi" +) + +func TestClearSessions(t *testing.T) { + ruleapi.GetOrCreateRuleSession("test") + ruleapi.ClearSessions() +} From cb2a6afe518054518b1229cac9342241ab9d5101 Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Fri, 13 Sep 2019 12:50:45 -0600 Subject: [PATCH 067/125] Fixed race condition --- rete/network.go | 63 +++++++++++++++++++++++++++++----- ruleapi/rulesession.go | 9 +---- ruleapi/tests/sessions_test.go | 31 +++++++++++++++++ ruleapi/tests/tests.json | 25 +++++++++++++- 4 files changed, 110 insertions(+), 18 deletions(-) diff --git a/rete/network.go b/rete/network.go index 52dc416..2dde16a 100644 --- a/rete/network.go +++ b/rete/network.go @@ -28,7 +28,7 @@ type Network interface { RemoveRule(string) model.Rule GetRules() []model.Rule //changedProps are the properties that changed in a previous action - Assert(ctx context.Context, rs model.RuleSession, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) + Assert(ctx context.Context, rs model.RuleSession, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) error //mode can be one of retract, modify, delete Retract(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) @@ -63,7 +63,7 @@ type reteNetworkImpl struct { currentId int assertLock sync.Mutex - crudLock sync.Mutex + crudLock sync.RWMutex txnHandler model.RtcTransactionHandler txnContext interface{} } @@ -185,6 +185,9 @@ func (nw *reteNetworkImpl) RemoveRule(ruleName string) model.Rule { } func (nw *reteNetworkImpl) GetRules() []model.Rule { + nw.crudLock.RLock() + defer nw.crudLock.RUnlock() + rules := make([]model.Rule, 0) for _, rule := range nw.allRules { @@ -473,16 +476,32 @@ func getClassNode(nw *reteNetworkImpl, name model.TupleType) classNode { } func (nw *reteNetworkImpl) String() string { + nw.crudLock.RLock() + defer nw.crudLock.RUnlock() str := "\n>>> Class View <<<\n" - for _, classNodeImpl := range nw.allClassNodes { str += classNodeImpl.String() + "\n" } - str += ">>>> Rule View <<<<\n" + str += ">>>> Rule View <<<<\n" for _, rule := range nw.allRules { - str += nw.PrintRule(rule) + str += "[Rule (" + rule.GetName() + ") Id()]\n" + nodesOfRule := nw.ruleNameNodesOfRule[rule.GetName()] + for e := nodesOfRule.Front(); e != nil; e = e.Next() { + n := e.Value.(abstractNode) + switch nodeImpl := n.(type) { + case *filterNodeImpl: + str += nodeImpl.String() + case *joinNodeImpl: + str += nodeImpl.String() + case *classNodeImpl: + str += nw.printClassNode(rule.GetName(), nodeImpl) + case *ruleNodeImpl: + str += nodeImpl.String() + } + str += "\n" + } } return str @@ -493,6 +512,9 @@ func pickIdentifier(idrs []model.TupleType) model.TupleType { } func (nw *reteNetworkImpl) PrintRule(rule model.Rule) string { + nw.crudLock.RLock() + defer nw.crudLock.RUnlock() + //str := "[Rule (" + rule.GetName() + ") Id(" + strconv.Itoa(rule.GetID()) + ")]\n" str := "[Rule (" + rule.GetName() + ") Id()]\n" @@ -527,7 +549,7 @@ func (nw *reteNetworkImpl) printClassNode(ruleName string, classNodeImpl *classN return "\t[ClassNode Class(" + classNodeImpl.getName() + ")" + links + "]\n" } -func (nw *reteNetworkImpl) Assert(ctx context.Context, rs model.RuleSession, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) { +func (nw *reteNetworkImpl) Assert(ctx context.Context, rs model.RuleSession, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) error { if ctx == nil { ctx = context.Background() @@ -538,6 +560,16 @@ func (nw *reteNetworkImpl) Assert(ctx context.Context, rs model.RuleSession, tup if !isRecursive { nw.crudLock.Lock() defer nw.crudLock.Unlock() + + if mode == ADD { + assertedTuple := nw.GetAssertedTuple(tuple.GetKey()) + if assertedTuple == tuple { + return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) + } else if assertedTuple != nil { + return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) + } + } + nw.assertInternal(newCtx, tuple, changedProps, mode) reteCtxVar.getConflictResolver().resolveConflict(newCtx) //if Timeout is 0, remove it from rete @@ -546,7 +578,9 @@ func (nw *reteNetworkImpl) Assert(ctx context.Context, rs model.RuleSession, tup if td.TTLInSeconds == 0 { //remove immediately. nw.removeTupleFromRete(tuple) } else if td.TTLInSeconds > 0 { // TTL for the tuple type, after that, remove it from RETE - go time.AfterFunc(time.Second*time.Duration(td.TTLInSeconds), func() { + time.AfterFunc(time.Second*time.Duration(td.TTLInSeconds), func() { + nw.crudLock.Lock() + defer nw.crudLock.Unlock() nw.removeTupleFromRete(tuple) }) } //else, its -ve and means, never expire @@ -555,9 +589,20 @@ func (nw *reteNetworkImpl) Assert(ctx context.Context, rs model.RuleSession, tup rtcTxn := newRtcTxn(reteCtxVar.getRtcAdded(), reteCtxVar.getRtcModified(), reteCtxVar.getRtcDeleted()) nw.txnHandler(ctx, rs, rtcTxn, nw.txnContext) } - } else { - reteCtxVar.getOpsList().PushBack(newAssertEntry(tuple, changedProps, mode)) + return nil } + + if mode == ADD { + assertedTuple := nw.GetAssertedTuple(tuple.GetKey()) + if assertedTuple == tuple { + return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) + } else if assertedTuple != nil { + return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) + } + } + + reteCtxVar.getOpsList().PushBack(newAssertEntry(tuple, changedProps, mode)) + return nil } func (nw *reteNetworkImpl) removeTupleFromRete(tuple model.Tuple) { diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index 0f7261a..a826094 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -97,17 +97,10 @@ func (rs *rulesessionImpl) Assert(ctx context.Context, tuple model.Tuple) (err e if !rs.started { return fmt.Errorf("Cannot assert tuple. Rulesession [%s] not started", rs.name) } - assertedTuple := rs.GetAssertedTuple(tuple.GetKey()) - if assertedTuple == tuple { - return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) - } else if assertedTuple != nil { - return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) - } if ctx == nil { ctx = context.Context(context.Background()) } - rs.reteNetwork.Assert(ctx, rs, tuple, nil, rete.ADD) - return nil + return rs.reteNetwork.Assert(ctx, rs, tuple, nil, rete.ADD) } func (rs *rulesessionImpl) Retract(ctx context.Context, tuple model.Tuple) { diff --git a/ruleapi/tests/sessions_test.go b/ruleapi/tests/sessions_test.go index 63ac21f..246206d 100644 --- a/ruleapi/tests/sessions_test.go +++ b/ruleapi/tests/sessions_test.go @@ -1,8 +1,11 @@ package tests import ( + "context" "testing" + "time" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" ) @@ -10,3 +13,31 @@ func TestClearSessions(t *testing.T) { ruleapi.GetOrCreateRuleSession("test") ruleapi.ClearSessions() } + +func TestAssert(t *testing.T) { + rs, _ := createRuleSession() + rule := ruleapi.NewRule("R2") + rule.AddCondition("R2_c1", []string{"t4.none"}, trueCondition, nil) + rule.SetAction(emptyAction) + rule.SetPriority(1) + rs.AddRule(rule) + t.Logf("Rule added: [%s]\n", rule.GetName()) + rs.Start(nil) + + t1, _ := model.NewTupleWithKeyValues("t4", "t4") + err := rs.Assert(context.TODO(), t1) + if err != nil { + t.Fatalf("err should be nil: %v", err) + } + err = rs.Assert(context.TODO(), t1) + if err == nil { + t.Fatalf("err should be not be nil: %v", err) + } + + time.Sleep(2 * time.Second) + err = rs.Assert(context.TODO(), t1) + if err != nil { + t.Fatalf("err should be nil: %v", err) + } + rs.Unregister() +} diff --git a/ruleapi/tests/tests.json b/ruleapi/tests/tests.json index 40a4d82..3496558 100644 --- a/ruleapi/tests/tests.json +++ b/ruleapi/tests/tests.json @@ -65,5 +65,28 @@ "type":"string" } ] + }, + { + "name":"t4", + "ttl": 1, + "properties":[ + { + "name":"id", + "type":"string", + "pk-index":0 + }, + { + "name":"p1", + "type":"int" + }, + { + "name":"p2", + "type":"double" + }, + { + "name":"p3", + "type":"string" + } + ] } -] \ No newline at end of file +] From ae2e3c1a4ce48f421735016dc9d0febe5ec5d9fa Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Fri, 13 Sep 2019 13:46:03 -0600 Subject: [PATCH 068/125] Added GetPathForResource function --- common/model/td_test.go | 5 +---- common/utils.go | 22 ++++++++++++++++++++++ examples/ordermanagement/main.go | 16 +++++++++------- examples/rulesapp/main.go | 4 ++-- examples/trackntrace/trackntrace_test.go | 5 +---- ruleapi/tests/common.go | 5 +---- ruleapi/tests/rules_2_test.go | 5 +---- 7 files changed, 37 insertions(+), 25 deletions(-) diff --git a/common/model/td_test.go b/common/model/td_test.go index b97b1bc..9584616 100644 --- a/common/model/td_test.go +++ b/common/model/td_test.go @@ -37,10 +37,7 @@ func TestOne(t *testing.T) { } func TestTwo(t *testing.T) { - tupleDescAbsFileNm := common.GetAbsPathForResource("src/github.com/project-flogo/rules/examples/rulesapp/rulesapp.json") - if tupleDescAbsFileNm == "" { - tupleDescAbsFileNm = "../../examples/rulesapp/rulesapp.json" - } + tupleDescAbsFileNm := common.GetPathForResource("examples/rulesapp/rulesapp.json", "../../examples/rulesapp/rulesapp.json") tupleDescriptor := common.FileToString(tupleDescAbsFileNm) t.Logf("Loaded tuple descriptor: \n%s\n", tupleDescriptor) diff --git a/common/utils.go b/common/utils.go index 47131e8..5094a7f 100644 --- a/common/utils.go +++ b/common/utils.go @@ -57,6 +57,28 @@ func GetAbsPathForResource(resourcepath string) string { return "" } +// GetPathForResource gets the resource path off $GOPATH or relative +func GetPathForResource(resource, relative string) string { + GOPATH := os.Getenv("GOPATH") + // fmt.Printf("GOPATH - [%s]\n", GOPATH) + paths := strings.Split(GOPATH, ":") + for _, path := range paths { + // fmt.Printf("path[%s]\n", path) + absPath := path + "/src/github.com/project-flogo/rules/" + resource + _, err := os.Stat(absPath) + if err == nil { + return absPath + } + } + + _, err := os.Stat(relative) + if err == nil { + return relative + } + + return "" +} + // FileToString reads file content to string func FileToString(fileName string) string { dat, err := ioutil.ReadFile(fileName) diff --git a/examples/ordermanagement/main.go b/examples/ordermanagement/main.go index a09cc25..5c8ce0b 100644 --- a/examples/ordermanagement/main.go +++ b/examples/ordermanagement/main.go @@ -15,9 +15,11 @@ import ( //go:generate go run $GOPATH/src/github.com/TIBCOSoftware/flogo-lib/flogo/gen/gen.go $GOPATH const ( - msgValueField = "message" - tupleSchemaPath = "src/github.com/project-flogo/rules/examples/ordermanagement/schema/oms_schema.json" - ruleDefinitionPath = "src/github.com/project-flogo/rules/examples/ordermanagement/schema/rule_definition.json" + msgValueField = "message" + tupleSchemaPath = "examples/ordermanagement/schema/oms_schema.json" + ruleDefinitionPath = "examples/ordermanagement/schema/rule_definition.json" + tupleSchemaRelativePath = "./schema/oms_schema.json" + ruleDefinitionRelativePath = "./schema/rule_definition.json" ) // cli arguments @@ -75,7 +77,7 @@ func main() { // loads the tuple schema func loadTupleSchema() error { - content := getFileContent(tupleSchemaPath) + content := getFileContent(tupleSchemaPath, tupleSchemaRelativePath) err := model.RegisterTupleDescriptors(string(content)) if err != nil { return err @@ -85,13 +87,13 @@ func loadTupleSchema() error { // create rulesession and load rules in it func createAndLoadRuleSession() (model.RuleSession, error) { - content := getFileContent(ruleDefinitionPath) + content := getFileContent(ruleDefinitionPath, ruleDefinitionRelativePath) return ruleapi.GetOrCreateRuleSessionFromConfig("oms_session", string(content)) } // Get file content -func getFileContent(filePath string) string { - absPath := common.GetAbsPathForResource(filePath) +func getFileContent(filePath, relative string) string { + absPath := common.GetPathForResource(filePath, relative) return common.FileToString(absPath) } diff --git a/examples/rulesapp/main.go b/examples/rulesapp/main.go index 0cfef18..a89781b 100644 --- a/examples/rulesapp/main.go +++ b/examples/rulesapp/main.go @@ -4,9 +4,9 @@ import ( "context" "fmt" + "github.com/project-flogo/rules/common" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "github.com/project-flogo/rules/common" ) func main() { @@ -14,7 +14,7 @@ func main() { fmt.Println("** rulesapp: Example usage of the Rules module/API **") //Load the tuple descriptor file (relative to GOPATH) - tupleDescAbsFileNm := common.GetAbsPathForResource("src/github.com/project-flogo/rules/examples/rulesapp/rulesapp.json") + tupleDescAbsFileNm := common.GetPathForResource("examples/rulesapp/rulesapp.json", "./rulesapp.json") tupleDescriptor := common.FileToString(tupleDescAbsFileNm) fmt.Printf("Loaded tuple descriptor: \n%s\n", tupleDescriptor) diff --git a/examples/trackntrace/trackntrace_test.go b/examples/trackntrace/trackntrace_test.go index 865192c..365eeb8 100644 --- a/examples/trackntrace/trackntrace_test.go +++ b/examples/trackntrace/trackntrace_test.go @@ -200,10 +200,7 @@ func TestSameTupleInstanceAssert(t *testing.T) { func createRuleSessionAndRules(t *testing.T) (model.RuleSession, error) { rs, _ := ruleapi.GetOrCreateRuleSession("asession") - tupleDescFileAbsPath := common.GetAbsPathForResource("src/github.com/project-flogo/rules/examples/trackntrace/trackntrace.json") - if tupleDescFileAbsPath == "" { - tupleDescFileAbsPath = "trackntrace.json" - } + tupleDescFileAbsPath := common.GetPathForResource("examples/trackntrace/trackntrace.json", "./trackntrace.json") dat, err := ioutil.ReadFile(tupleDescFileAbsPath) if err != nil { diff --git a/ruleapi/tests/common.go b/ruleapi/tests/common.go index cad40a9..c202d36 100644 --- a/ruleapi/tests/common.go +++ b/ruleapi/tests/common.go @@ -14,10 +14,7 @@ import ( func createRuleSession() (model.RuleSession, error) { rs, _ := ruleapi.GetOrCreateRuleSession("test") - tupleDescFileAbsPath := common.GetAbsPathForResource("src/github.com/project-flogo/rules/ruleapi/tests/tests.json") - if tupleDescFileAbsPath == "" { - tupleDescFileAbsPath = "tests.json" - } + tupleDescFileAbsPath := common.GetPathForResource("ruleapi/tests/tests.json", "./tests.json") dat, err := ioutil.ReadFile(tupleDescFileAbsPath) if err != nil { diff --git a/ruleapi/tests/rules_2_test.go b/ruleapi/tests/rules_2_test.go index 4a681d0..d9f000c 100644 --- a/ruleapi/tests/rules_2_test.go +++ b/ruleapi/tests/rules_2_test.go @@ -15,10 +15,7 @@ func Test_Two(t *testing.T) { // fmt.Println("** rulesapp: Example usage of the Rules module/API **") //Load the tuple descriptor file (relative to GOPATH) - tupleDescAbsFileNm := common.GetAbsPathForResource("src/github.com/project-flogo/rules/examples/rulesapp/rulesapp.json") - if tupleDescAbsFileNm == "" { - tupleDescAbsFileNm = "../../examples/rulesapp/rulesapp.json" - } + tupleDescAbsFileNm := common.GetPathForResource("examples/rulesapp/rulesapp.json", "../../examples/rulesapp/rulesapp.json") tupleDescriptor := common.FileToString(tupleDescAbsFileNm) // fmt.Printf("Loaded tuple descriptor: \n%s\n", tupleDescriptor) From 2ada74935f1fa83fa6ccb47bd9e70baeae201de8 Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Fri, 13 Sep 2019 14:38:48 -0600 Subject: [PATCH 069/125] Fixed races in ruleapi package --- ruleapi/rulesession.go | 10 +++++++++ ruleapi/tests/identifier_1_test.go | 19 ++++++++-------- ruleapi/tests/identifier_2_test.go | 35 +++++++++++++++--------------- ruleapi/tests/rtctxn_15_test.go | 13 ++++++----- ruleapi/tests/rtctxn_16_test.go | 9 ++++---- ruleapi/tests/rules_3_test.go | 9 ++++---- 6 files changed, 55 insertions(+), 40 deletions(-) diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index a826094..9c3e12c 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -18,6 +18,8 @@ var ( ) type rulesessionImpl struct { + sync.RWMutex + name string reteNetwork rete.Network @@ -126,17 +128,25 @@ func (rs *rulesessionImpl) Unregister() { func (rs *rulesessionImpl) ScheduleAssert(ctx context.Context, delayInMillis uint64, key interface{}, tuple model.Tuple) { timer := time.AfterFunc(time.Millisecond*time.Duration(delayInMillis), func() { + rs.Lock() + defer rs.Unlock() ctxNew := context.TODO() delete(rs.timers, key) rs.Assert(ctxNew, tuple) }) + rs.Lock() + defer rs.Unlock() rs.timers[key] = timer } func (rs *rulesessionImpl) CancelScheduledAssert(ctx context.Context, key interface{}) { + rs.RLock() timer, ok := rs.timers[key] + rs.RUnlock() if ok { + rs.Lock() + defer rs.Unlock() fmt.Printf("Cancelling timer attached to key [%v]\n", key) delete(rs.timers, key) timer.Stop() diff --git a/ruleapi/tests/identifier_1_test.go b/ruleapi/tests/identifier_1_test.go index 7dfc736..2cd6635 100644 --- a/ruleapi/tests/identifier_1_test.go +++ b/ruleapi/tests/identifier_1_test.go @@ -2,13 +2,14 @@ package tests import ( "context" + "sync/atomic" "testing" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" ) -var count int +var count uint64 //Check if all combination of tuples t1 and t3 are triggering actions func Test_I1(t *testing.T) { @@ -34,8 +35,8 @@ func Test_I1(t *testing.T) { rs.Assert(context.TODO(), t3) //Check if the 2 combinations {t1=t10,t3=t12} and {t1=t11,t3=t12} triggers action twice - if count != 2 { - t.Errorf("Expecting [2] actions, got [%d]", count) + if cnt := atomic.LoadUint64(&count); cnt != 2 { + t.Errorf("Expecting [2] actions, got [%d]", cnt) t.FailNow() } @@ -43,8 +44,8 @@ func Test_I1(t *testing.T) { rs.Assert(context.TODO(), t4) //Check if the 2 combinations {t1=t10,t3=t13} and {t1=t11,t3=t13} triggers action two more times making the total action count 4 - if count != 4 { - t.Errorf("Expecting [4] actions, got [%d]", count) + if cnt := atomic.LoadUint64(&count); cnt != 4 { + t.Errorf("Expecting [4] actions, got [%d]", cnt) t.FailNow() } @@ -60,15 +61,15 @@ func i1_action(ctx context.Context, rs model.RuleSession, ruleName string, tuple id3, _ := t2.GetString("id") if id1 == "t11" && id3 == "t12" { - count++ + atomic.AddUint64(&count, 1) } if id1 == "t10" && id3 == "t12" { - count++ + atomic.AddUint64(&count, 1) } if id1 == "t11" && id3 == "t13" { - count++ + atomic.AddUint64(&count, 1) } if id1 == "t10" && id3 == "t13" { - count++ + atomic.AddUint64(&count, 1) } } diff --git a/ruleapi/tests/identifier_2_test.go b/ruleapi/tests/identifier_2_test.go index 7e82a51..02d4f2f 100644 --- a/ruleapi/tests/identifier_2_test.go +++ b/ruleapi/tests/identifier_2_test.go @@ -2,13 +2,14 @@ package tests import ( "context" + "sync/atomic" "testing" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" ) -var cnt int +var cnt uint64 //Using 3 Identifiers, different Join conditions and triggering respective actions --->Verify order of actions and count. func Test_I2(t *testing.T) { @@ -57,24 +58,24 @@ func Test_I2(t *testing.T) { t2, _ := model.NewTupleWithKeyValues("t2", "t11") rs.Assert(context.TODO(), t2) - if cnt != 1 { - t.Errorf("Expecting [1] actions, got [%d]", cnt) + if count := atomic.LoadUint64(&cnt); count != 1 { + t.Errorf("Expecting [1] actions, got [%d]", count) t.FailNow() } t3, _ := model.NewTupleWithKeyValues("t3", "t12") rs.Assert(context.TODO(), t3) - if cnt != 2 { - t.Errorf("Expecting [2] actions, got [%d]", cnt) + if count := atomic.LoadUint64(&cnt); count != 2 { + t.Errorf("Expecting [2] actions, got [%d]", count) t.FailNow() } t4, _ := model.NewTupleWithKeyValues("t2", "t13") rs.Assert(context.TODO(), t4) - if cnt != 5 { - t.Errorf("Expecting [5] actions, got [%d]", cnt) + if count := atomic.LoadUint64(&cnt); count != 5 { + t.Errorf("Expecting [5] actions, got [%d]", count) t.FailNow() } @@ -89,13 +90,13 @@ func i21_action(ctx context.Context, rs model.RuleSession, ruleName string, tupl t2 := tuples[model.TupleType("t2")].(model.MutableTuple) id2, _ := t2.GetString("id") - if id1 == "t10" && id2 == "t11" && cnt == 0 { - cnt++ + if count := atomic.LoadUint64(&cnt); id1 == "t10" && id2 == "t11" && count == 0 { + atomic.AddUint64(&cnt, 1) } if id1 == "t10" && id2 == "t13" { - if cnt >= 2 && cnt <= 4 { - cnt++ + if count := atomic.LoadUint64(&cnt); count >= 2 && count <= 4 { + atomic.AddUint64(&cnt, 1) } } } @@ -107,8 +108,8 @@ func i22_action(ctx context.Context, rs model.RuleSession, ruleName string, tupl t2 := tuples[model.TupleType("t3")].(model.MutableTuple) id3, _ := t2.GetString("id") - if id1 == "t10" && id3 == "t12" && cnt == 1 { - cnt++ + if count := atomic.LoadUint64(&cnt); id1 == "t10" && id3 == "t12" && count == 1 { + atomic.AddUint64(&cnt, 1) } } @@ -120,8 +121,8 @@ func i23_action(ctx context.Context, rs model.RuleSession, ruleName string, tupl id3, _ := t2.GetString("id") if id1 == "t13" && id3 == "t12" { - if cnt >= 2 && cnt <= 4 { - cnt++ + if count := atomic.LoadUint64(&cnt); count >= 2 && count <= 4 { + atomic.AddUint64(&cnt, 1) } } } @@ -137,8 +138,8 @@ func i24_action(ctx context.Context, rs model.RuleSession, ruleName string, tupl id3, _ := t3.GetString("id") if id1 == "t10" && id2 == "t13" && id3 == "t12" { - if cnt >= 2 && cnt <= 4 { - cnt++ + if count := atomic.LoadUint64(&cnt); count >= 2 && count <= 4 { + atomic.AddUint64(&cnt, 1) } } } diff --git a/ruleapi/tests/rtctxn_15_test.go b/ruleapi/tests/rtctxn_15_test.go index 86e5aff..f9d5cb5 100644 --- a/ruleapi/tests/rtctxn_15_test.go +++ b/ruleapi/tests/rtctxn_15_test.go @@ -2,6 +2,7 @@ package tests import ( "context" + "sync/atomic" "testing" "time" @@ -9,7 +10,7 @@ import ( "github.com/project-flogo/rules/ruleapi" ) -var actionCnt int +var actionCnt uint64 //1 rtc->Scheduled assert, Action should be fired after the delay time. func Test_T15(t *testing.T) { @@ -28,14 +29,14 @@ func Test_T15(t *testing.T) { t1, _ := model.NewTupleWithKeyValues("t1", "t10") rs.ScheduleAssert(context.TODO(), 1000, "1", t1) - if actionCnt != 0 { - t.Errorf("Expecting [0] actions, got [%d]", actionCnt) + if count := atomic.LoadUint64(&actionCnt); count != 0 { + t.Errorf("Expecting [0] actions, got [%d]", count) t.FailNow() } time.Sleep(2000 * time.Millisecond) - if actionCnt != 1 { - t.Errorf("Expecting [1] actions, got [%d]", actionCnt) + if count := atomic.LoadUint64(&actionCnt); count != 1 { + t.Errorf("Expecting [1] actions, got [%d]", count) t.FailNow() } @@ -44,5 +45,5 @@ func Test_T15(t *testing.T) { } func r15_action(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { - actionCnt++ + atomic.AddUint64(&actionCnt, 1) } diff --git a/ruleapi/tests/rtctxn_16_test.go b/ruleapi/tests/rtctxn_16_test.go index b89dac7..b32e849 100644 --- a/ruleapi/tests/rtctxn_16_test.go +++ b/ruleapi/tests/rtctxn_16_test.go @@ -2,6 +2,7 @@ package tests import ( "context" + "sync/atomic" "testing" "time" @@ -9,7 +10,7 @@ import ( "github.com/project-flogo/rules/ruleapi" ) -var actionCnt1 int +var actionCnt1 uint64 //1 rtc->Schedule assert, Cancel scheduled assert and action should not be fired func Test_T16(t *testing.T) { @@ -31,8 +32,8 @@ func Test_T16(t *testing.T) { time.Sleep(2000 * time.Millisecond) - if actionCnt1 != 0 { - t.Errorf("Expecting [0] actions, got [%d]", actionCnt1) + if count := atomic.LoadUint64(&actionCnt1); count != 0 { + t.Errorf("Expecting [0] actions, got [%d]", count) t.FailNow() } @@ -41,5 +42,5 @@ func Test_T16(t *testing.T) { } func r16_action(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { - actionCnt1++ + atomic.AddUint64(&actionCnt1, 1) } diff --git a/ruleapi/tests/rules_3_test.go b/ruleapi/tests/rules_3_test.go index 0b54fe6..05ced93 100644 --- a/ruleapi/tests/rules_3_test.go +++ b/ruleapi/tests/rules_3_test.go @@ -2,13 +2,14 @@ package tests import ( "context" + "sync/atomic" "testing" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" ) -var actCnt int +var actCnt uint64 //Forward chain-Data change in r3action and r32action triggers the r32action. func Test_Three(t *testing.T) { @@ -42,8 +43,8 @@ func Test_Three(t *testing.T) { t2.SetInt(context.TODO(), "p1", 2000) rs.Assert(context.TODO(), t2) - if actCnt != 2 { - t.Errorf("Expecting [2] actions, got [%d]", actCnt) + if count := atomic.LoadUint64(&actCnt); count != 2 { + t.Errorf("Expecting [2] actions, got [%d]", count) t.FailNow() } @@ -76,7 +77,7 @@ func r3action(ctx context.Context, rs model.RuleSession, ruleName string, tuples func r32action(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { //fmt.Println("r132_action triggered") - actCnt++ + atomic.AddUint64(&actCnt, 1) tk, _ := model.NewTupleKeyWithKeyValues("t1", "t10") t10 := rs.GetAssertedTuple(tk).(model.MutableTuple) From f045de17f0f15c9850c9ec92fe96d845090cc4d9 Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Mon, 16 Sep 2019 13:43:38 -0600 Subject: [PATCH 070/125] Added race condition test --- ruleapi/tests/sessions_test.go | 57 ++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/ruleapi/tests/sessions_test.go b/ruleapi/tests/sessions_test.go index 246206d..2ef4afe 100644 --- a/ruleapi/tests/sessions_test.go +++ b/ruleapi/tests/sessions_test.go @@ -2,6 +2,7 @@ package tests import ( "context" + "fmt" "testing" "time" @@ -41,3 +42,59 @@ func TestAssert(t *testing.T) { } rs.Unregister() } + +func TestRace(t *testing.T) { + rs, _ := createRuleSession() + defer rs.Unregister() + rule := ruleapi.NewRule("R2") + rule.AddCondition("R2_c1", []string{"t4.none"}, trueCondition, nil) + rule.SetAction(emptyAction) + rule.SetPriority(1) + rs.AddRule(rule) + t.Logf("Rule added: [%s]\n", rule.GetName()) + rs.Start(nil) + + done := make(chan bool, 8) + withTTL := func() { + for i := 0; i < 10; i++ { + t1, _ := model.NewTupleWithKeyValues("t4", fmt.Sprintf("ttl%d", i)) + err := rs.Assert(context.TODO(), t1) + if err != nil { + t.Fatalf("err should be nil: %v", err) + } + time.Sleep(10 * time.Millisecond) + } + done <- true + } + withDelete := func() { + for i := 0; i < 10; i++ { + t1, _ := model.NewTupleWithKeyValues("t3", fmt.Sprintf("delete%d", i)) + err := rs.Assert(context.TODO(), t1) + if err != nil { + t.Fatalf("err should be nil: %v", err) + } + time.Sleep(10 * time.Millisecond) + rs.Delete(context.TODO(), t1) + time.Sleep(10 * time.Millisecond) + } + done <- true + } + addOnly := func() { + for i := 0; i < 10; i++ { + t1, _ := model.NewTupleWithKeyValues("t3", fmt.Sprintf("add%d", i)) + err := rs.Assert(context.TODO(), t1) + if err != nil { + t.Fatalf("err should be nil: %v", err) + } + time.Sleep(10 * time.Millisecond) + } + done <- true + } + go withTTL() + go withDelete() + go addOnly() + for i := 0; i < 3; i++ { + <-done + } + time.Sleep(3 * time.Second) +} From aad89adddbb69b2fbc084c8f7257ca76abc8363c Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Mon, 16 Sep 2019 15:40:23 -0600 Subject: [PATCH 071/125] Added memory leak test --- ruleapi/tests/memory/memory.go | 129 +++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 ruleapi/tests/memory/memory.go diff --git a/ruleapi/tests/memory/memory.go b/ruleapi/tests/memory/memory.go new file mode 100644 index 0000000..6bfff3b --- /dev/null +++ b/ruleapi/tests/memory/memory.go @@ -0,0 +1,129 @@ +package main + +import ( + "context" + "flag" + "fmt" + "io/ioutil" + "log" + "net/http" + _ "net/http/pprof" + "time" + + "github.com/project-flogo/rules/common" + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/ruleapi" +) + +func createRuleSession() (model.RuleSession, error) { + rs, _ := ruleapi.GetOrCreateRuleSession("test") + + tupleDescFileAbsPath := common.GetPathForResource("ruleapi/tests/tests.json", "./../tests.json") + + dat, err := ioutil.ReadFile(tupleDescFileAbsPath) + if err != nil { + log.Fatal(err) + } + err = model.RegisterTupleDescriptors(string(dat)) + if err != nil { + return nil, err + } + return rs, nil +} + +func trueCondition(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + return true +} + +func emptyAction(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { +} + +var ( + adddelete = flag.Bool("adddelete", false, "add followed by delete mode") + addsdeletes = flag.Bool("addsdeletes", false, "adds followed by deletes mode") + ttl = flag.Bool("ttl", false, "add with ttl mode") +) + +func main() { + flag.Parse() + + go func() { + log.Println(http.ListenAndServe("localhost:6060", nil)) + }() + + if *adddelete { + rs, _ := createRuleSession() + defer rs.Unregister() + rule := ruleapi.NewRule("R2") + rule.AddCondition("R2_c1", []string{"t3.none"}, trueCondition, nil) + rule.SetAction(emptyAction) + rule.SetPriority(1) + rs.AddRule(rule) + log.Printf("Rule added: [%s]\n", rule.GetName()) + rs.Start(nil) + + i := 0 + for { + t1, _ := model.NewTupleWithKeyValues("t3", fmt.Sprintf("tuple%d", i)) + err := rs.Assert(context.TODO(), t1) + if err != nil { + log.Fatalf("err should be nil: %v", err) + } + time.Sleep(10 * time.Millisecond) + rs.Delete(context.TODO(), t1) + time.Sleep(10 * time.Millisecond) + i++ + } + } else if *addsdeletes { + rs, _ := createRuleSession() + defer rs.Unregister() + rule := ruleapi.NewRule("R2") + rule.AddCondition("R2_c1", []string{"t3.none"}, trueCondition, nil) + rule.SetAction(emptyAction) + rule.SetPriority(1) + rs.AddRule(rule) + log.Printf("Rule added: [%s]\n", rule.GetName()) + rs.Start(nil) + + i := 0 + for { + tuples := make([]model.MutableTuple, 0, 10) + for j := 0; j < 10; j++ { + t1, _ := model.NewTupleWithKeyValues("t3", fmt.Sprintf("tuple%d_%d", i, j)) + err := rs.Assert(context.TODO(), t1) + if err != nil { + log.Fatalf("err should be nil: %v", err) + } + tuples = append(tuples, t1) + time.Sleep(10 * time.Millisecond) + } + + for _, tuple := range tuples { + rs.Delete(context.TODO(), tuple) + time.Sleep(10 * time.Millisecond) + } + i++ + } + } else if *ttl { + rs, _ := createRuleSession() + defer rs.Unregister() + rule := ruleapi.NewRule("R2") + rule.AddCondition("R2_c1", []string{"t4.none"}, trueCondition, nil) + rule.SetAction(emptyAction) + rule.SetPriority(1) + rs.AddRule(rule) + log.Printf("Rule added: [%s]\n", rule.GetName()) + rs.Start(nil) + + i := 0 + for { + t1, _ := model.NewTupleWithKeyValues("t4", fmt.Sprintf("tuple%d", i)) + err := rs.Assert(context.TODO(), t1) + if err != nil { + log.Fatalf("err should be nil: %v", err) + } + time.Sleep(time.Second) + i++ + } + } +} From 8894b65149d013ba22bd735df140737ef1221914 Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Mon, 16 Sep 2019 15:46:29 -0600 Subject: [PATCH 072/125] Added same tuple memory leak mode --- ruleapi/tests/memory/memory.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ruleapi/tests/memory/memory.go b/ruleapi/tests/memory/memory.go index 6bfff3b..caf440e 100644 --- a/ruleapi/tests/memory/memory.go +++ b/ruleapi/tests/memory/memory.go @@ -42,6 +42,7 @@ var ( adddelete = flag.Bool("adddelete", false, "add followed by delete mode") addsdeletes = flag.Bool("addsdeletes", false, "adds followed by deletes mode") ttl = flag.Bool("ttl", false, "add with ttl mode") + same = flag.Bool("same", false, "add same tuple") ) func main() { @@ -125,5 +126,25 @@ func main() { time.Sleep(time.Second) i++ } + } else if *same { + rs, _ := createRuleSession() + defer rs.Unregister() + rule := ruleapi.NewRule("R2") + rule.AddCondition("R2_c1", []string{"t3.none"}, trueCondition, nil) + rule.SetAction(emptyAction) + rule.SetPriority(1) + rs.AddRule(rule) + log.Printf("Rule added: [%s]\n", rule.GetName()) + rs.Start(nil) + + t1, _ := model.NewTupleWithKeyValues("t3", "t3") + err := rs.Assert(context.TODO(), t1) + if err != nil { + log.Fatalf("err should be nil: %v", err) + } + for { + rs.Assert(context.TODO(), t1) + time.Sleep(10 * time.Millisecond) + } } } From 2f39c282f05fb66a6915768034e430f65c1e82eb Mon Sep 17 00:00:00 2001 From: nthota Date: Wed, 18 Sep 2019 11:51:10 +0530 Subject: [PATCH 073/125] improvised example for decision table --- activity/dtable/activity.go | 2 -- examples/flogo/dtable/README.md | 4 ++-- examples/flogo/dtable/flogo.json | 5 +++-- examples/flogo/dtable/imports.go | 1 + 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/activity/dtable/activity.go b/activity/dtable/activity.go index 47eaad5..2577940 100644 --- a/activity/dtable/activity.go +++ b/activity/dtable/activity.go @@ -28,8 +28,6 @@ func New(ctx activity.InitContext) (activity.Activity, error) { if err != nil { return nil, err } - fmt.Println("settings: ", settings) - fmt.Println("settings: ", settings.Make[0]) // Read setting from init context act := &Activity{ diff --git a/examples/flogo/dtable/README.md b/examples/flogo/dtable/README.md index 5a12de5..fef24c4 100644 --- a/examples/flogo/dtable/README.md +++ b/examples/flogo/dtable/README.md @@ -37,8 +37,8 @@ curl localhost:7777/test/studentanalysis?name=s2 ``` You should see following output: ``` -2019-09-05T18:35:12.142+0530 INFO [flogo.rules] - s1 -2019-09-05T18:35:12.142+0530 INFO [flogo.rules] - s2 +2019-09-05T18:35:12.142+0530 INFO [flogo.rules] - Student: s1 -- Comments: additional study hours required +2019-09-05T18:35:12.142+0530 INFO [flogo.rules] - Student: s2 -- Comments: little care can be taken to achieve grade-a ``` ### Writing Decision Table in JSON diff --git a/examples/flogo/dtable/flogo.json b/examples/flogo/dtable/flogo.json index a7aa0c4..49e140c 100644 --- a/examples/flogo/dtable/flogo.json +++ b/examples/flogo/dtable/flogo.json @@ -8,7 +8,8 @@ "github.com/project-flogo/contrib/trigger/rest", "github.com/project-flogo/contrib/activity/log", "github.com/project-flogo/rules/ruleaction", - "github.com/project-flogo/rules/activity/dtable" + "github.com/project-flogo/rules/activity/dtable", + "github.com/project-flogo/contrib/function/string" ], "triggers": [ { @@ -69,7 +70,7 @@ "actionService": { "service": "LogCareRequiredStudents", "input": { - "message": "=$.student.name" + "message": "=string.concat(\" Student: \",$.student.name, \" -- Comments: \",$.student.comments)" } }, "priority": 2 diff --git a/examples/flogo/dtable/imports.go b/examples/flogo/dtable/imports.go index dab9b05..f95bf3c 100644 --- a/examples/flogo/dtable/imports.go +++ b/examples/flogo/dtable/imports.go @@ -2,6 +2,7 @@ package main import ( _ "github.com/project-flogo/contrib/activity/log" + _ "github.com/project-flogo/contrib/function/string" _ "github.com/project-flogo/contrib/trigger/rest" _ "github.com/project-flogo/legacybridge" _ "github.com/project-flogo/rules/activity/dtable" From b6c80522560df6817d09c90d060354b21dbf9571 Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Wed, 18 Sep 2019 14:36:10 -0600 Subject: [PATCH 074/125] Fixed race condition in test --- examples/flogo/simple-kafka/kakfa_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/flogo/simple-kafka/kakfa_test.go b/examples/flogo/simple-kafka/kakfa_test.go index db90410..695c364 100644 --- a/examples/flogo/simple-kafka/kakfa_test.go +++ b/examples/flogo/simple-kafka/kakfa_test.go @@ -22,9 +22,12 @@ const ( topic = "orderinfo" ) -func initProducer() (sarama.SyncProducer, error) { - // setup sarama log to stdout +func TestMain(m *testing.M) { sarama.Logger = log.New(os.Stdout, "", log.Ltime) + os.Exit(m.Run()) +} + +func initProducer() (sarama.SyncProducer, error) { // producer config config := sarama.NewConfig() From 15da903b4ac16292998394ca0d881f2bc5d1324a Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Sun, 22 Sep 2019 22:31:50 -0600 Subject: [PATCH 075/125] Updated go.sum --- go.sum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.sum b/go.sum index 33958f5..6605823 100644 --- a/go.sum +++ b/go.sum @@ -47,7 +47,7 @@ github.com/project-flogo/contrib/activity/log v0.9.0 h1:Uptmxa+tAi79mtTn4DNwoWIi github.com/project-flogo/contrib/activity/log v0.9.0/go.mod h1:Eso9z1s2DyrGdHwLwni0qP1TPtY9L/NclLYIs17dq30= github.com/project-flogo/contrib/function/string v0.9.0 h1:9BOAScGsTdnP6Jor6lnTcUwDdYgVBcyAXFpg969qw6E= github.com/project-flogo/contrib/function/string v0.9.0/go.mod h1:ucysnPOToMKCKoSROm4cRXDcQFZGit8LUK5SfbCeDh8= -github.com/project-flogo/contrib/trigger/kafka v0.9.0 h1:EhDEpQOIsdz2Y/4pdGBoUEuAS8XIPrh60p5OfgAV5ts= +github.com/project-flogo/contrib/trigger/kafka v0.9.0 h1:xeGFM73L0jSYPOd0ihOIpkdF/8ko6/sOZircJYOT5Ew= github.com/project-flogo/contrib/trigger/kafka v0.9.0/go.mod h1:NqVLzyM1u4dMGp9q06YsI/JahK/oFNvpV+72tKIt8bk= github.com/project-flogo/contrib/trigger/rest v0.9.0 h1:3e9uLWLmg8VG8fcf8Fi4MrLptPc6WI1ZpaylC8eQ3Pk= github.com/project-flogo/contrib/trigger/rest v0.9.0/go.mod h1:vxFRTpssjn5eF1fXdCc+L6+PxZOKm5iqkTZPA4lUIsY= From 776aca77ec8a32eadad45c637e7aab1d286f555a Mon Sep 17 00:00:00 2001 From: nthota Date: Tue, 24 Sep 2019 14:37:27 +0530 Subject: [PATCH 076/125] credit card example with decision table is added --- activity/dtable/activity.go | 28 ++- examples/flogo/creditcard-dt/README.md | 115 +++++++++++ examples/flogo/creditcard-dt/flogo.json | 252 ++++++++++++++++++++++++ examples/flogo/creditcard-dt/imports.go | 10 + examples/flogo/creditcard-dt/main.go | 67 +++++++ 5 files changed, 467 insertions(+), 5 deletions(-) create mode 100644 examples/flogo/creditcard-dt/README.md create mode 100644 examples/flogo/creditcard-dt/flogo.json create mode 100644 examples/flogo/creditcard-dt/imports.go create mode 100644 examples/flogo/creditcard-dt/main.go diff --git a/activity/dtable/activity.go b/activity/dtable/activity.go index 2577940..22c4656 100644 --- a/activity/dtable/activity.go +++ b/activity/dtable/activity.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strconv" + "strings" "github.com/project-flogo/core/activity" "github.com/project-flogo/core/data" @@ -69,39 +70,58 @@ func (a *Activity) Eval(ctx activity.Context) (done bool, err error) { continue } mutableTuple := tuple.(model.MutableTuple) - tds := mutableTuple.GetTupleDescriptor() strVal := fmt.Sprintf("%v", act.Value) - switch tds.GetProperty(act.Field).PropType { case data.TypeString: + if strings.Compare(strVal, "") == 0 { + strVal = "" + } mutableTuple.SetString(context, act.Field, strVal) case data.TypeBool: + if strings.Compare(strVal, "") == 0 { + strVal = "false" + } b, err := strconv.ParseBool(strVal) if err == nil { mutableTuple.SetBool(context, act.Field, b) } case data.TypeInt: + if strings.Compare(strVal, "") == 0 { + strVal = "0" + } i, err := strconv.ParseInt(strVal, 10, 64) if err == nil { mutableTuple.SetInt(context, act.Field, int(i)) } case data.TypeInt32: + if strings.Compare(strVal, "") == 0 { + strVal = "0" + } i, err := strconv.ParseInt(strVal, 10, 64) if err == nil { mutableTuple.SetInt(context, act.Field, int(i)) } case data.TypeInt64: + if strings.Compare(strVal, "") == 0 { + strVal = "0" + } i, err := strconv.ParseInt(strVal, 10, 64) if err == nil { mutableTuple.SetLong(context, act.Field, i) } case data.TypeFloat32: + if strings.Compare(strVal, "") == 0 { + strVal = "0.0" + } f, err := strconv.ParseFloat(strVal, 32) if err == nil { mutableTuple.SetDouble(context, act.Field, f) } case data.TypeFloat64: + if strings.Compare(strVal, "") == 0 { + strVal = "0.0" + } f, err := strconv.ParseFloat(strVal, 64) if err == nil { mutableTuple.SetDouble(context, act.Field, f) @@ -110,17 +130,15 @@ func (a *Activity) Eval(ctx activity.Context) (done bool, err error) { mutableTuple.SetValue(context, act.Field, act.Value) } - } } } - return false, nil + return true, nil } func evaluateCondition(cond *DtCondition, tuples map[model.TupleType]model.Tuple) bool { condExprsn := "$." + cond.Tuple + "." + cond.Field + " " + cond.Expr - condExprs := ruleapi.NewExprCondition(condExprsn) res, err := condExprs.Evaluate("", "", tuples, "") if err != nil { diff --git a/examples/flogo/creditcard-dt/README.md b/examples/flogo/creditcard-dt/README.md new file mode 100644 index 0000000..5a7c901 --- /dev/null +++ b/examples/flogo/creditcard-dt/README.md @@ -0,0 +1,115 @@ +# Decision Table Usage + +This example demonstrates how to use decision table activity with credit card application example. + +## Setup and build +Once you have the `flogo.json` file, you are ready to build your Flogo App + +### Pre-requisites +* Go 1.11 +* Download and build the Flogo CLI 'flogo' and add it to your system PATH + +### Steps + +```sh +cd $GOPATH/src/github.com/project-flogo/rules/examples/flogo/creditcard-dt +flogo create -f flogo.json +cd creditcard-dt +flogo build +cd bin +./creditcard-dt +``` +### Testing + +#### #1 Invoke applicant decision table + +Store aplicants information. +```sh +curl localhost:7777/test/applicant?name=JohnDoe\&gender=Male\&age=20\&address=BoltonUK\&hasDL=false\&ssn=1231231234\&income=45000\&maritalStatus=single\&creditScore=500 +curl localhost:7777/test/applicant?name=JaneDoe\&gender=Female\&age=38\&address=BoltonUK\&hasDL=false\&ssn=2424354532\&income=32000\&maritalStatus=single\&creditScore=650 +curl localhost:7777/test/applicant?name=PrakashY\&gender=Male\&age=30\&address=RedwoodShore\&hasDL=true\&ssn=2345342132\&income=150000\&maritalStatus=married\&creditScore=750 +curl localhost:7777/test/applicant?name=SandraW\&gender=Female\&age=26\&address=RedwoodShore\&hasDL=true\&ssn=3213214321\&income=50000\&maritalStatus=single\&creditScore=625 +``` + +Send a process application event. +```sh +curl localhost:7777/test/processapplication?start=true\&ssn=1231231234 +curl localhost:7777/test/processapplication?start=true\&ssn=2345342132 +curl localhost:7777/test/processapplication?start=true\&ssn=3213214321 +curl localhost:7777/test/processapplication?start=true\&ssn=2424354532 +``` +You should see following output: +``` +2019-09-24T12:54:08.674+0530 INFO [flogo.rules] - Applicant: JohnDoe -- CreditLimit: 2500 -- status: VISA-Granted +2019-09-24T12:54:08.683+0530 INFO [flogo.rules] - Applicant: PrakashY -- CreditLimit: 7500 -- status: Pending +2019-09-24T12:54:08.696+0530 INFO [flogo.rules] - Applicant: SandraW -- CreditLimit: 0 -- status: Loan-Rejected +2019-09-24T12:54:09.884+0530 INFO [flogo.rules] - Applicant: JaneDoe -- CreditLimit: 25000 -- status: Platinum-Status +``` + +### Writing Decision Table in JSON + +Sample usage can be as below. +```json + { + "name": "ApplicantSimple", + "description": "Simple Applicants approval dt", + "type": "activity", + "ref": "github.com/project-flogo/rules/activity/dtable", + "settings": { + "make": [ + { + "condition": [ + {"tuple": "applicant","field": "name","expr": "== 'JohnDoe'"}, + {"tuple": "applicant","field": "age","expr": ">= 20"}, + {"tuple": "applicant","field": "age","expr": "<= 30"} + ], + "action": [ + + { "tuple": "applicant","field": "creditLimit","value": 2500.0}, + { "tuple": "applicant","field": "eligible","value": true}, + { "tuple": "applicant","field": "status","value": "VISA-Granted"} + ] + }, + { + "condition": [ + {"tuple": "applicant","field": "name","expr": "== 'SandraW'"}, + {"tuple": "applicant","field": "age","expr": ">= 20"}, + {"tuple": "applicant","field": "age","expr": "<= 30"} + ], + "action": [ + + { "tuple": "applicant","field": "creditLimit","value": 0.0}, + { "tuple": "applicant","field": "eligible","value": false}, + { "tuple": "applicant","field": "status","value": "Loan-Rejected"} + ] + }, + { + "condition": [ + {"tuple": "applicant","field": "name","expr": "== 'PrakashY'"}, + {"tuple": "applicant","field": "age","expr": ">= 20"}, + {"tuple": "applicant","field": "age","expr": "<= 30"} + ], + "action": [ + + { "tuple": "applicant","field": "creditLimit","value": 7500.0}, + { "tuple": "applicant","field": "eligible","value": true}, + { "tuple": "applicant","field": "status","value": "Pending"} + ] + }, + { + "condition": [ + {"tuple": "applicant","field": "name","expr": "== 'JaneDoe'"}, + {"tuple": "applicant","field": "age","expr": ">30"} + ], + "action": [ + + { "tuple": "applicant","field": "creditLimit","value": 25000.0}, + { "tuple": "applicant","field": "eligible","value": false}, + { "tuple": "applicant","field": "status","value": "Platinum-Status"} + ] + } + ] + } + } +``` +Decision table will have condition and action included into decition table activity. \ No newline at end of file diff --git a/examples/flogo/creditcard-dt/flogo.json b/examples/flogo/creditcard-dt/flogo.json new file mode 100644 index 0000000..ea7b91c --- /dev/null +++ b/examples/flogo/creditcard-dt/flogo.json @@ -0,0 +1,252 @@ +{ + "name": "decisiontable", + "type": "flogo:app", + "version": "0.0.1", + "description": "Sample Rules App", + "appModel": "1.0.0", + "imports": [ + "github.com/project-flogo/contrib/trigger/rest", + "github.com/project-flogo/contrib/activity/log", + "github.com/project-flogo/rules/ruleaction", + "github.com/project-flogo/rules/activity/dtable", + "github.com/project-flogo/contrib/function/string" + ], + "triggers": [ + { + "id": "receive_http_message", + "ref": "github.com/project-flogo/contrib/trigger/rest", + "settings": { + "port": "7777" + }, + "handlers": [ + { + "settings": { + "method": "GET", + "path": "/test/:tupleType" + }, + "actions": [ + { + "id": "simple_rule", + "input": { + "tupletype": "=$.pathParams.tupleType", + "values": "=$.queryParams" + } + } + ] + } + ] + } + ], + "resources": [ + { + "id": "rulesession:simple", + "data": { + "metadata": { + "input": [ + { + "name": "values", + "type": "string" + }, + { + "name": "tupletype", + "type": "string" + } + ], + "output": [ + { + "name": "outputData", + "type": "any" + } + ] + }, + "rules": [ + { + "name": "Process Application with applicant simple dt", + "conditions": [ + { + "expression": "$.processapplication.start == true " + }, + { + "expression": "$.processapplication.ssn == $.applicant.ssn" + } + ], + "actionService": { + "service": "ApplicantSimple", + "input": { + "message": "test ApplicantSimple" + } + }, + "priority": 1 + }, + { + "name": "Display Information", + "conditions": [ + { + "expression": "$.applicant.creditLimit >= 0 " + }, + { + "expression": "$.applicant.status != nil " + } + ], + "actionService": { + "service": "LogInformation", + "input": { + "message": "=string.concat(\" Applicant: \",$.applicant.name, \" -- CreditLimit: \",$.applicant.creditLimit, \" -- status: \",$.applicant.status)" + } + }, + "priority": 3 + } + ], + "services": [ + { + "name": "ApplicantSimple", + "description": "Simple Applicants approval dt", + "type": "activity", + "ref": "github.com/project-flogo/rules/activity/dtable", + "settings": { + "make": [ + { + "condition": [ + {"tuple": "applicant","field": "name","expr": "== 'JohnDoe'"}, + {"tuple": "applicant","field": "age","expr": ">= 20"}, + {"tuple": "applicant","field": "age","expr": "<= 30"} + ], + "action": [ + + { "tuple": "applicant","field": "creditLimit","value": 2500.0}, + { "tuple": "applicant","field": "eligible","value": true}, + { "tuple": "applicant","field": "status","value": "VISA-Granted"} + ] + }, + { + "condition": [ + {"tuple": "applicant","field": "name","expr": "== 'SandraW'"}, + {"tuple": "applicant","field": "age","expr": ">= 20"}, + {"tuple": "applicant","field": "age","expr": "<= 30"} + ], + "action": [ + + { "tuple": "applicant","field": "creditLimit","value": 0.0}, + { "tuple": "applicant","field": "eligible","value": false}, + { "tuple": "applicant","field": "status","value": "Loan-Rejected"} + ] + }, + { + "condition": [ + {"tuple": "applicant","field": "name","expr": "== 'PrakashY'"}, + {"tuple": "applicant","field": "age","expr": ">= 20"}, + {"tuple": "applicant","field": "age","expr": "<= 30"} + ], + "action": [ + + { "tuple": "applicant","field": "creditLimit","value": 7500.0}, + { "tuple": "applicant","field": "eligible","value": true}, + { "tuple": "applicant","field": "status","value": "Pending"} + ] + }, + { + "condition": [ + {"tuple": "applicant","field": "name","expr": "== 'JaneDoe'"}, + {"tuple": "applicant","field": "age","expr": ">30"} + ], + "action": [ + + { "tuple": "applicant","field": "creditLimit","value": 25000.0}, + { "tuple": "applicant","field": "eligible","value": false}, + { "tuple": "applicant","field": "status","value": "Platinum-Status"} + ] + } + ] + } + }, + { + "name": "LogInformation", + "description": "Logs Applicant Information", + "type": "activity", + "ref": "github.com/project-flogo/contrib/activity/log" + } + ] + } + } + ], + "actions": [ + { + "ref": "github.com/project-flogo/rules/ruleaction", + "settings": { + "ruleSessionURI": "res://rulesession:simple", + "tds": [ + { + "name": "applicant", + "properties": [ + { + "name": "name", + "pk-index": 0, + "type": "string" + }, + { + "name": "gender", + "type": "string" + }, + { + "name": "age", + "type": "int" + }, + { + "name": "address", + "type": "string" + }, + { + "name": "hasDL", + "type": "bool" + }, + { + "name": "ssn", + "type": "long" + }, + { + "name": "income", + "type": "double" + }, + { + "name": "maritalStatus", + "type": "string" + }, + { + "name": "creditScore", + "type": "int" + }, + { + "name": "status", + "type": "string" + }, + { + "name": "eligible", + "type": "bool" + }, + { + "name": "creditLimit", + "type": "double" + } + ] + }, + { + "name": "processapplication", + "ttl":0, + "properties": [ + { + "name": "ssn", + "pk-index": 0, + "type": "long" + }, + { + "name": "start", + "type": "bool" + } + ] + } + ] + }, + "id": "simple_rule" + } + ] +} \ No newline at end of file diff --git a/examples/flogo/creditcard-dt/imports.go b/examples/flogo/creditcard-dt/imports.go new file mode 100644 index 0000000..f95bf3c --- /dev/null +++ b/examples/flogo/creditcard-dt/imports.go @@ -0,0 +1,10 @@ +package main + +import ( + _ "github.com/project-flogo/contrib/activity/log" + _ "github.com/project-flogo/contrib/function/string" + _ "github.com/project-flogo/contrib/trigger/rest" + _ "github.com/project-flogo/legacybridge" + _ "github.com/project-flogo/rules/activity/dtable" + _ "github.com/project-flogo/rules/ruleaction" +) diff --git a/examples/flogo/creditcard-dt/main.go b/examples/flogo/creditcard-dt/main.go new file mode 100644 index 0000000..234f2eb --- /dev/null +++ b/examples/flogo/creditcard-dt/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "flag" + "fmt" + "os" + "runtime" + "runtime/pprof" + + _ "github.com/project-flogo/core/data/expression/script" + "github.com/project-flogo/core/engine" +) + +var ( + cpuProfile = flag.String("cpuprofile", "", "Writes CPU profile to the specified file") + memProfile = flag.String("memprofile", "", "Writes memory profile to the specified file") + cfgJson string + cfgCompressed bool +) + +func main() { + + flag.Parse() + if *cpuProfile != "" { + f, err := os.Create(*cpuProfile) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to create CPU profiling file: %v\n", err) + os.Exit(1) + } + if err = pprof.StartCPUProfile(f); err != nil { + fmt.Fprintf(os.Stderr, "Failed to start CPU profiling: %v\n", err) + os.Exit(1) + } + defer pprof.StopCPUProfile() + } + + cfg, err := engine.LoadAppConfig(cfgJson, cfgCompressed) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to create engine: %v\n", err) + os.Exit(1) + } + + e, err := engine.New(cfg) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to create engine: %v\n", err) + os.Exit(1) + } + + code := engine.RunEngine(e) + + if *memProfile != "" { + f, err := os.Create(*memProfile) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to create memory profiling file: %v\n", err) + os.Exit(1) + } + + runtime.GC() // get up-to-date statistics + if err := pprof.WriteHeapProfile(f); err != nil { + fmt.Fprintf(os.Stderr, "Failed to write memory profiling data: %v", err) + os.Exit(1) + } + _ = f.Close() + } + + os.Exit(code) +} From 3f94ff855da4dca983a7c199ace986ad997f0d97 Mon Sep 17 00:00:00 2001 From: nthota Date: Tue, 24 Sep 2019 15:35:07 +0530 Subject: [PATCH 077/125] updated readme --- examples/flogo/creditcard-dt/README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/examples/flogo/creditcard-dt/README.md b/examples/flogo/creditcard-dt/README.md index 5a7c901..ea50ce8 100644 --- a/examples/flogo/creditcard-dt/README.md +++ b/examples/flogo/creditcard-dt/README.md @@ -112,4 +112,14 @@ Sample usage can be as below. } } ``` -Decision table will have condition and action included into decition table activity. \ No newline at end of file +Same above json can be veiwed as decition table notation in BE as below. + +|S.No | Conditions ||Actions ||| +| :--- | :--- | :--- | :--- | :--- | :--- | +| | applicant.name | applicant.age | applicant.creditLimit | applicant.eligible | applicant.status| +| 1 | JohnDoe | >=20 && <= 30 | 2500.0 | true | VISA-Granted | +| 2 | SandraW | >=20 && <= 30 | 0.0 | false | Loan-Rejected | +| 3 | PrakashY | >=20 && <= 30 | 7500.0 | true | Pending | +| 4 | JaneDoe | >30 | 25000.0 | false | Platinum-Status | + + From dadd27b6dc58934cad6a6746ac103da560863d2c Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Mon, 30 Sep 2019 17:57:10 -0600 Subject: [PATCH 078/125] Added status to handle --- common/model/types.go | 4 +- redisutils/redisutils.go | 14 +++++ rete/classnode.go | 2 +- rete/common/types.go | 2 +- rete/conflict.go | 4 +- rete/internal/mem/mhandleservice.go | 6 +- rete/internal/mem/mretehandle.go | 16 +++++- rete/internal/redis/rhandleservice.go | 47 +++++++++------- rete/internal/redis/rjointablerowimpl.go | 2 +- rete/internal/redis/rretehandle.go | 22 +++++++- rete/internal/types/types.go | 4 +- rete/network.go | 72 +++++++++++++----------- ruleapi/rulesession.go | 15 ++--- ruleapi/tests/retract_1_test.go | 6 +- 14 files changed, 135 insertions(+), 81 deletions(-) diff --git a/common/model/types.go b/common/model/types.go index 60bc361..8f82f5b 100644 --- a/common/model/types.go +++ b/common/model/types.go @@ -49,7 +49,7 @@ type RuleSession interface { GetRules() []Rule Assert(ctx context.Context, tuple Tuple) (err error) - Retract(ctx context.Context, tuple Tuple) + Retract(ctx context.Context, tuple Tuple) error ScheduleAssert(ctx context.Context, delayInMillis uint64, key interface{}, tuple Tuple) CancelScheduledAssert(ctx context.Context, key interface{}) @@ -69,7 +69,7 @@ type RuleSession interface { GetAssertedTuple(key TupleKey) Tuple //Retract, and remove - Delete(ctx context.Context, tuple Tuple) + Delete(ctx context.Context, tuple Tuple) error //RtcTransactionHandler RegisterRtcTransactionHandler(txnHandler RtcTransactionHandler, handlerCtx interface{}) diff --git a/redisutils/redisutils.go b/redisutils/redisutils.go index d63efc1..d41448d 100644 --- a/redisutils/redisutils.go +++ b/redisutils/redisutils.go @@ -57,6 +57,20 @@ func (rh *RedisHandle) getPool() *redis.Pool { return rh.pool } +func (rh *RedisHandle) HSet(key string, field string, value interface{}) (bool, error) { + c := rh.getPool().Get() + defer c.Close() + exists, err := redis.Int(c.Do("HSET", key, field, value)) + return exists == 0, err +} + +func (rh *RedisHandle) HSetNX(key string, field string, value interface{}) (bool, error) { + c := rh.getPool().Get() + defer c.Close() + exists, err := redis.Int(c.Do("HSETNX", key, field, value)) + return exists == 0, err +} + func (rh *RedisHandle) HSetAll(key string, kvs map[string]interface{}) error { var args = []interface{}{key} diff --git a/rete/classnode.go b/rete/classnode.go index 60c97e2..4536bc0 100644 --- a/rete/classnode.go +++ b/rete/classnode.go @@ -76,7 +76,7 @@ func (cn *classNodeImpl) String() string { } func (cn *classNodeImpl) assert(ctx context.Context, tuple model.Tuple, changedProps map[string]bool) { - handle := getOrCreateHandle(ctx, tuple) + handle, _ := getOrCreateHandle(ctx, tuple) handles := make([]types.ReteHandle, 1) handles[0] = handle propagate := false diff --git a/rete/common/types.go b/rete/common/types.go index 1b2cba1..f2147c5 100644 --- a/rete/common/types.go +++ b/rete/common/types.go @@ -23,7 +23,7 @@ type Network interface { //changedProps are the properties that changed in a previous action Assert(ctx context.Context, rs model.RuleSession, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) error //mode can be one of retract, modify, delete - Retract(ctx context.Context, rs model.RuleSession, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) + Retract(ctx context.Context, rs model.RuleSession, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) error GetAssertedTuple(key model.TupleKey) model.Tuple RegisterRtcTransactionHandler(txnHandler model.RtcTransactionHandler, txnContext interface{}) diff --git a/rete/conflict.go b/rete/conflict.go index 4db539c..353b10d 100644 --- a/rete/conflict.go +++ b/rete/conflict.go @@ -90,13 +90,13 @@ func (cr *conflictResImpl) resolveConflict(ctx context.Context) { func (cr *conflictResImpl) deleteAgendaFor(ctx context.Context, modifiedTuple model.Tuple, changeProps map[string]bool) { - hdlModified := getOrCreateHandle(ctx, modifiedTuple) + hdlModified, _ := getOrCreateHandle(ctx, modifiedTuple) for e := cr.agendaList.Front(); e != nil; { item := e.Value.(agendaItem) next := e.Next() for _, tuple := range item.getTuples() { - hdl := getOrCreateHandle(ctx, tuple) + hdl, _ := getOrCreateHandle(ctx, tuple) if hdl == hdlModified { //this agendaitem has the modified tuple, remove the agenda item! toRemove := true //check if the rule depends on this change prop diff --git a/rete/internal/mem/mhandleservice.go b/rete/internal/mem/mhandleservice.go index ff75835..edbb136 100644 --- a/rete/internal/mem/mhandleservice.go +++ b/rete/internal/mem/mhandleservice.go @@ -46,14 +46,14 @@ func (hc *handleServiceImpl) GetHandleByKey(key model.TupleKey) types.ReteHandle return hc.allHandles[key.String()] } -func (hc *handleServiceImpl) GetOrCreateHandle(nw types.Network, tuple model.Tuple) types.ReteHandle { +func (hc *handleServiceImpl) GetOrCreateHandle(nw types.Network, tuple model.Tuple) (types.ReteHandle, bool) { hc.Lock() defer hc.Unlock() h, found := hc.allHandles[tuple.GetKey().String()] if !found { - h = newReteHandleImpl(nw, tuple) + h = newReteHandleImpl(nw, tuple, "creating") hc.allHandles[tuple.GetKey().String()] = h //[tuple.GetKey().String()] = h } - return h + return h, found } diff --git a/rete/internal/mem/mretehandle.go b/rete/internal/mem/mretehandle.go index ab4c79e..5131332 100644 --- a/rete/internal/mem/mretehandle.go +++ b/rete/internal/mem/mretehandle.go @@ -11,11 +11,12 @@ type reteHandleImpl struct { types.NwElemIdImpl tuple model.Tuple tupleKey model.TupleKey + status string } -func newReteHandleImpl(nw types.Network, tuple model.Tuple) types.ReteHandle { +func newReteHandleImpl(nw types.Network, tuple model.Tuple, status string) types.ReteHandle { h1 := reteHandleImpl{} - h1.initHandleImpl(nw, tuple) + h1.initHandleImpl(nw, tuple, status) return &h1 } @@ -24,10 +25,11 @@ func (hdl *reteHandleImpl) SetTuple(tuple model.Tuple) { hdl.tupleKey = tuple.GetKey() } -func (hdl *reteHandleImpl) initHandleImpl(nw types.Network, tuple model.Tuple) { +func (hdl *reteHandleImpl) initHandleImpl(nw types.Network, tuple model.Tuple, status string) { hdl.SetID(nw) hdl.SetTuple(tuple) hdl.tupleKey = tuple.GetKey() + hdl.status = status } func (hdl *reteHandleImpl) GetTuple() model.Tuple { @@ -38,6 +40,14 @@ func (hdl *reteHandleImpl) GetTupleKey() model.TupleKey { return hdl.tupleKey } +func (hdl *reteHandleImpl) SetStatus(status string) { + hdl.status = status +} + +func (hdl *reteHandleImpl) GetStatus() string { + return hdl.status +} + func (hdl *reteHandleImpl) AddJoinTableRowRef(joinTableRowVar types.JoinTableRow, joinTableVar types.JoinTable) { hdl.Nw.GetJtRefService().AddEntry(hdl, joinTableVar.GetName(), joinTableRowVar.GetID()) } diff --git a/rete/internal/redis/rhandleservice.go b/rete/internal/redis/rhandleservice.go index 6c3c853..369a565 100644 --- a/rete/internal/redis/rhandleservice.go +++ b/rete/internal/redis/rhandleservice.go @@ -34,7 +34,7 @@ func (hc *handleServiceImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle { rkey := hc.prefix + tuple.GetKey().String() redisutils.GetRedisHdl().Del(rkey) //TODO: Dummy handle - h := newReteHandleImpl(hc.GetNw(), tuple) + h := newReteHandleImpl(hc.GetNw(), tuple, rkey, "dummy") return h } @@ -49,28 +49,37 @@ func (hc *handleServiceImpl) GetHandleByKey(key model.TupleKey) types.ReteHandle m := redisutils.GetRedisHdl().HGetAll(rkey) if len(m) == 0 { return nil - } else { - tuple := hc.Nw.GetTupleStore().GetTupleByKey(key) - if tuple == nil { - //TODO: error handling - return nil + } + + tuple := hc.Nw.GetTupleStore().GetTupleByKey(key) + if tuple == nil { + //TODO: error handling + return nil + } + status := "" + if value, ok := m["status"]; ok { + if value, ok := value.(string); ok { + status = value } - h := newReteHandleImpl(hc.GetNw(), tuple) - return h } + h := newReteHandleImpl(hc.GetNw(), tuple, rkey, status) + return h } -func (hc *handleServiceImpl) GetOrCreateHandle(nw types.Network, tuple model.Tuple) types.ReteHandle { - - key := hc.prefix + tuple.GetKey().String() - - m := redisutils.GetRedisHdl().HGetAll(key) - if len(m) == 0 { - m := make(map[string]interface{}) - m["k"] = "v" - redisutils.GetRedisHdl().HSetAll(key, m) +func (hc *handleServiceImpl) GetOrCreateHandle(nw types.Network, tuple model.Tuple) (types.ReteHandle, bool) { + key, status := hc.prefix+tuple.GetKey().String(), "creating" + exists, _ := redisutils.GetRedisHdl().HSetNX(key, "status", status) + if exists { + m := redisutils.GetRedisHdl().HGetAll(key) + if len(m) > 0 { + if value, ok := m["status"]; ok { + if value, ok := value.(string); ok { + status = value + } + } + } } - h := newReteHandleImpl(nw, tuple) - return h + h := newReteHandleImpl(nw, tuple, key, status) + return h, exists } diff --git a/rete/internal/redis/rjointablerowimpl.go b/rete/internal/redis/rjointablerowimpl.go index af07241..019d03b 100644 --- a/rete/internal/redis/rjointablerowimpl.go +++ b/rete/internal/redis/rjointablerowimpl.go @@ -66,7 +66,7 @@ func createRow(jtKey string, rowID string, key string, nw types.Network) types.J for _, key := range values { tupleKey := model.FromStringKey(key) tuple := nw.GetTupleStore().GetTupleByKey(tupleKey) - handle := newReteHandleImpl(nw, tuple) + handle := newReteHandleImpl(nw, tuple, "", "unknown") handles = append(handles, handle) } diff --git a/rete/internal/redis/rretehandle.go b/rete/internal/redis/rretehandle.go index bcb770e..46db7ad 100644 --- a/rete/internal/redis/rretehandle.go +++ b/rete/internal/redis/rretehandle.go @@ -2,6 +2,7 @@ package redis import ( "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/redisutils" "github.com/project-flogo/rules/rete/internal/types" ) @@ -11,12 +12,14 @@ type reteHandleImpl struct { types.NwElemIdImpl tuple model.Tuple tupleKey model.TupleKey + key string + status string //jtRefs types.JtRefsService } -func newReteHandleImpl(nw types.Network, tuple model.Tuple) types.ReteHandle { +func newReteHandleImpl(nw types.Network, tuple model.Tuple, key, status string) types.ReteHandle { h1 := reteHandleImpl{} - h1.initHandleImpl(nw, tuple) + h1.initHandleImpl(nw, tuple, key, status) return &h1 } @@ -27,10 +30,12 @@ func (hdl *reteHandleImpl) SetTuple(tuple model.Tuple) { } } -func (hdl *reteHandleImpl) initHandleImpl(nw types.Network, tuple model.Tuple) { +func (hdl *reteHandleImpl) initHandleImpl(nw types.Network, tuple model.Tuple, key, status string) { hdl.SetID(nw) hdl.SetTuple(tuple) hdl.tupleKey = tuple.GetKey() + hdl.key = key + hdl.status = status } func (hdl *reteHandleImpl) GetTuple() model.Tuple { @@ -41,6 +46,17 @@ func (hdl *reteHandleImpl) GetTupleKey() model.TupleKey { return hdl.tupleKey } +func (hdl *reteHandleImpl) SetStatus(status string) { + if hdl.key == "" { + return + } + redisutils.GetRedisHdl().HSetNX(hdl.key, "status", status) +} + +func (hdl *reteHandleImpl) GetStatus() string { + return hdl.status +} + func (hdl *reteHandleImpl) AddJoinTableRowRef(joinTableRowVar types.JoinTableRow, joinTableVar types.JoinTable) { hdl.Nw.GetJtRefService().AddEntry(hdl, joinTableVar.GetName(), joinTableRowVar.GetID()) } diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index 57dba46..6ff47bd 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -39,6 +39,8 @@ type ReteHandle interface { SetTuple(tuple model.Tuple) GetTuple() model.Tuple GetTupleKey() model.TupleKey + SetStatus(status string) + GetStatus() string } type JtRefsService interface { @@ -62,7 +64,7 @@ type HandleService interface { RemoveHandle(tuple model.Tuple) ReteHandle GetHandle(tuple model.Tuple) ReteHandle GetHandleByKey(key model.TupleKey) ReteHandle - GetOrCreateHandle(nw Network, tuple model.Tuple) ReteHandle + GetOrCreateHandle(nw Network, tuple model.Tuple) (ReteHandle, bool) } type IdGen interface { diff --git a/rete/network.go b/rete/network.go index e8660e7..532d9ea 100644 --- a/rete/network.go +++ b/rete/network.go @@ -576,16 +576,11 @@ func (nw *reteNetworkImpl) Assert(ctx context.Context, rs model.RuleSession, tup nw.crudLock.Lock() defer nw.crudLock.Unlock() - if mode == common.ADD { - assertedTuple := nw.GetAssertedTuple(tuple.GetKey()) - if assertedTuple == tuple { - return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) - } else if assertedTuple != nil { - return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) - } + err := nw.assertInternal(newCtx, tuple, changedProps, mode) + if err != nil { + return err } - nw.assertInternal(newCtx, tuple, changedProps, mode) reteCtxVar.getConflictResolver().resolveConflict(newCtx) //if Timeout is 0, remove it from rete td := model.GetTupleDescriptor(tuple.GetTupleType()) @@ -609,15 +604,6 @@ func (nw *reteNetworkImpl) Assert(ctx context.Context, rs model.RuleSession, tup return nil } - if mode == common.ADD { - assertedTuple := nw.GetAssertedTuple(tuple.GetKey()) - if assertedTuple == tuple { - return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) - } else if assertedTuple != nil { - return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) - } - } - reteCtxVar.getOpsList().PushBack(newAssertEntry(tuple, changedProps, mode)) return nil } @@ -629,7 +615,7 @@ func (nw *reteNetworkImpl) removeTupleFromRete(tuple model.Tuple) { } } -func (nw *reteNetworkImpl) Retract(ctx context.Context, rs model.RuleSession, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) { +func (nw *reteNetworkImpl) Retract(ctx context.Context, rs model.RuleSession, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) error { if ctx == nil { ctx = context.Background() @@ -638,7 +624,10 @@ func (nw *reteNetworkImpl) Retract(ctx context.Context, rs model.RuleSession, tu if !isRecursive { nw.crudLock.Lock() defer nw.crudLock.Unlock() - nw.retractInternal(ctx, tuple, changedProps, mode) + err := nw.retractInternal(ctx, tuple, changedProps, mode) + if err != nil { + return err + } if nw.txnHandler != nil && mode == common.DELETE { rtcTxn := newRtcTxn(reteCtxVar.getRtcAdded(), reteCtxVar.getRtcModified(), reteCtxVar.getRtcDeleted()) for i, txnHandler := range nw.txnHandler { @@ -648,23 +637,31 @@ func (nw *reteNetworkImpl) Retract(ctx context.Context, rs model.RuleSession, tu } else { reteCtxVar.getOpsList().PushBack(newDeleteEntry(tuple, mode, changedProps)) } + + return nil } -func (nw *reteNetworkImpl) retractInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) { +func (nw *reteNetworkImpl) retractInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) error { + handle := nw.handleService.GetHandle(tuple) + if handle == nil { + return fmt.Errorf("Tuple with key [%s] doesn't exist", tuple.GetKey().String()) + } else if handle.GetStatus() != "created" { + return fmt.Errorf("Tuple with key [%s] is being asserted", tuple.GetKey().String()) + } + if ctx == nil { ctx = context.Background() } rCtx, _, _ := getOrSetReteCtx(ctx, nw, nil) - reteHandle := nw.handleService.RemoveHandle(tuple) - if reteHandle != nil { - nw.removeJoinTableRowRefs(reteHandle, changedProps) - - //add it to the delete list - if mode == common.DELETE { - rCtx.addToRtcDeleted(tuple) - } + nw.removeJoinTableRowRefs(handle, changedProps) + // add it to the delete list + if mode == common.DELETE { + rCtx.addToRtcDeleted(tuple) } + nw.handleService.RemoveHandle(tuple) + + return nil } func (nw *reteNetworkImpl) GetAssertedTuple(key model.TupleKey) model.Tuple { @@ -675,7 +672,15 @@ func (nw *reteNetworkImpl) GetAssertedTuple(key model.TupleKey) model.Tuple { return nil } -func (nw *reteNetworkImpl) assertInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) { +func (nw *reteNetworkImpl) assertInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) error { + if mode == common.ADD { + handle, exists := nw.getOrCreateHandle(ctx, tuple) + if exists { + return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) + } + defer handle.SetStatus("created") + } + tupleType := tuple.GetTupleType() listItem := nw.allClassNodes[string(tupleType)] if listItem != nil { @@ -691,11 +696,12 @@ func (nw *reteNetworkImpl) assertInternal(ctx context.Context, tuple model.Tuple } } } + + return nil } -func (nw *reteNetworkImpl) getOrCreateHandle(ctx context.Context, tuple model.Tuple) types.ReteHandle { - h := nw.handleService.GetOrCreateHandle(nw, tuple) - return h +func (nw *reteNetworkImpl) getOrCreateHandle(ctx context.Context, tuple model.Tuple) (types.ReteHandle, bool) { + return nw.handleService.GetOrCreateHandle(nw, tuple) } func (nw *reteNetworkImpl) getHandle(tuple model.Tuple) types.ReteHandle { @@ -728,7 +734,7 @@ func (nw *reteNetworkImpl) GetTupleStore() model.TupleStore { return nw.tupleStore } -func getOrCreateHandle(ctx context.Context, tuple model.Tuple) types.ReteHandle { +func getOrCreateHandle(ctx context.Context, tuple model.Tuple) (types.ReteHandle, bool) { reteCtxVar := getReteCtx(ctx) return reteCtxVar.getNetwork().getOrCreateHandle(ctx, tuple) } diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index affdc50..128b231 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -179,13 +179,6 @@ func (rs *rulesessionImpl) Assert(ctx context.Context, tuple model.Tuple) (err e if !rs.started { return fmt.Errorf("Cannot assert tuple. Rulesession [%s] not started", rs.name) } - assertedTuple := rs.GetAssertedTuple(tuple.GetKey()) - //if assertedTuple == tuple { - // return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) - //} else - if assertedTuple != nil { - return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) - } if ctx == nil { ctx = context.Context(context.Background()) } @@ -193,12 +186,12 @@ func (rs *rulesessionImpl) Assert(ctx context.Context, tuple model.Tuple) (err e return rs.reteNetwork.Assert(ctx, rs, tuple, nil, common.ADD) } -func (rs *rulesessionImpl) Retract(ctx context.Context, tuple model.Tuple) { - rs.reteNetwork.Retract(ctx, rs, tuple, nil, common.RETRACT) +func (rs *rulesessionImpl) Retract(ctx context.Context, tuple model.Tuple) error { + return rs.reteNetwork.Retract(ctx, rs, tuple, nil, common.RETRACT) } -func (rs *rulesessionImpl) Delete(ctx context.Context, tuple model.Tuple) { - rs.reteNetwork.Retract(ctx, rs, tuple, nil, common.DELETE) +func (rs *rulesessionImpl) Delete(ctx context.Context, tuple model.Tuple) error { + return rs.reteNetwork.Retract(ctx, rs, tuple, nil, common.DELETE) } func (rs *rulesessionImpl) printNetwork() { diff --git a/ruleapi/tests/retract_1_test.go b/ruleapi/tests/retract_1_test.go index f83c762..41f9ccd 100644 --- a/ruleapi/tests/retract_1_test.go +++ b/ruleapi/tests/retract_1_test.go @@ -70,7 +70,11 @@ func Test_Retract_1(t *testing.T) { { ctx := context.WithValue(context.TODO(), "key", t) tuple, _ := model.NewTupleWithKeyValues("t3", "t3") - rs.Retract(ctx, tuple) + err := rs.Retract(ctx, tuple) + if err != nil { + t.Logf("%s", err) + t.FailNow() + } } /** From b0026653be6a2b906740a6233c95f609ead9cf01 Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Tue, 1 Oct 2019 15:11:44 -0600 Subject: [PATCH 079/125] Refactor join node name code --- rete/joinnode.go | 6 +----- rete/network.go | 14 +++++++++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/rete/joinnode.go b/rete/joinnode.go index ca895ef..71d6cb4 100644 --- a/rete/joinnode.go +++ b/rete/joinnode.go @@ -8,8 +8,6 @@ import ( "github.com/project-flogo/rules/rete/internal/types" ) -var names = 0 - //joinNode holds the join tables for unmatched entries type joinNode interface { node @@ -45,11 +43,9 @@ func (jn *joinNodeImpl) initjoinNodeImplVar(nw *reteNetworkImpl, rule model.Rule jn.rightIdrs = rightIdrs jn.conditionVar = conditionVar - // TODO conditionVar nil check name := "" if conditionVar == nil { - name = strconv.Itoa(names) - names++ + name = nw.getJoinNodeName() } else { name = conditionVar.GetName() } diff --git a/rete/network.go b/rete/network.go index 532d9ea..91b696b 100644 --- a/rete/network.go +++ b/rete/network.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math" + "strconv" "github.com/project-flogo/rules/common/model" @@ -45,9 +46,10 @@ type reteNetworkImpl struct { config map[string]string - factory *TypeFactory - idGen types.IdGen - tupleStore model.TupleStore + factory *TypeFactory + idGen types.IdGen + tupleStore model.TupleStore + joinNodeNames int } //NewReteNetwork ... creates a new rete network @@ -794,6 +796,12 @@ func (nw *reteNetworkImpl) removeJtRowsForTable(hdl types.ReteHandle, joinTable } } +func (nw *reteNetworkImpl) getJoinNodeName() string { + name := strconv.Itoa(nw.joinNodeNames) + nw.joinNodeNames++ + return name +} + func (nw *reteNetworkImpl) GetIdGenService() types.IdGen { return nw.idGen } From 754f1b7209d994af9795332f3a02d0fefaef0db0 Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Wed, 2 Oct 2019 17:02:30 -0600 Subject: [PATCH 080/125] Support parallel Assert and Delete of different tuples --- rete/internal/mem/mjointableimpl.go | 6 +- rete/internal/mem/mjointablerowimpl.go | 11 +-- rete/internal/mem/mjtrefsservice.go | 104 ++++------------------- rete/internal/mem/mretehandle.go | 5 -- rete/internal/redis/rjointableimpl.go | 3 +- rete/internal/redis/rjointablerowimpl.go | 26 +++--- rete/internal/redis/rjtrefsservice.go | 99 ++++----------------- rete/internal/redis/rretehandle.go | 7 +- rete/internal/types/types.go | 10 +-- rete/network.go | 27 ++---- 10 files changed, 63 insertions(+), 235 deletions(-) diff --git a/rete/internal/mem/mjointableimpl.go b/rete/internal/mem/mjointableimpl.go index a88f627..23b2e77 100644 --- a/rete/internal/mem/mjointableimpl.go +++ b/rete/internal/mem/mjointableimpl.go @@ -29,14 +29,12 @@ func (jt *joinTableImpl) initJoinTableImpl(nw types.Network, rule model.Rule, id } func (jt *joinTableImpl) AddRow(handles []types.ReteHandle) types.JoinTableRow { - row := newJoinTableRow(handles, jt.Nw) - - jt.table[row.GetID()] = row for i := 0; i < len(row.GetHandles()); i++ { handle := row.GetHandles()[i] jt.Nw.GetJtRefService().AddEntry(handle, jt.name, row.GetID()) } + jt.table[row.GetID()] = row return row } @@ -76,7 +74,7 @@ func (jt *joinTableImpl) RemoveAllRows() { //first, from jTable, remove row jt.RemoveRow(row.GetID()) for _, hdl := range row.GetHandles() { - jt.Nw.GetJtRefService().RemoveTableEntry(hdl, jt.GetName()) + jt.Nw.GetJtRefService().RemoveEntry(hdl, jt.GetName(), row.GetID()) } //Delete the rowRef itself rowIter.Remove() diff --git a/rete/internal/mem/mjointablerowimpl.go b/rete/internal/mem/mjointablerowimpl.go index 8afa750..a7fd5c5 100644 --- a/rete/internal/mem/mjointablerowimpl.go +++ b/rete/internal/mem/mjointablerowimpl.go @@ -8,14 +8,15 @@ type joinTableRowImpl struct { } func newJoinTableRow(handles []types.ReteHandle, nw types.Network) types.JoinTableRow { - jtr := joinTableRowImpl{} - jtr.initJoinTableRow(handles, nw) + jtr := joinTableRowImpl{ + handles: append([]types.ReteHandle{}, handles...), + } + jtr.SetID(nw) return &jtr } -func (jtr *joinTableRowImpl) initJoinTableRow(handles []types.ReteHandle, nw types.Network) { - jtr.SetID(nw) - jtr.handles = append([]types.ReteHandle{}, handles...) +func (jtr *joinTableRowImpl) Write() { + } func (jtr *joinTableRowImpl) GetHandles() []types.ReteHandle { diff --git a/rete/internal/mem/mjtrefsservice.go b/rete/internal/mem/mjtrefsservice.go index ed67fb1..7692e7e 100644 --- a/rete/internal/mem/mjtrefsservice.go +++ b/rete/internal/mem/mjtrefsservice.go @@ -8,13 +8,13 @@ import ( type jtRefsServiceImpl struct { //keys are jointable-ids and values are lists of row-ids in the corresponding join table types.NwServiceImpl - tablesAndRows map[string]map[string]map[int]int + tablesAndRows map[string]map[int]string } func NewJoinTableRefsInHdlImpl(nw types.Network, config map[string]interface{}) types.JtRefsService { hdlJt := jtRefsServiceImpl{} hdlJt.Nw = nw - hdlJt.tablesAndRows = make(map[string]map[string]map[int]int) + hdlJt.tablesAndRows = make(map[string]map[int]string) return &hdlJt } @@ -23,88 +23,23 @@ func (h *jtRefsServiceImpl) Init() { } func (h *jtRefsServiceImpl) AddEntry(handle types.ReteHandle, jtName string, rowID int) { - tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] - if !found { - tblMap = make(map[string]map[int]int) + tblMap = make(map[int]string) h.tablesAndRows[handle.GetTupleKey().String()] = tblMap } - - rowsForJoinTable, found := tblMap[jtName] - if !found { - rowsForJoinTable = make(map[int]int) - tblMap[jtName] = rowsForJoinTable - } - rowsForJoinTable[rowID] = rowID -} - -func (h *jtRefsServiceImpl) RemoveEntry(handle types.ReteHandle, jtName string) { - tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] - if found { - delete(tblMap, jtName) - } + tblMap[rowID] = jtName } -func (h *jtRefsServiceImpl) RemoveRowEntry(handle types.ReteHandle, jtName string, rowID int) { +func (h *jtRefsServiceImpl) RemoveEntry(handle types.ReteHandle, jtName string, rowID int) { tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] if found { - rowIDs, fnd := tblMap[jtName] - if fnd { - delete(rowIDs, rowID) - } + delete(tblMap, rowID) } } -func (h *jtRefsServiceImpl) RemoveTableEntry(handle types.ReteHandle, jtName string) { - tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] - if found { - delete(tblMap, jtName) - } -} - -func (h *jtRefsServiceImpl) GetTableIterator(handle types.ReteHandle) types.JointableIterator { - ri := hdlRefsTableIterator{} - ri.nw = h.Nw - ri.kList = list.List{} - - tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] - if found { - ri.tblMap = tblMap - for k, _ := range tblMap { - ri.kList.PushBack(k) - } - } - ri.curr = ri.kList.Front() - return &ri -} - -type hdlRefsTableIterator struct { - tblMap map[string]map[int]int - kList list.List - currJtName string - curr *list.Element - nw types.Network -} - -func (ri *hdlRefsTableIterator) HasNext() bool { - return ri.curr != nil -} - -func (ri *hdlRefsTableIterator) Next() types.JoinTable { - ri.currJtName = ri.curr.Value.(string) - jT := ri.nw.GetJtService().GetJoinTable(ri.currJtName) - ri.curr = ri.curr.Next() - return jT -} - -func (ri *hdlRefsTableIterator) Remove() { - delete(ri.tblMap, ri.currJtName) -} - type hdlRefsRowIterator struct { - jtName string - rowIdMap map[int]int + rowIdMap map[int]string kList list.List curr *list.Element currRowId int @@ -115,36 +50,29 @@ func (ri *hdlRefsRowIterator) HasNext() bool { return ri.curr != nil } -func (ri *hdlRefsRowIterator) Next() types.JoinTableRow { +func (ri *hdlRefsRowIterator) Next() (types.JoinTableRow, types.JoinTable) { rowID := ri.curr.Value.(int) ri.currRowId = rowID - var jtRow types.JoinTableRow - jT := ri.nw.GetJtService().GetJoinTable(ri.jtName) + ri.curr = ri.curr.Next() + jT := ri.nw.GetJtService().GetJoinTable(ri.rowIdMap[rowID]) if jT != nil { - jtRow = jT.GetRow(rowID) + return jT.GetRow(rowID), jT } - ri.curr = ri.curr.Next() - return jtRow + return nil, jT } func (ri *hdlRefsRowIterator) Remove() { delete(ri.rowIdMap, ri.currRowId) } -func (h *jtRefsServiceImpl) GetRowIterator(handle types.ReteHandle, jtName string) types.JointableRowIterator { +func (h *jtRefsServiceImpl) GetRowIterator(handle types.ReteHandle) types.JointableIterator { ri := hdlRefsRowIterator{} - ri.jtName = jtName ri.kList = list.List{} ri.nw = h.Nw tblMap := h.tablesAndRows[handle.GetTupleKey().String()] - if tblMap != nil { - rowMap := tblMap[jtName] - if rowMap != nil { - ri.rowIdMap = rowMap - for k, _ := range ri.rowIdMap { - ri.kList.PushBack(k) - } - } + ri.rowIdMap = tblMap + for rowID := range tblMap { + ri.kList.PushBack(rowID) } ri.curr = ri.kList.Front() return &ri diff --git a/rete/internal/mem/mretehandle.go b/rete/internal/mem/mretehandle.go index 5131332..c4478f1 100644 --- a/rete/internal/mem/mretehandle.go +++ b/rete/internal/mem/mretehandle.go @@ -51,8 +51,3 @@ func (hdl *reteHandleImpl) GetStatus() string { func (hdl *reteHandleImpl) AddJoinTableRowRef(joinTableRowVar types.JoinTableRow, joinTableVar types.JoinTable) { hdl.Nw.GetJtRefService().AddEntry(hdl, joinTableVar.GetName(), joinTableRowVar.GetID()) } - -//Used when a rule is deleted. See Network.RemoveRule -func (hdl *reteHandleImpl) RemoveJoinTable(joinTableID string) { - hdl.Nw.GetJtRefService().RemoveEntry(hdl, joinTableID) -} diff --git a/rete/internal/redis/rjointableimpl.go b/rete/internal/redis/rjointableimpl.go index 88f287f..4a902bf 100644 --- a/rete/internal/redis/rjointableimpl.go +++ b/rete/internal/redis/rjointableimpl.go @@ -36,6 +36,7 @@ func (jt *joinTableImpl) AddRow(handles []types.ReteHandle) types.JoinTableRow { handle := row.GetHandles()[i] jt.Nw.GetJtRefService().AddEntry(handle, jt.name, row.GetID()) } + row.Write() return row } @@ -54,7 +55,7 @@ func (jt *joinTableImpl) RemoveAllRows() { //first, from jTable, remove row jt.RemoveRow(row.GetID()) for _, hdl := range row.GetHandles() { - jt.Nw.GetJtRefService().RemoveTableEntry(hdl, jt.GetName()) + jt.Nw.GetJtRefService().RemoveEntry(hdl, jt.GetName(), row.GetID()) } //Delete the rowRef itself rowIter.Remove() diff --git a/rete/internal/redis/rjointablerowimpl.go b/rete/internal/redis/rjointablerowimpl.go index 019d03b..7291cf7 100644 --- a/rete/internal/redis/rjointablerowimpl.go +++ b/rete/internal/redis/rjointablerowimpl.go @@ -16,9 +16,11 @@ type joinTableRowImpl struct { } func newJoinTableRow(jtKey string, handles []types.ReteHandle, nw types.Network) types.JoinTableRow { - jtr := joinTableRowImpl{} + jtr := joinTableRowImpl{ + handles: append([]types.ReteHandle{}, handles...), + jtKey: jtKey, + } jtr.SetID(nw) - jtr.initJoinTableRow(jtKey, handles, nw) return &jtr } @@ -32,26 +34,18 @@ func newJoinTableRowLoadedFromStore(jtKey string, rowID int, handles []types.Ret return &jtr } -func (jtr *joinTableRowImpl) initJoinTableRow(jtKey string, handles []types.ReteHandle, nw types.Network) { - jtr.jtKey = jtKey - jtr.handles = append([]types.ReteHandle{}, handles...) - - rowEntry := make(map[string]interface{}) - - str := "" - +func (jtr *joinTableRowImpl) Write() { + handles, row := jtr.handles, make(map[string]interface{}) + end, str := len(handles)-1, "" for i, v := range handles { str += v.GetTupleKey().String() - if i < len(handles)-1 { + if i < end { str += "," } } - - rowEntry[strconv.Itoa(jtr.ID)] = str - + row[strconv.Itoa(jtr.ID)] = str hdl := redisutils.GetRedisHdl() - hdl.HSetAll(jtKey, rowEntry) - + hdl.HSetAll(jtr.jtKey, row) } func (jtr *joinTableRowImpl) GetHandles() []types.ReteHandle { diff --git a/rete/internal/redis/rjtrefsservice.go b/rete/internal/redis/rjtrefsservice.go index b584ca3..64fcfc0 100644 --- a/rete/internal/redis/rjtrefsservice.go +++ b/rete/internal/redis/rjtrefsservice.go @@ -25,115 +25,48 @@ func (h *jtRefsServiceImpl) Init() { } func (h *jtRefsServiceImpl) AddEntry(handle types.ReteHandle, jtName string, rowID int) { - - //format: prefix:rtbls:tkey ==> {jtname=jtname, ...} - //format: prefix:rrows:tkey:jtname ==> {rowid=rowid, ...} - + // format: prefix:rtbls:tkey ==> {rowID=jtname, ...} key := h.Nw.GetPrefix() + ":rtbls:" + handle.GetTupleKey().String() hdl := redisutils.GetRedisHdl() valMap := make(map[string]interface{}) - valMap[jtName] = jtName + valMap[strconv.Itoa(rowID)] = jtName hdl.HSetAll(key, valMap) - - rkey := h.Nw.GetPrefix() + ":rrows:" + handle.GetTupleKey().String() + ":" + jtName - rowMap := make(map[string]interface{}) - rowIdStr := strconv.Itoa(rowID) - rowMap[rowIdStr] = rowIdStr - hdl.HSetAll(rkey, rowMap) -} - -func (h *jtRefsServiceImpl) RemoveEntry(handle types.ReteHandle, jtName string) { - key := h.Nw.GetPrefix() + ":rtbls:" + handle.GetTupleKey().String() - hdl := redisutils.GetRedisHdl() - hdl.HDel(key, jtName) - - rkey := h.Nw.GetPrefix() + ":rrows:" + handle.GetTupleKey().String() + ":" + jtName - hdl.Del(rkey) - -} - -func (h *jtRefsServiceImpl) RemoveRowEntry(handle types.ReteHandle, jtName string, rowID int) { - rowKey := h.Nw.GetPrefix() + ":rrows:" + handle.GetTupleKey().String() + ":" + jtName - hdl := redisutils.GetRedisHdl() - rowIdStr := strconv.Itoa(rowID) - hdl.HDel(rowKey, rowIdStr) - - //hkey := h.Nw.GetPrefix() + ":rtbls:" + handle.GetTupleKey().String() - //hdl.HDel(hkey, jtName) -} - -func (h *jtRefsServiceImpl) RemoveTableEntry(handle types.ReteHandle, jtName string) { - - //Delete all row entry refs for this table - rowKey := h.Nw.GetPrefix() + ":rrows:" + handle.GetTupleKey().String() + ":" + jtName - hdl := redisutils.GetRedisHdl() - hdl.Del(rowKey) - - //Delete the table entry for this key - hkey := h.Nw.GetPrefix() + ":rtbls:" + handle.GetTupleKey().String() - hdl.HDel(hkey, jtName) } -func (h *jtRefsServiceImpl) GetTableIterator(handle types.ReteHandle) types.JointableIterator { - ri := hdlRefsTableIteratorImpl{} - ri.nw = h.Nw - //format: prefix:rtbls:tkey ==> {jtname=jtname, ...} +func (h *jtRefsServiceImpl) RemoveEntry(handle types.ReteHandle, jtName string, rowID int) { key := h.Nw.GetPrefix() + ":rtbls:" + handle.GetTupleKey().String() hdl := redisutils.GetRedisHdl() - ri.iter = hdl.GetMapIterator(key) - return &ri + hdl.HDel(key, strconv.Itoa(rowID)) } -type hdlRefsTableIteratorImpl struct { +type hdlRefsRowIteratorImpl struct { + key string iter *redisutils.MapIterator nw types.Network } -func (ri *hdlRefsTableIteratorImpl) HasNext() bool { - return ri.iter.HasNext() -} - -func (ri *hdlRefsTableIteratorImpl) Next() types.JoinTable { - jtName, _ := ri.iter.Next() - jT := ri.nw.GetJtService().GetJoinTable(jtName) - return jT -} -func (ri *hdlRefsTableIteratorImpl) Remove() { - ri.iter.Remove() -} - -type hdlRefsRowIteratorImpl struct { - key string - iter *redisutils.MapIterator - nw types.Network - jtName string -} - func (r *hdlRefsRowIteratorImpl) HasNext() bool { return r.iter.HasNext() } -func (r *hdlRefsRowIteratorImpl) Next() types.JoinTableRow { - rowIdStr, _ := r.iter.Next() - rowID, _ := strconv.Atoi(rowIdStr) - jT := r.nw.GetJtService().GetJoinTable(r.jtName) - row := jT.GetRow(rowID) - return row +func (r *hdlRefsRowIteratorImpl) Next() (types.JoinTableRow, types.JoinTable) { + rowIDStr, jtName := r.iter.Next() + rowID, _ := strconv.Atoi(rowIDStr) + jT := r.nw.GetJtService().GetJoinTable(jtName.(string)) + if jT != nil { + return jT.GetRow(rowID), jT + } + return nil, jT } func (r *hdlRefsRowIteratorImpl) Remove() { r.iter.Remove() } -//format: prefix:rtbls:tkey ==> {jtname=jtname, ...} -//format: prefix:rrows:tkey:jtname ==> {rowid=rowid, ...} - -func (h *jtRefsServiceImpl) GetRowIterator(handle types.ReteHandle, jtName string) types.JointableRowIterator { +func (h *jtRefsServiceImpl) GetRowIterator(handle types.ReteHandle) types.JointableIterator { r := hdlRefsRowIteratorImpl{} r.nw = h.Nw - r.jtName = jtName - //ex: a:rrows:n1:a:b1:L_tbl - r.key = h.Nw.GetPrefix() + ":rrows:" + handle.GetTupleKey().String() + ":" + jtName + r.key = h.Nw.GetPrefix() + ":rtbls:" + handle.GetTupleKey().String() hdl := redisutils.GetRedisHdl() r.iter = hdl.GetMapIterator(r.key) return &r diff --git a/rete/internal/redis/rretehandle.go b/rete/internal/redis/rretehandle.go index 46db7ad..d53634a 100644 --- a/rete/internal/redis/rretehandle.go +++ b/rete/internal/redis/rretehandle.go @@ -61,12 +61,7 @@ func (hdl *reteHandleImpl) AddJoinTableRowRef(joinTableRowVar types.JoinTableRow hdl.Nw.GetJtRefService().AddEntry(hdl, joinTableVar.GetName(), joinTableRowVar.GetID()) } -//Used when a rule is deleted. See Network.RemoveRule -func (hdl *reteHandleImpl) RemoveJoinTable(jtName string) { - hdl.Nw.GetJtRefService().RemoveEntry(hdl, jtName) -} - func (hdl *reteHandleImpl) GetRefTableIterator() types.JointableIterator { - refTblIterator := hdl.Nw.GetJtRefService().GetTableIterator(hdl) + refTblIterator := hdl.Nw.GetJtRefService().GetRowIterator(hdl) return refTblIterator } diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index 6ff47bd..af2796b 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -31,6 +31,7 @@ type JoinTable interface { type JoinTableRow interface { NwElemId + Write() GetHandles() []ReteHandle } @@ -46,11 +47,8 @@ type ReteHandle interface { type JtRefsService interface { NwService AddEntry(handle ReteHandle, jtName string, rowID int) - RemoveRowEntry(handle ReteHandle, jtName string, rowID int) - RemoveTableEntry(handle ReteHandle, jtName string) - RemoveEntry(handle ReteHandle, jtName string) - GetTableIterator(handle ReteHandle) JointableIterator - GetRowIterator(handle ReteHandle, jtName string) JointableRowIterator + RemoveEntry(handle ReteHandle, jtName string, rowID int) + GetRowIterator(handle ReteHandle) JointableIterator } type JtService interface { @@ -75,7 +73,7 @@ type IdGen interface { type JointableIterator interface { HasNext() bool - Next() JoinTable + Next() (JoinTableRow, JoinTable) Remove() } diff --git a/rete/network.go b/rete/network.go index 91b696b..362c7da 100644 --- a/rete/network.go +++ b/rete/network.go @@ -745,13 +745,13 @@ func (nw *reteNetworkImpl) removeJoinTableRowRefs(hdl types.ReteHandle, changedP tuple := hdl.GetTuple() alias := tuple.GetTupleType() - hdlTblIter := nw.jtRefsService.GetTableIterator(hdl) - + hdlTblIter := nw.jtRefsService.GetRowIterator(hdl) for hdlTblIter.HasNext() { - joinTable := hdlTblIter.Next() - if joinTable == nil { + row, joinTable := hdlTblIter.Next() + if row == nil || joinTable == nil { continue } + toDelete := false if changedProps != nil { rule := joinTable.GetRule() @@ -773,26 +773,11 @@ func (nw *reteNetworkImpl) removeJoinTableRowRefs(hdl types.ReteHandle, changedP continue } - nw.removeJtRowsForTable(hdl, joinTable) - } - //TODO: Remove the current entry from underneath - hdlTblIter.Remove() -} - -func (nw *reteNetworkImpl) removeJtRowsForTable(hdl types.ReteHandle, joinTable types.JoinTable) { - rowIter := nw.jtRefsService.GetRowIterator(hdl, joinTable.GetName()) - ////Remove rows from corresponding join tables - for rowIter.HasNext() { - row := rowIter.Next() - //first, from jTable, remove row joinTable.RemoveRow(row.GetID()) for _, otherHdl := range row.GetHandles() { - //if otherHdl.GetTupleKey().String() != hdl.GetTupleKey().String() { - nw.jtRefsService.RemoveRowEntry(otherHdl, joinTable.GetName(), row.GetID()) - //} + nw.jtRefsService.RemoveEntry(otherHdl, joinTable.GetName(), row.GetID()) } - //TODO: Delete the rowRef itself - rowIter.Remove() + hdlTblIter.Remove() } } From b1a67bafbf61506634fa4739cf1bd30160d6f12e Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Thu, 3 Oct 2019 14:29:58 -0600 Subject: [PATCH 081/125] Added mutexes to memory store --- rete/internal/mem/mjointableimpl.go | 42 ++++++++---- rete/internal/mem/mjtrefsservice.go | 95 ++++++++++++++++++++-------- rete/internal/mem/mjtservice.go | 8 +++ rete/network.go | 37 ++++++----- ruleapi/internal/store/mem/mstore.go | 15 ++++- 5 files changed, 135 insertions(+), 62 deletions(-) diff --git a/rete/internal/mem/mjointableimpl.go b/rete/internal/mem/mjointableimpl.go index 23b2e77..306e015 100644 --- a/rete/internal/mem/mjointableimpl.go +++ b/rete/internal/mem/mjointableimpl.go @@ -1,6 +1,8 @@ package mem import ( + "sync" + "container/list" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/rete/internal/types" @@ -12,6 +14,7 @@ type joinTableImpl struct { idr []model.TupleType rule model.Rule name string + sync.RWMutex } func newJoinTableImpl(nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) types.JoinTable { @@ -34,11 +37,15 @@ func (jt *joinTableImpl) AddRow(handles []types.ReteHandle) types.JoinTableRow { handle := row.GetHandles()[i] jt.Nw.GetJtRefService().AddEntry(handle, jt.name, row.GetID()) } + jt.Lock() + defer jt.Unlock() jt.table[row.GetID()] = row return row } func (jt *joinTableImpl) RemoveRow(rowID int) types.JoinTableRow { + jt.Lock() + defer jt.Unlock() row, found := jt.table[rowID] if found { delete(jt.table, rowID) @@ -48,6 +55,8 @@ func (jt *joinTableImpl) RemoveRow(rowID int) types.JoinTableRow { } func (jt *joinTableImpl) GetRowCount() int { + jt.RLock() + defer jt.RUnlock() return len(jt.table) } @@ -56,10 +65,12 @@ func (jt *joinTableImpl) GetRule() model.Rule { } func (jt *joinTableImpl) GetRowIterator() types.JointableRowIterator { - return newRowIterator(jt.table) + return newRowIterator(jt) } func (jt *joinTableImpl) GetRow(rowID int) types.JoinTableRow { + jt.RLock() + defer jt.RUnlock() return jt.table[rowID] } @@ -82,20 +93,22 @@ func (jt *joinTableImpl) RemoveAllRows() { } type rowIteratorImpl struct { - table map[int]types.JoinTableRow - kList list.List + table *joinTableImpl + list list.List currKey int curr *list.Element } -func newRowIterator(jTable map[int]types.JoinTableRow) types.JointableRowIterator { - ri := rowIteratorImpl{} - ri.table = jTable - ri.kList = list.List{} - for k, _ := range jTable { - ri.kList.PushBack(k) +func newRowIterator(jt *joinTableImpl) types.JointableRowIterator { + ri := rowIteratorImpl{ + table: jt, + } + jt.RLock() + defer jt.RUnlock() + for k := range jt.table { + ri.list.PushBack(k) } - ri.curr = ri.kList.Front() + ri.curr = ri.list.Front() return &ri } @@ -105,11 +118,14 @@ func (ri *rowIteratorImpl) HasNext() bool { func (ri *rowIteratorImpl) Next() types.JoinTableRow { ri.currKey = ri.curr.Value.(int) - val := ri.table[ri.currKey] ri.curr = ri.curr.Next() - return val + ri.table.RLock() + defer ri.table.RUnlock() + return ri.table.table[ri.currKey] } func (ri *rowIteratorImpl) Remove() { - delete(ri.table, ri.currKey) + ri.table.Lock() + defer ri.table.Unlock() + delete(ri.table.table, ri.currKey) } diff --git a/rete/internal/mem/mjtrefsservice.go b/rete/internal/mem/mjtrefsservice.go index 7692e7e..97b0026 100644 --- a/rete/internal/mem/mjtrefsservice.go +++ b/rete/internal/mem/mjtrefsservice.go @@ -1,20 +1,27 @@ package mem import ( + "sync" + "container/list" "github.com/project-flogo/rules/rete/internal/types" ) +type jtRowsImpl struct { + rows map[int]string + sync.RWMutex +} + type jtRefsServiceImpl struct { - //keys are jointable-ids and values are lists of row-ids in the corresponding join table types.NwServiceImpl - tablesAndRows map[string]map[int]string + tablesAndRows map[string]*jtRowsImpl + sync.RWMutex } func NewJoinTableRefsInHdlImpl(nw types.Network, config map[string]interface{}) types.JtRefsService { hdlJt := jtRefsServiceImpl{} hdlJt.Nw = nw - hdlJt.tablesAndRows = make(map[string]map[int]string) + hdlJt.tablesAndRows = make(map[string]*jtRowsImpl) return &hdlJt } @@ -23,38 +30,57 @@ func (h *jtRefsServiceImpl) Init() { } func (h *jtRefsServiceImpl) AddEntry(handle types.ReteHandle, jtName string, rowID int) { - tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] + key := handle.GetTupleKey().String() + h.Lock() + defer h.Unlock() + tblMap, found := h.tablesAndRows[key] if !found { - tblMap = make(map[int]string) + tblMap = &jtRowsImpl{ + rows: make(map[int]string), + } h.tablesAndRows[handle.GetTupleKey().String()] = tblMap } - tblMap[rowID] = jtName + tblMap.Lock() + defer tblMap.Unlock() + tblMap.rows[rowID] = jtName } func (h *jtRefsServiceImpl) RemoveEntry(handle types.ReteHandle, jtName string, rowID int) { - tblMap, found := h.tablesAndRows[handle.GetTupleKey().String()] + key := handle.GetTupleKey().String() + h.Lock() + defer h.Unlock() + tblMap, found := h.tablesAndRows[key] if found { - delete(tblMap, rowID) + tblMap.Lock() + defer tblMap.Unlock() + delete(tblMap.rows, rowID) + if len(tblMap.rows) == 0 { + delete(h.tablesAndRows, key) + } } } type hdlRefsRowIterator struct { - rowIdMap map[int]string - kList list.List - curr *list.Element - currRowId int - nw types.Network + refs *jtRefsServiceImpl + key string + rows *jtRowsImpl + list list.List + current *list.Element + rowID int + nw types.Network } func (ri *hdlRefsRowIterator) HasNext() bool { - return ri.curr != nil + return ri.current != nil } func (ri *hdlRefsRowIterator) Next() (types.JoinTableRow, types.JoinTable) { - rowID := ri.curr.Value.(int) - ri.currRowId = rowID - ri.curr = ri.curr.Next() - jT := ri.nw.GetJtService().GetJoinTable(ri.rowIdMap[rowID]) + rowID := ri.current.Value.(int) + ri.rowID = rowID + ri.current = ri.current.Next() + ri.rows.RLock() + defer ri.rows.RUnlock() + jT := ri.nw.GetJtService().GetJoinTable(ri.rows.rows[rowID]) if jT != nil { return jT.GetRow(rowID), jT } @@ -62,18 +88,33 @@ func (ri *hdlRefsRowIterator) Next() (types.JoinTableRow, types.JoinTable) { } func (ri *hdlRefsRowIterator) Remove() { - delete(ri.rowIdMap, ri.currRowId) + ri.rows.Lock() + defer ri.rows.Unlock() + delete(ri.rows.rows, ri.rowID) + if len(ri.rows.rows) == 0 { + ri.refs.Lock() + defer ri.refs.Unlock() + delete(ri.refs.tablesAndRows, ri.key) + } } func (h *jtRefsServiceImpl) GetRowIterator(handle types.ReteHandle) types.JointableIterator { - ri := hdlRefsRowIterator{} - ri.kList = list.List{} - ri.nw = h.Nw - tblMap := h.tablesAndRows[handle.GetTupleKey().String()] - ri.rowIdMap = tblMap - for rowID := range tblMap { - ri.kList.PushBack(rowID) + key := handle.GetTupleKey().String() + ri := hdlRefsRowIterator{ + refs: h, + key: key, + nw: h.Nw, + } + h.RLock() + defer h.RUnlock() + tblMap := h.tablesAndRows[key] + if tblMap == nil { + return &ri + } + ri.rows = tblMap + for rowID := range tblMap.rows { + ri.list.PushBack(rowID) } - ri.curr = ri.kList.Front() + ri.current = ri.list.Front() return &ri } diff --git a/rete/internal/mem/mjtservice.go b/rete/internal/mem/mjtservice.go index 235ea7f..6c21cfb 100644 --- a/rete/internal/mem/mjtservice.go +++ b/rete/internal/mem/mjtservice.go @@ -1,6 +1,8 @@ package mem import ( + "sync" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/rete/internal/types" ) @@ -8,6 +10,7 @@ import ( type jtServiceImpl struct { types.NwServiceImpl allJoinTables map[string]types.JoinTable + sync.RWMutex } func NewJoinTableCollection(nw types.Network, config map[string]interface{}) types.JtService { @@ -16,15 +19,20 @@ func NewJoinTableCollection(nw types.Network, config map[string]interface{}) typ jtc.allJoinTables = make(map[string]types.JoinTable) return &jtc } + func (jtc *jtServiceImpl) Init() { } func (jtc *jtServiceImpl) GetJoinTable(joinTableName string) types.JoinTable { + jtc.RLock() + defer jtc.RUnlock() return jtc.allJoinTables[joinTableName] } func (jtc *jtServiceImpl) GetOrCreateJoinTable(nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) types.JoinTable { + jtc.Lock() + defer jtc.Unlock() jT, found := jtc.allJoinTables[name] if !found { jT = newJoinTableImpl(nw, rule, identifiers, name) diff --git a/rete/network.go b/rete/network.go index 362c7da..d08ac76 100644 --- a/rete/network.go +++ b/rete/network.go @@ -35,7 +35,6 @@ type reteNetworkImpl struct { //handleService map[string]types.ReteHandle handleService types.HandleService - crudLock sync.RWMutex txnHandler []model.RtcTransactionHandler txnContext []interface{} @@ -50,6 +49,8 @@ type reteNetworkImpl struct { idGen types.IdGen tupleStore model.TupleStore joinNodeNames int + + sync.RWMutex } //NewReteNetwork ... creates a new rete network @@ -95,9 +96,8 @@ func (nw *reteNetworkImpl) initNwServices() { } func (nw *reteNetworkImpl) AddRule(rule model.Rule) (err error) { - - nw.crudLock.Lock() - defer nw.crudLock.Unlock() + nw.Lock() + defer nw.Unlock() if nw.allRules[rule.GetName()] != nil { return fmt.Errorf("Rule already exists.." + rule.GetName()) @@ -160,9 +160,8 @@ func (nw *reteNetworkImpl) setClassNodeAndLinkJoinTables(nodesOfRule *list.List, } func (nw *reteNetworkImpl) RemoveRule(ruleName string) model.Rule { - - nw.crudLock.Lock() - defer nw.crudLock.Unlock() + nw.Lock() + defer nw.Unlock() rule := nw.allRules[ruleName] delete(nw.allRules, ruleName) @@ -202,8 +201,8 @@ func (nw *reteNetworkImpl) RemoveRule(ruleName string) model.Rule { } func (nw *reteNetworkImpl) GetRules() []model.Rule { - nw.crudLock.RLock() - defer nw.crudLock.RUnlock() + nw.RLock() + defer nw.RUnlock() rules := make([]model.Rule, 0) @@ -493,8 +492,8 @@ func getClassNode(nw *reteNetworkImpl, name model.TupleType) classNode { } func (nw *reteNetworkImpl) String() string { - nw.crudLock.RLock() - defer nw.crudLock.RUnlock() + nw.RLock() + defer nw.RUnlock() str := "\n>>> Class View <<<\n" for _, classNodeImpl := range nw.allClassNodes { @@ -529,8 +528,8 @@ func pickIdentifier(idrs []model.TupleType) model.TupleType { } func (nw *reteNetworkImpl) PrintRule(rule model.Rule) string { - nw.crudLock.RLock() - defer nw.crudLock.RUnlock() + nw.RLock() + defer nw.RUnlock() //str := "[Rule (" + rule.GetName() + ") Id(" + strconv.Itoa(rule.GetID()) + ")]\n" str := "[Rule (" + rule.GetName() + ") Id()]\n" @@ -575,8 +574,8 @@ func (nw *reteNetworkImpl) Assert(ctx context.Context, rs model.RuleSession, tup reteCtxVar, isRecursive, newCtx := getOrSetReteCtx(ctx, nw, rs) if !isRecursive { - nw.crudLock.Lock() - defer nw.crudLock.Unlock() + nw.RLock() + defer nw.RUnlock() err := nw.assertInternal(newCtx, tuple, changedProps, mode) if err != nil { @@ -591,8 +590,8 @@ func (nw *reteNetworkImpl) Assert(ctx context.Context, rs model.RuleSession, tup nw.removeTupleFromRete(tuple) } else if td.TTLInSeconds > 0 { // TTL for the tuple type, after that, remove it from RETE time.AfterFunc(time.Second*time.Duration(td.TTLInSeconds), func() { - nw.crudLock.Lock() - defer nw.crudLock.Unlock() + nw.RLock() + defer nw.RUnlock() nw.removeTupleFromRete(tuple) }) } //else, its -ve and means, never expire @@ -624,8 +623,8 @@ func (nw *reteNetworkImpl) Retract(ctx context.Context, rs model.RuleSession, tu } reteCtxVar, isRecursive, ctx := getOrSetReteCtx(ctx, nw, rs) if !isRecursive { - nw.crudLock.Lock() - defer nw.crudLock.Unlock() + nw.RLock() + defer nw.RUnlock() err := nw.retractInternal(ctx, tuple, changedProps, mode) if err != nil { return err diff --git a/ruleapi/internal/store/mem/mstore.go b/ruleapi/internal/store/mem/mstore.go index 631c241..b62d801 100644 --- a/ruleapi/internal/store/mem/mstore.go +++ b/ruleapi/internal/store/mem/mstore.go @@ -2,17 +2,20 @@ package mem import ( "fmt" + "sync" + "github.com/project-flogo/rules/common/model" ) type storeImpl struct { allTuples map[string]model.Tuple + sync.RWMutex } func NewStore(jsonConfig map[string]interface{}) model.TupleStore { - ms := storeImpl{} - - ms.allTuples = make(map[string]model.Tuple) + ms := storeImpl{ + allTuples: make(map[string]model.Tuple), + } return &ms } @@ -21,14 +24,20 @@ func (ms *storeImpl) Init() { } func (ms *storeImpl) GetTupleByKey(key model.TupleKey) model.Tuple { + ms.RLock() + defer ms.RUnlock() return ms.allTuples[key.String()] } func (ms *storeImpl) SaveTuple(tuple model.Tuple) { + ms.Lock() + defer ms.Unlock() ms.allTuples[tuple.GetKey().String()] = tuple } func (ms *storeImpl) DeleteTuple(key model.TupleKey) { + ms.Lock() + defer ms.Unlock() delete(ms.allTuples, key.String()) } From 71548602d917fd5cbcbf8509991405bbed2a669c Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Thu, 3 Oct 2019 17:16:27 -0600 Subject: [PATCH 082/125] Garbage collection --- examples/rulesapp/main.go | 57 +++++++++++++++++------- rete/internal/mem/mhandleservice.go | 2 +- rete/internal/mem/mretehandle.go | 10 ++--- rete/internal/redis/rhandleservice.go | 28 +++++++++--- rete/internal/redis/rjointablerowimpl.go | 2 +- rete/internal/redis/rretehandle.go | 12 ++--- rete/internal/types/types.go | 13 +++++- rete/joinnode.go | 26 ++++++++++- rete/network.go | 7 +-- 9 files changed, 115 insertions(+), 42 deletions(-) diff --git a/examples/rulesapp/main.go b/examples/rulesapp/main.go index 9083eb5..9dc6ed5 100644 --- a/examples/rulesapp/main.go +++ b/examples/rulesapp/main.go @@ -22,12 +22,14 @@ func main() { //First register the tuple descriptors err := model.RegisterTupleDescriptors(tupleDescriptor) if err != nil { - fmt.Printf("Error [%s]\n", err) - return + panic(err) } //Create a RuleSession - rs, _ := ruleapi.GetOrCreateRuleSession("asession") + rs, err := ruleapi.GetOrCreateRuleSession("asession") + if err != nil { + panic(err) + } //// check for name "Bob" in n1 rule := ruleapi.NewRule("n1.name == Bob") @@ -37,10 +39,16 @@ func main() { Function: checkForBobAction, Type: "function", } - aService, _ := ruleapi.NewActionService(serviceCfg) + aService, err := ruleapi.NewActionService(serviceCfg) + if err != nil { + panic(err) + } rule.SetActionService(aService) rule.SetContext("This is a test of context") - rs.AddRule(rule) + err = rs.AddRule(rule) + if err != nil { + panic(err) + } fmt.Printf("Rule added: [%s]\n", rule.GetName()) // check for name "Bob" in n1, match the "name" field in n2, @@ -54,14 +62,20 @@ func main() { Type: "function", } aService2, _ := ruleapi.NewActionService(serviceCfg2) - rule.SetActionService(aService2) - rs.AddRule(rule2) + rule2.SetActionService(aService2) + err = rs.AddRule(rule2) + if err != nil { + panic(err) + } fmt.Printf("Rule added: [%s]\n", rule2.GetName()) //set a transaction handler rs.RegisterRtcTransactionHandler(txHandler, nil) //Start the rule session - rs.Start(nil) + err = rs.Start(nil) + if err != nil { + panic(err) + } //Now assert a "n1" tuple fmt.Println("Asserting n1 tuple with name=Tom") @@ -69,20 +83,20 @@ func main() { t1.SetString(nil, "name", "Tom") err = rs.Assert(nil, t1) if err != nil { - fmt.Printf("Warn: [%s]\n", err) + panic(err) } - t11 := rs.GetStore().GetTupleByKey(t1.GetKey()) - if t11 != nil { - fmt.Printf("Warn: Tuple already in store[%s]\n", t11.GetKey()) + if t11 == nil { + panic(fmt.Errorf("Warn: Tuple should be in store[%s]", t11.GetKey())) } + //Now assert a "n1" tuple fmt.Println("Asserting n1 tuple with name=Bob") t2, _ := model.NewTupleWithKeyValues("n1", "Bob") t2.SetString(nil, "name", "Bob") err = rs.Assert(nil, t2) if err != nil { - fmt.Printf("Warn: [%s]\n", err) + panic(err) } //Now assert a "n2" tuple @@ -91,13 +105,22 @@ func main() { t3.SetString(nil, "name", "Bob") err = rs.Assert(nil, t3) if err != nil { - fmt.Printf("Warn: [%s]\n", err) + panic(err) } //Retract tuples - rs.Retract(nil, t1) - rs.Retract(nil, t2) - rs.Retract(nil, t3) + err = rs.Retract(nil, t1) + if err != nil { + panic(err) + } + err = rs.Retract(nil, t2) + if err != nil { + panic(err) + } + err = rs.Retract(nil, t3) + if err != nil { + panic(err) + } //delete the rule rs.DeleteRule(rule2.GetName()) diff --git a/rete/internal/mem/mhandleservice.go b/rete/internal/mem/mhandleservice.go index edbb136..11d92a8 100644 --- a/rete/internal/mem/mhandleservice.go +++ b/rete/internal/mem/mhandleservice.go @@ -51,7 +51,7 @@ func (hc *handleServiceImpl) GetOrCreateHandle(nw types.Network, tuple model.Tup defer hc.Unlock() h, found := hc.allHandles[tuple.GetKey().String()] if !found { - h = newReteHandleImpl(nw, tuple, "creating") + h = newReteHandleImpl(nw, tuple, types.ReteHandleStatusCreating) hc.allHandles[tuple.GetKey().String()] = h //[tuple.GetKey().String()] = h } diff --git a/rete/internal/mem/mretehandle.go b/rete/internal/mem/mretehandle.go index c4478f1..7265396 100644 --- a/rete/internal/mem/mretehandle.go +++ b/rete/internal/mem/mretehandle.go @@ -11,10 +11,10 @@ type reteHandleImpl struct { types.NwElemIdImpl tuple model.Tuple tupleKey model.TupleKey - status string + status types.ReteHandleStatus } -func newReteHandleImpl(nw types.Network, tuple model.Tuple, status string) types.ReteHandle { +func newReteHandleImpl(nw types.Network, tuple model.Tuple, status types.ReteHandleStatus) types.ReteHandle { h1 := reteHandleImpl{} h1.initHandleImpl(nw, tuple, status) return &h1 @@ -25,7 +25,7 @@ func (hdl *reteHandleImpl) SetTuple(tuple model.Tuple) { hdl.tupleKey = tuple.GetKey() } -func (hdl *reteHandleImpl) initHandleImpl(nw types.Network, tuple model.Tuple, status string) { +func (hdl *reteHandleImpl) initHandleImpl(nw types.Network, tuple model.Tuple, status types.ReteHandleStatus) { hdl.SetID(nw) hdl.SetTuple(tuple) hdl.tupleKey = tuple.GetKey() @@ -40,11 +40,11 @@ func (hdl *reteHandleImpl) GetTupleKey() model.TupleKey { return hdl.tupleKey } -func (hdl *reteHandleImpl) SetStatus(status string) { +func (hdl *reteHandleImpl) SetStatus(status types.ReteHandleStatus) { hdl.status = status } -func (hdl *reteHandleImpl) GetStatus() string { +func (hdl *reteHandleImpl) GetStatus() types.ReteHandleStatus { return hdl.status } diff --git a/rete/internal/redis/rhandleservice.go b/rete/internal/redis/rhandleservice.go index 369a565..b65aee3 100644 --- a/rete/internal/redis/rhandleservice.go +++ b/rete/internal/redis/rhandleservice.go @@ -1,6 +1,8 @@ package redis import ( + "strconv" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/redisutils" "github.com/project-flogo/rules/rete/internal/types" @@ -34,7 +36,7 @@ func (hc *handleServiceImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle { rkey := hc.prefix + tuple.GetKey().String() redisutils.GetRedisHdl().Del(rkey) //TODO: Dummy handle - h := newReteHandleImpl(hc.GetNw(), tuple, rkey, "dummy") + h := newReteHandleImpl(hc.GetNw(), tuple, rkey, types.ReteHandleStatusUnknown) return h } @@ -56,26 +58,42 @@ func (hc *handleServiceImpl) GetHandleByKey(key model.TupleKey) types.ReteHandle //TODO: error handling return nil } - status := "" + status := types.ReteHandleStatusUnknown if value, ok := m["status"]; ok { if value, ok := value.(string); ok { - status = value + number, err := strconv.Atoi(value) + if err != nil { + panic(err) + } + status = types.ReteHandleStatus(number) + } else { + panic("status not string") } + } else { + panic("missing status") } h := newReteHandleImpl(hc.GetNw(), tuple, rkey, status) return h } func (hc *handleServiceImpl) GetOrCreateHandle(nw types.Network, tuple model.Tuple) (types.ReteHandle, bool) { - key, status := hc.prefix+tuple.GetKey().String(), "creating" + key, status := hc.prefix+tuple.GetKey().String(), types.ReteHandleStatusCreating exists, _ := redisutils.GetRedisHdl().HSetNX(key, "status", status) if exists { m := redisutils.GetRedisHdl().HGetAll(key) if len(m) > 0 { if value, ok := m["status"]; ok { if value, ok := value.(string); ok { - status = value + number, err := strconv.Atoi(value) + if err != nil { + panic(err) + } + status = types.ReteHandleStatus(number) + } else { + panic("status not string") } + } else { + panic("missing status") } } } diff --git a/rete/internal/redis/rjointablerowimpl.go b/rete/internal/redis/rjointablerowimpl.go index 7291cf7..044e5ce 100644 --- a/rete/internal/redis/rjointablerowimpl.go +++ b/rete/internal/redis/rjointablerowimpl.go @@ -60,7 +60,7 @@ func createRow(jtKey string, rowID string, key string, nw types.Network) types.J for _, key := range values { tupleKey := model.FromStringKey(key) tuple := nw.GetTupleStore().GetTupleByKey(tupleKey) - handle := newReteHandleImpl(nw, tuple, "", "unknown") + handle := newReteHandleImpl(nw, tuple, "", types.ReteHandleStatusUnknown) handles = append(handles, handle) } diff --git a/rete/internal/redis/rretehandle.go b/rete/internal/redis/rretehandle.go index d53634a..57bd2c2 100644 --- a/rete/internal/redis/rretehandle.go +++ b/rete/internal/redis/rretehandle.go @@ -13,11 +13,11 @@ type reteHandleImpl struct { tuple model.Tuple tupleKey model.TupleKey key string - status string + status types.ReteHandleStatus //jtRefs types.JtRefsService } -func newReteHandleImpl(nw types.Network, tuple model.Tuple, key, status string) types.ReteHandle { +func newReteHandleImpl(nw types.Network, tuple model.Tuple, key string, status types.ReteHandleStatus) types.ReteHandle { h1 := reteHandleImpl{} h1.initHandleImpl(nw, tuple, key, status) return &h1 @@ -30,7 +30,7 @@ func (hdl *reteHandleImpl) SetTuple(tuple model.Tuple) { } } -func (hdl *reteHandleImpl) initHandleImpl(nw types.Network, tuple model.Tuple, key, status string) { +func (hdl *reteHandleImpl) initHandleImpl(nw types.Network, tuple model.Tuple, key string, status types.ReteHandleStatus) { hdl.SetID(nw) hdl.SetTuple(tuple) hdl.tupleKey = tuple.GetKey() @@ -46,14 +46,14 @@ func (hdl *reteHandleImpl) GetTupleKey() model.TupleKey { return hdl.tupleKey } -func (hdl *reteHandleImpl) SetStatus(status string) { +func (hdl *reteHandleImpl) SetStatus(status types.ReteHandleStatus) { if hdl.key == "" { return } - redisutils.GetRedisHdl().HSetNX(hdl.key, "status", status) + redisutils.GetRedisHdl().HSet(hdl.key, "status", status) } -func (hdl *reteHandleImpl) GetStatus() string { +func (hdl *reteHandleImpl) GetStatus() types.ReteHandleStatus { return hdl.status } diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index af2796b..e87fc5b 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -35,13 +35,22 @@ type JoinTableRow interface { GetHandles() []ReteHandle } +type ReteHandleStatus uint + +const ( + ReteHandleStatusUnknown ReteHandleStatus = iota + ReteHandleStatusCreating + ReteHandleStatusCreated + ReteHandleStatusDeleting +) + type ReteHandle interface { NwElemId SetTuple(tuple model.Tuple) GetTuple() model.Tuple GetTupleKey() model.TupleKey - SetStatus(status string) - GetStatus() string + SetStatus(status ReteHandleStatus) + GetStatus() ReteHandleStatus } type JtRefsService interface { diff --git a/rete/joinnode.go b/rete/joinnode.go index 71d6cb4..e4f2fc7 100644 --- a/rete/joinnode.go +++ b/rete/joinnode.go @@ -180,9 +180,20 @@ func (jn *joinNodeImpl) assertFromRight(ctx context.Context, handles []types.Ret jn.rightTable.AddRow(handles) //TODO: rete listeners etc. rIterator := jn.leftTable.GetRowIterator() +LOOP: for rIterator.HasNext() { tupleTableRowLeft := rIterator.Next() - success := jn.joinLeftObjects(tupleTableRowLeft.GetHandles(), joinedHandles) + handles := tupleTableRowLeft.GetHandles() + for _, handle := range handles { + if jn.GetNw().GetHandleService().GetHandle(handle.GetTuple()) == nil { + rIterator.Remove() + for _, otherHdl := range handles { + jn.GetNw().GetJtRefService().RemoveEntry(otherHdl, jn.leftTable.GetName(), tupleTableRowLeft.GetID()) + } + continue LOOP + } + } + success := jn.joinLeftObjects(handles, joinedHandles) if !success { //TODO: handle it continue @@ -236,9 +247,20 @@ func (jn *joinNodeImpl) assertFromLeft(ctx context.Context, handles []types.Rete jn.leftTable.AddRow(handles) //TODO: rete listeners etc. rIterator := jn.rightTable.GetRowIterator() +LOOP: for rIterator.HasNext() { tupleTableRowRight := rIterator.Next() - success := jn.joinRightObjects(tupleTableRowRight.GetHandles(), joinedHandles) + handles := tupleTableRowRight.GetHandles() + for _, handle := range handles { + if jn.GetNw().GetHandleService().GetHandle(handle.GetTuple()) == nil { + rIterator.Remove() + for _, otherHdl := range handles { + jn.GetNw().GetJtRefService().RemoveEntry(otherHdl, jn.rightTable.GetName(), tupleTableRowRight.GetID()) + } + continue LOOP + } + } + success := jn.joinRightObjects(handles, joinedHandles) if !success { //TODO: handle it continue diff --git a/rete/network.go b/rete/network.go index d08ac76..9610832 100644 --- a/rete/network.go +++ b/rete/network.go @@ -646,9 +646,10 @@ func (nw *reteNetworkImpl) retractInternal(ctx context.Context, tuple model.Tupl handle := nw.handleService.GetHandle(tuple) if handle == nil { return fmt.Errorf("Tuple with key [%s] doesn't exist", tuple.GetKey().String()) - } else if handle.GetStatus() != "created" { - return fmt.Errorf("Tuple with key [%s] is being asserted", tuple.GetKey().String()) + } else if handle.GetStatus() != types.ReteHandleStatusCreated { + return fmt.Errorf("Tuple with key [%s] is being asserted or deleted: %d", tuple.GetKey().String(), handle.GetStatus()) } + handle.SetStatus(types.ReteHandleStatusDeleting) if ctx == nil { ctx = context.Background() @@ -679,7 +680,7 @@ func (nw *reteNetworkImpl) assertInternal(ctx context.Context, tuple model.Tuple if exists { return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) } - defer handle.SetStatus("created") + defer handle.SetStatus(types.ReteHandleStatusCreated) } tupleType := tuple.GetTupleType() From 5bccdbae95398ac4f37bbe2eb578f5325383c1a6 Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Mon, 7 Oct 2019 22:47:45 -0600 Subject: [PATCH 083/125] Redis store tests --- common/model/types.go | 2 +- examples/ordermanagement/main.go | 2 +- examples/rulesapp/main.go | 166 +++++++++++++++++------ examples/rulesapp/ruleapp_test.go | 47 +++++-- examples/trackntrace/trackntrace_test.go | 2 +- redisutils/redisutil_test.go | 58 +++++++- rete/common/types.go | 3 +- rete/conflict.go | 31 ++--- rete/context.go | 85 ++++-------- rete/internal/mem/mhandleservice.go | 5 +- rete/internal/mem/mjointableimpl.go | 11 +- rete/internal/mem/mjtrefsservice.go | 9 +- rete/internal/redis/rhandleservice.go | 39 ++++-- rete/internal/redis/rjointableimpl.go | 24 ++-- rete/internal/redis/rjointablerowimpl.go | 24 +++- rete/internal/redis/rjtrefsservice.go | 10 +- rete/internal/redis/rretehandle.go | 3 +- rete/internal/types/types.go | 49 ++++++- rete/joinnode.go | 10 +- rete/network.go | 79 ++++++----- rete/opsList.go | 12 +- rete/rulenode.go | 4 +- ruleaction/action.go | 2 +- ruleapi/rulesession.go | 19 ++- ruleapi/tests/common.go | 27 +++- ruleapi/tests/expr_1_test.go | 69 ++++++++-- ruleapi/tests/expr_2_test.go | 2 +- ruleapi/tests/expr_3_test.go | 2 +- ruleapi/tests/expr_4_test.go | 2 +- ruleapi/tests/expr_5_test.go | 2 +- ruleapi/tests/identifier_1_test.go | 4 +- ruleapi/tests/identifier_2_test.go | 4 +- ruleapi/tests/main_test.go | 42 ++++++ ruleapi/tests/memory/memory.go | 2 +- ruleapi/tests/retract_1_test.go | 5 +- ruleapi/tests/rsconfig.json | 35 +++++ ruleapi/tests/rtctxn_10_test.go | 19 ++- ruleapi/tests/rtctxn_11_test.go | 13 +- ruleapi/tests/rtctxn_12_test.go | 31 +++-- ruleapi/tests/rtctxn_13_test.go | 24 +++- ruleapi/tests/rtctxn_14_test.go | 26 +++- ruleapi/tests/rtctxn_15_test.go | 4 +- ruleapi/tests/rtctxn_16_test.go | 4 +- ruleapi/tests/rtctxn_1_test.go | 13 +- ruleapi/tests/rtctxn_2_test.go | 15 +- ruleapi/tests/rtctxn_3_test.go | 13 +- ruleapi/tests/rtctxn_4_test.go | 10 +- ruleapi/tests/rtctxn_5_test.go | 24 +++- ruleapi/tests/rtctxn_6_test.go | 28 ++-- ruleapi/tests/rtctxn_7_test.go | 10 +- ruleapi/tests/rtctxn_8_test.go | 11 +- ruleapi/tests/rtctxn_9_test.go | 31 +++-- ruleapi/tests/rules_1_test.go | 2 +- ruleapi/tests/rules_2_test.go | 8 +- ruleapi/tests/rules_3_test.go | 20 +-- ruleapi/tests/sessions_test.go | 4 +- 56 files changed, 860 insertions(+), 342 deletions(-) create mode 100644 ruleapi/tests/main_test.go create mode 100644 ruleapi/tests/rsconfig.json diff --git a/common/model/types.go b/common/model/types.go index 8f82f5b..75cb787 100644 --- a/common/model/types.go +++ b/common/model/types.go @@ -66,7 +66,7 @@ type RuleSession interface { Start(startupCtx map[string]interface{}) (err error) //return the asserted tuple, nil if not found - GetAssertedTuple(key TupleKey) Tuple + GetAssertedTuple(ctx context.Context, key TupleKey) Tuple //Retract, and remove Delete(ctx context.Context, tuple Tuple) error diff --git a/examples/ordermanagement/main.go b/examples/ordermanagement/main.go index 5c8ce0b..5a6ea7b 100644 --- a/examples/ordermanagement/main.go +++ b/examples/ordermanagement/main.go @@ -88,7 +88,7 @@ func loadTupleSchema() error { // create rulesession and load rules in it func createAndLoadRuleSession() (model.RuleSession, error) { content := getFileContent(ruleDefinitionPath, ruleDefinitionRelativePath) - return ruleapi.GetOrCreateRuleSessionFromConfig("oms_session", string(content)) + return ruleapi.GetOrCreateRuleSessionFromConfig("oms_session", "", string(content)) } // Get file content diff --git a/examples/rulesapp/main.go b/examples/rulesapp/main.go index 9dc6ed5..8acf6c2 100644 --- a/examples/rulesapp/main.go +++ b/examples/rulesapp/main.go @@ -11,29 +11,40 @@ import ( ) func main() { + err := example(true) + if err != nil { + panic(err) + } +} - fmt.Println("** rulesapp: Example usage of the Rules module/API **") - +func example(redis bool) error { //Load the tuple descriptor file (relative to GOPATH) tupleDescAbsFileNm := common.GetPathForResource("examples/rulesapp/rulesapp.json", "./rulesapp.json") tupleDescriptor := common.FileToString(tupleDescAbsFileNm) - fmt.Printf("Loaded tuple descriptor: \n%s\n", tupleDescriptor) //First register the tuple descriptors err := model.RegisterTupleDescriptors(tupleDescriptor) if err != nil { - panic(err) + return err } //Create a RuleSession - rs, err := ruleapi.GetOrCreateRuleSession("asession") + store := "" + if redis { + store = "rsconfig.json" + } + rs, err := ruleapi.GetOrCreateRuleSession("asession", store) if err != nil { - panic(err) + return err } + events := make(map[string]int, 8) //// check for name "Bob" in n1 rule := ruleapi.NewRule("n1.name == Bob") - rule.AddCondition("c1", []string{"n1"}, checkForBob, nil) + err = rule.AddCondition("c1", []string{"n1"}, checkForBob, events) + if err != nil { + return err + } serviceCfg := &config.ServiceDescriptor{ Name: "checkForBobAction", Function: checkForBobAction, @@ -41,85 +52,99 @@ func main() { } aService, err := ruleapi.NewActionService(serviceCfg) if err != nil { - panic(err) + return err } rule.SetActionService(aService) - rule.SetContext("This is a test of context") + rule.SetContext(events) err = rs.AddRule(rule) if err != nil { - panic(err) + return err } - fmt.Printf("Rule added: [%s]\n", rule.GetName()) // check for name "Bob" in n1, match the "name" field in n2, // in effect, fire the rule when name field in both tuples is "Bob" rule2 := ruleapi.NewRule("n1.name == Bob && n1.name == n2.name") - rule2.AddCondition("c1", []string{"n1"}, checkForBob, nil) - rule2.AddCondition("c2", []string{"n1", "n2"}, checkSameNamesCondition, nil) + err = rule2.AddCondition("c1", []string{"n1"}, checkForBob, events) + if err != nil { + return err + } + err = rule2.AddCondition("c2", []string{"n1", "n2"}, checkSameNamesCondition, events) + if err != nil { + return err + } serviceCfg2 := &config.ServiceDescriptor{ Name: "checkSameNamesAction", Function: checkSameNamesAction, Type: "function", } - aService2, _ := ruleapi.NewActionService(serviceCfg2) + aService2, err := ruleapi.NewActionService(serviceCfg2) + if err != nil { + return err + } rule2.SetActionService(aService2) + rule2.SetContext(events) err = rs.AddRule(rule2) if err != nil { - panic(err) + return err } - fmt.Printf("Rule added: [%s]\n", rule2.GetName()) //set a transaction handler rs.RegisterRtcTransactionHandler(txHandler, nil) //Start the rule session err = rs.Start(nil) if err != nil { - panic(err) + return err } //Now assert a "n1" tuple - fmt.Println("Asserting n1 tuple with name=Tom") - t1, _ := model.NewTupleWithKeyValues("n1", "Tom") + t1, err := model.NewTupleWithKeyValues("n1", "Tom") + if err != nil { + return err + } t1.SetString(nil, "name", "Tom") err = rs.Assert(nil, t1) if err != nil { - panic(err) + return err } t11 := rs.GetStore().GetTupleByKey(t1.GetKey()) if t11 == nil { - panic(fmt.Errorf("Warn: Tuple should be in store[%s]", t11.GetKey())) + return fmt.Errorf("Warn: Tuple should be in store[%s]", t11.GetKey()) } //Now assert a "n1" tuple - fmt.Println("Asserting n1 tuple with name=Bob") - t2, _ := model.NewTupleWithKeyValues("n1", "Bob") + t2, err := model.NewTupleWithKeyValues("n1", "Bob") + if err != nil { + return err + } t2.SetString(nil, "name", "Bob") err = rs.Assert(nil, t2) if err != nil { - panic(err) + return err } //Now assert a "n2" tuple - fmt.Println("Asserting n2 tuple with name=Bob") - t3, _ := model.NewTupleWithKeyValues("n2", "Bob") + t3, err := model.NewTupleWithKeyValues("n2", "Bob") + if err != nil { + return err + } t3.SetString(nil, "name", "Bob") err = rs.Assert(nil, t3) if err != nil { - panic(err) + return err } //Retract tuples err = rs.Retract(nil, t1) if err != nil { - panic(err) + return err } err = rs.Retract(nil, t2) if err != nil { - panic(err) + return err } err = rs.Retract(nil, t3) if err != nil { - panic(err) + return err } //delete the rule @@ -128,52 +153,107 @@ func main() { //unregister the session, i.e; cleanup rs.Unregister() + if events["checkForBob"] != 4 { + return fmt.Errorf("checkForBob should have been called 4 times") + } + if events["checkForBobAction"] != 1 { + return fmt.Errorf("checkForBobAction should have been called once") + } + if events["checkSameNamesCondition"] != 1 { + return fmt.Errorf("checkSameNamesCondition should have been called once") + } + if events["checkSameNamesAction"] != 1 { + return fmt.Errorf("checkSameNamesAction should have been called once") + } + + return nil } func checkForBob(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { //This conditions filters on name="Bob" t1 := tuples["n1"] if t1 == nil { - fmt.Println("Should not get a nil tuple in FilterCondition! This is an error") return false } - name, _ := t1.GetString("name") + name, err := t1.GetString("name") + if err != nil { + return false + } + if name == "" { + return false + } + events := ctx.(map[string]int) + count := events["checkForBob"] + events["checkForBob"] = count + 1 return name == "Bob" } func checkForBobAction(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { - fmt.Printf("Rule fired: [%s]\n", ruleName) - fmt.Printf("Context is [%s]\n", ruleCtx) t1 := tuples["n1"] if t1 == nil { - fmt.Println("Should not get nil tuples here in JoinCondition1! This is an error") return } + name, err := t1.GetString("name") + if err != nil { + return + } + if name == "" { + return + } + events := ruleCtx.(map[string]int) + count := events["checkForBobAction"] + events["checkForBobAction"] = count + 1 } func checkSameNamesCondition(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { t1 := tuples["n1"] t2 := tuples["n2"] if t1 == nil || t2 == nil { - fmt.Println("Should not get nil tuples here in JoinCondition2! This is an error") return false } - name1, _ := t1.GetString("name") - name2, _ := t2.GetString("name") + name1, err := t1.GetString("name") + if err != nil { + return false + } + if name1 == "" { + return false + } + name2, err := t2.GetString("name") + if err != nil { + return false + } + if name2 == "" { + return false + } + events := ctx.(map[string]int) + count := events["checkSameNamesCondition"] + events["checkSameNamesCondition"] = count + 1 return name1 == name2 } func checkSameNamesAction(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { - fmt.Printf("Rule fired: [%s]\n", ruleName) t1 := tuples["n1"] t2 := tuples["n2"] if t1 == nil || t2 == nil { - fmt.Println("Should not get nil tuples here in Action! This is an error") return } - name1, _ := t1.GetString("name") - name2, _ := t2.GetString("name") - fmt.Printf("n1.name = [%s], n2.name = [%s]\n", name1, name2) + name1, err := t1.GetString("name") + if err != nil { + return + } + if name1 == "" { + return + } + name2, err := t2.GetString("name") + if err != nil { + return + } + if name2 == "" { + return + } + events := ruleCtx.(map[string]int) + count := events["checkSameNamesAction"] + events["checkSameNamesAction"] = count + 1 } func getFileContent(filePath string) string { diff --git a/examples/rulesapp/ruleapp_test.go b/examples/rulesapp/ruleapp_test.go index 7590694..9c0f31a 100644 --- a/examples/rulesapp/ruleapp_test.go +++ b/examples/rulesapp/ruleapp_test.go @@ -1,6 +1,8 @@ package main import ( + "os" + "os/exec" "strings" "testing" @@ -8,14 +10,43 @@ import ( "github.com/stretchr/testify/assert" ) -func TestRuleApp(t *testing.T) { - request := func() { - tests.Command("go", "run", "main.go") +var redis = false + +func TestMain(m *testing.M) { + code := m.Run() + if code != 0 { + os.Exit(code) } - output := tests.CaptureStdOutput(request) - var result string - if strings.Contains(output, "Rule fired") && strings.Contains(output, "Loaded tuple descriptor") { - result = "success" + + run := func() int { + command := exec.Command("docker", "run", "-p", "6379:6379", "-d", "redis") + hash, err := command.Output() + if err != nil { + panic(err) + } + tests.Pour("6379") + + defer func() { + command := exec.Command("docker", "stop", strings.TrimSpace(string(hash))) + err := command.Run() + if err != nil { + panic(err) + } + command = exec.Command("docker", "rm", strings.TrimSpace(string(hash))) + err = command.Run() + if err != nil { + panic(err) + } + tests.Drain("6379") + }() + + return m.Run() } - assert.Equal(t, "success", result) + redis = true + os.Exit(run()) +} + +func TestRuleApp(t *testing.T) { + err := example(redis) + assert.Nil(t, err) } diff --git a/examples/trackntrace/trackntrace_test.go b/examples/trackntrace/trackntrace_test.go index e0a34ad..2624e16 100644 --- a/examples/trackntrace/trackntrace_test.go +++ b/examples/trackntrace/trackntrace_test.go @@ -203,7 +203,7 @@ func TestSameTupleInstanceAssert(t *testing.T) { } func createRuleSessionAndRules(t *testing.T) (model.RuleSession, error) { - rs, _ := ruleapi.GetOrCreateRuleSession("asession") + rs, _ := ruleapi.GetOrCreateRuleSession("asession", "") tupleDescFileAbsPath := common.GetPathForResource("examples/trackntrace/trackntrace.json", "./trackntrace.json") diff --git a/redisutils/redisutil_test.go b/redisutils/redisutil_test.go index adb7772..ea370dd 100644 --- a/redisutils/redisutil_test.go +++ b/redisutils/redisutil_test.go @@ -3,11 +3,67 @@ package redisutils import ( "encoding/json" "fmt" - "github.com/gomodule/redigo/redis" + "net" + "os" + "os/exec" "strconv" + "strings" "testing" + "time" + + "github.com/gomodule/redigo/redis" ) +func Drain(port string) { + for { + conn, err := net.DialTimeout("tcp", net.JoinHostPort("", port), time.Second) + if conn != nil { + conn.Close() + } + if err != nil && strings.Contains(err.Error(), "connect: connection refused") { + break + } + } +} + +func Pour(port string) { + for { + conn, _ := net.Dial("tcp", net.JoinHostPort("", port)) + if conn != nil { + conn.Close() + break + } + } +} + +func TestMain(m *testing.M) { + run := func() int { + command := exec.Command("docker", "run", "-p", "6379:6379", "-d", "redis") + hash, err := command.Output() + if err != nil { + panic(err) + } + Pour("6379") + + defer func() { + command := exec.Command("docker", "stop", strings.TrimSpace(string(hash))) + err := command.Run() + if err != nil { + panic(err) + } + command = exec.Command("docker", "rm", strings.TrimSpace(string(hash))) + err = command.Run() + if err != nil { + panic(err) + } + Drain("6379") + }() + + return m.Run() + } + os.Exit(run()) +} + func Test_first(t *testing.T) { InitService(nil) diff --git a/rete/common/types.go b/rete/common/types.go index f2147c5..4b8d1c4 100644 --- a/rete/common/types.go +++ b/rete/common/types.go @@ -2,6 +2,7 @@ package common import ( "context" + "github.com/project-flogo/rules/common/model" ) @@ -25,7 +26,7 @@ type Network interface { //mode can be one of retract, modify, delete Retract(ctx context.Context, rs model.RuleSession, tuple model.Tuple, changedProps map[string]bool, mode RtcOprn) error - GetAssertedTuple(key model.TupleKey) model.Tuple + GetAssertedTuple(ctx context.Context, rs model.RuleSession, key model.TupleKey) model.Tuple RegisterRtcTransactionHandler(txnHandler model.RtcTransactionHandler, txnContext interface{}) //SetConfig(config map[string]string) //GetConfigValue(key string) string diff --git a/rete/conflict.go b/rete/conflict.go index 353b10d..8141df2 100644 --- a/rete/conflict.go +++ b/rete/conflict.go @@ -5,19 +5,14 @@ import ( "context" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/internal/types" ) -type conflictRes interface { - addAgendaItem(rule model.Rule, tupleMap map[model.TupleType]model.Tuple) - resolveConflict(ctx context.Context) - deleteAgendaFor(ctx context.Context, tuple model.Tuple, changeProps map[string]bool) -} - type conflictResImpl struct { agendaList list.List } -func newConflictRes() conflictRes { +func newConflictRes() types.ConflictRes { cr := conflictResImpl{} cr.initCR() return &cr @@ -27,7 +22,7 @@ func (cr *conflictResImpl) initCR() { cr.agendaList = list.List{} } -func (cr *conflictResImpl) addAgendaItem(rule model.Rule, tupleMap map[model.TupleType]model.Tuple) { +func (cr *conflictResImpl) AddAgendaItem(rule model.Rule, tupleMap map[model.TupleType]model.Tuple) { item := newAgendaItem(rule, tupleMap) v := rule.GetPriority() found := false @@ -44,7 +39,7 @@ func (cr *conflictResImpl) addAgendaItem(rule model.Rule, tupleMap map[model.Tup } } -func (cr *conflictResImpl) resolveConflict(ctx context.Context) { +func (cr *conflictResImpl) ResolveConflict(ctx context.Context) { var item agendaItem front := cr.agendaList.Front() @@ -57,25 +52,25 @@ func (cr *conflictResImpl) resolveConflict(ctx context.Context) { aService := item.getRule().GetActionService() if aService != nil { reteCtxV := getReteCtx(ctx) - aService.Execute(ctx, reteCtxV.getRuleSession(), item.getRule().GetName(), actionTuples, item.getRule().GetContext()) + aService.Execute(ctx, reteCtxV.GetRuleSession(), item.getRule().GetName(), actionTuples, item.getRule().GetContext()) } } reteCtxV := getReteCtx(ctx) - reteCtxV.addRuleModifiedToOpsList() + reteCtxV.AddRuleModifiedToOpsList() - reteCtxV.copyRuleModifiedToRtcModified() + reteCtxV.CopyRuleModifiedToRtcModified() //action scoped, clear it for the next action - reteCtxV.resetModified() + reteCtxV.ResetModified() if reteCtxV != nil { - opsFront := reteCtxV.getOpsList().Front() + opsFront := reteCtxV.GetOpsList().Front() for opsFront != nil { - opsVal := reteCtxV.getOpsList().Remove(opsFront) + opsVal := reteCtxV.GetOpsList().Remove(opsFront) oprn := opsVal.(opsEntry) oprn.execute(ctx) - opsFront = reteCtxV.getOpsList().Front() + opsFront = reteCtxV.GetOpsList().Front() } } @@ -83,12 +78,12 @@ func (cr *conflictResImpl) resolveConflict(ctx context.Context) { } reteCtxV := getReteCtx(ctx) - reteCtxV.normalize() + reteCtxV.Normalize() //reteCtxV.printRtcChangeList() } -func (cr *conflictResImpl) deleteAgendaFor(ctx context.Context, modifiedTuple model.Tuple, changeProps map[string]bool) { +func (cr *conflictResImpl) DeleteAgendaFor(ctx context.Context, modifiedTuple model.Tuple, changeProps map[string]bool) { hdlModified, _ := getOrCreateHandle(ctx, modifiedTuple) diff --git a/rete/context.go b/rete/context.go index d3acb27..f5cdd51 100644 --- a/rete/context.go +++ b/rete/context.go @@ -3,39 +3,17 @@ package rete import ( "container/list" "context" - "fmt" + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/internal/types" ) var reteCTXKEY = model.RetecontextKeyType{} -type reteCtx interface { - getConflictResolver() conflictRes - getOpsList() *list.List - getNetwork() *reteNetworkImpl - getRuleSession() model.RuleSession - OnValueChange(tuple model.Tuple, prop string) - - getRtcAdded() map[string]model.Tuple - getRtcModified() map[string]model.RtcModified - getRtcDeleted() map[string]model.Tuple - - addToRtcAdded(tuple model.Tuple) - addToRtcModified(tuple model.Tuple) - addToRtcDeleted(tuple model.Tuple) - addRuleModifiedToOpsList() - - normalize() - copyRuleModifiedToRtcModified() - resetModified() - - printRtcChangeList() -} - //store any context, may not know all keys upfront type reteCtxImpl struct { - cr conflictRes + cr types.ConflictRes opsList *list.List network *reteNetworkImpl rs model.RuleSession @@ -53,7 +31,7 @@ type reteCtxImpl struct { rtcModifyMap map[string]model.RtcModified } -func newReteCtxImpl(network *reteNetworkImpl, rs model.RuleSession) reteCtx { +func newReteCtxImpl(network *reteNetworkImpl, rs model.RuleSession) types.ReteCtx { reteCtxVal := reteCtxImpl{} reteCtxVal.cr = newConflictRes() reteCtxVal.opsList = list.New() @@ -66,26 +44,26 @@ func newReteCtxImpl(network *reteNetworkImpl, rs model.RuleSession) reteCtx { return &reteCtxVal } -func (rctx *reteCtxImpl) getConflictResolver() conflictRes { +func (rctx *reteCtxImpl) GetConflictResolver() types.ConflictRes { return rctx.cr } -func (rctx *reteCtxImpl) getOpsList() *list.List { +func (rctx *reteCtxImpl) GetOpsList() *list.List { return rctx.opsList } -func (rctx *reteCtxImpl) getNetwork() *reteNetworkImpl { +func (rctx *reteCtxImpl) GetNetwork() types.Network { return rctx.network } -func (rctx *reteCtxImpl) getRuleSession() model.RuleSession { +func (rctx *reteCtxImpl) GetRuleSession() model.RuleSession { return rctx.rs } func (rctx *reteCtxImpl) OnValueChange(tuple model.Tuple, prop string) { //if handle does not exist means its new - if nil != rctx.network.getHandle(tuple) { + if nil != rctx.network.getHandle(context.WithValue(context.Background(), reteCTXKEY, rctx), tuple) { rtcModified := rctx.modifyMap[tuple.GetKey().String()] if rtcModified == nil { rtcModified = NewRtcModified(tuple) @@ -95,35 +73,35 @@ func (rctx *reteCtxImpl) OnValueChange(tuple model.Tuple, prop string) { } } -func (rctx *reteCtxImpl) getRtcAdded() map[string]model.Tuple { +func (rctx *reteCtxImpl) GetRtcAdded() map[string]model.Tuple { return rctx.addMap } -func (rctx *reteCtxImpl) getRtcModified() map[string]model.RtcModified { +func (rctx *reteCtxImpl) GetRtcModified() map[string]model.RtcModified { return rctx.rtcModifyMap } -func (rctx *reteCtxImpl) getRtcDeleted() map[string]model.Tuple { +func (rctx *reteCtxImpl) GetRtcDeleted() map[string]model.Tuple { return rctx.deleteMap } -func (rctx *reteCtxImpl) addToRtcAdded(tuple model.Tuple) { +func (rctx *reteCtxImpl) AddToRtcAdded(tuple model.Tuple) { rctx.addMap[tuple.GetKey().String()] = tuple } -func (rctx *reteCtxImpl) addToRtcModified(tuple model.Tuple) { +func (rctx *reteCtxImpl) AddToRtcModified(tuple model.Tuple) { rctx.addMap[tuple.GetKey().String()] = tuple } -func (rctx *reteCtxImpl) addToRtcDeleted(tuple model.Tuple) { +func (rctx *reteCtxImpl) AddToRtcDeleted(tuple model.Tuple) { rctx.deleteMap[tuple.GetKey().String()] = tuple } -func (rctx *reteCtxImpl) addRuleModifiedToOpsList() { +func (rctx *reteCtxImpl) AddRuleModifiedToOpsList() { for _, rtcModified := range rctx.modifyMap { - rctx.getOpsList().PushBack(newModifyEntry(rtcModified.GetTuple(), rtcModified.GetModifiedProps())) + rctx.GetOpsList().PushBack(newModifyEntry(rtcModified.GetTuple(), rtcModified.GetModifiedProps())) } } -func (rctx *reteCtxImpl) normalize() { +func (rctx *reteCtxImpl) Normalize() { //remove from modify map, those in add map for k, _ := range rctx.addMap { @@ -135,49 +113,42 @@ func (rctx *reteCtxImpl) normalize() { } } -func (rctx *reteCtxImpl) copyRuleModifiedToRtcModified() { +func (rctx *reteCtxImpl) CopyRuleModifiedToRtcModified() { for k, v := range rctx.modifyMap { rctx.rtcModifyMap[k] = v } } -func (rctx *reteCtxImpl) resetModified() { +func (rctx *reteCtxImpl) ResetModified() { rctx.modifyMap = make(map[string]model.RtcModified) } -func (rctx *reteCtxImpl) printRtcChangeList() { - for k, _ := range rctx.getRtcAdded() { - +func (rctx *reteCtxImpl) PrintRtcChangeList() { + for k := range rctx.GetRtcAdded() { fmt.Printf("Added Tuple: [%s]\n", k) - } - for k, _ := range rctx.getRtcModified() { - + for k := range rctx.GetRtcModified() { fmt.Printf("Modified Tuple: [%s]\n", k) - } - for k, _ := range rctx.getRtcDeleted() { - + for k := range rctx.GetRtcDeleted() { fmt.Printf("Deleted Tuple: [%s]\n", k) - } - } -func getReteCtx(ctx context.Context) reteCtx { +func getReteCtx(ctx context.Context) types.ReteCtx { intr := ctx.Value(reteCTXKEY) if intr == nil { return nil } - return intr.(reteCtx) + return intr.(types.ReteCtx) } -func newReteCtx(ctx context.Context, network *reteNetworkImpl, rs model.RuleSession) (context.Context, reteCtx) { +func newReteCtx(ctx context.Context, network *reteNetworkImpl, rs model.RuleSession) (context.Context, types.ReteCtx) { reteCtxVar := newReteCtxImpl(network, rs) ctx = context.WithValue(ctx, reteCTXKEY, reteCtxVar) return ctx, reteCtxVar } -func getOrSetReteCtx(ctx context.Context, network *reteNetworkImpl, rs model.RuleSession) (reteCtx, bool, context.Context) { +func getOrSetReteCtx(ctx context.Context, network *reteNetworkImpl, rs model.RuleSession) (types.ReteCtx, bool, context.Context) { isRecursive := false newCtx := ctx reteCtxVar := getReteCtx(ctx) diff --git a/rete/internal/mem/mhandleservice.go b/rete/internal/mem/mhandleservice.go index 11d92a8..3c7bf76 100644 --- a/rete/internal/mem/mhandleservice.go +++ b/rete/internal/mem/mhandleservice.go @@ -1,6 +1,7 @@ package mem import ( + "context" "sync" "github.com/project-flogo/rules/common/model" @@ -34,13 +35,13 @@ func (hc *handleServiceImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle { return nil } -func (hc *handleServiceImpl) GetHandle(tuple model.Tuple) types.ReteHandle { +func (hc *handleServiceImpl) GetHandle(ctx context.Context, tuple model.Tuple) types.ReteHandle { hc.RLock() defer hc.RUnlock() return hc.allHandles[tuple.GetKey().String()] } -func (hc *handleServiceImpl) GetHandleByKey(key model.TupleKey) types.ReteHandle { +func (hc *handleServiceImpl) GetHandleByKey(ctx context.Context, key model.TupleKey) types.ReteHandle { hc.RLock() defer hc.RUnlock() return hc.allHandles[key.String()] diff --git a/rete/internal/mem/mjointableimpl.go b/rete/internal/mem/mjointableimpl.go index 306e015..f561a20 100644 --- a/rete/internal/mem/mjointableimpl.go +++ b/rete/internal/mem/mjointableimpl.go @@ -1,9 +1,10 @@ package mem import ( + "container/list" + "context" "sync" - "container/list" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/rete/internal/types" ) @@ -64,11 +65,11 @@ func (jt *joinTableImpl) GetRule() model.Rule { return jt.rule } -func (jt *joinTableImpl) GetRowIterator() types.JointableRowIterator { +func (jt *joinTableImpl) GetRowIterator(ctx context.Context) types.JointableRowIterator { return newRowIterator(jt) } -func (jt *joinTableImpl) GetRow(rowID int) types.JoinTableRow { +func (jt *joinTableImpl) GetRow(ctx context.Context, rowID int) types.JoinTableRow { jt.RLock() defer jt.RUnlock() return jt.table[rowID] @@ -78,8 +79,8 @@ func (jt *joinTableImpl) GetName() string { return jt.name } -func (jt *joinTableImpl) RemoveAllRows() { - rowIter := jt.GetRowIterator() +func (jt *joinTableImpl) RemoveAllRows(ctx context.Context) { + rowIter := jt.GetRowIterator(ctx) for rowIter.HasNext() { row := rowIter.Next() //first, from jTable, remove row diff --git a/rete/internal/mem/mjtrefsservice.go b/rete/internal/mem/mjtrefsservice.go index 97b0026..dce01e7 100644 --- a/rete/internal/mem/mjtrefsservice.go +++ b/rete/internal/mem/mjtrefsservice.go @@ -1,9 +1,10 @@ package mem import ( + "container/list" + "context" "sync" - "container/list" "github.com/project-flogo/rules/rete/internal/types" ) @@ -61,6 +62,7 @@ func (h *jtRefsServiceImpl) RemoveEntry(handle types.ReteHandle, jtName string, } type hdlRefsRowIterator struct { + ctx context.Context refs *jtRefsServiceImpl key string rows *jtRowsImpl @@ -82,7 +84,7 @@ func (ri *hdlRefsRowIterator) Next() (types.JoinTableRow, types.JoinTable) { defer ri.rows.RUnlock() jT := ri.nw.GetJtService().GetJoinTable(ri.rows.rows[rowID]) if jT != nil { - return jT.GetRow(rowID), jT + return jT.GetRow(ri.ctx, rowID), jT } return nil, jT } @@ -98,9 +100,10 @@ func (ri *hdlRefsRowIterator) Remove() { } } -func (h *jtRefsServiceImpl) GetRowIterator(handle types.ReteHandle) types.JointableIterator { +func (h *jtRefsServiceImpl) GetRowIterator(ctx context.Context, handle types.ReteHandle) types.JointableIterator { key := handle.GetTupleKey().String() ri := hdlRefsRowIterator{ + ctx: ctx, refs: h, key: key, nw: h.Nw, diff --git a/rete/internal/redis/rhandleservice.go b/rete/internal/redis/rhandleservice.go index b65aee3..8c1a876 100644 --- a/rete/internal/redis/rhandleservice.go +++ b/rete/internal/redis/rhandleservice.go @@ -1,6 +1,7 @@ package redis import ( + "context" "strconv" "github.com/project-flogo/rules/common/model" @@ -41,23 +42,17 @@ func (hc *handleServiceImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle { } -func (hc *handleServiceImpl) GetHandle(tuple model.Tuple) types.ReteHandle { - return hc.GetHandleByKey(tuple.GetKey()) +func (hc *handleServiceImpl) GetHandle(ctx context.Context, tuple model.Tuple) types.ReteHandle { + return hc.GetHandleByKey(ctx, tuple.GetKey()) } -func (hc *handleServiceImpl) GetHandleByKey(key model.TupleKey) types.ReteHandle { +func (hc *handleServiceImpl) GetHandleByKey(ctx context.Context, key model.TupleKey) types.ReteHandle { rkey := hc.prefix + key.String() m := redisutils.GetRedisHdl().HGetAll(rkey) if len(m) == 0 { return nil } - - tuple := hc.Nw.GetTupleStore().GetTupleByKey(key) - if tuple == nil { - //TODO: error handling - return nil - } status := types.ReteHandleStatusUnknown if value, ok := m["status"]; ok { if value, ok := value.(string); ok { @@ -72,6 +67,32 @@ func (hc *handleServiceImpl) GetHandleByKey(key model.TupleKey) types.ReteHandle } else { panic("missing status") } + + var tuple model.Tuple + if ctx != nil { + if value := ctx.Value(model.RetecontextKeyType{}); value != nil { + if value, ok := value.(types.ReteCtx); ok { + if modified := value.GetRtcModified(); modified != nil { + if value := modified[key.String()]; value != nil { + tuple = value.GetTuple() + } + } + if tuple == nil { + if added := value.GetRtcAdded(); added != nil { + tuple = added[key.String()] + } + } + } + } + } + if tuple == nil { + tuple = hc.Nw.GetTupleStore().GetTupleByKey(key) + } + if tuple == nil { + //TODO: error handling + return nil + } + h := newReteHandleImpl(hc.GetNw(), tuple, rkey, status) return h } diff --git a/rete/internal/redis/rjointableimpl.go b/rete/internal/redis/rjointableimpl.go index 4a902bf..e66186e 100644 --- a/rete/internal/redis/rjointableimpl.go +++ b/rete/internal/redis/rjointableimpl.go @@ -1,10 +1,12 @@ package redis import ( + "context" + "strconv" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/redisutils" "github.com/project-flogo/rules/rete/internal/types" - "strconv" ) type joinTableImpl struct { @@ -41,15 +43,15 @@ func (jt *joinTableImpl) AddRow(handles []types.ReteHandle) types.JoinTableRow { } func (jt *joinTableImpl) RemoveRow(rowID int) types.JoinTableRow { - row := jt.GetRow(rowID) + row := jt.GetRow(nil, rowID) hdl := redisutils.GetRedisHdl() rowId := strconv.Itoa(rowID) hdl.HDel(jt.jtKey, rowId) return row } -func (jt *joinTableImpl) RemoveAllRows() { - rowIter := jt.GetRowIterator() +func (jt *joinTableImpl) RemoveAllRows(ctx context.Context) { + rowIter := jt.GetRowIterator(ctx) for rowIter.HasNext() { row := rowIter.Next() //first, from jTable, remove row @@ -71,15 +73,15 @@ func (jt *joinTableImpl) GetRule() model.Rule { return jt.rule } -func (jt *joinTableImpl) GetRowIterator() types.JointableRowIterator { - return newRowIterator(jt) +func (jt *joinTableImpl) GetRowIterator(ctx context.Context) types.JointableRowIterator { + return newRowIterator(ctx, jt) } -func (jt *joinTableImpl) GetRow(rowID int) types.JoinTableRow { +func (jt *joinTableImpl) GetRow(ctx context.Context, rowID int) types.JoinTableRow { hdl := redisutils.GetRedisHdl() key := hdl.HGet(jt.jtKey, strconv.Itoa(rowID)) rowId := strconv.Itoa(rowID) - return createRow(jt.name, rowId, key.(string), jt.Nw) + return createRow(ctx, jt.name, rowId, key.(string), jt.Nw) } func (jt *joinTableImpl) GetName() string { @@ -87,15 +89,17 @@ func (jt *joinTableImpl) GetName() string { } type rowIteratorImpl struct { + ctx context.Context iter *redisutils.MapIterator jtName string nw types.Network curr types.JoinTableRow } -func newRowIterator(jTable types.JoinTable) types.JointableRowIterator { +func newRowIterator(ctx context.Context, jTable types.JoinTable) types.JointableRowIterator { key := jTable.GetNw().GetPrefix() + ":jt:" + jTable.GetName() ri := rowIteratorImpl{} + ri.ctx = ctx ri.iter = redisutils.GetRedisHdl().GetMapIterator(key) ri.nw = jTable.GetNw() ri.jtName = jTable.GetName() @@ -109,7 +113,7 @@ func (ri *rowIteratorImpl) HasNext() bool { func (ri *rowIteratorImpl) Next() types.JoinTableRow { rowId, key := ri.iter.Next() tupleKeyStr := key.(string) - ri.curr = createRow(ri.jtName, rowId, tupleKeyStr, ri.nw) + ri.curr = createRow(ri.ctx, ri.jtName, rowId, tupleKeyStr, ri.nw) return ri.curr } diff --git a/rete/internal/redis/rjointablerowimpl.go b/rete/internal/redis/rjointablerowimpl.go index 044e5ce..4976566 100644 --- a/rete/internal/redis/rjointablerowimpl.go +++ b/rete/internal/redis/rjointablerowimpl.go @@ -1,6 +1,7 @@ package redis import ( + "context" "strconv" "strings" @@ -52,14 +53,33 @@ func (jtr *joinTableRowImpl) GetHandles() []types.ReteHandle { return jtr.handles } -func createRow(jtKey string, rowID string, key string, nw types.Network) types.JoinTableRow { +func createRow(ctx context.Context, jtKey string, rowID string, key string, nw types.Network) types.JoinTableRow { values := strings.Split(key, ",") handles := []types.ReteHandle{} for _, key := range values { tupleKey := model.FromStringKey(key) - tuple := nw.GetTupleStore().GetTupleByKey(tupleKey) + var tuple model.Tuple + if ctx != nil { + if value := ctx.Value(model.RetecontextKeyType{}); value != nil { + if value, ok := value.(types.ReteCtx); ok { + if modified := value.GetRtcModified(); modified != nil { + if value := modified[tupleKey.String()]; value != nil { + tuple = value.GetTuple() + } + } + if tuple == nil { + if added := value.GetRtcAdded(); added != nil { + tuple = added[tupleKey.String()] + } + } + } + } + } + if tuple == nil { + tuple = nw.GetTupleStore().GetTupleByKey(tupleKey) + } handle := newReteHandleImpl(nw, tuple, "", types.ReteHandleStatusUnknown) handles = append(handles, handle) } diff --git a/rete/internal/redis/rjtrefsservice.go b/rete/internal/redis/rjtrefsservice.go index 64fcfc0..473f7fe 100644 --- a/rete/internal/redis/rjtrefsservice.go +++ b/rete/internal/redis/rjtrefsservice.go @@ -1,9 +1,11 @@ package redis import ( + "context" + "strconv" + "github.com/project-flogo/rules/redisutils" "github.com/project-flogo/rules/rete/internal/types" - "strconv" ) type jtRefsServiceImpl struct { @@ -40,6 +42,7 @@ func (h *jtRefsServiceImpl) RemoveEntry(handle types.ReteHandle, jtName string, } type hdlRefsRowIteratorImpl struct { + ctx context.Context key string iter *redisutils.MapIterator nw types.Network @@ -54,7 +57,7 @@ func (r *hdlRefsRowIteratorImpl) Next() (types.JoinTableRow, types.JoinTable) { rowID, _ := strconv.Atoi(rowIDStr) jT := r.nw.GetJtService().GetJoinTable(jtName.(string)) if jT != nil { - return jT.GetRow(rowID), jT + return jT.GetRow(r.ctx, rowID), jT } return nil, jT } @@ -63,8 +66,9 @@ func (r *hdlRefsRowIteratorImpl) Remove() { r.iter.Remove() } -func (h *jtRefsServiceImpl) GetRowIterator(handle types.ReteHandle) types.JointableIterator { +func (h *jtRefsServiceImpl) GetRowIterator(ctx context.Context, handle types.ReteHandle) types.JointableIterator { r := hdlRefsRowIteratorImpl{} + r.ctx = ctx r.nw = h.Nw r.key = h.Nw.GetPrefix() + ":rtbls:" + handle.GetTupleKey().String() hdl := redisutils.GetRedisHdl() diff --git a/rete/internal/redis/rretehandle.go b/rete/internal/redis/rretehandle.go index 57bd2c2..005f86c 100644 --- a/rete/internal/redis/rretehandle.go +++ b/rete/internal/redis/rretehandle.go @@ -33,7 +33,6 @@ func (hdl *reteHandleImpl) SetTuple(tuple model.Tuple) { func (hdl *reteHandleImpl) initHandleImpl(nw types.Network, tuple model.Tuple, key string, status types.ReteHandleStatus) { hdl.SetID(nw) hdl.SetTuple(tuple) - hdl.tupleKey = tuple.GetKey() hdl.key = key hdl.status = status } @@ -62,6 +61,6 @@ func (hdl *reteHandleImpl) AddJoinTableRowRef(joinTableRowVar types.JoinTableRow } func (hdl *reteHandleImpl) GetRefTableIterator() types.JointableIterator { - refTblIterator := hdl.Nw.GetJtRefService().GetRowIterator(hdl) + refTblIterator := hdl.Nw.GetJtRefService().GetRowIterator(nil, hdl) return refTblIterator } diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index e87fc5b..8fd8c01 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -1,12 +1,18 @@ package types import ( + "container/list" + "context" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/rete/common" ) type Network interface { common.Network + GetOrCreateHandle(ctx context.Context, tuple model.Tuple) (ReteHandle, bool) + AssertInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) error + RetractInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) error GetPrefix() string GetIdGenService() IdGen GetJtService() JtService @@ -15,6 +21,35 @@ type Network interface { GetTupleStore() model.TupleStore } +type ConflictRes interface { + AddAgendaItem(rule model.Rule, tupleMap map[model.TupleType]model.Tuple) + ResolveConflict(ctx context.Context) + DeleteAgendaFor(ctx context.Context, tuple model.Tuple, changeProps map[string]bool) +} + +type ReteCtx interface { + GetConflictResolver() ConflictRes + GetOpsList() *list.List + GetNetwork() Network + GetRuleSession() model.RuleSession + OnValueChange(tuple model.Tuple, prop string) + + GetRtcAdded() map[string]model.Tuple + GetRtcModified() map[string]model.RtcModified + GetRtcDeleted() map[string]model.Tuple + + AddToRtcAdded(tuple model.Tuple) + AddToRtcModified(tuple model.Tuple) + AddToRtcDeleted(tuple model.Tuple) + AddRuleModifiedToOpsList() + + Normalize() + CopyRuleModifiedToRtcModified() + ResetModified() + + PrintRtcChangeList() +} + type JoinTable interface { NwElemId GetName() string @@ -22,11 +57,11 @@ type JoinTable interface { AddRow(handles []ReteHandle) JoinTableRow RemoveRow(rowID int) JoinTableRow - GetRow(rowID int) JoinTableRow - GetRowIterator() JointableRowIterator + GetRow(ctx context.Context, rowID int) JoinTableRow + GetRowIterator(ctx context.Context) JointableRowIterator GetRowCount() int - RemoveAllRows() //used when join table needs to be deleted + RemoveAllRows(ctx context.Context) //used when join table needs to be deleted } type JoinTableRow interface { @@ -42,6 +77,8 @@ const ( ReteHandleStatusCreating ReteHandleStatusCreated ReteHandleStatusDeleting + ReteHandleStatusRetracting + ReteHandleStatusRetracted ) type ReteHandle interface { @@ -57,7 +94,7 @@ type JtRefsService interface { NwService AddEntry(handle ReteHandle, jtName string, rowID int) RemoveEntry(handle ReteHandle, jtName string, rowID int) - GetRowIterator(handle ReteHandle) JointableIterator + GetRowIterator(ctx context.Context, handle ReteHandle) JointableIterator } type JtService interface { @@ -69,8 +106,8 @@ type JtService interface { type HandleService interface { NwService RemoveHandle(tuple model.Tuple) ReteHandle - GetHandle(tuple model.Tuple) ReteHandle - GetHandleByKey(key model.TupleKey) ReteHandle + GetHandle(ctx context.Context, tuple model.Tuple) ReteHandle + GetHandleByKey(ctx context.Context, key model.TupleKey) ReteHandle GetOrCreateHandle(nw Network, tuple model.Tuple) (ReteHandle, bool) } diff --git a/rete/joinnode.go b/rete/joinnode.go index e4f2fc7..48e34fd 100644 --- a/rete/joinnode.go +++ b/rete/joinnode.go @@ -2,6 +2,7 @@ package rete import ( "context" + "fmt" "strconv" "github.com/project-flogo/rules/common/model" @@ -179,13 +180,13 @@ func (jn *joinNodeImpl) assertFromRight(ctx context.Context, handles []types.Ret //tupleTableRow := newJoinTableRow(handles, jn.nw.incrementAndGetId()) jn.rightTable.AddRow(handles) //TODO: rete listeners etc. - rIterator := jn.leftTable.GetRowIterator() + rIterator := jn.leftTable.GetRowIterator(ctx) LOOP: for rIterator.HasNext() { tupleTableRowLeft := rIterator.Next() handles := tupleTableRowLeft.GetHandles() for _, handle := range handles { - if jn.GetNw().GetHandleService().GetHandle(handle.GetTuple()) == nil { + if jn.GetNw().GetHandleService().GetHandle(ctx, handle.GetTuple()) == nil { rIterator.Remove() for _, otherHdl := range handles { jn.GetNw().GetJtRefService().RemoveEntry(otherHdl, jn.leftTable.GetName(), tupleTableRowLeft.GetID()) @@ -246,13 +247,14 @@ func (jn *joinNodeImpl) assertFromLeft(ctx context.Context, handles []types.Rete //tupleTableRow := newJoinTableRow(handles, jn.nw.incrementAndGetId()) jn.leftTable.AddRow(handles) //TODO: rete listeners etc. - rIterator := jn.rightTable.GetRowIterator() + rIterator := jn.rightTable.GetRowIterator(ctx) LOOP: for rIterator.HasNext() { tupleTableRowRight := rIterator.Next() handles := tupleTableRowRight.GetHandles() for _, handle := range handles { - if jn.GetNw().GetHandleService().GetHandle(handle.GetTuple()) == nil { + fmt.Println("handle", handle.GetTupleKey(), handle.GetTuple()) + if jn.GetNw().GetHandleService().GetHandle(ctx, handle.GetTuple()) == nil { rIterator.Remove() for _, otherHdl := range handles { jn.GetNw().GetJtRefService().RemoveEntry(otherHdl, jn.rightTable.GetName(), tupleTableRowRight.GetID()) diff --git a/rete/network.go b/rete/network.go index 9610832..20008b2 100644 --- a/rete/network.go +++ b/rete/network.go @@ -191,8 +191,8 @@ func (nw *reteNetworkImpl) RemoveRule(ruleName string) model.Rule { case *joinNodeImpl: //nw.removeRefsFromReteHandles(nodeImpl.leftTable) //nw.removeRefsFromReteHandles(nodeImpl.rightTable) - nodeImpl.leftTable.RemoveAllRows() - nodeImpl.rightTable.RemoveAllRows() + nodeImpl.leftTable.RemoveAllRows(nil) + nodeImpl.rightTable.RemoveAllRows(nil) } } } @@ -216,11 +216,11 @@ func (nw *reteNetworkImpl) removeRefsFromReteHandles(joinTableVar types.JoinTabl if joinTableVar == nil { return } - rIterator := joinTableVar.GetRowIterator() + rIterator := joinTableVar.GetRowIterator(nil) for rIterator.HasNext() { tableRow := rIterator.Next() for _, handle := range tableRow.GetHandles() { - nw.removeJoinTableRowRefs(handle, nil) + nw.removeJoinTableRowRefs(nil, handle, nil) } } } @@ -577,27 +577,27 @@ func (nw *reteNetworkImpl) Assert(ctx context.Context, rs model.RuleSession, tup nw.RLock() defer nw.RUnlock() - err := nw.assertInternal(newCtx, tuple, changedProps, mode) + err := nw.AssertInternal(newCtx, tuple, changedProps, mode) if err != nil { return err } - reteCtxVar.getConflictResolver().resolveConflict(newCtx) + reteCtxVar.GetConflictResolver().ResolveConflict(newCtx) //if Timeout is 0, remove it from rete td := model.GetTupleDescriptor(tuple.GetTupleType()) if td != nil { if td.TTLInSeconds == 0 { //remove immediately. - nw.removeTupleFromRete(tuple) + nw.removeTupleFromRete(newCtx, tuple) } else if td.TTLInSeconds > 0 { // TTL for the tuple type, after that, remove it from RETE time.AfterFunc(time.Second*time.Duration(td.TTLInSeconds), func() { nw.RLock() defer nw.RUnlock() - nw.removeTupleFromRete(tuple) + nw.removeTupleFromRete(nil, tuple) }) } //else, its -ve and means, never expire } if nw.txnHandler != nil { - rtcTxn := newRtcTxn(reteCtxVar.getRtcAdded(), reteCtxVar.getRtcModified(), reteCtxVar.getRtcDeleted()) + rtcTxn := newRtcTxn(reteCtxVar.GetRtcAdded(), reteCtxVar.GetRtcModified(), reteCtxVar.GetRtcDeleted()) for i, txnHandler := range nw.txnHandler { txnHandler(newCtx, rs, rtcTxn, nw.txnContext[i]) } @@ -605,14 +605,14 @@ func (nw *reteNetworkImpl) Assert(ctx context.Context, rs model.RuleSession, tup return nil } - reteCtxVar.getOpsList().PushBack(newAssertEntry(tuple, changedProps, mode)) + reteCtxVar.GetOpsList().PushBack(newAssertEntry(tuple, changedProps, mode)) return nil } -func (nw *reteNetworkImpl) removeTupleFromRete(tuple model.Tuple) { +func (nw *reteNetworkImpl) removeTupleFromRete(ctx context.Context, tuple model.Tuple) { reteHandle := nw.handleService.RemoveHandle(tuple) if reteHandle != nil { - nw.removeJoinTableRowRefs(reteHandle, nil) + nw.removeJoinTableRowRefs(ctx, reteHandle, nil) } } @@ -625,59 +625,65 @@ func (nw *reteNetworkImpl) Retract(ctx context.Context, rs model.RuleSession, tu if !isRecursive { nw.RLock() defer nw.RUnlock() - err := nw.retractInternal(ctx, tuple, changedProps, mode) + err := nw.RetractInternal(ctx, tuple, changedProps, mode) if err != nil { return err } if nw.txnHandler != nil && mode == common.DELETE { - rtcTxn := newRtcTxn(reteCtxVar.getRtcAdded(), reteCtxVar.getRtcModified(), reteCtxVar.getRtcDeleted()) + rtcTxn := newRtcTxn(reteCtxVar.GetRtcAdded(), reteCtxVar.GetRtcModified(), reteCtxVar.GetRtcDeleted()) for i, txnHandler := range nw.txnHandler { txnHandler(ctx, rs, rtcTxn, nw.txnContext[i]) } } } else { - reteCtxVar.getOpsList().PushBack(newDeleteEntry(tuple, mode, changedProps)) + reteCtxVar.GetOpsList().PushBack(newDeleteEntry(tuple, mode, changedProps)) } return nil } -func (nw *reteNetworkImpl) retractInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) error { - handle := nw.handleService.GetHandle(tuple) +func (nw *reteNetworkImpl) RetractInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) error { + handle := nw.handleService.GetHandle(ctx, tuple) if handle == nil { return fmt.Errorf("Tuple with key [%s] doesn't exist", tuple.GetKey().String()) } else if handle.GetStatus() != types.ReteHandleStatusCreated { return fmt.Errorf("Tuple with key [%s] is being asserted or deleted: %d", tuple.GetKey().String(), handle.GetStatus()) } - handle.SetStatus(types.ReteHandleStatusDeleting) + if mode == common.DELETE { + handle.SetStatus(types.ReteHandleStatusDeleting) + } else if mode == common.RETRACT { + handle.SetStatus(types.ReteHandleStatusRetracting) + } if ctx == nil { ctx = context.Background() } - rCtx, _, _ := getOrSetReteCtx(ctx, nw, nil) + rCtx, _, newCtx := getOrSetReteCtx(ctx, nw, nil) - nw.removeJoinTableRowRefs(handle, changedProps) + nw.removeJoinTableRowRefs(newCtx, handle, changedProps) // add it to the delete list if mode == common.DELETE { - rCtx.addToRtcDeleted(tuple) + rCtx.AddToRtcDeleted(tuple) + nw.handleService.RemoveHandle(tuple) + } else if mode == common.RETRACT { + handle.SetStatus(types.ReteHandleStatusRetracted) } - nw.handleService.RemoveHandle(tuple) - return nil } -func (nw *reteNetworkImpl) GetAssertedTuple(key model.TupleKey) model.Tuple { - reteHandle := nw.handleService.GetHandleByKey(key) +func (nw *reteNetworkImpl) GetAssertedTuple(ctx context.Context, rs model.RuleSession, key model.TupleKey) model.Tuple { + _, _, newCtx := getOrSetReteCtx(ctx, nw, rs) + reteHandle := nw.handleService.GetHandleByKey(newCtx, key) if reteHandle != nil { return reteHandle.GetTuple() } return nil } -func (nw *reteNetworkImpl) assertInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) error { +func (nw *reteNetworkImpl) AssertInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) error { if mode == common.ADD { - handle, exists := nw.getOrCreateHandle(ctx, tuple) - if exists { + handle, exists := nw.GetOrCreateHandle(ctx, tuple) + if exists && handle.GetStatus() != types.ReteHandleStatusRetracted { return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) } defer handle.SetStatus(types.ReteHandleStatusCreated) @@ -691,23 +697,22 @@ func (nw *reteNetworkImpl) assertInternal(ctx context.Context, tuple model.Tuple } td := model.GetTupleDescriptor(tuple.GetTupleType()) if td != nil { - if td.TTLInSeconds != 0 && mode == common.ADD { + if /*td.TTLInSeconds != 0 && */ mode == common.ADD { rCtx := getReteCtx(ctx) if rCtx != nil { - rCtx.addToRtcAdded(tuple) + rCtx.AddToRtcAdded(tuple) } } } - return nil } -func (nw *reteNetworkImpl) getOrCreateHandle(ctx context.Context, tuple model.Tuple) (types.ReteHandle, bool) { +func (nw *reteNetworkImpl) GetOrCreateHandle(ctx context.Context, tuple model.Tuple) (types.ReteHandle, bool) { return nw.handleService.GetOrCreateHandle(nw, tuple) } -func (nw *reteNetworkImpl) getHandle(tuple model.Tuple) types.ReteHandle { - h := nw.handleService.GetHandleByKey(tuple.GetKey()) +func (nw *reteNetworkImpl) getHandle(ctx context.Context, tuple model.Tuple) types.ReteHandle { + h := nw.handleService.GetHandleByKey(ctx, tuple.GetKey()) return h } @@ -738,14 +743,14 @@ func (nw *reteNetworkImpl) GetTupleStore() model.TupleStore { func getOrCreateHandle(ctx context.Context, tuple model.Tuple) (types.ReteHandle, bool) { reteCtxVar := getReteCtx(ctx) - return reteCtxVar.getNetwork().getOrCreateHandle(ctx, tuple) + return reteCtxVar.GetNetwork().GetOrCreateHandle(ctx, tuple) } -func (nw *reteNetworkImpl) removeJoinTableRowRefs(hdl types.ReteHandle, changedProps map[string]bool) { +func (nw *reteNetworkImpl) removeJoinTableRowRefs(ctx context.Context, hdl types.ReteHandle, changedProps map[string]bool) { tuple := hdl.GetTuple() alias := tuple.GetTupleType() - hdlTblIter := nw.jtRefsService.GetRowIterator(hdl) + hdlTblIter := nw.jtRefsService.GetRowIterator(ctx, hdl) for hdlTblIter.HasNext() { row, joinTable := hdlTblIter.Next() if row == nil || joinTable == nil { diff --git a/rete/opsList.go b/rete/opsList.go index 23ce989..994d8ef 100644 --- a/rete/opsList.go +++ b/rete/opsList.go @@ -37,7 +37,7 @@ func newAssertEntry(tuple model.Tuple, changeProps map[string]bool, mode common. func (ai *assertEntryImpl) execute(ctx context.Context) { reteCtx := getReteCtx(ctx) - reteCtx.getNetwork().assertInternal(ctx, ai.tuple, ai.changeProps, ai.mode) + reteCtx.GetNetwork().AssertInternal(ctx, ai.tuple, ai.changeProps, ai.mode) } //Modify Entry @@ -59,9 +59,9 @@ func newModifyEntry(tuple model.Tuple, changeProps map[string]bool) modifyEntry func (me *modifyEntryImpl) execute(ctx context.Context) { reteCtx := getReteCtx(ctx) - reteCtx.getConflictResolver().deleteAgendaFor(ctx, me.tuple, me.changeProps) - reteCtx.getNetwork().Retract(ctx, reteCtx.getRuleSession(), me.tuple, me.changeProps, common.MODIFY) - reteCtx.getNetwork().Assert(ctx, reteCtx.getRuleSession(), me.tuple, me.changeProps, common.MODIFY) + reteCtx.GetConflictResolver().DeleteAgendaFor(ctx, me.tuple, me.changeProps) + reteCtx.GetNetwork().Retract(ctx, reteCtx.GetRuleSession(), me.tuple, me.changeProps, common.MODIFY) + reteCtx.GetNetwork().Assert(ctx, reteCtx.GetRuleSession(), me.tuple, me.changeProps, common.MODIFY) } //Delete Entry @@ -85,6 +85,6 @@ func newDeleteEntry(tuple model.Tuple, mode common.RtcOprn, changeProps map[stri func (de *deleteEntryImpl) execute(ctx context.Context) { reteCtx := getReteCtx(ctx) - reteCtx.getConflictResolver().deleteAgendaFor(ctx, de.tuple, de.changeProps) - reteCtx.getNetwork().retractInternal(ctx, de.tuple, de.changeProps, de.mode) + reteCtx.GetConflictResolver().DeleteAgendaFor(ctx, de.tuple, de.changeProps) + reteCtx.GetNetwork().RetractInternal(ctx, de.tuple, de.changeProps, de.mode) } diff --git a/rete/rulenode.go b/rete/rulenode.go index 56aec0f..8e42743 100644 --- a/rete/rulenode.go +++ b/rete/rulenode.go @@ -36,9 +36,9 @@ func (rn *ruleNodeImpl) assertObjects(ctx context.Context, handles []types.ReteH tupleMap := copyIntoTupleMap(handles) - cr := getReteCtx(ctx).getConflictResolver() + cr := getReteCtx(ctx).GetConflictResolver() - cr.addAgendaItem(rn.getRule(), tupleMap) + cr.AddAgendaItem(rn.getRule(), tupleMap) } diff --git a/ruleaction/action.go b/ruleaction/action.go index 855395c..64bdae1 100644 --- a/ruleaction/action.go +++ b/ruleaction/action.go @@ -107,7 +107,7 @@ func (f *ActionFactory) New(cfg *action.Config) (action.Action, error) { if err != nil { return nil, fmt.Errorf("failed to marshall RuleSessionDescriptor : %s", err.Error()) } - ruleAction.rs, err = ruleapi.GetOrCreateRuleSessionFromConfig(settings.RuleSessionURI, string(ruleCollectionJSON)) + ruleAction.rs, err = ruleapi.GetOrCreateRuleSessionFromConfig(settings.RuleSessionURI, "", string(ruleCollectionJSON)) if err != nil { return nil, fmt.Errorf("failed to create rulesession for %s\n %s", settings.RuleSessionURI, err.Error()) diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index 128b231..ae97aee 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -43,12 +43,12 @@ func ClearSessions() { }) } -func GetOrCreateRuleSession(name string) (model.RuleSession, error) { +func GetOrCreateRuleSession(name, config string) (model.RuleSession, error) { if name == "" { return nil, errors.New("RuleSession name cannot be empty") } rs := rulesessionImpl{} - rs.loadStoreConfig() + rs.loadStoreConfig(config) rs.initRuleSession(name) rs1, _ := sessionMap.LoadOrStore(name, &rs) @@ -56,8 +56,8 @@ func GetOrCreateRuleSession(name string) (model.RuleSession, error) { } // GetOrCreateRuleSessionFromConfig returns rule session from created from config -func GetOrCreateRuleSessionFromConfig(name string, jsonConfig string) (model.RuleSession, error) { - rs, err := GetOrCreateRuleSession(name) +func GetOrCreateRuleSessionFromConfig(name, store, jsonConfig string) (model.RuleSession, error) { + rs, err := GetOrCreateRuleSession(name, store) if err != nil { return nil, err @@ -117,14 +117,13 @@ func GetOrCreateRuleSessionFromConfig(name string, jsonConfig string) (model.Rul return rs, nil } -func (rs *rulesessionImpl) loadStoreConfig() { - storeConfigFileName := "rsconfig.json" - _, err := os.Stat(storeConfigFileName) +func (rs *rulesessionImpl) loadStoreConfig(name string) { + _, err := os.Stat(name) if err != nil { // TO DO -- get the config from env or assign it as empty json rs.storeConfig = "{}" } else { - rs.storeConfig = utils.FileToString(storeConfigFileName) + rs.storeConfig = utils.FileToString(name) } } @@ -258,8 +257,8 @@ func (rs *rulesessionImpl) Start(startupCtx map[string]interface{}) error { return nil } -func (rs *rulesessionImpl) GetAssertedTuple(key model.TupleKey) model.Tuple { - return rs.reteNetwork.GetAssertedTuple(key) +func (rs *rulesessionImpl) GetAssertedTuple(ctx context.Context, key model.TupleKey) model.Tuple { + return rs.reteNetwork.GetAssertedTuple(ctx, rs, key) } func (rs *rulesessionImpl) RegisterRtcTransactionHandler(txnHandler model.RtcTransactionHandler, txnContext interface{}) { diff --git a/ruleapi/tests/common.go b/ruleapi/tests/common.go index d202ef1..e76877f 100644 --- a/ruleapi/tests/common.go +++ b/ruleapi/tests/common.go @@ -24,7 +24,13 @@ import ( "github.com/stretchr/testify/assert" ) +var ( + redis = false + done = false +) + func createRuleSession() (model.RuleSession, error) { + done = false tupleDescFileAbsPath := common.GetPathForResource("ruleapi/tests/tests.json", "./tests.json") dat, err := ioutil.ReadFile(tupleDescFileAbsPath) @@ -36,7 +42,26 @@ func createRuleSession() (model.RuleSession, error) { return nil, err } - return ruleapi.GetOrCreateRuleSession("test") + store := "" + if redis { + store = "rsconfig.json" + } + return ruleapi.GetOrCreateRuleSession("test", store) +} + +func deleteRuleSession(t *testing.T, session model.RuleSession, tuples ...model.Tuple) { + done = true + defer session.Unregister() + rules := session.GetRules() + for _, rule := range rules { + session.DeleteRule(rule.GetName()) + } + for _, tuple := range tuples { + err := session.Delete(nil, tuple) + if err != nil { + t.Fatal(err) + } + } } //conditions and actions diff --git a/ruleapi/tests/expr_1_test.go b/ruleapi/tests/expr_1_test.go index b9055a6..9c7d23f 100644 --- a/ruleapi/tests/expr_1_test.go +++ b/ruleapi/tests/expr_1_test.go @@ -16,29 +16,70 @@ func Test_1_Expr(t *testing.T) { t.Fatal(err) } r1 := ruleapi.NewRule("r1") - r1.AddExprCondition("c1", "$.t2.p2 > $.t1.p1", nil) + err = r1.AddExprCondition("c1", "$.t2.p2 > $.t1.p1", nil) + if err != nil { + t.Fatal(err) + } r1.SetActionService(createActionServiceFromFunction(t, a1)) r1.SetContext(actionCount) - rs.AddRule(r1) - rs.Start(nil) + err = rs.AddRule(r1) + if err != nil { + t.Fatal(err) + } + err = rs.Start(nil) + if err != nil { + t.Fatal(err) + } var ctx context.Context - t1, _ := model.NewTupleWithKeyValues("t1", "t1") - t1.SetInt(nil, "p1", 1) - t1.SetDouble(nil, "p2", 1.3) - t1.SetString(nil, "p3", "t3") + t1, err := model.NewTupleWithKeyValues("t1", "t1") + if err != nil { + t.Fatal(err) + } + err = t1.SetInt(nil, "p1", 1) + if err != nil { + t.Fatal(err) + } + err = t1.SetDouble(nil, "p2", 1.3) + if err != nil { + t.Fatal(err) + } + err = t1.SetString(nil, "p3", "t3") + if err != nil { + t.Fatal(err) + } ctx = context.WithValue(context.TODO(), TestKey{}, t) - rs.Assert(ctx, t1) + err = rs.Assert(ctx, t1) + if err != nil { + t.Fatal(err) + } - t2, _ := model.NewTupleWithKeyValues("t2", "t2") - t2.SetInt(nil, "p1", 1) - t2.SetDouble(nil, "p2", 1.0001) - t2.SetString(nil, "p3", "t3") + t2, err := model.NewTupleWithKeyValues("t2", "t2") + if err != nil { + t.Fatal(err) + } + err = t2.SetInt(nil, "p1", 1) + if err != nil { + t.Fatal(err) + } + err = t2.SetDouble(nil, "p2", 1.0001) + if err != nil { + t.Fatal(err) + } + err = t2.SetString(nil, "p3", "t3") + if err != nil { + t.Fatal(err) + } ctx = context.WithValue(context.TODO(), TestKey{}, t) - rs.Assert(ctx, t2) - rs.Unregister() + err = rs.Assert(ctx, t2) + if err != nil { + t.Fatal(err) + } + + deleteRuleSession(t, rs, t1) + count := actionCount["count"] if count != 1 { t.Errorf("expected [%d], got [%d]\n", 1, count) diff --git a/ruleapi/tests/expr_2_test.go b/ruleapi/tests/expr_2_test.go index 2d8d486..7bb9335 100644 --- a/ruleapi/tests/expr_2_test.go +++ b/ruleapi/tests/expr_2_test.go @@ -40,7 +40,7 @@ func Test_2_Expr(t *testing.T) { ctx = context.WithValue(context.TODO(), TestKey{}, t) rs.Assert(ctx, t2) - rs.Unregister() + deleteRuleSession(t, rs, t1) count := actionCount["count"] if count != 1 { t.Errorf("expected [%d], got [%d]\n", 1, count) diff --git a/ruleapi/tests/expr_3_test.go b/ruleapi/tests/expr_3_test.go index 5b865b1..c698193 100644 --- a/ruleapi/tests/expr_3_test.go +++ b/ruleapi/tests/expr_3_test.go @@ -39,7 +39,7 @@ func Test_3_Expr(t *testing.T) { ctx = context.WithValue(context.TODO(), TestKey{}, t) rs.Assert(ctx, t2) - rs.Unregister() + deleteRuleSession(t, rs, t1) count := actionCount["count"] if count != 1 { t.Errorf("expected [%d], got [%d]\n", 1, count) diff --git a/ruleapi/tests/expr_4_test.go b/ruleapi/tests/expr_4_test.go index 779b181..e7b0846 100644 --- a/ruleapi/tests/expr_4_test.go +++ b/ruleapi/tests/expr_4_test.go @@ -39,7 +39,7 @@ func Test_4_Expr(t *testing.T) { ctx = context.WithValue(context.TODO(), TestKey{}, t) rs.Assert(ctx, t2) - rs.Unregister() + deleteRuleSession(t, rs, t1) count := actionCount["count"] if count != 1 { t.Errorf("expected [%d], got [%d]\n", 1, count) diff --git a/ruleapi/tests/expr_5_test.go b/ruleapi/tests/expr_5_test.go index ad49c6a..b210d27 100644 --- a/ruleapi/tests/expr_5_test.go +++ b/ruleapi/tests/expr_5_test.go @@ -39,7 +39,7 @@ func Test_5_Expr(t *testing.T) { ctx = context.WithValue(context.TODO(), TestKey{}, t) rs.Assert(ctx, t2) - rs.Unregister() + deleteRuleSession(t, rs, t1) count := actionCount["count"] if count != 1 { t.Errorf("expected [%d], got [%d]\n", 1, count) diff --git a/ruleapi/tests/identifier_1_test.go b/ruleapi/tests/identifier_1_test.go index a910e3c..e49b74c 100644 --- a/ruleapi/tests/identifier_1_test.go +++ b/ruleapi/tests/identifier_1_test.go @@ -13,7 +13,7 @@ var count uint64 //Check if all combination of tuples t1 and t3 are triggering actions func Test_I1(t *testing.T) { - + count = 0 rs, _ := createRuleSession() rule := ruleapi.NewRule("I1") @@ -50,7 +50,7 @@ func Test_I1(t *testing.T) { t.FailNow() } - rs.Unregister() + deleteRuleSession(t, rs, t1, t2, t3, t4) } diff --git a/ruleapi/tests/identifier_2_test.go b/ruleapi/tests/identifier_2_test.go index e43755b..4b9873f 100644 --- a/ruleapi/tests/identifier_2_test.go +++ b/ruleapi/tests/identifier_2_test.go @@ -13,7 +13,7 @@ var cnt uint64 //Using 3 Identifiers, different Join conditions and triggering respective actions --->Verify order of actions and count. func Test_I2(t *testing.T) { - + cnt = 0 rs, _ := createRuleSession() //actionMap := make(map[string]string) @@ -79,7 +79,7 @@ func Test_I2(t *testing.T) { t.FailNow() } - rs.Unregister() + deleteRuleSession(t, rs, t1, t3) } diff --git a/ruleapi/tests/main_test.go b/ruleapi/tests/main_test.go new file mode 100644 index 0000000..52e32e2 --- /dev/null +++ b/ruleapi/tests/main_test.go @@ -0,0 +1,42 @@ +package tests + +import ( + "os" + "os/exec" + "strings" + "testing" +) + +func TestMain(m *testing.M) { + code := m.Run() + if code != 0 { + os.Exit(code) + } + + run := func() int { + command := exec.Command("docker", "run", "-p", "6380:6379", "-d", "redis") + hash, err := command.Output() + if err != nil { + panic(err) + } + Pour("6380") + + defer func() { + command := exec.Command("docker", "stop", strings.TrimSpace(string(hash))) + err := command.Run() + if err != nil { + panic(err) + } + command = exec.Command("docker", "rm", strings.TrimSpace(string(hash))) + err = command.Run() + if err != nil { + panic(err) + } + Drain("6380") + }() + + return m.Run() + } + redis = true + os.Exit(run()) +} diff --git a/ruleapi/tests/memory/memory.go b/ruleapi/tests/memory/memory.go index e3c52cd..0f0f675 100644 --- a/ruleapi/tests/memory/memory.go +++ b/ruleapi/tests/memory/memory.go @@ -19,7 +19,7 @@ import ( ) func createRuleSession() (model.RuleSession, error) { - rs, _ := ruleapi.GetOrCreateRuleSession("test") + rs, _ := ruleapi.GetOrCreateRuleSession("test", "") tupleDescFileAbsPath := common.GetPathForResource("ruleapi/tests/tests.json", "./../tests.json") diff --git a/ruleapi/tests/retract_1_test.go b/ruleapi/tests/retract_1_test.go index 41f9ccd..0becdc8 100644 --- a/ruleapi/tests/retract_1_test.go +++ b/ruleapi/tests/retract_1_test.go @@ -37,10 +37,12 @@ func Test_Retract_1(t *testing.T) { t.FailNow() } + tuples := []model.Tuple{} // Case1: assert a t1 { ctx := context.WithValue(context.TODO(), "key", t) tuple, _ := model.NewTupleWithKeyValues("t1", "t1") + tuples = append(tuples, tuple) err := rs.Assert(ctx, tuple) if err != nil { t.Logf("%s", err) @@ -118,6 +120,7 @@ func Test_Retract_1(t *testing.T) { { ctx := context.WithValue(context.TODO(), "key", t) tuple, _ := model.NewTupleWithKeyValues("t1", "t11") + tuples = append(tuples, tuple) err := rs.Assert(ctx, tuple) if err != nil { t.Logf("%s", err) @@ -130,7 +133,7 @@ func Test_Retract_1(t *testing.T) { t.FailNow() } } - rs.Unregister() + deleteRuleSession(t, rs, tuples...) } diff --git a/ruleapi/tests/rsconfig.json b/ruleapi/tests/rsconfig.json new file mode 100644 index 0000000..e85a748 --- /dev/null +++ b/ruleapi/tests/rsconfig.json @@ -0,0 +1,35 @@ +{ + "rs": { + "prefix": "x", + "store-ref": "redis" + }, + "rete": { + "jt-ref": "redis", + "idgen-ref": "redis", + "jt":"redis" + }, + "stores": { + "mem": { + }, + "redis": { + "network": "tcp", + "address": ":6380" + } + }, + "idgens": { + "mem": { + }, + "redis": { + "network": "tcp", + "address": ":6380" + } + }, + "jts": { + "mem": { + }, + "redis": { + "network": "tcp", + "address": ":6380" + } + } +} diff --git a/ruleapi/tests/rtctxn_10_test.go b/ruleapi/tests/rtctxn_10_test.go index 5c9c22b..abd01bc 100644 --- a/ruleapi/tests/rtctxn_10_test.go +++ b/ruleapi/tests/rtctxn_10_test.go @@ -25,12 +25,18 @@ func Test_T10(t *testing.T) { rs.Start(nil) t1, _ := model.NewTupleWithKeyValues("t1", "t10") - rs.Assert(context.TODO(), t1) + err := rs.Assert(context.TODO(), t1) + if err != nil { + t.Fatal(err) + } t3, _ := model.NewTupleWithKeyValues("t3", "t11") - rs.Assert(context.TODO(), t3) + err = rs.Assert(context.TODO(), t3) + if err != nil { + t.Fatal(err) + } - rs.Unregister() + deleteRuleSession(t, rs) } @@ -40,13 +46,13 @@ func r10_action(ctx context.Context, rs model.RuleSession, ruleName string, tupl id, _ := t3.GetString("id") if id == "t11" { tk, _ := model.NewTupleKeyWithKeyValues("t1", "t10") - t10 := rs.GetAssertedTuple(tk).(model.MutableTuple) + t10 := rs.GetAssertedTuple(ctx, tk).(model.MutableTuple) if t10 != nil { rs.Delete(ctx, t10) } tk1, _ := model.NewTupleKeyWithKeyValues("t3", "t11") - t11 := rs.GetAssertedTuple(tk1).(model.MutableTuple) + t11 := rs.GetAssertedTuple(ctx, tk1).(model.MutableTuple) if t11 != nil { rs.Delete(ctx, t11) } @@ -54,6 +60,9 @@ func r10_action(ctx context.Context, rs model.RuleSession, ruleName string, tupl } func t10Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { + if done { + return + } txnCtx := handlerCtx.(*txnCtx) txnCtx.TxnCnt = txnCtx.TxnCnt + 1 diff --git a/ruleapi/tests/rtctxn_11_test.go b/ruleapi/tests/rtctxn_11_test.go index b153be6..c59a6fc 100644 --- a/ruleapi/tests/rtctxn_11_test.go +++ b/ruleapi/tests/rtctxn_11_test.go @@ -32,9 +32,15 @@ func Test_T11(t *testing.T) { rs.Start(nil) t1, _ := model.NewTupleWithKeyValues("t1", "t10") - rs.Assert(context.TODO(), t1) + err := rs.Assert(context.TODO(), t1) + if err != nil { + t.Fatal(err) + } + + t2, _ := model.NewTupleWithKeyValues("t3", "t2") + t3, _ := model.NewTupleWithKeyValues("t3", "t1") - rs.Unregister() + deleteRuleSession(t, rs, t1, t2, t3) } @@ -59,6 +65,9 @@ func r112_action(ctx context.Context, rs model.RuleSession, ruleName string, tup } func t11Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { + if done { + return + } txnCtx := handlerCtx.(*txnCtx) txnCtx.TxnCnt = txnCtx.TxnCnt + 1 diff --git a/ruleapi/tests/rtctxn_12_test.go b/ruleapi/tests/rtctxn_12_test.go index e8f74bc..a2a3c6d 100644 --- a/ruleapi/tests/rtctxn_12_test.go +++ b/ruleapi/tests/rtctxn_12_test.go @@ -32,18 +32,30 @@ func Test_T12(t *testing.T) { rs.Start(nil) t1, _ := model.NewTupleWithKeyValues("t1", "t10") - rs.Assert(context.TODO(), t1) + err := rs.Assert(context.TODO(), t1) + if err != nil { + t.Fatal(err) + } t2, _ := model.NewTupleWithKeyValues("t1", "t11") - rs.Assert(context.TODO(), t2) + err = rs.Assert(context.TODO(), t2) + if err != nil { + t.Fatal(err) + } t3, _ := model.NewTupleWithKeyValues("t3", "t12") - rs.Assert(context.TODO(), t3) + err = rs.Assert(context.TODO(), t3) + if err != nil { + t.Fatal(err) + } t4, _ := model.NewTupleWithKeyValues("t3", "t13") - rs.Assert(context.TODO(), t4) + err = rs.Assert(context.TODO(), t4) + if err != nil { + t.Fatal(err) + } - rs.Unregister() + deleteRuleSession(t, rs, t1, t2, t3, t4) } @@ -53,7 +65,7 @@ func r12_action(ctx context.Context, rs model.RuleSession, ruleName string, tupl if id == "t13" { tk, _ := model.NewTupleKeyWithKeyValues("t1", "t10") - t10 := rs.GetAssertedTuple(tk).(model.MutableTuple) + t10 := rs.GetAssertedTuple(ctx, tk).(model.MutableTuple) t10.SetDouble(ctx, "p2", 11.11) } } @@ -64,16 +76,19 @@ func r122_action(ctx context.Context, rs model.RuleSession, ruleName string, tup if id == "t13" { tk1, _ := model.NewTupleKeyWithKeyValues("t1", "t11") - t11 := rs.GetAssertedTuple(tk1).(model.MutableTuple) + t11 := rs.GetAssertedTuple(ctx, tk1).(model.MutableTuple) t11.SetDouble(ctx, "p2", 11.11) tk2, _ := model.NewTupleKeyWithKeyValues("t3", "t12") - t12 := rs.GetAssertedTuple(tk2).(model.MutableTuple) + t12 := rs.GetAssertedTuple(ctx, tk2).(model.MutableTuple) t12.SetDouble(ctx, "p2", 11.11) } } func t12Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { + if done { + return + } txnCtx := handlerCtx.(*txnCtx) txnCtx.TxnCnt = txnCtx.TxnCnt + 1 diff --git a/ruleapi/tests/rtctxn_13_test.go b/ruleapi/tests/rtctxn_13_test.go index 17001a3..fcdeeb9 100644 --- a/ruleapi/tests/rtctxn_13_test.go +++ b/ruleapi/tests/rtctxn_13_test.go @@ -29,15 +29,24 @@ func Test_T13(t *testing.T) { txnCtx := txnCtx{t, 0} rs.RegisterRtcTransactionHandler(t13Handler, &txnCtx) - rs.Start(nil) + err := rs.Start(nil) + if err != nil { + t.Fatal(err) + } t1, _ := model.NewTupleWithKeyValues("t1", "t10") - rs.Assert(context.TODO(), t1) + err = rs.Assert(context.TODO(), t1) + if err != nil { + t.Fatal(err) + } t2, _ := model.NewTupleWithKeyValues("t3", "t12") - rs.Assert(context.TODO(), t2) + err = rs.Assert(context.TODO(), t2) + if err != nil { + t.Fatal(err) + } - rs.Unregister() + deleteRuleSession(t, rs) } @@ -47,7 +56,7 @@ func r13_action(ctx context.Context, rs model.RuleSession, ruleName string, tupl if id == "t12" { tk, _ := model.NewTupleKeyWithKeyValues("t1", "t10") - t11 := rs.GetAssertedTuple(tk).(model.MutableTuple) + t11 := rs.GetAssertedTuple(ctx, tk).(model.MutableTuple) if t11 != nil { rs.Delete(ctx, t11) } @@ -60,7 +69,7 @@ func r132_action(ctx context.Context, rs model.RuleSession, ruleName string, tup if id == "t12" { tk, _ := model.NewTupleKeyWithKeyValues("t3", "t12") - t12 := rs.GetAssertedTuple(tk).(model.MutableTuple) + t12 := rs.GetAssertedTuple(ctx, tk).(model.MutableTuple) if t12 != nil { rs.Delete(ctx, t12) } @@ -68,6 +77,9 @@ func r132_action(ctx context.Context, rs model.RuleSession, ruleName string, tup } func t13Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { + if done { + return + } txnCtx := handlerCtx.(*txnCtx) txnCtx.TxnCnt = txnCtx.TxnCnt + 1 diff --git a/ruleapi/tests/rtctxn_14_test.go b/ruleapi/tests/rtctxn_14_test.go index 5766f36..6501495 100644 --- a/ruleapi/tests/rtctxn_14_test.go +++ b/ruleapi/tests/rtctxn_14_test.go @@ -33,15 +33,26 @@ func Test_T14(t *testing.T) { t1, _ := model.NewTupleWithKeyValues("t1", "t10") t1.SetDouble(context.TODO(), "p2", 11.11) - rs.Assert(context.TODO(), t1) + err := rs.Assert(context.TODO(), t1) + if err != nil { + t.Fatal(err) + } t2, _ := model.NewTupleWithKeyValues("t3", "t12") - rs.Assert(context.TODO(), t2) + err = rs.Assert(context.TODO(), t2) + if err != nil { + t.Fatal(err) + } t3, _ := model.NewTupleWithKeyValues("t3", "t13") - rs.Assert(context.TODO(), t3) + err = rs.Assert(context.TODO(), t3) + if err != nil { + t.Fatal(err) + } - rs.Unregister() + t4, _ := model.NewTupleWithKeyValues("t1", "t2") + + deleteRuleSession(t, rs, t1, t2, t3, t4) } @@ -64,18 +75,21 @@ func r142_action(ctx context.Context, rs model.RuleSession, ruleName string, tup if id == "t12" { //Modifing p2 with the same value tk, _ := model.NewTupleKeyWithKeyValues("t1", "t10") - t10 := rs.GetAssertedTuple(tk).(model.MutableTuple) + t10 := rs.GetAssertedTuple(ctx, tk).(model.MutableTuple) t10.SetDouble(ctx, "p2", 11.11) } if id == "t13" { //Modifing p2 value tk1, _ := model.NewTupleKeyWithKeyValues("t1", "t10") - t11 := rs.GetAssertedTuple(tk1).(model.MutableTuple) + t11 := rs.GetAssertedTuple(ctx, tk1).(model.MutableTuple) t11.SetDouble(ctx, "p2", 12.11) } } func t14Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { + if done { + return + } txnCtx := handlerCtx.(*txnCtx) txnCtx.TxnCnt = txnCtx.TxnCnt + 1 diff --git a/ruleapi/tests/rtctxn_15_test.go b/ruleapi/tests/rtctxn_15_test.go index 156f8a6..066853a 100644 --- a/ruleapi/tests/rtctxn_15_test.go +++ b/ruleapi/tests/rtctxn_15_test.go @@ -14,7 +14,7 @@ var actionCnt uint64 //1 rtc->Scheduled assert, Action should be fired after the delay time. func Test_T15(t *testing.T) { - + actionCnt = 0 rs, _ := createRuleSession() rule := ruleapi.NewRule("R15") @@ -40,7 +40,7 @@ func Test_T15(t *testing.T) { t.FailNow() } - rs.Unregister() + deleteRuleSession(t, rs, t1) } diff --git a/ruleapi/tests/rtctxn_16_test.go b/ruleapi/tests/rtctxn_16_test.go index 33912bb..fbd147f 100644 --- a/ruleapi/tests/rtctxn_16_test.go +++ b/ruleapi/tests/rtctxn_16_test.go @@ -14,7 +14,7 @@ var actionCnt1 uint64 //1 rtc->Schedule assert, Cancel scheduled assert and action should not be fired func Test_T16(t *testing.T) { - + actionCnt1 = 0 rs, _ := createRuleSession() rule := ruleapi.NewRule("R16") @@ -37,7 +37,7 @@ func Test_T16(t *testing.T) { t.FailNow() } - rs.Unregister() + deleteRuleSession(t, rs) } diff --git a/ruleapi/tests/rtctxn_1_test.go b/ruleapi/tests/rtctxn_1_test.go index a5c6c06..05d4533 100644 --- a/ruleapi/tests/rtctxn_1_test.go +++ b/ruleapi/tests/rtctxn_1_test.go @@ -10,9 +10,7 @@ import ( //TTL != 0 asserted func Test_T1(t *testing.T) { - rs, _ := createRuleSession() - rule := ruleapi.NewRule("R1") rule.AddCondition("R1_c1", []string{"t1.none"}, trueCondition, t) rule.SetActionService(createActionServiceFromFunction(t, emptyAction)) @@ -24,13 +22,18 @@ func Test_T1(t *testing.T) { rs.Start(nil) t1, _ := model.NewTupleWithKeyValues("t1", "t1") - rs.Assert(context.TODO(), t1) - rs.Unregister() + err := rs.Assert(context.TODO(), t1) + if err != nil { + t.Fatal(err) + } + deleteRuleSession(t, rs, t1) } func t1Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { - + if done { + return + } t := handlerCtx.(*testing.T) lA := len(rtxn.GetRtcAdded()) diff --git a/ruleapi/tests/rtctxn_2_test.go b/ruleapi/tests/rtctxn_2_test.go index af9891e..317b539 100644 --- a/ruleapi/tests/rtctxn_2_test.go +++ b/ruleapi/tests/rtctxn_2_test.go @@ -24,18 +24,23 @@ func Test_T2(t *testing.T) { rs.Start(nil) t1, _ := model.NewTupleWithKeyValues("t2", "t2") - rs.Assert(context.TODO(), t1) - rs.Unregister() + err := rs.Assert(context.TODO(), t1) + if err != nil { + t.Fatal(err) + } + deleteRuleSession(t, rs) } func t2Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { - + if done { + return + } t := handlerCtx.(*testing.T) lA := len(rtxn.GetRtcAdded()) - if lA != 0 { - t.Errorf("RtcAdded: Expected [%d], got [%d]\n", 0, lA) + if lA != 1 { + t.Errorf("RtcAdded: Expected [%d], got [%d]\n", 1, lA) printTuples(t, "Added", rtxn.GetRtcAdded()) } lM := len(rtxn.GetRtcModified()) diff --git a/ruleapi/tests/rtctxn_3_test.go b/ruleapi/tests/rtctxn_3_test.go index 1bfadfa..cb5622b 100644 --- a/ruleapi/tests/rtctxn_3_test.go +++ b/ruleapi/tests/rtctxn_3_test.go @@ -24,8 +24,14 @@ func Test_T3(t *testing.T) { rs.Start(nil) t1, _ := model.NewTupleWithKeyValues("t1", "t10") - rs.Assert(context.TODO(), t1) - rs.Unregister() + err := rs.Assert(context.TODO(), t1) + if err != nil { + t.Fatal(err) + } + + t2, _ := model.NewTupleWithKeyValues("t1", "t11") + + deleteRuleSession(t, rs, t1, t2) } func R3_action(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { @@ -34,6 +40,9 @@ func R3_action(ctx context.Context, rs model.RuleSession, ruleName string, tuple } func t3Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { + if done { + return + } t := handlerCtx.(*testing.T) diff --git a/ruleapi/tests/rtctxn_4_test.go b/ruleapi/tests/rtctxn_4_test.go index 0f12bea..9e89ab0 100644 --- a/ruleapi/tests/rtctxn_4_test.go +++ b/ruleapi/tests/rtctxn_4_test.go @@ -24,8 +24,11 @@ func Test_T4(t *testing.T) { rs.Start(nil) t1, _ := model.NewTupleWithKeyValues("t1", "t10") - rs.Assert(context.TODO(), t1) - rs.Unregister() + err := rs.Assert(context.TODO(), t1) + if err != nil { + t.Fatal(err) + } + deleteRuleSession(t, rs, t1) } @@ -35,6 +38,9 @@ func r4_action(ctx context.Context, rs model.RuleSession, ruleName string, tuple } func t4Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { + if done { + return + } t := handlerCtx.(*testing.T) diff --git a/ruleapi/tests/rtctxn_5_test.go b/ruleapi/tests/rtctxn_5_test.go index 718ad24..bc6097a 100644 --- a/ruleapi/tests/rtctxn_5_test.go +++ b/ruleapi/tests/rtctxn_5_test.go @@ -25,15 +25,24 @@ func Test_T5(t *testing.T) { rs.Start(nil) i1, _ := model.NewTupleWithKeyValues("t1", "t10") - rs.Assert(context.TODO(), i1) + err := rs.Assert(context.TODO(), i1) + if err != nil { + t.Fatal(err) + } i2, _ := model.NewTupleWithKeyValues("t1", "t11") - rs.Assert(context.TODO(), i2) + err = rs.Assert(context.TODO(), i2) + if err != nil { + t.Fatal(err) + } i3, _ := model.NewTupleWithKeyValues("t1", "t13") - rs.Assert(context.TODO(), i3) + err = rs.Assert(context.TODO(), i3) + if err != nil { + t.Fatal(err) + } - rs.Unregister() + deleteRuleSession(t, rs, i1, i3) } @@ -43,7 +52,7 @@ func r5_action(ctx context.Context, rs model.RuleSession, ruleName string, tuple id, _ := t1.GetString("id") if id == "t11" { tk, _ := model.NewTupleKeyWithKeyValues("t1", "t10") - t10 := rs.GetAssertedTuple(tk).(model.MutableTuple) + t10 := rs.GetAssertedTuple(ctx, tk).(model.MutableTuple) if t10 != nil { t10.SetString(ctx, "p3", "v3") t10.SetDouble(ctx, "p2", 11.11) @@ -51,7 +60,7 @@ func r5_action(ctx context.Context, rs model.RuleSession, ruleName string, tuple } else if id == "t13" { //delete t11 tk, _ := model.NewTupleKeyWithKeyValues("t1", "t11") - t11 := rs.GetAssertedTuple(tk).(model.MutableTuple) + t11 := rs.GetAssertedTuple(ctx, tk).(model.MutableTuple) if t11 != nil { rs.Delete(ctx, t11) } @@ -59,6 +68,9 @@ func r5_action(ctx context.Context, rs model.RuleSession, ruleName string, tuple } func t5Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { + if done { + return + } txnCtx := handlerCtx.(*txnCtx) txnCtx.TxnCnt = txnCtx.TxnCnt + 1 diff --git a/ruleapi/tests/rtctxn_6_test.go b/ruleapi/tests/rtctxn_6_test.go index 08759c3..7c1f4ee 100644 --- a/ruleapi/tests/rtctxn_6_test.go +++ b/ruleapi/tests/rtctxn_6_test.go @@ -25,14 +25,23 @@ func Test_T6(t *testing.T) { rs.Start(nil) i1, _ := model.NewTupleWithKeyValues("t1", "t10") - rs.Assert(context.TODO(), i1) + err := rs.Assert(context.TODO(), i1) + if err != nil { + t.Fatal(err) + } i2, _ := model.NewTupleWithKeyValues("t1", "t11") - rs.Assert(context.TODO(), i2) + err = rs.Assert(context.TODO(), i2) + if err != nil { + t.Fatal(err) + } i3, _ := model.NewTupleWithKeyValues("t1", "t13") - rs.Assert(context.TODO(), i3) - rs.Unregister() + err = rs.Assert(context.TODO(), i3) + if err != nil { + t.Fatal(err) + } + deleteRuleSession(t, rs, i1, i3) } @@ -41,7 +50,7 @@ func r6_action(ctx context.Context, rs model.RuleSession, ruleName string, tuple id, _ := t1.GetString("id") if id == "t11" { tk, _ := model.NewTupleKeyWithKeyValues("t1", "t10") - t10 := rs.GetAssertedTuple(tk).(model.MutableTuple) + t10 := rs.GetAssertedTuple(ctx, tk).(model.MutableTuple) if t10 != nil { t10.SetString(ctx, "p3", "v3") t10.SetDouble(ctx, "p2", 11.11) @@ -49,7 +58,7 @@ func r6_action(ctx context.Context, rs model.RuleSession, ruleName string, tuple } else if id == "t13" { //delete t11 tk, _ := model.NewTupleKeyWithKeyValues("t1", "t11") - t11 := rs.GetAssertedTuple(tk).(model.MutableTuple) + t11 := rs.GetAssertedTuple(ctx, tk).(model.MutableTuple) if t11 != nil { rs.Delete(ctx, t11) } @@ -63,6 +72,9 @@ func r6_action(ctx context.Context, rs model.RuleSession, ruleName string, tuple } func t6Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { + if done { + return + } txnCtx := handlerCtx.(*txnCtx) txnCtx.TxnCnt++ @@ -101,8 +113,8 @@ func t6Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han } } else if txnCtx.TxnCnt == 3 { lA := len(rtxn.GetRtcAdded()) - if lA != 1 { - t.Errorf("RtcAdded: Types expected [%d], got [%d]\n", 1, lA) + if lA != 2 { + t.Errorf("RtcAdded: Types expected [%d], got [%d]\n", 2, lA) printTuples(t, "Added", rtxn.GetRtcAdded()) } else { added, _ := rtxn.GetRtcAdded()["t1"] diff --git a/ruleapi/tests/rtctxn_7_test.go b/ruleapi/tests/rtctxn_7_test.go index 61b260b..fca9477 100644 --- a/ruleapi/tests/rtctxn_7_test.go +++ b/ruleapi/tests/rtctxn_7_test.go @@ -25,9 +25,12 @@ func Test_T7(t *testing.T) { rs.Start(nil) i1, _ := model.NewTupleWithKeyValues("t1", "t10") - rs.Assert(context.TODO(), i1) + err := rs.Assert(context.TODO(), i1) + if err != nil { + t.Fatal(err) + } - rs.Unregister() + deleteRuleSession(t, rs) } @@ -40,6 +43,9 @@ func r7_action(ctx context.Context, rs model.RuleSession, ruleName string, tuple } func t7Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { + if done { + return + } txnCtx := handlerCtx.(*txnCtx) txnCtx.TxnCnt++ diff --git a/ruleapi/tests/rtctxn_8_test.go b/ruleapi/tests/rtctxn_8_test.go index 48c9f81..47957fb 100644 --- a/ruleapi/tests/rtctxn_8_test.go +++ b/ruleapi/tests/rtctxn_8_test.go @@ -25,8 +25,12 @@ func Test_T8(t *testing.T) { rs.Start(nil) t1, _ := model.NewTupleWithKeyValues("t1", "t1") - rs.Assert(context.TODO(), t1) - rs.Unregister() + err := rs.Assert(context.TODO(), t1) + if err != nil { + t.Fatal(err) + } + + deleteRuleSession(t, rs, t1) } @@ -36,6 +40,9 @@ func assertTuple(ctx context.Context, rs model.RuleSession, ruleName string, tup } func t8Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { + if done { + return + } t := handlerCtx.(*testing.T) if m, found := rtxn.GetRtcAdded()["t1"]; found { diff --git a/ruleapi/tests/rtctxn_9_test.go b/ruleapi/tests/rtctxn_9_test.go index 694e5ba..05d04e2 100644 --- a/ruleapi/tests/rtctxn_9_test.go +++ b/ruleapi/tests/rtctxn_9_test.go @@ -25,18 +25,30 @@ func Test_T9(t *testing.T) { rs.Start(nil) t1, _ := model.NewTupleWithKeyValues("t1", "t10") - rs.Assert(context.TODO(), t1) + err := rs.Assert(context.TODO(), t1) + if err != nil { + t.Fatal(err) + } t2, _ := model.NewTupleWithKeyValues("t1", "t11") - rs.Assert(context.TODO(), t2) + err = rs.Assert(context.TODO(), t2) + if err != nil { + t.Fatal(err) + } t3, _ := model.NewTupleWithKeyValues("t3", "t12") - rs.Assert(context.TODO(), t3) + err = rs.Assert(context.TODO(), t3) + if err != nil { + t.Fatal(err) + } t4, _ := model.NewTupleWithKeyValues("t3", "t13") - rs.Assert(context.TODO(), t4) + err = rs.Assert(context.TODO(), t4) + if err != nil { + t.Fatal(err) + } - rs.Unregister() + deleteRuleSession(t, rs, t1, t2, t3, t4) } @@ -53,21 +65,24 @@ func r9_action(ctx context.Context, rs model.RuleSession, ruleName string, tuple rs.Assert(ctx, t3) } else if id == "t13" { tk, _ := model.NewTupleKeyWithKeyValues("t1", "t10") - t10 := rs.GetAssertedTuple(tk).(model.MutableTuple) + t10 := rs.GetAssertedTuple(ctx, tk).(model.MutableTuple) t10.SetDouble(ctx, "p2", 11.11) tk1, _ := model.NewTupleKeyWithKeyValues("t1", "t11") - t11 := rs.GetAssertedTuple(tk1).(model.MutableTuple) + t11 := rs.GetAssertedTuple(ctx, tk1).(model.MutableTuple) t11.SetDouble(ctx, "p2", 11.11) tk2, _ := model.NewTupleKeyWithKeyValues("t3", "t12") - t12 := rs.GetAssertedTuple(tk2).(model.MutableTuple) + t12 := rs.GetAssertedTuple(ctx, tk2).(model.MutableTuple) t12.SetDouble(ctx, "p2", 11.11) } } func t9Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { + if done { + return + } txnCtx := handlerCtx.(*txnCtx) txnCtx.TxnCnt = txnCtx.TxnCnt + 1 diff --git a/ruleapi/tests/rules_1_test.go b/ruleapi/tests/rules_1_test.go index 69ea6fd..c88639c 100644 --- a/ruleapi/tests/rules_1_test.go +++ b/ruleapi/tests/rules_1_test.go @@ -57,7 +57,7 @@ func Test_One(t *testing.T) { rs.Assert(nil, t1) //unregister the session, i.e; cleanup - rs.Unregister() + deleteRuleSession(t, rs, t1) if len(actionMap) != 3 { t.Errorf("Expecting [3] actions, got [%d]", len(actionMap)) diff --git a/ruleapi/tests/rules_2_test.go b/ruleapi/tests/rules_2_test.go index 9bb96ee..869e062 100644 --- a/ruleapi/tests/rules_2_test.go +++ b/ruleapi/tests/rules_2_test.go @@ -27,7 +27,11 @@ func Test_Two(t *testing.T) { } //Create a RuleSession - rs, _ := ruleapi.GetOrCreateRuleSession("asession") + store := "" + if redis { + store = "rsconfig.json" + } + rs, _ := ruleapi.GetOrCreateRuleSession("asession", store) actionFireCount := make(map[string]int) //// check for name "Bob" in n1 @@ -63,7 +67,7 @@ func Test_Two(t *testing.T) { rs.DeleteRule(rule.GetName()) //unregister the session, i.e; cleanup - rs.Unregister() + deleteRuleSession(t, rs) } func checkForName(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { diff --git a/ruleapi/tests/rules_3_test.go b/ruleapi/tests/rules_3_test.go index 20a2ff0..d686f64 100644 --- a/ruleapi/tests/rules_3_test.go +++ b/ruleapi/tests/rules_3_test.go @@ -13,7 +13,7 @@ var actCnt uint64 //Forward chain-Data change in r3action and r32action triggers the r32action. func Test_Three(t *testing.T) { - + actCnt = 0 rs, _ := createRuleSession() actionMap := make(map[string]string) @@ -37,18 +37,24 @@ func Test_Three(t *testing.T) { t1, _ := model.NewTupleWithKeyValues("t1", "t10") t1.SetInt(context.TODO(), "p1", 2000) - rs.Assert(context.TODO(), t1) + err := rs.Assert(context.TODO(), t1) + if err != nil { + t.Fatal(err) + } t2, _ := model.NewTupleWithKeyValues("t1", "t11") t2.SetInt(context.TODO(), "p1", 2000) - rs.Assert(context.TODO(), t2) + err = rs.Assert(context.TODO(), t2) + if err != nil { + t.Fatal(err) + } if count := atomic.LoadUint64(&actCnt); count != 2 { t.Errorf("Expecting [2] actions, got [%d]", count) t.FailNow() } - rs.Unregister() + deleteRuleSession(t, rs, t1, t2) } @@ -64,22 +70,20 @@ func r3Condition(ruleName string, condName string, tuples map[model.TupleType]mo } func r3action(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { - //fmt.Println("r13_action triggered") t1 := tuples[model.TupleType("t1")].(model.MutableTuple) id, _ := t1.GetString("id") if id == "t11" { tk, _ := model.NewTupleKeyWithKeyValues("t1", "t10") - t10 := rs.GetAssertedTuple(tk).(model.MutableTuple) + t10 := rs.GetAssertedTuple(ctx, tk).(model.MutableTuple) t10.SetInt(ctx, "p1", 100) } } func r32action(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { - //fmt.Println("r132_action triggered") atomic.AddUint64(&actCnt, 1) tk, _ := model.NewTupleKeyWithKeyValues("t1", "t10") - t10 := rs.GetAssertedTuple(tk).(model.MutableTuple) + t10 := rs.GetAssertedTuple(ctx, tk).(model.MutableTuple) t10.SetInt(ctx, "p1", 500) } diff --git a/ruleapi/tests/sessions_test.go b/ruleapi/tests/sessions_test.go index 86d21d0..a2483f4 100644 --- a/ruleapi/tests/sessions_test.go +++ b/ruleapi/tests/sessions_test.go @@ -11,7 +11,7 @@ import ( ) func TestClearSessions(t *testing.T) { - ruleapi.GetOrCreateRuleSession("test") + ruleapi.GetOrCreateRuleSession("test", "") ruleapi.ClearSessions() } @@ -40,7 +40,7 @@ func TestAssert(t *testing.T) { if err != nil { t.Fatalf("err should be nil: %v", err) } - rs.Unregister() + deleteRuleSession(t, rs, t1) } func TestRace(t *testing.T) { From 2622077ff1cd816842922e7f92c14f8967fe23ed Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Tue, 8 Oct 2019 14:24:20 -0600 Subject: [PATCH 084/125] Check for errors in tests --- ruleapi/tests/common.go | 5 +- ruleapi/tests/expr_1_test.go | 58 ++++++---------------- ruleapi/tests/expr_2_test.go | 47 ++++++++++++------ ruleapi/tests/expr_3_test.go | 44 +++++++++++------ ruleapi/tests/expr_4_test.go | 44 +++++++++++------ ruleapi/tests/expr_5_test.go | 44 +++++++++++------ ruleapi/tests/identifier_1_test.go | 38 ++++++++++----- ruleapi/tests/identifier_2_test.go | 56 ++++++++++++++------- ruleapi/tests/retract_1_test.go | 78 ++++++++++++------------------ ruleapi/tests/rtctxn_10_test.go | 30 +++++++----- ruleapi/tests/rtctxn_11_test.go | 39 +++++++++------ ruleapi/tests/rtctxn_12_test.go | 50 ++++++++++--------- ruleapi/tests/rtctxn_13_test.go | 37 +++++++------- ruleapi/tests/rtctxn_14_test.go | 53 +++++++++++--------- ruleapi/tests/rtctxn_15_test.go | 17 +++++-- ruleapi/tests/rtctxn_16_test.go | 17 +++++-- ruleapi/tests/rtctxn_1_test.go | 27 ++++++----- ruleapi/tests/rtctxn_2_test.go | 23 +++++---- ruleapi/tests/rtctxn_3_test.go | 26 ++++++---- ruleapi/tests/rtctxn_4_test.go | 23 +++++---- ruleapi/tests/rtctxn_5_test.go | 37 +++++++------- ruleapi/tests/rtctxn_6_test.go | 37 +++++++------- ruleapi/tests/rtctxn_7_test.go | 23 +++++---- ruleapi/tests/rtctxn_8_test.go | 30 +++++++----- ruleapi/tests/rtctxn_9_test.go | 44 +++++++++-------- ruleapi/tests/rules_1_test.go | 32 ++++++++---- ruleapi/tests/rules_2_test.go | 37 +++++++++----- ruleapi/tests/rules_3_test.go | 42 +++++++++------- ruleapi/tests/sessions_test.go | 76 +++++++++++++++-------------- 29 files changed, 645 insertions(+), 469 deletions(-) diff --git a/ruleapi/tests/common.go b/ruleapi/tests/common.go index e76877f..631a00e 100644 --- a/ruleapi/tests/common.go +++ b/ruleapi/tests/common.go @@ -21,6 +21,7 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/config" "github.com/project-flogo/rules/ruleapi" + "github.com/stretchr/testify/assert" ) @@ -58,9 +59,7 @@ func deleteRuleSession(t *testing.T, session model.RuleSession, tuples ...model. } for _, tuple := range tuples { err := session.Delete(nil, tuple) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) } } diff --git a/ruleapi/tests/expr_1_test.go b/ruleapi/tests/expr_1_test.go index 9c7d23f..4a86232 100644 --- a/ruleapi/tests/expr_1_test.go +++ b/ruleapi/tests/expr_1_test.go @@ -6,77 +6,51 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) //1 condition, 1 expression func Test_1_Expr(t *testing.T) { actionCount := map[string]int{"count": 0} rs, err := createRuleSession() - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) r1 := ruleapi.NewRule("r1") err = r1.AddExprCondition("c1", "$.t2.p2 > $.t1.p1", nil) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) r1.SetActionService(createActionServiceFromFunction(t, a1)) r1.SetContext(actionCount) err = rs.AddRule(r1) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) err = rs.Start(nil) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) var ctx context.Context t1, err := model.NewTupleWithKeyValues("t1", "t1") - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) err = t1.SetInt(nil, "p1", 1) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) err = t1.SetDouble(nil, "p2", 1.3) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) err = t1.SetString(nil, "p3", "t3") - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) ctx = context.WithValue(context.TODO(), TestKey{}, t) err = rs.Assert(ctx, t1) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) t2, err := model.NewTupleWithKeyValues("t2", "t2") - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) err = t2.SetInt(nil, "p1", 1) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) err = t2.SetDouble(nil, "p2", 1.0001) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) err = t2.SetString(nil, "p3", "t3") - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) ctx = context.WithValue(context.TODO(), TestKey{}, t) err = rs.Assert(ctx, t2) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) deleteRuleSession(t, rs, t1) diff --git a/ruleapi/tests/expr_2_test.go b/ruleapi/tests/expr_2_test.go index 7bb9335..0943847 100644 --- a/ruleapi/tests/expr_2_test.go +++ b/ruleapi/tests/expr_2_test.go @@ -6,40 +6,57 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) //2 conditions, 1 expr each func Test_2_Expr(t *testing.T) { actionCount := map[string]int{"count": 0} - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) r1 := ruleapi.NewRule("r1") - r1.AddExprCondition("c1", "$.t1.p1 > $.t2.p1", nil) - r1.AddExprCondition("c2", "$.t1.p1 == 2", nil) + err = r1.AddExprCondition("c1", "$.t1.p1 > $.t2.p1", nil) + assert.Nil(t, err) + err = r1.AddExprCondition("c2", "$.t1.p1 == 2", nil) + assert.Nil(t, err) r1.SetActionService(createActionServiceFromFunction(t, a2)) r1.SetContext(actionCount) - rs.AddRule(r1) + err = rs.AddRule(r1) + assert.Nil(t, err) - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) var ctx context.Context - t1, _ := model.NewTupleWithKeyValues("t1", "t1") - t1.SetInt(nil, "p1", 2) - t1.SetDouble(nil, "p2", 1.3) - t1.SetString(nil, "p3", "t3") + t1, err := model.NewTupleWithKeyValues("t1", "t1") + assert.Nil(t, err) + err = t1.SetInt(nil, "p1", 2) + assert.Nil(t, err) + err = t1.SetDouble(nil, "p2", 1.3) + assert.Nil(t, err) + err = t1.SetString(nil, "p3", "t3") + assert.Nil(t, err) ctx = context.WithValue(context.TODO(), TestKey{}, t) - rs.Assert(ctx, t1) + err = rs.Assert(ctx, t1) + assert.Nil(t, err) - t2, _ := model.NewTupleWithKeyValues("t2", "t2") - t2.SetInt(nil, "p1", 1) - t2.SetDouble(nil, "p2", 1.1) - t2.SetString(nil, "p3", "t3") + t2, err := model.NewTupleWithKeyValues("t2", "t2") + assert.Nil(t, err) + err = t2.SetInt(nil, "p1", 1) + assert.Nil(t, err) + err = t2.SetDouble(nil, "p2", 1.1) + assert.Nil(t, err) + err = t2.SetString(nil, "p3", "t3") + assert.Nil(t, err) ctx = context.WithValue(context.TODO(), TestKey{}, t) - rs.Assert(ctx, t2) + err = rs.Assert(ctx, t2) + assert.Nil(t, err) deleteRuleSession(t, rs, t1) count := actionCount["count"] if count != 1 { diff --git a/ruleapi/tests/expr_3_test.go b/ruleapi/tests/expr_3_test.go index c698193..e342e6d 100644 --- a/ruleapi/tests/expr_3_test.go +++ b/ruleapi/tests/expr_3_test.go @@ -6,39 +6,55 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) //1 conditions, 2 expr func Test_3_Expr(t *testing.T) { actionCount := map[string]int{"count": 0} - rs, _ := createRuleSession() + rs, err := createRuleSession() r1 := ruleapi.NewRule("r1") - r1.AddExprCondition("c1", "($.t1.p1 > $.t2.p1) && ($.t1.p2 > $.t2.p2)", nil) + assert.Nil(t, err) + err = r1.AddExprCondition("c1", "($.t1.p1 > $.t2.p1) && ($.t1.p2 > $.t2.p2)", nil) + assert.Nil(t, err) r1.SetActionService(createActionServiceFromFunction(t, a3)) r1.SetContext(actionCount) - rs.AddRule(r1) + err = rs.AddRule(r1) + assert.Nil(t, err) - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) var ctx context.Context - t1, _ := model.NewTupleWithKeyValues("t1", "t1") - t1.SetInt(nil, "p1", 2) - t1.SetDouble(nil, "p2", 1.3) - t1.SetString(nil, "p3", "t3") + t1, err := model.NewTupleWithKeyValues("t1", "t1") + assert.Nil(t, err) + err = t1.SetInt(nil, "p1", 2) + assert.Nil(t, err) + err = t1.SetDouble(nil, "p2", 1.3) + assert.Nil(t, err) + err = t1.SetString(nil, "p3", "t3") + assert.Nil(t, err) ctx = context.WithValue(context.TODO(), TestKey{}, t) - rs.Assert(ctx, t1) + err = rs.Assert(ctx, t1) + assert.Nil(t, err) - t2, _ := model.NewTupleWithKeyValues("t2", "t2") - t2.SetInt(nil, "p1", 1) - t2.SetDouble(nil, "p2", 1.1) - t2.SetString(nil, "p3", "t3") + t2, err := model.NewTupleWithKeyValues("t2", "t2") + assert.Nil(t, err) + err = t2.SetInt(nil, "p1", 1) + assert.Nil(t, err) + err = t2.SetDouble(nil, "p2", 1.1) + assert.Nil(t, err) + err = t2.SetString(nil, "p3", "t3") + assert.Nil(t, err) ctx = context.WithValue(context.TODO(), TestKey{}, t) - rs.Assert(ctx, t2) + err = rs.Assert(ctx, t2) + assert.Nil(t, err) deleteRuleSession(t, rs, t1) count := actionCount["count"] if count != 1 { diff --git a/ruleapi/tests/expr_4_test.go b/ruleapi/tests/expr_4_test.go index e7b0846..6f4a606 100644 --- a/ruleapi/tests/expr_4_test.go +++ b/ruleapi/tests/expr_4_test.go @@ -6,39 +6,55 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) //1 conditions, 3 expr func Test_4_Expr(t *testing.T) { actionCount := map[string]int{"count": 0} - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) r1 := ruleapi.NewRule("r1") - r1.AddExprCondition("c1", "($.t1.p1 > $.t2.p1) && (($.t1.p2 > $.t2.p2) && ($.t1.p3 == $.t2.p3))", nil) + err = r1.AddExprCondition("c1", "($.t1.p1 > $.t2.p1) && (($.t1.p2 > $.t2.p2) && ($.t1.p3 == $.t2.p3))", nil) + assert.Nil(t, err) r1.SetActionService(createActionServiceFromFunction(t, a4)) r1.SetContext(actionCount) - rs.AddRule(r1) + err = rs.AddRule(r1) + assert.Nil(t, err) - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) var ctx context.Context - t1, _ := model.NewTupleWithKeyValues("t1", "t1") - t1.SetInt(nil, "p1", 2) - t1.SetDouble(nil, "p2", 1.3) - t1.SetString(nil, "p3", "t3") + t1, err := model.NewTupleWithKeyValues("t1", "t1") + assert.Nil(t, err) + err = t1.SetInt(nil, "p1", 2) + assert.Nil(t, err) + err = t1.SetDouble(nil, "p2", 1.3) + assert.Nil(t, err) + err = t1.SetString(nil, "p3", "t3") + assert.Nil(t, err) ctx = context.WithValue(context.TODO(), TestKey{}, t) - rs.Assert(ctx, t1) + err = rs.Assert(ctx, t1) + assert.Nil(t, err) - t2, _ := model.NewTupleWithKeyValues("t2", "t2") - t2.SetInt(nil, "p1", 1) - t2.SetDouble(nil, "p2", 1.1) - t2.SetString(nil, "p3", "t3") + t2, err := model.NewTupleWithKeyValues("t2", "t2") + assert.Nil(t, err) + err = t2.SetInt(nil, "p1", 1) + assert.Nil(t, err) + err = t2.SetDouble(nil, "p2", 1.1) + assert.Nil(t, err) + err = t2.SetString(nil, "p3", "t3") + assert.Nil(t, err) ctx = context.WithValue(context.TODO(), TestKey{}, t) - rs.Assert(ctx, t2) + err = rs.Assert(ctx, t2) + assert.Nil(t, err) deleteRuleSession(t, rs, t1) count := actionCount["count"] if count != 1 { diff --git a/ruleapi/tests/expr_5_test.go b/ruleapi/tests/expr_5_test.go index b210d27..43439f1 100644 --- a/ruleapi/tests/expr_5_test.go +++ b/ruleapi/tests/expr_5_test.go @@ -6,39 +6,55 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) //1 arithmetic operation func Test_5_Expr(t *testing.T) { actionCount := map[string]int{"count": 0} - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) r1 := ruleapi.NewRule("r1") - r1.AddExprCondition("c1", "(($.t1.p1 + $.t2.p1) == 5) && (($.t1.p2 > $.t2.p2) && ($.t1.p3 == $.t2.p3))", nil) + err = r1.AddExprCondition("c1", "(($.t1.p1 + $.t2.p1) == 5) && (($.t1.p2 > $.t2.p2) && ($.t1.p3 == $.t2.p3))", nil) + assert.Nil(t, err) r1.SetActionService(createActionServiceFromFunction(t, a5)) r1.SetContext(actionCount) - rs.AddRule(r1) + err = rs.AddRule(r1) + assert.Nil(t, err) - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) var ctx context.Context - t1, _ := model.NewTupleWithKeyValues("t1", "t1") - t1.SetInt(nil, "p1", 1) - t1.SetDouble(nil, "p2", 1.3) - t1.SetString(nil, "p3", "t3") + t1, err := model.NewTupleWithKeyValues("t1", "t1") + assert.Nil(t, err) + err = t1.SetInt(nil, "p1", 1) + assert.Nil(t, err) + err = t1.SetDouble(nil, "p2", 1.3) + assert.Nil(t, err) + err = t1.SetString(nil, "p3", "t3") + assert.Nil(t, err) ctx = context.WithValue(context.TODO(), TestKey{}, t) - rs.Assert(ctx, t1) + err = rs.Assert(ctx, t1) + assert.Nil(t, err) - t2, _ := model.NewTupleWithKeyValues("t2", "t2") - t2.SetInt(nil, "p1", 4) - t2.SetDouble(nil, "p2", 1.1) - t2.SetString(nil, "p3", "t3") + t2, err := model.NewTupleWithKeyValues("t2", "t2") + assert.Nil(t, err) + err = t2.SetInt(nil, "p1", 4) + assert.Nil(t, err) + err = t2.SetDouble(nil, "p2", 1.1) + assert.Nil(t, err) + err = t2.SetString(nil, "p3", "t3") + assert.Nil(t, err) ctx = context.WithValue(context.TODO(), TestKey{}, t) - rs.Assert(ctx, t2) + err = rs.Assert(ctx, t2) + assert.Nil(t, err) deleteRuleSession(t, rs, t1) count := actionCount["count"] if count != 1 { diff --git a/ruleapi/tests/identifier_1_test.go b/ruleapi/tests/identifier_1_test.go index e49b74c..f39c4f7 100644 --- a/ruleapi/tests/identifier_1_test.go +++ b/ruleapi/tests/identifier_1_test.go @@ -7,6 +7,8 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) var count uint64 @@ -14,26 +16,36 @@ var count uint64 //Check if all combination of tuples t1 and t3 are triggering actions func Test_I1(t *testing.T) { count = 0 - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) rule := ruleapi.NewRule("I1") - rule.AddCondition("I1_c1", []string{"t1.none", "t3.none"}, trueCondition, nil) + err = rule.AddCondition("I1_c1", []string{"t1.none", "t3.none"}, trueCondition, nil) + assert.Nil(t, err) rule.SetActionService(createActionServiceFromFunction(t, i1_action)) rule.SetPriority(1) - rs.AddRule(rule) + err = rs.AddRule(rule) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule.GetName()) - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) - t1, _ := model.NewTupleWithKeyValues("t1", "t10") - rs.Assert(context.TODO(), t1) + t1, err := model.NewTupleWithKeyValues("t1", "t10") + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t1) + assert.Nil(t, err) - t2, _ := model.NewTupleWithKeyValues("t1", "t11") - rs.Assert(context.TODO(), t2) + t2, err := model.NewTupleWithKeyValues("t1", "t11") + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t2) + assert.Nil(t, err) - t3, _ := model.NewTupleWithKeyValues("t3", "t12") - rs.Assert(context.TODO(), t3) + t3, err := model.NewTupleWithKeyValues("t3", "t12") + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t3) + assert.Nil(t, err) //Check if the 2 combinations {t1=t10,t3=t12} and {t1=t11,t3=t12} triggers action twice if cnt := atomic.LoadUint64(&count); cnt != 2 { @@ -41,8 +53,10 @@ func Test_I1(t *testing.T) { t.FailNow() } - t4, _ := model.NewTupleWithKeyValues("t3", "t13") - rs.Assert(context.TODO(), t4) + t4, err := model.NewTupleWithKeyValues("t3", "t13") + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t4) + assert.Nil(t, err) //Check if the 2 combinations {t1=t10,t3=t13} and {t1=t11,t3=t13} triggers action two more times making the total action count 4 if cnt := atomic.LoadUint64(&count); cnt != 4 { diff --git a/ruleapi/tests/identifier_2_test.go b/ruleapi/tests/identifier_2_test.go index 4b9873f..a4aea7d 100644 --- a/ruleapi/tests/identifier_2_test.go +++ b/ruleapi/tests/identifier_2_test.go @@ -7,6 +7,8 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) var cnt uint64 @@ -14,65 +16,83 @@ var cnt uint64 //Using 3 Identifiers, different Join conditions and triggering respective actions --->Verify order of actions and count. func Test_I2(t *testing.T) { cnt = 0 - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) //actionMap := make(map[string]string) rule := ruleapi.NewRule("I21") - rule.AddCondition("I2_c1", []string{"t1.none", "t2.none"}, trueCondition, nil) + err = rule.AddCondition("I2_c1", []string{"t1.none", "t2.none"}, trueCondition, nil) + assert.Nil(t, err) rule.SetActionService(createActionServiceFromFunction(t, i21_action)) rule.SetPriority(1) //rule.SetContext(actionMap) - rs.AddRule(rule) + err = rs.AddRule(rule) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule.GetName()) rule1 := ruleapi.NewRule("I22") - rule1.AddCondition("I2_c2", []string{"t1.none", "t3.none"}, trueCondition, nil) + err = rule1.AddCondition("I2_c2", []string{"t1.none", "t3.none"}, trueCondition, nil) + assert.Nil(t, err) rule1.SetActionService(createActionServiceFromFunction(t, i22_action)) rule1.SetPriority(1) //rule.SetContext(actionMap) - rs.AddRule(rule1) + err = rs.AddRule(rule1) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule1.GetName()) rule2 := ruleapi.NewRule("I23") - rule2.AddCondition("I2_c3", []string{"t2.none", "t3.none"}, trueCondition, nil) + err = rule2.AddCondition("I2_c3", []string{"t2.none", "t3.none"}, trueCondition, nil) + assert.Nil(t, err) rule2.SetActionService(createActionServiceFromFunction(t, i23_action)) rule2.SetPriority(1) //rule.SetContext(actionMap) - rs.AddRule(rule2) + err = rs.AddRule(rule2) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule1.GetName()) rule3 := ruleapi.NewRule("I24") - rule3.AddCondition("I2_c4", []string{"t1.none", "t2.none", "t3.none"}, trueCondition, nil) + err = rule3.AddCondition("I2_c4", []string{"t1.none", "t2.none", "t3.none"}, trueCondition, nil) + assert.Nil(t, err) rule3.SetActionService(createActionServiceFromFunction(t, i24_action)) rule3.SetPriority(1) //rule.SetContext(actionMap) - rs.AddRule(rule3) + err = rs.AddRule(rule3) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule2.GetName()) - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) - t1, _ := model.NewTupleWithKeyValues("t1", "t10") - rs.Assert(context.TODO(), t1) + t1, err := model.NewTupleWithKeyValues("t1", "t10") + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t1) + assert.Nil(t, err) - t2, _ := model.NewTupleWithKeyValues("t2", "t11") - rs.Assert(context.TODO(), t2) + t2, err := model.NewTupleWithKeyValues("t2", "t11") + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t2) + assert.Nil(t, err) if count := atomic.LoadUint64(&cnt); count != 1 { t.Errorf("Expecting [1] actions, got [%d]", count) t.FailNow() } - t3, _ := model.NewTupleWithKeyValues("t3", "t12") - rs.Assert(context.TODO(), t3) + t3, err := model.NewTupleWithKeyValues("t3", "t12") + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t3) + assert.Nil(t, err) if count := atomic.LoadUint64(&cnt); count != 2 { t.Errorf("Expecting [2] actions, got [%d]", count) t.FailNow() } - t4, _ := model.NewTupleWithKeyValues("t2", "t13") - rs.Assert(context.TODO(), t4) + t4, err := model.NewTupleWithKeyValues("t2", "t13") + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t4) + assert.Nil(t, err) if count := atomic.LoadUint64(&cnt); count != 5 { t.Errorf("Expecting [5] actions, got [%d]", count) diff --git a/ruleapi/tests/retract_1_test.go b/ruleapi/tests/retract_1_test.go index 0becdc8..ebd6a5c 100644 --- a/ruleapi/tests/retract_1_test.go +++ b/ruleapi/tests/retract_1_test.go @@ -6,59 +6,49 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) //Retract func Test_Retract_1(t *testing.T) { - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) //create a rule joining t1 and t3 rule := ruleapi.NewRule("Retract_Test") - err := rule.AddCondition("R7_c1", []string{"t1.none", "t3.none"}, trueCondition, nil) - if err != nil { - t.Logf("%s", err) - t.FailNow() - } + err = rule.AddCondition("R7_c1", []string{"t1.none", "t3.none"}, trueCondition, nil) + assert.Nil(t, err) ruleActionCtx := make(map[string]string) rule.SetContext(ruleActionCtx) rule.SetActionService(createActionServiceFromFunction(t, assertAction)) rule.SetPriority(1) err = rs.AddRule(rule) - if err != nil { - t.Logf("%s", err) - t.FailNow() - } + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule.GetName()) err = rs.Start(nil) - if err != nil { - t.Logf("%s", err) - t.FailNow() - } + assert.Nil(t, err) tuples := []model.Tuple{} // Case1: assert a t1 { ctx := context.WithValue(context.TODO(), "key", t) - tuple, _ := model.NewTupleWithKeyValues("t1", "t1") + tuple, err := model.NewTupleWithKeyValues("t1", "t1") + assert.Nil(t, err) tuples = append(tuples, tuple) - err := rs.Assert(ctx, tuple) - if err != nil { - t.Logf("%s", err) - t.FailNow() - } + err = rs.Assert(ctx, tuple) + assert.Nil(t, err) } // Case2: assert a t3 so that the rule fires for keys t1 and t3 { ctx := context.WithValue(context.TODO(), "key", t) - tuple, _ := model.NewTupleWithKeyValues("t3", "t3") - err := rs.Assert(ctx, tuple) - if err != nil { - t.Logf("%s", err) - t.FailNow() - } + tuple, err := model.NewTupleWithKeyValues("t3", "t3") + assert.Nil(t, err) + err = rs.Assert(ctx, tuple) + assert.Nil(t, err) // make sure that rule action got fired by inspecting the rule context isActionFired, ok := ruleActionCtx["isActionFired"] if !ok || isActionFired != "Fired" { @@ -71,12 +61,10 @@ func Test_Retract_1(t *testing.T) { // Case3: now retract t3 { ctx := context.WithValue(context.TODO(), "key", t) - tuple, _ := model.NewTupleWithKeyValues("t3", "t3") - err := rs.Retract(ctx, tuple) - if err != nil { - t.Logf("%s", err) - t.FailNow() - } + tuple, err := model.NewTupleWithKeyValues("t3", "t3") + assert.Nil(t, err) + err = rs.Retract(ctx, tuple) + assert.Nil(t, err) } /** @@ -86,12 +74,10 @@ func Test_Retract_1(t *testing.T) { */ { ctx := context.WithValue(context.TODO(), "key", t) - tuple, _ := model.NewTupleWithKeyValues("t3", "t3") - err := rs.Assert(ctx, tuple) - if err != nil { - t.Logf("%s", err) - t.FailNow() - } + tuple, err := model.NewTupleWithKeyValues("t3", "t3") + assert.Nil(t, err) + err = rs.Assert(ctx, tuple) + assert.Nil(t, err) // make sure that rule action got fired by inspecting the rule context isActionFired, ok := ruleActionCtx["isActionFired"] if !ok || isActionFired != "Fired" { @@ -108,8 +94,10 @@ func Test_Retract_1(t *testing.T) { */ { ctx := context.WithValue(context.TODO(), "key", t) - tuple, _ := model.NewTupleWithKeyValues("t3", "t3") - rs.Retract(ctx, tuple) + tuple, err := model.NewTupleWithKeyValues("t3", "t3") + assert.Nil(t, err) + err = rs.Retract(ctx, tuple) + assert.Nil(t, err) } /** @@ -119,13 +107,11 @@ func Test_Retract_1(t *testing.T) { */ { ctx := context.WithValue(context.TODO(), "key", t) - tuple, _ := model.NewTupleWithKeyValues("t1", "t11") + tuple, err := model.NewTupleWithKeyValues("t1", "t11") + assert.Nil(t, err) tuples = append(tuples, tuple) - err := rs.Assert(ctx, tuple) - if err != nil { - t.Logf("%s", err) - t.FailNow() - } + err = rs.Assert(ctx, tuple) + assert.Nil(t, err) // make sure that rule action doesn't fire by inspecting the rule context _, ok := ruleActionCtx["isActionFired"] if ok { diff --git a/ruleapi/tests/rtctxn_10_test.go b/ruleapi/tests/rtctxn_10_test.go index abd01bc..2ea7634 100644 --- a/ruleapi/tests/rtctxn_10_test.go +++ b/ruleapi/tests/rtctxn_10_test.go @@ -6,35 +6,39 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) //1 rtc ->Delete multiple tuple types and verify count. func Test_T10(t *testing.T) { - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) rule := ruleapi.NewRule("R10") - rule.AddCondition("R10_c1", []string{"t1.none", "t3.none"}, trueCondition, nil) + err = rule.AddCondition("R10_c1", []string{"t1.none", "t3.none"}, trueCondition, nil) + assert.Nil(t, err) rule.SetActionService(createActionServiceFromFunction(t, r10_action)) rule.SetPriority(1) - rs.AddRule(rule) + err = rs.AddRule(rule) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule.GetName()) txnCtx := txnCtx{t, 0} rs.RegisterRtcTransactionHandler(t10Handler, &txnCtx) - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) - t1, _ := model.NewTupleWithKeyValues("t1", "t10") - err := rs.Assert(context.TODO(), t1) - if err != nil { - t.Fatal(err) - } + t1, err := model.NewTupleWithKeyValues("t1", "t10") + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t1) + assert.Nil(t, err) - t3, _ := model.NewTupleWithKeyValues("t3", "t11") + t3, err := model.NewTupleWithKeyValues("t3", "t11") + assert.Nil(t, err) err = rs.Assert(context.TODO(), t3) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) deleteRuleSession(t, rs) diff --git a/ruleapi/tests/rtctxn_11_test.go b/ruleapi/tests/rtctxn_11_test.go index c59a6fc..edfa9a6 100644 --- a/ruleapi/tests/rtctxn_11_test.go +++ b/ruleapi/tests/rtctxn_11_test.go @@ -6,39 +6,48 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) //1 rtc->one assert triggers two rule actions each rule action asserts 2 more tuples.Verify Tuple type and Tuples count. func Test_T11(t *testing.T) { - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) rule := ruleapi.NewRule("R11") - rule.AddCondition("R11_c1", []string{"t1.none"}, trueCondition, nil) + err = rule.AddCondition("R11_c1", []string{"t1.none"}, trueCondition, nil) + assert.Nil(t, err) rule.SetActionService(createActionServiceFromFunction(t, r11_action)) rule.SetPriority(1) - rs.AddRule(rule) + err = rs.AddRule(rule) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule.GetName()) rule1 := ruleapi.NewRule("R112") - rule1.AddCondition("R112_c1", []string{"t1.none"}, trueCondition, nil) + err = rule1.AddCondition("R112_c1", []string{"t1.none"}, trueCondition, nil) + assert.Nil(t, err) rule1.SetActionService(createActionServiceFromFunction(t, r112_action)) rule1.SetPriority(1) - rs.AddRule(rule1) + err = rs.AddRule(rule1) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule1.GetName()) txnCtx := txnCtx{t, 0} rs.RegisterRtcTransactionHandler(t11Handler, &txnCtx) - rs.Start(nil) - - t1, _ := model.NewTupleWithKeyValues("t1", "t10") - err := rs.Assert(context.TODO(), t1) - if err != nil { - t.Fatal(err) - } - - t2, _ := model.NewTupleWithKeyValues("t3", "t2") - t3, _ := model.NewTupleWithKeyValues("t3", "t1") + err = rs.Start(nil) + assert.Nil(t, err) + + t1, err := model.NewTupleWithKeyValues("t1", "t10") + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t1) + assert.Nil(t, err) + + t2, err := model.NewTupleWithKeyValues("t3", "t2") + assert.Nil(t, err) + t3, err := model.NewTupleWithKeyValues("t3", "t1") + assert.Nil(t, err) deleteRuleSession(t, rs, t1, t2, t3) diff --git a/ruleapi/tests/rtctxn_12_test.go b/ruleapi/tests/rtctxn_12_test.go index a2a3c6d..b91d264 100644 --- a/ruleapi/tests/rtctxn_12_test.go +++ b/ruleapi/tests/rtctxn_12_test.go @@ -6,54 +6,58 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) //1 rtc->one assert triggers two rule actions each rule action modifies tuples.Verify Tuple type and Tuples count. func Test_T12(t *testing.T) { - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) rule := ruleapi.NewRule("R12") - rule.AddCondition("R12_c1", []string{"t1.none", "t3.none"}, trueCondition, nil) + err = rule.AddCondition("R12_c1", []string{"t1.none", "t3.none"}, trueCondition, nil) + assert.Nil(t, err) rule.SetActionService(createActionServiceFromFunction(t, r122_action)) rule.SetPriority(1) - rs.AddRule(rule) + err = rs.AddRule(rule) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule.GetName()) rule1 := ruleapi.NewRule("R122") - rule1.AddCondition("R122_c1", []string{"t1.none", "t3.none"}, trueCondition, nil) + err = rule1.AddCondition("R122_c1", []string{"t1.none", "t3.none"}, trueCondition, nil) + assert.Nil(t, err) rule1.SetActionService(createActionServiceFromFunction(t, r12_action)) rule1.SetPriority(1) - rs.AddRule(rule1) + err = rs.AddRule(rule1) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule1.GetName()) txnCtx := txnCtx{t, 0} rs.RegisterRtcTransactionHandler(t12Handler, &txnCtx) - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) - t1, _ := model.NewTupleWithKeyValues("t1", "t10") - err := rs.Assert(context.TODO(), t1) - if err != nil { - t.Fatal(err) - } + t1, err := model.NewTupleWithKeyValues("t1", "t10") + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t1) + assert.Nil(t, err) - t2, _ := model.NewTupleWithKeyValues("t1", "t11") + t2, err := model.NewTupleWithKeyValues("t1", "t11") + assert.Nil(t, err) err = rs.Assert(context.TODO(), t2) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) - t3, _ := model.NewTupleWithKeyValues("t3", "t12") + t3, err := model.NewTupleWithKeyValues("t3", "t12") + assert.Nil(t, err) err = rs.Assert(context.TODO(), t3) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) - t4, _ := model.NewTupleWithKeyValues("t3", "t13") + t4, err := model.NewTupleWithKeyValues("t3", "t13") + assert.Nil(t, err) err = rs.Assert(context.TODO(), t4) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) deleteRuleSession(t, rs, t1, t2, t3, t4) diff --git a/ruleapi/tests/rtctxn_13_test.go b/ruleapi/tests/rtctxn_13_test.go index fcdeeb9..aba581f 100644 --- a/ruleapi/tests/rtctxn_13_test.go +++ b/ruleapi/tests/rtctxn_13_test.go @@ -6,45 +6,48 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) //1 rtc->one assert triggers two rule actions each rule action deletes tuples.Verify Deleted Tuple types and Tuples count. func Test_T13(t *testing.T) { - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) rule := ruleapi.NewRule("R13") - rule.AddCondition("R13_c1", []string{"t1.none", "t3.none"}, trueCondition, nil) + err = rule.AddCondition("R13_c1", []string{"t1.none", "t3.none"}, trueCondition, nil) + assert.Nil(t, err) rule.SetActionService(createActionServiceFromFunction(t, r13_action)) rule.SetPriority(1) - rs.AddRule(rule) + err = rs.AddRule(rule) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule.GetName()) rule1 := ruleapi.NewRule("R132") - rule1.AddCondition("R132_c1", []string{"t3.none"}, trueCondition, nil) + err = rule1.AddCondition("R132_c1", []string{"t3.none"}, trueCondition, nil) + assert.Nil(t, err) rule1.SetActionService(createActionServiceFromFunction(t, r132_action)) rule1.SetPriority(2) - rs.AddRule(rule1) + err = rs.AddRule(rule1) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule1.GetName()) txnCtx := txnCtx{t, 0} rs.RegisterRtcTransactionHandler(t13Handler, &txnCtx) - err := rs.Start(nil) - if err != nil { - t.Fatal(err) - } + err = rs.Start(nil) + assert.Nil(t, err) - t1, _ := model.NewTupleWithKeyValues("t1", "t10") + t1, err := model.NewTupleWithKeyValues("t1", "t10") + assert.Nil(t, err) err = rs.Assert(context.TODO(), t1) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) - t2, _ := model.NewTupleWithKeyValues("t3", "t12") + t2, err := model.NewTupleWithKeyValues("t3", "t12") + assert.Nil(t, err) err = rs.Assert(context.TODO(), t2) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) deleteRuleSession(t, rs) diff --git a/ruleapi/tests/rtctxn_14_test.go b/ruleapi/tests/rtctxn_14_test.go index 6501495..e1f928d 100644 --- a/ruleapi/tests/rtctxn_14_test.go +++ b/ruleapi/tests/rtctxn_14_test.go @@ -6,51 +6,58 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) //1 rtc->Redundant add and modify on same tuple->Verify added and modified count func Test_T14(t *testing.T) { - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) rule := ruleapi.NewRule("R14") - rule.AddCondition("R14_c1", []string{"t1.none"}, trueCondition, nil) + err = rule.AddCondition("R14_c1", []string{"t1.none"}, trueCondition, nil) + assert.Nil(t, err) rule.SetActionService(createActionServiceFromFunction(t, r14_action)) rule.SetPriority(1) - rs.AddRule(rule) + err = rs.AddRule(rule) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule.GetName()) rule1 := ruleapi.NewRule("R142") - rule1.AddCondition("R142_c1", []string{"t3.none"}, trueCondition, nil) + err = rule1.AddCondition("R142_c1", []string{"t3.none"}, trueCondition, nil) + assert.Nil(t, err) rule1.SetActionService(createActionServiceFromFunction(t, r142_action)) rule1.SetPriority(2) - rs.AddRule(rule1) + err = rs.AddRule(rule1) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule1.GetName()) txnCtx := txnCtx{t, 0} rs.RegisterRtcTransactionHandler(t14Handler, &txnCtx) - rs.Start(nil) - - t1, _ := model.NewTupleWithKeyValues("t1", "t10") - t1.SetDouble(context.TODO(), "p2", 11.11) - err := rs.Assert(context.TODO(), t1) - if err != nil { - t.Fatal(err) - } - - t2, _ := model.NewTupleWithKeyValues("t3", "t12") + err = rs.Start(nil) + assert.Nil(t, err) + + t1, err := model.NewTupleWithKeyValues("t1", "t10") + assert.Nil(t, err) + err = t1.SetDouble(context.TODO(), "p2", 11.11) + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t1) + assert.Nil(t, err) + + t2, err := model.NewTupleWithKeyValues("t3", "t12") + assert.Nil(t, err) err = rs.Assert(context.TODO(), t2) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) - t3, _ := model.NewTupleWithKeyValues("t3", "t13") + t3, err := model.NewTupleWithKeyValues("t3", "t13") + assert.Nil(t, err) err = rs.Assert(context.TODO(), t3) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) - t4, _ := model.NewTupleWithKeyValues("t1", "t2") + t4, err := model.NewTupleWithKeyValues("t1", "t2") + assert.Nil(t, err) deleteRuleSession(t, rs, t1, t2, t3, t4) diff --git a/ruleapi/tests/rtctxn_15_test.go b/ruleapi/tests/rtctxn_15_test.go index 066853a..e88d529 100644 --- a/ruleapi/tests/rtctxn_15_test.go +++ b/ruleapi/tests/rtctxn_15_test.go @@ -8,6 +8,8 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) var actionCnt uint64 @@ -15,18 +17,23 @@ var actionCnt uint64 //1 rtc->Scheduled assert, Action should be fired after the delay time. func Test_T15(t *testing.T) { actionCnt = 0 - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) rule := ruleapi.NewRule("R15") - rule.AddCondition("R15_c1", []string{"t1.none"}, trueCondition, nil) + err = rule.AddCondition("R15_c1", []string{"t1.none"}, trueCondition, nil) + assert.Nil(t, err) rule.SetActionService(createActionServiceFromFunction(t, r15_action)) rule.SetPriority(1) - rs.AddRule(rule) + err = rs.AddRule(rule) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule.GetName()) - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) - t1, _ := model.NewTupleWithKeyValues("t1", "t10") + t1, err := model.NewTupleWithKeyValues("t1", "t10") + assert.Nil(t, err) rs.ScheduleAssert(context.TODO(), 1000, "1", t1) if count := atomic.LoadUint64(&actionCnt); count != 0 { diff --git a/ruleapi/tests/rtctxn_16_test.go b/ruleapi/tests/rtctxn_16_test.go index fbd147f..a7f6dc4 100644 --- a/ruleapi/tests/rtctxn_16_test.go +++ b/ruleapi/tests/rtctxn_16_test.go @@ -8,6 +8,8 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) var actionCnt1 uint64 @@ -15,18 +17,23 @@ var actionCnt1 uint64 //1 rtc->Schedule assert, Cancel scheduled assert and action should not be fired func Test_T16(t *testing.T) { actionCnt1 = 0 - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) rule := ruleapi.NewRule("R16") - rule.AddCondition("R16_c1", []string{"t1.none"}, trueCondition, nil) + err = rule.AddCondition("R16_c1", []string{"t1.none"}, trueCondition, nil) + assert.Nil(t, err) rule.SetActionService(createActionServiceFromFunction(t, r16_action)) rule.SetPriority(1) - rs.AddRule(rule) + err = rs.AddRule(rule) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule.GetName()) - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) - t1, _ := model.NewTupleWithKeyValues("t1", "t10") + t1, err := model.NewTupleWithKeyValues("t1", "t10") + assert.Nil(t, err) rs.ScheduleAssert(context.TODO(), 1000, "1", t1) rs.CancelScheduledAssert(context.TODO(), "1") diff --git a/ruleapi/tests/rtctxn_1_test.go b/ruleapi/tests/rtctxn_1_test.go index 05d4533..9767db6 100644 --- a/ruleapi/tests/rtctxn_1_test.go +++ b/ruleapi/tests/rtctxn_1_test.go @@ -1,31 +1,36 @@ package tests import ( + "context" + "testing" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "context" - "testing" + "github.com/stretchr/testify/assert" ) //TTL != 0 asserted func Test_T1(t *testing.T) { - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) rule := ruleapi.NewRule("R1") - rule.AddCondition("R1_c1", []string{"t1.none"}, trueCondition, t) + err = rule.AddCondition("R1_c1", []string{"t1.none"}, trueCondition, t) + assert.Nil(t, err) rule.SetActionService(createActionServiceFromFunction(t, emptyAction)) rule.SetPriority(1) - rs.AddRule(rule) + err = rs.AddRule(rule) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule.GetName()) rs.RegisterRtcTransactionHandler(t1Handler, t) - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) - t1, _ := model.NewTupleWithKeyValues("t1", "t1") - err := rs.Assert(context.TODO(), t1) - if err != nil { - t.Fatal(err) - } + t1, err := model.NewTupleWithKeyValues("t1", "t1") + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t1) + assert.Nil(t, err) deleteRuleSession(t, rs, t1) } diff --git a/ruleapi/tests/rtctxn_2_test.go b/ruleapi/tests/rtctxn_2_test.go index 317b539..0217e9b 100644 --- a/ruleapi/tests/rtctxn_2_test.go +++ b/ruleapi/tests/rtctxn_2_test.go @@ -6,28 +6,33 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) //TTL = 0, asserted func Test_T2(t *testing.T) { - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) rule := ruleapi.NewRule("R2") - rule.AddCondition("R2_c1", []string{"t2.none"}, trueCondition, nil) + err = rule.AddCondition("R2_c1", []string{"t2.none"}, trueCondition, nil) + assert.Nil(t, err) rule.SetActionService(createActionServiceFromFunction(t, emptyAction)) rule.SetPriority(1) - rs.AddRule(rule) + err = rs.AddRule(rule) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule.GetName()) rs.RegisterRtcTransactionHandler(t2Handler, t) - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) - t1, _ := model.NewTupleWithKeyValues("t2", "t2") - err := rs.Assert(context.TODO(), t1) - if err != nil { - t.Fatal(err) - } + t1, err := model.NewTupleWithKeyValues("t2", "t2") + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t1) + assert.Nil(t, err) deleteRuleSession(t, rs) } diff --git a/ruleapi/tests/rtctxn_3_test.go b/ruleapi/tests/rtctxn_3_test.go index cb5622b..67ef957 100644 --- a/ruleapi/tests/rtctxn_3_test.go +++ b/ruleapi/tests/rtctxn_3_test.go @@ -6,30 +6,36 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) //New asserted in action (forward chain) func Test_T3(t *testing.T) { - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) rule := ruleapi.NewRule("R3") - rule.AddCondition("R3_c1", []string{"t1.none"}, trueCondition, nil) + err = rule.AddCondition("R3_c1", []string{"t1.none"}, trueCondition, nil) + assert.Nil(t, err) rule.SetActionService(createActionServiceFromFunction(t, R3_action)) rule.SetPriority(1) - rs.AddRule(rule) + err = rs.AddRule(rule) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule.GetName()) rs.RegisterRtcTransactionHandler(t3Handler, t) - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) - t1, _ := model.NewTupleWithKeyValues("t1", "t10") - err := rs.Assert(context.TODO(), t1) - if err != nil { - t.Fatal(err) - } + t1, err := model.NewTupleWithKeyValues("t1", "t10") + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t1) + assert.Nil(t, err) - t2, _ := model.NewTupleWithKeyValues("t1", "t11") + t2, err := model.NewTupleWithKeyValues("t1", "t11") + assert.Nil(t, err) deleteRuleSession(t, rs, t1, t2) } diff --git a/ruleapi/tests/rtctxn_4_test.go b/ruleapi/tests/rtctxn_4_test.go index 9e89ab0..c2da52b 100644 --- a/ruleapi/tests/rtctxn_4_test.go +++ b/ruleapi/tests/rtctxn_4_test.go @@ -6,28 +6,33 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) // modified in action (forward chain) func Test_T4(t *testing.T) { - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) rule := ruleapi.NewRule("R4") - rule.AddCondition("R4_c1", []string{"t1.none"}, trueCondition, nil) + err = rule.AddCondition("R4_c1", []string{"t1.none"}, trueCondition, nil) + assert.Nil(t, err) rule.SetActionService(createActionServiceFromFunction(t, r4_action)) rule.SetPriority(1) - rs.AddRule(rule) + err = rs.AddRule(rule) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule.GetName()) rs.RegisterRtcTransactionHandler(t4Handler, t) - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) - t1, _ := model.NewTupleWithKeyValues("t1", "t10") - err := rs.Assert(context.TODO(), t1) - if err != nil { - t.Fatal(err) - } + t1, err := model.NewTupleWithKeyValues("t1", "t10") + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t1) + assert.Nil(t, err) deleteRuleSession(t, rs, t1) } diff --git a/ruleapi/tests/rtctxn_5_test.go b/ruleapi/tests/rtctxn_5_test.go index bc6097a..4bb8f8a 100644 --- a/ruleapi/tests/rtctxn_5_test.go +++ b/ruleapi/tests/rtctxn_5_test.go @@ -6,41 +6,44 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) //3 rtcs, 1st rtc ->asserted, 2nd rtc ->modified the 1st one, 3rd rtc ->deleted the 2nd one func Test_T5(t *testing.T) { - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) rule := ruleapi.NewRule("R5") - rule.AddCondition("R5_c1", []string{"t1.none"}, trueCondition, nil) + err = rule.AddCondition("R5_c1", []string{"t1.none"}, trueCondition, nil) + assert.Nil(t, err) rule.SetActionService(createActionServiceFromFunction(t, r5_action)) rule.SetPriority(1) - rs.AddRule(rule) + err = rs.AddRule(rule) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule.GetName()) txnCtx := txnCtx{t, 0} rs.RegisterRtcTransactionHandler(t5Handler, &txnCtx) - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) - i1, _ := model.NewTupleWithKeyValues("t1", "t10") - err := rs.Assert(context.TODO(), i1) - if err != nil { - t.Fatal(err) - } + i1, err := model.NewTupleWithKeyValues("t1", "t10") + assert.Nil(t, err) + err = rs.Assert(context.TODO(), i1) + assert.Nil(t, err) - i2, _ := model.NewTupleWithKeyValues("t1", "t11") + i2, err := model.NewTupleWithKeyValues("t1", "t11") + assert.Nil(t, err) err = rs.Assert(context.TODO(), i2) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) - i3, _ := model.NewTupleWithKeyValues("t1", "t13") + i3, err := model.NewTupleWithKeyValues("t1", "t13") + assert.Nil(t, err) err = rs.Assert(context.TODO(), i3) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) deleteRuleSession(t, rs, i1, i3) diff --git a/ruleapi/tests/rtctxn_6_test.go b/ruleapi/tests/rtctxn_6_test.go index 7c1f4ee..b5995aa 100644 --- a/ruleapi/tests/rtctxn_6_test.go +++ b/ruleapi/tests/rtctxn_6_test.go @@ -6,41 +6,44 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) //Same as Test_T5, but in 3rd rtc, assert a TTL=0 based and a TTL=1 based func Test_T6(t *testing.T) { - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) rule := ruleapi.NewRule("R6") - rule.AddCondition("R6_c1", []string{"t1.none"}, trueCondition, nil) + err = rule.AddCondition("R6_c1", []string{"t1.none"}, trueCondition, nil) + assert.Nil(t, err) rule.SetActionService(createActionServiceFromFunction(t, r6_action)) rule.SetPriority(1) - rs.AddRule(rule) + err = rs.AddRule(rule) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule.GetName()) txnCtx := txnCtx{t, 0} rs.RegisterRtcTransactionHandler(t6Handler, &txnCtx) - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) - i1, _ := model.NewTupleWithKeyValues("t1", "t10") - err := rs.Assert(context.TODO(), i1) - if err != nil { - t.Fatal(err) - } + i1, err := model.NewTupleWithKeyValues("t1", "t10") + assert.Nil(t, err) + err = rs.Assert(context.TODO(), i1) + assert.Nil(t, err) - i2, _ := model.NewTupleWithKeyValues("t1", "t11") + i2, err := model.NewTupleWithKeyValues("t1", "t11") + assert.Nil(t, err) err = rs.Assert(context.TODO(), i2) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) - i3, _ := model.NewTupleWithKeyValues("t1", "t13") + i3, err := model.NewTupleWithKeyValues("t1", "t13") + assert.Nil(t, err) err = rs.Assert(context.TODO(), i3) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) deleteRuleSession(t, rs, i1, i3) } diff --git a/ruleapi/tests/rtctxn_7_test.go b/ruleapi/tests/rtctxn_7_test.go index fca9477..7dace34 100644 --- a/ruleapi/tests/rtctxn_7_test.go +++ b/ruleapi/tests/rtctxn_7_test.go @@ -6,29 +6,34 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) //add and delete in the same rtc func Test_T7(t *testing.T) { - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) rule := ruleapi.NewRule("R7") - rule.AddCondition("R7_c1", []string{"t1.none"}, trueCondition, nil) + err = rule.AddCondition("R7_c1", []string{"t1.none"}, trueCondition, nil) + assert.Nil(t, err) rule.SetActionService(createActionServiceFromFunction(t, r7_action)) rule.SetPriority(1) - rs.AddRule(rule) + err = rs.AddRule(rule) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule.GetName()) txnCtx := txnCtx{t, 0} rs.RegisterRtcTransactionHandler(t7Handler, &txnCtx) - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) - i1, _ := model.NewTupleWithKeyValues("t1", "t10") - err := rs.Assert(context.TODO(), i1) - if err != nil { - t.Fatal(err) - } + i1, err := model.NewTupleWithKeyValues("t1", "t10") + assert.Nil(t, err) + err = rs.Assert(context.TODO(), i1) + assert.Nil(t, err) deleteRuleSession(t, rs) diff --git a/ruleapi/tests/rtctxn_8_test.go b/ruleapi/tests/rtctxn_8_test.go index 47957fb..f052d6c 100644 --- a/ruleapi/tests/rtctxn_8_test.go +++ b/ruleapi/tests/rtctxn_8_test.go @@ -1,34 +1,40 @@ package tests import ( + "context" + "testing" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" - "context" - "testing" + "github.com/stretchr/testify/assert" ) //no-identifier condition func Test_T8(t *testing.T) { - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) rule := ruleapi.NewRule("R1") - rule.AddCondition("R1_c1", []string{"t1.none"}, trueCondition, nil) - rule.AddCondition("R1_c2", []string{}, falseCondition, nil) + err = rule.AddCondition("R1_c1", []string{"t1.none"}, trueCondition, nil) + assert.Nil(t, err) + err = rule.AddCondition("R1_c2", []string{}, falseCondition, nil) + assert.Nil(t, err) rule.SetActionService(createActionServiceFromFunction(t, assertTuple)) rule.SetPriority(1) - rs.AddRule(rule) + err = rs.AddRule(rule) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule.GetName()) rs.RegisterRtcTransactionHandler(t8Handler, t) - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) - t1, _ := model.NewTupleWithKeyValues("t1", "t1") - err := rs.Assert(context.TODO(), t1) - if err != nil { - t.Fatal(err) - } + t1, err := model.NewTupleWithKeyValues("t1", "t1") + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t1) + assert.Nil(t, err) deleteRuleSession(t, rs, t1) diff --git a/ruleapi/tests/rtctxn_9_test.go b/ruleapi/tests/rtctxn_9_test.go index 05d04e2..ae49598 100644 --- a/ruleapi/tests/rtctxn_9_test.go +++ b/ruleapi/tests/rtctxn_9_test.go @@ -6,47 +6,49 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) //2 rtcs, 1st rtc ->Asserted multiple tuple types and verify count, 2nd rtc -> Modified multiple tuple types and verify count. func Test_T9(t *testing.T) { - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) rule := ruleapi.NewRule("R9") - rule.AddCondition("R9_c1", []string{"t1.none", "t3.none"}, trueCondition, nil) + err = rule.AddCondition("R9_c1", []string{"t1.none", "t3.none"}, trueCondition, nil) + assert.Nil(t, err) rule.SetActionService(createActionServiceFromFunction(t, r9_action)) rule.SetPriority(1) - rs.AddRule(rule) + err = rs.AddRule(rule) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule.GetName()) txnCtx := txnCtx{t, 0} rs.RegisterRtcTransactionHandler(t9Handler, &txnCtx) - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) - t1, _ := model.NewTupleWithKeyValues("t1", "t10") - err := rs.Assert(context.TODO(), t1) - if err != nil { - t.Fatal(err) - } + t1, err := model.NewTupleWithKeyValues("t1", "t10") + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t1) + assert.Nil(t, err) - t2, _ := model.NewTupleWithKeyValues("t1", "t11") + t2, err := model.NewTupleWithKeyValues("t1", "t11") + assert.Nil(t, err) err = rs.Assert(context.TODO(), t2) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) - t3, _ := model.NewTupleWithKeyValues("t3", "t12") + t3, err := model.NewTupleWithKeyValues("t3", "t12") + assert.Nil(t, err) err = rs.Assert(context.TODO(), t3) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) - t4, _ := model.NewTupleWithKeyValues("t3", "t13") + t4, err := model.NewTupleWithKeyValues("t3", "t13") + assert.Nil(t, err) err = rs.Assert(context.TODO(), t4) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) deleteRuleSession(t, rs, t1, t2, t3, t4) diff --git a/ruleapi/tests/rules_1_test.go b/ruleapi/tests/rules_1_test.go index c88639c..8a73db9 100644 --- a/ruleapi/tests/rules_1_test.go +++ b/ruleapi/tests/rules_1_test.go @@ -7,6 +7,8 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) /** @@ -17,44 +19,54 @@ the expected outcome is that all three rules should fire func Test_One(t *testing.T) { - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) actionMap := make(map[string]string) //// rule 1 r1 := ruleapi.NewRule("R1") - r1.AddCondition("C1", []string{"t1"}, checkC1, nil) + err = r1.AddCondition("C1", []string{"t1"}, checkC1, nil) + assert.Nil(t, err) r1.SetActionService(createActionServiceFromFunction(t, actionA1)) r1.SetPriority(1) r1.SetContext(actionMap) - rs.AddRule(r1) + err = rs.AddRule(r1) + assert.Nil(t, err) // rule 2 r2 := ruleapi.NewRule("R2") - r2.AddCondition("C2", []string{"t1"}, checkC2, nil) + err = r2.AddCondition("C2", []string{"t1"}, checkC2, nil) + assert.Nil(t, err) r2.SetActionService(createActionServiceFromFunction(t, actionA2)) r2.SetPriority(2) r2.SetContext(actionMap) - rs.AddRule(r2) + err = rs.AddRule(r2) + assert.Nil(t, err) // rule 3 r3 := ruleapi.NewRule("R3") - r3.AddCondition("C3", []string{"t1"}, checkC3, nil) + err = r3.AddCondition("C3", []string{"t1"}, checkC3, nil) + assert.Nil(t, err) r3.SetActionService(createActionServiceFromFunction(t, actionA3)) r3.SetPriority(3) r3.SetContext(actionMap) - rs.AddRule(r3) + err = rs.AddRule(r3) + assert.Nil(t, err) //Start the rule session - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) //Now assert a "t1" tuple - t1, _ := model.NewTupleWithKeyValues("t1", "Tom") + t1, err := model.NewTupleWithKeyValues("t1", "Tom") + assert.Nil(t, err) t1.SetString(nil, "p3", "test") - rs.Assert(nil, t1) + err = rs.Assert(nil, t1) + assert.Nil(t, err) //unregister the session, i.e; cleanup deleteRuleSession(t, rs, t1) diff --git a/ruleapi/tests/rules_2_test.go b/ruleapi/tests/rules_2_test.go index 869e062..ed9d0cd 100644 --- a/ruleapi/tests/rules_2_test.go +++ b/ruleapi/tests/rules_2_test.go @@ -8,6 +8,8 @@ import ( "github.com/project-flogo/rules/common" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) func Test_Two(t *testing.T) { @@ -31,30 +33,41 @@ func Test_Two(t *testing.T) { if redis { store = "rsconfig.json" } - rs, _ := ruleapi.GetOrCreateRuleSession("asession", store) + rs, err := ruleapi.GetOrCreateRuleSession("asession", store) + assert.Nil(t, err) actionFireCount := make(map[string]int) //// check for name "Bob" in n1 rule := ruleapi.NewRule("rule1") - rule.AddCondition("c1", []string{"n1"}, checkForBob, nil) - rule.AddCondition("c2", []string{"n1"}, checkForName, nil) + err = rule.AddCondition("c1", []string{"n1"}, checkForBob, nil) + assert.Nil(t, err) + err = rule.AddCondition("c2", []string{"n1"}, checkForName, nil) + assert.Nil(t, err) rule.SetActionService(createActionServiceFromFunction(t, checkForBobAction)) rule.SetContext(actionFireCount) - rs.AddRule(rule) + err = rs.AddRule(rule) + assert.Nil(t, err) fmt.Printf("Rule added: [%s]\n", rule.GetName()) //Start the rule session - rs.Start(nil) - t1, _ := model.NewTupleWithKeyValues("n1", "Tom") - rs.Assert(nil, t1) - - t2, _ := model.NewTupleWithKeyValues("n1", "Bob") - rs.Assert(nil, t2) + err = rs.Start(nil) + assert.Nil(t, err) + t1, err := model.NewTupleWithKeyValues("n1", "Tom") + assert.Nil(t, err) + err = rs.Assert(nil, t1) + assert.Nil(t, err) + + t2, err := model.NewTupleWithKeyValues("n1", "Bob") + assert.Nil(t, err) + err = rs.Assert(nil, t2) + assert.Nil(t, err) //Retract tuples - rs.Retract(nil, t1) - rs.Retract(nil, t2) + err = rs.Retract(nil, t1) + assert.Nil(t, err) + err = rs.Retract(nil, t2) + assert.Nil(t, err) if cnt, found := actionFireCount["count"]; found { if cnt > 1 { diff --git a/ruleapi/tests/rules_3_test.go b/ruleapi/tests/rules_3_test.go index d686f64..c57f58c 100644 --- a/ruleapi/tests/rules_3_test.go +++ b/ruleapi/tests/rules_3_test.go @@ -7,6 +7,8 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) var actCnt uint64 @@ -14,40 +16,46 @@ var actCnt uint64 //Forward chain-Data change in r3action and r32action triggers the r32action. func Test_Three(t *testing.T) { actCnt = 0 - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) actionMap := make(map[string]string) rule := ruleapi.NewRule("R3") - rule.AddCondition("R3c1", []string{"t1.id"}, trueCondition, nil) + err = rule.AddCondition("R3c1", []string{"t1.id"}, trueCondition, nil) + assert.Nil(t, err) rule.SetActionService(createActionServiceFromFunction(t, r3action)) rule.SetPriority(1) - rs.AddRule(rule) + err = rs.AddRule(rule) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule.GetName()) rule1 := ruleapi.NewRule("R32") - rule1.AddCondition("R32c1", []string{"t1.p1"}, r3Condition, nil) + err = rule1.AddCondition("R32c1", []string{"t1.p1"}, r3Condition, nil) + assert.Nil(t, err) rule1.SetActionService(createActionServiceFromFunction(t, r32action)) rule1.SetPriority(1) rule1.SetContext(actionMap) - rs.AddRule(rule1) + err = rs.AddRule(rule1) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule1.GetName()) - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) - t1, _ := model.NewTupleWithKeyValues("t1", "t10") - t1.SetInt(context.TODO(), "p1", 2000) - err := rs.Assert(context.TODO(), t1) - if err != nil { - t.Fatal(err) - } + t1, err := model.NewTupleWithKeyValues("t1", "t10") + assert.Nil(t, err) + err = t1.SetInt(context.TODO(), "p1", 2000) + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t1) + assert.Nil(t, err) - t2, _ := model.NewTupleWithKeyValues("t1", "t11") - t2.SetInt(context.TODO(), "p1", 2000) + t2, err := model.NewTupleWithKeyValues("t1", "t11") + assert.Nil(t, err) + err = t2.SetInt(context.TODO(), "p1", 2000) + assert.Nil(t, err) err = rs.Assert(context.TODO(), t2) - if err != nil { - t.Fatal(err) - } + assert.Nil(t, err) if count := atomic.LoadUint64(&actCnt); count != 2 { t.Errorf("Expecting [2] actions, got [%d]", count) diff --git a/ruleapi/tests/sessions_test.go b/ruleapi/tests/sessions_test.go index a2483f4..ba997b0 100644 --- a/ruleapi/tests/sessions_test.go +++ b/ruleapi/tests/sessions_test.go @@ -8,84 +8,88 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" + + "github.com/stretchr/testify/assert" ) func TestClearSessions(t *testing.T) { - ruleapi.GetOrCreateRuleSession("test", "") + _, err := ruleapi.GetOrCreateRuleSession("test", "") + assert.Nil(t, err) ruleapi.ClearSessions() } func TestAssert(t *testing.T) { - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) rule := ruleapi.NewRule("R2") - rule.AddCondition("R2_c1", []string{"t4.none"}, trueCondition, nil) + err = rule.AddCondition("R2_c1", []string{"t4.none"}, trueCondition, nil) + assert.Nil(t, err) rule.SetActionService(createActionServiceFromFunction(t, emptyAction)) rule.SetPriority(1) - rs.AddRule(rule) + err = rs.AddRule(rule) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule.GetName()) - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) - t1, _ := model.NewTupleWithKeyValues("t4", "t4") - err := rs.Assert(context.TODO(), t1) - if err != nil { - t.Fatalf("err should be nil: %v", err) - } + t1, err := model.NewTupleWithKeyValues("t4", "t4") + assert.Nil(t, err) err = rs.Assert(context.TODO(), t1) - if err == nil { - t.Fatalf("err should be not be nil: %v", err) - } + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t1) + assert.NotNil(t, err) time.Sleep(2 * time.Second) err = rs.Assert(context.TODO(), t1) - if err != nil { - t.Fatalf("err should be nil: %v", err) - } + assert.Nil(t, err) deleteRuleSession(t, rs, t1) } func TestRace(t *testing.T) { - rs, _ := createRuleSession() + rs, err := createRuleSession() + assert.Nil(t, err) defer rs.Unregister() rule := ruleapi.NewRule("R2") - rule.AddCondition("R2_c1", []string{"t4.none"}, trueCondition, nil) + err = rule.AddCondition("R2_c1", []string{"t4.none"}, trueCondition, nil) + assert.Nil(t, err) rule.SetActionService(createActionServiceFromFunction(t, emptyAction)) rule.SetPriority(1) - rs.AddRule(rule) + err = rs.AddRule(rule) + assert.Nil(t, err) t.Logf("Rule added: [%s]\n", rule.GetName()) - rs.Start(nil) + err = rs.Start(nil) + assert.Nil(t, err) done := make(chan bool, 8) withTTL := func() { for i := 0; i < 10; i++ { - t1, _ := model.NewTupleWithKeyValues("t4", fmt.Sprintf("ttl%d", i)) - err := rs.Assert(context.TODO(), t1) - if err != nil { - t.Fatalf("err should be nil: %v", err) - } + t1, err := model.NewTupleWithKeyValues("t4", fmt.Sprintf("ttl%d", i)) + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t1) + assert.Nil(t, err) time.Sleep(10 * time.Millisecond) } done <- true } withDelete := func() { for i := 0; i < 10; i++ { - t1, _ := model.NewTupleWithKeyValues("t3", fmt.Sprintf("delete%d", i)) - err := rs.Assert(context.TODO(), t1) - if err != nil { - t.Fatalf("err should be nil: %v", err) - } + t1, err := model.NewTupleWithKeyValues("t3", fmt.Sprintf("delete%d", i)) + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t1) + assert.Nil(t, err) time.Sleep(10 * time.Millisecond) - rs.Delete(context.TODO(), t1) + err = rs.Delete(context.TODO(), t1) + assert.Nil(t, err) time.Sleep(10 * time.Millisecond) } done <- true } addOnly := func() { for i := 0; i < 10; i++ { - t1, _ := model.NewTupleWithKeyValues("t3", fmt.Sprintf("add%d", i)) - err := rs.Assert(context.TODO(), t1) - if err != nil { - t.Fatalf("err should be nil: %v", err) - } + t1, err := model.NewTupleWithKeyValues("t3", fmt.Sprintf("add%d", i)) + assert.Nil(t, err) + err = rs.Assert(context.TODO(), t1) + assert.Nil(t, err) time.Sleep(10 * time.Millisecond) } done <- true From e84cb4429b7b6fa3b74f0e40780e3d93a62b54ab Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Wed, 9 Oct 2019 16:52:30 -0600 Subject: [PATCH 085/125] Add tuple to store when added to join table --- rete/joinnode.go | 10 ++++++++++ rete/network.go | 32 ++++++++++++++++++-------------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/rete/joinnode.go b/rete/joinnode.go index 48e34fd..5302269 100644 --- a/rete/joinnode.go +++ b/rete/joinnode.go @@ -178,6 +178,11 @@ func (jn *joinNodeImpl) assertFromRight(ctx context.Context, handles []types.Ret //TODO: other stuff. right now focus on tuple table jn.joinRightObjects(handles, joinedHandles) //tupleTableRow := newJoinTableRow(handles, jn.nw.incrementAndGetId()) + for _, handle := range handles { + if status := handle.GetStatus(); status == types.ReteHandleStatusCreating { + jn.GetNw().GetTupleStore().SaveTuple(handle.GetTuple()) + } + } jn.rightTable.AddRow(handles) //TODO: rete listeners etc. rIterator := jn.leftTable.GetRowIterator(ctx) @@ -245,6 +250,11 @@ func (jn *joinNodeImpl) assertFromLeft(ctx context.Context, handles []types.Rete jn.joinLeftObjects(handles, joinedHandles) //TODO: other stuff. right now focus on tuple table //tupleTableRow := newJoinTableRow(handles, jn.nw.incrementAndGetId()) + for _, handle := range handles { + if status := handle.GetStatus(); status == types.ReteHandleStatusCreating { + jn.GetNw().GetTupleStore().SaveTuple(handle.GetTuple()) + } + } jn.leftTable.AddRow(handles) //TODO: rete listeners etc. rIterator := jn.rightTable.GetRowIterator(ctx) diff --git a/rete/network.go b/rete/network.go index 20008b2..860574c 100644 --- a/rete/network.go +++ b/rete/network.go @@ -649,25 +649,25 @@ func (nw *reteNetworkImpl) RetractInternal(ctx context.Context, tuple model.Tupl } else if handle.GetStatus() != types.ReteHandleStatusCreated { return fmt.Errorf("Tuple with key [%s] is being asserted or deleted: %d", tuple.GetKey().String(), handle.GetStatus()) } - if mode == common.DELETE { - handle.SetStatus(types.ReteHandleStatusDeleting) - } else if mode == common.RETRACT { - handle.SetStatus(types.ReteHandleStatusRetracting) - } if ctx == nil { ctx = context.Background() } rCtx, _, newCtx := getOrSetReteCtx(ctx, nw, nil) - nw.removeJoinTableRowRefs(newCtx, handle, changedProps) - // add it to the delete list if mode == common.DELETE { - rCtx.AddToRtcDeleted(tuple) - nw.handleService.RemoveHandle(tuple) - } else if mode == common.RETRACT { - handle.SetStatus(types.ReteHandleStatusRetracted) + handle.SetStatus(types.ReteHandleStatusDeleting) + defer func() { + rCtx.AddToRtcDeleted(tuple) + nw.handleService.RemoveHandle(tuple) + }() + } else if mode == common.RETRACT || mode == common.MODIFY { + handle.SetStatus(types.ReteHandleStatusRetracting) + defer handle.SetStatus(types.ReteHandleStatusRetracted) } + + nw.removeJoinTableRowRefs(newCtx, handle, changedProps) + return nil } @@ -681,10 +681,14 @@ func (nw *reteNetworkImpl) GetAssertedTuple(ctx context.Context, rs model.RuleSe } func (nw *reteNetworkImpl) AssertInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) error { - if mode == common.ADD { + if mode == common.ADD || mode == common.MODIFY { handle, exists := nw.GetOrCreateHandle(ctx, tuple) - if exists && handle.GetStatus() != types.ReteHandleStatusRetracted { - return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) + if exists { + if handle.GetStatus() == types.ReteHandleStatusRetracted { + handle.SetStatus(types.ReteHandleStatusCreating) + } else { + return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) + } } defer handle.SetStatus(types.ReteHandleStatusCreated) } From f0dcce0b57b119d8ea32e04b5c082838b2b78a14 Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Fri, 11 Oct 2019 18:05:10 -0600 Subject: [PATCH 086/125] Improved handles --- rete/classnode.go | 2 +- rete/conflict.go | 4 +- rete/internal/mem/mhandleservice.go | 56 ++++++++++-- rete/internal/mem/mretehandle.go | 14 ++- rete/internal/redis/rhandleservice.go | 108 +++++++++++++++++++++-- rete/internal/redis/rjointablerowimpl.go | 2 +- rete/internal/redis/rretehandle.go | 7 +- rete/internal/types/types.go | 7 +- rete/network.go | 42 +++++---- 9 files changed, 199 insertions(+), 43 deletions(-) diff --git a/rete/classnode.go b/rete/classnode.go index 4536bc0..a796311 100644 --- a/rete/classnode.go +++ b/rete/classnode.go @@ -76,7 +76,7 @@ func (cn *classNodeImpl) String() string { } func (cn *classNodeImpl) assert(ctx context.Context, tuple model.Tuple, changedProps map[string]bool) { - handle, _ := getOrCreateHandle(ctx, tuple) + handle := getHandleWithTuple(ctx, tuple) handles := make([]types.ReteHandle, 1) handles[0] = handle propagate := false diff --git a/rete/conflict.go b/rete/conflict.go index 8141df2..6d2cba6 100644 --- a/rete/conflict.go +++ b/rete/conflict.go @@ -85,13 +85,13 @@ func (cr *conflictResImpl) ResolveConflict(ctx context.Context) { func (cr *conflictResImpl) DeleteAgendaFor(ctx context.Context, modifiedTuple model.Tuple, changeProps map[string]bool) { - hdlModified, _ := getOrCreateHandle(ctx, modifiedTuple) + hdlModified := getHandleWithTuple(ctx, modifiedTuple) for e := cr.agendaList.Front(); e != nil; { item := e.Value.(agendaItem) next := e.Next() for _, tuple := range item.getTuples() { - hdl, _ := getOrCreateHandle(ctx, tuple) + hdl := getHandleWithTuple(ctx, tuple) if hdl == hdlModified { //this agendaitem has the modified tuple, remove the agenda item! toRemove := true //check if the rule depends on this change prop diff --git a/rete/internal/mem/mhandleservice.go b/rete/internal/mem/mhandleservice.go index 3c7bf76..e7e9c41 100644 --- a/rete/internal/mem/mhandleservice.go +++ b/rete/internal/mem/mhandleservice.go @@ -2,7 +2,10 @@ package mem import ( "context" + "math/rand" "sync" + "sync/atomic" + "time" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/rete/internal/types" @@ -10,14 +13,19 @@ import ( type handleServiceImpl struct { types.NwServiceImpl - allHandles map[string]types.ReteHandle + allHandles map[string]*reteHandleImpl + rand.Source sync.RWMutex } func NewHandleCollection(nw types.Network, config map[string]interface{}) types.HandleService { - hc := handleServiceImpl{} - hc.Nw = nw - hc.allHandles = make(map[string]types.ReteHandle) + hc := handleServiceImpl{ + NwServiceImpl: types.NwServiceImpl{ + Nw: nw, + }, + allHandles: make(map[string]*reteHandleImpl), + Source: rand.NewSource(time.Now().UnixNano()), + } return &hc } @@ -47,14 +55,48 @@ func (hc *handleServiceImpl) GetHandleByKey(ctx context.Context, key model.Tuple return hc.allHandles[key.String()] } -func (hc *handleServiceImpl) GetOrCreateHandle(nw types.Network, tuple model.Tuple) (types.ReteHandle, bool) { +func (hc *handleServiceImpl) GetOrCreateLockedHandle(nw types.Network, tuple model.Tuple) (types.ReteHandle, bool) { + hc.Lock() + defer hc.Unlock() + id := hc.Int63() + h, found := hc.allHandles[tuple.GetKey().String()] + if !found { + h = newReteHandleImpl(nw, tuple, types.ReteHandleStatusCreating, id) + hc.allHandles[tuple.GetKey().String()] = h + return h, false + } + + if atomic.CompareAndSwapInt64(&h.id, -1, id) { + return h, false + } + + return nil, true +} + +func (hc *handleServiceImpl) GetLockedHandle(nw types.Network, tuple model.Tuple) (types.ReteHandle, bool) { + hc.Lock() + defer hc.Unlock() + id := hc.Int63() + h, found := hc.allHandles[tuple.GetKey().String()] + if !found { + return nil, true + } + + if atomic.CompareAndSwapInt64(&h.id, -1, id) { + return h, false + } + + return nil, true +} + +func (hc *handleServiceImpl) GetHandleWithTuple(nw types.Network, tuple model.Tuple) types.ReteHandle { hc.Lock() defer hc.Unlock() h, found := hc.allHandles[tuple.GetKey().String()] if !found { - h = newReteHandleImpl(nw, tuple, types.ReteHandleStatusCreating) + h = newReteHandleImpl(nw, tuple, types.ReteHandleStatusCreating, 0) hc.allHandles[tuple.GetKey().String()] = h //[tuple.GetKey().String()] = h } - return h, found + return h } diff --git a/rete/internal/mem/mretehandle.go b/rete/internal/mem/mretehandle.go index 7265396..2044782 100644 --- a/rete/internal/mem/mretehandle.go +++ b/rete/internal/mem/mretehandle.go @@ -1,6 +1,8 @@ package mem import ( + "sync/atomic" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/rete/internal/types" ) @@ -12,11 +14,12 @@ type reteHandleImpl struct { tuple model.Tuple tupleKey model.TupleKey status types.ReteHandleStatus + id int64 } -func newReteHandleImpl(nw types.Network, tuple model.Tuple, status types.ReteHandleStatus) types.ReteHandle { +func newReteHandleImpl(nw types.Network, tuple model.Tuple, status types.ReteHandleStatus, id int64) *reteHandleImpl { h1 := reteHandleImpl{} - h1.initHandleImpl(nw, tuple, status) + h1.initHandleImpl(nw, tuple, status, id) return &h1 } @@ -25,11 +28,12 @@ func (hdl *reteHandleImpl) SetTuple(tuple model.Tuple) { hdl.tupleKey = tuple.GetKey() } -func (hdl *reteHandleImpl) initHandleImpl(nw types.Network, tuple model.Tuple, status types.ReteHandleStatus) { +func (hdl *reteHandleImpl) initHandleImpl(nw types.Network, tuple model.Tuple, status types.ReteHandleStatus, id int64) { hdl.SetID(nw) hdl.SetTuple(tuple) hdl.tupleKey = tuple.GetKey() hdl.status = status + hdl.id = id } func (hdl *reteHandleImpl) GetTuple() model.Tuple { @@ -44,6 +48,10 @@ func (hdl *reteHandleImpl) SetStatus(status types.ReteHandleStatus) { hdl.status = status } +func (hdl *reteHandleImpl) Unlock() { + atomic.StoreInt64(&hdl.id, -1) +} + func (hdl *reteHandleImpl) GetStatus() types.ReteHandleStatus { return hdl.status } diff --git a/rete/internal/redis/rhandleservice.go b/rete/internal/redis/rhandleservice.go index 8c1a876..3c45a86 100644 --- a/rete/internal/redis/rhandleservice.go +++ b/rete/internal/redis/rhandleservice.go @@ -2,7 +2,10 @@ package redis import ( "context" + "math/rand" "strconv" + "sync" + "time" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/redisutils" @@ -14,16 +17,28 @@ type handleServiceImpl struct { types.NwServiceImpl prefix string config map[string]interface{} + rand.Source + sync.Mutex } func NewHandleCollection(nw types.Network, config map[string]interface{}) types.HandleService { - hc := handleServiceImpl{} - hc.Nw = nw - hc.config = config + hc := handleServiceImpl{ + NwServiceImpl: types.NwServiceImpl{ + Nw: nw, + }, + config: config, + Source: rand.NewSource(time.Now().UnixNano()), + } //hc.allHandles = make(map[string]types.ReteHandle) return &hc } +func (hc *handleServiceImpl) Int63() int64 { + hc.Lock() + defer hc.Unlock() + return hc.Source.Int63() +} + func (hc *handleServiceImpl) Init() { hc.prefix = hc.Nw.GetPrefix() + ":h:" reteCfg := hc.config["rete"].(map[string]interface{}) @@ -37,7 +52,7 @@ func (hc *handleServiceImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle { rkey := hc.prefix + tuple.GetKey().String() redisutils.GetRedisHdl().Del(rkey) //TODO: Dummy handle - h := newReteHandleImpl(hc.GetNw(), tuple, rkey, types.ReteHandleStatusUnknown) + h := newReteHandleImpl(hc.GetNw(), tuple, rkey, types.ReteHandleStatusUnknown, -1) return h } @@ -67,6 +82,16 @@ func (hc *handleServiceImpl) GetHandleByKey(ctx context.Context, key model.Tuple } else { panic("missing status") } + id := int64(-1) + if value, ok := m["id"]; ok { + if value, ok := value.(string); ok { + number, err := strconv.ParseInt(value, 10, 64) + if err != nil { + panic(err) + } + id = number + } + } var tuple model.Tuple if ctx != nil { @@ -93,13 +118,20 @@ func (hc *handleServiceImpl) GetHandleByKey(ctx context.Context, key model.Tuple return nil } - h := newReteHandleImpl(hc.GetNw(), tuple, rkey, status) + h := newReteHandleImpl(hc.GetNw(), tuple, rkey, status, id) return h } -func (hc *handleServiceImpl) GetOrCreateHandle(nw types.Network, tuple model.Tuple) (types.ReteHandle, bool) { +func (hc *handleServiceImpl) GetOrCreateLockedHandle(nw types.Network, tuple model.Tuple) (types.ReteHandle, bool) { + id := hc.Int63() key, status := hc.prefix+tuple.GetKey().String(), types.ReteHandleStatusCreating - exists, _ := redisutils.GetRedisHdl().HSetNX(key, "status", status) + + exists, _ := redisutils.GetRedisHdl().HSetNX(key, "id", id) + if exists { + return nil, true + } + + exists, _ = redisutils.GetRedisHdl().HSetNX(key, "status", status) if exists { m := redisutils.GetRedisHdl().HGetAll(key) if len(m) > 0 { @@ -119,6 +151,64 @@ func (hc *handleServiceImpl) GetOrCreateHandle(nw types.Network, tuple model.Tup } } - h := newReteHandleImpl(nw, tuple, key, status) - return h, exists + h := newReteHandleImpl(nw, tuple, key, status, id) + return h, false +} + +func (hc *handleServiceImpl) GetLockedHandle(nw types.Network, tuple model.Tuple) (types.ReteHandle, bool) { + id := hc.Int63() + key := hc.prefix + tuple.GetKey().String() + + exists, _ := redisutils.GetRedisHdl().HSetNX(key, "id", id) + if exists { + return nil, true + } + + m := redisutils.GetRedisHdl().HGetAll(key) + if len(m) > 0 { + if value, ok := m["status"]; ok { + if value, ok := value.(string); ok { + number, err := strconv.Atoi(value) + if err != nil { + panic(err) + } + h := newReteHandleImpl(nw, tuple, key, types.ReteHandleStatus(number), id) + return h, false + } + } + } + + return nil, true +} + +func (hc *handleServiceImpl) GetHandleWithTuple(nw types.Network, tuple model.Tuple) types.ReteHandle { + key, status, id := hc.prefix+tuple.GetKey().String(), types.ReteHandleStatusCreating, int64(-1) + m := redisutils.GetRedisHdl().HGetAll(key) + if len(m) > 0 { + if value, ok := m["status"]; ok { + if value, ok := value.(string); ok { + number, err := strconv.Atoi(value) + if err != nil { + panic(err) + } + status = types.ReteHandleStatus(number) + } else { + panic("status not string") + } + } else { + panic("missing status") + } + if value, ok := m["id"]; ok { + if value, ok := value.(string); ok { + number, err := strconv.ParseInt(value, 10, 64) + if err != nil { + panic(err) + } + id = number + } + } + } + + h := newReteHandleImpl(nw, tuple, key, status, id) + return h } diff --git a/rete/internal/redis/rjointablerowimpl.go b/rete/internal/redis/rjointablerowimpl.go index 4976566..bf0751c 100644 --- a/rete/internal/redis/rjointablerowimpl.go +++ b/rete/internal/redis/rjointablerowimpl.go @@ -80,7 +80,7 @@ func createRow(ctx context.Context, jtKey string, rowID string, key string, nw t if tuple == nil { tuple = nw.GetTupleStore().GetTupleByKey(tupleKey) } - handle := newReteHandleImpl(nw, tuple, "", types.ReteHandleStatusUnknown) + handle := newReteHandleImpl(nw, tuple, "", types.ReteHandleStatusUnknown, 0) handles = append(handles, handle) } diff --git a/rete/internal/redis/rretehandle.go b/rete/internal/redis/rretehandle.go index 005f86c..a1e1e53 100644 --- a/rete/internal/redis/rretehandle.go +++ b/rete/internal/redis/rretehandle.go @@ -14,10 +14,11 @@ type reteHandleImpl struct { tupleKey model.TupleKey key string status types.ReteHandleStatus + id int64 //jtRefs types.JtRefsService } -func newReteHandleImpl(nw types.Network, tuple model.Tuple, key string, status types.ReteHandleStatus) types.ReteHandle { +func newReteHandleImpl(nw types.Network, tuple model.Tuple, key string, status types.ReteHandleStatus, id int64) types.ReteHandle { h1 := reteHandleImpl{} h1.initHandleImpl(nw, tuple, key, status) return &h1 @@ -52,6 +53,10 @@ func (hdl *reteHandleImpl) SetStatus(status types.ReteHandleStatus) { redisutils.GetRedisHdl().HSet(hdl.key, "status", status) } +func (hdl *reteHandleImpl) Unlock() { + redisutils.GetRedisHdl().HDel(hdl.key, "id") +} + func (hdl *reteHandleImpl) GetStatus() types.ReteHandleStatus { return hdl.status } diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index 8fd8c01..34b393f 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -10,7 +10,7 @@ import ( type Network interface { common.Network - GetOrCreateHandle(ctx context.Context, tuple model.Tuple) (ReteHandle, bool) + GetHandleWithTuple(ctx context.Context, tuple model.Tuple) ReteHandle AssertInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) error RetractInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) error GetPrefix() string @@ -88,6 +88,7 @@ type ReteHandle interface { GetTupleKey() model.TupleKey SetStatus(status ReteHandleStatus) GetStatus() ReteHandleStatus + Unlock() } type JtRefsService interface { @@ -108,7 +109,9 @@ type HandleService interface { RemoveHandle(tuple model.Tuple) ReteHandle GetHandle(ctx context.Context, tuple model.Tuple) ReteHandle GetHandleByKey(ctx context.Context, key model.TupleKey) ReteHandle - GetOrCreateHandle(nw Network, tuple model.Tuple) (ReteHandle, bool) + GetHandleWithTuple(nw Network, tuple model.Tuple) ReteHandle + GetOrCreateLockedHandle(nw Network, tuple model.Tuple) (ReteHandle, bool) + GetLockedHandle(nw Network, tuple model.Tuple) (ReteHandle, bool) } type IdGen interface { diff --git a/rete/network.go b/rete/network.go index 860574c..95655a7 100644 --- a/rete/network.go +++ b/rete/network.go @@ -643,11 +643,12 @@ func (nw *reteNetworkImpl) Retract(ctx context.Context, rs model.RuleSession, tu } func (nw *reteNetworkImpl) RetractInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) error { - handle := nw.handleService.GetHandle(ctx, tuple) - if handle == nil { - return fmt.Errorf("Tuple with key [%s] doesn't exist", tuple.GetKey().String()) + handle, locked := nw.handleService.GetLockedHandle(nw, tuple) + if locked { + return fmt.Errorf("Tuple with key [%s] is locked or doesn't exist", tuple.GetKey().String()) } else if handle.GetStatus() != types.ReteHandleStatusCreated { - return fmt.Errorf("Tuple with key [%s] is being asserted or deleted: %d", tuple.GetKey().String(), handle.GetStatus()) + handle.Unlock() + return fmt.Errorf("Tuple with key [%s] is not created: %d", tuple.GetKey().String(), handle.GetStatus()) } if ctx == nil { @@ -663,7 +664,10 @@ func (nw *reteNetworkImpl) RetractInternal(ctx context.Context, tuple model.Tupl }() } else if mode == common.RETRACT || mode == common.MODIFY { handle.SetStatus(types.ReteHandleStatusRetracting) - defer handle.SetStatus(types.ReteHandleStatusRetracted) + defer func() { + handle.SetStatus(types.ReteHandleStatusRetracted) + handle.Unlock() + }() } nw.removeJoinTableRowRefs(newCtx, handle, changedProps) @@ -682,15 +686,19 @@ func (nw *reteNetworkImpl) GetAssertedTuple(ctx context.Context, rs model.RuleSe func (nw *reteNetworkImpl) AssertInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) error { if mode == common.ADD || mode == common.MODIFY { - handle, exists := nw.GetOrCreateHandle(ctx, tuple) - if exists { - if handle.GetStatus() == types.ReteHandleStatusRetracted { - handle.SetStatus(types.ReteHandleStatusCreating) - } else { - return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) - } + handle, locked := nw.handleService.GetOrCreateLockedHandle(nw, tuple) + if locked { + return fmt.Errorf("Tuple with key [%s] is locked", tuple.GetKey().String()) + } else if handle.GetStatus() == types.ReteHandleStatusRetracted { + handle.SetStatus(types.ReteHandleStatusCreating) + } else if handle.GetStatus() == types.ReteHandleStatusCreated { + handle.Unlock() + return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) } - defer handle.SetStatus(types.ReteHandleStatusCreated) + defer func() { + handle.SetStatus(types.ReteHandleStatusCreated) + handle.Unlock() + }() } tupleType := tuple.GetTupleType() @@ -711,8 +719,8 @@ func (nw *reteNetworkImpl) AssertInternal(ctx context.Context, tuple model.Tuple return nil } -func (nw *reteNetworkImpl) GetOrCreateHandle(ctx context.Context, tuple model.Tuple) (types.ReteHandle, bool) { - return nw.handleService.GetOrCreateHandle(nw, tuple) +func (nw *reteNetworkImpl) GetHandleWithTuple(ctx context.Context, tuple model.Tuple) types.ReteHandle { + return nw.handleService.GetHandleWithTuple(nw, tuple) } func (nw *reteNetworkImpl) getHandle(ctx context.Context, tuple model.Tuple) types.ReteHandle { @@ -745,9 +753,9 @@ func (nw *reteNetworkImpl) GetTupleStore() model.TupleStore { return nw.tupleStore } -func getOrCreateHandle(ctx context.Context, tuple model.Tuple) (types.ReteHandle, bool) { +func getHandleWithTuple(ctx context.Context, tuple model.Tuple) types.ReteHandle { reteCtxVar := getReteCtx(ctx) - return reteCtxVar.GetNetwork().GetOrCreateHandle(ctx, tuple) + return reteCtxVar.GetNetwork().GetHandleWithTuple(ctx, tuple) } func (nw *reteNetworkImpl) removeJoinTableRowRefs(ctx context.Context, hdl types.ReteHandle, changedProps map[string]bool) { From 60cd8c053339c480df22c7e6a87b5c480db8c050 Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Mon, 14 Oct 2019 13:30:54 -0600 Subject: [PATCH 087/125] Added mutex to redis JtService --- rete/internal/redis/rjtservice.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rete/internal/redis/rjtservice.go b/rete/internal/redis/rjtservice.go index e4e753f..e545f0b 100644 --- a/rete/internal/redis/rjtservice.go +++ b/rete/internal/redis/rjtservice.go @@ -1,6 +1,8 @@ package redis import ( + "sync" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/rete/internal/types" ) @@ -8,6 +10,7 @@ import ( type jtServiceImpl struct { types.NwServiceImpl allJoinTables map[string]types.JoinTable + sync.RWMutex } func NewJoinTableCollection(nw types.Network, config map[string]interface{}) types.JtService { @@ -21,10 +24,14 @@ func (jtc *jtServiceImpl) Init() { } func (jtc *jtServiceImpl) GetJoinTable(name string) types.JoinTable { + jtc.RLock() + defer jtc.RUnlock() return jtc.allJoinTables[name] } func (jtc *jtServiceImpl) GetOrCreateJoinTable(nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) types.JoinTable { + jtc.Lock() + defer jtc.Unlock() jT, found := jtc.allJoinTables[name] if !found { jT = newJoinTableImpl(nw, rule, identifiers, name) From a74526d02fe02e258229a0979ebc379a02c3d206 Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Mon, 14 Oct 2019 14:57:17 -0600 Subject: [PATCH 088/125] Refactor configuration --- redisutils/redisutil_test.go | 8 +- redisutils/redisutils.go | 18 ++-- rete/common/types.go | 26 ++++++ rete/factory.go | 115 ++++++++----------------- rete/internal/mem/mhandleservice.go | 3 +- rete/internal/mem/midgenservice.go | 8 +- rete/internal/mem/mjtrefsservice.go | 3 +- rete/internal/mem/mjtservice.go | 3 +- rete/internal/redis/rhandleservice.go | 11 +-- rete/internal/redis/ridgenservice.go | 11 +-- rete/internal/redis/rjtrefsservice.go | 3 +- rete/internal/redis/rjtservice.go | 3 +- ruleapi/internal/store/mem/mstore.go | 3 +- ruleapi/internal/store/redis/rstore.go | 19 ++-- ruleapi/rulesession.go | 63 ++++++++++---- 15 files changed, 158 insertions(+), 139 deletions(-) diff --git a/redisutils/redisutil_test.go b/redisutils/redisutil_test.go index ea370dd..0f17f70 100644 --- a/redisutils/redisutil_test.go +++ b/redisutils/redisutil_test.go @@ -66,7 +66,7 @@ func TestMain(m *testing.M) { func Test_first(t *testing.T) { - InitService(nil) + InitService(RedisConfig{}) rd := GetRedisHdl() m := make(map[string]interface{}) @@ -211,7 +211,7 @@ func getStruct(c redis.Conn) error { func Test_three(t *testing.T) { - InitService(nil) + InitService(RedisConfig{}) hdl := GetRedisHdl() @@ -241,7 +241,7 @@ func Test_three(t *testing.T) { } func Test_four(t *testing.T) { - InitService(nil) + InitService(RedisConfig{}) hdl := GetRedisHdl() @@ -252,7 +252,7 @@ func Test_four(t *testing.T) { } func Test_five(t *testing.T) { - InitService(nil) + InitService(RedisConfig{}) hdl := GetRedisHdl() diff --git a/redisutils/redisutils.go b/redisutils/redisutils.go index d41448d..682b77e 100644 --- a/redisutils/redisutils.go +++ b/redisutils/redisutils.go @@ -9,22 +9,28 @@ var rd RedisHdl type RedisHdl = *RedisHandle +type RedisConfig struct { + Network string `json:"network"` + Address string `json:"address"` +} + type RedisHandle struct { - config map[string]interface{} + config RedisConfig pool *redis.Pool network string address string } -func InitService(config map[string]interface{}) { +func InitService(config RedisConfig) { if rd == nil { rd = &RedisHandle{} rd.config = config - if config != nil { - rd.network = rd.config["network"].(string) - rd.address = rd.config["address"].(string) - } else { + rd.network = config.Network + if rd.network == "" { rd.network = "tcp" + } + rd.address = config.Address + if rd.address == "" { rd.address = ":6379" } rd.newPool(rd.network, rd.address) diff --git a/rete/common/types.go b/rete/common/types.go index 4b8d1c4..2285ec6 100644 --- a/rete/common/types.go +++ b/rete/common/types.go @@ -4,6 +4,7 @@ import ( "context" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/redisutils" ) type RtcOprn int @@ -34,3 +35,28 @@ type Network interface { SetTupleStore(tupleStore model.TupleStore) } + +const ( + ServiceTypeMem = "mem" + ServiceTypeRedis = "redis" +) + +type Service struct { + Mem map[string]interface{} `json:"mem"` + Redis redisutils.RedisConfig `json:"redis"` +} + +type Config struct { + Rs struct { + Prefix string `json:"prefix"` + StoreRef string `json:"store-ref"` + } `json:"rs"` + Rete struct { + JtRef string `json:"jt-ref"` + IDGenRef string `json:"idgen-ref"` + Jt string `json:"jt"` + } `json:"rete"` + Stores Service `json:"stores"` + IDGens Service `json:"idgens"` + Jts Service `json:"jts"` +} diff --git a/rete/factory.go b/rete/factory.go index c269cdf..1336bc6 100644 --- a/rete/factory.go +++ b/rete/factory.go @@ -2,21 +2,23 @@ package rete import ( "encoding/json" + + "github.com/project-flogo/rules/rete/common" "github.com/project-flogo/rules/rete/internal/mem" "github.com/project-flogo/rules/rete/internal/redis" "github.com/project-flogo/rules/rete/internal/types" ) type TypeFactory struct { - nw *reteNetworkImpl - config string - parsedJson map[string]interface{} + nw *reteNetworkImpl + config string + parsed common.Config } func NewFactory(nw *reteNetworkImpl, config string) (*TypeFactory, error) { tf := TypeFactory{} tf.config = config - err := json.Unmarshal([]byte(config), &tf.parsedJson) + err := json.Unmarshal([]byte(config), &tf.parsed) if err != nil { return nil, err } @@ -26,94 +28,45 @@ func NewFactory(nw *reteNetworkImpl, config string) (*TypeFactory, error) { } func (f *TypeFactory) getJoinTableRefs() types.JtRefsService { - var jtRefs types.JtRefsService - if f.parsedJson == nil { - jtRefs = mem.NewJoinTableRefsInHdlImpl(f.nw, f.parsedJson) - } else { - if rete, found := f.parsedJson["rete"].(map[string]interface{}); found { - if rete != nil { - if idgen, found2 := rete["jt"].(string); found2 { - if idgen == "" || idgen == "mem" { - jtRefs = mem.NewJoinTableRefsInHdlImpl(f.nw, f.parsedJson) - } else if idgen == "redis" { - jtRefs = redis.NewJoinTableRefsInHdlImpl(f.nw, f.parsedJson) - } - } - } - } - } - if jtRefs == nil { - //default in-mem - jtRefs = mem.NewJoinTableRefsInHdlImpl(f.nw, f.parsedJson) + switch f.parsed.Rete.Jt { + case common.ServiceTypeMem: + return mem.NewJoinTableRefsInHdlImpl(f.nw, f.parsed) + case common.ServiceTypeRedis: + return redis.NewJoinTableRefsInHdlImpl(f.nw, f.parsed) + default: + panic("invalid service type") } - return jtRefs } func (f *TypeFactory) getJoinTableCollection() types.JtService { - var allJt types.JtService - if f.parsedJson == nil { - allJt = mem.NewJoinTableCollection(f.nw, f.parsedJson) - } else { - if rete, found := f.parsedJson["rete"].(map[string]interface{}); found { - if rete != nil { - if idgen, found2 := rete["jt"].(string); found2 { - if idgen == "" || idgen == "mem" { - allJt = mem.NewJoinTableCollection(f.nw, f.parsedJson) - } else if idgen == "redis" { - allJt = redis.NewJoinTableCollection(f.nw, f.parsedJson) - } - } - } - } - } - if allJt == nil { - allJt = mem.NewJoinTableCollection(f.nw, f.parsedJson) + switch f.parsed.Rete.Jt { + case common.ServiceTypeMem: + return mem.NewJoinTableCollection(f.nw, f.parsed) + case common.ServiceTypeRedis: + return redis.NewJoinTableCollection(f.nw, f.parsed) + default: + panic("invalid service type") } - return allJt } func (f *TypeFactory) getHandleCollection() types.HandleService { - var hc types.HandleService - if f.parsedJson == nil { - hc = mem.NewHandleCollection(f.nw, f.parsedJson) - } else { - if rete, found := f.parsedJson["rete"].(map[string]interface{}); found { - if rete != nil { - if idgen, found2 := rete["jt-ref"].(string); found2 { - if idgen == "" || idgen == "mem" { - hc = mem.NewHandleCollection(f.nw, f.parsedJson) - } else if idgen == "redis" { - hc = redis.NewHandleCollection(f.nw, f.parsedJson) - } - } - } - } + switch f.parsed.Rete.JtRef { + case common.ServiceTypeMem: + return mem.NewHandleCollection(f.nw, f.parsed) + case common.ServiceTypeRedis: + return redis.NewHandleCollection(f.nw, f.parsed) + default: + panic("invalid service type") } - if hc == nil { - hc = mem.NewHandleCollection(f.nw, f.parsedJson) - } - return hc } func (f *TypeFactory) getIdGen() types.IdGen { - var idg types.IdGen - if f.parsedJson == nil { - idg = mem.NewIdGenImpl(f.nw, f.parsedJson) - } else { - if rete, found := f.parsedJson["rete"].(map[string]interface{}); found { - if rete != nil { - if idgen, found2 := rete["idgen-ref"].(string); found2 { - if idgen == "" || idgen == "mem" { - idg = mem.NewIdGenImpl(f.nw, f.parsedJson) - } else if idgen == "redis" { - idg = redis.NewIdGenImpl(f.nw, f.parsedJson) - } - } - } - } - } - if idg == nil { - idg = mem.NewIdGenImpl(f.nw, f.parsedJson) + switch f.parsed.Rete.IDGenRef { + case common.ServiceTypeMem: + return mem.NewIdGenImpl(f.nw, f.parsed) + case common.ServiceTypeRedis: + return redis.NewIdGenImpl(f.nw, f.parsed) + default: + panic("invalid service type") } - return idg } diff --git a/rete/internal/mem/mhandleservice.go b/rete/internal/mem/mhandleservice.go index e7e9c41..c8040f2 100644 --- a/rete/internal/mem/mhandleservice.go +++ b/rete/internal/mem/mhandleservice.go @@ -8,6 +8,7 @@ import ( "time" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/common" "github.com/project-flogo/rules/rete/internal/types" ) @@ -18,7 +19,7 @@ type handleServiceImpl struct { sync.RWMutex } -func NewHandleCollection(nw types.Network, config map[string]interface{}) types.HandleService { +func NewHandleCollection(nw types.Network, config common.Config) types.HandleService { hc := handleServiceImpl{ NwServiceImpl: types.NwServiceImpl{ Nw: nw, diff --git a/rete/internal/mem/midgenservice.go b/rete/internal/mem/midgenservice.go index 1aca2a0..81bc74d 100644 --- a/rete/internal/mem/midgenservice.go +++ b/rete/internal/mem/midgenservice.go @@ -1,17 +1,19 @@ package mem import ( - "github.com/project-flogo/rules/rete/internal/types" "sync/atomic" + + "github.com/project-flogo/rules/rete/common" + "github.com/project-flogo/rules/rete/internal/types" ) type idGenServiceImpl struct { types.NwServiceImpl - config map[string]interface{} + config common.Config currentId int32 } -func NewIdGenImpl(nw types.Network, config map[string]interface{}) types.IdGen { +func NewIdGenImpl(nw types.Network, config common.Config) types.IdGen { idg := idGenServiceImpl{} idg.config = config idg.currentId = 0 diff --git a/rete/internal/mem/mjtrefsservice.go b/rete/internal/mem/mjtrefsservice.go index dce01e7..8fb76ed 100644 --- a/rete/internal/mem/mjtrefsservice.go +++ b/rete/internal/mem/mjtrefsservice.go @@ -5,6 +5,7 @@ import ( "context" "sync" + "github.com/project-flogo/rules/rete/common" "github.com/project-flogo/rules/rete/internal/types" ) @@ -19,7 +20,7 @@ type jtRefsServiceImpl struct { sync.RWMutex } -func NewJoinTableRefsInHdlImpl(nw types.Network, config map[string]interface{}) types.JtRefsService { +func NewJoinTableRefsInHdlImpl(nw types.Network, config common.Config) types.JtRefsService { hdlJt := jtRefsServiceImpl{} hdlJt.Nw = nw hdlJt.tablesAndRows = make(map[string]*jtRowsImpl) diff --git a/rete/internal/mem/mjtservice.go b/rete/internal/mem/mjtservice.go index 6c21cfb..2d8115d 100644 --- a/rete/internal/mem/mjtservice.go +++ b/rete/internal/mem/mjtservice.go @@ -4,6 +4,7 @@ import ( "sync" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/common" "github.com/project-flogo/rules/rete/internal/types" ) @@ -13,7 +14,7 @@ type jtServiceImpl struct { sync.RWMutex } -func NewJoinTableCollection(nw types.Network, config map[string]interface{}) types.JtService { +func NewJoinTableCollection(nw types.Network, config common.Config) types.JtService { jtc := jtServiceImpl{} jtc.Nw = nw jtc.allJoinTables = make(map[string]types.JoinTable) diff --git a/rete/internal/redis/rhandleservice.go b/rete/internal/redis/rhandleservice.go index 3c45a86..42516b4 100644 --- a/rete/internal/redis/rhandleservice.go +++ b/rete/internal/redis/rhandleservice.go @@ -9,6 +9,7 @@ import ( "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/redisutils" + "github.com/project-flogo/rules/rete/common" "github.com/project-flogo/rules/rete/internal/types" ) @@ -16,12 +17,12 @@ type handleServiceImpl struct { //allHandles map[string]types.ReteHandle types.NwServiceImpl prefix string - config map[string]interface{} + config common.Config rand.Source sync.Mutex } -func NewHandleCollection(nw types.Network, config map[string]interface{}) types.HandleService { +func NewHandleCollection(nw types.Network, config common.Config) types.HandleService { hc := handleServiceImpl{ NwServiceImpl: types.NwServiceImpl{ Nw: nw, @@ -41,11 +42,7 @@ func (hc *handleServiceImpl) Int63() int64 { func (hc *handleServiceImpl) Init() { hc.prefix = hc.Nw.GetPrefix() + ":h:" - reteCfg := hc.config["rete"].(map[string]interface{}) - jtRef := reteCfg["jt-ref"].(string) - jts := hc.config["jts"].(map[string]interface{}) - redisCfg := jts[jtRef].(map[string]interface{}) - redisutils.InitService(redisCfg) + redisutils.InitService(hc.config.Jts.Redis) } func (hc *handleServiceImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle { diff --git a/rete/internal/redis/ridgenservice.go b/rete/internal/redis/ridgenservice.go index 6fcb989..a3c8fd1 100644 --- a/rete/internal/redis/ridgenservice.go +++ b/rete/internal/redis/ridgenservice.go @@ -3,13 +3,14 @@ package redis import ( "fmt" "github.com/project-flogo/rules/redisutils" + "github.com/project-flogo/rules/rete/common" "github.com/project-flogo/rules/rete/internal/types" ) type idGenServiceImpl struct { types.NwServiceImpl - config map[string]interface{} + config common.Config //current int rh redisutils.RedisHdl @@ -20,7 +21,7 @@ type idGenServiceImpl struct { fld string } -func NewIdGenImpl(nw types.Network, config map[string]interface{}) types.IdGen { +func NewIdGenImpl(nw types.Network, config common.Config) types.IdGen { r := idGenServiceImpl{} r.Nw = nw r.config = config @@ -29,11 +30,7 @@ func NewIdGenImpl(nw types.Network, config map[string]interface{}) types.IdGen { func (ri *idGenServiceImpl) Init() { ri.key = ri.Nw.GetPrefix() + ":idgen" - reteCfg := ri.config["rete"].(map[string]interface{}) - idgenRef := reteCfg["idgen-ref"].(string) - idGens := ri.config["idgens"].(map[string]interface{}) - redisCfg := idGens[idgenRef].(map[string]interface{}) - redisutils.InitService(redisCfg) + redisutils.InitService(ri.config.IDGens.Redis) ri.rh = redisutils.GetRedisHdl() j := ri.GetMaxID() diff --git a/rete/internal/redis/rjtrefsservice.go b/rete/internal/redis/rjtrefsservice.go index 473f7fe..1f92f73 100644 --- a/rete/internal/redis/rjtrefsservice.go +++ b/rete/internal/redis/rjtrefsservice.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/project-flogo/rules/redisutils" + "github.com/project-flogo/rules/rete/common" "github.com/project-flogo/rules/rete/internal/types" ) @@ -15,7 +16,7 @@ type jtRefsServiceImpl struct { //tablesAndRows map[string]map[string]map[int]int } -func NewJoinTableRefsInHdlImpl(nw types.Network, config map[string]interface{}) types.JtRefsService { +func NewJoinTableRefsInHdlImpl(nw types.Network, config common.Config) types.JtRefsService { hdlJt := jtRefsServiceImpl{} hdlJt.Nw = nw //hdlJt.tablesAndRows = make(map[string]map[string]map[int]int) diff --git a/rete/internal/redis/rjtservice.go b/rete/internal/redis/rjtservice.go index e545f0b..d0f61d0 100644 --- a/rete/internal/redis/rjtservice.go +++ b/rete/internal/redis/rjtservice.go @@ -4,6 +4,7 @@ import ( "sync" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/common" "github.com/project-flogo/rules/rete/internal/types" ) @@ -13,7 +14,7 @@ type jtServiceImpl struct { sync.RWMutex } -func NewJoinTableCollection(nw types.Network, config map[string]interface{}) types.JtService { +func NewJoinTableCollection(nw types.Network, config common.Config) types.JtService { jtc := jtServiceImpl{} jtc.Nw = nw jtc.allJoinTables = make(map[string]types.JoinTable) diff --git a/ruleapi/internal/store/mem/mstore.go b/ruleapi/internal/store/mem/mstore.go index b62d801..a4c5f51 100644 --- a/ruleapi/internal/store/mem/mstore.go +++ b/ruleapi/internal/store/mem/mstore.go @@ -5,6 +5,7 @@ import ( "sync" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/rete/common" ) type storeImpl struct { @@ -12,7 +13,7 @@ type storeImpl struct { sync.RWMutex } -func NewStore(jsonConfig map[string]interface{}) model.TupleStore { +func NewStore(config common.Config) model.TupleStore { ms := storeImpl{ allTuples: make(map[string]model.Tuple), } diff --git a/ruleapi/internal/store/redis/rstore.go b/ruleapi/internal/store/redis/rstore.go index cd03752..308bdad 100644 --- a/ruleapi/internal/store/redis/rstore.go +++ b/ruleapi/internal/store/redis/rstore.go @@ -2,28 +2,27 @@ package redis import ( "fmt" + "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/redisutils" + "github.com/project-flogo/rules/rete/common" ) type storeImpl struct { //allTuples map[string]model.Tuple - prefix string - jsonConfig map[string]interface{} + prefix string + config common.Config } -func NewStore(jsonConfig map[string]interface{}) model.TupleStore { - ms := storeImpl{} - ms.jsonConfig = jsonConfig +func NewStore(config common.Config) model.TupleStore { + ms := storeImpl{ + config: config, + } return &ms } func (ms *storeImpl) Init() { - reteCfg := ms.jsonConfig["rs"].(map[string]interface{}) - storeRef := reteCfg["store-ref"].(string) - storeCfg := ms.jsonConfig["stores"].(map[string]interface{}) - redisCfg := storeCfg[storeRef].(map[string]interface{}) - redisutils.InitService(redisCfg) + redisutils.InitService(ms.config.Stores.Redis) } func (ms *storeImpl) GetTupleByKey(key model.TupleKey) model.Tuple { diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index ae97aee..bee231a 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -33,7 +33,7 @@ type rulesessionImpl struct { started bool storeConfig string tupleStore model.TupleStore - jsonConfig map[string]interface{} + config common.Config } func ClearSessions() { @@ -117,11 +117,47 @@ func GetOrCreateRuleSessionFromConfig(name, store, jsonConfig string) (model.Rul return rs, nil } +const defaultConfig = `{ + "rs": { + "prefix": "x", + "store-ref": "mem" + }, + "rete": { + "jt-ref": "mem", + "idgen-ref": "mem", + "jt":"mem" + }, + "stores": { + "mem": { + }, + "redis": { + "network": "tcp", + "address": ":6379" + } + }, + "idgens": { + "mem": { + }, + "redis": { + "network": "tcp", + "address": ":6379" + } + }, + "jts": { + "mem": { + }, + "redis": { + "network": "tcp", + "address": ":6379" + } + } +}` + func (rs *rulesessionImpl) loadStoreConfig(name string) { _, err := os.Stat(name) if err != nil { // TO DO -- get the config from env or assign it as empty json - rs.storeConfig = "{}" + rs.storeConfig = defaultConfig } else { rs.storeConfig = utils.FileToString(name) } @@ -129,7 +165,7 @@ func (rs *rulesessionImpl) loadStoreConfig(name string) { func (rs *rulesessionImpl) initRuleSession(name string) error { - err := json.Unmarshal([]byte(rs.storeConfig), &rs.jsonConfig) + err := json.Unmarshal([]byte(rs.storeConfig), &rs.config) if err != nil { return err } @@ -139,7 +175,7 @@ func (rs *rulesessionImpl) initRuleSession(name string) error { rs.reteNetwork = rete.NewReteNetwork(rs.name, rs.storeConfig) //TODO: Configure it from jconsonfig - tupleStore := getTupleStore(rs.jsonConfig) + tupleStore := getTupleStore(rs.config) if tupleStore != nil { tupleStore.Init() rs.SetStore(tupleStore) @@ -148,18 +184,15 @@ func (rs *rulesessionImpl) initRuleSession(name string) error { return nil } -func getTupleStore(jsonConfig map[string]interface{}) model.TupleStore { - if rsCfg, found := jsonConfig["rs"].(map[string]interface{}); found { - if storeRef, found2 := rsCfg["store-ref"].(string); found2 { - if storeRef == "" || storeRef == "mem" { - return mem.NewStore(jsonConfig) - } else if storeRef == "redis" { - return redis.NewStore(jsonConfig) - } - } +func getTupleStore(config common.Config) model.TupleStore { + switch config.Rs.StoreRef { + case common.ServiceTypeMem: + return mem.NewStore(config) + case common.ServiceTypeRedis: + return redis.NewStore(config) + default: + panic("invalid service type") } - //default to in-mem - return mem.NewStore(jsonConfig) } func (rs *rulesessionImpl) AddRule(rule model.Rule) (err error) { From 3149f9c5dad0203a91c2c742865eaf744b96a574 Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Mon, 14 Oct 2019 16:41:57 -0600 Subject: [PATCH 089/125] Get store config from environment --- ruleapi/rulesession.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index bee231a..5340f20 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -154,13 +154,16 @@ const defaultConfig = `{ }` func (rs *rulesessionImpl) loadStoreConfig(name string) { - _, err := os.Stat(name) - if err != nil { - // TO DO -- get the config from env or assign it as empty json + if value := os.Getenv("STORECONFIG"); value != "" { + name = value + } + + if _, err := os.Stat(name); err != nil { rs.storeConfig = defaultConfig - } else { - rs.storeConfig = utils.FileToString(name) + return } + + rs.storeConfig = utils.FileToString(name) } func (rs *rulesessionImpl) initRuleSession(name string) error { From 508ab60b9813da28f126a9c10663ae5ecdac146e Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Wed, 16 Oct 2019 13:41:12 -0600 Subject: [PATCH 090/125] Added dtable tests --- examples/flogo/dtable/mem/mem_test.go | 11 ++++ examples/flogo/dtable/redis/redis_test.go | 39 ++++++++++++++ examples/flogo/dtable/rsconfig.json | 35 +++++++++++++ examples/flogo/dtable/test/imports.go | 10 ++++ examples/flogo/dtable/test/testing.go | 64 +++++++++++++++++++++++ 5 files changed, 159 insertions(+) create mode 100644 examples/flogo/dtable/mem/mem_test.go create mode 100644 examples/flogo/dtable/redis/redis_test.go create mode 100644 examples/flogo/dtable/rsconfig.json create mode 100644 examples/flogo/dtable/test/imports.go create mode 100644 examples/flogo/dtable/test/testing.go diff --git a/examples/flogo/dtable/mem/mem_test.go b/examples/flogo/dtable/mem/mem_test.go new file mode 100644 index 0000000..827da1e --- /dev/null +++ b/examples/flogo/dtable/mem/mem_test.go @@ -0,0 +1,11 @@ +package main + +import ( + "testing" + + "github.com/project-flogo/rules/examples/flogo/dtable/test" +) + +func TestDTableMem(t *testing.T) { + test.Test(t) +} diff --git a/examples/flogo/dtable/redis/redis_test.go b/examples/flogo/dtable/redis/redis_test.go new file mode 100644 index 0000000..7eff948 --- /dev/null +++ b/examples/flogo/dtable/redis/redis_test.go @@ -0,0 +1,39 @@ +package main + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/project-flogo/rules/examples/flogo/dtable/test" + "github.com/project-flogo/rules/ruleapi/tests" + + "github.com/stretchr/testify/assert" +) + +func TestDTableRedis(t *testing.T) { + command := exec.Command("docker", "run", "-p", "6381:6379", "-d", "redis") + hash, err := command.Output() + if err != nil { + assert.Nil(t, err) + } + tests.Pour("6381") + + defer func() { + command := exec.Command("docker", "stop", strings.TrimSpace(string(hash))) + err := command.Run() + if err != nil { + assert.Nil(t, err) + } + command = exec.Command("docker", "rm", strings.TrimSpace(string(hash))) + err = command.Run() + if err != nil { + assert.Nil(t, err) + } + tests.Drain("6381") + }() + os.Setenv("STORECONFIG", filepath.FromSlash("../rsconfig.json")) + test.Test(t) +} diff --git a/examples/flogo/dtable/rsconfig.json b/examples/flogo/dtable/rsconfig.json new file mode 100644 index 0000000..c0e2d37 --- /dev/null +++ b/examples/flogo/dtable/rsconfig.json @@ -0,0 +1,35 @@ +{ + "rs": { + "prefix": "x", + "store-ref": "redis" + }, + "rete": { + "jt-ref": "redis", + "idgen-ref": "redis", + "jt":"redis" + }, + "stores": { + "mem": { + }, + "redis": { + "network": "tcp", + "address": ":6381" + } + }, + "idgens": { + "mem": { + }, + "redis": { + "network": "tcp", + "address": ":6381" + } + }, + "jts": { + "mem": { + }, + "redis": { + "network": "tcp", + "address": ":6381" + } + } +} diff --git a/examples/flogo/dtable/test/imports.go b/examples/flogo/dtable/test/imports.go new file mode 100644 index 0000000..95890fc --- /dev/null +++ b/examples/flogo/dtable/test/imports.go @@ -0,0 +1,10 @@ +package test + +import ( + _ "github.com/project-flogo/contrib/activity/log" + _ "github.com/project-flogo/contrib/function/string" + _ "github.com/project-flogo/contrib/trigger/rest" + _ "github.com/project-flogo/legacybridge" + _ "github.com/project-flogo/rules/activity/dtable" + _ "github.com/project-flogo/rules/ruleaction" +) diff --git a/examples/flogo/dtable/test/testing.go b/examples/flogo/dtable/test/testing.go new file mode 100644 index 0000000..a7ead00 --- /dev/null +++ b/examples/flogo/dtable/test/testing.go @@ -0,0 +1,64 @@ +package test + +import ( + "io/ioutil" + "net/http" + "path/filepath" + "testing" + + _ "github.com/project-flogo/core/data/expression/script" + "github.com/project-flogo/core/engine" + "github.com/project-flogo/rules/ruleapi/tests" + + "github.com/stretchr/testify/assert" +) + +func Test(t *testing.T) { + data, err := ioutil.ReadFile(filepath.FromSlash("../flogo.json")) + assert.Nil(t, err) + cfg, err := engine.LoadAppConfig(string(data), false) + assert.Nil(t, err) + e, err := engine.New(cfg) + assert.Nil(t, err) + tests.Drain("7777") + err = e.Start() + assert.Nil(t, err) + defer func() { + err := e.Stop() + assert.Nil(t, err) + }() + tests.Pour("7777") + + client := &http.Client{} + request := func() { + response, err := client.Get("http://localhost:7777/test/student?grade=GRADE-C&name=s1&class=X-A&careRequired=false") + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, response.StatusCode) + } + output := tests.CaptureStdOutput(request) + assert.Contains(t, output, "Saving tuple.") + + request = func() { + response, err := client.Get("http://localhost:7777/test/student?grade=GRADE-B&name=s2&class=X-A&careRequired=false") + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, response.StatusCode) + } + output = tests.CaptureStdOutput(request) + assert.Contains(t, output, "Saving tuple.") + + request = func() { + response, err := client.Get("http://localhost:7777/test/studentanalysis?name=s1") + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, response.StatusCode) + } + output = tests.CaptureStdOutput(request) + assert.Contains(t, output, "Saving tuple.") + + request = func() { + response, err := client.Get("http://localhost:7777/test/studentanalysis?name=s2") + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, response.StatusCode) + } + output = tests.CaptureStdOutput(request) + assert.Contains(t, output, "Saving tuple.") +} From 110bffe116ca2af722850a90fa4b0b4cae5da736 Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Wed, 16 Oct 2019 13:46:07 -0600 Subject: [PATCH 091/125] Updated dtable readme for redis store support --- examples/flogo/dtable/README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/examples/flogo/dtable/README.md b/examples/flogo/dtable/README.md index fef24c4..96b89b9 100644 --- a/examples/flogo/dtable/README.md +++ b/examples/flogo/dtable/README.md @@ -17,8 +17,21 @@ flogo create -f flogo.json cd dtable flogo build cd bin +``` + +#### With mem store + +```sh ./dtable ``` + +#### With redis store + +```sh +docker run -p 6381:6379 -d redis +STORECONFIG=../../rsconfig.json ./dtable +``` + ### Testing #### #1 Invoke student analysis decision table @@ -85,4 +98,4 @@ Sample usage can be as below. } } ``` -Decision table will have condition and action included into decition table activity. \ No newline at end of file +Decision table will have condition and action included into decition table activity. From 7419bb43ef13a2f653a1a097a3c4095605dec076 Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Thu, 17 Oct 2019 12:46:30 -0600 Subject: [PATCH 092/125] Removed print --- rete/joinnode.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/rete/joinnode.go b/rete/joinnode.go index 5302269..3d07537 100644 --- a/rete/joinnode.go +++ b/rete/joinnode.go @@ -2,7 +2,6 @@ package rete import ( "context" - "fmt" "strconv" "github.com/project-flogo/rules/common/model" @@ -263,7 +262,6 @@ LOOP: tupleTableRowRight := rIterator.Next() handles := tupleTableRowRight.GetHandles() for _, handle := range handles { - fmt.Println("handle", handle.GetTupleKey(), handle.GetTuple()) if jn.GetNw().GetHandleService().GetHandle(ctx, handle.GetTuple()) == nil { rIterator.Remove() for _, otherHdl := range handles { From b403946d0e7ca29d22ee67711d02d1cb425d32ac Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Thu, 17 Oct 2019 16:48:14 -0600 Subject: [PATCH 093/125] Removed global redis handle --- redisutils/redisutil_test.go | 21 +++++------ redisutils/redisutils.go | 46 +++++++++++------------- rete/internal/redis/rhandleservice.go | 35 +++++++++--------- rete/internal/redis/ridgenservice.go | 29 +++++++-------- rete/internal/redis/rjointableimpl.go | 31 ++++++++-------- rete/internal/redis/rjointablerowimpl.go | 34 ++++++++++-------- rete/internal/redis/rjtrefsservice.go | 22 ++++++------ rete/internal/redis/rjtservice.go | 15 +++++--- rete/internal/redis/rretehandle.go | 11 +++--- ruleapi/internal/store/redis/rstore.go | 25 ++++++------- 10 files changed, 131 insertions(+), 138 deletions(-) diff --git a/redisutils/redisutil_test.go b/redisutils/redisutil_test.go index 0f17f70..f12be59 100644 --- a/redisutils/redisutil_test.go +++ b/redisutils/redisutil_test.go @@ -65,9 +65,8 @@ func TestMain(m *testing.M) { } func Test_first(t *testing.T) { - - InitService(RedisConfig{}) - rd := GetRedisHdl() + rd := NewRedisHdl(RedisConfig{}) + defer Shutdown() m := make(map[string]interface{}) m["k1"] = "v1" @@ -210,10 +209,8 @@ func getStruct(c redis.Conn) error { } func Test_three(t *testing.T) { - - InitService(RedisConfig{}) - - hdl := GetRedisHdl() + hdl := NewRedisHdl(RedisConfig{}) + defer Shutdown() //iter := hdl.GetListIterator("x:jt:L_c2") // @@ -241,9 +238,8 @@ func Test_three(t *testing.T) { } func Test_four(t *testing.T) { - InitService(RedisConfig{}) - - hdl := GetRedisHdl() + hdl := NewRedisHdl(RedisConfig{}) + defer Shutdown() //v := hdl.HGet("a", "d") len := hdl.HLen("a") @@ -252,9 +248,8 @@ func Test_four(t *testing.T) { } func Test_five(t *testing.T) { - InitService(RedisConfig{}) - - hdl := GetRedisHdl() + hdl := NewRedisHdl(RedisConfig{}) + defer Shutdown() for i := 0; i < 10; i++ { m := make(map[string]interface{}) diff --git a/redisutils/redisutils.go b/redisutils/redisutils.go index 682b77e..0f07e62 100644 --- a/redisutils/redisutils.go +++ b/redisutils/redisutils.go @@ -5,10 +5,10 @@ import ( "github.com/gomodule/redigo/redis" ) -var rd RedisHdl - type RedisHdl = *RedisHandle +var rd = make([]RedisHdl, 0, 8) + type RedisConfig struct { Network string `json:"network"` Address string `json:"address"` @@ -21,24 +21,21 @@ type RedisHandle struct { address string } -func InitService(config RedisConfig) { - if rd == nil { - rd = &RedisHandle{} - rd.config = config - rd.network = config.Network - if rd.network == "" { - rd.network = "tcp" - } - rd.address = config.Address - if rd.address == "" { - rd.address = ":6379" - } - rd.newPool(rd.network, rd.address) +func NewRedisHdl(config RedisConfig) RedisHdl { + handle := RedisHandle{ + config: config, + network: config.Network, + address: config.Address, } -} - -func GetRedisHdl() RedisHdl { - return rd + if handle.network == "" { + handle.network = "tcp" + } + if handle.address == "" { + handle.address = ":6379" + } + handle.newPool(handle.network, handle.address) + rd = append(rd, &handle) + return &handle } func (rh *RedisHandle) newPool(network string, address string) { @@ -318,9 +315,9 @@ func (iter *MapIterator) HasNext() bool { return true } + func (iter *MapIterator) Remove() { - hdl := GetRedisHdl() - hdl.HDel(iter.key, iter.currKey) + iter.rh.HDel(iter.key, iter.currKey) } func (iter *MapIterator) Next() (string, interface{}) { @@ -352,9 +349,8 @@ func (rh *RedisHandle) GetMapIterator(key string) *MapIterator { } func Shutdown() { - if rd != nil { - - rd.pool.Close() - rd = nil + for _, value := range rd { + value.pool.Close() } + rd = rd[:0] } diff --git a/rete/internal/redis/rhandleservice.go b/rete/internal/redis/rhandleservice.go index 42516b4..7159ffb 100644 --- a/rete/internal/redis/rhandleservice.go +++ b/rete/internal/redis/rhandleservice.go @@ -14,12 +14,12 @@ import ( ) type handleServiceImpl struct { - //allHandles map[string]types.ReteHandle types.NwServiceImpl prefix string config common.Config rand.Source sync.Mutex + redisutils.RedisHdl } func NewHandleCollection(nw types.Network, config common.Config) types.HandleService { @@ -27,10 +27,10 @@ func NewHandleCollection(nw types.Network, config common.Config) types.HandleSer NwServiceImpl: types.NwServiceImpl{ Nw: nw, }, - config: config, - Source: rand.NewSource(time.Now().UnixNano()), + config: config, + Source: rand.NewSource(time.Now().UnixNano()), + RedisHdl: redisutils.NewRedisHdl(config.Jts.Redis), } - //hc.allHandles = make(map[string]types.ReteHandle) return &hc } @@ -42,14 +42,13 @@ func (hc *handleServiceImpl) Int63() int64 { func (hc *handleServiceImpl) Init() { hc.prefix = hc.Nw.GetPrefix() + ":h:" - redisutils.InitService(hc.config.Jts.Redis) } func (hc *handleServiceImpl) RemoveHandle(tuple model.Tuple) types.ReteHandle { rkey := hc.prefix + tuple.GetKey().String() - redisutils.GetRedisHdl().Del(rkey) + hc.Del(rkey) //TODO: Dummy handle - h := newReteHandleImpl(hc.GetNw(), tuple, rkey, types.ReteHandleStatusUnknown, -1) + h := newReteHandleImpl(hc.GetNw(), hc.RedisHdl, tuple, rkey, types.ReteHandleStatusUnknown, -1) return h } @@ -61,7 +60,7 @@ func (hc *handleServiceImpl) GetHandle(ctx context.Context, tuple model.Tuple) t func (hc *handleServiceImpl) GetHandleByKey(ctx context.Context, key model.TupleKey) types.ReteHandle { rkey := hc.prefix + key.String() - m := redisutils.GetRedisHdl().HGetAll(rkey) + m := hc.HGetAll(rkey) if len(m) == 0 { return nil } @@ -115,7 +114,7 @@ func (hc *handleServiceImpl) GetHandleByKey(ctx context.Context, key model.Tuple return nil } - h := newReteHandleImpl(hc.GetNw(), tuple, rkey, status, id) + h := newReteHandleImpl(hc.GetNw(), hc.RedisHdl, tuple, rkey, status, id) return h } @@ -123,14 +122,14 @@ func (hc *handleServiceImpl) GetOrCreateLockedHandle(nw types.Network, tuple mod id := hc.Int63() key, status := hc.prefix+tuple.GetKey().String(), types.ReteHandleStatusCreating - exists, _ := redisutils.GetRedisHdl().HSetNX(key, "id", id) + exists, _ := hc.HSetNX(key, "id", id) if exists { return nil, true } - exists, _ = redisutils.GetRedisHdl().HSetNX(key, "status", status) + exists, _ = hc.HSetNX(key, "status", status) if exists { - m := redisutils.GetRedisHdl().HGetAll(key) + m := hc.HGetAll(key) if len(m) > 0 { if value, ok := m["status"]; ok { if value, ok := value.(string); ok { @@ -148,7 +147,7 @@ func (hc *handleServiceImpl) GetOrCreateLockedHandle(nw types.Network, tuple mod } } - h := newReteHandleImpl(nw, tuple, key, status, id) + h := newReteHandleImpl(nw, hc.RedisHdl, tuple, key, status, id) return h, false } @@ -156,12 +155,12 @@ func (hc *handleServiceImpl) GetLockedHandle(nw types.Network, tuple model.Tuple id := hc.Int63() key := hc.prefix + tuple.GetKey().String() - exists, _ := redisutils.GetRedisHdl().HSetNX(key, "id", id) + exists, _ := hc.HSetNX(key, "id", id) if exists { return nil, true } - m := redisutils.GetRedisHdl().HGetAll(key) + m := hc.HGetAll(key) if len(m) > 0 { if value, ok := m["status"]; ok { if value, ok := value.(string); ok { @@ -169,7 +168,7 @@ func (hc *handleServiceImpl) GetLockedHandle(nw types.Network, tuple model.Tuple if err != nil { panic(err) } - h := newReteHandleImpl(nw, tuple, key, types.ReteHandleStatus(number), id) + h := newReteHandleImpl(nw, hc.RedisHdl, tuple, key, types.ReteHandleStatus(number), id) return h, false } } @@ -180,7 +179,7 @@ func (hc *handleServiceImpl) GetLockedHandle(nw types.Network, tuple model.Tuple func (hc *handleServiceImpl) GetHandleWithTuple(nw types.Network, tuple model.Tuple) types.ReteHandle { key, status, id := hc.prefix+tuple.GetKey().String(), types.ReteHandleStatusCreating, int64(-1) - m := redisutils.GetRedisHdl().HGetAll(key) + m := hc.HGetAll(key) if len(m) > 0 { if value, ok := m["status"]; ok { if value, ok := value.(string); ok { @@ -206,6 +205,6 @@ func (hc *handleServiceImpl) GetHandleWithTuple(nw types.Network, tuple model.Tu } } - h := newReteHandleImpl(nw, tuple, key, status, id) + h := newReteHandleImpl(nw, hc.RedisHdl, tuple, key, status, id) return h } diff --git a/rete/internal/redis/ridgenservice.go b/rete/internal/redis/ridgenservice.go index a3c8fd1..40235f0 100644 --- a/rete/internal/redis/ridgenservice.go +++ b/rete/internal/redis/ridgenservice.go @@ -9,38 +9,33 @@ import ( type idGenServiceImpl struct { types.NwServiceImpl - config common.Config - //current int - rh redisutils.RedisHdl - - //key used to access idgen - key string - - //redis field in key - fld string + key string // key used to access idgen + fld string // redis field in key + redisutils.RedisHdl } func NewIdGenImpl(nw types.Network, config common.Config) types.IdGen { - r := idGenServiceImpl{} - r.Nw = nw - r.config = config + r := idGenServiceImpl{ + NwServiceImpl: types.NwServiceImpl{ + Nw: nw, + }, + config: config, + RedisHdl: redisutils.NewRedisHdl(config.IDGens.Redis), + } return &r } func (ri *idGenServiceImpl) Init() { ri.key = ri.Nw.GetPrefix() + ":idgen" - redisutils.InitService(ri.config.IDGens.Redis) - - ri.rh = redisutils.GetRedisHdl() j := ri.GetMaxID() fmt.Printf("maxid : [%d]\n ", j) } func (ri *idGenServiceImpl) GetMaxID() int { - return ri.rh.GetAsInt(ri.key) + return ri.GetAsInt(ri.key) } func (ri *idGenServiceImpl) GetNextID() int { - return ri.rh.IncrBy(ri.key, 1) + return ri.IncrBy(ri.key, 1) } diff --git a/rete/internal/redis/rjointableimpl.go b/rete/internal/redis/rjointableimpl.go index e66186e..9a4230d 100644 --- a/rete/internal/redis/rjointableimpl.go +++ b/rete/internal/redis/rjointableimpl.go @@ -11,15 +11,17 @@ import ( type joinTableImpl struct { types.NwElemIdImpl - //table map[int]types.JoinTableRow + redisutils.RedisHdl idr []model.TupleType rule model.Rule name string jtKey string } -func newJoinTableImpl(nw types.Network, rule model.Rule, identifiers []model.TupleType, name string) types.JoinTable { - jt := joinTableImpl{} +func newJoinTableImpl(nw types.Network, handle redisutils.RedisHdl, rule model.Rule, identifiers []model.TupleType, name string) types.JoinTable { + jt := joinTableImpl{ + RedisHdl: handle, + } jt.initJoinTableImpl(nw, rule, identifiers, name) return &jt } @@ -33,7 +35,7 @@ func (jt *joinTableImpl) initJoinTableImpl(nw types.Network, rule model.Rule, id } func (jt *joinTableImpl) AddRow(handles []types.ReteHandle) types.JoinTableRow { - row := newJoinTableRow(jt.jtKey, handles, jt.Nw) + row := newJoinTableRow(jt.RedisHdl, jt.jtKey, handles, jt.Nw) for i := 0; i < len(row.GetHandles()); i++ { handle := row.GetHandles()[i] jt.Nw.GetJtRefService().AddEntry(handle, jt.name, row.GetID()) @@ -44,9 +46,8 @@ func (jt *joinTableImpl) AddRow(handles []types.ReteHandle) types.JoinTableRow { func (jt *joinTableImpl) RemoveRow(rowID int) types.JoinTableRow { row := jt.GetRow(nil, rowID) - hdl := redisutils.GetRedisHdl() rowId := strconv.Itoa(rowID) - hdl.HDel(jt.jtKey, rowId) + jt.HDel(jt.jtKey, rowId) return row } @@ -65,8 +66,7 @@ func (jt *joinTableImpl) RemoveAllRows(ctx context.Context) { } func (jt *joinTableImpl) GetRowCount() int { - hdl := redisutils.GetRedisHdl() - return hdl.HLen(jt.name) + return jt.HLen(jt.name) } func (jt *joinTableImpl) GetRule() model.Rule { @@ -74,14 +74,13 @@ func (jt *joinTableImpl) GetRule() model.Rule { } func (jt *joinTableImpl) GetRowIterator(ctx context.Context) types.JointableRowIterator { - return newRowIterator(ctx, jt) + return newRowIterator(ctx, jt.RedisHdl, jt) } func (jt *joinTableImpl) GetRow(ctx context.Context, rowID int) types.JoinTableRow { - hdl := redisutils.GetRedisHdl() - key := hdl.HGet(jt.jtKey, strconv.Itoa(rowID)) + key := jt.HGet(jt.jtKey, strconv.Itoa(rowID)) rowId := strconv.Itoa(rowID) - return createRow(ctx, jt.name, rowId, key.(string), jt.Nw) + return createRow(ctx, jt.RedisHdl, jt.name, rowId, key.(string), jt.Nw) } func (jt *joinTableImpl) GetName() string { @@ -94,15 +93,17 @@ type rowIteratorImpl struct { jtName string nw types.Network curr types.JoinTableRow + redisutils.RedisHdl } -func newRowIterator(ctx context.Context, jTable types.JoinTable) types.JointableRowIterator { +func newRowIterator(ctx context.Context, handle redisutils.RedisHdl, jTable types.JoinTable) types.JointableRowIterator { key := jTable.GetNw().GetPrefix() + ":jt:" + jTable.GetName() ri := rowIteratorImpl{} ri.ctx = ctx - ri.iter = redisutils.GetRedisHdl().GetMapIterator(key) + ri.iter = handle.GetMapIterator(key) ri.nw = jTable.GetNw() ri.jtName = jTable.GetName() + ri.RedisHdl = handle return &ri } @@ -113,7 +114,7 @@ func (ri *rowIteratorImpl) HasNext() bool { func (ri *rowIteratorImpl) Next() types.JoinTableRow { rowId, key := ri.iter.Next() tupleKeyStr := key.(string) - ri.curr = createRow(ri.ctx, ri.jtName, rowId, tupleKeyStr, ri.nw) + ri.curr = createRow(ri.ctx, ri.RedisHdl, ri.jtName, rowId, tupleKeyStr, ri.nw) return ri.curr } diff --git a/rete/internal/redis/rjointablerowimpl.go b/rete/internal/redis/rjointablerowimpl.go index bf0751c..d2c4678 100644 --- a/rete/internal/redis/rjointablerowimpl.go +++ b/rete/internal/redis/rjointablerowimpl.go @@ -14,24 +14,29 @@ type joinTableRowImpl struct { types.NwElemIdImpl handles []types.ReteHandle jtKey string + redisutils.RedisHdl } -func newJoinTableRow(jtKey string, handles []types.ReteHandle, nw types.Network) types.JoinTableRow { +func newJoinTableRow(handle redisutils.RedisHdl, jtKey string, handles []types.ReteHandle, nw types.Network) types.JoinTableRow { jtr := joinTableRowImpl{ - handles: append([]types.ReteHandle{}, handles...), - jtKey: jtKey, + RedisHdl: handle, + jtKey: jtKey, + handles: append([]types.ReteHandle{}, handles...), } jtr.SetID(nw) return &jtr } -func newJoinTableRowLoadedFromStore(jtKey string, rowID int, handles []types.ReteHandle, nw types.Network) types.JoinTableRow { - jtr := joinTableRowImpl{} - jtr.jtKey = jtKey - jtr.Nw = nw - jtr.ID = rowID - jtr.handles = handles - +func newJoinTableRowLoadedFromStore(handle redisutils.RedisHdl, jtKey string, rowID int, handles []types.ReteHandle, nw types.Network) types.JoinTableRow { + jtr := joinTableRowImpl{ + handles: handles, + jtKey: jtKey, + NwElemIdImpl: types.NwElemIdImpl{ + ID: rowID, + Nw: nw, + }, + RedisHdl: handle, + } return &jtr } @@ -45,15 +50,14 @@ func (jtr *joinTableRowImpl) Write() { } } row[strconv.Itoa(jtr.ID)] = str - hdl := redisutils.GetRedisHdl() - hdl.HSetAll(jtr.jtKey, row) + jtr.HSetAll(jtr.jtKey, row) } func (jtr *joinTableRowImpl) GetHandles() []types.ReteHandle { return jtr.handles } -func createRow(ctx context.Context, jtKey string, rowID string, key string, nw types.Network) types.JoinTableRow { +func createRow(ctx context.Context, handle redisutils.RedisHdl, jtKey string, rowID string, key string, nw types.Network) types.JoinTableRow { values := strings.Split(key, ",") @@ -80,12 +84,12 @@ func createRow(ctx context.Context, jtKey string, rowID string, key string, nw t if tuple == nil { tuple = nw.GetTupleStore().GetTupleByKey(tupleKey) } - handle := newReteHandleImpl(nw, tuple, "", types.ReteHandleStatusUnknown, 0) + handle := newReteHandleImpl(nw, handle, tuple, "", types.ReteHandleStatusUnknown, 0) handles = append(handles, handle) } rowId, _ := strconv.Atoi(rowID) - jtRow := newJoinTableRowLoadedFromStore(jtKey, rowId, handles, nw) + jtRow := newJoinTableRowLoadedFromStore(handle, jtKey, rowId, handles, nw) return jtRow } diff --git a/rete/internal/redis/rjtrefsservice.go b/rete/internal/redis/rjtrefsservice.go index 1f92f73..307f5aa 100644 --- a/rete/internal/redis/rjtrefsservice.go +++ b/rete/internal/redis/rjtrefsservice.go @@ -10,16 +10,17 @@ import ( ) type jtRefsServiceImpl struct { - //keys are jointable-ids and values are lists of row-ids in the corresponding join table types.NwServiceImpl - - //tablesAndRows map[string]map[string]map[int]int + redisutils.RedisHdl } func NewJoinTableRefsInHdlImpl(nw types.Network, config common.Config) types.JtRefsService { - hdlJt := jtRefsServiceImpl{} - hdlJt.Nw = nw - //hdlJt.tablesAndRows = make(map[string]map[string]map[int]int) + hdlJt := jtRefsServiceImpl{ + NwServiceImpl: types.NwServiceImpl{ + Nw: nw, + }, + RedisHdl: redisutils.NewRedisHdl(config.Jts.Redis), + } return &hdlJt } @@ -30,16 +31,14 @@ func (h *jtRefsServiceImpl) Init() { func (h *jtRefsServiceImpl) AddEntry(handle types.ReteHandle, jtName string, rowID int) { // format: prefix:rtbls:tkey ==> {rowID=jtname, ...} key := h.Nw.GetPrefix() + ":rtbls:" + handle.GetTupleKey().String() - hdl := redisutils.GetRedisHdl() valMap := make(map[string]interface{}) valMap[strconv.Itoa(rowID)] = jtName - hdl.HSetAll(key, valMap) + h.HSetAll(key, valMap) } func (h *jtRefsServiceImpl) RemoveEntry(handle types.ReteHandle, jtName string, rowID int) { key := h.Nw.GetPrefix() + ":rtbls:" + handle.GetTupleKey().String() - hdl := redisutils.GetRedisHdl() - hdl.HDel(key, strconv.Itoa(rowID)) + h.HDel(key, strconv.Itoa(rowID)) } type hdlRefsRowIteratorImpl struct { @@ -72,7 +71,6 @@ func (h *jtRefsServiceImpl) GetRowIterator(ctx context.Context, handle types.Ret r.ctx = ctx r.nw = h.Nw r.key = h.Nw.GetPrefix() + ":rtbls:" + handle.GetTupleKey().String() - hdl := redisutils.GetRedisHdl() - r.iter = hdl.GetMapIterator(r.key) + r.iter = h.GetMapIterator(r.key) return &r } diff --git a/rete/internal/redis/rjtservice.go b/rete/internal/redis/rjtservice.go index d0f61d0..ef659b2 100644 --- a/rete/internal/redis/rjtservice.go +++ b/rete/internal/redis/rjtservice.go @@ -4,6 +4,7 @@ import ( "sync" "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/redisutils" "github.com/project-flogo/rules/rete/common" "github.com/project-flogo/rules/rete/internal/types" ) @@ -11,15 +12,21 @@ import ( type jtServiceImpl struct { types.NwServiceImpl allJoinTables map[string]types.JoinTable + redisutils.RedisHdl sync.RWMutex } func NewJoinTableCollection(nw types.Network, config common.Config) types.JtService { - jtc := jtServiceImpl{} - jtc.Nw = nw - jtc.allJoinTables = make(map[string]types.JoinTable) + jtc := jtServiceImpl{ + NwServiceImpl: types.NwServiceImpl{ + Nw: nw, + }, + allJoinTables: make(map[string]types.JoinTable), + RedisHdl: redisutils.NewRedisHdl(config.Jts.Redis), + } return &jtc } + func (jtc *jtServiceImpl) Init() { } @@ -35,7 +42,7 @@ func (jtc *jtServiceImpl) GetOrCreateJoinTable(nw types.Network, rule model.Rule defer jtc.Unlock() jT, found := jtc.allJoinTables[name] if !found { - jT = newJoinTableImpl(nw, rule, identifiers, name) + jT = newJoinTableImpl(nw, jtc.RedisHdl, rule, identifiers, name) jtc.allJoinTables[name] = jT } return jT diff --git a/rete/internal/redis/rretehandle.go b/rete/internal/redis/rretehandle.go index a1e1e53..83edf65 100644 --- a/rete/internal/redis/rretehandle.go +++ b/rete/internal/redis/rretehandle.go @@ -15,11 +15,14 @@ type reteHandleImpl struct { key string status types.ReteHandleStatus id int64 + redisutils.RedisHdl //jtRefs types.JtRefsService } -func newReteHandleImpl(nw types.Network, tuple model.Tuple, key string, status types.ReteHandleStatus, id int64) types.ReteHandle { - h1 := reteHandleImpl{} +func newReteHandleImpl(nw types.Network, handle redisutils.RedisHdl, tuple model.Tuple, key string, status types.ReteHandleStatus, id int64) types.ReteHandle { + h1 := reteHandleImpl{ + RedisHdl: handle, + } h1.initHandleImpl(nw, tuple, key, status) return &h1 } @@ -50,11 +53,11 @@ func (hdl *reteHandleImpl) SetStatus(status types.ReteHandleStatus) { if hdl.key == "" { return } - redisutils.GetRedisHdl().HSet(hdl.key, "status", status) + hdl.HSet(hdl.key, "status", status) } func (hdl *reteHandleImpl) Unlock() { - redisutils.GetRedisHdl().HDel(hdl.key, "id") + hdl.HDel(hdl.key, "id") } func (hdl *reteHandleImpl) GetStatus() types.ReteHandleStatus { diff --git a/ruleapi/internal/store/redis/rstore.go b/ruleapi/internal/store/redis/rstore.go index 308bdad..77a9f10 100644 --- a/ruleapi/internal/store/redis/rstore.go +++ b/ruleapi/internal/store/redis/rstore.go @@ -12,24 +12,24 @@ type storeImpl struct { //allTuples map[string]model.Tuple prefix string config common.Config + redisutils.RedisHdl } func NewStore(config common.Config) model.TupleStore { ms := storeImpl{ - config: config, + config: config, + RedisHdl: redisutils.NewRedisHdl(config.Stores.Redis), } return &ms } func (ms *storeImpl) Init() { - redisutils.InitService(ms.config.Stores.Redis) + } func (ms *storeImpl) GetTupleByKey(key model.TupleKey) model.Tuple { - hdl := redisutils.GetRedisHdl() - strKey := ms.prefix + key.String() - vals := hdl.HGetAll(strKey) + vals := ms.HGetAll(strKey) tuple, err := model.NewTuple(model.TupleType(key.GetTupleDescriptor().Name), vals) @@ -44,45 +44,40 @@ func (ms *storeImpl) SaveTuple(tuple model.Tuple) { strKey := ms.prefix + tuple.GetKey().String() - hdl := redisutils.GetRedisHdl() - hdl.HSetAll(strKey, m) + ms.HSetAll(strKey, m) } func (ms *storeImpl) DeleteTuple(key model.TupleKey) { strKey := ms.prefix + key.String() - hdl := redisutils.GetRedisHdl() - hdl.Del(strKey) + ms.Del(strKey) } func (ms *storeImpl) SaveTuples(added map[string]map[string]model.Tuple) { - hdl := redisutils.GetRedisHdl() for tupleType, tuples := range added { for key, tuple := range tuples { fmt.Printf("Saving tuple. Type [%s] Key [%s]\n", tupleType, key) strKey := ms.prefix + key - hdl.HSetAll(strKey, tuple.ToMap()) + ms.HSetAll(strKey, tuple.ToMap()) } } } func (ms *storeImpl) SaveModifiedTuples(modified map[string]map[string]model.RtcModified) { - hdl := redisutils.GetRedisHdl() for tupleType, mmap := range modified { for key, mdfd := range mmap { fmt.Printf("Saving tuple. Type [%s] Key [%s]\n", tupleType, key) strKey := ms.prefix + key - hdl.HSetAll(strKey, mdfd.GetTuple().ToMap()) + ms.HSetAll(strKey, mdfd.GetTuple().ToMap()) } } } func (ms *storeImpl) DeleteTuples(deleted map[string]map[string]model.Tuple) { - hdl := redisutils.GetRedisHdl() for tupleType, tuples := range deleted { for key, _ := range tuples { fmt.Printf("Deleting tuple. Type [%s] Key [%s]\n", tupleType, key) strKey := ms.prefix + key - hdl.Del(strKey) + ms.Del(strKey) } } } From 1e93ad9a79ee8ca24cc92f307f5fe366b213feb2 Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Fri, 18 Oct 2019 10:54:04 -0600 Subject: [PATCH 094/125] Improved support for ttl of 0 --- rete/joinnode.go | 24 ++++++++++++++++++++---- rete/network.go | 2 +- ruleapi/tests/rtctxn_2_test.go | 4 ++-- ruleapi/tests/rtctxn_6_test.go | 4 ++-- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/rete/joinnode.go b/rete/joinnode.go index 3d07537..5ddcb38 100644 --- a/rete/joinnode.go +++ b/rete/joinnode.go @@ -177,12 +177,20 @@ func (jn *joinNodeImpl) assertFromRight(ctx context.Context, handles []types.Ret //TODO: other stuff. right now focus on tuple table jn.joinRightObjects(handles, joinedHandles) //tupleTableRow := newJoinTableRow(handles, jn.nw.incrementAndGetId()) + add := true for _, handle := range handles { if status := handle.GetStatus(); status == types.ReteHandleStatusCreating { - jn.GetNw().GetTupleStore().SaveTuple(handle.GetTuple()) + tuple := handle.GetTuple() + if descriptor := model.GetTupleDescriptor(tuple.GetTupleType()); descriptor.TTLInSeconds != 0 { + jn.GetNw().GetTupleStore().SaveTuple(tuple) + } else { + add = false + } } } - jn.rightTable.AddRow(handles) + if add { + jn.rightTable.AddRow(handles) + } //TODO: rete listeners etc. rIterator := jn.leftTable.GetRowIterator(ctx) LOOP: @@ -249,12 +257,20 @@ func (jn *joinNodeImpl) assertFromLeft(ctx context.Context, handles []types.Rete jn.joinLeftObjects(handles, joinedHandles) //TODO: other stuff. right now focus on tuple table //tupleTableRow := newJoinTableRow(handles, jn.nw.incrementAndGetId()) + add := true for _, handle := range handles { if status := handle.GetStatus(); status == types.ReteHandleStatusCreating { - jn.GetNw().GetTupleStore().SaveTuple(handle.GetTuple()) + tuple := handle.GetTuple() + if descriptor := model.GetTupleDescriptor(tuple.GetTupleType()); descriptor.TTLInSeconds != 0 { + jn.GetNw().GetTupleStore().SaveTuple(tuple) + } else { + add = false + } } } - jn.leftTable.AddRow(handles) + if add { + jn.leftTable.AddRow(handles) + } //TODO: rete listeners etc. rIterator := jn.rightTable.GetRowIterator(ctx) LOOP: diff --git a/rete/network.go b/rete/network.go index 95655a7..0e57d57 100644 --- a/rete/network.go +++ b/rete/network.go @@ -709,7 +709,7 @@ func (nw *reteNetworkImpl) AssertInternal(ctx context.Context, tuple model.Tuple } td := model.GetTupleDescriptor(tuple.GetTupleType()) if td != nil { - if /*td.TTLInSeconds != 0 && */ mode == common.ADD { + if td.TTLInSeconds != 0 && mode == common.ADD { rCtx := getReteCtx(ctx) if rCtx != nil { rCtx.AddToRtcAdded(tuple) diff --git a/ruleapi/tests/rtctxn_2_test.go b/ruleapi/tests/rtctxn_2_test.go index 0217e9b..77951ec 100644 --- a/ruleapi/tests/rtctxn_2_test.go +++ b/ruleapi/tests/rtctxn_2_test.go @@ -44,8 +44,8 @@ func t2Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han t := handlerCtx.(*testing.T) lA := len(rtxn.GetRtcAdded()) - if lA != 1 { - t.Errorf("RtcAdded: Expected [%d], got [%d]\n", 1, lA) + if lA != 0 { + t.Errorf("RtcAdded: Expected [%d], got [%d]\n", 0, lA) printTuples(t, "Added", rtxn.GetRtcAdded()) } lM := len(rtxn.GetRtcModified()) diff --git a/ruleapi/tests/rtctxn_6_test.go b/ruleapi/tests/rtctxn_6_test.go index b5995aa..90cfe2a 100644 --- a/ruleapi/tests/rtctxn_6_test.go +++ b/ruleapi/tests/rtctxn_6_test.go @@ -116,8 +116,8 @@ func t6Handler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, han } } else if txnCtx.TxnCnt == 3 { lA := len(rtxn.GetRtcAdded()) - if lA != 2 { - t.Errorf("RtcAdded: Types expected [%d], got [%d]\n", 2, lA) + if lA != 1 { + t.Errorf("RtcAdded: Types expected [%d], got [%d]\n", 1, lA) printTuples(t, "Added", rtxn.GetRtcAdded()) } else { added, _ := rtxn.GetRtcAdded()["t1"] From 9baaf041d038ed795e8f6a6e000ea1169315a6d8 Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Mon, 21 Oct 2019 11:29:34 -0600 Subject: [PATCH 095/125] Use different port for redis test --- redisutils/redisutil_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/redisutils/redisutil_test.go b/redisutils/redisutil_test.go index f12be59..0139b05 100644 --- a/redisutils/redisutil_test.go +++ b/redisutils/redisutil_test.go @@ -38,12 +38,12 @@ func Pour(port string) { func TestMain(m *testing.M) { run := func() int { - command := exec.Command("docker", "run", "-p", "6379:6379", "-d", "redis") + command := exec.Command("docker", "run", "-p", "6382:6379", "-d", "redis") hash, err := command.Output() if err != nil { panic(err) } - Pour("6379") + Pour("6382") defer func() { command := exec.Command("docker", "stop", strings.TrimSpace(string(hash))) @@ -56,7 +56,7 @@ func TestMain(m *testing.M) { if err != nil { panic(err) } - Drain("6379") + Drain("6382") }() return m.Run() @@ -65,7 +65,7 @@ func TestMain(m *testing.M) { } func Test_first(t *testing.T) { - rd := NewRedisHdl(RedisConfig{}) + rd := NewRedisHdl(RedisConfig{Address: ":6382"}) defer Shutdown() m := make(map[string]interface{}) @@ -209,7 +209,7 @@ func getStruct(c redis.Conn) error { } func Test_three(t *testing.T) { - hdl := NewRedisHdl(RedisConfig{}) + hdl := NewRedisHdl(RedisConfig{Address: ":6382"}) defer Shutdown() //iter := hdl.GetListIterator("x:jt:L_c2") @@ -238,7 +238,7 @@ func Test_three(t *testing.T) { } func Test_four(t *testing.T) { - hdl := NewRedisHdl(RedisConfig{}) + hdl := NewRedisHdl(RedisConfig{Address: ":6382"}) defer Shutdown() //v := hdl.HGet("a", "d") @@ -248,7 +248,7 @@ func Test_four(t *testing.T) { } func Test_five(t *testing.T) { - hdl := NewRedisHdl(RedisConfig{}) + hdl := NewRedisHdl(RedisConfig{Address: ":6382"}) defer Shutdown() for i := 0; i < 10; i++ { From 220ee4d1b915e085dd6d7c0e6fc1bf8f01d4fe7b Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Mon, 21 Oct 2019 12:04:40 -0600 Subject: [PATCH 096/125] Use different redis port for ruleapp test --- examples/rulesapp/rsconfig.json | 8 ++++---- examples/rulesapp/ruleapp_test.go | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/rulesapp/rsconfig.json b/examples/rulesapp/rsconfig.json index 2681c7d..8016974 100644 --- a/examples/rulesapp/rsconfig.json +++ b/examples/rulesapp/rsconfig.json @@ -13,7 +13,7 @@ }, "redis": { "network": "tcp", - "address": ":6379" + "address": ":6383" } }, "idgens": { @@ -21,7 +21,7 @@ }, "redis": { "network": "tcp", - "address": ":6379" + "address": ":6383" } }, "jts": { @@ -29,7 +29,7 @@ }, "redis": { "network": "tcp", - "address": ":6379" + "address": ":6383" } } -} \ No newline at end of file +} diff --git a/examples/rulesapp/ruleapp_test.go b/examples/rulesapp/ruleapp_test.go index 9c0f31a..1e195c2 100644 --- a/examples/rulesapp/ruleapp_test.go +++ b/examples/rulesapp/ruleapp_test.go @@ -19,12 +19,12 @@ func TestMain(m *testing.M) { } run := func() int { - command := exec.Command("docker", "run", "-p", "6379:6379", "-d", "redis") + command := exec.Command("docker", "run", "-p", "6383:6379", "-d", "redis") hash, err := command.Output() if err != nil { panic(err) } - tests.Pour("6379") + tests.Pour("6383") defer func() { command := exec.Command("docker", "stop", strings.TrimSpace(string(hash))) @@ -37,7 +37,7 @@ func TestMain(m *testing.M) { if err != nil { panic(err) } - tests.Drain("6379") + tests.Drain("6383") }() return m.Run() From 69f648213d6a2ff00bb601026039a11d8178d002 Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Thu, 24 Oct 2019 13:16:50 -0600 Subject: [PATCH 097/125] Improved handle logic --- rete/internal/mem/mhandleservice.go | 10 +++++----- rete/internal/redis/rhandleservice.go | 16 +++++++++------- rete/internal/types/types.go | 2 +- rete/network.go | 6 ++++-- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/rete/internal/mem/mhandleservice.go b/rete/internal/mem/mhandleservice.go index c8040f2..3577cde 100644 --- a/rete/internal/mem/mhandleservice.go +++ b/rete/internal/mem/mhandleservice.go @@ -74,20 +74,20 @@ func (hc *handleServiceImpl) GetOrCreateLockedHandle(nw types.Network, tuple mod return nil, true } -func (hc *handleServiceImpl) GetLockedHandle(nw types.Network, tuple model.Tuple) (types.ReteHandle, bool) { +func (hc *handleServiceImpl) GetLockedHandle(nw types.Network, tuple model.Tuple) (handle types.ReteHandle, locked, dne bool) { hc.Lock() defer hc.Unlock() - id := hc.Int63() h, found := hc.allHandles[tuple.GetKey().String()] if !found { - return nil, true + return nil, false, true } + id := hc.Int63() if atomic.CompareAndSwapInt64(&h.id, -1, id) { - return h, false + return h, false, false } - return nil, true + return nil, true, false } func (hc *handleServiceImpl) GetHandleWithTuple(nw types.Network, tuple model.Tuple) types.ReteHandle { diff --git a/rete/internal/redis/rhandleservice.go b/rete/internal/redis/rhandleservice.go index 7159ffb..5b81f61 100644 --- a/rete/internal/redis/rhandleservice.go +++ b/rete/internal/redis/rhandleservice.go @@ -151,13 +151,13 @@ func (hc *handleServiceImpl) GetOrCreateLockedHandle(nw types.Network, tuple mod return h, false } -func (hc *handleServiceImpl) GetLockedHandle(nw types.Network, tuple model.Tuple) (types.ReteHandle, bool) { +func (hc *handleServiceImpl) GetLockedHandle(nw types.Network, tuple model.Tuple) (handle types.ReteHandle, locked, dne bool) { id := hc.Int63() key := hc.prefix + tuple.GetKey().String() - exists, _ := hc.HSetNX(key, "id", id) - if exists { - return nil, true + locked, _ = hc.HSetNX(key, "id", id) + if locked { + return nil, true, false } m := hc.HGetAll(key) @@ -168,13 +168,15 @@ func (hc *handleServiceImpl) GetLockedHandle(nw types.Network, tuple model.Tuple if err != nil { panic(err) } - h := newReteHandleImpl(nw, hc.RedisHdl, tuple, key, types.ReteHandleStatus(number), id) - return h, false + handle = newReteHandleImpl(nw, hc.RedisHdl, tuple, key, types.ReteHandleStatus(number), id) + return handle, false, false } } } - return nil, true + hc.Del(key) + + return nil, false, true } func (hc *handleServiceImpl) GetHandleWithTuple(nw types.Network, tuple model.Tuple) types.ReteHandle { diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index 34b393f..9fbd81b 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -111,7 +111,7 @@ type HandleService interface { GetHandleByKey(ctx context.Context, key model.TupleKey) ReteHandle GetHandleWithTuple(nw Network, tuple model.Tuple) ReteHandle GetOrCreateLockedHandle(nw Network, tuple model.Tuple) (ReteHandle, bool) - GetLockedHandle(nw Network, tuple model.Tuple) (ReteHandle, bool) + GetLockedHandle(nw Network, tuple model.Tuple) (handle ReteHandle, locked, dne bool) } type IdGen interface { diff --git a/rete/network.go b/rete/network.go index 0e57d57..3e7e6d1 100644 --- a/rete/network.go +++ b/rete/network.go @@ -643,9 +643,11 @@ func (nw *reteNetworkImpl) Retract(ctx context.Context, rs model.RuleSession, tu } func (nw *reteNetworkImpl) RetractInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) error { - handle, locked := nw.handleService.GetLockedHandle(nw, tuple) + handle, locked, dne := nw.handleService.GetLockedHandle(nw, tuple) if locked { - return fmt.Errorf("Tuple with key [%s] is locked or doesn't exist", tuple.GetKey().String()) + return fmt.Errorf("Tuple with key [%s] is locked", tuple.GetKey().String()) + } else if dne { + return fmt.Errorf("Tuple with key [%s] doesn't exist", tuple.GetKey().String()) } else if handle.GetStatus() != types.ReteHandleStatusCreated { handle.Unlock() return fmt.Errorf("Tuple with key [%s] is not created: %d", tuple.GetKey().String(), handle.GetStatus()) From b17aaa1560c5ea8dcc9de6a2b2bf92e3e7cb0376 Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Fri, 25 Oct 2019 14:54:59 -0600 Subject: [PATCH 098/125] Allow the user to select consistency or performance --- examples/flogo/dtable/rsconfig.json | 1 + examples/rulesapp/rsconfig.json | 1 + redisutils/redisutil_test.go | 31 +++++ redisutils/redisutils.go | 28 ++++ rete/common/types.go | 5 +- rete/factory.go | 51 ++++---- rete/internal/mem/mlockservice.go | 28 ++++ rete/internal/redis/redis_test.go | 190 ++++++++++++++++++++++++++++ rete/internal/redis/rlockservice.go | 69 ++++++++++ rete/internal/types/types.go | 7 + rete/network.go | 36 +++++- ruleapi/rulesession.go | 1 + ruleapi/tests/common.go | 11 +- ruleapi/tests/main_test.go | 6 + ruleapi/tests/rsconfig.json | 1 + ruleapi/tests/rsconfigp.json | 36 ++++++ 16 files changed, 474 insertions(+), 28 deletions(-) create mode 100644 rete/internal/mem/mlockservice.go create mode 100644 rete/internal/redis/redis_test.go create mode 100644 rete/internal/redis/rlockservice.go create mode 100644 ruleapi/tests/rsconfigp.json diff --git a/examples/flogo/dtable/rsconfig.json b/examples/flogo/dtable/rsconfig.json index c0e2d37..7511a15 100644 --- a/examples/flogo/dtable/rsconfig.json +++ b/examples/flogo/dtable/rsconfig.json @@ -1,4 +1,5 @@ { + "mode": "consistency", "rs": { "prefix": "x", "store-ref": "redis" diff --git a/examples/rulesapp/rsconfig.json b/examples/rulesapp/rsconfig.json index 8016974..fae556a 100644 --- a/examples/rulesapp/rsconfig.json +++ b/examples/rulesapp/rsconfig.json @@ -1,4 +1,5 @@ { + "mode": "consistency", "rs": { "prefix": "x", "store-ref": "redis" diff --git a/redisutils/redisutil_test.go b/redisutils/redisutil_test.go index 0139b05..be3b311 100644 --- a/redisutils/redisutil_test.go +++ b/redisutils/redisutil_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/gomodule/redigo/redis" + "github.com/stretchr/testify/assert" ) func Drain(port string) { @@ -258,3 +259,33 @@ func Test_five(t *testing.T) { } } + +func TestSet(t *testing.T) { + hdl := NewRedisHdl(RedisConfig{Address: ":6382"}) + defer Shutdown() + + ok, err := hdl.Set("lock", "123", true, 60000) + assert.Nil(t, err) + assert.True(t, ok) + ok, err = hdl.Set("lock", "123", true, 60000) + assert.NotNil(t, err) + assert.False(t, ok) + ok, err = hdl.Set("lock", "1234", false, 60000) + assert.Nil(t, err) + assert.True(t, ok) +} + +func TestDelIfEqual(t *testing.T) { + hdl := NewRedisHdl(RedisConfig{Address: ":6382"}) + defer Shutdown() + + ok, err := hdl.Set("lockx", "123", true, 60000) + assert.Nil(t, err) + assert.True(t, ok) + count, err := hdl.DelIfEqual("lockx", "1234") + assert.Nil(t, err) + assert.Equal(t, 0, count) + count, err = hdl.DelIfEqual("lockx", "123") + assert.Nil(t, err) + assert.Equal(t, 1, count) +} diff --git a/redisutils/redisutils.go b/redisutils/redisutils.go index 0f07e62..04a2a28 100644 --- a/redisutils/redisutils.go +++ b/redisutils/redisutils.go @@ -2,6 +2,7 @@ package redisutils import ( "fmt" + "github.com/gomodule/redigo/redis" ) @@ -60,6 +61,33 @@ func (rh *RedisHandle) getPool() *redis.Pool { return rh.pool } +func (rh *RedisHandle) Set(key string, value string, nx bool, px int64) (bool, error) { + c := rh.getPool().Get() + defer c.Close() + args := []interface{}{key, value} + if nx { + args = append(args, "NX") + } + if px > 0 { + args = append(args, "PX", px) + } + ok, err := redis.String(c.Do("SET", args...)) + return ok == "OK", err +} + +const delIfEqualScript = `if redis.call("get",KEYS[1]) == ARGV[1] +then + return redis.call("del",KEYS[1]) +else + return 0 +end` + +func (rh *RedisHandle) DelIfEqual(key string, value string) (int, error) { + c := rh.getPool().Get() + defer c.Close() + return redis.Int(c.Do("EVAL", delIfEqualScript, 1, key, value)) +} + func (rh *RedisHandle) HSet(key string, field string, value interface{}) (bool, error) { c := rh.getPool().Get() defer c.Close() diff --git a/rete/common/types.go b/rete/common/types.go index 2285ec6..15e33a2 100644 --- a/rete/common/types.go +++ b/rete/common/types.go @@ -39,6 +39,8 @@ type Network interface { const ( ServiceTypeMem = "mem" ServiceTypeRedis = "redis" + ModeConsistency = "consistency" + ModePerformance = "performance" ) type Service struct { @@ -47,7 +49,8 @@ type Service struct { } type Config struct { - Rs struct { + Mode string `json:"mode"` + Rs struct { Prefix string `json:"prefix"` StoreRef string `json:"store-ref"` } `json:"rs"` diff --git a/rete/factory.go b/rete/factory.go index 1336bc6..ebd2c7b 100644 --- a/rete/factory.go +++ b/rete/factory.go @@ -1,8 +1,6 @@ package rete import ( - "encoding/json" - "github.com/project-flogo/rules/rete/common" "github.com/project-flogo/rules/rete/internal/mem" "github.com/project-flogo/rules/rete/internal/redis" @@ -11,61 +9,68 @@ import ( type TypeFactory struct { nw *reteNetworkImpl - config string - parsed common.Config + config common.Config } -func NewFactory(nw *reteNetworkImpl, config string) (*TypeFactory, error) { - tf := TypeFactory{} - tf.config = config - err := json.Unmarshal([]byte(config), &tf.parsed) - if err != nil { - return nil, err +func NewFactory(nw *reteNetworkImpl, config common.Config) (*TypeFactory, error) { + tf := TypeFactory{ + nw: nw, + config: config, } - tf.nw = nw return &tf, nil } func (f *TypeFactory) getJoinTableRefs() types.JtRefsService { - switch f.parsed.Rete.Jt { + switch f.config.Rete.Jt { case common.ServiceTypeMem: - return mem.NewJoinTableRefsInHdlImpl(f.nw, f.parsed) + return mem.NewJoinTableRefsInHdlImpl(f.nw, f.config) case common.ServiceTypeRedis: - return redis.NewJoinTableRefsInHdlImpl(f.nw, f.parsed) + return redis.NewJoinTableRefsInHdlImpl(f.nw, f.config) default: panic("invalid service type") } } func (f *TypeFactory) getJoinTableCollection() types.JtService { - switch f.parsed.Rete.Jt { + switch f.config.Rete.Jt { case common.ServiceTypeMem: - return mem.NewJoinTableCollection(f.nw, f.parsed) + return mem.NewJoinTableCollection(f.nw, f.config) case common.ServiceTypeRedis: - return redis.NewJoinTableCollection(f.nw, f.parsed) + return redis.NewJoinTableCollection(f.nw, f.config) default: panic("invalid service type") } } func (f *TypeFactory) getHandleCollection() types.HandleService { - switch f.parsed.Rete.JtRef { + switch f.config.Rete.JtRef { case common.ServiceTypeMem: - return mem.NewHandleCollection(f.nw, f.parsed) + return mem.NewHandleCollection(f.nw, f.config) case common.ServiceTypeRedis: - return redis.NewHandleCollection(f.nw, f.parsed) + return redis.NewHandleCollection(f.nw, f.config) default: panic("invalid service type") } } func (f *TypeFactory) getIdGen() types.IdGen { - switch f.parsed.Rete.IDGenRef { + switch f.config.Rete.IDGenRef { + case common.ServiceTypeMem: + return mem.NewIdGenImpl(f.nw, f.config) + case common.ServiceTypeRedis: + return redis.NewIdGenImpl(f.nw, f.config) + default: + panic("invalid service type") + } +} + +func (f *TypeFactory) getLockService() types.LockService { + switch f.config.Rete.IDGenRef { case common.ServiceTypeMem: - return mem.NewIdGenImpl(f.nw, f.parsed) + return mem.NewLockServiceImpl(f.nw, f.config) case common.ServiceTypeRedis: - return redis.NewIdGenImpl(f.nw, f.parsed) + return redis.NewLockServiceImpl(f.nw, f.config) default: panic("invalid service type") } diff --git a/rete/internal/mem/mlockservice.go b/rete/internal/mem/mlockservice.go new file mode 100644 index 0000000..e557853 --- /dev/null +++ b/rete/internal/mem/mlockservice.go @@ -0,0 +1,28 @@ +package mem + +import ( + "sync" + + "github.com/project-flogo/rules/rete/common" + "github.com/project-flogo/rules/rete/internal/types" +) + +type lockServiceImpl struct { + types.NwServiceImpl + config common.Config + sync.Mutex +} + +func NewLockServiceImpl(nw types.Network, config common.Config) types.LockService { + lockService := lockServiceImpl{ + NwServiceImpl: types.NwServiceImpl{ + Nw: nw, + }, + config: config, + } + return &lockService +} + +func (l *lockServiceImpl) Init() { + +} diff --git a/rete/internal/redis/redis_test.go b/rete/internal/redis/redis_test.go new file mode 100644 index 0000000..9aefc49 --- /dev/null +++ b/rete/internal/redis/redis_test.go @@ -0,0 +1,190 @@ +package redis + +import ( + "context" + "math/rand" + "net" + "os" + "os/exec" + "strings" + "testing" + "time" + + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/redisutils" + "github.com/project-flogo/rules/rete/common" + "github.com/project-flogo/rules/rete/internal/types" +) + +func Drain(port string) { + for { + conn, err := net.DialTimeout("tcp", net.JoinHostPort("", port), time.Second) + if conn != nil { + conn.Close() + } + if err != nil && strings.Contains(err.Error(), "connect: connection refused") { + break + } + } +} + +func Pour(port string) { + for { + conn, _ := net.Dial("tcp", net.JoinHostPort("", port)) + if conn != nil { + conn.Close() + break + } + } +} + +func TestMain(m *testing.M) { + run := func() int { + command := exec.Command("docker", "run", "-p", "6384:6379", "-d", "redis") + hash, err := command.Output() + if err != nil { + panic(err) + } + Pour("6384") + + defer func() { + command := exec.Command("docker", "stop", strings.TrimSpace(string(hash))) + err := command.Run() + if err != nil { + panic(err) + } + command = exec.Command("docker", "rm", strings.TrimSpace(string(hash))) + err = command.Run() + if err != nil { + panic(err) + } + Drain("6384") + }() + + return m.Run() + } + os.Exit(run()) +} + +type testNetwork struct { + Prefix string +} + +func (n *testNetwork) AddRule(model.Rule) error { + return nil +} + +func (n *testNetwork) String() string { + return "" +} + +func (n *testNetwork) RemoveRule(string) model.Rule { + return nil +} + +func (n *testNetwork) GetRules() []model.Rule { + return nil +} + +func (n *testNetwork) Assert(ctx context.Context, rs model.RuleSession, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) error { + return nil +} + +func (n *testNetwork) Retract(ctx context.Context, rs model.RuleSession, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) error { + return nil +} + +func (n *testNetwork) GetAssertedTuple(ctx context.Context, rs model.RuleSession, key model.TupleKey) model.Tuple { + return nil +} + +func (n *testNetwork) RegisterRtcTransactionHandler(txnHandler model.RtcTransactionHandler, txnContext interface{}) { + +} + +func (n *testNetwork) SetTupleStore(tupleStore model.TupleStore) { + +} + +func (n *testNetwork) GetHandleWithTuple(ctx context.Context, tuple model.Tuple) types.ReteHandle { + return nil +} + +func (n *testNetwork) AssertInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) error { + return nil +} + +func (n *testNetwork) RetractInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) error { + return nil +} + +func (n *testNetwork) GetPrefix() string { + return n.Prefix +} + +func (n *testNetwork) GetIdGenService() types.IdGen { + return nil +} + +func (n *testNetwork) GetLockService() types.LockService { + return nil +} + +func (n *testNetwork) GetJtService() types.JtService { + return nil +} + +func (n *testNetwork) GetHandleService() types.HandleService { + return nil +} + +func (n *testNetwork) GetJtRefService() types.JtRefsService { + return nil +} + +func (n *testNetwork) GetTupleStore() model.TupleStore { + return nil +} + +func TestLockServiceImpl(t *testing.T) { + fini := make(chan bool, 1) + go func() { + config := common.Config{ + IDGens: common.Service{ + Redis: redisutils.RedisConfig{ + Network: "tcp", + Address: ":6384", + }, + }, + } + network := &testNetwork{ + Prefix: "a", + } + serviceA, serviceB := NewLockServiceImpl(network, config), NewLockServiceImpl(network, config) + done := make(chan bool, 8) + for i := 0; i < 100; i++ { + go func() { + serviceA.Lock() + time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond) + serviceA.Unlock() + done <- true + }() + go func() { + serviceB.Lock() + time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond) + serviceB.Unlock() + done <- true + }() + } + for i := 0; i < 100; i++ { + <-done + <-done + } + fini <- true + }() + select { + case <-time.After(60 * time.Second): + t.Fatal("test took too long") + case <-fini: + } +} diff --git a/rete/internal/redis/rlockservice.go b/rete/internal/redis/rlockservice.go new file mode 100644 index 0000000..137b31b --- /dev/null +++ b/rete/internal/redis/rlockservice.go @@ -0,0 +1,69 @@ +package redis + +import ( + "math/rand" + "strconv" + "sync" + "time" + + "github.com/project-flogo/rules/redisutils" + "github.com/project-flogo/rules/rete/common" + "github.com/project-flogo/rules/rete/internal/types" +) + +type lockServiceImpl struct { + types.NwServiceImpl + config common.Config + key string // key used to access lock + redisutils.RedisHdl + rand.Source + sync.Mutex + done chan bool +} + +func NewLockServiceImpl(nw types.Network, config common.Config) types.LockService { + r := lockServiceImpl{ + NwServiceImpl: types.NwServiceImpl{ + Nw: nw, + }, + config: config, + RedisHdl: redisutils.NewRedisHdl(config.IDGens.Redis), + Source: rand.NewSource(time.Now().UnixNano()), + done: make(chan bool, 1), + } + return &r +} + +func (l *lockServiceImpl) Init() { + l.key = l.Nw.GetPrefix() + ":lock" +} + +func (l *lockServiceImpl) Lock() { + l.Mutex.Lock() + value := strconv.FormatInt(l.Int63(), 10) + for { + ok, _ := l.Set(l.key, value, true, 16000) + if ok { + go func() { + defer l.Mutex.Unlock() + ticker := time.NewTicker(4 * time.Second) + for { + select { + case <-ticker.C: + l.Set(l.key, value, false, 16000) + case <-l.done: + ticker.Stop() + l.DelIfEqual(l.key, value) + return + } + } + }() + return + } + time.Sleep(128 * time.Millisecond) + } +} + +func (l *lockServiceImpl) Unlock() { + l.done <- true +} diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index 9fbd81b..46c8229 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -15,6 +15,7 @@ type Network interface { RetractInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) error GetPrefix() string GetIdGenService() IdGen + GetLockService() LockService GetJtService() JtService GetHandleService() HandleService GetJtRefService() JtRefsService @@ -120,6 +121,12 @@ type IdGen interface { GetNextID() int } +type LockService interface { + NwService + Lock() + Unlock() +} + type JointableIterator interface { HasNext() bool Next() (JoinTableRow, JoinTable) diff --git a/rete/network.go b/rete/network.go index 3e7e6d1..e85e9ff 100644 --- a/rete/network.go +++ b/rete/network.go @@ -2,6 +2,7 @@ package rete import ( "context" + "encoding/json" "fmt" "math" "strconv" @@ -47,6 +48,7 @@ type reteNetworkImpl struct { factory *TypeFactory idGen types.IdGen + lock types.LockService tupleStore model.TupleStore joinNodeNames int @@ -68,7 +70,12 @@ func (nw *reteNetworkImpl) initReteNetwork(sessionName string, config string) er nw.ruleNameClassNodeLinksOfRule = make(map[string]*list.List) nw.txnHandler = []model.RtcTransactionHandler{} - factory, err := NewFactory(nw, config) + var parsed common.Config + err := json.Unmarshal([]byte(config), &parsed) + if err != nil { + return err + } + factory, err := NewFactory(nw, parsed) if err != nil { return err } @@ -80,6 +87,13 @@ func (nw *reteNetworkImpl) initReteNetwork(sessionName string, config string) er //} nw.prefix = sessionName nw.idGen = factory.getIdGen() + switch parsed.Mode { + case "", common.ModeConsistency: + nw.lock = factory.getLockService() + case common.ModePerformance: + default: + return fmt.Errorf("%s is an invalid mode", parsed.Mode) + } nw.jtService = factory.getJoinTableCollection() nw.handleService = factory.getHandleCollection() nw.jtRefsService = factory.getJoinTableRefs() @@ -90,6 +104,9 @@ func (nw *reteNetworkImpl) initReteNetwork(sessionName string, config string) er func (nw *reteNetworkImpl) initNwServices() { nw.idGen.Init() + if nw.lock != nil { + nw.lock.Init() + } nw.jtService.Init() nw.handleService.Init() nw.jtRefsService.Init() @@ -576,6 +593,10 @@ func (nw *reteNetworkImpl) Assert(ctx context.Context, rs model.RuleSession, tup if !isRecursive { nw.RLock() defer nw.RUnlock() + if nw.lock != nil { + nw.lock.Lock() + defer nw.lock.Unlock() + } err := nw.AssertInternal(newCtx, tuple, changedProps, mode) if err != nil { @@ -592,6 +613,10 @@ func (nw *reteNetworkImpl) Assert(ctx context.Context, rs model.RuleSession, tup time.AfterFunc(time.Second*time.Duration(td.TTLInSeconds), func() { nw.RLock() defer nw.RUnlock() + if nw.lock != nil { + nw.lock.Lock() + defer nw.lock.Unlock() + } nw.removeTupleFromRete(nil, tuple) }) } //else, its -ve and means, never expire @@ -625,6 +650,11 @@ func (nw *reteNetworkImpl) Retract(ctx context.Context, rs model.RuleSession, tu if !isRecursive { nw.RLock() defer nw.RUnlock() + if nw.lock != nil { + nw.lock.Lock() + defer nw.lock.Unlock() + } + err := nw.RetractInternal(ctx, tuple, changedProps, mode) if err != nil { return err @@ -810,6 +840,10 @@ func (nw *reteNetworkImpl) GetIdGenService() types.IdGen { return nw.idGen } +func (nw *reteNetworkImpl) GetLockService() types.LockService { + return nw.lock +} + func (nw *reteNetworkImpl) GetJtService() types.JtService { return nw.jtService } diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index 5340f20..3871f5a 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -118,6 +118,7 @@ func GetOrCreateRuleSessionFromConfig(name, store, jsonConfig string) (model.Rul } const defaultConfig = `{ + "mode": "consistency", "rs": { "prefix": "x", "store-ref": "mem" diff --git a/ruleapi/tests/common.go b/ruleapi/tests/common.go index 631a00e..ade2e57 100644 --- a/ruleapi/tests/common.go +++ b/ruleapi/tests/common.go @@ -26,8 +26,9 @@ import ( ) var ( - redis = false - done = false + redis = false + performance = false + done = false ) func createRuleSession() (model.RuleSession, error) { @@ -45,7 +46,11 @@ func createRuleSession() (model.RuleSession, error) { store := "" if redis { - store = "rsconfig.json" + if performance { + store = "rsconfigp.json" + } else { + store = "rsconfig.json" + } } return ruleapi.GetOrCreateRuleSession("test", store) } diff --git a/ruleapi/tests/main_test.go b/ruleapi/tests/main_test.go index 52e32e2..4f1fa27 100644 --- a/ruleapi/tests/main_test.go +++ b/ruleapi/tests/main_test.go @@ -38,5 +38,11 @@ func TestMain(m *testing.M) { return m.Run() } redis = true + code = run() + if code != 0 { + os.Exit(code) + } + + performance = true os.Exit(run()) } diff --git a/ruleapi/tests/rsconfig.json b/ruleapi/tests/rsconfig.json index e85a748..e5a49bf 100644 --- a/ruleapi/tests/rsconfig.json +++ b/ruleapi/tests/rsconfig.json @@ -1,4 +1,5 @@ { + "mode": "consistency", "rs": { "prefix": "x", "store-ref": "redis" diff --git a/ruleapi/tests/rsconfigp.json b/ruleapi/tests/rsconfigp.json new file mode 100644 index 0000000..d79588c --- /dev/null +++ b/ruleapi/tests/rsconfigp.json @@ -0,0 +1,36 @@ +{ + "mode": "performance", + "rs": { + "prefix": "x", + "store-ref": "redis" + }, + "rete": { + "jt-ref": "redis", + "idgen-ref": "redis", + "jt":"redis" + }, + "stores": { + "mem": { + }, + "redis": { + "network": "tcp", + "address": ":6380" + } + }, + "idgens": { + "mem": { + }, + "redis": { + "network": "tcp", + "address": ":6380" + } + }, + "jts": { + "mem": { + }, + "redis": { + "network": "tcp", + "address": ":6380" + } + } +} From af2b2f19146bc2fa83ba87d17e11b05e2f3cab68 Mon Sep 17 00:00:00 2001 From: Ramesh Polishetti Date: Sun, 27 Oct 2019 02:20:50 +0530 Subject: [PATCH 099/125] Add excel file support for decision table --- activity/dtable/activity.go | 409 ++++++++++++++++++++++++------- activity/dtable/activity_test.go | 179 ++++++++++++++ activity/dtable/metadata.go | 66 +---- activity/dtable/test_dtable.csv | 3 + activity/dtable/test_dtable.xlsx | Bin 0 -> 9220 bytes 5 files changed, 507 insertions(+), 150 deletions(-) create mode 100644 activity/dtable/activity_test.go create mode 100644 activity/dtable/test_dtable.csv create mode 100644 activity/dtable/test_dtable.xlsx diff --git a/activity/dtable/activity.go b/activity/dtable/activity.go index 22c4656..25342ef 100644 --- a/activity/dtable/activity.go +++ b/activity/dtable/activity.go @@ -2,37 +2,82 @@ package dtable import ( "context" + "encoding/csv" "fmt" + "os" "strconv" "strings" + "github.com/360EntSecGroup-Skylar/excelize" "github.com/project-flogo/core/activity" "github.com/project-flogo/core/data" + "github.com/project-flogo/core/data/metadata" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" ) +// Decision table column types +type dtColType int8 + +const ( + ctID dtColType = iota // ID + ctCondition // condition + ctAction // action + ctDescription // Description + ctPriority // priority +) + func init() { _ = activity.Register(&Activity{}, New) } // Activity decision table based rule action type Activity struct { - tasks []*DecisionTable + dtable *dTable +} + +type dTable struct { + titleRow1 []genCell + titleRow2 []genCell + metaRow []metaCell + rows [][]genCell +} + +type metaCell struct { + colType dtColType + tupleDesc *model.TupleDescriptor + propDesc *model.TuplePropertyDescriptor +} + +type genCell struct { + *metaCell + rawValue string + cdExpr string } // New creates new decision table activity func New(ctx activity.InitContext) (activity.Activity, error) { // Read settings settings := &Settings{} - err := settings.FromMap(ctx.Settings()) + err := metadata.MapToStruct(ctx.Settings(), settings, true) if err != nil { return nil, err } + // Read decision table from file + dtable, err := loadFromFile(settings.DTableFile) + if err != nil { + return nil, err + } + err = dtable.compile() + if err != nil { + return nil, err + } + dtable.print() + // Read setting from init context act := &Activity{ - tasks: settings.Make, + dtable: dtable, } return act, nil } @@ -48,102 +93,296 @@ func (a *Activity) Eval(ctx activity.Context) (done bool, err error) { context := ctx.GetInput("ctx").(context.Context) tuples := ctx.GetInput("tuples").(map[model.TupleType]model.Tuple) - // Run tasks - for _, task := range a.tasks { - conditionEval := false - for _, cond := range task.DtConditions { - - eval := evaluateCondition(cond, tuples) - if eval { - conditionEval = true - continue - } else { - conditionEval = false - break + // evaluate decision table + a.dtable.apply(context, tuples) + + return true, nil +} + +func loadFromFile(fileName string) (*dTable, error) { + if fileName == "" { + return nil, fmt.Errorf("file name can't be empty") + } + tokens := strings.Split(fileName, ".") + fileExtension := tokens[len(tokens)-1] + + if fileExtension == "csv" || fileExtension == "CSV" { + return loadFromCSVFile(fileName) + } else if fileExtension == "xls" || fileExtension == "XLS" || fileExtension == "xlsx" || fileExtension == "XLSX" { + return loadFromXLSFile(fileName) + } + + return nil, fmt.Errorf("file[%s] extension not supported", fileName) +} + +// loadFromCSVFile loads decision table from CSV file +func loadFromCSVFile(fileName string) (*dTable, error) { + file, err := os.Open(fileName) + if err != nil { + return nil, fmt.Errorf("not able open the file [%s] - %s", fileName, err) + } + defer file.Close() + + lines, err := csv.NewReader(file).ReadAll() + if err != nil { + return nil, fmt.Errorf("not able read the file [%s] - %s", fileName, err) + } + + dtable := &dTable{} + dtable.rows = make([][]genCell, len(lines)-2) + for i, line := range lines { + if i == 0 { + // title row + dtable.titleRow1 = make([]genCell, len(line)) + for j, c := range line { + dtable.titleRow1[j].rawValue = c } + continue + } + if i == 1 { + dtable.titleRow2 = make([]genCell, len(line)) + for j, c := range line { + dtable.titleRow2[j].rawValue = c + } + continue + } + // data + rowData := make([]genCell, len(line)) + for j, val := range line { + rowData[j].rawValue = val } + dtable.rows[i-2] = rowData + } + return dtable, nil +} - if conditionEval { - for _, act := range task.DtActions { - tuple := tuples[model.TupleType(act.Tuple)] - if tuple == nil { - continue +// loadFromXLSFile loads decision table from Excel file +func loadFromXLSFile(fileName string) (*dTable, error) { + file, err := excelize.OpenFile(fileName) + if err != nil { + return nil, fmt.Errorf("not able open the file [%s] - %s", fileName, err) + } + rows, err := file.GetRows("DecisionTable") + if err != nil { + return nil, fmt.Errorf("DecisionTable worksheet not available in %s", fileName) + } + // find titleRowIndex + titleRowIndex := 0 + for i, r := range rows { + if len(r) > 0 { + if r[0] == "DecisionTable" { + titleRowIndex = i + 2 + } + } + } + titleRowSize := len(rows[titleRowIndex]) + + dtable := &dTable{ + titleRow1: make([]genCell, titleRowSize), + titleRow2: make([]genCell, titleRowSize), + rows: make([][]genCell, 1), + } + for i, c := range rows[titleRowIndex] { + dtable.titleRow1[i].rawValue = c + } + for i, c := range rows[titleRowIndex+1] { + dtable.titleRow2[i].rawValue = c + } + for _, r := range rows[titleRowIndex+2:] { + if len(r) == 0 { + break + } + dtrow := make([]genCell, titleRowSize) + for i, c := range r { + dtrow[i].rawValue = c + } + dtable.rows = append(dtable.rows, dtrow) + } + + return dtable, nil +} + +func (dtable *dTable) compile() error { + // compute meta row from titleRow1 & titleRow2 + metaRow := make([]metaCell, len(dtable.titleRow1)) + dtable.metaRow = metaRow + // titleRow1 determines column type + for colIndex, cell := range dtable.titleRow1 { + if strings.Contains(cell.rawValue, "Id") { + metaRow[colIndex].colType = ctID + } else if strings.Contains(cell.rawValue, "Condition") { + metaRow[colIndex].colType = ctCondition + } else if strings.Contains(cell.rawValue, "Action") { + metaRow[colIndex].colType = ctAction + } else if strings.Contains(cell.rawValue, "Description") { + metaRow[colIndex].colType = ctDescription + } else if strings.Contains(cell.rawValue, "Priority") { + metaRow[colIndex].colType = ctPriority + } else { + return fmt.Errorf("unknown column type - %s", cell.rawValue) + } + } + // titleRow2 determines tuple type & property + for colIndex, cell := range dtable.titleRow2 { + if cell.rawValue == "" { + continue + } + tokens := strings.Split(cell.rawValue, ".") + if len(tokens) != 2 { + return fmt.Errorf("[%s] is not a valid tuple property representation", cell.rawValue) + } + tupleType := tokens[0] + propName := tokens[1] + tupleDesc := model.GetTupleDescriptor(model.TupleType(tupleType)) + if tupleDesc == nil { + return fmt.Errorf("tuple type[%s] is not recognised", tupleType) + } + propDesc := tupleDesc.GetProperty(propName) + if propDesc == nil { + return fmt.Errorf("property[%s] is not a valid property for the tuple type[%s]", propName, tupleType) + } + metaRow[colIndex].tupleDesc = tupleDesc + metaRow[colIndex].propDesc = propDesc + } + // process all rows + for rowIndex, row := range dtable.rows { + for colIndex, cell := range row { + cell.metaCell = &metaRow[colIndex] + dtable.rows[rowIndex][colIndex].metaCell = &metaRow[colIndex] + if cell.colType == ctCondition { + value := cell.rawValue + if !strings.HasPrefix(value, "==") && !strings.HasPrefix(value, ">") && !strings.HasPrefix(value, "<") && !strings.HasPrefix(value, "!") { + value = "== " + value } - mutableTuple := tuple.(model.MutableTuple) - tds := mutableTuple.GetTupleDescriptor() - strVal := fmt.Sprintf("%v", act.Value) - switch tds.GetProperty(act.Field).PropType { - case data.TypeString: - if strings.Compare(strVal, "") == 0 { - strVal = "" - } - mutableTuple.SetString(context, act.Field, strVal) - case data.TypeBool: - if strings.Compare(strVal, "") == 0 { - strVal = "false" - } - b, err := strconv.ParseBool(strVal) - if err == nil { - mutableTuple.SetBool(context, act.Field, b) - } - case data.TypeInt: - if strings.Compare(strVal, "") == 0 { - strVal = "0" - } - i, err := strconv.ParseInt(strVal, 10, 64) - if err == nil { - mutableTuple.SetInt(context, act.Field, int(i)) - } - case data.TypeInt32: - if strings.Compare(strVal, "") == 0 { - strVal = "0" - } - i, err := strconv.ParseInt(strVal, 10, 64) - if err == nil { - mutableTuple.SetInt(context, act.Field, int(i)) - } - case data.TypeInt64: - if strings.Compare(strVal, "") == 0 { - strVal = "0" - } - i, err := strconv.ParseInt(strVal, 10, 64) - if err == nil { - mutableTuple.SetLong(context, act.Field, i) - } - case data.TypeFloat32: - if strings.Compare(strVal, "") == 0 { - strVal = "0.0" - } - f, err := strconv.ParseFloat(strVal, 32) - if err == nil { - mutableTuple.SetDouble(context, act.Field, f) - } - case data.TypeFloat64: - if strings.Compare(strVal, "") == 0 { - strVal = "0.0" - } - f, err := strconv.ParseFloat(strVal, 64) - if err == nil { - mutableTuple.SetDouble(context, act.Field, f) - } - default: - mutableTuple.SetValue(context, act.Field, act.Value) + expr := "$." + cell.tupleDesc.Name + "." + cell.propDesc.Name + " " + value + dtable.rows[rowIndex][colIndex].cdExpr = expr + } + } + } + return nil +} +func (dtable *dTable) apply(ctx context.Context, tuples map[model.TupleType]model.Tuple) { + // process all rows + for _, row := range dtable.rows { + // process row conditions + rowTruthiness := true + for _, cell := range row { + if cell.colType == ctCondition { + cellTruthiness := evaluateExpression(cell.cdExpr, tuples) + rowTruthiness = rowTruthiness && cellTruthiness + if !rowTruthiness { + break + } + } + } + // process row actions if all row conditions are evaluated to true + if rowTruthiness { + for _, cell := range row { + if cell.colType == ctAction { + updateTuple(ctx, tuples, cell.tupleDesc.Name, cell.propDesc.Name, cell.rawValue) } } } } - return true, nil } -func evaluateCondition(cond *DtCondition, tuples map[model.TupleType]model.Tuple) bool { +// print prints decision table into stdout - TO BE REMOVED +func (dtable *dTable) print() { + // title + for _, v := range dtable.titleRow1 { + fmt.Printf("| %v |", v.rawValue) + } + fmt.Println() + // meta title + for _, v := range dtable.titleRow2 { + fmt.Printf("| %v |", v.rawValue) + } + fmt.Println() + // data + for _, row := range dtable.rows { + for _, rv := range row { + fmt.Printf("| %v--%v |", rv.cdExpr, rv.metaCell) + } + fmt.Println() + } +} - condExprsn := "$." + cond.Tuple + "." + cond.Field + " " + cond.Expr - condExprs := ruleapi.NewExprCondition(condExprsn) - res, err := condExprs.Evaluate("", "", tuples, "") +// evaluateExpression evaluates expr into boolean value in tuples scope +func evaluateExpression(expr string, tuples map[model.TupleType]model.Tuple) bool { + condExpr := ruleapi.NewExprCondition(expr) + result, err := condExpr.Evaluate("", "", tuples, "") if err != nil { return false } + return result +} + +// updateTuple updates tuple's prop with toValue +func updateTuple(context context.Context, tuples map[model.TupleType]model.Tuple, tupleType string, prop string, toVlaue interface{}) { + tuple := tuples[model.TupleType(tupleType)] + if tuple == nil { + return + } + mutableTuple := tuple.(model.MutableTuple) + tds := mutableTuple.GetTupleDescriptor() + strVal := fmt.Sprintf("%v", toVlaue) + switch tds.GetProperty(prop).PropType { + case data.TypeString: + if strings.Compare(strVal, "") == 0 { + strVal = "" + } + mutableTuple.SetString(context, prop, strVal) + case data.TypeBool: + if strings.Compare(strVal, "") == 0 { + strVal = "false" + } + b, err := strconv.ParseBool(strVal) + if err == nil { + mutableTuple.SetBool(context, prop, b) + } + case data.TypeInt: + if strings.Compare(strVal, "") == 0 { + strVal = "0" + } + i, err := strconv.ParseInt(strVal, 10, 64) + if err == nil { + mutableTuple.SetInt(context, prop, int(i)) + } + case data.TypeInt32: + if strings.Compare(strVal, "") == 0 { + strVal = "0" + } + i, err := strconv.ParseInt(strVal, 10, 64) + if err == nil { + mutableTuple.SetInt(context, prop, int(i)) + } + case data.TypeInt64: + if strings.Compare(strVal, "") == 0 { + strVal = "0" + } + i, err := strconv.ParseInt(strVal, 10, 64) + if err == nil { + mutableTuple.SetLong(context, prop, i) + } + case data.TypeFloat32: + if strings.Compare(strVal, "") == 0 { + strVal = "0.0" + } + f, err := strconv.ParseFloat(strVal, 32) + if err == nil { + mutableTuple.SetDouble(context, prop, f) + } + case data.TypeFloat64: + if strings.Compare(strVal, "") == 0 { + strVal = "0.0" + } + f, err := strconv.ParseFloat(strVal, 64) + if err == nil { + mutableTuple.SetDouble(context, prop, f) + } + default: + mutableTuple.SetValue(context, prop, toVlaue) - return res + } } diff --git a/activity/dtable/activity_test.go b/activity/dtable/activity_test.go new file mode 100644 index 0000000..b293a0a --- /dev/null +++ b/activity/dtable/activity_test.go @@ -0,0 +1,179 @@ +package dtable + +import ( + "context" + "testing" + + "github.com/project-flogo/core/data/resolve" + "github.com/project-flogo/core/support/test" + "github.com/project-flogo/rules/common/model" + "github.com/stretchr/testify/assert" + + "github.com/project-flogo/core/data/mapper" +) + +const tupleDescriptor = `[ + { + "name":"applicant", + "properties":[ + { + "name":"name", + "pk-index":0, + "type":"string" + }, + { + "name":"gender", + "type":"string" + }, + { + "name":"age", + "type":"int" + }, + { + "name":"address", + "type":"string" + }, + { + "name":"hasDL", + "type":"bool" + }, + { + "name":"ssn", + "type":"long" + }, + { + "name":"income", + "type":"double" + }, + { + "name":"maritalStatus", + "type":"string" + }, + { + "name":"creditScore", + "type":"int" + }, + { + "name":"status", + "type":"string" + }, + { + "name":"eligible", + "type":"bool" + }, + { + "name":"creditLimit", + "type":"double" + } + ] + }, + { + "name":"processapplication", + "ttl":0, + "properties":[ + { + "name":"ssn", + "pk-index":0, + "type":"long" + }, + { + "name":"start", + "type":"bool" + } + ] + } +]` + +var testApplicants = []map[string]interface{}{ + { + "name": "JohnDoe", + "gender": "Male", + "age": 20, + "address": "BoltonUK", + "hasDL": true, + "ssn": "1231231234", + "income": 45000, + "maritalStatus": "single", + "creditScore": 500, + }, + { + "name": "JaneDoe", + "gender": "Female", + "age": 38, + "address": "BoltonUK", + "hasDL": false, + "ssn": "2424354532", + "income": 32000, + "maritalStatus": "single", + "creditScore": 650, + }, + { + "name": "PrakashY", + "gender": "Male", + "age": 30, + "address": "RedwoodShore", + "hasDL": true, + "ssn": "2345342132", + "income": 150000, + "maritalStatus": "married", + "creditScore": 750, + }, + { + "name": "SandraW", + "gender": "Female", + "age": 26, + "address": "RedwoodShore", + "hasDL": true, + "ssn": "3213214321", + "income": 50000, + "maritalStatus": "single", + "creditScore": 625, + }, +} + +func TestNew(t *testing.T) { + err := model.RegisterTupleDescriptors(string(tupleDescriptor)) + + settings := &Settings{ + DTableFile: "test_dtable.csv", + } + mf := mapper.NewFactory(resolve.GetBasicResolver()) + initCtx := test.NewActivityInitContext(settings, mf) + act, err := New(initCtx) + assert.Nil(t, err) + assert.NotNil(t, act) +} + +func TestEval(t *testing.T) { + err := model.RegisterTupleDescriptors(string(tupleDescriptor)) + + settings := &Settings{ + DTableFile: "test_dtable.xlsx", + } + mf := mapper.NewFactory(resolve.GetBasicResolver()) + initCtx := test.NewActivityInitContext(settings, mf) + act, err := New(initCtx) + assert.Nil(t, err) + assert.NotNil(t, act) + + tuples := make(map[model.TupleType]model.Tuple) + tuple, err := model.NewTuple(model.TupleType("applicant"), testApplicants[0]) + assert.Nil(t, err) + assert.NotNil(t, tuple) + tuples[tuple.GetTupleType()] = tuple + + tc := test.NewActivityContext(act.Metadata()) + tc.SetInput("ctx", context.Background()) + tc.SetInput("tuples", tuples) + act.Eval(tc) + + expectedStatus, err := tuple.GetString("status") + assert.Nil(t, err) + assert.Equal(t, "VISA-Granted", expectedStatus) + expectedEligible, err := tuple.GetBool("eligible") + assert.Nil(t, err) + assert.Equal(t, true, expectedEligible) + expectedCreditLimit, err := tuple.GetDouble("creditLimit") + assert.Nil(t, err) + assert.Equal(t, 2500.0, expectedCreditLimit) +} diff --git a/activity/dtable/metadata.go b/activity/dtable/metadata.go index 62e5b04..fb5c4af 100644 --- a/activity/dtable/metadata.go +++ b/activity/dtable/metadata.go @@ -1,10 +1,7 @@ package dtable import ( - "strings" - "github.com/project-flogo/core/data/coerce" - "github.com/project-flogo/core/data/metadata" ) // Input activity input @@ -27,66 +24,5 @@ func (i *Input) FromMap(values map[string]interface{}) (err error) { // Settings activity settings type Settings struct { - Make []*DecisionTable `md:"make"` -} - -// DecisionTable decision table rows -type DecisionTable struct { - DtConditions []*DtCondition `md:"condition"` - DtActions []*DtAction `md:"action"` -} - -// DtCondition decision row condition -type DtCondition struct { - Tuple string `md:"tuple"` - Field string `md:"field"` - Expr string `md:"expr"` -} - -// DtAction decision row action -type DtAction struct { - Tuple string `md:"tuple"` - Field string `md:"field"` - Value interface{} `md:"value"` -} - -// FromMap fills Input struct from map -func (s *Settings) FromMap(values map[string]interface{}) (err error) { - tasks, err := coerce.ToArray(values["make"]) - if err != nil { - return - } - s.Make = make([]*DecisionTable, len(tasks)) - for i, t := range tasks { - task := &DecisionTable{} - - condArr := make([]*DtCondition, 0) - actArr := make([]*DtAction, 0) - - taskMap := t.(map[string]interface{}) - - for k, v := range taskMap { - if strings.Compare(k, "condition") == 0 { - tempCondArr := v.([]interface{}) - for _, cond := range tempCondArr { - tempMap := cond.(map[string]interface{}) - dtCondtion := &DtCondition{} - err = metadata.MapToStruct(tempMap, dtCondtion, true) - condArr = append(condArr, dtCondtion) - } - } else { - tempActArr := v.([]interface{}) - for _, act := range tempActArr { - tempMap := act.(map[string]interface{}) - dtAction := &DtAction{} - err = metadata.MapToStruct(tempMap, dtAction, true) - actArr = append(actArr, dtAction) - } - } - } - task.DtConditions = condArr - task.DtActions = actArr - s.Make[i] = task - } - return + DTableFile string `md:"dTableFile,required"` // Path to decision table file } diff --git a/activity/dtable/test_dtable.csv b/activity/dtable/test_dtable.csv new file mode 100644 index 0000000..a887aee --- /dev/null +++ b/activity/dtable/test_dtable.csv @@ -0,0 +1,3 @@ +Id,Condition (1),Condition (2),Condition (3),Condition (4),Condition (5),Action (6),Action (7),Action (8),Description,Priority +,applicant.age,applicant.creditScore,applicant.hasDL,applicant.maritalStatus,applicant.income,applicant.eligible,applicant.status,applicant.creditLimit,, +1,>= 16,>= 500,== true,== 'single',> 10000,true,VISA-Granted,2500.0,, \ No newline at end of file diff --git a/activity/dtable/test_dtable.xlsx b/activity/dtable/test_dtable.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..ae8aef92f233bcad640fd939db5e650c7da222e1 GIT binary patch literal 9220 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 V1^de7um~?c*Df}@FASIe{tZ-26*~X` literal 0 HcmV?d00001 From 81e3bc05872acc69a64e2c5ff00b5b454232af0a Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Tue, 29 Oct 2019 16:53:35 -0600 Subject: [PATCH 100/125] Support for keydb --- examples/flogo/dtable/README.md | 7 ++++ examples/flogo/dtable/keydb/keydb_test.go | 39 +++++++++++++++++++++++ ruleapi/tests/common.go | 17 +++------- ruleapi/tests/expr_1_test.go | 2 +- ruleapi/tests/expr_2_test.go | 2 +- ruleapi/tests/expr_3_test.go | 2 +- ruleapi/tests/expr_4_test.go | 2 +- ruleapi/tests/expr_5_test.go | 2 +- ruleapi/tests/identifier_1_test.go | 2 +- ruleapi/tests/identifier_2_test.go | 2 +- ruleapi/tests/main_test.go | 32 +++++++++++++++---- ruleapi/tests/retract_1_test.go | 2 +- ruleapi/tests/rsconfigmp.json | 36 +++++++++++++++++++++ ruleapi/tests/rtctxn_10_test.go | 2 +- ruleapi/tests/rtctxn_11_test.go | 2 +- ruleapi/tests/rtctxn_12_test.go | 2 +- ruleapi/tests/rtctxn_13_test.go | 2 +- ruleapi/tests/rtctxn_14_test.go | 2 +- ruleapi/tests/rtctxn_15_test.go | 2 +- ruleapi/tests/rtctxn_16_test.go | 2 +- ruleapi/tests/rtctxn_1_test.go | 2 +- ruleapi/tests/rtctxn_2_test.go | 2 +- ruleapi/tests/rtctxn_3_test.go | 2 +- ruleapi/tests/rtctxn_4_test.go | 2 +- ruleapi/tests/rtctxn_5_test.go | 2 +- ruleapi/tests/rtctxn_6_test.go | 2 +- ruleapi/tests/rtctxn_7_test.go | 2 +- ruleapi/tests/rtctxn_8_test.go | 2 +- ruleapi/tests/rtctxn_9_test.go | 2 +- ruleapi/tests/rules_1_test.go | 2 +- ruleapi/tests/rules_2_test.go | 4 --- ruleapi/tests/rules_3_test.go | 2 +- ruleapi/tests/sessions_test.go | 4 +-- 33 files changed, 141 insertions(+), 50 deletions(-) create mode 100644 examples/flogo/dtable/keydb/keydb_test.go create mode 100644 ruleapi/tests/rsconfigmp.json diff --git a/examples/flogo/dtable/README.md b/examples/flogo/dtable/README.md index 96b89b9..72c78ef 100644 --- a/examples/flogo/dtable/README.md +++ b/examples/flogo/dtable/README.md @@ -32,6 +32,13 @@ docker run -p 6381:6379 -d redis STORECONFIG=../../rsconfig.json ./dtable ``` +#### With keydb store + +```sh +docker run -p 6381:6379 -d eqalpha/keydb +STORECONFIG=../../rsconfig.json ./dtable +``` + ### Testing #### #1 Invoke student analysis decision table diff --git a/examples/flogo/dtable/keydb/keydb_test.go b/examples/flogo/dtable/keydb/keydb_test.go new file mode 100644 index 0000000..e83a880 --- /dev/null +++ b/examples/flogo/dtable/keydb/keydb_test.go @@ -0,0 +1,39 @@ +package main + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/project-flogo/rules/examples/flogo/dtable/test" + "github.com/project-flogo/rules/ruleapi/tests" + + "github.com/stretchr/testify/assert" +) + +func TestDTableKeyDB(t *testing.T) { + command := exec.Command("docker", "run", "-p", "6381:6379", "-d", "eqalpha/keydb") + hash, err := command.Output() + if err != nil { + assert.Nil(t, err) + } + tests.Pour("6381") + + defer func() { + command := exec.Command("docker", "stop", strings.TrimSpace(string(hash))) + err := command.Run() + if err != nil { + assert.Nil(t, err) + } + command = exec.Command("docker", "rm", strings.TrimSpace(string(hash))) + err = command.Run() + if err != nil { + assert.Nil(t, err) + } + tests.Drain("6381") + }() + os.Setenv("STORECONFIG", filepath.FromSlash("../rsconfig.json")) + test.Test(t) +} diff --git a/ruleapi/tests/common.go b/ruleapi/tests/common.go index ade2e57..3eb14f4 100644 --- a/ruleapi/tests/common.go +++ b/ruleapi/tests/common.go @@ -26,12 +26,13 @@ import ( ) var ( - redis = false - performance = false - done = false + store = "" + image = "" + done = false ) -func createRuleSession() (model.RuleSession, error) { +func createRuleSession(t *testing.T) (model.RuleSession, error) { + t.Logf("createRuleSession with store=%s and docker image=%s", store, image) done = false tupleDescFileAbsPath := common.GetPathForResource("ruleapi/tests/tests.json", "./tests.json") @@ -44,14 +45,6 @@ func createRuleSession() (model.RuleSession, error) { return nil, err } - store := "" - if redis { - if performance { - store = "rsconfigp.json" - } else { - store = "rsconfig.json" - } - } return ruleapi.GetOrCreateRuleSession("test", store) } diff --git a/ruleapi/tests/expr_1_test.go b/ruleapi/tests/expr_1_test.go index 4a86232..646b2e6 100644 --- a/ruleapi/tests/expr_1_test.go +++ b/ruleapi/tests/expr_1_test.go @@ -13,7 +13,7 @@ import ( //1 condition, 1 expression func Test_1_Expr(t *testing.T) { actionCount := map[string]int{"count": 0} - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) r1 := ruleapi.NewRule("r1") err = r1.AddExprCondition("c1", "$.t2.p2 > $.t1.p1", nil) diff --git a/ruleapi/tests/expr_2_test.go b/ruleapi/tests/expr_2_test.go index 0943847..8f44bfb 100644 --- a/ruleapi/tests/expr_2_test.go +++ b/ruleapi/tests/expr_2_test.go @@ -14,7 +14,7 @@ import ( func Test_2_Expr(t *testing.T) { actionCount := map[string]int{"count": 0} - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) r1 := ruleapi.NewRule("r1") err = r1.AddExprCondition("c1", "$.t1.p1 > $.t2.p1", nil) diff --git a/ruleapi/tests/expr_3_test.go b/ruleapi/tests/expr_3_test.go index e342e6d..54b1a74 100644 --- a/ruleapi/tests/expr_3_test.go +++ b/ruleapi/tests/expr_3_test.go @@ -14,7 +14,7 @@ import ( func Test_3_Expr(t *testing.T) { actionCount := map[string]int{"count": 0} - rs, err := createRuleSession() + rs, err := createRuleSession(t) r1 := ruleapi.NewRule("r1") assert.Nil(t, err) err = r1.AddExprCondition("c1", "($.t1.p1 > $.t2.p1) && ($.t1.p2 > $.t2.p2)", nil) diff --git a/ruleapi/tests/expr_4_test.go b/ruleapi/tests/expr_4_test.go index 6f4a606..fb7345a 100644 --- a/ruleapi/tests/expr_4_test.go +++ b/ruleapi/tests/expr_4_test.go @@ -14,7 +14,7 @@ import ( func Test_4_Expr(t *testing.T) { actionCount := map[string]int{"count": 0} - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) r1 := ruleapi.NewRule("r1") err = r1.AddExprCondition("c1", "($.t1.p1 > $.t2.p1) && (($.t1.p2 > $.t2.p2) && ($.t1.p3 == $.t2.p3))", nil) diff --git a/ruleapi/tests/expr_5_test.go b/ruleapi/tests/expr_5_test.go index 43439f1..75547ed 100644 --- a/ruleapi/tests/expr_5_test.go +++ b/ruleapi/tests/expr_5_test.go @@ -14,7 +14,7 @@ import ( func Test_5_Expr(t *testing.T) { actionCount := map[string]int{"count": 0} - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) r1 := ruleapi.NewRule("r1") err = r1.AddExprCondition("c1", "(($.t1.p1 + $.t2.p1) == 5) && (($.t1.p2 > $.t2.p2) && ($.t1.p3 == $.t2.p3))", nil) diff --git a/ruleapi/tests/identifier_1_test.go b/ruleapi/tests/identifier_1_test.go index f39c4f7..4a40022 100644 --- a/ruleapi/tests/identifier_1_test.go +++ b/ruleapi/tests/identifier_1_test.go @@ -16,7 +16,7 @@ var count uint64 //Check if all combination of tuples t1 and t3 are triggering actions func Test_I1(t *testing.T) { count = 0 - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) rule := ruleapi.NewRule("I1") diff --git a/ruleapi/tests/identifier_2_test.go b/ruleapi/tests/identifier_2_test.go index a4aea7d..0d61ce9 100644 --- a/ruleapi/tests/identifier_2_test.go +++ b/ruleapi/tests/identifier_2_test.go @@ -16,7 +16,7 @@ var cnt uint64 //Using 3 Identifiers, different Join conditions and triggering respective actions --->Verify order of actions and count. func Test_I2(t *testing.T) { cnt = 0 - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) //actionMap := make(map[string]string) diff --git a/ruleapi/tests/main_test.go b/ruleapi/tests/main_test.go index 4f1fa27..71b8de5 100644 --- a/ruleapi/tests/main_test.go +++ b/ruleapi/tests/main_test.go @@ -8,13 +8,21 @@ import ( ) func TestMain(m *testing.M) { + image = "none" code := m.Run() if code != 0 { os.Exit(code) } - run := func() int { - command := exec.Command("docker", "run", "-p", "6380:6379", "-d", "redis") + store = "rsconfigmp.json" + code = m.Run() + if code != 0 { + os.Exit(code) + } + + run := func(img string) int { + image = img + command := exec.Command("docker", "run", "-p", "6380:6379", "-d", img) hash, err := command.Output() if err != nil { panic(err) @@ -37,12 +45,24 @@ func TestMain(m *testing.M) { return m.Run() } - redis = true - code = run() + store = "rsconfig.json" + code = run("redis") + if code != 0 { + os.Exit(code) + } + + store = "rsconfigp.json" + code = run("redis") + if code != 0 { + os.Exit(code) + } + + store = "rsconfig.json" + code = run("eqalpha/keydb") if code != 0 { os.Exit(code) } - performance = true - os.Exit(run()) + store = "rsconfigp.json" + os.Exit(run("eqalpha/keydb")) } diff --git a/ruleapi/tests/retract_1_test.go b/ruleapi/tests/retract_1_test.go index ebd6a5c..91c9064 100644 --- a/ruleapi/tests/retract_1_test.go +++ b/ruleapi/tests/retract_1_test.go @@ -13,7 +13,7 @@ import ( //Retract func Test_Retract_1(t *testing.T) { - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) //create a rule joining t1 and t3 diff --git a/ruleapi/tests/rsconfigmp.json b/ruleapi/tests/rsconfigmp.json new file mode 100644 index 0000000..caf347c --- /dev/null +++ b/ruleapi/tests/rsconfigmp.json @@ -0,0 +1,36 @@ +{ + "mode": "performance", + "rs": { + "prefix": "x", + "store-ref": "mem" + }, + "rete": { + "jt-ref": "mem", + "idgen-ref": "mem", + "jt":"mem" + }, + "stores": { + "mem": { + }, + "redis": { + "network": "tcp", + "address": ":6379" + } + }, + "idgens": { + "mem": { + }, + "redis": { + "network": "tcp", + "address": ":6379" + } + }, + "jts": { + "mem": { + }, + "redis": { + "network": "tcp", + "address": ":6379" + } + } +} diff --git a/ruleapi/tests/rtctxn_10_test.go b/ruleapi/tests/rtctxn_10_test.go index 2ea7634..6187281 100644 --- a/ruleapi/tests/rtctxn_10_test.go +++ b/ruleapi/tests/rtctxn_10_test.go @@ -13,7 +13,7 @@ import ( //1 rtc ->Delete multiple tuple types and verify count. func Test_T10(t *testing.T) { - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) rule := ruleapi.NewRule("R10") diff --git a/ruleapi/tests/rtctxn_11_test.go b/ruleapi/tests/rtctxn_11_test.go index edfa9a6..d1b9db9 100644 --- a/ruleapi/tests/rtctxn_11_test.go +++ b/ruleapi/tests/rtctxn_11_test.go @@ -13,7 +13,7 @@ import ( //1 rtc->one assert triggers two rule actions each rule action asserts 2 more tuples.Verify Tuple type and Tuples count. func Test_T11(t *testing.T) { - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) rule := ruleapi.NewRule("R11") diff --git a/ruleapi/tests/rtctxn_12_test.go b/ruleapi/tests/rtctxn_12_test.go index b91d264..b24deb5 100644 --- a/ruleapi/tests/rtctxn_12_test.go +++ b/ruleapi/tests/rtctxn_12_test.go @@ -13,7 +13,7 @@ import ( //1 rtc->one assert triggers two rule actions each rule action modifies tuples.Verify Tuple type and Tuples count. func Test_T12(t *testing.T) { - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) rule := ruleapi.NewRule("R12") diff --git a/ruleapi/tests/rtctxn_13_test.go b/ruleapi/tests/rtctxn_13_test.go index aba581f..9c9a628 100644 --- a/ruleapi/tests/rtctxn_13_test.go +++ b/ruleapi/tests/rtctxn_13_test.go @@ -13,7 +13,7 @@ import ( //1 rtc->one assert triggers two rule actions each rule action deletes tuples.Verify Deleted Tuple types and Tuples count. func Test_T13(t *testing.T) { - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) rule := ruleapi.NewRule("R13") diff --git a/ruleapi/tests/rtctxn_14_test.go b/ruleapi/tests/rtctxn_14_test.go index e1f928d..40ca1aa 100644 --- a/ruleapi/tests/rtctxn_14_test.go +++ b/ruleapi/tests/rtctxn_14_test.go @@ -13,7 +13,7 @@ import ( //1 rtc->Redundant add and modify on same tuple->Verify added and modified count func Test_T14(t *testing.T) { - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) rule := ruleapi.NewRule("R14") diff --git a/ruleapi/tests/rtctxn_15_test.go b/ruleapi/tests/rtctxn_15_test.go index e88d529..d7fe2b5 100644 --- a/ruleapi/tests/rtctxn_15_test.go +++ b/ruleapi/tests/rtctxn_15_test.go @@ -17,7 +17,7 @@ var actionCnt uint64 //1 rtc->Scheduled assert, Action should be fired after the delay time. func Test_T15(t *testing.T) { actionCnt = 0 - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) rule := ruleapi.NewRule("R15") diff --git a/ruleapi/tests/rtctxn_16_test.go b/ruleapi/tests/rtctxn_16_test.go index a7f6dc4..353dc50 100644 --- a/ruleapi/tests/rtctxn_16_test.go +++ b/ruleapi/tests/rtctxn_16_test.go @@ -17,7 +17,7 @@ var actionCnt1 uint64 //1 rtc->Schedule assert, Cancel scheduled assert and action should not be fired func Test_T16(t *testing.T) { actionCnt1 = 0 - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) rule := ruleapi.NewRule("R16") diff --git a/ruleapi/tests/rtctxn_1_test.go b/ruleapi/tests/rtctxn_1_test.go index 9767db6..dc5d6da 100644 --- a/ruleapi/tests/rtctxn_1_test.go +++ b/ruleapi/tests/rtctxn_1_test.go @@ -12,7 +12,7 @@ import ( //TTL != 0 asserted func Test_T1(t *testing.T) { - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) rule := ruleapi.NewRule("R1") err = rule.AddCondition("R1_c1", []string{"t1.none"}, trueCondition, t) diff --git a/ruleapi/tests/rtctxn_2_test.go b/ruleapi/tests/rtctxn_2_test.go index 77951ec..5df2a1e 100644 --- a/ruleapi/tests/rtctxn_2_test.go +++ b/ruleapi/tests/rtctxn_2_test.go @@ -13,7 +13,7 @@ import ( //TTL = 0, asserted func Test_T2(t *testing.T) { - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) rule := ruleapi.NewRule("R2") diff --git a/ruleapi/tests/rtctxn_3_test.go b/ruleapi/tests/rtctxn_3_test.go index 67ef957..d1b563e 100644 --- a/ruleapi/tests/rtctxn_3_test.go +++ b/ruleapi/tests/rtctxn_3_test.go @@ -13,7 +13,7 @@ import ( //New asserted in action (forward chain) func Test_T3(t *testing.T) { - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) rule := ruleapi.NewRule("R3") diff --git a/ruleapi/tests/rtctxn_4_test.go b/ruleapi/tests/rtctxn_4_test.go index c2da52b..18e2588 100644 --- a/ruleapi/tests/rtctxn_4_test.go +++ b/ruleapi/tests/rtctxn_4_test.go @@ -13,7 +13,7 @@ import ( // modified in action (forward chain) func Test_T4(t *testing.T) { - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) rule := ruleapi.NewRule("R4") diff --git a/ruleapi/tests/rtctxn_5_test.go b/ruleapi/tests/rtctxn_5_test.go index 4bb8f8a..70aac69 100644 --- a/ruleapi/tests/rtctxn_5_test.go +++ b/ruleapi/tests/rtctxn_5_test.go @@ -13,7 +13,7 @@ import ( //3 rtcs, 1st rtc ->asserted, 2nd rtc ->modified the 1st one, 3rd rtc ->deleted the 2nd one func Test_T5(t *testing.T) { - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) rule := ruleapi.NewRule("R5") diff --git a/ruleapi/tests/rtctxn_6_test.go b/ruleapi/tests/rtctxn_6_test.go index 90cfe2a..f68d8a4 100644 --- a/ruleapi/tests/rtctxn_6_test.go +++ b/ruleapi/tests/rtctxn_6_test.go @@ -13,7 +13,7 @@ import ( //Same as Test_T5, but in 3rd rtc, assert a TTL=0 based and a TTL=1 based func Test_T6(t *testing.T) { - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) rule := ruleapi.NewRule("R6") diff --git a/ruleapi/tests/rtctxn_7_test.go b/ruleapi/tests/rtctxn_7_test.go index 7dace34..40d001b 100644 --- a/ruleapi/tests/rtctxn_7_test.go +++ b/ruleapi/tests/rtctxn_7_test.go @@ -13,7 +13,7 @@ import ( //add and delete in the same rtc func Test_T7(t *testing.T) { - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) rule := ruleapi.NewRule("R7") diff --git a/ruleapi/tests/rtctxn_8_test.go b/ruleapi/tests/rtctxn_8_test.go index f052d6c..477af3c 100644 --- a/ruleapi/tests/rtctxn_8_test.go +++ b/ruleapi/tests/rtctxn_8_test.go @@ -13,7 +13,7 @@ import ( //no-identifier condition func Test_T8(t *testing.T) { - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) rule := ruleapi.NewRule("R1") diff --git a/ruleapi/tests/rtctxn_9_test.go b/ruleapi/tests/rtctxn_9_test.go index ae49598..a550a5c 100644 --- a/ruleapi/tests/rtctxn_9_test.go +++ b/ruleapi/tests/rtctxn_9_test.go @@ -13,7 +13,7 @@ import ( //2 rtcs, 1st rtc ->Asserted multiple tuple types and verify count, 2nd rtc -> Modified multiple tuple types and verify count. func Test_T9(t *testing.T) { - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) rule := ruleapi.NewRule("R9") diff --git a/ruleapi/tests/rules_1_test.go b/ruleapi/tests/rules_1_test.go index 8a73db9..3006680 100644 --- a/ruleapi/tests/rules_1_test.go +++ b/ruleapi/tests/rules_1_test.go @@ -19,7 +19,7 @@ the expected outcome is that all three rules should fire func Test_One(t *testing.T) { - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) actionMap := make(map[string]string) diff --git a/ruleapi/tests/rules_2_test.go b/ruleapi/tests/rules_2_test.go index ed9d0cd..049ab99 100644 --- a/ruleapi/tests/rules_2_test.go +++ b/ruleapi/tests/rules_2_test.go @@ -29,10 +29,6 @@ func Test_Two(t *testing.T) { } //Create a RuleSession - store := "" - if redis { - store = "rsconfig.json" - } rs, err := ruleapi.GetOrCreateRuleSession("asession", store) assert.Nil(t, err) actionFireCount := make(map[string]int) diff --git a/ruleapi/tests/rules_3_test.go b/ruleapi/tests/rules_3_test.go index c57f58c..83e07e5 100644 --- a/ruleapi/tests/rules_3_test.go +++ b/ruleapi/tests/rules_3_test.go @@ -16,7 +16,7 @@ var actCnt uint64 //Forward chain-Data change in r3action and r32action triggers the r32action. func Test_Three(t *testing.T) { actCnt = 0 - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) actionMap := make(map[string]string) diff --git a/ruleapi/tests/sessions_test.go b/ruleapi/tests/sessions_test.go index ba997b0..9f9f5e2 100644 --- a/ruleapi/tests/sessions_test.go +++ b/ruleapi/tests/sessions_test.go @@ -19,7 +19,7 @@ func TestClearSessions(t *testing.T) { } func TestAssert(t *testing.T) { - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) rule := ruleapi.NewRule("R2") err = rule.AddCondition("R2_c1", []string{"t4.none"}, trueCondition, nil) @@ -46,7 +46,7 @@ func TestAssert(t *testing.T) { } func TestRace(t *testing.T) { - rs, err := createRuleSession() + rs, err := createRuleSession(t) assert.Nil(t, err) defer rs.Unregister() rule := ruleapi.NewRule("R2") From 3117b115f6d3a1fb8815d37aaf7978a5b9c1cae4 Mon Sep 17 00:00:00 2001 From: nthota Date: Fri, 1 Nov 2019 16:10:28 +0530 Subject: [PATCH 101/125] excel file added for creditcard dt example --- examples/flogo/creditcard-dt/README.md | 80 +----------------- .../creditcard-dt/creditcard-dt-file.xlsx | Bin 0 -> 5602 bytes examples/flogo/creditcard-dt/flogo.json | 54 +----------- 3 files changed, 2 insertions(+), 132 deletions(-) create mode 100644 examples/flogo/creditcard-dt/creditcard-dt-file.xlsx diff --git a/examples/flogo/creditcard-dt/README.md b/examples/flogo/creditcard-dt/README.md index ea50ce8..7f18ca6 100644 --- a/examples/flogo/creditcard-dt/README.md +++ b/examples/flogo/creditcard-dt/README.md @@ -44,82 +44,4 @@ You should see following output: 2019-09-24T12:54:08.683+0530 INFO [flogo.rules] - Applicant: PrakashY -- CreditLimit: 7500 -- status: Pending 2019-09-24T12:54:08.696+0530 INFO [flogo.rules] - Applicant: SandraW -- CreditLimit: 0 -- status: Loan-Rejected 2019-09-24T12:54:09.884+0530 INFO [flogo.rules] - Applicant: JaneDoe -- CreditLimit: 25000 -- status: Platinum-Status -``` - -### Writing Decision Table in JSON - -Sample usage can be as below. -```json - { - "name": "ApplicantSimple", - "description": "Simple Applicants approval dt", - "type": "activity", - "ref": "github.com/project-flogo/rules/activity/dtable", - "settings": { - "make": [ - { - "condition": [ - {"tuple": "applicant","field": "name","expr": "== 'JohnDoe'"}, - {"tuple": "applicant","field": "age","expr": ">= 20"}, - {"tuple": "applicant","field": "age","expr": "<= 30"} - ], - "action": [ - - { "tuple": "applicant","field": "creditLimit","value": 2500.0}, - { "tuple": "applicant","field": "eligible","value": true}, - { "tuple": "applicant","field": "status","value": "VISA-Granted"} - ] - }, - { - "condition": [ - {"tuple": "applicant","field": "name","expr": "== 'SandraW'"}, - {"tuple": "applicant","field": "age","expr": ">= 20"}, - {"tuple": "applicant","field": "age","expr": "<= 30"} - ], - "action": [ - - { "tuple": "applicant","field": "creditLimit","value": 0.0}, - { "tuple": "applicant","field": "eligible","value": false}, - { "tuple": "applicant","field": "status","value": "Loan-Rejected"} - ] - }, - { - "condition": [ - {"tuple": "applicant","field": "name","expr": "== 'PrakashY'"}, - {"tuple": "applicant","field": "age","expr": ">= 20"}, - {"tuple": "applicant","field": "age","expr": "<= 30"} - ], - "action": [ - - { "tuple": "applicant","field": "creditLimit","value": 7500.0}, - { "tuple": "applicant","field": "eligible","value": true}, - { "tuple": "applicant","field": "status","value": "Pending"} - ] - }, - { - "condition": [ - {"tuple": "applicant","field": "name","expr": "== 'JaneDoe'"}, - {"tuple": "applicant","field": "age","expr": ">30"} - ], - "action": [ - - { "tuple": "applicant","field": "creditLimit","value": 25000.0}, - { "tuple": "applicant","field": "eligible","value": false}, - { "tuple": "applicant","field": "status","value": "Platinum-Status"} - ] - } - ] - } - } -``` -Same above json can be veiwed as decition table notation in BE as below. - -|S.No | Conditions ||Actions ||| -| :--- | :--- | :--- | :--- | :--- | :--- | -| | applicant.name | applicant.age | applicant.creditLimit | applicant.eligible | applicant.status| -| 1 | JohnDoe | >=20 && <= 30 | 2500.0 | true | VISA-Granted | -| 2 | SandraW | >=20 && <= 30 | 0.0 | false | Loan-Rejected | -| 3 | PrakashY | >=20 && <= 30 | 7500.0 | true | Pending | -| 4 | JaneDoe | >30 | 25000.0 | false | Platinum-Status | - - +``` \ No newline at end of file diff --git a/examples/flogo/creditcard-dt/creditcard-dt-file.xlsx b/examples/flogo/creditcard-dt/creditcard-dt-file.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..89304b16c7dc8d7d15c0d5751f5932056990d2b9 GIT binary patch literal 5602 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 literal 0 HcmV?d00001 diff --git a/examples/flogo/creditcard-dt/flogo.json b/examples/flogo/creditcard-dt/flogo.json index ea7b91c..4cac045 100644 --- a/examples/flogo/creditcard-dt/flogo.json +++ b/examples/flogo/creditcard-dt/flogo.json @@ -104,59 +104,7 @@ "type": "activity", "ref": "github.com/project-flogo/rules/activity/dtable", "settings": { - "make": [ - { - "condition": [ - {"tuple": "applicant","field": "name","expr": "== 'JohnDoe'"}, - {"tuple": "applicant","field": "age","expr": ">= 20"}, - {"tuple": "applicant","field": "age","expr": "<= 30"} - ], - "action": [ - - { "tuple": "applicant","field": "creditLimit","value": 2500.0}, - { "tuple": "applicant","field": "eligible","value": true}, - { "tuple": "applicant","field": "status","value": "VISA-Granted"} - ] - }, - { - "condition": [ - {"tuple": "applicant","field": "name","expr": "== 'SandraW'"}, - {"tuple": "applicant","field": "age","expr": ">= 20"}, - {"tuple": "applicant","field": "age","expr": "<= 30"} - ], - "action": [ - - { "tuple": "applicant","field": "creditLimit","value": 0.0}, - { "tuple": "applicant","field": "eligible","value": false}, - { "tuple": "applicant","field": "status","value": "Loan-Rejected"} - ] - }, - { - "condition": [ - {"tuple": "applicant","field": "name","expr": "== 'PrakashY'"}, - {"tuple": "applicant","field": "age","expr": ">= 20"}, - {"tuple": "applicant","field": "age","expr": "<= 30"} - ], - "action": [ - - { "tuple": "applicant","field": "creditLimit","value": 7500.0}, - { "tuple": "applicant","field": "eligible","value": true}, - { "tuple": "applicant","field": "status","value": "Pending"} - ] - }, - { - "condition": [ - {"tuple": "applicant","field": "name","expr": "== 'JaneDoe'"}, - {"tuple": "applicant","field": "age","expr": ">30"} - ], - "action": [ - - { "tuple": "applicant","field": "creditLimit","value": 25000.0}, - { "tuple": "applicant","field": "eligible","value": false}, - { "tuple": "applicant","field": "status","value": "Platinum-Status"} - ] - } - ] + "dTableFile":"creditcard-dt-file.xlsx" } }, { From 409dabbdb07d5175545d07cda1737142d61e6ec4 Mon Sep 17 00:00:00 2001 From: Ramesh Polishetti Date: Fri, 1 Nov 2019 16:39:36 +0530 Subject: [PATCH 102/125] Optimize the memory used by the decision table --- activity/dtable/activity.go | 67 +++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/activity/dtable/activity.go b/activity/dtable/activity.go index 25342ef..6119af5 100644 --- a/activity/dtable/activity.go +++ b/activity/dtable/activity.go @@ -40,7 +40,7 @@ type dTable struct { titleRow1 []genCell titleRow2 []genCell metaRow []metaCell - rows [][]genCell + rows [][]*genCell } type metaCell struct { @@ -69,11 +69,12 @@ func New(ctx activity.InitContext) (activity.Activity, error) { if err != nil { return nil, err } + // dtable.print() err = dtable.compile() if err != nil { return nil, err } - dtable.print() + // dtable.print() // Read setting from init context act := &Activity{ @@ -129,29 +130,32 @@ func loadFromCSVFile(fileName string) (*dTable, error) { } dtable := &dTable{} - dtable.rows = make([][]genCell, len(lines)-2) + dtable.rows = make([][]*genCell, len(lines)-2) for i, line := range lines { if i == 0 { - // title row + // title row 1 dtable.titleRow1 = make([]genCell, len(line)) - for j, c := range line { - dtable.titleRow1[j].rawValue = c + for j, val := range line { + dtable.titleRow1[j].rawValue = val } continue } if i == 1 { + // title row 2 dtable.titleRow2 = make([]genCell, len(line)) - for j, c := range line { - dtable.titleRow2[j].rawValue = c + for j, val := range line { + dtable.titleRow2[j].rawValue = val } continue } - // data - rowData := make([]genCell, len(line)) + // other rows + row := make([]*genCell, len(line)) for j, val := range line { - rowData[j].rawValue = val + row[j] = &genCell{ + rawValue: val, + } } - dtable.rows[i-2] = rowData + dtable.rows[i-2] = row } return dtable, nil } @@ -180,21 +184,26 @@ func loadFromXLSFile(fileName string) (*dTable, error) { dtable := &dTable{ titleRow1: make([]genCell, titleRowSize), titleRow2: make([]genCell, titleRowSize), - rows: make([][]genCell, 1), + rows: make([][]*genCell, 1), } - for i, c := range rows[titleRowIndex] { - dtable.titleRow1[i].rawValue = c + // title row 1 + for i, val := range rows[titleRowIndex] { + dtable.titleRow1[i].rawValue = val } - for i, c := range rows[titleRowIndex+1] { - dtable.titleRow2[i].rawValue = c + // title row 2 + for i, val := range rows[titleRowIndex+1] { + dtable.titleRow2[i].rawValue = val } + // other rows for _, r := range rows[titleRowIndex+2:] { if len(r) == 0 { break } - dtrow := make([]genCell, titleRowSize) - for i, c := range r { - dtrow[i].rawValue = c + dtrow := make([]*genCell, titleRowSize) + for i, cell := range r { + dtrow[i] = &genCell{ + rawValue: cell, + } } dtable.rows = append(dtable.rows, dtrow) } @@ -245,17 +254,18 @@ func (dtable *dTable) compile() error { metaRow[colIndex].propDesc = propDesc } // process all rows - for rowIndex, row := range dtable.rows { + for _, row := range dtable.rows { for colIndex, cell := range row { + if cell == nil { + continue + } cell.metaCell = &metaRow[colIndex] - dtable.rows[rowIndex][colIndex].metaCell = &metaRow[colIndex] if cell.colType == ctCondition { value := cell.rawValue if !strings.HasPrefix(value, "==") && !strings.HasPrefix(value, ">") && !strings.HasPrefix(value, "<") && !strings.HasPrefix(value, "!") { value = "== " + value } - expr := "$." + cell.tupleDesc.Name + "." + cell.propDesc.Name + " " + value - dtable.rows[rowIndex][colIndex].cdExpr = expr + cell.cdExpr = "$." + cell.tupleDesc.Name + "." + cell.propDesc.Name + " " + value } } } @@ -268,6 +278,9 @@ func (dtable *dTable) apply(ctx context.Context, tuples map[model.TupleType]mode // process row conditions rowTruthiness := true for _, cell := range row { + if cell == nil { + continue + } if cell.colType == ctCondition { cellTruthiness := evaluateExpression(cell.cdExpr, tuples) rowTruthiness = rowTruthiness && cellTruthiness @@ -279,6 +292,9 @@ func (dtable *dTable) apply(ctx context.Context, tuples map[model.TupleType]mode // process row actions if all row conditions are evaluated to true if rowTruthiness { for _, cell := range row { + if cell == nil { + continue + } if cell.colType == ctAction { updateTuple(ctx, tuples, cell.tupleDesc.Name, cell.propDesc.Name, cell.rawValue) } @@ -302,7 +318,8 @@ func (dtable *dTable) print() { // data for _, row := range dtable.rows { for _, rv := range row { - fmt.Printf("| %v--%v |", rv.cdExpr, rv.metaCell) + // fmt.Printf("| %v--%v |", rv.cdExpr, rv.metaCell) + fmt.Print(rv) } fmt.Println() } From d01c81624bce18d6e13c9a1df756745e58744455 Mon Sep 17 00:00:00 2001 From: Ramesh Polishetti Date: Sun, 3 Nov 2019 15:31:30 +0530 Subject: [PATCH 103/125] Add support for multiple condition in a decision table cell --- activity/dtable/activity.go | 58 +++++++++++++++++++++++++++++--- activity/dtable/activity_test.go | 45 +++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 5 deletions(-) diff --git a/activity/dtable/activity.go b/activity/dtable/activity.go index 6119af5..ac77a5c 100644 --- a/activity/dtable/activity.go +++ b/activity/dtable/activity.go @@ -55,6 +55,58 @@ type genCell struct { cdExpr string } +func (cell *genCell) compileExpr() { + lhsToken := fmt.Sprintf("$.%s.%s", cell.tupleDesc.Name, cell.propDesc.Name) + tokens := strings.Split(cell.rawValue, "&&") + + andConditions := make([]string, 0) + for i, v := range tokens { + if i == 0 { + andConditions = append(andConditions, v) + continue + } + andConditions = append(andConditions, "&&", v) + } + + conditions := make([]string, 0) + for _, v := range andConditions { + tokens = strings.Split(v, "||") + if len(tokens) > 1 { + for j, v1 := range tokens { + if j == 0 { + conditions = append(conditions, v1) + continue + } + conditions = append(conditions, "||", v1) + } + continue + } + conditions = append(conditions, v) + } + + expr := "" + for _, c := range conditions { + if c == "&&" || c == "||" { + expr = fmt.Sprintf("%s %s ", expr, c) + continue + } + + if !strings.HasPrefix(c, "==") && + !strings.HasPrefix(c, "!=") && + !strings.HasPrefix(c, ">") && + !strings.HasPrefix(c, ">=") && + !strings.HasPrefix(c, "<") && + !strings.HasPrefix(c, "<=") && + !strings.HasPrefix(c, "!") { + c = "==" + c + } + fullCondition := lhsToken + c + expr = expr + fullCondition + } + + cell.cdExpr = expr +} + // New creates new decision table activity func New(ctx activity.InitContext) (activity.Activity, error) { // Read settings @@ -261,11 +313,7 @@ func (dtable *dTable) compile() error { } cell.metaCell = &metaRow[colIndex] if cell.colType == ctCondition { - value := cell.rawValue - if !strings.HasPrefix(value, "==") && !strings.HasPrefix(value, ">") && !strings.HasPrefix(value, "<") && !strings.HasPrefix(value, "!") { - value = "== " + value - } - cell.cdExpr = "$." + cell.tupleDesc.Name + "." + cell.propDesc.Name + " " + value + cell.compileExpr() } } } diff --git a/activity/dtable/activity_test.go b/activity/dtable/activity_test.go index b293a0a..e713916 100644 --- a/activity/dtable/activity_test.go +++ b/activity/dtable/activity_test.go @@ -131,6 +131,51 @@ var testApplicants = []map[string]interface{}{ }, } +func TestCellCompileExpr(t *testing.T) { + err := model.RegisterTupleDescriptors(string(tupleDescriptor)) + assert.Nil(t, err) + + // test cases input + tupleType := "applicant" + propName := "name" + testcases := make(map[string]string) + testcases["foo"] = "$.applicant.name==foo" + testcases["==foo"] = "$.applicant.name==foo" + testcases["!=foo"] = "$.applicant.name!=foo" + testcases[">foo"] = "$.applicant.name>foo" + testcases[">=foo"] = "$.applicant.name>=foo" + testcases["=foo&&bar"] = "$.applicant.name>=foo && $.applicant.name==bar" + testcases["car&&jeep&&bus"] = "$.applicant.name==car && $.applicant.name==jeep && $.applicant.name==bus" + testcases["foo||bar"] = "$.applicant.name==foo || $.applicant.name==bar" + testcases["car||jeep||bus"] = "$.applicant.name==car || $.applicant.name==jeep || $.applicant.name==bus" + testcases["car&&jeep||bus"] = "$.applicant.name==car && $.applicant.name==jeep || $.applicant.name==bus" + testcases["car||jeep&&bus"] = "$.applicant.name==car || $.applicant.name==jeep && $.applicant.name==bus" + + // prepare cell + tupleDesc := model.GetTupleDescriptor(model.TupleType(tupleType)) + assert.NotNil(t, tupleDesc) + propDesc := tupleDesc.GetProperty(propName) + assert.NotNil(t, propDesc) + cell := &genCell{ + metaCell: &metaCell{ + colType: ctCondition, + tupleDesc: tupleDesc, + propDesc: propDesc, + }, + } + + // run test cases + for k, v := range testcases { + cell.rawValue = k + cell.compileExpr() + assert.Equal(t, v, cell.cdExpr) + } +} + func TestNew(t *testing.T) { err := model.RegisterTupleDescriptors(string(tupleDescriptor)) From 710c39f4594f387c5eb8271041e9f01fe22af5dd Mon Sep 17 00:00:00 2001 From: Ramesh Polishetti Date: Thu, 7 Nov 2019 14:45:21 +0530 Subject: [PATCH 104/125] Adding README for decision table activity --- activity/dtable/README.md | 44 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 activity/dtable/README.md diff --git a/activity/dtable/README.md b/activity/dtable/README.md new file mode 100644 index 0000000..54c684f --- /dev/null +++ b/activity/dtable/README.md @@ -0,0 +1,44 @@ +# Decision Table +This is a `flogo activity` based `Decision Table` implementation used inside a rules application. It can be invoked as rule action service. `Decision Table` provide a tabular way to build complex business rules. Each column can be created based on predefined properties. These properties are defined in the `tuple descriptor` used inside a rule application. Each row can be thought of as one rule in a table made up of many rules. The individual rules are often straightforward, as in the following example: +Rule conditions: +``` +person.age > 30 +person.gender == "male" +``` +Rule actions: +``` +application.status = "ACCEPTED" +application.credit = 4000 +``` +A decision table can consist of hundreds, even thousands of rules each of which is executed only when its specific conditions are satisfied. + +## Usage + +The available activity `settings` are as follows: +| Name | Type | Description | +|:----------|:---------:|:-------------:| +|dTableFile | string | Decision table file path (xlsx & csv extensions are supported)| + +A sample `decision table` definition is: +```json + { + "name": "ApplicantSimple", + "description": "Simple Applicants approval dt", + "type": "activity", + "ref": "github.com/project-flogo/rules/activity/dtable", + "settings": { + "dTableFile":"creditcard-dt-file.xlsx" + } + } + +``` + +An example rule action service that invokes the above `ApplicantSimple` decision table is: +```json + "actionService": { + "service": "ApplicantSimple", + "input": { + "message": "test ApplicantSimple" + } + } +``` \ No newline at end of file From cb712031701807b56f5ef3d55bcb0d257900db8a Mon Sep 17 00:00:00 2001 From: Ramesh Polishetti Date: Thu, 7 Nov 2019 14:51:29 +0530 Subject: [PATCH 105/125] Fix README format issue --- activity/dtable/README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/activity/dtable/README.md b/activity/dtable/README.md index 54c684f..6ffda60 100644 --- a/activity/dtable/README.md +++ b/activity/dtable/README.md @@ -1,5 +1,6 @@ # Decision Table -This is a `flogo activity` based `Decision Table` implementation used inside a rules application. It can be invoked as rule action service. `Decision Table` provide a tabular way to build complex business rules. Each column can be created based on predefined properties. These properties are defined in the `tuple descriptor` used inside a rule application. Each row can be thought of as one rule in a table made up of many rules. The individual rules are often straightforward, as in the following example: +This is a `flogo activity` based `Decision Table` implementation used inside a rules application. It can be invoked as rule action service. `Decision Table` provide a tabular way to build complex business rules. Each column can be created based on predefined properties. These properties are defined in the `tuple descriptor` used inside a rule application. Each row can be thought of as one rule in a table made up of many rules. The individual rules are often straightforward, as in the following example. + Rule conditions: ``` person.age > 30 @@ -15,9 +16,10 @@ A decision table can consist of hundreds, even thousands of rules each of which ## Usage The available activity `settings` are as follows: -| Name | Type | Description | -|:----------|:---------:|:-------------:| -|dTableFile | string | Decision table file path (xlsx & csv extensions are supported)| + +| Name | Type | Description | +|:-----------|:--------|:--------------| +| dTableFile | string | Decision table file path (xlsx & csv extensions are supported) | A sample `decision table` definition is: ```json From 60b843a6214759710d6fc78bca23a18ed4dd105e Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Tue, 3 Dec 2019 12:11:12 -0700 Subject: [PATCH 106/125] Added formal expression parser --- activity/dtable/activity.go | 60 +- activity/dtable/activity_test.go | 31 +- activity/dtable/expression.go | 121 ++++ activity/dtable/expression.peg | 35 + activity/dtable/expression.peg.go | 1001 +++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 5 + 7 files changed, 1191 insertions(+), 63 deletions(-) create mode 100644 activity/dtable/expression.go create mode 100644 activity/dtable/expression.peg create mode 100644 activity/dtable/expression.peg.go diff --git a/activity/dtable/activity.go b/activity/dtable/activity.go index ac77a5c..87a543d 100644 --- a/activity/dtable/activity.go +++ b/activity/dtable/activity.go @@ -56,55 +56,19 @@ type genCell struct { } func (cell *genCell) compileExpr() { - lhsToken := fmt.Sprintf("$.%s.%s", cell.tupleDesc.Name, cell.propDesc.Name) - tokens := strings.Split(cell.rawValue, "&&") - - andConditions := make([]string, 0) - for i, v := range tokens { - if i == 0 { - andConditions = append(andConditions, v) - continue - } - andConditions = append(andConditions, "&&", v) - } - - conditions := make([]string, 0) - for _, v := range andConditions { - tokens = strings.Split(v, "||") - if len(tokens) > 1 { - for j, v1 := range tokens { - if j == 0 { - conditions = append(conditions, v1) - continue - } - conditions = append(conditions, "||", v1) - } - continue - } - conditions = append(conditions, v) + rawValue := cell.rawValue + if len(rawValue) == 0 { + return } - - expr := "" - for _, c := range conditions { - if c == "&&" || c == "||" { - expr = fmt.Sprintf("%s %s ", expr, c) - continue - } - - if !strings.HasPrefix(c, "==") && - !strings.HasPrefix(c, "!=") && - !strings.HasPrefix(c, ">") && - !strings.HasPrefix(c, ">=") && - !strings.HasPrefix(c, "<") && - !strings.HasPrefix(c, "<=") && - !strings.HasPrefix(c, "!") { - c = "==" + c - } - fullCondition := lhsToken + c - expr = expr + fullCondition + lhsToken := fmt.Sprintf("$.%s.%s", cell.tupleDesc.Name, cell.propDesc.Name) + expression := &Expr{Buffer: cell.rawValue} + expression.Init() + expression.Expression.Init(cell.rawValue) + if err := expression.Parse(); err != nil { + panic(err) } - - cell.cdExpr = expr + expression.Execute() + cell.cdExpr = expression.Evaluate(lhsToken) } // New creates new decision table activity @@ -218,7 +182,7 @@ func loadFromXLSFile(fileName string) (*dTable, error) { if err != nil { return nil, fmt.Errorf("not able open the file [%s] - %s", fileName, err) } - rows, err := file.GetRows("DecisionTable") + rows := file.GetRows("DecisionTable") if err != nil { return nil, fmt.Errorf("DecisionTable worksheet not available in %s", fileName) } diff --git a/activity/dtable/activity_test.go b/activity/dtable/activity_test.go index e713916..d3fff88 100644 --- a/activity/dtable/activity_test.go +++ b/activity/dtable/activity_test.go @@ -139,21 +139,21 @@ func TestCellCompileExpr(t *testing.T) { tupleType := "applicant" propName := "name" testcases := make(map[string]string) - testcases["foo"] = "$.applicant.name==foo" - testcases["==foo"] = "$.applicant.name==foo" - testcases["!=foo"] = "$.applicant.name!=foo" - testcases[">foo"] = "$.applicant.name>foo" - testcases[">=foo"] = "$.applicant.name>=foo" - testcases["=foo&&bar"] = "$.applicant.name>=foo && $.applicant.name==bar" - testcases["car&&jeep&&bus"] = "$.applicant.name==car && $.applicant.name==jeep && $.applicant.name==bus" - testcases["foo||bar"] = "$.applicant.name==foo || $.applicant.name==bar" - testcases["car||jeep||bus"] = "$.applicant.name==car || $.applicant.name==jeep || $.applicant.name==bus" - testcases["car&&jeep||bus"] = "$.applicant.name==car && $.applicant.name==jeep || $.applicant.name==bus" - testcases["car||jeep&&bus"] = "$.applicant.name==car || $.applicant.name==jeep && $.applicant.name==bus" + testcases["foo"] = "$.applicant.name == foo" + testcases["==foo"] = "$.applicant.name == foo" + testcases["!=foo"] = "$.applicant.name != foo" + testcases[">foo"] = "$.applicant.name > foo" + testcases[">=foo"] = "$.applicant.name >= foo" + testcases["=foo&&bar"] = "($.applicant.name >= foo && $.applicant.name == bar)" + testcases["car&&jeep&&bus"] = "(($.applicant.name == car && $.applicant.name == jeep) && $.applicant.name == bus)" + testcases["foo||bar"] = "($.applicant.name == foo || $.applicant.name == bar)" + testcases["car||jeep||bus"] = "(($.applicant.name == car || $.applicant.name == jeep) || $.applicant.name == bus)" + testcases["car&&jeep||bus"] = "(($.applicant.name == car && $.applicant.name == jeep) || $.applicant.name == bus)" + testcases["car||jeep&&bus"] = "($.applicant.name == car || ($.applicant.name == jeep && $.applicant.name == bus))" // prepare cell tupleDesc := model.GetTupleDescriptor(model.TupleType(tupleType)) @@ -170,6 +170,7 @@ func TestCellCompileExpr(t *testing.T) { // run test cases for k, v := range testcases { + t.Log(k, v) cell.rawValue = k cell.compileExpr() assert.Equal(t, v, cell.cdExpr) diff --git a/activity/dtable/expression.go b/activity/dtable/expression.go new file mode 100644 index 0000000..ce07032 --- /dev/null +++ b/activity/dtable/expression.go @@ -0,0 +1,121 @@ +package dtable + +// Type is a type of byte code +type Type uint8 + +const ( + // TypeLiteral a literal type + TypeLiteral Type = iota + // TypeOr a logical or operation + TypeOr + // TypeAnd a logical and operation + TypeAnd + // TypeEq == operator + TypeEq + // TypeNEq != operator + TypeNEq + // TypeGt > operator + TypeGt + // TypeGte >= operator + TypeGte + // TypeLt < operator + TypeLt + // TypeLte <= operator + TypeLte +) + +// ByteCode is a expression byte code +type ByteCode struct { + T Type + Literal string +} + +// String prints out the string representation of the code +func (code *ByteCode) String() string { + switch code.T { + case TypeLiteral: + return code.Literal + case TypeOr: + return "||" + case TypeAnd: + return "&&" + case TypeEq: + return "==" + case TypeNEq: + return "!=" + case TypeGt: + return ">" + case TypeGte: + return ">=" + case TypeLt: + return "<" + case TypeLte: + return "<=" + } + return "" +} + +// Expression is a decision table expression +type Expression struct { + Code []ByteCode + Top int +} + +// Init initializes the expression +func (e *Expression) Init(expression string) { + e.Code = make([]ByteCode, len(expression)) +} + +// AddOperator adds an operator to the stack +func (e *Expression) AddOperator(operator Type) { + code, top := e.Code, e.Top + e.Top++ + code[top].T = operator +} + +// AddLiteral adds a literal to the stack +func (e *Expression) AddLiteral(literal string) { + code, top := e.Code, e.Top + e.Top++ + code[top].T = TypeLiteral + code[top].Literal = literal +} + +// Evaluate evaluates the expression +func (e *Expression) Evaluate(lhsToken string) string { + stack, top := make([]string, len(e.Code)), 0 + for _, code := range e.Code[0:e.Top] { + switch code.T { + case TypeLiteral: + stack[top] = code.Literal + top++ + case TypeOr: + a, b := &stack[top-2], &stack[top-1] + top-- + *a = "(" + *a + " || " + *b + ")" + case TypeAnd: + a, b := &stack[top-2], &stack[top-1] + top-- + *a = "(" + *a + " && " + *b + ")" + case TypeEq: + a := &stack[top-1] + *a = lhsToken + " == " + *a + case TypeNEq: + a := &stack[top-1] + *a = lhsToken + " != " + *a + case TypeGt: + a := &stack[top-1] + *a = lhsToken + " > " + *a + case TypeGte: + a := &stack[top-1] + *a = lhsToken + " >= " + *a + case TypeLt: + a := &stack[top-1] + *a = lhsToken + " < " + *a + case TypeLte: + a := &stack[top-1] + *a = lhsToken + " <= " + *a + } + } + return stack[0] +} diff --git a/activity/dtable/expression.peg b/activity/dtable/expression.peg new file mode 100644 index 0000000..1ebdc38 --- /dev/null +++ b/activity/dtable/expression.peg @@ -0,0 +1,35 @@ +# Copyright 2010 The Go Authors. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +package dtable + +type Expr Peg { + Expression +} + +e <- sp e1 !. +e1 <- e2 ( or e2 { p.AddOperator(TypeOr) } )* +e2 <- e3 ( and e3 { p.AddOperator(TypeAnd) } )* +e3 <- eq e4 { p.AddOperator(TypeEq) } + / neq e4 { p.AddOperator(TypeNEq) } + / gte e4 { p.AddOperator(TypeGte) } + / gt e4 { p.AddOperator(TypeGt) } + / lte e4 { p.AddOperator(TypeLte) } + / lt e4 { p.AddOperator(TypeLt) } + / open e1 close + / e4 { p.AddOperator(TypeEq) } +e4 <- < name > sp { p.AddLiteral(buffer[begin:end]) } +name <- [a-zA-Z_0-9'"]* + +and <- '&&' sp +or <- '||' sp +eq <- '==' sp +neq <- '!=' sp +gt <- '>' sp +gte <- '>=' sp +lt <- '<' sp +lte <- '<=' sp +open <- '(' sp +close <- ')' sp +sp <- ( ' ' / '\t' )* diff --git a/activity/dtable/expression.peg.go b/activity/dtable/expression.peg.go new file mode 100644 index 0000000..57e785a --- /dev/null +++ b/activity/dtable/expression.peg.go @@ -0,0 +1,1001 @@ +package dtable + +// Code generated by peg expression.peg DO NOT EDIT. + +import ( + "fmt" + "io" + "os" + "sort" + "strconv" +) + +const endSymbol rune = 1114112 + +/* The rule types inferred from the grammar are below. */ +type pegRule uint8 + +const ( + ruleUnknown pegRule = iota + rulee + rulee1 + rulee2 + rulee3 + rulee4 + rulename + ruleand + ruleor + ruleeq + ruleneq + rulegt + rulegte + rulelt + rulelte + ruleopen + ruleclose + rulesp + ruleAction0 + ruleAction1 + ruleAction2 + ruleAction3 + ruleAction4 + ruleAction5 + ruleAction6 + ruleAction7 + ruleAction8 + rulePegText + ruleAction9 +) + +var rul3s = [...]string{ + "Unknown", + "e", + "e1", + "e2", + "e3", + "e4", + "name", + "and", + "or", + "eq", + "neq", + "gt", + "gte", + "lt", + "lte", + "open", + "close", + "sp", + "Action0", + "Action1", + "Action2", + "Action3", + "Action4", + "Action5", + "Action6", + "Action7", + "Action8", + "PegText", + "Action9", +} + +type token32 struct { + pegRule + begin, end uint32 +} + +func (t *token32) String() string { + return fmt.Sprintf("\x1B[34m%v\x1B[m %v %v", rul3s[t.pegRule], t.begin, t.end) +} + +type node32 struct { + token32 + up, next *node32 +} + +func (node *node32) print(w io.Writer, pretty bool, buffer string) { + var print func(node *node32, depth int) + print = func(node *node32, depth int) { + for node != nil { + for c := 0; c < depth; c++ { + fmt.Fprintf(w, " ") + } + rule := rul3s[node.pegRule] + quote := strconv.Quote(string(([]rune(buffer)[node.begin:node.end]))) + if !pretty { + fmt.Fprintf(w, "%v %v\n", rule, quote) + } else { + fmt.Fprintf(w, "\x1B[34m%v\x1B[m %v\n", rule, quote) + } + if node.up != nil { + print(node.up, depth+1) + } + node = node.next + } + } + print(node, 0) +} + +func (node *node32) Print(w io.Writer, buffer string) { + node.print(w, false, buffer) +} + +func (node *node32) PrettyPrint(w io.Writer, buffer string) { + node.print(w, true, buffer) +} + +type tokens32 struct { + tree []token32 +} + +func (t *tokens32) Trim(length uint32) { + t.tree = t.tree[:length] +} + +func (t *tokens32) Print() { + for _, token := range t.tree { + fmt.Println(token.String()) + } +} + +func (t *tokens32) AST() *node32 { + type element struct { + node *node32 + down *element + } + tokens := t.Tokens() + var stack *element + for _, token := range tokens { + if token.begin == token.end { + continue + } + node := &node32{token32: token} + for stack != nil && stack.node.begin >= token.begin && stack.node.end <= token.end { + stack.node.next = node.up + node.up = stack.node + stack = stack.down + } + stack = &element{node: node, down: stack} + } + if stack != nil { + return stack.node + } + return nil +} + +func (t *tokens32) PrintSyntaxTree(buffer string) { + t.AST().Print(os.Stdout, buffer) +} + +func (t *tokens32) WriteSyntaxTree(w io.Writer, buffer string) { + t.AST().Print(w, buffer) +} + +func (t *tokens32) PrettyPrintSyntaxTree(buffer string) { + t.AST().PrettyPrint(os.Stdout, buffer) +} + +func (t *tokens32) Add(rule pegRule, begin, end, index uint32) { + tree, i := t.tree, int(index) + if i >= len(tree) { + t.tree = append(tree, token32{pegRule: rule, begin: begin, end: end}) + return + } + tree[i] = token32{pegRule: rule, begin: begin, end: end} +} + +func (t *tokens32) Tokens() []token32 { + return t.tree +} + +type Expr struct { + Expression + + Buffer string + buffer []rune + rules [29]func() bool + parse func(rule ...int) error + reset func() + Pretty bool + tokens32 +} + +func (p *Expr) Parse(rule ...int) error { + return p.parse(rule...) +} + +func (p *Expr) Reset() { + p.reset() +} + +type textPosition struct { + line, symbol int +} + +type textPositionMap map[int]textPosition + +func translatePositions(buffer []rune, positions []int) textPositionMap { + length, translations, j, line, symbol := len(positions), make(textPositionMap, len(positions)), 0, 1, 0 + sort.Ints(positions) + +search: + for i, c := range buffer { + if c == '\n' { + line, symbol = line+1, 0 + } else { + symbol++ + } + if i == positions[j] { + translations[positions[j]] = textPosition{line, symbol} + for j++; j < length; j++ { + if i != positions[j] { + continue search + } + } + break search + } + } + + return translations +} + +type parseError struct { + p *Expr + max token32 +} + +func (e *parseError) Error() string { + tokens, err := []token32{e.max}, "\n" + positions, p := make([]int, 2*len(tokens)), 0 + for _, token := range tokens { + positions[p], p = int(token.begin), p+1 + positions[p], p = int(token.end), p+1 + } + translations := translatePositions(e.p.buffer, positions) + format := "parse error near %v (line %v symbol %v - line %v symbol %v):\n%v\n" + if e.p.Pretty { + format = "parse error near \x1B[34m%v\x1B[m (line %v symbol %v - line %v symbol %v):\n%v\n" + } + for _, token := range tokens { + begin, end := int(token.begin), int(token.end) + err += fmt.Sprintf(format, + rul3s[token.pegRule], + translations[begin].line, translations[begin].symbol, + translations[end].line, translations[end].symbol, + strconv.Quote(string(e.p.buffer[begin:end]))) + } + + return err +} + +func (p *Expr) PrintSyntaxTree() { + if p.Pretty { + p.tokens32.PrettyPrintSyntaxTree(p.Buffer) + } else { + p.tokens32.PrintSyntaxTree(p.Buffer) + } +} + +func (p *Expr) WriteSyntaxTree(w io.Writer) { + p.tokens32.WriteSyntaxTree(w, p.Buffer) +} + +func (p *Expr) Execute() { + buffer, _buffer, text, begin, end := p.Buffer, p.buffer, "", 0, 0 + for _, token := range p.Tokens() { + switch token.pegRule { + + case rulePegText: + begin, end = int(token.begin), int(token.end) + text = string(_buffer[begin:end]) + + case ruleAction0: + p.AddOperator(TypeOr) + case ruleAction1: + p.AddOperator(TypeAnd) + case ruleAction2: + p.AddOperator(TypeEq) + case ruleAction3: + p.AddOperator(TypeNEq) + case ruleAction4: + p.AddOperator(TypeGte) + case ruleAction5: + p.AddOperator(TypeGt) + case ruleAction6: + p.AddOperator(TypeLte) + case ruleAction7: + p.AddOperator(TypeLt) + case ruleAction8: + p.AddOperator(TypeEq) + case ruleAction9: + p.AddLiteral(buffer[begin:end]) + + } + } + _, _, _, _, _ = buffer, _buffer, text, begin, end +} + +func Pretty(pretty bool) func(*Expr) error { + return func(p *Expr) error { + p.Pretty = pretty + return nil + } +} + +func Size(size int) func(*Expr) error { + return func(p *Expr) error { + p.tokens32 = tokens32{tree: make([]token32, 0, size)} + return nil + } +} +func (p *Expr) Init(options ...func(*Expr) error) error { + var ( + max token32 + position, tokenIndex uint32 + buffer []rune + ) + for _, option := range options { + err := option(p) + if err != nil { + return err + } + } + p.reset = func() { + max = token32{} + position, tokenIndex = 0, 0 + + p.buffer = []rune(p.Buffer) + if len(p.buffer) == 0 || p.buffer[len(p.buffer)-1] != endSymbol { + p.buffer = append(p.buffer, endSymbol) + } + buffer = p.buffer + } + p.reset() + + _rules := p.rules + tree := p.tokens32 + p.parse = func(rule ...int) error { + r := 1 + if len(rule) > 0 { + r = rule[0] + } + matches := p.rules[r]() + p.tokens32 = tree + if matches { + p.Trim(tokenIndex) + return nil + } + return &parseError{p, max} + } + + add := func(rule pegRule, begin uint32) { + tree.Add(rule, begin, position, tokenIndex) + tokenIndex++ + if begin != position && position > max.end { + max = token32{rule, begin, position} + } + } + + matchDot := func() bool { + if buffer[position] != endSymbol { + position++ + return true + } + return false + } + + /*matchChar := func(c byte) bool { + if buffer[position] == c { + position++ + return true + } + return false + }*/ + + /*matchRange := func(lower byte, upper byte) bool { + if c := buffer[position]; c >= lower && c <= upper { + position++ + return true + } + return false + }*/ + + _rules = [...]func() bool{ + nil, + /* 0 e <- <(sp e1 !.)> */ + func() bool { + position0, tokenIndex0 := position, tokenIndex + { + position1 := position + if !_rules[rulesp]() { + goto l0 + } + if !_rules[rulee1]() { + goto l0 + } + { + position2, tokenIndex2 := position, tokenIndex + if !matchDot() { + goto l2 + } + goto l0 + l2: + position, tokenIndex = position2, tokenIndex2 + } + add(rulee, position1) + } + return true + l0: + position, tokenIndex = position0, tokenIndex0 + return false + }, + /* 1 e1 <- <(e2 (or e2 Action0)*)> */ + func() bool { + position3, tokenIndex3 := position, tokenIndex + { + position4 := position + if !_rules[rulee2]() { + goto l3 + } + l5: + { + position6, tokenIndex6 := position, tokenIndex + if !_rules[ruleor]() { + goto l6 + } + if !_rules[rulee2]() { + goto l6 + } + if !_rules[ruleAction0]() { + goto l6 + } + goto l5 + l6: + position, tokenIndex = position6, tokenIndex6 + } + add(rulee1, position4) + } + return true + l3: + position, tokenIndex = position3, tokenIndex3 + return false + }, + /* 2 e2 <- <(e3 (and e3 Action1)*)> */ + func() bool { + position7, tokenIndex7 := position, tokenIndex + { + position8 := position + if !_rules[rulee3]() { + goto l7 + } + l9: + { + position10, tokenIndex10 := position, tokenIndex + if !_rules[ruleand]() { + goto l10 + } + if !_rules[rulee3]() { + goto l10 + } + if !_rules[ruleAction1]() { + goto l10 + } + goto l9 + l10: + position, tokenIndex = position10, tokenIndex10 + } + add(rulee2, position8) + } + return true + l7: + position, tokenIndex = position7, tokenIndex7 + return false + }, + /* 3 e3 <- <((eq e4 Action2) / (neq e4 Action3) / (gte e4 Action4) / (gt e4 Action5) / (lte e4 Action6) / (lt e4 Action7) / (open e1 close) / (e4 Action8))> */ + func() bool { + position11, tokenIndex11 := position, tokenIndex + { + position12 := position + { + position13, tokenIndex13 := position, tokenIndex + if !_rules[ruleeq]() { + goto l14 + } + if !_rules[rulee4]() { + goto l14 + } + if !_rules[ruleAction2]() { + goto l14 + } + goto l13 + l14: + position, tokenIndex = position13, tokenIndex13 + if !_rules[ruleneq]() { + goto l15 + } + if !_rules[rulee4]() { + goto l15 + } + if !_rules[ruleAction3]() { + goto l15 + } + goto l13 + l15: + position, tokenIndex = position13, tokenIndex13 + if !_rules[rulegte]() { + goto l16 + } + if !_rules[rulee4]() { + goto l16 + } + if !_rules[ruleAction4]() { + goto l16 + } + goto l13 + l16: + position, tokenIndex = position13, tokenIndex13 + if !_rules[rulegt]() { + goto l17 + } + if !_rules[rulee4]() { + goto l17 + } + if !_rules[ruleAction5]() { + goto l17 + } + goto l13 + l17: + position, tokenIndex = position13, tokenIndex13 + if !_rules[rulelte]() { + goto l18 + } + if !_rules[rulee4]() { + goto l18 + } + if !_rules[ruleAction6]() { + goto l18 + } + goto l13 + l18: + position, tokenIndex = position13, tokenIndex13 + if !_rules[rulelt]() { + goto l19 + } + if !_rules[rulee4]() { + goto l19 + } + if !_rules[ruleAction7]() { + goto l19 + } + goto l13 + l19: + position, tokenIndex = position13, tokenIndex13 + if !_rules[ruleopen]() { + goto l20 + } + if !_rules[rulee1]() { + goto l20 + } + if !_rules[ruleclose]() { + goto l20 + } + goto l13 + l20: + position, tokenIndex = position13, tokenIndex13 + if !_rules[rulee4]() { + goto l11 + } + if !_rules[ruleAction8]() { + goto l11 + } + } + l13: + add(rulee3, position12) + } + return true + l11: + position, tokenIndex = position11, tokenIndex11 + return false + }, + /* 4 e4 <- <( sp Action9)> */ + func() bool { + position21, tokenIndex21 := position, tokenIndex + { + position22 := position + { + position23 := position + if !_rules[rulename]() { + goto l21 + } + add(rulePegText, position23) + } + if !_rules[rulesp]() { + goto l21 + } + if !_rules[ruleAction9]() { + goto l21 + } + add(rulee4, position22) + } + return true + l21: + position, tokenIndex = position21, tokenIndex21 + return false + }, + /* 5 name <- <([a-z] / [A-Z] / '_' / [0-9] / '\'' / '"')*> */ + func() bool { + { + position25 := position + l26: + { + position27, tokenIndex27 := position, tokenIndex + { + position28, tokenIndex28 := position, tokenIndex + if c := buffer[position]; c < rune('a') || c > rune('z') { + goto l29 + } + position++ + goto l28 + l29: + position, tokenIndex = position28, tokenIndex28 + if c := buffer[position]; c < rune('A') || c > rune('Z') { + goto l30 + } + position++ + goto l28 + l30: + position, tokenIndex = position28, tokenIndex28 + if buffer[position] != rune('_') { + goto l31 + } + position++ + goto l28 + l31: + position, tokenIndex = position28, tokenIndex28 + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l32 + } + position++ + goto l28 + l32: + position, tokenIndex = position28, tokenIndex28 + if buffer[position] != rune('\'') { + goto l33 + } + position++ + goto l28 + l33: + position, tokenIndex = position28, tokenIndex28 + if buffer[position] != rune('"') { + goto l27 + } + position++ + } + l28: + goto l26 + l27: + position, tokenIndex = position27, tokenIndex27 + } + add(rulename, position25) + } + return true + }, + /* 6 and <- <('&' '&' sp)> */ + func() bool { + position34, tokenIndex34 := position, tokenIndex + { + position35 := position + if buffer[position] != rune('&') { + goto l34 + } + position++ + if buffer[position] != rune('&') { + goto l34 + } + position++ + if !_rules[rulesp]() { + goto l34 + } + add(ruleand, position35) + } + return true + l34: + position, tokenIndex = position34, tokenIndex34 + return false + }, + /* 7 or <- <('|' '|' sp)> */ + func() bool { + position36, tokenIndex36 := position, tokenIndex + { + position37 := position + if buffer[position] != rune('|') { + goto l36 + } + position++ + if buffer[position] != rune('|') { + goto l36 + } + position++ + if !_rules[rulesp]() { + goto l36 + } + add(ruleor, position37) + } + return true + l36: + position, tokenIndex = position36, tokenIndex36 + return false + }, + /* 8 eq <- <('=' '=' sp)> */ + func() bool { + position38, tokenIndex38 := position, tokenIndex + { + position39 := position + if buffer[position] != rune('=') { + goto l38 + } + position++ + if buffer[position] != rune('=') { + goto l38 + } + position++ + if !_rules[rulesp]() { + goto l38 + } + add(ruleeq, position39) + } + return true + l38: + position, tokenIndex = position38, tokenIndex38 + return false + }, + /* 9 neq <- <('!' '=' sp)> */ + func() bool { + position40, tokenIndex40 := position, tokenIndex + { + position41 := position + if buffer[position] != rune('!') { + goto l40 + } + position++ + if buffer[position] != rune('=') { + goto l40 + } + position++ + if !_rules[rulesp]() { + goto l40 + } + add(ruleneq, position41) + } + return true + l40: + position, tokenIndex = position40, tokenIndex40 + return false + }, + /* 10 gt <- <('>' sp)> */ + func() bool { + position42, tokenIndex42 := position, tokenIndex + { + position43 := position + if buffer[position] != rune('>') { + goto l42 + } + position++ + if !_rules[rulesp]() { + goto l42 + } + add(rulegt, position43) + } + return true + l42: + position, tokenIndex = position42, tokenIndex42 + return false + }, + /* 11 gte <- <('>' '=' sp)> */ + func() bool { + position44, tokenIndex44 := position, tokenIndex + { + position45 := position + if buffer[position] != rune('>') { + goto l44 + } + position++ + if buffer[position] != rune('=') { + goto l44 + } + position++ + if !_rules[rulesp]() { + goto l44 + } + add(rulegte, position45) + } + return true + l44: + position, tokenIndex = position44, tokenIndex44 + return false + }, + /* 12 lt <- <('<' sp)> */ + func() bool { + position46, tokenIndex46 := position, tokenIndex + { + position47 := position + if buffer[position] != rune('<') { + goto l46 + } + position++ + if !_rules[rulesp]() { + goto l46 + } + add(rulelt, position47) + } + return true + l46: + position, tokenIndex = position46, tokenIndex46 + return false + }, + /* 13 lte <- <('<' '=' sp)> */ + func() bool { + position48, tokenIndex48 := position, tokenIndex + { + position49 := position + if buffer[position] != rune('<') { + goto l48 + } + position++ + if buffer[position] != rune('=') { + goto l48 + } + position++ + if !_rules[rulesp]() { + goto l48 + } + add(rulelte, position49) + } + return true + l48: + position, tokenIndex = position48, tokenIndex48 + return false + }, + /* 14 open <- <('(' sp)> */ + func() bool { + position50, tokenIndex50 := position, tokenIndex + { + position51 := position + if buffer[position] != rune('(') { + goto l50 + } + position++ + if !_rules[rulesp]() { + goto l50 + } + add(ruleopen, position51) + } + return true + l50: + position, tokenIndex = position50, tokenIndex50 + return false + }, + /* 15 close <- <(')' sp)> */ + func() bool { + position52, tokenIndex52 := position, tokenIndex + { + position53 := position + if buffer[position] != rune(')') { + goto l52 + } + position++ + if !_rules[rulesp]() { + goto l52 + } + add(ruleclose, position53) + } + return true + l52: + position, tokenIndex = position52, tokenIndex52 + return false + }, + /* 16 sp <- <(' ' / '\t')*> */ + func() bool { + { + position55 := position + l56: + { + position57, tokenIndex57 := position, tokenIndex + { + position58, tokenIndex58 := position, tokenIndex + if buffer[position] != rune(' ') { + goto l59 + } + position++ + goto l58 + l59: + position, tokenIndex = position58, tokenIndex58 + if buffer[position] != rune('\t') { + goto l57 + } + position++ + } + l58: + goto l56 + l57: + position, tokenIndex = position57, tokenIndex57 + } + add(rulesp, position55) + } + return true + }, + /* 18 Action0 <- <{ p.AddOperator(TypeOr) }> */ + func() bool { + { + add(ruleAction0, position) + } + return true + }, + /* 19 Action1 <- <{ p.AddOperator(TypeAnd) }> */ + func() bool { + { + add(ruleAction1, position) + } + return true + }, + /* 20 Action2 <- <{ p.AddOperator(TypeEq) }> */ + func() bool { + { + add(ruleAction2, position) + } + return true + }, + /* 21 Action3 <- <{ p.AddOperator(TypeNEq) }> */ + func() bool { + { + add(ruleAction3, position) + } + return true + }, + /* 22 Action4 <- <{ p.AddOperator(TypeGte) }> */ + func() bool { + { + add(ruleAction4, position) + } + return true + }, + /* 23 Action5 <- <{ p.AddOperator(TypeGt) }> */ + func() bool { + { + add(ruleAction5, position) + } + return true + }, + /* 24 Action6 <- <{ p.AddOperator(TypeLte) }> */ + func() bool { + { + add(ruleAction6, position) + } + return true + }, + /* 25 Action7 <- <{ p.AddOperator(TypeLt) }> */ + func() bool { + { + add(ruleAction7, position) + } + return true + }, + /* 26 Action8 <- <{ p.AddOperator(TypeEq) }> */ + func() bool { + { + add(ruleAction8, position) + } + return true + }, + nil, + /* 28 Action9 <- <{ p.AddLiteral(buffer[begin:end]) }> */ + func() bool { + { + add(ruleAction9, position) + } + return true + }, + } + p.rules = _rules + return nil +} diff --git a/go.mod b/go.mod index 4b3abca..f3290dc 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/project-flogo/rules go 1.12 require ( + github.com/360EntSecGroup-Skylar/excelize v1.4.1 github.com/Shopify/sarama v1.23.1 github.com/aws/aws-sdk-go v1.24.1 github.com/gomodule/redigo v2.0.0+incompatible diff --git a/go.sum b/go.sum index a315570..8a0c4df 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/360EntSecGroup-Skylar/excelize v1.4.1 h1:l55mJb6rkkaUzOpSsgEeKYtS6/0gHwBYyfo5Jcjv/Ks= +github.com/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE= github.com/DataDog/zstd v1.3.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798 h1:2T/jmrHeTezcCM58lvEQXs0UpQJCo5SoGAcg+mbSTIg= github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= @@ -36,6 +38,8 @@ github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41 h1:GeinFsrjWz97fAxVUEd748aV0cYL+I6k44gFJTCVvpU= @@ -69,6 +73,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.3-0.20181224173747-660f15d67dbb/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= From af493e61013ecd3d70bffc09dea09e4c8b4f16ce Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Tue, 3 Dec 2019 16:47:13 -0700 Subject: [PATCH 107/125] Improve parser --- activity/dtable/activity_test.go | 8 + activity/dtable/expression.go | 7 + activity/dtable/expression.peg | 25 +- activity/dtable/expression.peg.go | 663 +++++++++++++++++++----------- 4 files changed, 465 insertions(+), 238 deletions(-) diff --git a/activity/dtable/activity_test.go b/activity/dtable/activity_test.go index d3fff88..ba94269 100644 --- a/activity/dtable/activity_test.go +++ b/activity/dtable/activity_test.go @@ -140,9 +140,16 @@ func TestCellCompileExpr(t *testing.T) { propName := "name" testcases := make(map[string]string) testcases["foo"] = "$.applicant.name == foo" + testcases["123foo"] = "$.applicant.name == 123foo" + testcases["foo123"] = "$.applicant.name == foo123" + testcases["123"] = "$.applicant.name == 123" + testcases["123.123"] = "$.applicant.name == 123.123" + testcases[".123"] = "$.applicant.name == .123" + testcases["!foo"] = "!($.applicant.name == foo)" testcases["==foo"] = "$.applicant.name == foo" testcases["!=foo"] = "$.applicant.name != foo" testcases[">foo"] = "$.applicant.name > foo" + testcases["!>foo"] = "!($.applicant.name > foo)" testcases[">=foo"] = "$.applicant.name >= foo" testcases[" sp { p.AddLiteral(buffer[begin:end]) } -name <- [a-zA-Z_0-9'"]* + / e5 { p.AddOperator(TypeEq) } +e5 <- < value > sp { p.AddLiteral(buffer[begin:end]) } +value <- [0-9]+ [.] [0-9]* + / [.] [0-9]+ + / ["] (!["] .)* ["] + / ['] (!['] .)* ['] + / [a-zA-Z_0-9]+ +not <- '!' sp and <- '&&' sp or <- '||' sp eq <- '==' sp diff --git a/activity/dtable/expression.peg.go b/activity/dtable/expression.peg.go index 57e785a..16d1122 100644 --- a/activity/dtable/expression.peg.go +++ b/activity/dtable/expression.peg.go @@ -22,7 +22,9 @@ const ( rulee2 rulee3 rulee4 - rulename + rulee5 + rulevalue + rulenot ruleand ruleor ruleeq @@ -43,8 +45,9 @@ const ( ruleAction6 ruleAction7 ruleAction8 - rulePegText ruleAction9 + rulePegText + ruleAction10 ) var rul3s = [...]string{ @@ -54,7 +57,9 @@ var rul3s = [...]string{ "e2", "e3", "e4", - "name", + "e5", + "value", + "not", "and", "or", "eq", @@ -75,8 +80,9 @@ var rul3s = [...]string{ "Action6", "Action7", "Action8", - "PegText", "Action9", + "PegText", + "Action10", } type token32 struct { @@ -193,7 +199,7 @@ type Expr struct { Buffer string buffer []rune - rules [29]func() bool + rules [32]func() bool parse func(rule ...int) error reset func() Pretty bool @@ -294,20 +300,22 @@ func (p *Expr) Execute() { case ruleAction1: p.AddOperator(TypeAnd) case ruleAction2: - p.AddOperator(TypeEq) + p.AddOperator(TypeNot) case ruleAction3: - p.AddOperator(TypeNEq) + p.AddOperator(TypeEq) case ruleAction4: - p.AddOperator(TypeGte) + p.AddOperator(TypeNEq) case ruleAction5: - p.AddOperator(TypeGt) + p.AddOperator(TypeGte) case ruleAction6: - p.AddOperator(TypeLte) + p.AddOperator(TypeGt) case ruleAction7: - p.AddOperator(TypeLt) + p.AddOperator(TypeLte) case ruleAction8: - p.AddOperator(TypeEq) + p.AddOperator(TypeLt) case ruleAction9: + p.AddOperator(TypeEq) + case ruleAction10: p.AddLiteral(buffer[begin:end]) } @@ -491,14 +499,14 @@ func (p *Expr) Init(options ...func(*Expr) error) error { position, tokenIndex = position7, tokenIndex7 return false }, - /* 3 e3 <- <((eq e4 Action2) / (neq e4 Action3) / (gte e4 Action4) / (gt e4 Action5) / (lte e4 Action6) / (lt e4 Action7) / (open e1 close) / (e4 Action8))> */ + /* 3 e3 <- <((not e4 Action2) / e4)> */ func() bool { position11, tokenIndex11 := position, tokenIndex { position12 := position { position13, tokenIndex13 := position, tokenIndex - if !_rules[ruleeq]() { + if !_rules[rulenot]() { goto l14 } if !_rules[rulee4]() { @@ -510,491 +518,688 @@ func (p *Expr) Init(options ...func(*Expr) error) error { goto l13 l14: position, tokenIndex = position13, tokenIndex13 - if !_rules[ruleneq]() { - goto l15 - } if !_rules[rulee4]() { - goto l15 + goto l11 + } + } + l13: + add(rulee3, position12) + } + return true + l11: + position, tokenIndex = position11, tokenIndex11 + return false + }, + /* 4 e4 <- <((eq e5 Action3) / (neq e5 Action4) / (gte e5 Action5) / (gt e5 Action6) / (lte e5 Action7) / (lt e5 Action8) / (open e1 close) / (e5 Action9))> */ + func() bool { + position15, tokenIndex15 := position, tokenIndex + { + position16 := position + { + position17, tokenIndex17 := position, tokenIndex + if !_rules[ruleeq]() { + goto l18 + } + if !_rules[rulee5]() { + goto l18 } if !_rules[ruleAction3]() { - goto l15 + goto l18 } - goto l13 - l15: - position, tokenIndex = position13, tokenIndex13 - if !_rules[rulegte]() { - goto l16 + goto l17 + l18: + position, tokenIndex = position17, tokenIndex17 + if !_rules[ruleneq]() { + goto l19 } - if !_rules[rulee4]() { - goto l16 + if !_rules[rulee5]() { + goto l19 } if !_rules[ruleAction4]() { - goto l16 + goto l19 } - goto l13 - l16: - position, tokenIndex = position13, tokenIndex13 - if !_rules[rulegt]() { - goto l17 + goto l17 + l19: + position, tokenIndex = position17, tokenIndex17 + if !_rules[rulegte]() { + goto l20 } - if !_rules[rulee4]() { - goto l17 + if !_rules[rulee5]() { + goto l20 } if !_rules[ruleAction5]() { - goto l17 + goto l20 } - goto l13 - l17: - position, tokenIndex = position13, tokenIndex13 - if !_rules[rulelte]() { - goto l18 + goto l17 + l20: + position, tokenIndex = position17, tokenIndex17 + if !_rules[rulegt]() { + goto l21 } - if !_rules[rulee4]() { - goto l18 + if !_rules[rulee5]() { + goto l21 } if !_rules[ruleAction6]() { - goto l18 + goto l21 } - goto l13 - l18: - position, tokenIndex = position13, tokenIndex13 - if !_rules[rulelt]() { - goto l19 + goto l17 + l21: + position, tokenIndex = position17, tokenIndex17 + if !_rules[rulelte]() { + goto l22 } - if !_rules[rulee4]() { - goto l19 + if !_rules[rulee5]() { + goto l22 } if !_rules[ruleAction7]() { - goto l19 + goto l22 } - goto l13 - l19: - position, tokenIndex = position13, tokenIndex13 + goto l17 + l22: + position, tokenIndex = position17, tokenIndex17 + if !_rules[rulelt]() { + goto l23 + } + if !_rules[rulee5]() { + goto l23 + } + if !_rules[ruleAction8]() { + goto l23 + } + goto l17 + l23: + position, tokenIndex = position17, tokenIndex17 if !_rules[ruleopen]() { - goto l20 + goto l24 } if !_rules[rulee1]() { - goto l20 + goto l24 } if !_rules[ruleclose]() { - goto l20 + goto l24 } - goto l13 - l20: - position, tokenIndex = position13, tokenIndex13 - if !_rules[rulee4]() { - goto l11 + goto l17 + l24: + position, tokenIndex = position17, tokenIndex17 + if !_rules[rulee5]() { + goto l15 } - if !_rules[ruleAction8]() { - goto l11 + if !_rules[ruleAction9]() { + goto l15 } } - l13: - add(rulee3, position12) + l17: + add(rulee4, position16) } return true - l11: - position, tokenIndex = position11, tokenIndex11 + l15: + position, tokenIndex = position15, tokenIndex15 return false }, - /* 4 e4 <- <( sp Action9)> */ + /* 5 e5 <- <( sp Action10)> */ func() bool { - position21, tokenIndex21 := position, tokenIndex + position25, tokenIndex25 := position, tokenIndex { - position22 := position + position26 := position { - position23 := position - if !_rules[rulename]() { - goto l21 + position27 := position + if !_rules[rulevalue]() { + goto l25 } - add(rulePegText, position23) + add(rulePegText, position27) } if !_rules[rulesp]() { - goto l21 + goto l25 } - if !_rules[ruleAction9]() { - goto l21 + if !_rules[ruleAction10]() { + goto l25 } - add(rulee4, position22) + add(rulee5, position26) } return true - l21: - position, tokenIndex = position21, tokenIndex21 + l25: + position, tokenIndex = position25, tokenIndex25 return false }, - /* 5 name <- <([a-z] / [A-Z] / '_' / [0-9] / '\'' / '"')*> */ + /* 6 value <- <(([0-9]+ '.' [0-9]*) / ('.' [0-9]+) / ('"' (!'"' .)* '"') / ('\'' (!'\'' .)* '\'') / ([a-z] / [A-Z] / '_' / [0-9])+)> */ func() bool { + position28, tokenIndex28 := position, tokenIndex { - position25 := position - l26: + position29 := position { - position27, tokenIndex27 := position, tokenIndex + position30, tokenIndex30 := position, tokenIndex + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l31 + } + position++ + l32: { - position28, tokenIndex28 := position, tokenIndex + position33, tokenIndex33 := position, tokenIndex + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l33 + } + position++ + goto l32 + l33: + position, tokenIndex = position33, tokenIndex33 + } + if buffer[position] != rune('.') { + goto l31 + } + position++ + l34: + { + position35, tokenIndex35 := position, tokenIndex + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l35 + } + position++ + goto l34 + l35: + position, tokenIndex = position35, tokenIndex35 + } + goto l30 + l31: + position, tokenIndex = position30, tokenIndex30 + if buffer[position] != rune('.') { + goto l36 + } + position++ + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l36 + } + position++ + l37: + { + position38, tokenIndex38 := position, tokenIndex + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l38 + } + position++ + goto l37 + l38: + position, tokenIndex = position38, tokenIndex38 + } + goto l30 + l36: + position, tokenIndex = position30, tokenIndex30 + if buffer[position] != rune('"') { + goto l39 + } + position++ + l40: + { + position41, tokenIndex41 := position, tokenIndex + { + position42, tokenIndex42 := position, tokenIndex + if buffer[position] != rune('"') { + goto l42 + } + position++ + goto l41 + l42: + position, tokenIndex = position42, tokenIndex42 + } + if !matchDot() { + goto l41 + } + goto l40 + l41: + position, tokenIndex = position41, tokenIndex41 + } + if buffer[position] != rune('"') { + goto l39 + } + position++ + goto l30 + l39: + position, tokenIndex = position30, tokenIndex30 + if buffer[position] != rune('\'') { + goto l43 + } + position++ + l44: + { + position45, tokenIndex45 := position, tokenIndex + { + position46, tokenIndex46 := position, tokenIndex + if buffer[position] != rune('\'') { + goto l46 + } + position++ + goto l45 + l46: + position, tokenIndex = position46, tokenIndex46 + } + if !matchDot() { + goto l45 + } + goto l44 + l45: + position, tokenIndex = position45, tokenIndex45 + } + if buffer[position] != rune('\'') { + goto l43 + } + position++ + goto l30 + l43: + position, tokenIndex = position30, tokenIndex30 + { + position49, tokenIndex49 := position, tokenIndex if c := buffer[position]; c < rune('a') || c > rune('z') { - goto l29 + goto l50 } position++ - goto l28 - l29: - position, tokenIndex = position28, tokenIndex28 + goto l49 + l50: + position, tokenIndex = position49, tokenIndex49 if c := buffer[position]; c < rune('A') || c > rune('Z') { - goto l30 + goto l51 } position++ - goto l28 - l30: - position, tokenIndex = position28, tokenIndex28 + goto l49 + l51: + position, tokenIndex = position49, tokenIndex49 if buffer[position] != rune('_') { - goto l31 + goto l52 } position++ - goto l28 - l31: - position, tokenIndex = position28, tokenIndex28 + goto l49 + l52: + position, tokenIndex = position49, tokenIndex49 if c := buffer[position]; c < rune('0') || c > rune('9') { - goto l32 - } - position++ - goto l28 - l32: - position, tokenIndex = position28, tokenIndex28 - if buffer[position] != rune('\'') { - goto l33 + goto l28 } position++ - goto l28 - l33: - position, tokenIndex = position28, tokenIndex28 - if buffer[position] != rune('"') { - goto l27 + } + l49: + l47: + { + position48, tokenIndex48 := position, tokenIndex + { + position53, tokenIndex53 := position, tokenIndex + if c := buffer[position]; c < rune('a') || c > rune('z') { + goto l54 + } + position++ + goto l53 + l54: + position, tokenIndex = position53, tokenIndex53 + if c := buffer[position]; c < rune('A') || c > rune('Z') { + goto l55 + } + position++ + goto l53 + l55: + position, tokenIndex = position53, tokenIndex53 + if buffer[position] != rune('_') { + goto l56 + } + position++ + goto l53 + l56: + position, tokenIndex = position53, tokenIndex53 + if c := buffer[position]; c < rune('0') || c > rune('9') { + goto l48 + } + position++ } - position++ + l53: + goto l47 + l48: + position, tokenIndex = position48, tokenIndex48 } - l28: - goto l26 - l27: - position, tokenIndex = position27, tokenIndex27 } - add(rulename, position25) + l30: + add(rulevalue, position29) + } + return true + l28: + position, tokenIndex = position28, tokenIndex28 + return false + }, + /* 7 not <- <('!' sp)> */ + func() bool { + position57, tokenIndex57 := position, tokenIndex + { + position58 := position + if buffer[position] != rune('!') { + goto l57 + } + position++ + if !_rules[rulesp]() { + goto l57 + } + add(rulenot, position58) } return true + l57: + position, tokenIndex = position57, tokenIndex57 + return false }, - /* 6 and <- <('&' '&' sp)> */ + /* 8 and <- <('&' '&' sp)> */ func() bool { - position34, tokenIndex34 := position, tokenIndex + position59, tokenIndex59 := position, tokenIndex { - position35 := position + position60 := position if buffer[position] != rune('&') { - goto l34 + goto l59 } position++ if buffer[position] != rune('&') { - goto l34 + goto l59 } position++ if !_rules[rulesp]() { - goto l34 + goto l59 } - add(ruleand, position35) + add(ruleand, position60) } return true - l34: - position, tokenIndex = position34, tokenIndex34 + l59: + position, tokenIndex = position59, tokenIndex59 return false }, - /* 7 or <- <('|' '|' sp)> */ + /* 9 or <- <('|' '|' sp)> */ func() bool { - position36, tokenIndex36 := position, tokenIndex + position61, tokenIndex61 := position, tokenIndex { - position37 := position + position62 := position if buffer[position] != rune('|') { - goto l36 + goto l61 } position++ if buffer[position] != rune('|') { - goto l36 + goto l61 } position++ if !_rules[rulesp]() { - goto l36 + goto l61 } - add(ruleor, position37) + add(ruleor, position62) } return true - l36: - position, tokenIndex = position36, tokenIndex36 + l61: + position, tokenIndex = position61, tokenIndex61 return false }, - /* 8 eq <- <('=' '=' sp)> */ + /* 10 eq <- <('=' '=' sp)> */ func() bool { - position38, tokenIndex38 := position, tokenIndex + position63, tokenIndex63 := position, tokenIndex { - position39 := position + position64 := position if buffer[position] != rune('=') { - goto l38 + goto l63 } position++ if buffer[position] != rune('=') { - goto l38 + goto l63 } position++ if !_rules[rulesp]() { - goto l38 + goto l63 } - add(ruleeq, position39) + add(ruleeq, position64) } return true - l38: - position, tokenIndex = position38, tokenIndex38 + l63: + position, tokenIndex = position63, tokenIndex63 return false }, - /* 9 neq <- <('!' '=' sp)> */ + /* 11 neq <- <('!' '=' sp)> */ func() bool { - position40, tokenIndex40 := position, tokenIndex + position65, tokenIndex65 := position, tokenIndex { - position41 := position + position66 := position if buffer[position] != rune('!') { - goto l40 + goto l65 } position++ if buffer[position] != rune('=') { - goto l40 + goto l65 } position++ if !_rules[rulesp]() { - goto l40 + goto l65 } - add(ruleneq, position41) + add(ruleneq, position66) } return true - l40: - position, tokenIndex = position40, tokenIndex40 + l65: + position, tokenIndex = position65, tokenIndex65 return false }, - /* 10 gt <- <('>' sp)> */ + /* 12 gt <- <('>' sp)> */ func() bool { - position42, tokenIndex42 := position, tokenIndex + position67, tokenIndex67 := position, tokenIndex { - position43 := position + position68 := position if buffer[position] != rune('>') { - goto l42 + goto l67 } position++ if !_rules[rulesp]() { - goto l42 + goto l67 } - add(rulegt, position43) + add(rulegt, position68) } return true - l42: - position, tokenIndex = position42, tokenIndex42 + l67: + position, tokenIndex = position67, tokenIndex67 return false }, - /* 11 gte <- <('>' '=' sp)> */ + /* 13 gte <- <('>' '=' sp)> */ func() bool { - position44, tokenIndex44 := position, tokenIndex + position69, tokenIndex69 := position, tokenIndex { - position45 := position + position70 := position if buffer[position] != rune('>') { - goto l44 + goto l69 } position++ if buffer[position] != rune('=') { - goto l44 + goto l69 } position++ if !_rules[rulesp]() { - goto l44 + goto l69 } - add(rulegte, position45) + add(rulegte, position70) } return true - l44: - position, tokenIndex = position44, tokenIndex44 + l69: + position, tokenIndex = position69, tokenIndex69 return false }, - /* 12 lt <- <('<' sp)> */ + /* 14 lt <- <('<' sp)> */ func() bool { - position46, tokenIndex46 := position, tokenIndex + position71, tokenIndex71 := position, tokenIndex { - position47 := position + position72 := position if buffer[position] != rune('<') { - goto l46 + goto l71 } position++ if !_rules[rulesp]() { - goto l46 + goto l71 } - add(rulelt, position47) + add(rulelt, position72) } return true - l46: - position, tokenIndex = position46, tokenIndex46 + l71: + position, tokenIndex = position71, tokenIndex71 return false }, - /* 13 lte <- <('<' '=' sp)> */ + /* 15 lte <- <('<' '=' sp)> */ func() bool { - position48, tokenIndex48 := position, tokenIndex + position73, tokenIndex73 := position, tokenIndex { - position49 := position + position74 := position if buffer[position] != rune('<') { - goto l48 + goto l73 } position++ if buffer[position] != rune('=') { - goto l48 + goto l73 } position++ if !_rules[rulesp]() { - goto l48 + goto l73 } - add(rulelte, position49) + add(rulelte, position74) } return true - l48: - position, tokenIndex = position48, tokenIndex48 + l73: + position, tokenIndex = position73, tokenIndex73 return false }, - /* 14 open <- <('(' sp)> */ + /* 16 open <- <('(' sp)> */ func() bool { - position50, tokenIndex50 := position, tokenIndex + position75, tokenIndex75 := position, tokenIndex { - position51 := position + position76 := position if buffer[position] != rune('(') { - goto l50 + goto l75 } position++ if !_rules[rulesp]() { - goto l50 + goto l75 } - add(ruleopen, position51) + add(ruleopen, position76) } return true - l50: - position, tokenIndex = position50, tokenIndex50 + l75: + position, tokenIndex = position75, tokenIndex75 return false }, - /* 15 close <- <(')' sp)> */ + /* 17 close <- <(')' sp)> */ func() bool { - position52, tokenIndex52 := position, tokenIndex + position77, tokenIndex77 := position, tokenIndex { - position53 := position + position78 := position if buffer[position] != rune(')') { - goto l52 + goto l77 } position++ if !_rules[rulesp]() { - goto l52 + goto l77 } - add(ruleclose, position53) + add(ruleclose, position78) } return true - l52: - position, tokenIndex = position52, tokenIndex52 + l77: + position, tokenIndex = position77, tokenIndex77 return false }, - /* 16 sp <- <(' ' / '\t')*> */ + /* 18 sp <- <(' ' / '\t')*> */ func() bool { { - position55 := position - l56: + position80 := position + l81: { - position57, tokenIndex57 := position, tokenIndex + position82, tokenIndex82 := position, tokenIndex { - position58, tokenIndex58 := position, tokenIndex + position83, tokenIndex83 := position, tokenIndex if buffer[position] != rune(' ') { - goto l59 + goto l84 } position++ - goto l58 - l59: - position, tokenIndex = position58, tokenIndex58 + goto l83 + l84: + position, tokenIndex = position83, tokenIndex83 if buffer[position] != rune('\t') { - goto l57 + goto l82 } position++ } - l58: - goto l56 - l57: - position, tokenIndex = position57, tokenIndex57 + l83: + goto l81 + l82: + position, tokenIndex = position82, tokenIndex82 } - add(rulesp, position55) + add(rulesp, position80) } return true }, - /* 18 Action0 <- <{ p.AddOperator(TypeOr) }> */ + /* 20 Action0 <- <{ p.AddOperator(TypeOr) }> */ func() bool { { add(ruleAction0, position) } return true }, - /* 19 Action1 <- <{ p.AddOperator(TypeAnd) }> */ + /* 21 Action1 <- <{ p.AddOperator(TypeAnd) }> */ func() bool { { add(ruleAction1, position) } return true }, - /* 20 Action2 <- <{ p.AddOperator(TypeEq) }> */ + /* 22 Action2 <- <{ p.AddOperator(TypeNot) }> */ func() bool { { add(ruleAction2, position) } return true }, - /* 21 Action3 <- <{ p.AddOperator(TypeNEq) }> */ + /* 23 Action3 <- <{ p.AddOperator(TypeEq) }> */ func() bool { { add(ruleAction3, position) } return true }, - /* 22 Action4 <- <{ p.AddOperator(TypeGte) }> */ + /* 24 Action4 <- <{ p.AddOperator(TypeNEq) }> */ func() bool { { add(ruleAction4, position) } return true }, - /* 23 Action5 <- <{ p.AddOperator(TypeGt) }> */ + /* 25 Action5 <- <{ p.AddOperator(TypeGte) }> */ func() bool { { add(ruleAction5, position) } return true }, - /* 24 Action6 <- <{ p.AddOperator(TypeLte) }> */ + /* 26 Action6 <- <{ p.AddOperator(TypeGt) }> */ func() bool { { add(ruleAction6, position) } return true }, - /* 25 Action7 <- <{ p.AddOperator(TypeLt) }> */ + /* 27 Action7 <- <{ p.AddOperator(TypeLte) }> */ func() bool { { add(ruleAction7, position) } return true }, - /* 26 Action8 <- <{ p.AddOperator(TypeEq) }> */ + /* 28 Action8 <- <{ p.AddOperator(TypeLt) }> */ func() bool { { add(ruleAction8, position) } return true }, - nil, - /* 28 Action9 <- <{ p.AddLiteral(buffer[begin:end]) }> */ + /* 29 Action9 <- <{ p.AddOperator(TypeEq) }> */ func() bool { { add(ruleAction9, position) } return true }, + nil, + /* 31 Action10 <- <{ p.AddLiteral(buffer[begin:end]) }> */ + func() bool { + { + add(ruleAction10, position) + } + return true + }, } p.rules = _rules return nil From 4226a51cef4556110504d83e2874e42184b2011b Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Tue, 3 Dec 2019 17:22:40 -0700 Subject: [PATCH 108/125] Removed license --- activity/dtable/expression.peg | 4 ---- 1 file changed, 4 deletions(-) diff --git a/activity/dtable/expression.peg b/activity/dtable/expression.peg index 29f9cd3..1682e20 100644 --- a/activity/dtable/expression.peg +++ b/activity/dtable/expression.peg @@ -1,7 +1,3 @@ -# Copyright 2010 The Go Authors. All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - package dtable type Expr Peg { From 7625da3ff240da1b5e9cecddbff3b5f0008fdee4 Mon Sep 17 00:00:00 2001 From: Andrew Snodgrass Date: Tue, 3 Dec 2019 19:29:35 -0700 Subject: [PATCH 109/125] Refactor --- activity/dtable/activity.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activity/dtable/activity.go b/activity/dtable/activity.go index 87a543d..d4e931c 100644 --- a/activity/dtable/activity.go +++ b/activity/dtable/activity.go @@ -61,9 +61,9 @@ func (cell *genCell) compileExpr() { return } lhsToken := fmt.Sprintf("$.%s.%s", cell.tupleDesc.Name, cell.propDesc.Name) - expression := &Expr{Buffer: cell.rawValue} + expression := &Expr{Buffer: rawValue} expression.Init() - expression.Expression.Init(cell.rawValue) + expression.Expression.Init(rawValue) if err := expression.Parse(); err != nil { panic(err) } From 56747f9e2413f70da6ed5da94d237aadd8982b8f Mon Sep 17 00:00:00 2001 From: Ramesh Polishetti Date: Fri, 6 Dec 2019 16:46:33 +0530 Subject: [PATCH 110/125] upgrade excelize version to latest --- activity/dtable/activity.go | 4 ++-- go.mod | 2 +- go.sum | 7 ++++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/activity/dtable/activity.go b/activity/dtable/activity.go index d4e931c..04c286b 100644 --- a/activity/dtable/activity.go +++ b/activity/dtable/activity.go @@ -8,7 +8,7 @@ import ( "strconv" "strings" - "github.com/360EntSecGroup-Skylar/excelize" + excelize "github.com/360EntSecGroup-Skylar/excelize/v2" "github.com/project-flogo/core/activity" "github.com/project-flogo/core/data" "github.com/project-flogo/core/data/metadata" @@ -182,7 +182,7 @@ func loadFromXLSFile(fileName string) (*dTable, error) { if err != nil { return nil, fmt.Errorf("not able open the file [%s] - %s", fileName, err) } - rows := file.GetRows("DecisionTable") + rows, err := file.GetRows("DecisionTable") if err != nil { return nil, fmt.Errorf("DecisionTable worksheet not available in %s", fileName) } diff --git a/go.mod b/go.mod index f3290dc..7746e27 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/project-flogo/rules go 1.12 require ( - github.com/360EntSecGroup-Skylar/excelize v1.4.1 + github.com/360EntSecGroup-Skylar/excelize/v2 v2.0.2 github.com/Shopify/sarama v1.23.1 github.com/aws/aws-sdk-go v1.24.1 github.com/gomodule/redigo v2.0.0+incompatible diff --git a/go.sum b/go.sum index 8a0c4df..d76ea43 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/360EntSecGroup-Skylar/excelize v1.4.1 h1:l55mJb6rkkaUzOpSsgEeKYtS6/0gHwBYyfo5Jcjv/Ks= -github.com/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE= +github.com/360EntSecGroup-Skylar/excelize/v2 v2.0.2 h1:StMrA6UQ5Cm6206DxXGuV/NMqSIOIDoMXMYt8JPe1lE= +github.com/360EntSecGroup-Skylar/excelize/v2 v2.0.2/go.mod h1:EfRHD2k+Kd7ijnqlwOrH1IifwgWB9yYJ0pdXtBZmlpU= github.com/DataDog/zstd v1.3.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798 h1:2T/jmrHeTezcCM58lvEQXs0UpQJCo5SoGAcg+mbSTIg= github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= @@ -73,7 +73,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.3-0.20181224173747-660f15d67dbb/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -89,6 +88,8 @@ go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5 h1:bselrhR0Or1vomJZC8ZIjWtbDmn9OYFLX5Ik9alpJpE= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a h1:gHevYm0pO4QUbwy8Dmdr01R5r1BuKtfYqRqF0h/Cbh0= +golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= From 9c8f217b8c60fef37bd2a272adce4a4d22d0b2ca Mon Sep 17 00:00:00 2001 From: Naresh Kumar Thota <30426686+nareshkumarthota@users.noreply.github.com> Date: Fri, 6 Dec 2019 20:20:08 +0530 Subject: [PATCH 111/125] updated example to use store config file (#81) * updated example to use store config file * fixing excelise v2 upgrade * update decisiontable example to work with xlsx file and fix go tests (#82) * update decisiontable example to work with xlsx file and fix go tests * removing duplicated flogo.json from test folder * updated decision table example readme * stabilising mod and sum files --- activity/dtable/activity.go | 1 + examples/flogo/creditcard-dt/README.md | 4 ++ examples/flogo/creditcard-dt/flogo.json | 2 +- examples/flogo/creditcard-dt/rsconfig.json | 36 ++++++++++++++ examples/flogo/dtable/README.md | 53 ++------------------- examples/flogo/dtable/dtable-file.xlsx | Bin 0 -> 5600 bytes examples/flogo/dtable/flogo.json | 36 ++------------ go.sum | 2 +- 8 files changed, 50 insertions(+), 84 deletions(-) create mode 100644 examples/flogo/creditcard-dt/rsconfig.json create mode 100644 examples/flogo/dtable/dtable-file.xlsx diff --git a/activity/dtable/activity.go b/activity/dtable/activity.go index 04c286b..d505978 100644 --- a/activity/dtable/activity.go +++ b/activity/dtable/activity.go @@ -186,6 +186,7 @@ func loadFromXLSFile(fileName string) (*dTable, error) { if err != nil { return nil, fmt.Errorf("DecisionTable worksheet not available in %s", fileName) } + // find titleRowIndex titleRowIndex := 0 for i, r := range rows { diff --git a/examples/flogo/creditcard-dt/README.md b/examples/flogo/creditcard-dt/README.md index 7f18ca6..52015ad 100644 --- a/examples/flogo/creditcard-dt/README.md +++ b/examples/flogo/creditcard-dt/README.md @@ -11,14 +11,18 @@ Once you have the `flogo.json` file, you are ready to build your Flogo App ### Steps +Note: Store implementation can be configured via given `rsconfig.json` file. Start redis-server and use `export STORECONFIG=` before running binary.
      + ```sh cd $GOPATH/src/github.com/project-flogo/rules/examples/flogo/creditcard-dt flogo create -f flogo.json cd creditcard-dt flogo build cd bin +cp ../../creditcard-dt-file.xlsx . ./creditcard-dt ``` + ### Testing #### #1 Invoke applicant decision table diff --git a/examples/flogo/creditcard-dt/flogo.json b/examples/flogo/creditcard-dt/flogo.json index 4cac045..d536953 100644 --- a/examples/flogo/creditcard-dt/flogo.json +++ b/examples/flogo/creditcard-dt/flogo.json @@ -1,5 +1,5 @@ { - "name": "decisiontable", + "name": "creditcard-dt", "type": "flogo:app", "version": "0.0.1", "description": "Sample Rules App", diff --git a/examples/flogo/creditcard-dt/rsconfig.json b/examples/flogo/creditcard-dt/rsconfig.json new file mode 100644 index 0000000..31cb504 --- /dev/null +++ b/examples/flogo/creditcard-dt/rsconfig.json @@ -0,0 +1,36 @@ +{ + "mode": "consistency", + "rs": { + "prefix": "x", + "store-ref": "redis" + }, + "rete": { + "jt-ref": "redis", + "idgen-ref": "redis", + "jt":"redis" + }, + "stores": { + "mem": { + }, + "redis": { + "network": "tcp", + "address": ":6379" + } + }, + "idgens": { + "mem": { + }, + "redis": { + "network": "tcp", + "address": ":6379" + } + }, + "jts": { + "mem": { + }, + "redis": { + "network": "tcp", + "address": ":6379" + } + } +} diff --git a/examples/flogo/dtable/README.md b/examples/flogo/dtable/README.md index 96b89b9..d49708d 100644 --- a/examples/flogo/dtable/README.md +++ b/examples/flogo/dtable/README.md @@ -14,15 +14,16 @@ Once you have the `flogo.json` file, you are ready to build your Flogo App ```sh cd $GOPATH/src/github.com/project-flogo/rules/examples/flogo/dtable flogo create -f flogo.json -cd dtable +cd decisiontable flogo build +cp ../dtable-file.xlsx . cd bin ``` #### With mem store ```sh -./dtable +./decisiontable ``` #### With redis store @@ -52,50 +53,4 @@ You should see following output: ``` 2019-09-05T18:35:12.142+0530 INFO [flogo.rules] - Student: s1 -- Comments: additional study hours required 2019-09-05T18:35:12.142+0530 INFO [flogo.rules] - Student: s2 -- Comments: little care can be taken to achieve grade-a -``` - -### Writing Decision Table in JSON - -Sample usage can be as below. -```json - { - "name": "AnalyseStudent", - "description": "Analysing student data", - "type": "activity", - "ref": "github.com/project-flogo/rules/activity/dtable", - "settings": { - "make": [ - { - "condition": [ - {"tuple": "student","field": "grade","expr": "== 'GRADE-C'"}, - {"tuple": "student","field": "class","expr": "== 'X-A'"} - ], - "action": [ - { "tuple": "student","field": "careRequired","value": true}, - { "tuple": "student","field": "comments","value": "additional study hours required"} - ] - }, - { - "condition": [ - {"tuple": "student","field": "grade","expr": "== 'GRADE-A'"}, - {"tuple": "student","field": "class","expr": "== 'X-A'"} - ], - "action": [ - {"tuple": "student","field": "careRequired","value": false} - ] - }, - { - "condition": [ - {"tuple": "student","field": "grade","expr": "== 'GRADE-B'"}, - {"tuple": "student","field": "class","expr": "== 'X-A'"} - ], - "action": [ - {"tuple": "student","field": "careRequired","value": true}, - {"tuple": "student","field": "comments","value": "little care can be taken to achieve grade-a "} - ] - } - ] - } - } -``` -Decision table will have condition and action included into decition table activity. +``` \ No newline at end of file diff --git a/examples/flogo/dtable/dtable-file.xlsx b/examples/flogo/dtable/dtable-file.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..a9314e1df0bf640402bbbe99760f0a82a3d08bc7 GIT binary patch literal 5600 zcmaJ_1yq#nx+SCq>F(}S8cFHy?jfX08bxA|W<&&}OUXgHd*~rVI+X72k~{qWxkrxo zo`26;GvBQBJ?}T~6MOc%)s+w*;lrV#qQdQ{hN!_k00!*Z%pKt9!Nqz1UJ4pkMC8E+ z?gvK3`+L_A$XHi4M~r-u4}6n6_{%~dg`31H&_4v#urMDbP9f+RDj=OYk85)pnI;!0 z>r!OM79D#-?4O&7b-qp3?4Spw8`4#ze`3{N^u5dRgc{@wUqQw_WvtPmZz{ZGo=;v6 zw4S&24|OszL%g%zIEyGeTd-TWj?o34hkh(C+?EA;ub3jp3XLx5<-~KGGs|-EHQ}fY z%V_Cjk*YR2a(Q3+tD-Vb7=8U2;g(*fT^XI+>LpqabWaz@TMsS!=y8YE$&K_9txHA_#D>@Bb|^IIugcU98mIU0gl5tX$jyoIXyDPMRia zoz(b&=Xy_@Y7;q0e@CPbL9b`_1=8n+mzHZk{7^8pbVcEC>ma&eo{RD7avrGhJ96p3KCND$SQh zBC>k4v(&Z)SPhV8zE#I?ol{)y>_<6_My<1HmhBhYb&P}Im??BoQ00VVYyX z?RI23Y{A^gnsa7lFvr2_tuPr;AEgwxA^Kp-LG!QZgQLdKnsHXW$|(l{XlG9W#f+C5 zANLAViv+F7cvO!9nGS#SLI-j4T6Zh_eWqwhlTCMEc?f`I>%aFdChQI$M=lRfUq^t) zeVTR+Mpb9I@dJP8=?6{C${Wrg(4kp6rZcMGy`@!2Ju}B;`s%tf_$qsG+qOp`waPOj z_8sji%h{pWc?M-q?5ZDARPemS-R0T9xGpQ>#)Aw7vxoT3tyoe<{6{oAtKRiV8fMLL&%jU)s#)o-d0U} zjDw6A2d%U(phvw-W1+$#L!IjRDDUton!0>*$R{s9p!+d~O!l$)K^@u6*<@Pr@-$?e zX;ydvPFbzhR5wClN4p<^Yt50x6@dfW=ZJESwg{skA1l#fR-B+qF-k+$&Z3={Q8^)g zhF^&-YD#;SagOGynnK+&7CKe=sS=cVQ|rFkIVhZ6b!>qs<#3$L-^*u2o+R8=bn0AVr%)?>t3AuTyC4Bd*Xn>^1821x-F{oR6Z4M zWr==Rl|?~6Tn}LwNl#FF6p;rHHO!H)bW=4eprE~5rX#1Kobr7k^^Q&V=RA@L{?(Yr623kmxrWw?v?@+7 zgrj^QEZ&=A=0=&U>BX|ui#1xO4hMc+kNtVbx*VHp{CJv^O_jL{h$>@}M}Kh!vdD#` zYx2#oJnJ;k^f>9TOglTAr(82yzv!#!xp>i3)>OJqmET&!7uZuXI_Bu#g7$j6EXIGb z#&!3dQkC~Ez<9Q_I5P5zd9u6NA(VFHG6l zZkNvqX1td%85Q*P=n3Qw3fCI>9nUQ`15HI9ON{ghHlLt}NzvJH^90DLl+Y==4kX2YMUPA` zr|H5rs$XN6B-Np%7vJ+~jSsBwssk`|GhVy{?jG=I5r4|^qNil#8v=ph=Gey_krjCY zH<{JklO@Hgws{N3UsS zYc%3SE1QpR*tN6Ocye(}7Sh!AF4)ma?B0xxI?i41N$jvjPqU}V4N7N-RmDV3(`Py4 za}%22_H-12=+s*#a^Btu5Y(rfnyJ-?J5v)mMQw(AMxr%v$-glo%8Q^|#Nxw{d{fUw zD>qlEF)kD%NlhH7b|RSoD*s@Ub(4pYqmMUB@H%~TeBq6Zsv%GkyQa#uJC1Pvyg^-a z>=K+0qU9SYO_*g~qlk`}jyoT-OC2m2r8WfSN)j)qB2&pO_fa>iXl+y2F@=^voZV-nas`=XGSPf)SmjcMw9nJW9%g|DhjLtQp@N^fehFD$Yg zKX(P=mq6SJ1w$H|Y#t(|n!DD^jQJD4$?q?tf=j$po|CCnN!bS2&kK*4CGE?(RMR5s zN$pbD_{HUckBL}tiX(*kJt1?Z3}`9nWLaNbMj_&CRHQ#INGH&$;j{D56FvzGWh!&N zM=lxbzZh~W;Nq;>N}Kl-6bbV2C_GMd%%Ds&*1%xAiq%E!B=q`qO6;2In!(t-M>Qjq zN{Zn6{XJ?OVwp^by_2LJZ&%Tyg{ZE+L6Hy+EvdN=8~QSsqYD9sLGfXlj=9ivi8je* zCbogg*U3hn#96wZ{R|Qf+{1 z%Ae~m!j)t(8fd%KIJKgpFwrhqv49{r>`s}veqLvO%x;`?1vc>^O!XsIKQXmv3Q5cGoi_^v1XzX11FYX7bEjgv#A>VgNut+7<3am>8Q2wb)NdNAVzXkO} ziuqeyC#c+umGJ#r0@-tj0*Y0%U~{x2`1S(6TVrXYiyt&meHX+@W`qvd8F@c4dE6p@ zy7e?>E22yKF>+DPqp)_!E7a!T+r8aI5?yG{!cRMfxN<7o;wHSQH$)0thLy*H2Aq~1 z@ggR9eTu4ulYI>#yBb^rM+#$U@K6G+QX|AFXEyalkB+-?t4T~Ovt0j7?p{nZs5@l> z->7DsF+hst$YPQT;8>oS0AW8~H9X;`FWdN}c^)dnlq9e)-;w!ZIRcnvN5vme;@8XxoJMqCyf$k)%KTI0LKXC;g||TMQOI6q5eM zY;)R2s&aO=;ztqduUI@e>XbCmA{kiG6FxiTBV41LQz6xys`duG?|57M$k_Yfwm7ZK zFf1ePAcj1TefJA<5|NZ?n8Jk@gtd=7w1ogaY+TmDDBdXU%R8G*UukK+$)dXiC0gWb z2D~!h@cdE<;P7DFx%#K@O&iH9bk`c;h8cNGa3^eO;D`im)OT(nRPUFFS?tg^PB{C- zAQ?Gr*QUpIcFT`c-VPz%S$6A`uC_}3K+vt$Z=bOD`$95Z8~n_ns?#7`k*;)Qt}|0q z@fXp@BNymG7b#jbnE;$}fds0jks>(%L^<)WE6Q1oq$;r`_?rjQ*u`0*g%SxVv$Xfp zb?xV9!~R~0P2e5IYE^!;ibIMrXqJzW!^+Y!nvuFrjA29Iox=h{_8fz1rTA)ri6k-+ zpQ0+^2mj)1=Nk9nSI-#|xk*o2$;m)wO7ZI)8(r|mO1jStjBR_uL_A_?+X&snWfE~0 zO=y!Sq*fqf8y<&pLy$>#_`p1(;4nHA!v3}nLTV(2mMVg1dp20-f3B0 z?$_A*U#yGiXc=a)Ms17L-a)6k>1@dyf>k~^ddcKilNC>IX_Z-jicWLNap9RHCn3J2 zN1O?VlMowtOuYLPaZ8mCs#&wngvVIdRRYZ5sy^zT=og`yF6Au(6dLH30`!xpwY}7B zOMyuPB_Urcu}-#P%Cz^VfQ%6*QKfoHVbGxxSR}F? zwS*a?=C-Voa;I`Lioe86;np*aYyQe*Har&VC*FiRJEfOLv@r?v!zAI}`r5j~b2|`X zpX7%l81kc&0K(v3WL-QyL&NOXwQjn8Hn*P1F}s~m)WeyXZj{eh|%Gb z8!Ya=mZo9bP`Y3JI?AqK)u(A_-~X~XgeeRY@z3xAzkz|bQ76^{%^Nkf@H5~*wr@3| zvrMdX{k??&3)x5SwjtXQmYg3QUk~p_s>{;Yh*lyLfT;#{dax>6nwW~WMCkWoo@utEg5bB0D{Ap6-mmtX;xc< zY6fSUJ`4O&Bz+YHl0(I=2ZK~(aWVC;DM5$e+dDP(pudiWhmre)Qb=%c%l{~U@nAe` zB<IVB12nBLjn#P24Z z?UOJO6cvpCBlHc<2AgedcIZDjX}>RN_qF#_W%N|W9SOtH)D!YG63wr*=BEHel_F)D zmz@a}N|$>_U*;e~#GVm8l~r8Ee^Tc~P}!t^gg$lZ6VQpCi?|UVz9CsKjhGlP9=hTd z(nCSCN?*T!M9a!FHzP|ZYMu;X-X0gSyTMsBj0)NMW&*(f;1mnaKh0(9y0M+L&ux)-w7QJzTTum(;7_wN(qL*i#@-fYx<(fK^lyK zJ4Barx+#98XIoy2Rd?`ALLGBgSuMUb+9-&)l020{ zuc+?(XJK$X;vp${&|t_D^B>nT6!Xgqncc5NPv()Tko+e&-vpUE{dvWs{c8~wPjsGKc{#@l@ hXWWnGZydmC`u_~7x)KsBOK@;#u$MTjP*HzH{SU~*tmgm# literal 0 HcmV?d00001 diff --git a/examples/flogo/dtable/flogo.json b/examples/flogo/dtable/flogo.json index 49e140c..aa5e1e2 100644 --- a/examples/flogo/dtable/flogo.json +++ b/examples/flogo/dtable/flogo.json @@ -67,7 +67,7 @@ "expression": "$.student.careRequired" } ], - "actionService": { + "actionService": { "service": "LogCareRequiredStudents", "input": { "message": "=string.concat(\" Student: \",$.student.name, \" -- Comments: \",$.student.comments)" @@ -82,7 +82,7 @@ "expression": "$.studentanalysis.name == $.student.name" } ], - "actionService": { + "actionService": { "service": "AnalyseStudent", "input": { "message": "=$.studentanalysis.name" @@ -98,37 +98,7 @@ "type": "activity", "ref": "github.com/project-flogo/rules/activity/dtable", "settings": { - "make": [ - { - "condition": [ - {"tuple": "student","field": "grade","expr": "== 'GRADE-C'"}, - {"tuple": "student","field": "class","expr": "== 'X-A'"} - ], - "action": [ - { "tuple": "student","field": "careRequired","value": true}, - { "tuple": "student","field": "comments","value": "additional study hours required"} - ] - }, - { - "condition": [ - {"tuple": "student","field": "grade","expr": "== 'GRADE-A'"}, - {"tuple": "student","field": "class","expr": "== 'X-A'"} - ], - "action": [ - {"tuple": "student","field": "careRequired","value": false} - ] - }, - { - "condition": [ - {"tuple": "student","field": "grade","expr": "== 'GRADE-B'"}, - {"tuple": "student","field": "class","expr": "== 'X-A'"} - ], - "action": [ - {"tuple": "student","field": "careRequired","value": true}, - {"tuple": "student","field": "comments","value": "little care can be taken to achieve grade-a "} - ] - } - ] + "dTableFile":"../dtable-file.xlsx" } }, { diff --git a/go.sum b/go.sum index d76ea43..8e1b413 100644 --- a/go.sum +++ b/go.sum @@ -111,4 +111,4 @@ gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuv gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU= gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= \ No newline at end of file From 97a95d036e0c7b310f990f1347ffbca88bb06a44 Mon Sep 17 00:00:00 2001 From: aashish2001 <51983668+aashish2001@users.noreply.github.com> Date: Wed, 11 Dec 2019 14:37:16 +0530 Subject: [PATCH 112/125] Update README.md (#86) --- examples/flogo/creditcard-dt/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/flogo/creditcard-dt/README.md b/examples/flogo/creditcard-dt/README.md index 52015ad..a690378 100644 --- a/examples/flogo/creditcard-dt/README.md +++ b/examples/flogo/creditcard-dt/README.md @@ -22,7 +22,7 @@ cd bin cp ../../creditcard-dt-file.xlsx . ./creditcard-dt ``` - +### Note - To execute following commands on windows platform the backward slashes need to be omitted . ### Testing #### #1 Invoke applicant decision table @@ -48,4 +48,4 @@ You should see following output: 2019-09-24T12:54:08.683+0530 INFO [flogo.rules] - Applicant: PrakashY -- CreditLimit: 7500 -- status: Pending 2019-09-24T12:54:08.696+0530 INFO [flogo.rules] - Applicant: SandraW -- CreditLimit: 0 -- status: Loan-Rejected 2019-09-24T12:54:09.884+0530 INFO [flogo.rules] - Applicant: JaneDoe -- CreditLimit: 25000 -- status: Platinum-Status -``` \ No newline at end of file +``` From eb1f2352d790eee72baec099a610febc035e359e Mon Sep 17 00:00:00 2001 From: nthota Date: Thu, 12 Dec 2019 19:31:59 +0530 Subject: [PATCH 113/125] removed redundant examples --- activity/dtable/README.md | 2 +- examples/flogo/creditcard-dt/README.md | 51 --- examples/flogo/creditcard-dt/flogo.json | 200 ----------- examples/flogo/creditcard-dt/main.go | 67 ---- examples/flogo/creditcard/README.md | 79 ++--- .../creditcard-file.xlsx} | Bin examples/flogo/creditcard/creditcard_test.go | 334 ------------------ examples/flogo/creditcard/flogo.json | 242 ++++--------- examples/flogo/creditcard/functions.go | 177 ---------- .../{creditcard-dt => creditcard}/imports.go | 0 examples/flogo/creditcard/main.go | 21 +- .../rsconfig.json | 0 12 files changed, 108 insertions(+), 1065 deletions(-) delete mode 100644 examples/flogo/creditcard-dt/README.md delete mode 100644 examples/flogo/creditcard-dt/flogo.json delete mode 100644 examples/flogo/creditcard-dt/main.go rename examples/flogo/{creditcard-dt/creditcard-dt-file.xlsx => creditcard/creditcard-file.xlsx} (100%) delete mode 100644 examples/flogo/creditcard/creditcard_test.go delete mode 100644 examples/flogo/creditcard/functions.go rename examples/flogo/{creditcard-dt => creditcard}/imports.go (100%) rename examples/flogo/{creditcard-dt => creditcard}/rsconfig.json (100%) diff --git a/activity/dtable/README.md b/activity/dtable/README.md index 6ffda60..f2b40b8 100644 --- a/activity/dtable/README.md +++ b/activity/dtable/README.md @@ -29,7 +29,7 @@ A sample `decision table` definition is: "type": "activity", "ref": "github.com/project-flogo/rules/activity/dtable", "settings": { - "dTableFile":"creditcard-dt-file.xlsx" + "dTableFile":"creditcard-file.xlsx" } } diff --git a/examples/flogo/creditcard-dt/README.md b/examples/flogo/creditcard-dt/README.md deleted file mode 100644 index a690378..0000000 --- a/examples/flogo/creditcard-dt/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# Decision Table Usage - -This example demonstrates how to use decision table activity with credit card application example. - -## Setup and build -Once you have the `flogo.json` file, you are ready to build your Flogo App - -### Pre-requisites -* Go 1.11 -* Download and build the Flogo CLI 'flogo' and add it to your system PATH - -### Steps - -Note: Store implementation can be configured via given `rsconfig.json` file. Start redis-server and use `export STORECONFIG=` before running binary.
      - -```sh -cd $GOPATH/src/github.com/project-flogo/rules/examples/flogo/creditcard-dt -flogo create -f flogo.json -cd creditcard-dt -flogo build -cd bin -cp ../../creditcard-dt-file.xlsx . -./creditcard-dt -``` -### Note - To execute following commands on windows platform the backward slashes need to be omitted . -### Testing - -#### #1 Invoke applicant decision table - -Store aplicants information. -```sh -curl localhost:7777/test/applicant?name=JohnDoe\&gender=Male\&age=20\&address=BoltonUK\&hasDL=false\&ssn=1231231234\&income=45000\&maritalStatus=single\&creditScore=500 -curl localhost:7777/test/applicant?name=JaneDoe\&gender=Female\&age=38\&address=BoltonUK\&hasDL=false\&ssn=2424354532\&income=32000\&maritalStatus=single\&creditScore=650 -curl localhost:7777/test/applicant?name=PrakashY\&gender=Male\&age=30\&address=RedwoodShore\&hasDL=true\&ssn=2345342132\&income=150000\&maritalStatus=married\&creditScore=750 -curl localhost:7777/test/applicant?name=SandraW\&gender=Female\&age=26\&address=RedwoodShore\&hasDL=true\&ssn=3213214321\&income=50000\&maritalStatus=single\&creditScore=625 -``` - -Send a process application event. -```sh -curl localhost:7777/test/processapplication?start=true\&ssn=1231231234 -curl localhost:7777/test/processapplication?start=true\&ssn=2345342132 -curl localhost:7777/test/processapplication?start=true\&ssn=3213214321 -curl localhost:7777/test/processapplication?start=true\&ssn=2424354532 -``` -You should see following output: -``` -2019-09-24T12:54:08.674+0530 INFO [flogo.rules] - Applicant: JohnDoe -- CreditLimit: 2500 -- status: VISA-Granted -2019-09-24T12:54:08.683+0530 INFO [flogo.rules] - Applicant: PrakashY -- CreditLimit: 7500 -- status: Pending -2019-09-24T12:54:08.696+0530 INFO [flogo.rules] - Applicant: SandraW -- CreditLimit: 0 -- status: Loan-Rejected -2019-09-24T12:54:09.884+0530 INFO [flogo.rules] - Applicant: JaneDoe -- CreditLimit: 25000 -- status: Platinum-Status -``` diff --git a/examples/flogo/creditcard-dt/flogo.json b/examples/flogo/creditcard-dt/flogo.json deleted file mode 100644 index d536953..0000000 --- a/examples/flogo/creditcard-dt/flogo.json +++ /dev/null @@ -1,200 +0,0 @@ -{ - "name": "creditcard-dt", - "type": "flogo:app", - "version": "0.0.1", - "description": "Sample Rules App", - "appModel": "1.0.0", - "imports": [ - "github.com/project-flogo/contrib/trigger/rest", - "github.com/project-flogo/contrib/activity/log", - "github.com/project-flogo/rules/ruleaction", - "github.com/project-flogo/rules/activity/dtable", - "github.com/project-flogo/contrib/function/string" - ], - "triggers": [ - { - "id": "receive_http_message", - "ref": "github.com/project-flogo/contrib/trigger/rest", - "settings": { - "port": "7777" - }, - "handlers": [ - { - "settings": { - "method": "GET", - "path": "/test/:tupleType" - }, - "actions": [ - { - "id": "simple_rule", - "input": { - "tupletype": "=$.pathParams.tupleType", - "values": "=$.queryParams" - } - } - ] - } - ] - } - ], - "resources": [ - { - "id": "rulesession:simple", - "data": { - "metadata": { - "input": [ - { - "name": "values", - "type": "string" - }, - { - "name": "tupletype", - "type": "string" - } - ], - "output": [ - { - "name": "outputData", - "type": "any" - } - ] - }, - "rules": [ - { - "name": "Process Application with applicant simple dt", - "conditions": [ - { - "expression": "$.processapplication.start == true " - }, - { - "expression": "$.processapplication.ssn == $.applicant.ssn" - } - ], - "actionService": { - "service": "ApplicantSimple", - "input": { - "message": "test ApplicantSimple" - } - }, - "priority": 1 - }, - { - "name": "Display Information", - "conditions": [ - { - "expression": "$.applicant.creditLimit >= 0 " - }, - { - "expression": "$.applicant.status != nil " - } - ], - "actionService": { - "service": "LogInformation", - "input": { - "message": "=string.concat(\" Applicant: \",$.applicant.name, \" -- CreditLimit: \",$.applicant.creditLimit, \" -- status: \",$.applicant.status)" - } - }, - "priority": 3 - } - ], - "services": [ - { - "name": "ApplicantSimple", - "description": "Simple Applicants approval dt", - "type": "activity", - "ref": "github.com/project-flogo/rules/activity/dtable", - "settings": { - "dTableFile":"creditcard-dt-file.xlsx" - } - }, - { - "name": "LogInformation", - "description": "Logs Applicant Information", - "type": "activity", - "ref": "github.com/project-flogo/contrib/activity/log" - } - ] - } - } - ], - "actions": [ - { - "ref": "github.com/project-flogo/rules/ruleaction", - "settings": { - "ruleSessionURI": "res://rulesession:simple", - "tds": [ - { - "name": "applicant", - "properties": [ - { - "name": "name", - "pk-index": 0, - "type": "string" - }, - { - "name": "gender", - "type": "string" - }, - { - "name": "age", - "type": "int" - }, - { - "name": "address", - "type": "string" - }, - { - "name": "hasDL", - "type": "bool" - }, - { - "name": "ssn", - "type": "long" - }, - { - "name": "income", - "type": "double" - }, - { - "name": "maritalStatus", - "type": "string" - }, - { - "name": "creditScore", - "type": "int" - }, - { - "name": "status", - "type": "string" - }, - { - "name": "eligible", - "type": "bool" - }, - { - "name": "creditLimit", - "type": "double" - } - ] - }, - { - "name": "processapplication", - "ttl":0, - "properties": [ - { - "name": "ssn", - "pk-index": 0, - "type": "long" - }, - { - "name": "start", - "type": "bool" - } - ] - } - ] - }, - "id": "simple_rule" - } - ] -} \ No newline at end of file diff --git a/examples/flogo/creditcard-dt/main.go b/examples/flogo/creditcard-dt/main.go deleted file mode 100644 index 234f2eb..0000000 --- a/examples/flogo/creditcard-dt/main.go +++ /dev/null @@ -1,67 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "os" - "runtime" - "runtime/pprof" - - _ "github.com/project-flogo/core/data/expression/script" - "github.com/project-flogo/core/engine" -) - -var ( - cpuProfile = flag.String("cpuprofile", "", "Writes CPU profile to the specified file") - memProfile = flag.String("memprofile", "", "Writes memory profile to the specified file") - cfgJson string - cfgCompressed bool -) - -func main() { - - flag.Parse() - if *cpuProfile != "" { - f, err := os.Create(*cpuProfile) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to create CPU profiling file: %v\n", err) - os.Exit(1) - } - if err = pprof.StartCPUProfile(f); err != nil { - fmt.Fprintf(os.Stderr, "Failed to start CPU profiling: %v\n", err) - os.Exit(1) - } - defer pprof.StopCPUProfile() - } - - cfg, err := engine.LoadAppConfig(cfgJson, cfgCompressed) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to create engine: %v\n", err) - os.Exit(1) - } - - e, err := engine.New(cfg) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to create engine: %v\n", err) - os.Exit(1) - } - - code := engine.RunEngine(e) - - if *memProfile != "" { - f, err := os.Create(*memProfile) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to create memory profiling file: %v\n", err) - os.Exit(1) - } - - runtime.GC() // get up-to-date statistics - if err := pprof.WriteHeapProfile(f); err != nil { - fmt.Fprintf(os.Stderr, "Failed to write memory profiling data: %v", err) - os.Exit(1) - } - _ = f.Close() - } - - os.Exit(code) -} diff --git a/examples/flogo/creditcard/README.md b/examples/flogo/creditcard/README.md index 8486172..5105d10 100644 --- a/examples/flogo/creditcard/README.md +++ b/examples/flogo/creditcard/README.md @@ -1,60 +1,51 @@ -## Flogo Rules based Creditcard application +# Decision Table Usage +This example demonstrates how to use decision table activity with credit card application example. -This example demonstrates rule based processing of credit card application. In this example three tuples are used, tuples description is given below. +## Setup and build +Once you have the `flogo.json` file, you are ready to build your Flogo App +### Pre-requisites +* Go 1.11 +* Download and build the Flogo CLI 'flogo' and add it to your system PATH -* `UserAccount` tuple is always stored in network, while the other tuples `NewAccount` and `UpdateCreditScore` are removed after usage as ttl is given as 0. +### Steps +Note: Store implementation can be configured via given `rsconfig.json` file. Start redis-server and use `export STORECONFIG=` before running binary.
      -## Usage - -Get the repo and in this example main.go, functions.go both are available. We can directly build and run the app or create flogo rule app and run it. - -#### Conditions - -``` -cBadUser : Check for new user input data - check if age <18 and >=45, empty address and salary less than 10k -cNewUser : Check for new user input data - check if age >=18 and <= 44, address and salary >= 10k -cUserIdMatch : Check for id match from 'UserAccount' and 'UpdateCreditScore' tuples -cUserCreditScore : Check for CreditScore >= 750 && < 820 -cUserLowCreditScore : Check for CreditScore < 750 -cUserHighCreditScore : Check for CreditScore >= 820 && <= 900 -``` -#### Actions -``` -aBadUser : Executes when age - < 18 and >=45, address empty, salary less than 10k -aNewUser : Add the newuser info to userAccount tuple -aApproveWithLowerLimit : Provides credit card application status approved with lower credit limit -aApproveWithHigherLimit : Provides credit card application status approved with higher credit limit -aUserReject : Rejects when lower Credit score provided and retracts NewAccount -``` -### Direct build and run -``` +```sh cd $GOPATH/src/github.com/project-flogo/rules/examples/flogo/creditcard -go build -./creditcard -``` -### Create app using flogo cli -``` -cd $GOPATH/src/github.com/project-flogo/rules/examples/flogo/creditcard -flogo create -f flogo.json creditcard -cp functions.go creditcard/src +flogo create -f flogo.json cd creditcard flogo build -./bin/creditcard +cd bin +cp ../../creditcard-file.xlsx . +./creditcard ``` -* Input new user details +### Testing +#### #1 Invoke applicant decision table + +Store aplicants information. +```sh +curl localhost:7777/test/applicant?name=JohnDoe\&gender=Male\&age=20\&address=BoltonUK\&hasDL=false\&ssn=1231231234\&income=45000\&maritalStatus=single\&creditScore=500 +curl localhost:7777/test/applicant?name=JaneDoe\&gender=Female\&age=38\&address=BoltonUK\&hasDL=false\&ssn=2424354532\&income=32000\&maritalStatus=single\&creditScore=650 +curl localhost:7777/test/applicant?name=PrakashY\&gender=Male\&age=30\&address=RedwoodShore\&hasDL=true\&ssn=2345342132\&income=150000\&maritalStatus=married\&creditScore=750 +curl localhost:7777/test/applicant?name=SandraW\&gender=Female\&age=26\&address=RedwoodShore\&hasDL=true\&ssn=3213214321\&income=50000\&maritalStatus=single\&creditScore=625 ``` -$ curl -X PUT http://localhost:7777/newaccount -H 'Content-Type: application/json' -d '{"Name":"Test","Age":"26","Income":"60100","Address":"TEt","Id":"12312","Gender":"male","maritalStatus":"single"}' -``` -* Update credit score details of the user +Send a process application event. +```sh +curl localhost:7777/test/processapplication?start=true\&ssn=1231231234 +curl localhost:7777/test/processapplication?start=true\&ssn=2345342132 +curl localhost:7777/test/processapplication?start=true\&ssn=3213214321 +curl localhost:7777/test/processapplication?start=true\&ssn=2424354532 ``` -$ curl -X PUT http://localhost:7777/credit -H 'Content-Type: application/json' -d '{"Id":12312,"creditScore":680}' +You should see following output: +``` +2019-09-24T12:54:08.674+0530 INFO [flogo.rules] - Applicant: JohnDoe -- CreditLimit: 2500 -- status: VISA-Granted +2019-09-24T12:54:08.683+0530 INFO [flogo.rules] - Applicant: PrakashY -- CreditLimit: 7500 -- status: Pending +2019-09-24T12:54:08.696+0530 INFO [flogo.rules] - Applicant: SandraW -- CreditLimit: 0 -- status: Loan-Rejected +2019-09-24T12:54:09.884+0530 INFO [flogo.rules] - Applicant: JaneDoe -- CreditLimit: 25000 -- status: Platinum-Status ``` - -* Application status will be printed on the console - \ No newline at end of file diff --git a/examples/flogo/creditcard-dt/creditcard-dt-file.xlsx b/examples/flogo/creditcard/creditcard-file.xlsx similarity index 100% rename from examples/flogo/creditcard-dt/creditcard-dt-file.xlsx rename to examples/flogo/creditcard/creditcard-file.xlsx diff --git a/examples/flogo/creditcard/creditcard_test.go b/examples/flogo/creditcard/creditcard_test.go deleted file mode 100644 index 8978961..0000000 --- a/examples/flogo/creditcard/creditcard_test.go +++ /dev/null @@ -1,334 +0,0 @@ -package main - -import ( - "bytes" - "encoding/json" - "io/ioutil" - "net/http" - "path/filepath" - "strings" - "testing" - - "github.com/project-flogo/core/engine" - "github.com/project-flogo/rules/ruleapi/tests" - "github.com/stretchr/testify/assert" -) - -// Response is a reply form the server -type Response struct { - Status string `json:"status"` - Error string `json:"error"` -} - -var ( - payload = `{ - "Name": "Sam", - "Age": "26", - "Income": "50100", - "Address": "SFO", - "Id": "4" - }` - payload1 = `{ - "Id": "4", - "creditScore": "850" - }` - payload2 = `{ - "Name": "Sam1", - "Age": "17", - "Income": "50100", - "Address": "SFO", - "Id": "5" - }` - payload3 = `{ - "Name": "Sam2", - "Age": "26", - "Income": "5100", - "Address": "SFO", - "Id": "6" - }` - payload4 = `{ - "Name": "Sam3", - "Age": "26", - "Income": "5100", - "Address": "", - "Id": "7" - }` - payload5 = `{ - "Name": "Sam5", - "Age": "32", - "Income": "75000", - "Address": "SFO", - "Id": "8" - }` - payload6 = `{ - "Id": "8", - "creditScore": "760" - }` - payload7 = `{ - "Name": "Sam4", - "Age": "41", - "Income": "30000", - "Address": "SFO", - "Id": "9" - }` - payload8 = `{ - "Id": "9", - "creditScore": "720" - }` -) - -func testapplication(t *testing.T, e engine.Engine) { - tests.Drain("7777") - err := e.Start() - assert.Nil(t, err) - defer func() { - err := e.Stop() - assert.Nil(t, err) - }() - tests.Pour("7777") - transport := &http.Transport{ - MaxIdleConns: 1, - } - defer transport.CloseIdleConnections() - client := &http.Client{ - Transport: transport, - } - - payload, err := json.Marshal(payload) - if err != nil { - panic(err) - } - - payload1, err := json.Marshal(payload1) - if err != nil { - panic(err) - } - - payload2, err := json.Marshal(payload2) - if err != nil { - panic(err) - } - - payload3, err := json.Marshal(payload3) - if err != nil { - panic(err) - } - - payload4, err := json.Marshal(payload4) - if err != nil { - panic(err) - } - - payload5, err := json.Marshal(payload5) - if err != nil { - panic(err) - } - - payload6, err := json.Marshal(payload6) - if err != nil { - panic(err) - } - - payload7, err := json.Marshal(payload7) - if err != nil { - panic(err) - } - - payload8, err := json.Marshal(payload8) - if err != nil { - panic(err) - } - - // valid new user details - request := func() { - req, err := http.NewRequest(http.MethodPut, "http://localhost:7777/newaccount", bytes.NewBuffer(payload)) - assert.Nil(t, err) - req.Header.Set("Content-Type", "application/json") - response, err := client.Do(req) - assert.Nil(t, err) - _, err = ioutil.ReadAll(response.Body) - assert.Nil(t, err) - response.Body.Close() - assert.Nil(t, err) - } - outpt := tests.CaptureStdOutput(request) - var result string - if strings.Contains(outpt, "Rule fired") { - result = "success" - } - assert.Equal(t, "success", result) - outpt = "" - result = "" - - // user with credit score > 850 - request1 := func() { - req, err := http.NewRequest(http.MethodPut, "http://localhost:7777/credit", bytes.NewBuffer(payload1)) - assert.Nil(t, err) - req.Header.Set("Content-Type", "application/json") - response, err := client.Do(req) - assert.Nil(t, err) - _, err = ioutil.ReadAll(response.Body) - assert.Nil(t, err) - response.Body.Close() - assert.Nil(t, err) - } - outpt = tests.CaptureStdOutput(request1) - if strings.Contains(outpt, "Rule fired") { - result = "success" - } - assert.Equal(t, "success", result) - outpt = "" - result = "" - - // New user with age < 17 - request2 := func() { - req, err := http.NewRequest(http.MethodPut, "http://localhost:7777/newaccount", bytes.NewBuffer(payload2)) - assert.Nil(t, err) - req.Header.Set("Content-Type", "application/json") - response, err := client.Do(req) - assert.Nil(t, err) - _, err = ioutil.ReadAll(response.Body) - assert.Nil(t, err) - response.Body.Close() - assert.Nil(t, err) - } - outpt = tests.CaptureStdOutput(request2) - if strings.Contains(outpt, "Applicant is not eligible to apply for creditcard") { - result = "success" - } - assert.Equal(t, "success", result) - outpt = "" - result = "" - - // New user with income < 10k - request3 := func() { - req, err := http.NewRequest(http.MethodPut, "http://localhost:7777/newaccount", bytes.NewBuffer(payload3)) - assert.Nil(t, err) - req.Header.Set("Content-Type", "application/json") - response, err := client.Do(req) - assert.Nil(t, err) - _, err = ioutil.ReadAll(response.Body) - assert.Nil(t, err) - response.Body.Close() - assert.Nil(t, err) - } - outpt = tests.CaptureStdOutput(request3) - if strings.Contains(outpt, "Applicant is not eligible to apply for creditcard") { - result = "success" - } - assert.Equal(t, "success", result) - outpt = "" - result = "" - - // New user with empty address - request4 := func() { - req, err := http.NewRequest(http.MethodPut, "http://localhost:7777/newaccount", bytes.NewBuffer(payload4)) - assert.Nil(t, err) - req.Header.Set("Content-Type", "application/json") - response, err := client.Do(req) - assert.Nil(t, err) - _, err = ioutil.ReadAll(response.Body) - assert.Nil(t, err) - response.Body.Close() - assert.Nil(t, err) - } - outpt = tests.CaptureStdOutput(request4) - if strings.Contains(outpt, "Applicant is not eligible to apply for creditcard") { - result = "success" - } - assert.Equal(t, "success", result) - outpt = "" - result = "" - - request5 := func() { - req, err := http.NewRequest(http.MethodPut, "http://localhost:7777/newaccount", bytes.NewBuffer(payload5)) - assert.Nil(t, err) - req.Header.Set("Content-Type", "application/json") - response, err := client.Do(req) - assert.Nil(t, err) - _, err = ioutil.ReadAll(response.Body) - assert.Nil(t, err) - response.Body.Close() - assert.Nil(t, err) - } - outpt = tests.CaptureStdOutput(request5) - if strings.Contains(outpt, "Rule fired") { - result = "success" - } - assert.Equal(t, "success", result) - outpt = "" - result = "" - - // user with credit score 760 - request6 := func() { - req, err := http.NewRequest(http.MethodPut, "http://localhost:7777/credit", bytes.NewBuffer(payload6)) - assert.Nil(t, err) - req.Header.Set("Content-Type", "application/json") - response, err := client.Do(req) - assert.Nil(t, err) - _, err = ioutil.ReadAll(response.Body) - assert.Nil(t, err) - response.Body.Close() - assert.Nil(t, err) - } - outpt = tests.CaptureStdOutput(request6) - if strings.Contains(outpt, "Rule fired") { - result = "success" - } - assert.Equal(t, "success", result) - outpt = "" - result = "" - - request7 := func() { - req, err := http.NewRequest(http.MethodPut, "http://localhost:7777/newaccount", bytes.NewBuffer(payload7)) - assert.Nil(t, err) - req.Header.Set("Content-Type", "application/json") - response, err := client.Do(req) - assert.Nil(t, err) - _, err = ioutil.ReadAll(response.Body) - assert.Nil(t, err) - response.Body.Close() - assert.Nil(t, err) - } - outpt = tests.CaptureStdOutput(request7) - if strings.Contains(outpt, "Rule fired") { - result = "success" - } - assert.Equal(t, "success", result) - outpt = "" - result = "" - - // New user with credit score < 720 - request8 := func() { - req, err := http.NewRequest(http.MethodPut, "http://localhost:7777/credit", bytes.NewBuffer(payload8)) - assert.Nil(t, err) - req.Header.Set("Content-Type", "application/json") - response, err := client.Do(req) - assert.Nil(t, err) - _, err = ioutil.ReadAll(response.Body) - assert.Nil(t, err) - response.Body.Close() - assert.Nil(t, err) - } - outpt = tests.CaptureStdOutput(request8) - if strings.Contains(outpt, "Rule fired: Rejected") { - result = "success" - } - assert.Equal(t, "success", result) - outpt = "" - result = "" -} - -func TestCreditCardJSON(t *testing.T) { - if testing.Short() { - t.Skip("skipping Handler Routing JSON integration test in short mode") - } - - data, err := ioutil.ReadFile(filepath.FromSlash("./flogo.json")) - assert.Nil(t, err) - cfg, err := engine.LoadAppConfig(string(data), false) - assert.Nil(t, err) - e, err := engine.New(cfg) - assert.Nil(t, err) - testapplication(t, e) -} diff --git a/examples/flogo/creditcard/flogo.json b/examples/flogo/creditcard/flogo.json index dc60c01..cb4c1f5 100644 --- a/examples/flogo/creditcard/flogo.json +++ b/examples/flogo/creditcard/flogo.json @@ -1,13 +1,15 @@ { - "name": "cardapp", + "name": "creditcard", "type": "flogo:app", "version": "0.0.1", - "description": "Sample Flogo App", + "description": "Sample Rules App", "appModel": "1.0.0", "imports": [ "github.com/project-flogo/contrib/trigger/rest", + "github.com/project-flogo/contrib/activity/log", "github.com/project-flogo/rules/ruleaction", - "github.com/project-flogo/legacybridge" + "github.com/project-flogo/rules/activity/dtable", + "github.com/project-flogo/contrib/function/string" ], "triggers": [ { @@ -19,30 +21,15 @@ "handlers": [ { "settings": { - "method": "PUT", - "path": "/newaccount" + "method": "GET", + "path": "/test/:tupleType" }, "actions": [ { "id": "simple_rule", "input": { - "tupletype": "NewAccount", - "values": "=$.content" - } - } - ] - }, - { - "settings": { - "method": "PUT", - "path": "/credit" - }, - "actions": [ - { - "id": "simple_rule", - "input": { - "tupletype": "UpdateCreditScore", - "values": "=$.content" + "tupletype": "=$.pathParams.tupleType", + "values": "=$.queryParams" } } ] @@ -74,135 +61,57 @@ }, "rules": [ { - "name": "UserData", + "name": "Process Application with applicant simple dt", "conditions": [ { - "name": "cBadUser", - "identifiers": [ - "NewAccount" - ], - "evaluator": "cBadUser" - } - ], - "actionService": { - "service": "FunctionService" - } - }, - { - "name": "NewUser", - "conditions": [ - { - "name": "cNewUser", - "identifiers": [ - "NewAccount" - ], - "evaluator": "cNewUser" - } - ], - "actionService": { - "service": "FunctionService1" - } - }, - { - "name": "NewUser1", - "conditions": [ - { - "name": "cUserIdMatch", - "identifiers": [ - "UpdateCreditScore", - "UserAccount" - ], - "evaluator": "cUserIdMatch" + "expression": "$.processapplication.start == true " }, { - "name": "cUserCreditScore", - "identifiers": [ - "UpdateCreditScore" - ], - "evaluator": "cUserCreditScore" + "expression": "$.processapplication.ssn == $.applicant.ssn" } ], "actionService": { - "service": "FunctionService2" - } - }, - { - "name": "NewUser2", - "conditions": [ - { - "name": "cUserIdMatch", - "identifiers": [ - "UpdateCreditScore", - "UserAccount" - ], - "evaluator": "cUserIdMatch" - }, - { - "name": "cUserCreditScore", - "identifiers": [ - "UpdateCreditScore" - ], - "evaluator": "cUserHighCreditScore" + "service": "ApplicantSimple", + "input": { + "message": "test ApplicantSimple" } - ], - "actionService": { - "service": "FunctionService3" - } + }, + "priority": 1 }, { - "name": "Rejected", + "name": "Display Information", "conditions": [ { - "name": "cUserIdMatch", - "identifiers": [ - "UpdateCreditScore", - "UserAccount" - ], - "evaluator": "cUserIdMatch" + "expression": "$.applicant.creditLimit >= 0 " }, { - "name": "cUserCreditScore", - "identifiers": [ - "UpdateCreditScore" - ], - "evaluator": "cUserLowCreditScore" + "expression": "$.applicant.status != nil " } ], "actionService": { - "service": "FunctionService4" - } + "service": "LogInformation", + "input": { + "message": "=string.concat(\" Applicant: \",$.applicant.name, \" -- CreditLimit: \",$.applicant.creditLimit, \" -- status: \",$.applicant.status)" + } + }, + "priority": 3 } ], "services": [ { - "name": "FunctionService", - "description": "function service for aBadUser", - "type": "function", - "function": "aBadUser" - }, - { - "name": "FunctionService1", - "description": "function service for aNewUser", - "type": "function", - "function": "aNewUser" - }, - { - "name": "FunctionService2", - "description": "function service for aApproveWithLowerLimit", - "type": "function", - "function": "aApproveWithLowerLimit" - }, - { - "name": "FunctionService3", - "description": "function service for aApproveWithHigherLimit", - "type": "function", - "function": "aApproveWithHigherLimit" + "name": "ApplicantSimple", + "description": "Simple Applicants approval dt", + "type": "activity", + "ref": "github.com/project-flogo/rules/activity/dtable", + "settings": { + "dTableFile":"creditcard-file.xlsx" + } }, { - "name": "FunctionService4", - "description": "function service for aUserReject", - "type": "function", - "function": "aUserReject" + "name": "LogInformation", + "description": "Logs Applicant Information", + "type": "activity", + "ref": "github.com/project-flogo/contrib/activity/log" } ] } @@ -215,100 +124,73 @@ "ruleSessionURI": "res://rulesession:simple", "tds": [ { - "name": "UserAccount", + "name": "applicant", "properties": [ { - "name": "Id", + "name": "name", "pk-index": 0, - "type": "int" - }, - { - "name": "Name", "type": "string" }, { - "name": "Gender", + "name": "gender", "type": "string" }, { - "name": "Age", + "name": "age", "type": "int" }, { - "name": "Address", + "name": "address", "type": "string" }, { - "name": "Income", - "type": "int" - }, - { - "name": "maritalStatus", - "type": "string" - }, - { - "name": "creditScore", - "type": "int" - }, - { - "name": "approvedLimit", - "type": "int" + "name": "hasDL", + "type": "bool" }, { - "name": "appStatus", - "type": "string" - } - ] - }, - { - "name": "NewAccount", - "ttl": 0, - "properties": [ - { - "name": "Id", - "pk-index": 0, - "type": "int" + "name": "ssn", + "type": "long" }, { - "name": "Name", - "type": "string" + "name": "income", + "type": "double" }, { - "name": "Gender", + "name": "maritalStatus", "type": "string" }, { - "name": "Age", + "name": "creditScore", "type": "int" }, { - "name": "Address", + "name": "status", "type": "string" }, { - "name": "Income", - "type": "int" + "name": "eligible", + "type": "bool" }, { - "name": "maritalStatus", - "type": "string" + "name": "creditLimit", + "type": "double" } ] }, { - "name": "UpdateCreditScore", + "name": "processapplication", + "ttl":0, "properties": [ { - "name": "Id", + "name": "ssn", "pk-index": 0, - "type": "int" + "type": "long" }, { - "name": "creditScore", - "type": "int" + "name": "start", + "type": "bool" } - ], - "ttl": 0 + ] } ] }, diff --git a/examples/flogo/creditcard/functions.go b/examples/flogo/creditcard/functions.go deleted file mode 100644 index 0587fb6..0000000 --- a/examples/flogo/creditcard/functions.go +++ /dev/null @@ -1,177 +0,0 @@ -package main - -import ( - "context" - "fmt" - - "github.com/project-flogo/rules/config" - - "github.com/project-flogo/rules/common/model" -) - -//add this sample file to your flogo project -func init() { - - // rule UserData - config.RegisterConditionEvaluator("cNewUser", cNewUser) - config.RegisterActionFunction("aNewUser", aNewUser) - - // rule NewUser - config.RegisterConditionEvaluator("cBadUser", cBadUser) - config.RegisterActionFunction("aBadUser", aBadUser) - - // rule NewUserApprove - config.RegisterConditionEvaluator("cUserIdMatch", cUserIdMatch) - config.RegisterConditionEvaluator("cUserCreditScore", cUserCreditScore) - config.RegisterActionFunction("aApproveWithLowerLimit", aApproveWithLowerLimit) - - // // rule NewUserReject - config.RegisterConditionEvaluator("cUserIdMatch", cUserIdMatch) - config.RegisterConditionEvaluator("cUserLowCreditScore", cUserLowCreditScore) - config.RegisterActionFunction("aUserReject", aUserReject) - - // // rule NewUserApprove1 - config.RegisterConditionEvaluator("cUserIdMatch", cUserIdMatch) - config.RegisterConditionEvaluator("cUserHighCreditScore", cUserHighCreditScore) - config.RegisterActionFunction("aApproveWithHigherLimit", aApproveWithHigherLimit) -} - -func cNewUser(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { - newaccount := tuples["NewAccount"] - if newaccount != nil { - address, _ := newaccount.GetString("Address") - age, _ := newaccount.GetInt("Age") - income, _ := newaccount.GetInt("Income") - if address != "" && age >= 18 && income >= 10000 && age <= 44 { - return true - } - } - return false -} - -func aNewUser(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { - fmt.Println("Rule fired:", ruleName) - newaccount := tuples["NewAccount"] - Id, _ := newaccount.GetInt("Id") - name, _ := newaccount.GetString("Name") - address, _ := newaccount.GetString("Address") - age, _ := newaccount.GetInt("Age") - income, _ := newaccount.GetInt("Income") - userInfo, _ := model.NewTupleWithKeyValues("UserAccount", Id) - userInfo.SetString(ctx, "Name", name) - userInfo.SetString(ctx, "Addresss", address) - userInfo.SetInt(ctx, "Age", age) - userInfo.SetInt(ctx, "Income", income) - fmt.Println(userInfo) - rs.Assert(ctx, userInfo) - fmt.Println("User information received") - -} - -func cBadUser(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { - newaccount := tuples["NewAccount"] - if newaccount != nil { - address, _ := newaccount.GetString("Address") - age, _ := newaccount.GetInt("Age") - income, _ := newaccount.GetInt("Income") - if address == "" || age < 18 || income < 10000 || age >= 45 { - return true - } - } - return false -} - -func aBadUser(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { - fmt.Println("Rule fired:", ruleName) - fmt.Println("Applicant is not eligible to apply for creditcard") -} - -func cUserIdMatch(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { - userInfo := tuples["UserAccount"] - updateScore := tuples["UpdateCreditScore"] - if userInfo != nil || updateScore != nil { - userId, _ := userInfo.GetInt("Id") - newUserId, _ := updateScore.GetInt("Id") - if userId == newUserId { - fmt.Println("Userid match found") - return true - } - } - return false -} - -func cUserCreditScore(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { - updateScore := tuples["UpdateCreditScore"] - if updateScore != nil { - CreditScore, _ := updateScore.GetInt("creditScore") - if CreditScore >= 750 && CreditScore < 820 { - fmt.Println("cUserCreditScore") - return true - } - } - return false -} - -func cUserLowCreditScore(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { - updateScore := tuples["UpdateCreditScore"] - if updateScore != nil { - CreditScore, _ := updateScore.GetInt("creditScore") - if CreditScore < 750 { - fmt.Println("cUserLowCreditScore") - return true - } - } - return false -} - -func cUserHighCreditScore(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { - updateScore := tuples["UpdateCreditScore"] - if updateScore != nil { - CreditScore, _ := updateScore.GetInt("creditScore") - if CreditScore >= 820 && CreditScore <= 900 { - fmt.Println("cUserHighCreditScore") - return true - } - } - return false -} - -func aApproveWithLowerLimit(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { - fmt.Println("Rule fired:", ruleName) - userInfo := tuples["UserAccount"] - updateScore := tuples["UpdateCreditScore"] - CreditScore, _ := updateScore.GetInt("creditScore") - income, _ := userInfo.GetInt("Income") - var limit = 2 * income - userInfoMutable := userInfo.(model.MutableTuple) - userInfoMutable.SetInt(ctx, "creditScore", CreditScore) - userInfoMutable.SetString(ctx, "appStatus", "Approved") - userInfoMutable.SetInt(ctx, "approvedLimit", limit) - fmt.Println(userInfo) -} - -func aApproveWithHigherLimit(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { - fmt.Println("Rule fired:", ruleName) - userInfo := tuples["UserAccount"] - updateScore := tuples["UpdateCreditScore"] - CreditScore, _ := updateScore.GetInt("creditScore") - income, _ := userInfo.GetInt("Income") - var limit = 3 * income - userInfoMutable := userInfo.(model.MutableTuple) - userInfoMutable.SetInt(ctx, "creditScore", CreditScore) - userInfoMutable.SetString(ctx, "appStatus", "Approved") - userInfoMutable.SetInt(ctx, "approvedLimit", limit) - fmt.Println(userInfo) -} - -func aUserReject(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { - fmt.Println("Rule fired:", ruleName) - userInfo := tuples["UserAccount"] - updateScore := tuples["UpdateCreditScore"] - CreditScore, _ := updateScore.GetInt("creditScore") - userInfoMutable := userInfo.(model.MutableTuple) - userInfoMutable.SetInt(ctx, "creditScore", CreditScore) - userInfoMutable.SetString(ctx, "appStatus", "Rejected") - userInfoMutable.SetInt(ctx, "approvedLimit", 0) - fmt.Println(userInfo) -} diff --git a/examples/flogo/creditcard-dt/imports.go b/examples/flogo/creditcard/imports.go similarity index 100% rename from examples/flogo/creditcard-dt/imports.go rename to examples/flogo/creditcard/imports.go diff --git a/examples/flogo/creditcard/main.go b/examples/flogo/creditcard/main.go index 132d1a2..234f2eb 100644 --- a/examples/flogo/creditcard/main.go +++ b/examples/flogo/creditcard/main.go @@ -9,10 +9,6 @@ import ( _ "github.com/project-flogo/core/data/expression/script" "github.com/project-flogo/core/engine" - "github.com/project-flogo/core/support/log" - - _ "github.com/project-flogo/contrib/trigger/rest" - _ "github.com/project-flogo/rules/ruleaction" ) var ( @@ -28,22 +24,25 @@ func main() { if *cpuProfile != "" { f, err := os.Create(*cpuProfile) if err != nil { - fmt.Println(fmt.Sprintf("Failed to create CPU profiling file due to error - %s", err.Error())) + fmt.Fprintf(os.Stderr, "Failed to create CPU profiling file: %v\n", err) + os.Exit(1) + } + if err = pprof.StartCPUProfile(f); err != nil { + fmt.Fprintf(os.Stderr, "Failed to start CPU profiling: %v\n", err) os.Exit(1) } - pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() } cfg, err := engine.LoadAppConfig(cfgJson, cfgCompressed) if err != nil { - log.RootLogger().Errorf("Failed to create engine: %s", err.Error()) + fmt.Fprintf(os.Stderr, "Failed to create engine: %v\n", err) os.Exit(1) } e, err := engine.New(cfg) if err != nil { - log.RootLogger().Errorf("Failed to create engine: %s", err.Error()) + fmt.Fprintf(os.Stderr, "Failed to create engine: %v\n", err) os.Exit(1) } @@ -52,16 +51,16 @@ func main() { if *memProfile != "" { f, err := os.Create(*memProfile) if err != nil { - fmt.Println(fmt.Sprintf("Failed to create memory profiling file due to error - %s", err.Error())) + fmt.Fprintf(os.Stderr, "Failed to create memory profiling file: %v\n", err) os.Exit(1) } runtime.GC() // get up-to-date statistics if err := pprof.WriteHeapProfile(f); err != nil { - fmt.Println(fmt.Sprintf("Failed to write memory profiling data to file due to error - %s", err.Error())) + fmt.Fprintf(os.Stderr, "Failed to write memory profiling data: %v", err) os.Exit(1) } - f.Close() + _ = f.Close() } os.Exit(code) diff --git a/examples/flogo/creditcard-dt/rsconfig.json b/examples/flogo/creditcard/rsconfig.json similarity index 100% rename from examples/flogo/creditcard-dt/rsconfig.json rename to examples/flogo/creditcard/rsconfig.json From 45b19576bea537bf77d4c91f0e2b71a20b9ba530 Mon Sep 17 00:00:00 2001 From: LakshmiNarayana Mekala Date: Fri, 17 Jan 2020 13:44:59 +0530 Subject: [PATCH 114/125] Feature travisbuilds (#88) * Adding travis file with basic configuration Test travis file currently runs go tests and this will be enhanced further * travis file enhancements * updating travis file install latest flogo cli * restructuring travis file * resolving build failure issue with gomodules * fix build failures * pass correct env values to travis file --- .travis.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b336c28 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,33 @@ +sudo: required +language: go +go: + - 1.12 + +os: + - linux +env : + - GO111MODULE=on +branches: + only: + - master + - develop + - /^v\d+\.\d+\.\d+(\.\d+)?(-\S*)?$/ + +services: + - docker + - redis-server + +script: + - env GO111MODULE=off go get -u github.com/project-flogo/cli/... + - env GO111MODULE=on go test -p 1 ./... + +notifications: + email: + on_failure: always + on_success: always + recipients: + - rpolishe@tibco.com + - lmekala@tibco.com + - nthota@tibco.com + - ykalidin@tibco.com + - asnodgra@tibco.com From 4fea44276f17549e511ef510de46c315f825d8e4 Mon Sep 17 00:00:00 2001 From: nthota Date: Tue, 17 Mar 2020 21:06:33 +0530 Subject: [PATCH 115/125] resolving conflicts --- common/model/types.go | 2 + rete/classnode.go | 12 +++- rete/common/types.go | 1 + rete/internal/redis/redis_test.go | 8 +++ rete/internal/types/types.go | 1 + rete/network.go | 30 ++++++++-- ruleapi/rulesession.go | 4 ++ ruleapi/tests/rules_4_test.go | 98 +++++++++++++++++++++++++++++++ 8 files changed, 149 insertions(+), 7 deletions(-) create mode 100644 ruleapi/tests/rules_4_test.go diff --git a/common/model/types.go b/common/model/types.go index 75cb787..4b7f9eb 100644 --- a/common/model/types.go +++ b/common/model/types.go @@ -76,6 +76,8 @@ type RuleSession interface { //SetStore GetStore() TupleStore + //replay existing tuples into a rule + ReplayTuplesForRule(ruleName string) (err error) } //ConditionEvaluator is a function pointer for handling condition evaluations on the server side diff --git a/rete/classnode.go b/rete/classnode.go index a796311..6da445f 100644 --- a/rete/classnode.go +++ b/rete/classnode.go @@ -15,7 +15,7 @@ type classNode interface { addClassNodeLink(classNodeLink) removeClassNodeLink(classNodeLink) getClassNodeLinks() *list.List - assert(ctx context.Context, tuple model.Tuple, changedProps map[string]bool) + assert(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, forRule string) } type classNodeImpl struct { @@ -75,13 +75,18 @@ func (cn *classNodeImpl) String() string { return ret } -func (cn *classNodeImpl) assert(ctx context.Context, tuple model.Tuple, changedProps map[string]bool) { +func (cn *classNodeImpl) assert(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, forRule string) { handle := getHandleWithTuple(ctx, tuple) handles := make([]types.ReteHandle, 1) handles[0] = handle propagate := false for e := cn.getClassNodeLinks().Front(); e != nil; e = e.Next() { classNodeLinkVar := e.Value.(classNodeLink) + if forRule != "" { + if classNodeLinkVar.getRule().GetName() != forRule { + continue + } + } if changedProps != nil { depProps, found := classNodeLinkVar.getRule().GetDeps()[model.TupleType(cn.name)] if found { // rule depends on this type @@ -99,5 +104,8 @@ func (cn *classNodeImpl) assert(ctx context.Context, tuple model.Tuple, changedP if propagate { classNodeLinkVar.propagateObjects(ctx, handles) } + if forRule != "" { + break + } } } diff --git a/rete/common/types.go b/rete/common/types.go index 15e33a2..5108316 100644 --- a/rete/common/types.go +++ b/rete/common/types.go @@ -34,6 +34,7 @@ type Network interface { //GetConfig() map[string]string SetTupleStore(tupleStore model.TupleStore) + ReplayTuplesForRule(ruleName string, rs model.RuleSession) (err error) } const ( diff --git a/rete/internal/redis/redis_test.go b/rete/internal/redis/redis_test.go index 9aefc49..12087b1 100644 --- a/rete/internal/redis/redis_test.go +++ b/rete/internal/redis/redis_test.go @@ -146,6 +146,14 @@ func (n *testNetwork) GetTupleStore() model.TupleStore { return nil } +func (n *testNetwork) GetAssertedTupleByStringKey(key string) model.Tuple { + return nil +} + +func (n *testNetwork) ReplayTuplesForRule(ruleName string, rs model.RuleSession) (err error) { + return nil +} + func TestLockServiceImpl(t *testing.T) { fini := make(chan bool, 1) go func() { diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index 46c8229..172297d 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -20,6 +20,7 @@ type Network interface { GetHandleService() HandleService GetJtRefService() JtRefsService GetTupleStore() model.TupleStore + GetAssertedTupleByStringKey(key string) model.Tuple } type ConflictRes interface { diff --git a/rete/network.go b/rete/network.go index e85e9ff..2560050 100644 --- a/rete/network.go +++ b/rete/network.go @@ -10,11 +10,11 @@ import ( "github.com/project-flogo/rules/common/model" "container/list" - "github.com/project-flogo/rules/rete/internal/types" "sync" "time" - //"github.com/project-flogo/rules/rete/common" + "github.com/project-flogo/rules/rete/common" + "github.com/project-flogo/rules/rete/internal/types" ) type reteNetworkImpl struct { @@ -169,6 +169,12 @@ func (nw *reteNetworkImpl) AddRule(rule model.Rule) (err error) { //Add NodeLinks nw.ruleNameClassNodeLinksOfRule[rule.GetName()] = classNodeLinksOfRule + + return nil +} + +func (nw *reteNetworkImpl) ReplayTuplesForRule(ruleName string, rs model.RuleSession) error { + // TO DO return nil } @@ -213,7 +219,8 @@ func (nw *reteNetworkImpl) RemoveRule(ruleName string) model.Rule { } } } - + rstr := nw.String() + fmt.Printf(rstr) return rule } @@ -583,6 +590,10 @@ func (nw *reteNetworkImpl) printClassNode(ruleName string, classNodeImpl *classN } func (nw *reteNetworkImpl) Assert(ctx context.Context, rs model.RuleSession, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) error { + return nw.assert(ctx, rs, tuple, changedProps, mode, "") +} + +func (nw *reteNetworkImpl) assert(ctx context.Context, rs model.RuleSession, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn, forRule string) error { if ctx == nil { ctx = context.Background() @@ -598,7 +609,7 @@ func (nw *reteNetworkImpl) Assert(ctx context.Context, rs model.RuleSession, tup defer nw.lock.Unlock() } - err := nw.AssertInternal(newCtx, tuple, changedProps, mode) + err := nw.assertInternal(newCtx, tuple, changedProps, mode, forRule) if err != nil { return err } @@ -717,6 +728,10 @@ func (nw *reteNetworkImpl) GetAssertedTuple(ctx context.Context, rs model.RuleSe } func (nw *reteNetworkImpl) AssertInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn) error { + return nw.assertInternal(ctx, tuple, changedProps, mode, "") +} + +func (nw *reteNetworkImpl) assertInternal(ctx context.Context, tuple model.Tuple, changedProps map[string]bool, mode common.RtcOprn, forRule string) error { if mode == common.ADD || mode == common.MODIFY { handle, locked := nw.handleService.GetOrCreateLockedHandle(nw, tuple) if locked { @@ -737,7 +752,7 @@ func (nw *reteNetworkImpl) AssertInternal(ctx context.Context, tuple model.Tuple listItem := nw.allClassNodes[string(tupleType)] if listItem != nil { classNodeVar := listItem.(classNode) - classNodeVar.assert(ctx, tuple, changedProps) + classNodeVar.assert(ctx, tuple, changedProps, forRule) } td := model.GetTupleDescriptor(tuple.GetTupleType()) if td != nil { @@ -859,3 +874,8 @@ func (nw *reteNetworkImpl) GetHandleService() types.HandleService { func (nw *reteNetworkImpl) GetPrefix() string { return nw.prefix } + +func (nw *reteNetworkImpl) GetAssertedTupleByStringKey(key string) model.Tuple { + // TO DO + return nil +} diff --git a/ruleapi/rulesession.go b/ruleapi/rulesession.go index 3871f5a..8a8c041 100644 --- a/ruleapi/rulesession.go +++ b/ruleapi/rulesession.go @@ -328,3 +328,7 @@ func internalTxnHandler(ctx context.Context, rs model.RuleSession, rtxn model.Rt store.SaveTuples(rtxn.GetRtcAdded()) store.SaveModifiedTuples(rtxn.GetRtcModified()) } + +func (rs *rulesessionImpl) ReplayTuplesForRule(ruleName string) (err error) { + return rs.reteNetwork.ReplayTuplesForRule(ruleName, rs) +} diff --git a/ruleapi/tests/rules_4_test.go b/ruleapi/tests/rules_4_test.go new file mode 100644 index 0000000..df6759a --- /dev/null +++ b/ruleapi/tests/rules_4_test.go @@ -0,0 +1,98 @@ +package tests + +import ( + "context" + "testing" + "time" + + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/ruleapi" +) + +// Add previously removed rule +func Test_Four(t *testing.T) { + rs, _ := createRuleSession(t) + + // create rule + r1 := ruleapi.NewRule("Rule1") + r1.AddCondition("r1c1", []string{"t1.none", "t2.none"}, trueCondition, nil) + r1.SetActionService(createActionServiceFromFunction(t, r1Action)) + // create tuples + t1, _ := model.NewTupleWithKeyValues("t1", "one") // No TTL + t2, _ := model.NewTupleWithKeyValues("t2", "two") // TTL is 0 + + // start rule session + rs.Start(nil) + + assertCtxValues := make(map[string]interface{}) + assertCtxValues["test"] = t + assertCtxValues["actionFired"] = "NOTFIRED" + assertCtx := context.WithValue(context.TODO(), "values", assertCtxValues) + + // Test1: add r1 and assert t1 & t2; rule action SHOULD be fired + addRule(t, rs, r1) + assertWithCtx(assertCtx, rs, t1) + assertWithCtx(assertCtx, rs, t2) + actionFired, _ := assertCtxValues["actionFired"].(string) + if actionFired != "FIRED" { + t.Log("rule action SHOULD be fired") + t.FailNow() + } + + // Test2: remove r1 and assert t2; rule action SHOULD NOT be fired + deleteRule(t, rs, r1) + assertCtxValues["actionFired"] = "NOTFIRED" + assertWithCtx(assertCtx, rs, t2) + actionFired, _ = assertCtxValues["actionFired"].(string) + if actionFired != "NOTFIRED" { + t.Log("rule action SHOULD NOT be fired") + t.FailNow() + } + + // Test3: add r1 again and assert t2; rule action SHOULD be fired + addRule(t, rs, r1) + rs.ReplayTuplesForRule(r1.GetName()) + assertCtxValues["actionFired"] = "NOTFIRED" + assertWithCtx(assertCtx, rs, t2) + actionFired, _ = assertCtxValues["actionFired"].(string) + if actionFired != "FIRED" { + t.Log("rule action SHOULD be fired") + t.FailNow() + } + + rs.Unregister() +} + +func addRule(t *testing.T, rs model.RuleSession, rule model.Rule) { + err := rs.AddRule(rule) + if err != nil { + t.Logf("[%s] error while adding rule[%s]", time.Now().Format("15:04:05.999999"), rule.GetName()) + return + } + t.Logf("[%s] Rule[%s] added. \n", time.Now().Format("15:04:05.999999"), rule.GetName()) +} + +func deleteRule(t *testing.T, rs model.RuleSession, rule model.Rule) { + rs.DeleteRule(rule.GetName()) + t.Logf("[%s] Rule[%s] deleted. \n", time.Now().Format("15:04:05.999999"), rule.GetName()) +} + +func assertWithCtx(ctx context.Context, rs model.RuleSession, tuple model.Tuple) { + assertCtxValues := ctx.Value("values").(map[string]interface{}) + t, _ := assertCtxValues["test"].(*testing.T) + err := rs.Assert(ctx, tuple) + if err != nil { + t.Logf("[%s] assert error %s", time.Now().Format("15:04:05.999999"), err) + } +} + +func r1Action(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + assertCtxValues := ctx.Value("values").(map[string]interface{}) + test, _ := assertCtxValues["test"].(*testing.T) + + t := tuples["t1"] + tSrt, _ := t.GetString("id") + test.Logf("[%s] r1Action called with the tuple[%s] \n", time.Now().Format("15:04:05.999999"), tSrt) + + assertCtxValues["actionFired"] = "FIRED" +} From c8c8125a5aa0ffea911f81d30045da39d0f2568f Mon Sep 17 00:00:00 2001 From: nthota Date: Wed, 18 Mar 2020 12:43:04 +0530 Subject: [PATCH 116/125] fixing replaytplesforrule --- rete/internal/mem/mhandleservice.go | 8 ++++++++ rete/internal/redis/redis_test.go | 4 ---- rete/internal/redis/rhandleservice.go | 4 ++++ rete/internal/types/types.go | 2 +- rete/network.go | 21 ++++++++++++--------- 5 files changed, 25 insertions(+), 14 deletions(-) diff --git a/rete/internal/mem/mhandleservice.go b/rete/internal/mem/mhandleservice.go index 3577cde..4f79780 100644 --- a/rete/internal/mem/mhandleservice.go +++ b/rete/internal/mem/mhandleservice.go @@ -101,3 +101,11 @@ func (hc *handleServiceImpl) GetHandleWithTuple(nw types.Network, tuple model.Tu } return h } + +func (hc *handleServiceImpl) GetAllHandles() map[string]types.ReteHandle { + all := make(map[string]types.ReteHandle) + for k, v := range hc.allHandles { + all[k] = v + } + return all +} diff --git a/rete/internal/redis/redis_test.go b/rete/internal/redis/redis_test.go index 12087b1..522597f 100644 --- a/rete/internal/redis/redis_test.go +++ b/rete/internal/redis/redis_test.go @@ -146,10 +146,6 @@ func (n *testNetwork) GetTupleStore() model.TupleStore { return nil } -func (n *testNetwork) GetAssertedTupleByStringKey(key string) model.Tuple { - return nil -} - func (n *testNetwork) ReplayTuplesForRule(ruleName string, rs model.RuleSession) (err error) { return nil } diff --git a/rete/internal/redis/rhandleservice.go b/rete/internal/redis/rhandleservice.go index 5b81f61..618c474 100644 --- a/rete/internal/redis/rhandleservice.go +++ b/rete/internal/redis/rhandleservice.go @@ -210,3 +210,7 @@ func (hc *handleServiceImpl) GetHandleWithTuple(nw types.Network, tuple model.Tu h := newReteHandleImpl(nw, hc.RedisHdl, tuple, key, status, id) return h } + +func (hc *handleServiceImpl) GetAllHandles() map[string]types.ReteHandle { + return nil +} diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index 172297d..5da7008 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -20,7 +20,6 @@ type Network interface { GetHandleService() HandleService GetJtRefService() JtRefsService GetTupleStore() model.TupleStore - GetAssertedTupleByStringKey(key string) model.Tuple } type ConflictRes interface { @@ -114,6 +113,7 @@ type HandleService interface { GetHandleWithTuple(nw Network, tuple model.Tuple) ReteHandle GetOrCreateLockedHandle(nw Network, tuple model.Tuple) (ReteHandle, bool) GetLockedHandle(nw Network, tuple model.Tuple) (handle ReteHandle, locked, dne bool) + GetAllHandles() map[string]ReteHandle } type IdGen interface { diff --git a/rete/network.go b/rete/network.go index 2560050..ec4cc16 100644 --- a/rete/network.go +++ b/rete/network.go @@ -173,11 +173,6 @@ func (nw *reteNetworkImpl) AddRule(rule model.Rule) (err error) { return nil } -func (nw *reteNetworkImpl) ReplayTuplesForRule(ruleName string, rs model.RuleSession) error { - // TO DO - return nil -} - func (nw *reteNetworkImpl) setClassNodeAndLinkJoinTables(nodesOfRule *list.List, classNodeLinksOfRule *list.List) { } @@ -219,8 +214,6 @@ func (nw *reteNetworkImpl) RemoveRule(ruleName string) model.Rule { } } } - rstr := nw.String() - fmt.Printf(rstr) return rule } @@ -875,7 +868,17 @@ func (nw *reteNetworkImpl) GetPrefix() string { return nw.prefix } -func (nw *reteNetworkImpl) GetAssertedTupleByStringKey(key string) model.Tuple { - // TO DO +func (nw *reteNetworkImpl) ReplayTuplesForRule(ruleName string, rs model.RuleSession) error { + if rule, exists := nw.allRules[ruleName]; !exists { + return fmt.Errorf("Rule not found [%s]", ruleName) + } else { + for _, h := range nw.handleService.GetAllHandles() { + tt := h.GetTuple() + if ContainedByFirst(rule.GetIdentifiers(), []model.TupleType{tt.GetTupleType()}) { + //assert it but only for this rule. + nw.assert(nil, rs, h.GetTuple(), nil, common.ADD, ruleName) + } + } + } return nil } From e4d69bd6444059d08869abb46e578958d4a2a1d1 Mon Sep 17 00:00:00 2001 From: nthota Date: Thu, 19 Mar 2020 10:00:43 +0530 Subject: [PATCH 117/125] adding replay tuple method in rete --- redisutils/redisutils.go | 26 ++++++++++++++++++++++++++ rete/internal/mem/mhandleservice.go | 2 +- rete/internal/redis/rhandleservice.go | 22 ++++++++++++++++++++-- rete/internal/types/types.go | 2 +- rete/network.go | 8 +++++--- 5 files changed, 53 insertions(+), 7 deletions(-) diff --git a/redisutils/redisutils.go b/redisutils/redisutils.go index 04a2a28..bd7a631 100644 --- a/redisutils/redisutils.go +++ b/redisutils/redisutils.go @@ -382,3 +382,29 @@ func Shutdown() { } rd = rd[:0] } + +// GetKeys returns all keys present in redis server +func (rh *RedisHandle) GetKeys(pattern string) ([]string, error) { + + conn := rh.getPool().Get() + defer conn.Close() + + iter := 0 + keys := []string{} + for { + arr, err := redis.Values(conn.Do("SCAN", iter, "MATCH", pattern)) + if err != nil { + return keys, fmt.Errorf("error retrieving '%s' keys", pattern) + } + + iter, _ = redis.Int(arr[0], nil) + k, _ := redis.Strings(arr[1], nil) + keys = append(keys, k...) + + if iter == 0 { + break + } + } + + return keys, nil +} diff --git a/rete/internal/mem/mhandleservice.go b/rete/internal/mem/mhandleservice.go index 4f79780..3004198 100644 --- a/rete/internal/mem/mhandleservice.go +++ b/rete/internal/mem/mhandleservice.go @@ -102,7 +102,7 @@ func (hc *handleServiceImpl) GetHandleWithTuple(nw types.Network, tuple model.Tu return h } -func (hc *handleServiceImpl) GetAllHandles() map[string]types.ReteHandle { +func (hc *handleServiceImpl) GetAllHandles(nw types.Network) map[string]types.ReteHandle { all := make(map[string]types.ReteHandle) for k, v := range hc.allHandles { all[k] = v diff --git a/rete/internal/redis/rhandleservice.go b/rete/internal/redis/rhandleservice.go index 618c474..749b6c8 100644 --- a/rete/internal/redis/rhandleservice.go +++ b/rete/internal/redis/rhandleservice.go @@ -4,9 +4,11 @@ import ( "context" "math/rand" "strconv" + "strings" "sync" "time" + "github.com/project-flogo/core/support/log" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/redisutils" "github.com/project-flogo/rules/rete/common" @@ -211,6 +213,22 @@ func (hc *handleServiceImpl) GetHandleWithTuple(nw types.Network, tuple model.Tu return h } -func (hc *handleServiceImpl) GetAllHandles() map[string]types.ReteHandle { - return nil +func (hc *handleServiceImpl) GetAllHandles(nw types.Network) map[string]types.ReteHandle { + + allHandles := make(map[string]types.ReteHandle) + + data, err := hc.GetKeys(hc.prefix + "*") + if err != nil { + log.RootLogger().Errorf("No keys found in redis: %s", err) + return allHandles + } + + for _, v := range data { + tupleKey := strings.Replace(v, hc.prefix, "", 1) + tuple := nw.GetTupleStore().GetTupleByKey(model.FromStringKey(tupleKey)) + reteHandle := hc.GetHandleWithTuple(nw, tuple) + allHandles[tupleKey] = reteHandle + } + + return allHandles } diff --git a/rete/internal/types/types.go b/rete/internal/types/types.go index 5da7008..305f538 100644 --- a/rete/internal/types/types.go +++ b/rete/internal/types/types.go @@ -113,7 +113,7 @@ type HandleService interface { GetHandleWithTuple(nw Network, tuple model.Tuple) ReteHandle GetOrCreateLockedHandle(nw Network, tuple model.Tuple) (ReteHandle, bool) GetLockedHandle(nw Network, tuple model.Tuple) (handle ReteHandle, locked, dne bool) - GetAllHandles() map[string]ReteHandle + GetAllHandles(nw Network) map[string]ReteHandle } type IdGen interface { diff --git a/rete/network.go b/rete/network.go index ec4cc16..5bca476 100644 --- a/rete/network.go +++ b/rete/network.go @@ -732,8 +732,10 @@ func (nw *reteNetworkImpl) assertInternal(ctx context.Context, tuple model.Tuple } else if handle.GetStatus() == types.ReteHandleStatusRetracted { handle.SetStatus(types.ReteHandleStatusCreating) } else if handle.GetStatus() == types.ReteHandleStatusCreated { - handle.Unlock() - return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) + if len(forRule) == 0 { + handle.Unlock() + return fmt.Errorf("Tuple with key [%s] already asserted", tuple.GetKey().String()) + } } defer func() { handle.SetStatus(types.ReteHandleStatusCreated) @@ -872,7 +874,7 @@ func (nw *reteNetworkImpl) ReplayTuplesForRule(ruleName string, rs model.RuleSes if rule, exists := nw.allRules[ruleName]; !exists { return fmt.Errorf("Rule not found [%s]", ruleName) } else { - for _, h := range nw.handleService.GetAllHandles() { + for _, h := range nw.handleService.GetAllHandles(nw) { tt := h.GetTuple() if ContainedByFirst(rule.GetIdentifiers(), []model.TupleType{tt.GetTupleType()}) { //assert it but only for this rule. From fb4ff64c4e7621658530dbcc9ae64c3c72c6fbb1 Mon Sep 17 00:00:00 2001 From: nthota Date: Thu, 19 Mar 2020 13:36:33 +0530 Subject: [PATCH 118/125] tuple null handled in redis --- rete/internal/redis/rhandleservice.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rete/internal/redis/rhandleservice.go b/rete/internal/redis/rhandleservice.go index 749b6c8..78dd135 100644 --- a/rete/internal/redis/rhandleservice.go +++ b/rete/internal/redis/rhandleservice.go @@ -226,8 +226,10 @@ func (hc *handleServiceImpl) GetAllHandles(nw types.Network) map[string]types.Re for _, v := range data { tupleKey := strings.Replace(v, hc.prefix, "", 1) tuple := nw.GetTupleStore().GetTupleByKey(model.FromStringKey(tupleKey)) - reteHandle := hc.GetHandleWithTuple(nw, tuple) - allHandles[tupleKey] = reteHandle + if tuple != nil { + reteHandle := hc.GetHandleWithTuple(nw, tuple) + allHandles[tupleKey] = reteHandle + } } return allHandles From 4567024435949ed6c5407a43ce4f0b42ca30e3a4 Mon Sep 17 00:00:00 2001 From: nthota Date: Thu, 19 Mar 2020 14:00:38 +0530 Subject: [PATCH 119/125] upgrading flogo dependency versions --- go.mod | 11 ++++---- go.sum | 59 ++++++++++++++++++++++++++++++++++++++- ruleapi/exprcondition.go | 7 ++--- ruleapi/servicecontext.go | 6 ++++ 4 files changed, 73 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 7746e27..317fdd1 100644 --- a/go.mod +++ b/go.mod @@ -9,11 +9,12 @@ require ( github.com/gomodule/redigo v2.0.0+incompatible github.com/gorilla/websocket v1.4.1 github.com/oklog/ulid v1.3.1 - github.com/project-flogo/contrib/activity/log v0.9.0 - github.com/project-flogo/contrib/function/string v0.9.0 - github.com/project-flogo/contrib/trigger/kafka v0.9.0 - github.com/project-flogo/contrib/trigger/rest v0.9.0 - github.com/project-flogo/core v0.9.3 + github.com/project-flogo/contrib/activity/log v0.10.0 + github.com/project-flogo/contrib/function/string v0.10.0 + github.com/project-flogo/contrib/trigger/kafka v0.10.0 + github.com/project-flogo/contrib/trigger/rest v0.10.0 + github.com/project-flogo/core v0.10.2 + github.com/project-flogo/flow v0.10.1 // indirect github.com/project-flogo/legacybridge v0.9.1 github.com/stretchr/testify v1.4.0 gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect diff --git a/go.sum b/go.sum index 8e1b413..05bf562 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/360EntSecGroup-Skylar/excelize/v2 v2.0.2 h1:StMrA6UQ5Cm6206DxXGuV/NMqSIOIDoMXMYt8JPe1lE= github.com/360EntSecGroup-Skylar/excelize/v2 v2.0.2/go.mod h1:EfRHD2k+Kd7ijnqlwOrH1IifwgWB9yYJ0pdXtBZmlpU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DataDog/zstd v1.3.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798 h1:2T/jmrHeTezcCM58lvEQXs0UpQJCo5SoGAcg+mbSTIg= github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= @@ -26,6 +28,7 @@ github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= @@ -36,8 +39,16 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5i github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= @@ -46,27 +57,44 @@ github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41 h1:GeinFsrjWz97fAxVUEd github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/project-flogo/contrib/activity/log v0.9.0 h1:Uptmxa+tAi79mtTn4DNwoWIiA21qnSXzBdVMc+AHfNw= github.com/project-flogo/contrib/activity/log v0.9.0/go.mod h1:Eso9z1s2DyrGdHwLwni0qP1TPtY9L/NclLYIs17dq30= +github.com/project-flogo/contrib/activity/log v0.10.0 h1:TPVeO2xUQfauFIPqLBsEBlNp7aGRaWiIMVDQcr7JQ6o= +github.com/project-flogo/contrib/activity/log v0.10.0/go.mod h1:M+qvbC3fVbONn1iYlhOsvxDUWCO7eAZT6rOj0N0Oexc= github.com/project-flogo/contrib/function/string v0.9.0 h1:9BOAScGsTdnP6Jor6lnTcUwDdYgVBcyAXFpg969qw6E= github.com/project-flogo/contrib/function/string v0.9.0/go.mod h1:ucysnPOToMKCKoSROm4cRXDcQFZGit8LUK5SfbCeDh8= +github.com/project-flogo/contrib/function/string v0.10.0 h1:U+gHaAwiZIqwORHAc6Fx8bDP8VO1Gi9o6yaKerEbDeU= +github.com/project-flogo/contrib/function/string v0.10.0/go.mod h1:LU3UHYCkihq3BWMJssYU9zOQQgAql6E2F+THq5QGaCo= github.com/project-flogo/contrib/trigger/kafka v0.9.0 h1:xeGFM73L0jSYPOd0ihOIpkdF/8ko6/sOZircJYOT5Ew= github.com/project-flogo/contrib/trigger/kafka v0.9.0/go.mod h1:NqVLzyM1u4dMGp9q06YsI/JahK/oFNvpV+72tKIt8bk= +github.com/project-flogo/contrib/trigger/kafka v0.10.0 h1:rU0k7S9U5GjF6nhXtKqySdVVMM/19ZoyIz9kyVTLPY4= +github.com/project-flogo/contrib/trigger/kafka v0.10.0/go.mod h1:nXuAgdpc9DoZCJNAe/MyLEI+oiPembca8v23aTzADvY= github.com/project-flogo/contrib/trigger/rest v0.9.0 h1:3e9uLWLmg8VG8fcf8Fi4MrLptPc6WI1ZpaylC8eQ3Pk= github.com/project-flogo/contrib/trigger/rest v0.9.0/go.mod h1:vxFRTpssjn5eF1fXdCc+L6+PxZOKm5iqkTZPA4lUIsY= +github.com/project-flogo/contrib/trigger/rest v0.10.0 h1:v6wm+A3BLCnCZT/ufiELxW08HL2Yc4bHby61/gKRhYg= +github.com/project-flogo/contrib/trigger/rest v0.10.0/go.mod h1:Omd0fWOL8E+e6emN97/UZ8UK1cDsVwR5MtvGKgUzOA4= github.com/project-flogo/core v0.9.0/go.mod h1:QGWi7TDLlhGUaYH3n/16ImCuulbEHGADYEXyrcHhX7U= github.com/project-flogo/core v0.9.2/go.mod h1:QGWi7TDLlhGUaYH3n/16ImCuulbEHGADYEXyrcHhX7U= github.com/project-flogo/core v0.9.3 h1:uZXHR9j1Byqt+x3faNnOqB8NlEfwE2gpCh40iQ+44oA= github.com/project-flogo/core v0.9.3/go.mod h1:QGWi7TDLlhGUaYH3n/16ImCuulbEHGADYEXyrcHhX7U= +github.com/project-flogo/core v0.9.4-hf.1/go.mod h1:QGWi7TDLlhGUaYH3n/16ImCuulbEHGADYEXyrcHhX7U= +github.com/project-flogo/core v0.10.1/go.mod h1:4DhTlZ5re1DKHBXYwNZmUswiakcD2E4v3FzlZT/rAI8= +github.com/project-flogo/core v0.10.2 h1:w+OweLultHbY5712r3fiXHFTMw7HJ0vCDmdKRoN0gus= +github.com/project-flogo/core v0.10.2/go.mod h1:4DhTlZ5re1DKHBXYwNZmUswiakcD2E4v3FzlZT/rAI8= github.com/project-flogo/flow v0.9.2 h1:0y+mWZ158yHWsbY/OsPB0pY0MVFXPRFqtEczHjtnNks= github.com/project-flogo/flow v0.9.2/go.mod h1:DPUBHFS5+5N+zXBJ9Q6qeNy+AD6kUDrSzPhhwNdNn1M= +github.com/project-flogo/flow v0.10.1 h1:P1h5UPjwvdc7CNE+GERt3EJ8xtzFza6ssClo5cBW+nM= +github.com/project-flogo/flow v0.10.1/go.mod h1:ruIoGt/yPBZsDwoUi3dIgGVXOllt+3YCBah9tdEjffM= github.com/project-flogo/legacybridge v0.9.1 h1:d0mYjPHUJCHtXA84tluEbG5sgK4gLFYgpYPWgLCsMpc= github.com/project-flogo/legacybridge v0.9.1/go.mod h1:1HcW7VRlgfiSbnAhHwyAWdkiVKkqr0QjMM6kluv8/xo= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -81,25 +109,52 @@ github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0 github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.5.1 h1:rsqfU5vBkVknbhUGbAUwQKR2H4ItV8tjJ+6kJX4cxHM= +go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.4.0 h1:f3WCSC2KzAcBXGATIxAB1E2XuCpNU255wNKZ505qi3E= +go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5 h1:bselrhR0Or1vomJZC8ZIjWtbDmn9OYFLX5Ik9alpJpE= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a h1:gHevYm0pO4QUbwy8Dmdr01R5r1BuKtfYqRqF0h/Cbh0= golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw= gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM= @@ -111,4 +166,6 @@ gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuv gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU= gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= \ No newline at end of file +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/ruleapi/exprcondition.go b/ruleapi/exprcondition.go index ffa509d..1708efa 100644 --- a/ruleapi/exprcondition.go +++ b/ruleapi/exprcondition.go @@ -19,10 +19,9 @@ func init() { td = tuplePropertyResolver{} //resolver = resolve.NewCompositeResolver(map[string]resolve.Resolver{".": &td}) resolver = resolve.NewCompositeResolver(map[string]resolve.Resolver{ - ".": &td, - "env": &resolve.EnvResolver{}, - "property": &resolve.PropertyResolver{}, - "loop": &resolve.LoopResolver{}, + ".": &td, + "env": &resolve.EnvResolver{}, + "loop": &resolve.LoopResolver{}, }) factory = script.NewExprFactory(resolver) } diff --git a/ruleapi/servicecontext.go b/ruleapi/servicecontext.go index 4b97a66..23fadec 100644 --- a/ruleapi/servicecontext.go +++ b/ruleapi/servicecontext.go @@ -7,6 +7,7 @@ import ( "github.com/project-flogo/core/data/metadata" "github.com/project-flogo/core/data/resolve" "github.com/project-flogo/core/support/log" + "github.com/project-flogo/core/support/trace" ) // activity init context @@ -135,6 +136,11 @@ func (sc *ServiceContext) SetInput(name string, value interface{}) { sc.inputs[name] = value } +// GetTracingContext returns tracing context +func (sc *ServiceContext) GetTracingContext() trace.TracingContext { + return nil +} + // ServiceHost hosts service type ServiceHost struct { HostId string From 2bffc7e109dea0ab1658e1234688fbeabca35a25 Mon Sep 17 00:00:00 2001 From: nthota Date: Thu, 19 Mar 2020 14:05:09 +0530 Subject: [PATCH 120/125] go modules updated --- go.sum | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/go.sum b/go.sum index 05bf562..34d8c63 100644 --- a/go.sum +++ b/go.sum @@ -62,26 +62,15 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/project-flogo/contrib/activity/log v0.9.0 h1:Uptmxa+tAi79mtTn4DNwoWIiA21qnSXzBdVMc+AHfNw= -github.com/project-flogo/contrib/activity/log v0.9.0/go.mod h1:Eso9z1s2DyrGdHwLwni0qP1TPtY9L/NclLYIs17dq30= github.com/project-flogo/contrib/activity/log v0.10.0 h1:TPVeO2xUQfauFIPqLBsEBlNp7aGRaWiIMVDQcr7JQ6o= github.com/project-flogo/contrib/activity/log v0.10.0/go.mod h1:M+qvbC3fVbONn1iYlhOsvxDUWCO7eAZT6rOj0N0Oexc= -github.com/project-flogo/contrib/function/string v0.9.0 h1:9BOAScGsTdnP6Jor6lnTcUwDdYgVBcyAXFpg969qw6E= -github.com/project-flogo/contrib/function/string v0.9.0/go.mod h1:ucysnPOToMKCKoSROm4cRXDcQFZGit8LUK5SfbCeDh8= github.com/project-flogo/contrib/function/string v0.10.0 h1:U+gHaAwiZIqwORHAc6Fx8bDP8VO1Gi9o6yaKerEbDeU= github.com/project-flogo/contrib/function/string v0.10.0/go.mod h1:LU3UHYCkihq3BWMJssYU9zOQQgAql6E2F+THq5QGaCo= -github.com/project-flogo/contrib/trigger/kafka v0.9.0 h1:xeGFM73L0jSYPOd0ihOIpkdF/8ko6/sOZircJYOT5Ew= -github.com/project-flogo/contrib/trigger/kafka v0.9.0/go.mod h1:NqVLzyM1u4dMGp9q06YsI/JahK/oFNvpV+72tKIt8bk= github.com/project-flogo/contrib/trigger/kafka v0.10.0 h1:rU0k7S9U5GjF6nhXtKqySdVVMM/19ZoyIz9kyVTLPY4= github.com/project-flogo/contrib/trigger/kafka v0.10.0/go.mod h1:nXuAgdpc9DoZCJNAe/MyLEI+oiPembca8v23aTzADvY= -github.com/project-flogo/contrib/trigger/rest v0.9.0 h1:3e9uLWLmg8VG8fcf8Fi4MrLptPc6WI1ZpaylC8eQ3Pk= -github.com/project-flogo/contrib/trigger/rest v0.9.0/go.mod h1:vxFRTpssjn5eF1fXdCc+L6+PxZOKm5iqkTZPA4lUIsY= github.com/project-flogo/contrib/trigger/rest v0.10.0 h1:v6wm+A3BLCnCZT/ufiELxW08HL2Yc4bHby61/gKRhYg= github.com/project-flogo/contrib/trigger/rest v0.10.0/go.mod h1:Omd0fWOL8E+e6emN97/UZ8UK1cDsVwR5MtvGKgUzOA4= -github.com/project-flogo/core v0.9.0/go.mod h1:QGWi7TDLlhGUaYH3n/16ImCuulbEHGADYEXyrcHhX7U= github.com/project-flogo/core v0.9.2/go.mod h1:QGWi7TDLlhGUaYH3n/16ImCuulbEHGADYEXyrcHhX7U= -github.com/project-flogo/core v0.9.3 h1:uZXHR9j1Byqt+x3faNnOqB8NlEfwE2gpCh40iQ+44oA= -github.com/project-flogo/core v0.9.3/go.mod h1:QGWi7TDLlhGUaYH3n/16ImCuulbEHGADYEXyrcHhX7U= github.com/project-flogo/core v0.9.4-hf.1/go.mod h1:QGWi7TDLlhGUaYH3n/16ImCuulbEHGADYEXyrcHhX7U= github.com/project-flogo/core v0.10.1/go.mod h1:4DhTlZ5re1DKHBXYwNZmUswiakcD2E4v3FzlZT/rAI8= github.com/project-flogo/core v0.10.2 h1:w+OweLultHbY5712r3fiXHFTMw7HJ0vCDmdKRoN0gus= From 05505b2892a97012feae4fa1bb05e76625f5d30b Mon Sep 17 00:00:00 2001 From: nthota Date: Tue, 24 Mar 2020 12:13:19 +0530 Subject: [PATCH 121/125] added fix for issue 91 --- rete/internal/mem/mhandleservice.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/rete/internal/mem/mhandleservice.go b/rete/internal/mem/mhandleservice.go index 3004198..81dc444 100644 --- a/rete/internal/mem/mhandleservice.go +++ b/rete/internal/mem/mhandleservice.go @@ -53,7 +53,11 @@ func (hc *handleServiceImpl) GetHandle(ctx context.Context, tuple model.Tuple) t func (hc *handleServiceImpl) GetHandleByKey(ctx context.Context, key model.TupleKey) types.ReteHandle { hc.RLock() defer hc.RUnlock() - return hc.allHandles[key.String()] + reteHndl, ok := hc.allHandles[key.String()] + if ok { + return reteHndl + } + return nil } func (hc *handleServiceImpl) GetOrCreateLockedHandle(nw types.Network, tuple model.Tuple) (types.ReteHandle, bool) { @@ -66,7 +70,6 @@ func (hc *handleServiceImpl) GetOrCreateLockedHandle(nw types.Network, tuple mod hc.allHandles[tuple.GetKey().String()] = h return h, false } - if atomic.CompareAndSwapInt64(&h.id, -1, id) { return h, false } @@ -95,7 +98,7 @@ func (hc *handleServiceImpl) GetHandleWithTuple(nw types.Network, tuple model.Tu defer hc.Unlock() h, found := hc.allHandles[tuple.GetKey().String()] if !found { - h = newReteHandleImpl(nw, tuple, types.ReteHandleStatusCreating, 0) + h = newReteHandleImpl(nw, tuple, types.ReteHandleStatusCreating, int64(-1)) hc.allHandles[tuple.GetKey().String()] = h //[tuple.GetKey().String()] = h } From 2e3a62f010462b001e16e1cf2f9e660ffa4d76e7 Mon Sep 17 00:00:00 2001 From: ykalidin Date: Tue, 31 Mar 2020 10:48:51 +0530 Subject: [PATCH 122/125] Feature ruleapi examples (#94) * Add examples * Updating examples * Updating comments --- .../{trackntrace => statemachine}/README.md | 20 +- .../{trackntrace => statemachine}/flogo.json | 2 +- .../functions.go | 0 .../{trackntrace => statemachine}/main.go | 0 .../statemachine.png} | Bin .../statemachine_test.go} | 2 +- examples/simple/README.md | 31 + examples/simple/main.go | 362 +++++++++++ examples/simple/rsconfig.json | 36 ++ examples/simple/rulesapp.json | 22 + examples/statemachine/README.md | 111 ++++ examples/statemachine/main.go | 598 ++++++++++++++++++ examples/statemachine/rulesapp.json | 54 ++ examples/statemachine/statemachine.png | Bin 0 -> 36921 bytes 14 files changed, 1226 insertions(+), 12 deletions(-) rename examples/flogo/{trackntrace => statemachine}/README.md (91%) rename examples/flogo/{trackntrace => statemachine}/flogo.json (99%) rename examples/flogo/{trackntrace => statemachine}/functions.go (100%) rename examples/flogo/{trackntrace => statemachine}/main.go (100%) rename examples/flogo/{trackntrace/trackntrace.png => statemachine/statemachine.png} (100%) rename examples/flogo/{trackntrace/trackntrace_test.go => statemachine/statemachine_test.go} (98%) create mode 100644 examples/simple/README.md create mode 100644 examples/simple/main.go create mode 100644 examples/simple/rsconfig.json create mode 100644 examples/simple/rulesapp.json create mode 100644 examples/statemachine/README.md create mode 100644 examples/statemachine/main.go create mode 100644 examples/statemachine/rulesapp.json create mode 100644 examples/statemachine/statemachine.png diff --git a/examples/flogo/trackntrace/README.md b/examples/flogo/statemachine/README.md similarity index 91% rename from examples/flogo/trackntrace/README.md rename to examples/flogo/statemachine/README.md index d240f9b..c1b3f1f 100644 --- a/examples/flogo/trackntrace/README.md +++ b/examples/flogo/statemachine/README.md @@ -1,6 +1,6 @@ -## Flogo Rules based Track and Trace +## Flogo Rules based State Machine -This example demonstrates the capability of rules to track and trace for a flogo app. In this example three tuples are used, tuples description is given below. +This example demonstrates the capability of rules to to process package states using a flogo app. In this example three tuples are used, tuples description is given below. ```json { @@ -64,7 +64,7 @@ Consider system having incoming packages. In order to move packages from source ### Package State Info

      - +

      In detail, above image represents state change for a given package. Consider insert package event this will insert a package into network with state as `normal`. This package now accepts only `sitting` event. When `sitting` event is triggered, a 10 seconds timer is created to trigger `delayed` event, within 10s only `moving` event can cancel the timer and state is changed to `moving` otherwise `delayed` event gets triggered. If `dropped` event occurs on a package with `moving` state, then package state is changed to `dropped`. Any package with state as `dropped` or `delayed` is retracted from network. You have to insert the package again to use. @@ -85,19 +85,19 @@ Get the repo and in this example `main.go`, `functions.go` both are available. W ### Direct build and run ```sh -cd $GOPATH/src/github.com/project-flogo/rules/examples/flogo/trackntrace +cd $GOPATH/src/github.com/project-flogo/rules/examples/flogo/statemachine go build -./trackntrace +./statemachine ``` ### Create app using flogo cli ```sh -cd $GOPATH/src/github.com/project-flogo/rules/examples/flogo/trackntrace -flogo create -f flogo.json trackNTraceApp -cp functions.go trackNTraceApp/src -cd trackNTraceApp +cd $GOPATH/src/github.com/project-flogo/rules/examples/flogo/statemachine +flogo create -f flogo.json statemachine +cp functions.go statemachine/src +cd statemachine flogo build -./bin/trackNTraceApp +./bin/statemachine ``` ## Moveevent test diff --git a/examples/flogo/trackntrace/flogo.json b/examples/flogo/statemachine/flogo.json similarity index 99% rename from examples/flogo/trackntrace/flogo.json rename to examples/flogo/statemachine/flogo.json index 1d53f8a..d379c21 100644 --- a/examples/flogo/trackntrace/flogo.json +++ b/examples/flogo/statemachine/flogo.json @@ -1,5 +1,5 @@ { - "name": "trackntrace", + "name": "statemachine", "type": "flogo:app", "version": "0.0.1", "appModel": "1.0.0", diff --git a/examples/flogo/trackntrace/functions.go b/examples/flogo/statemachine/functions.go similarity index 100% rename from examples/flogo/trackntrace/functions.go rename to examples/flogo/statemachine/functions.go diff --git a/examples/flogo/trackntrace/main.go b/examples/flogo/statemachine/main.go similarity index 100% rename from examples/flogo/trackntrace/main.go rename to examples/flogo/statemachine/main.go diff --git a/examples/flogo/trackntrace/trackntrace.png b/examples/flogo/statemachine/statemachine.png similarity index 100% rename from examples/flogo/trackntrace/trackntrace.png rename to examples/flogo/statemachine/statemachine.png diff --git a/examples/flogo/trackntrace/trackntrace_test.go b/examples/flogo/statemachine/statemachine_test.go similarity index 98% rename from examples/flogo/trackntrace/trackntrace_test.go rename to examples/flogo/statemachine/statemachine_test.go index 3186dcd..0218606 100644 --- a/examples/flogo/trackntrace/trackntrace_test.go +++ b/examples/flogo/statemachine/statemachine_test.go @@ -100,7 +100,7 @@ func testApplication(t *testing.T, e engine.Engine) { result = "" } -func TestTracknTraceJSON(t *testing.T) { +func TestStateMachineJSON(t *testing.T) { if testing.Short() { t.Skip("skipping Handler Routing JSON integration test in short mode") } diff --git a/examples/simple/README.md b/examples/simple/README.md new file mode 100644 index 0000000..d317b71 --- /dev/null +++ b/examples/simple/README.md @@ -0,0 +1,31 @@ + +## Installation + +### Prerequisites +* The Go programming language version 1.8 or later should be [installed](https://golang.org/doc/install). +* The **GOPATH** environment variable on your system must be set properly +* Docker + +## Setup and Usage + +The following conditions are used in the example: + +* `checkForBob`: Checks if the `n1` tuple name is Bob +* `checkSameNamesCondition`: Checks if the name in `n1` tuple is same as name in `n2` tuple +* `checkSameEnvName`: Checks if the name in `n1` tuple matches the value stored in environment variable `name` + +The following rules are used in the example: + +* `checkForBobAction`: Gets fired when `checkForBob` is true +* `checkSameNamesAction`: Gets fired when `checkForBob` and `checkSameNamesCondition` conditions are true +* `checkSameEnvNameAction`: Gets fired when `checkSameEnvName` condition is true + +Run the example: + +``` +docker run -p 6383:6379 -d redis +go get -u github.com/project-flogo/rules/... +cd $GOPATH/src/github.com/project-flogo/rules/examples/simple +export name=Smith +go run main.go +``` \ No newline at end of file diff --git a/examples/simple/main.go b/examples/simple/main.go new file mode 100644 index 0000000..338d7d1 --- /dev/null +++ b/examples/simple/main.go @@ -0,0 +1,362 @@ +package main + +import ( + "context" + "fmt" + "os" + + "github.com/project-flogo/rules/common" + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/config" + "github.com/project-flogo/rules/ruleapi" +) + +func main() { + err := example(true) + if err != nil { + panic(err) + } +} + +func example(redis bool) error { + //Load the tuple descriptor file (relative to GOPATH) + tupleDescAbsFileNm := common.GetPathForResource("examples/rulesapp/rulesapp.json", "./rulesapp.json") + tupleDescriptor := common.FileToString(tupleDescAbsFileNm) + + //First register the tuple descriptors + err := model.RegisterTupleDescriptors(tupleDescriptor) + if err != nil { + return err + } + + //Create a RuleSession + store := "" + if redis { + store = "rsconfig.json" + } + rs, err := ruleapi.GetOrCreateRuleSession("asession", store) + if err != nil { + return err + } + + events := make(map[string]int, 8) + //// check for name "Bob" in n1 + rule := ruleapi.NewRule("n1.name == Bob") + err = rule.AddCondition("c1", []string{"n1"}, checkForBob, events) + if err != nil { + return err + } + serviceCfg := &config.ServiceDescriptor{ + Name: "checkForBobAction", + Function: checkForBobAction, + Type: "function", + } + aService, err := ruleapi.NewActionService(serviceCfg) + if err != nil { + return err + } + rule.SetActionService(aService) + rule.SetContext(events) + err = rs.AddRule(rule) + if err != nil { + return err + } + + // check for name "Bob" in n1, match the "name" field in n2, + // in effect, fire the rule when name field in both tuples is "Bob" + rule2 := ruleapi.NewRule("n1.name == Bob && n1.name == n2.name") + err = rule2.AddCondition("c1", []string{"n1"}, checkForBob, events) + if err != nil { + return err + } + err = rule2.AddCondition("c2", []string{"n1", "n2"}, checkSameNamesCondition, events) + if err != nil { + return err + } + serviceCfg2 := &config.ServiceDescriptor{ + Name: "checkSameNamesAction", + Function: checkSameNamesAction, + Type: "function", + } + aService2, err := ruleapi.NewActionService(serviceCfg2) + if err != nil { + return err + } + rule2.SetActionService(aService2) + rule2.SetContext(events) + err = rs.AddRule(rule2) + if err != nil { + return err + } + + // check for name in n1, match the env variable "name" + rule3 := ruleapi.NewRule("n1.name == envname") + err = rule3.AddCondition("c1", []string{"n1"}, checkSameEnvName, events) + if err != nil { + return err + } + serviceCfg3 := &config.ServiceDescriptor{ + Name: "checkSameEnvNameAction", + Function: checkSameEnvNameAction, + Type: "function", + } + aService3, err := ruleapi.NewActionService(serviceCfg3) + if err != nil { + return err + } + rule3.SetActionService(aService3) + rule3.SetContext(events) + err = rs.AddRule(rule3) + if err != nil { + return err + } + + //set a transaction handler + rs.RegisterRtcTransactionHandler(txHandler, nil) + //Start the rule session + err = rs.Start(nil) + if err != nil { + return err + } + + //Now assert a "n1" tuple + t1, err := model.NewTupleWithKeyValues("n1", "Tom") + if err != nil { + return err + } + t1.SetString(nil, "name", "Tom") + err = rs.Assert(nil, t1) + if err != nil { + return err + } + t11 := rs.GetStore().GetTupleByKey(t1.GetKey()) + if t11 == nil { + return fmt.Errorf("Warn: Tuple should be in store[%s]", t11.GetKey()) + } + + //Now assert a "n1" tuple + t2, err := model.NewTupleWithKeyValues("n1", "Bob") + if err != nil { + return err + } + t2.SetString(nil, "name", "Bob") + err = rs.Assert(nil, t2) + if err != nil { + return err + } + + //Now assert a "n2" tuple + t3, err := model.NewTupleWithKeyValues("n2", "Bob") + if err != nil { + return err + } + t3.SetString(nil, "name", "Bob") + err = rs.Assert(nil, t3) + if err != nil { + return err + } + + //Now assert a "n1" tuple + t4, err := model.NewTupleWithKeyValues("n1", "Smith") + if err != nil { + return err + } + t4.SetString(nil, "name", "Smith") + err = rs.Assert(nil, t4) + if err != nil { + return err + } + + //Retract tuples + err = rs.Retract(nil, t1) + if err != nil { + return err + } + err = rs.Retract(nil, t2) + if err != nil { + return err + } + err = rs.Retract(nil, t3) + if err != nil { + return err + } + err = rs.Retract(nil, t4) + if err != nil { + return err + } + + //delete the rule + rs.DeleteRule(rule2.GetName()) + + //unregister the session, i.e; cleanup + rs.Unregister() + + if events["checkForBob"] != 6 { + return fmt.Errorf("checkForBob should have been called 6 times") + } + if events["checkForBobAction"] != 1 { + return fmt.Errorf("checkForBobAction should have been called once") + } + if events["checkSameNamesCondition"] != 1 { + return fmt.Errorf("checkSameNamesCondition should have been called once") + } + if events["checkSameNamesAction"] != 1 { + return fmt.Errorf("checkSameNamesAction should have been called once") + } + if events["checkSameEnvName"] != 3 { + return fmt.Errorf("checkSameEnvName should have been called thrice") + } + if events["checkSameEnvNameAction"] != 1 { + return fmt.Errorf("checkSameEnvNameAction should have been called once") + } + + return nil +} + +func checkForBob(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + //This conditions filters on name="Bob" + //fmt.Println("checkForBob") + t1 := tuples["n1"] + if t1 == nil { + return false + } + name, err := t1.GetString("name") + if err != nil { + return false + } + if name == "" { + return false + } + events := ctx.(map[string]int) + count := events["checkForBob"] + events["checkForBob"] = count + 1 + return name == "Bob" +} + +func checkForBobAction(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + //fmt.Println("checkForBobAction") + t1 := tuples["n1"] + if t1 == nil { + return + } + name, err := t1.GetString("name") + if err != nil { + return + } + if name == "" { + return + } + fmt.Println("Rule checkForBobAction is fired") + events := ruleCtx.(map[string]int) + count := events["checkForBobAction"] + events["checkForBobAction"] = count + 1 +} + +func checkSameNamesCondition(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + //fmt.Println("checkSameNamesCondition") + t1 := tuples["n1"] + t2 := tuples["n2"] + if t1 == nil || t2 == nil { + return false + } + name1, err := t1.GetString("name") + if err != nil { + return false + } + if name1 == "" { + return false + } + name2, err := t2.GetString("name") + if err != nil { + return false + } + if name2 == "" { + return false + } + events := ctx.(map[string]int) + count := events["checkSameNamesCondition"] + events["checkSameNamesCondition"] = count + 1 + return name1 == name2 +} + +func checkSameNamesAction(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + //fmt.Println("checkSameNamesAction") + t1 := tuples["n1"] + t2 := tuples["n2"] + if t1 == nil || t2 == nil { + return + } + name1, err := t1.GetString("name") + if err != nil { + return + } + if name1 == "" { + return + } + name2, err := t2.GetString("name") + if err != nil { + return + } + if name2 == "" { + return + } + fmt.Println("Rule checkSameNamesAction is fired") + events := ruleCtx.(map[string]int) + count := events["checkSameNamesAction"] + events["checkSameNamesAction"] = count + 1 +} + +func checkSameEnvName(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + //fmt.Println("checkSameEnvName") + t1 := tuples["n1"] + if t1 == nil { + return false + } + name, err := t1.GetString("name") + if err != nil { + return false + } + if name == "" { + return false + } + name1 := os.Getenv("name") + events := ctx.(map[string]int) + count := events["checkSameEnvName"] + events["checkSameEnvName"] = count + 1 + return name == name1 +} + +func checkSameEnvNameAction(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + + t1 := tuples["n1"] + if t1 == nil { + return + } + name, err := t1.GetString("name") + if err != nil { + return + } + if name == "" { + return + } + fmt.Println("Rule checkSameEnvNameAction is fired") + events := ruleCtx.(map[string]int) + count := events["checkSameEnvNameAction"] + events["checkSameEnvNameAction"] = count + 1 +} + +func getFileContent(filePath string) string { + absPath := common.GetAbsPathForResource(filePath) + return common.FileToString(absPath) +} + +func txHandler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { + + store := rs.GetStore() + store.SaveTuples(rtxn.GetRtcAdded()) + + store.SaveModifiedTuples(rtxn.GetRtcModified()) + + store.DeleteTuples(rtxn.GetRtcDeleted()) + +} diff --git a/examples/simple/rsconfig.json b/examples/simple/rsconfig.json new file mode 100644 index 0000000..fae556a --- /dev/null +++ b/examples/simple/rsconfig.json @@ -0,0 +1,36 @@ +{ + "mode": "consistency", + "rs": { + "prefix": "x", + "store-ref": "redis" + }, + "rete": { + "jt-ref": "redis", + "idgen-ref": "redis", + "jt":"redis" + }, + "stores": { + "mem": { + }, + "redis": { + "network": "tcp", + "address": ":6383" + } + }, + "idgens": { + "mem": { + }, + "redis": { + "network": "tcp", + "address": ":6383" + } + }, + "jts": { + "mem": { + }, + "redis": { + "network": "tcp", + "address": ":6383" + } + } +} diff --git a/examples/simple/rulesapp.json b/examples/simple/rulesapp.json new file mode 100644 index 0000000..452c470 --- /dev/null +++ b/examples/simple/rulesapp.json @@ -0,0 +1,22 @@ +[ + { + "name": "n1", + "properties": [ + { + "name": "name", + "type": "string", + "pk-index": 0 + } + ] + }, + { + "name": "n2", + "properties": [ + { + "name": "name", + "type": "string", + "pk-index": 0 + } + ] + } + ] \ No newline at end of file diff --git a/examples/statemachine/README.md b/examples/statemachine/README.md new file mode 100644 index 0000000..1a186cb --- /dev/null +++ b/examples/statemachine/README.md @@ -0,0 +1,111 @@ +## Rulesapi based State Machine + +This example demonstrates the capability of rules to process package states. In this example three tuples are used, tuples description is given below. + +```json +{ + "name": "package", + "properties": [ + { + "name": "id", + "type": "string", + "pk-index": 0 + }, + { + "name": "state", + "type": "string" + } + ] +}, +{ + "name": "moveevent", + "ttl": 0, + "properties": [ + { + "name": "id", + "type": "string", + "pk-index": 0 + }, + { + "name": "packageid", + "type": "string" + }, + { + "name": "targetstate", + "type": "string" + } + ] +}, +{ + "name": "movetimeoutevent", + "ttl": 0, + "properties": [ + { + "name": "id", + "type": "string", + "pk-index": 0 + }, + { + "name": "packageid", + "type": "string" + }, + { + "name": "timeoutinmillis", + "type": "integer" + } + ] +} +``` + +`package` tuple is always stored in network, while the other tuples `moveevent` and `movetimeoutevent` are removed after usage as `ttl` is given as `0`. During startup `PACKAGE1` is asserted into network. + +Consider system having incoming packages. In order to move packages from source to destination we have steps like sitting,moving,dropped and delayed. When the first event sitting comes, package is scheduled to 10s timeout. Within 10s if we receive moving event then scheduled timeout event will get canceled. Otherwise package is marked as delayed and retracted from network. + +### Package State Info + +

      + +

      + +In detail, above image represents state change for a given package. Consider insert package event this will insert a package into network with state as `normal`. This package now accepts only `sitting` event. When `sitting` event is triggered, a 10 seconds timer is created to trigger `delayed` event, within 10s only `moving` event can cancel the timer and state is changed to `moving` otherwise `delayed` event gets triggered. If `dropped` event occurs on a package with `moving` state, then package state is changed to `dropped`. Any package with state as `dropped` or `delayed` is retracted from network. You have to insert the package again to use. + +### Actions used here + +`aJoinMoveEventAndPackage` : If targetstate value sitting then package is scheduled to movetimeoutevent by 10 seconds.
      +`aPrintMoveEvent`: Prints received moveevents and store packages into network based on targetstate.
      +`aMoveTimeoutEvent`: Prints received movetimeoutevent.
      +`aJoinMoveTimeoutEventAndPackage`: Package is modified to moveevent target state.
      +`aPackageInSitting`: Prints package as sitting.
      +`aPackageInMoving`: Prints package as moving.
      +`aPackageInDropped`: Prints package as dropped and retracts package tuple from network.
      +`aPackageInDelayed`: Prints package as delayed and retracts package tuple from network. + +## Run the example + +The example contains `main.go` and `rulesapp.json`. To run the application: +``` +go run main.go +``` + +In the given example 2 packages PACKAGE1 and PACKAGE2 are inserted. +The PACKAGE1 is asserted into the Rulesession with `normal` state. The package is further moved to `sitting`, `moving` and `dropped` states.When the package reaches the dropped state the package is removed from the rulesession. +The PACKAGE2 is asserted into the Rulesession with `normal` state. The package is then moved to sitting state. After 10 seconds a movetimeout event is asserted and the package state is changed to `delayed`. + +Below is the final output for package PACKAGE2: + +``` +Received a 'moveevent' [PACKAGE2] target state [dropped] +Joining a 'moveevent' with packageid [PACKAGE2] to package [PACKAGE2], target state [dropped] +PACKAGE [PACKAGE2] is Dropped +Saving tuple. Type [package] Key [package:id:PACKAGE2], Val [&{package map[id:PACKAGE2 state:dropped] 0xc0000bad20 0xc0000c1720}] +``` + + +Below is the final output for package PACKAGE1: + +``` +Received a 'movetimeoutevent' id [01E4JW2J1V9AEJJ1KTTGXAAW54], packageid [PACKAGE1], timeoutinmillis [10000] +Joining a 'movetimeoutevent' [PACKAGE1] to package [PACKAGE1], timeout [10000] +PACKAGE [PACKAGE1] is Delayed +Saving tuple. Type [package] Key [package:id:PACKAGE1], Val [&{package map[id:PACKAGE1 state:delayed] 0xc0000baa20 0xc0000c0c80}] +``` \ No newline at end of file diff --git a/examples/statemachine/main.go b/examples/statemachine/main.go new file mode 100644 index 0000000..737f9c4 --- /dev/null +++ b/examples/statemachine/main.go @@ -0,0 +1,598 @@ +package main + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/project-flogo/rules/common" + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/config" + "github.com/project-flogo/rules/ruleapi" +) + +func main() { + err := example(false) + if err != nil { + panic(err) + } +} + +var ( + currentEventType string +) + +func example(redis bool) error { + //Load the tuple descriptor file (relative to GOPATH) + tupleDescAbsFileNm := common.GetPathForResource("examples/statemachine/rulesapp.json", "./rulesapp.json") + tupleDescriptor := common.FileToString(tupleDescAbsFileNm) + currentEventType = "none" + //First register the tuple descriptors + err := model.RegisterTupleDescriptors(tupleDescriptor) + if err != nil { + return err + } + + //Create a RuleSession + store := "" + if redis { + store = "rsconfig.json" + } + rs, err := ruleapi.GetOrCreateRuleSession("asession", store) + if err != nil { + return err + } + + events := make(map[string]int, 8) + + //// check if the packaage is in sitting state + rule := ruleapi.NewRule("cPackageInSittingRule") + err = rule.AddCondition("c1", []string{"package.state"}, cPackageInSitting, events) + if err != nil { + return err + } + serviceCfg := &config.ServiceDescriptor{ + Name: "aPackageInSitting", + Function: aPackageInSitting, + Type: "function", + } + aService, err := ruleapi.NewActionService(serviceCfg) + if err != nil { + return err + } + rule.SetActionService(aService) + rule.SetContext(events) + rule.SetPriority(1) + err = rs.AddRule(rule) + if err != nil { + return err + } + + //// check if the packaage is in Delayed state + rule2 := ruleapi.NewRule("packageInDelayedRule") + err = rule2.AddCondition("c1", []string{"package.state"}, cPackageInDelayed, events) + if err != nil { + return err + } + serviceCfg2 := &config.ServiceDescriptor{ + Name: "aPackageInDelayed", + Function: aPackageInDelayed, + Type: "function", + } + aService2, err := ruleapi.NewActionService(serviceCfg2) + if err != nil { + return err + } + rule2.SetActionService(aService2) + rule2.SetContext(events) + rule2.SetPriority(1) + err = rs.AddRule(rule2) + if err != nil { + return err + } + + //// check if the packaage is in moving state + rule3 := ruleapi.NewRule("packageInMovingRule") + err = rule3.AddCondition("c1", []string{"package.state"}, cPackageInMoving, events) + if err != nil { + return err + } + serviceCfg3 := &config.ServiceDescriptor{ + Name: "aPackageInMoving", + Function: aPackageInMoving, + Type: "function", + } + aService3, err := ruleapi.NewActionService(serviceCfg3) + if err != nil { + return err + } + rule3.SetActionService(aService3) + rule3.SetContext(events) + rule3.SetPriority(1) + err = rs.AddRule(rule3) + if err != nil { + return err + } + + //// check if the packaage is in Dropped state + rule4 := ruleapi.NewRule("packageInDroppedRule") + err = rule4.AddCondition("c1", []string{"package.state"}, cPackageInDropped, events) + if err != nil { + return err + } + serviceCfg4 := &config.ServiceDescriptor{ + Name: "aPackageInDropped", + Function: aPackageInDropped, + Type: "function", + } + aService4, err := ruleapi.NewActionService(serviceCfg4) + if err != nil { + return err + } + rule4.SetActionService(aService4) + rule4.SetContext(events) + rule4.SetPriority(1) + err = rs.AddRule(rule4) + if err != nil { + return err + } + + //// check if the packaage is in normal state and print + rule5 := ruleapi.NewRule("printPackageRule") + err = rule5.AddCondition("c1", []string{"package"}, cPackageEvent, events) + if err != nil { + return err + } + serviceCfg5 := &config.ServiceDescriptor{ + Name: "aPrintPackage", + Function: aPrintPackage, + Type: "function", + } + aService5, err := ruleapi.NewActionService(serviceCfg5) + if err != nil { + return err + } + rule5.SetActionService(aService5) + rule5.SetContext(events) + rule5.SetPriority(2) + err = rs.AddRule(rule5) + if err != nil { + return err + } + + // check for moveevent + rule6 := ruleapi.NewRule("printMoveEventRule") + err = rule6.AddCondition("c1", []string{"moveevent"}, cMoveEvent, events) + if err != nil { + return err + } + serviceCfg6 := &config.ServiceDescriptor{ + Name: "aPrintMoveEvent", + Function: aPrintMoveEvent, + Type: "function", + } + aService6, err := ruleapi.NewActionService(serviceCfg6) + if err != nil { + return err + } + rule6.SetActionService(aService6) + rule6.SetContext(events) + rule6.SetPriority(3) + err = rs.AddRule(rule6) + if err != nil { + return err + } + + // check if the package exists for received moveevent and join it with the package + rule7 := ruleapi.NewRule("joinMoveEventAndPackageEventRule") + err = rule7.AddCondition("c1", []string{"moveevent", "package"}, cMoveEventPkg, events) + if err != nil { + return err + } + serviceCfg7 := &config.ServiceDescriptor{ + Name: "aJoinMoveEventAndPackage", + Function: aJoinMoveEventAndPackage, + Type: "function", + } + aService7, err := ruleapi.NewActionService(serviceCfg7) + if err != nil { + return err + } + rule7.SetActionService(aService7) + rule7.SetContext(events) + rule7.SetPriority(4) + err = rs.AddRule(rule7) + if err != nil { + return err + } + + // check for movetimeoutevent for package + rule8 := ruleapi.NewRule("aMoveTimeoutEventRule") + err = rule8.AddCondition("c1", []string{"movetimeoutevent"}, cMoveTimeoutEvent, events) + if err != nil { + return err + } + serviceCfg8 := &config.ServiceDescriptor{ + Name: "aMoveTimeoutEvent", + Function: aMoveTimeoutEvent, + Type: "function", + } + aService8, err := ruleapi.NewActionService(serviceCfg8) + if err != nil { + return err + } + rule8.SetActionService(aService8) + rule8.SetContext(events) + rule8.SetPriority(5) + err = rs.AddRule(rule8) + if err != nil { + return err + } + + //Join movetimeoutevent and package + rule9 := ruleapi.NewRule("joinMoveTimeoutEventAndPackage") + err = rule9.AddCondition("c1", []string{"movetimeoutevent", "package"}, cMoveTimeoutEventPkg, events) + if err != nil { + return err + } + serviceCfg9 := &config.ServiceDescriptor{ + Name: "aJoinMoveTimeoutEventAndPackage", + Function: aJoinMoveTimeoutEventAndPackage, + Type: "function", + } + aService9, err := ruleapi.NewActionService(serviceCfg9) + if err != nil { + return err + } + rule9.SetActionService(aService9) + rule9.SetContext(events) + rule9.SetPriority(6) + err = rs.AddRule(rule9) + if err != nil { + return err + } + + rs.SetStartupFunction(AssertThisPackage) + + //set a transaction handler + rs.RegisterRtcTransactionHandler(txHandler, nil) + //Start the rule session + err = rs.Start(nil) + if err != nil { + return err + } + + t6, err := model.NewTupleWithKeyValues("moveevent", "PACKAGE1") + if err != nil { + return err + } + t6.SetString(nil, "packageid", "PACKAGE1") + t6.SetString(nil, "targetstate", "sitting") + err = rs.Assert(nil, t6) + if err != nil { + return err + } + + finished := make(chan bool) + go sleepfunc(finished) + + t1, err := model.NewTupleWithKeyValues("moveevent", "PACKAGE2") + if err != nil { + return err + } + t1.SetString(nil, "packageid", "PACKAGE2") + t1.SetString(nil, "targetstate", "normal") + err = rs.Assert(nil, t1) + if err != nil { + return err + } + fmt.Println("package 2", t1) + + t2, err := model.NewTupleWithKeyValues("moveevent", "PACKAGE2") + if err != nil { + return err + } + t2.SetString(nil, "packageid", "PACKAGE2") + t2.SetString(nil, "targetstate", "sitting") + err = rs.Assert(nil, t2) + if err != nil { + return err + } + + t3, err := model.NewTupleWithKeyValues("moveevent", "PACKAGE2") + if err != nil { + return err + } + t3.SetString(nil, "packageid", "PACKAGE2") + t3.SetString(nil, "targetstate", "moving") + err = rs.Assert(nil, t3) + if err != nil { + return err + } + + t4, err := model.NewTupleWithKeyValues("moveevent", "PACKAGE2") + if err != nil { + return err + } + t4.SetString(nil, "packageid", "PACKAGE2") + t4.SetString(nil, "targetstate", "dropped") + err = rs.Assert(nil, t4) + if err != nil { + return err + } + + <-finished + + //delete the rule + rs.DeleteRule(rule2.GetName()) + + //unregister the session + rs.Unregister() + + return nil +} + +func AssertThisPackage(ctx context.Context, rs model.RuleSession, startupCtx map[string]interface{}) (err error) { + fmt.Printf("In startup rule function..\n") + pkg, _ := model.NewTupleWithKeyValues("package", "PACKAGE1") + pkg.SetString(nil, "state", "normal") + rs.Assert(nil, pkg) + return nil +} + +func cPackageEvent(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + pkg := tuples["package"] + if pkg != nil { + state, _ := pkg.GetString("state") + return state == "normal" + } + return false +} + +func aPrintPackage(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + pkg := tuples["package"] + pkgid, _ := pkg.GetString("id") + fmt.Printf("Received package [%s]\n", pkgid) +} + +func aPrintMoveEvent(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + me := tuples["moveevent"] + meid, _ := me.GetString("id") + s, _ := me.GetString("targetstate") + pkgID, _ := me.GetString("packageid") + + fmt.Printf("Received a 'moveevent' [%s] target state [%s]\n", meid, s) + + if s == "normal" { + pkg, _ := model.NewTupleWithKeyValues("package", pkgID) + pkg.SetString(nil, "state", "normal") + err := rs.Assert(ctx, pkg) + if err != nil { + fmt.Println("Tuple already inserted: ", pkgID) + } else { + fmt.Println("Tuple inserted successfully: ", pkgID) + } + } + +} + +func aJoinMoveEventAndPackage(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + me := tuples["moveevent"] + mepkgid, _ := me.GetString("packageid") + s, _ := me.GetString("targetstate") + + pkg := tuples["package"] + pkgid, _ := pkg.GetString("id") + pkgState, _ := pkg.GetString("state") + + if strings.Compare("sitting", s) == 0 { + currentEventType = "sitting" + } else { + currentEventType = "" + } + + if currentEventType == "sitting" { + + if pkgState == "normal" { + fmt.Printf("Joining a 'moveevent' with packageid [%s] to package [%s], target state [%s]\n", mepkgid, pkgid, s) + + //change the package's state to "sitting" + pkgMutable := pkg.(model.MutableTuple) + pkgMutable.SetString(ctx, "state", "sitting") + + //very first sitting event since the last notsitting event. + id, _ := common.GetUniqueId() + timeoutEvent, _ := model.NewTupleWithKeyValues("movetimeoutevent", id) + timeoutEvent.SetString(ctx, "packageid", pkgid) + timeoutEvent.SetInt(ctx, "timeoutinmillis", 10000) + fmt.Printf("Starting a 10s timer.. [%s]\n", pkgid) + rs.ScheduleAssert(ctx, 10000, pkgid, timeoutEvent) + } + } else { + if strings.Compare("moving", s) == 0 && pkgState == "sitting" { + + fmt.Printf("Joining a 'moveevent' with packageid [%s] to package [%s], target state [%s]\n", mepkgid, pkgid, s) + + //a non-sitting event, cancel a previous timer + rs.CancelScheduledAssert(ctx, pkgid) + + pkgMutable := pkg.(model.MutableTuple) + pkgMutable.SetString(ctx, "state", "moving") + + } else if strings.Compare("dropped", s) == 0 && pkgState == "moving" { + + fmt.Printf("Joining a 'moveevent' with packageid [%s] to package [%s], target state [%s]\n", mepkgid, pkgid, s) + + pkgMutable := pkg.(model.MutableTuple) + pkgMutable.SetString(ctx, "state", "dropped") + + } + + } + +} + +func aMoveTimeoutEvent(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + t1 := tuples["movetimeoutevent"] + id, _ := t1.GetString("id") + pkgid, _ := t1.GetString("packageid") + tomillis, _ := t1.GetInt("timeoutinmillis") + + if t1 != nil { + fmt.Printf("Received a 'movetimeoutevent' id [%s], packageid [%s], timeoutinmillis [%d]\n", id, pkgid, tomillis) + } +} + +func aJoinMoveTimeoutEventAndPackage(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + me := tuples["movetimeoutevent"] + //meid, _ := me.GetString("id") + epkgid, _ := me.GetString("packageid") + toms, _ := me.GetInt("timeoutinmillis") + + pkg := tuples["package"].(model.MutableTuple) + pkgid, _ := pkg.GetString("id") + + fmt.Printf("Joining a 'movetimeoutevent' [%s] to package [%s], timeout [%d]\n", epkgid, pkgid, toms) + + //change the package's state to "delayed" + pkg.SetString(ctx, "state", "delayed") +} + +func cPackageInSitting(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + pkg := tuples["package"] + if pkg != nil { + //pkgid, _ := pkg.GetString("id") + state, _ := pkg.GetString("state") + return state == "sitting" + } + return false +} + +func aPackageInSitting(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + pkg := tuples["package"] + if pkg != nil { + pkgid, _ := pkg.GetString("id") + fmt.Printf("PACKAGE [%s] is Sitting\n", pkgid) + } +} + +func cMoveEvent(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + mpkg := tuples["moveevent"] + if mpkg != nil { + mpkgid, _ := mpkg.GetString("packageid") + return len(mpkgid) != 0 + } + return false +} + +func cMoveTimeoutEvent(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + mpkg := tuples["movetimeoutevent"] + if mpkg != nil { + mpkgid, _ := mpkg.GetString("packageid") + return len(mpkgid) != 0 + } + return false +} + +func cMoveTimeoutEventPkg(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + mpkg := tuples["movetimeoutevent"] + pkg := tuples["package"] + if mpkg != nil { + if pkg != nil { + pkgid, _ := pkg.GetString("id") + mpkgid, _ := mpkg.GetString("packageid") + return pkgid == mpkgid + } + } + return false +} + +func cMoveEventPkg(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + mpkg := tuples["moveevent"] + pkg := tuples["package"] + if mpkg != nil { + if pkg != nil { + pkgid, _ := pkg.GetString("id") + mpkgid, _ := mpkg.GetString("packageid") + return pkgid == mpkgid + } + } + return false +} + +func cPackageInDelayed(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + pkg := tuples["package"] + if pkg != nil { + //pkgid, _ := pkg.GetString("id") + state, _ := pkg.GetString("state") + return state == "delayed" + } + return false +} + +func aPackageInDelayed(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + pkg := tuples["package"] + if pkg != nil { + pkgid, _ := pkg.GetString("id") + fmt.Printf("PACKAGE [%s] is Delayed\n", pkgid) + rs.Retract(ctx, pkg) + } +} + +func cPackageInMoving(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + pkg := tuples["package"] + if pkg != nil { + //pkgid, _ := pkg.GetString("id") + state, _ := pkg.GetString("state") + return state == "moving" + } + return false +} + +func aPackageInMoving(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + pkg := tuples["package"] + if pkg != nil { + pkgid, _ := pkg.GetString("id") + fmt.Printf("PACKAGE [%s] is Moving\n", pkgid) + } +} + +func cPackageInDropped(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + pkg := tuples["package"] + if pkg != nil { + //pkgid, _ := pkg.GetString("id") + state, _ := pkg.GetString("state") + return state == "dropped" + } + return false +} + +func aPackageInDropped(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + pkg := tuples["package"] + if pkg != nil { + pkgid, _ := pkg.GetString("id") + fmt.Printf("PACKAGE [%s] is Dropped\n", pkgid) + rs.Retract(ctx, pkg) + } +} + +func getFileContent(filePath string) string { + absPath := common.GetAbsPathForResource(filePath) + return common.FileToString(absPath) +} + +func txHandler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { + + store := rs.GetStore() + store.SaveTuples(rtxn.GetRtcAdded()) + + store.SaveModifiedTuples(rtxn.GetRtcModified()) + + store.DeleteTuples(rtxn.GetRtcDeleted()) + +} +func sleepfunc(finished chan bool) { + time.Sleep(15 * time.Second) + finished <- true +} diff --git a/examples/statemachine/rulesapp.json b/examples/statemachine/rulesapp.json new file mode 100644 index 0000000..c0d5d25 --- /dev/null +++ b/examples/statemachine/rulesapp.json @@ -0,0 +1,54 @@ +[ + { + "name": "package", + "properties": [ + { + "name": "id", + "type": "string", + "pk-index": 0 + }, + { + "name": "state", + "type": "string" + } + ] + }, + { + "name": "moveevent", + "ttl": 0, + "properties": [ + { + "name": "id", + "type": "string", + "pk-index": 0 + }, + { + "name": "packageid", + "type": "string" + }, + { + "name": "targetstate", + "type": "string" + } + ] + }, + { + "name": "movetimeoutevent", + "ttl": 0, + "properties": [ + { + "name": "id", + "type": "string", + "pk-index": 0 + }, + { + "name": "packageid", + "type": "string" + }, + { + "name": "timeoutinmillis", + "type": "integer" + } + ] + } +] \ No newline at end of file diff --git a/examples/statemachine/statemachine.png b/examples/statemachine/statemachine.png new file mode 100644 index 0000000000000000000000000000000000000000..374bf34f84107398399a996e16a729564b3f309f GIT binary patch literal 36921 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 z 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 k=bvBtzhC!qc=Nwm`g8Rz>Mq~)KMtPd*Z=?k literal 0 HcmV?d00001 From 0a7f842843690d9ee3f9cbe488f15ed102aa07fc Mon Sep 17 00:00:00 2001 From: Naresh Kumar Thota <30426686+nareshkumarthota@users.noreply.github.com> Date: Tue, 31 Mar 2020 10:52:14 +0530 Subject: [PATCH 123/125] Feature Decision Table API model changes (#93) * dtable code refactoring to support api model * adding dtable ruleapi model example * adding dtable interface * example modified * decision table api model refactored and example modified * Adding Readme * Update Readme * go tests added * code refactoring Co-authored-by: ykalidin --- activity/dtable/activity.go | 362 +---------------- activity/dtable/activity_test.go | 54 --- config/config.go | 2 + examples/dtable/README.md | 29 ++ examples/dtable/dtable-file.xlsx | Bin 0 -> 5600 bytes examples/dtable/main.go | 170 ++++++++ examples/dtable/rulesapp.json | 39 ++ ruleapi/actionservice.go | 21 + ruleapi/dtable.go | 369 ++++++++++++++++++ ruleapi/dtable_test.go | 214 ++++++++++ {activity/dtable => ruleapi}/expression.go | 2 +- {activity/dtable => ruleapi}/expression.peg | 2 +- .../dtable => ruleapi}/expression.peg.go | 2 +- ruleapi/test_dtable.csv | 3 + ruleapi/test_dtable.xlsx | Bin 0 -> 9220 bytes 15 files changed, 854 insertions(+), 415 deletions(-) create mode 100644 examples/dtable/README.md create mode 100644 examples/dtable/dtable-file.xlsx create mode 100644 examples/dtable/main.go create mode 100644 examples/dtable/rulesapp.json create mode 100644 ruleapi/dtable.go create mode 100644 ruleapi/dtable_test.go rename {activity/dtable => ruleapi}/expression.go (99%) rename {activity/dtable => ruleapi}/expression.peg (98%) rename {activity/dtable => ruleapi}/expression.peg.go (99%) create mode 100644 ruleapi/test_dtable.csv create mode 100644 ruleapi/test_dtable.xlsx diff --git a/activity/dtable/activity.go b/activity/dtable/activity.go index d505978..4ae1ac1 100644 --- a/activity/dtable/activity.go +++ b/activity/dtable/activity.go @@ -2,73 +2,20 @@ package dtable import ( "context" - "encoding/csv" - "fmt" - "os" - "strconv" - "strings" - excelize "github.com/360EntSecGroup-Skylar/excelize/v2" "github.com/project-flogo/core/activity" - "github.com/project-flogo/core/data" "github.com/project-flogo/core/data/metadata" "github.com/project-flogo/rules/common/model" "github.com/project-flogo/rules/ruleapi" ) -// Decision table column types -type dtColType int8 - -const ( - ctID dtColType = iota // ID - ctCondition // condition - ctAction // action - ctDescription // Description - ctPriority // priority -) - func init() { _ = activity.Register(&Activity{}, New) } // Activity decision table based rule action type Activity struct { - dtable *dTable -} - -type dTable struct { - titleRow1 []genCell - titleRow2 []genCell - metaRow []metaCell - rows [][]*genCell -} - -type metaCell struct { - colType dtColType - tupleDesc *model.TupleDescriptor - propDesc *model.TuplePropertyDescriptor -} - -type genCell struct { - *metaCell - rawValue string - cdExpr string -} - -func (cell *genCell) compileExpr() { - rawValue := cell.rawValue - if len(rawValue) == 0 { - return - } - lhsToken := fmt.Sprintf("$.%s.%s", cell.tupleDesc.Name, cell.propDesc.Name) - expression := &Expr{Buffer: rawValue} - expression.Init() - expression.Expression.Init(rawValue) - if err := expression.Parse(); err != nil { - panic(err) - } - expression.Execute() - cell.cdExpr = expression.Evaluate(lhsToken) + dtable ruleapi.DecisionTable } // New creates new decision table activity @@ -81,12 +28,12 @@ func New(ctx activity.InitContext) (activity.Activity, error) { } // Read decision table from file - dtable, err := loadFromFile(settings.DTableFile) + dtable, err := ruleapi.LoadDecisionTableFromFile(settings.DTableFile) if err != nil { return nil, err } // dtable.print() - err = dtable.compile() + err = dtable.Compile() if err != nil { return nil, err } @@ -111,308 +58,7 @@ func (a *Activity) Eval(ctx activity.Context) (done bool, err error) { tuples := ctx.GetInput("tuples").(map[model.TupleType]model.Tuple) // evaluate decision table - a.dtable.apply(context, tuples) + a.dtable.Apply(context, tuples) return true, nil } - -func loadFromFile(fileName string) (*dTable, error) { - if fileName == "" { - return nil, fmt.Errorf("file name can't be empty") - } - tokens := strings.Split(fileName, ".") - fileExtension := tokens[len(tokens)-1] - - if fileExtension == "csv" || fileExtension == "CSV" { - return loadFromCSVFile(fileName) - } else if fileExtension == "xls" || fileExtension == "XLS" || fileExtension == "xlsx" || fileExtension == "XLSX" { - return loadFromXLSFile(fileName) - } - - return nil, fmt.Errorf("file[%s] extension not supported", fileName) -} - -// loadFromCSVFile loads decision table from CSV file -func loadFromCSVFile(fileName string) (*dTable, error) { - file, err := os.Open(fileName) - if err != nil { - return nil, fmt.Errorf("not able open the file [%s] - %s", fileName, err) - } - defer file.Close() - - lines, err := csv.NewReader(file).ReadAll() - if err != nil { - return nil, fmt.Errorf("not able read the file [%s] - %s", fileName, err) - } - - dtable := &dTable{} - dtable.rows = make([][]*genCell, len(lines)-2) - for i, line := range lines { - if i == 0 { - // title row 1 - dtable.titleRow1 = make([]genCell, len(line)) - for j, val := range line { - dtable.titleRow1[j].rawValue = val - } - continue - } - if i == 1 { - // title row 2 - dtable.titleRow2 = make([]genCell, len(line)) - for j, val := range line { - dtable.titleRow2[j].rawValue = val - } - continue - } - // other rows - row := make([]*genCell, len(line)) - for j, val := range line { - row[j] = &genCell{ - rawValue: val, - } - } - dtable.rows[i-2] = row - } - return dtable, nil -} - -// loadFromXLSFile loads decision table from Excel file -func loadFromXLSFile(fileName string) (*dTable, error) { - file, err := excelize.OpenFile(fileName) - if err != nil { - return nil, fmt.Errorf("not able open the file [%s] - %s", fileName, err) - } - rows, err := file.GetRows("DecisionTable") - if err != nil { - return nil, fmt.Errorf("DecisionTable worksheet not available in %s", fileName) - } - - // find titleRowIndex - titleRowIndex := 0 - for i, r := range rows { - if len(r) > 0 { - if r[0] == "DecisionTable" { - titleRowIndex = i + 2 - } - } - } - titleRowSize := len(rows[titleRowIndex]) - - dtable := &dTable{ - titleRow1: make([]genCell, titleRowSize), - titleRow2: make([]genCell, titleRowSize), - rows: make([][]*genCell, 1), - } - // title row 1 - for i, val := range rows[titleRowIndex] { - dtable.titleRow1[i].rawValue = val - } - // title row 2 - for i, val := range rows[titleRowIndex+1] { - dtable.titleRow2[i].rawValue = val - } - // other rows - for _, r := range rows[titleRowIndex+2:] { - if len(r) == 0 { - break - } - dtrow := make([]*genCell, titleRowSize) - for i, cell := range r { - dtrow[i] = &genCell{ - rawValue: cell, - } - } - dtable.rows = append(dtable.rows, dtrow) - } - - return dtable, nil -} - -func (dtable *dTable) compile() error { - // compute meta row from titleRow1 & titleRow2 - metaRow := make([]metaCell, len(dtable.titleRow1)) - dtable.metaRow = metaRow - // titleRow1 determines column type - for colIndex, cell := range dtable.titleRow1 { - if strings.Contains(cell.rawValue, "Id") { - metaRow[colIndex].colType = ctID - } else if strings.Contains(cell.rawValue, "Condition") { - metaRow[colIndex].colType = ctCondition - } else if strings.Contains(cell.rawValue, "Action") { - metaRow[colIndex].colType = ctAction - } else if strings.Contains(cell.rawValue, "Description") { - metaRow[colIndex].colType = ctDescription - } else if strings.Contains(cell.rawValue, "Priority") { - metaRow[colIndex].colType = ctPriority - } else { - return fmt.Errorf("unknown column type - %s", cell.rawValue) - } - } - // titleRow2 determines tuple type & property - for colIndex, cell := range dtable.titleRow2 { - if cell.rawValue == "" { - continue - } - tokens := strings.Split(cell.rawValue, ".") - if len(tokens) != 2 { - return fmt.Errorf("[%s] is not a valid tuple property representation", cell.rawValue) - } - tupleType := tokens[0] - propName := tokens[1] - tupleDesc := model.GetTupleDescriptor(model.TupleType(tupleType)) - if tupleDesc == nil { - return fmt.Errorf("tuple type[%s] is not recognised", tupleType) - } - propDesc := tupleDesc.GetProperty(propName) - if propDesc == nil { - return fmt.Errorf("property[%s] is not a valid property for the tuple type[%s]", propName, tupleType) - } - metaRow[colIndex].tupleDesc = tupleDesc - metaRow[colIndex].propDesc = propDesc - } - // process all rows - for _, row := range dtable.rows { - for colIndex, cell := range row { - if cell == nil { - continue - } - cell.metaCell = &metaRow[colIndex] - if cell.colType == ctCondition { - cell.compileExpr() - } - } - } - return nil -} - -func (dtable *dTable) apply(ctx context.Context, tuples map[model.TupleType]model.Tuple) { - // process all rows - for _, row := range dtable.rows { - // process row conditions - rowTruthiness := true - for _, cell := range row { - if cell == nil { - continue - } - if cell.colType == ctCondition { - cellTruthiness := evaluateExpression(cell.cdExpr, tuples) - rowTruthiness = rowTruthiness && cellTruthiness - if !rowTruthiness { - break - } - } - } - // process row actions if all row conditions are evaluated to true - if rowTruthiness { - for _, cell := range row { - if cell == nil { - continue - } - if cell.colType == ctAction { - updateTuple(ctx, tuples, cell.tupleDesc.Name, cell.propDesc.Name, cell.rawValue) - } - } - } - } -} - -// print prints decision table into stdout - TO BE REMOVED -func (dtable *dTable) print() { - // title - for _, v := range dtable.titleRow1 { - fmt.Printf("| %v |", v.rawValue) - } - fmt.Println() - // meta title - for _, v := range dtable.titleRow2 { - fmt.Printf("| %v |", v.rawValue) - } - fmt.Println() - // data - for _, row := range dtable.rows { - for _, rv := range row { - // fmt.Printf("| %v--%v |", rv.cdExpr, rv.metaCell) - fmt.Print(rv) - } - fmt.Println() - } -} - -// evaluateExpression evaluates expr into boolean value in tuples scope -func evaluateExpression(expr string, tuples map[model.TupleType]model.Tuple) bool { - condExpr := ruleapi.NewExprCondition(expr) - result, err := condExpr.Evaluate("", "", tuples, "") - if err != nil { - return false - } - return result -} - -// updateTuple updates tuple's prop with toValue -func updateTuple(context context.Context, tuples map[model.TupleType]model.Tuple, tupleType string, prop string, toVlaue interface{}) { - tuple := tuples[model.TupleType(tupleType)] - if tuple == nil { - return - } - mutableTuple := tuple.(model.MutableTuple) - tds := mutableTuple.GetTupleDescriptor() - strVal := fmt.Sprintf("%v", toVlaue) - switch tds.GetProperty(prop).PropType { - case data.TypeString: - if strings.Compare(strVal, "") == 0 { - strVal = "" - } - mutableTuple.SetString(context, prop, strVal) - case data.TypeBool: - if strings.Compare(strVal, "") == 0 { - strVal = "false" - } - b, err := strconv.ParseBool(strVal) - if err == nil { - mutableTuple.SetBool(context, prop, b) - } - case data.TypeInt: - if strings.Compare(strVal, "") == 0 { - strVal = "0" - } - i, err := strconv.ParseInt(strVal, 10, 64) - if err == nil { - mutableTuple.SetInt(context, prop, int(i)) - } - case data.TypeInt32: - if strings.Compare(strVal, "") == 0 { - strVal = "0" - } - i, err := strconv.ParseInt(strVal, 10, 64) - if err == nil { - mutableTuple.SetInt(context, prop, int(i)) - } - case data.TypeInt64: - if strings.Compare(strVal, "") == 0 { - strVal = "0" - } - i, err := strconv.ParseInt(strVal, 10, 64) - if err == nil { - mutableTuple.SetLong(context, prop, i) - } - case data.TypeFloat32: - if strings.Compare(strVal, "") == 0 { - strVal = "0.0" - } - f, err := strconv.ParseFloat(strVal, 32) - if err == nil { - mutableTuple.SetDouble(context, prop, f) - } - case data.TypeFloat64: - if strings.Compare(strVal, "") == 0 { - strVal = "0.0" - } - f, err := strconv.ParseFloat(strVal, 64) - if err == nil { - mutableTuple.SetDouble(context, prop, f) - } - default: - mutableTuple.SetValue(context, prop, toVlaue) - - } -} diff --git a/activity/dtable/activity_test.go b/activity/dtable/activity_test.go index ba94269..b293a0a 100644 --- a/activity/dtable/activity_test.go +++ b/activity/dtable/activity_test.go @@ -131,60 +131,6 @@ var testApplicants = []map[string]interface{}{ }, } -func TestCellCompileExpr(t *testing.T) { - err := model.RegisterTupleDescriptors(string(tupleDescriptor)) - assert.Nil(t, err) - - // test cases input - tupleType := "applicant" - propName := "name" - testcases := make(map[string]string) - testcases["foo"] = "$.applicant.name == foo" - testcases["123foo"] = "$.applicant.name == 123foo" - testcases["foo123"] = "$.applicant.name == foo123" - testcases["123"] = "$.applicant.name == 123" - testcases["123.123"] = "$.applicant.name == 123.123" - testcases[".123"] = "$.applicant.name == .123" - testcases["!foo"] = "!($.applicant.name == foo)" - testcases["==foo"] = "$.applicant.name == foo" - testcases["!=foo"] = "$.applicant.name != foo" - testcases[">foo"] = "$.applicant.name > foo" - testcases["!>foo"] = "!($.applicant.name > foo)" - testcases[">=foo"] = "$.applicant.name >= foo" - testcases["=foo&&bar"] = "($.applicant.name >= foo && $.applicant.name == bar)" - testcases["car&&jeep&&bus"] = "(($.applicant.name == car && $.applicant.name == jeep) && $.applicant.name == bus)" - testcases["foo||bar"] = "($.applicant.name == foo || $.applicant.name == bar)" - testcases["car||jeep||bus"] = "(($.applicant.name == car || $.applicant.name == jeep) || $.applicant.name == bus)" - testcases["car&&jeep||bus"] = "(($.applicant.name == car && $.applicant.name == jeep) || $.applicant.name == bus)" - testcases["!(car&&(jeep||bus))"] = "!(($.applicant.name == car && ($.applicant.name == jeep || $.applicant.name == bus)))" - testcases["car||jeep&&bus"] = "($.applicant.name == car || ($.applicant.name == jeep && $.applicant.name == bus))" - - // prepare cell - tupleDesc := model.GetTupleDescriptor(model.TupleType(tupleType)) - assert.NotNil(t, tupleDesc) - propDesc := tupleDesc.GetProperty(propName) - assert.NotNil(t, propDesc) - cell := &genCell{ - metaCell: &metaCell{ - colType: ctCondition, - tupleDesc: tupleDesc, - propDesc: propDesc, - }, - } - - // run test cases - for k, v := range testcases { - t.Log(k, v) - cell.rawValue = k - cell.compileExpr() - assert.Equal(t, v, cell.cdExpr) - } -} - func TestNew(t *testing.T) { err := model.RegisterTupleDescriptors(string(tupleDescriptor)) diff --git a/config/config.go b/config/config.go index 88b8b7a..c6d8065 100644 --- a/config/config.go +++ b/config/config.go @@ -17,6 +17,8 @@ const ( TypeServiceActivity = "activity" // TypeServiceAction action based rule service TypeServiceAction = "action" + // TypeDecisionTable decision table based rule service + TypeDecisionTable = "decisiontable" ) // RuleSessionDescriptor is a collection of rules to be loaded diff --git a/examples/dtable/README.md b/examples/dtable/README.md new file mode 100644 index 0000000..3673f66 --- /dev/null +++ b/examples/dtable/README.md @@ -0,0 +1,29 @@ +# Decision Table Usage + +This example demonstrates usage of decision table api model with student analysis example. + +The following rules are used in the example: +* `studentcare`: Gets fired when `student.careRequired` is true and invokes the `printstudentinfo` function. This function prints the Student name and comments. +* `studentanalysis`: Gets fired when `studentanalysis.name` and `student.name` are same. It invokes `dtableservice` which analyses the student and updates the `student.careRequired` and `student.comments` accordingly. + +### Pre-requisites +* Go 1.11 +* The **GOPATH** environment variable on your system must be set properly + +### Steps + +```sh +cd $GOPATH/src/github.com/project-flogo/rules/examples/dtable +go run main.go +``` + +### Testing + +s1 and s2 student tuples are Saved. se1 and se2 are events to analyse s1 and s2 students against the `dtable-file.xlsx` file respectively. +You should see following outputs in the logs: +``` +Student Name: s1 Comments: “additional study hours required” +``` +``` +Student Name: s2 Comments: “little care can be taken to achieve grade-a” +``` diff --git a/examples/dtable/dtable-file.xlsx b/examples/dtable/dtable-file.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..a9314e1df0bf640402bbbe99760f0a82a3d08bc7 GIT binary patch literal 5600 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 literal 0 HcmV?d00001 diff --git a/examples/dtable/main.go b/examples/dtable/main.go new file mode 100644 index 0000000..5b230c4 --- /dev/null +++ b/examples/dtable/main.go @@ -0,0 +1,170 @@ +package main + +import ( + "context" + "fmt" + + "github.com/project-flogo/rules/common" + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/config" + "github.com/project-flogo/rules/ruleapi" +) + +func main() { + err := example(true) + if err != nil { + panic(err) + } +} + +func example(redis bool) error { + //Load the tuple descriptor file (relative to GOPATH) + tupleDescAbsFileNm := common.GetPathForResource("examples/dtable/rulesapp.json", "./rulesapp.json") + tupleDescriptor := common.FileToString(tupleDescAbsFileNm) + + //First register the tuple descriptors + err := model.RegisterTupleDescriptors(tupleDescriptor) + if err != nil { + return err + } + + //Create a RuleSession + store := "" + if redis { + store = "rsconfig.json" + } + rs, err := ruleapi.GetOrCreateRuleSession("asession", store) + if err != nil { + return err + } + + // student care information rule + rule1 := ruleapi.NewRule("studentcare") + err = rule1.AddExprCondition("c2", "$.student.careRequired", nil) + if err != nil { + return err + } + printService := &config.ServiceDescriptor{ + Name: "printstudentinfo", + Function: printStudentInfo, + Type: "function", + } + aService1, err := ruleapi.NewActionService(printService) + if err != nil { + return err + } + rule1.SetActionService(aService1) + rule1.SetPriority(2) + + err = rs.AddRule(rule1) + if err != nil { + return err + } + + // student analysis rule + rule2 := ruleapi.NewRule("studentanalysis") + err = rule2.AddExprCondition("c2", "$.studentanalysis.name == $.student.name", nil) + if err != nil { + return err + } + + settings := make(map[string]interface{}) + settings["filename"] = "dtable-file.xlsx" + + dtableService := &config.ServiceDescriptor{ + Name: "dtableservice", + Type: "decisiontable", + Settings: settings, + } + aService2, err := ruleapi.NewActionService(dtableService) + if err != nil { + return err + } + rule2.SetActionService(aService2) + rule2.SetPriority(1) + + err = rs.AddRule(rule2) + if err != nil { + return err + } + + //set a transaction handler + rs.RegisterRtcTransactionHandler(txHandler, nil) + //Start the rule session + err = rs.Start(nil) + if err != nil { + return err + } + + // assert student info + s1, err := model.NewTupleWithKeyValues("student", "s1") + if err != nil { + return err + } + s1.SetString(nil, "grade", "GRADE-C") + s1.SetString(nil, "class", "X-A") + s1.SetBool(nil, "careRequired", false) + err = rs.Assert(nil, s1) + if err != nil { + return err + } + + // assert another student info + s2, err := model.NewTupleWithKeyValues("student", "s2") + if err != nil { + return err + } + s2.SetString(nil, "grade", "GRADE-B") + s2.SetString(nil, "class", "X-A") + s2.SetBool(nil, "careRequired", false) + err = rs.Assert(nil, s2) + if err != nil { + return err + } + + // assert studentanalysis event + se1, err := model.NewTupleWithKeyValues("studentanalysis", "s1") + if err != nil { + return err + } + err = rs.Assert(nil, se1) + if err != nil { + return err + } + + // assert studentanalysis event + se2, err := model.NewTupleWithKeyValues("studentanalysis", "s2") + if err != nil { + return err + } + err = rs.Assert(nil, se2) + if err != nil { + return err + } + + //unregister the session, i.e; cleanup + rs.Unregister() + + return nil +} + +func getFileContent(filePath string) string { + absPath := common.GetAbsPathForResource(filePath) + return common.FileToString(absPath) +} + +func txHandler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { + + store := rs.GetStore() + store.SaveTuples(rtxn.GetRtcAdded()) + + store.SaveModifiedTuples(rtxn.GetRtcModified()) + + store.DeleteTuples(rtxn.GetRtcDeleted()) + +} + +func printStudentInfo(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + student := tuples["student"].ToMap() + fmt.Println("Student Name: ", student["name"], " Comments: ", student["comments"]) +} diff --git a/examples/dtable/rulesapp.json b/examples/dtable/rulesapp.json new file mode 100644 index 0000000..80f8be4 --- /dev/null +++ b/examples/dtable/rulesapp.json @@ -0,0 +1,39 @@ +[ + { + "name": "student", + "properties": [ + { + "name": "name", + "pk-index": 0, + "type": "string" + }, + { + "name": "grade", + "type": "string" + }, + { + "name": "class", + "type": "string" + }, + { + "name": "careRequired", + "type": "bool" + }, + { + "name": "comments", + "type": "string" + } + ] + }, + { + "name": "studentanalysis", + "ttl":0, + "properties": [ + { + "name": "name", + "pk-index": 0, + "type": "string" + } + ] + } + ] \ No newline at end of file diff --git a/ruleapi/actionservice.go b/ruleapi/actionservice.go index 0272384..f15da91 100644 --- a/ruleapi/actionservice.go +++ b/ruleapi/actionservice.go @@ -24,6 +24,7 @@ type ruleActionService struct { Function model.ActionFunction Act activity.Activity Action action.Action + DTable DecisionTable Input map[string]interface{} } @@ -96,6 +97,18 @@ func NewActionService(serviceCfg *config.ServiceDescriptor) (model.ActionService if err != nil { return nil, fmt.Errorf("not able create action - %s", err) } + + case config.TypeDecisionTable: + fileName := serviceCfg.Settings["filename"].(string) + if len(fileName) != 0 { + var err error + raService.DTable, err = LoadDecisionTableFromFile(fileName) + if err != nil { + return nil, fmt.Errorf("Unable to load Decison Table - %s", err) + } + } else { + return nil, fmt.Errorf("Decision Table filename not specified") + } } return raService, nil @@ -188,6 +201,14 @@ func (raService *ruleActionService) Execute(ctx context.Context, rs model.RuleSe } return true, nil } + + case config.TypeDecisionTable: + err := raService.DTable.Compile() + if err != nil { + return false, fmt.Errorf("unable to compile decision table while running the action service[%s] - %s", raService.Name, err) + } + raService.DTable.Apply(ctx, tuples) + return true, nil } return false, fmt.Errorf("service not executed, something went wrong") diff --git a/ruleapi/dtable.go b/ruleapi/dtable.go new file mode 100644 index 0000000..2dfb6a8 --- /dev/null +++ b/ruleapi/dtable.go @@ -0,0 +1,369 @@ +package ruleapi + +import ( + "context" + "encoding/csv" + "fmt" + "os" + "strconv" + "strings" + + excelize "github.com/360EntSecGroup-Skylar/excelize/v2" + "github.com/project-flogo/core/data" + "github.com/project-flogo/rules/common/model" +) + +// Decision table column types +type dtColType int8 + +const ( + ctID dtColType = iota // ID + ctCondition // condition + ctAction // action + ctDescription // Description + ctPriority // priority +) + +type decisionTableImpl struct { + titleRow1 []genCell + titleRow2 []genCell + metaRow []metaCell + rows [][]*genCell +} + +// DecisionTable interface +type DecisionTable interface { + Apply(ctx context.Context, tuples map[model.TupleType]model.Tuple) + Compile() error + Print() +} + +type metaCell struct { + ColType dtColType + tupleDesc *model.TupleDescriptor + propDesc *model.TuplePropertyDescriptor +} + +type genCell struct { + *metaCell + rawValue string + cdExpr string +} + +func (cell *genCell) compileExpr() { + rawValue := cell.rawValue + if len(rawValue) == 0 { + return + } + lhsToken := fmt.Sprintf("$.%s.%s", cell.tupleDesc.Name, cell.propDesc.Name) + expression := &Expr{Buffer: rawValue} + expression.Init() + expression.Expression.Init(rawValue) + if err := expression.Parse(); err != nil { + panic(err) + } + expression.Execute() + cell.cdExpr = expression.Evaluate(lhsToken) +} + +// LoadDecisionTableFromFile returns dtable from file +func LoadDecisionTableFromFile(fileName string) (DecisionTable, error) { + if fileName == "" { + return nil, fmt.Errorf("file name can't be empty") + } + tokens := strings.Split(fileName, ".") + fileExtension := tokens[len(tokens)-1] + + if fileExtension == "csv" || fileExtension == "CSV" { + return loadFromCSVFile(fileName) + } else if fileExtension == "xls" || fileExtension == "XLS" || fileExtension == "xlsx" || fileExtension == "XLSX" { + return loadFromXLSFile(fileName) + } + + return nil, fmt.Errorf("file[%s] extension not supported", fileName) +} + +// loadFromCSVFile loads decision table from CSV file +func loadFromCSVFile(fileName string) (DecisionTable, error) { + file, err := os.Open(fileName) + if err != nil { + return nil, fmt.Errorf("not able open the file [%s] - %s", fileName, err) + } + defer file.Close() + + lines, err := csv.NewReader(file).ReadAll() + if err != nil { + return nil, fmt.Errorf("not able read the file [%s] - %s", fileName, err) + } + + dtable := &decisionTableImpl{} + dtable.rows = make([][]*genCell, len(lines)-2) + for i, line := range lines { + if i == 0 { + // title row 1 + dtable.titleRow1 = make([]genCell, len(line)) + for j, val := range line { + dtable.titleRow1[j].rawValue = val + } + continue + } + if i == 1 { + // title row 2 + dtable.titleRow2 = make([]genCell, len(line)) + for j, val := range line { + dtable.titleRow2[j].rawValue = val + } + continue + } + // other rows + row := make([]*genCell, len(line)) + for j, val := range line { + row[j] = &genCell{ + rawValue: val, + } + } + dtable.rows[i-2] = row + } + return dtable, nil +} + +// loadFromXLSFile loads decision table from Excel file +func loadFromXLSFile(fileName string) (DecisionTable, error) { + file, err := excelize.OpenFile(fileName) + if err != nil { + return nil, fmt.Errorf("not able open the file [%s] - %s", fileName, err) + } + rows, err := file.GetRows("DecisionTable") + if err != nil { + return nil, fmt.Errorf("DecisionTable worksheet not available in %s", fileName) + } + + // find titleRowIndex + titleRowIndex := 0 + for i, r := range rows { + if len(r) > 0 { + if r[0] == "DecisionTable" { + titleRowIndex = i + 2 + } + } + } + titleRowSize := len(rows[titleRowIndex]) + + dtable := &decisionTableImpl{ + titleRow1: make([]genCell, titleRowSize), + titleRow2: make([]genCell, titleRowSize), + rows: make([][]*genCell, 1), + } + // title row 1 + for i, val := range rows[titleRowIndex] { + dtable.titleRow1[i].rawValue = val + } + // title row 2 + for i, val := range rows[titleRowIndex+1] { + dtable.titleRow2[i].rawValue = val + } + // other rows + for _, r := range rows[titleRowIndex+2:] { + if len(r) == 0 { + break + } + dtrow := make([]*genCell, titleRowSize) + for i, cell := range r { + dtrow[i] = &genCell{ + rawValue: cell, + } + } + dtable.rows = append(dtable.rows, dtrow) + } + + return dtable, nil +} + +func (dtable *decisionTableImpl) Compile() error { + // compute meta row from titleRow1 & titleRow2 + metaRow := make([]metaCell, len(dtable.titleRow1)) + dtable.metaRow = metaRow + // titleRow1 determines column type + for colIndex, cell := range dtable.titleRow1 { + if strings.Contains(cell.rawValue, "Id") { + metaRow[colIndex].ColType = ctID + } else if strings.Contains(cell.rawValue, "Condition") { + metaRow[colIndex].ColType = ctCondition + } else if strings.Contains(cell.rawValue, "Action") { + metaRow[colIndex].ColType = ctAction + } else if strings.Contains(cell.rawValue, "Description") { + metaRow[colIndex].ColType = ctDescription + } else if strings.Contains(cell.rawValue, "Priority") { + metaRow[colIndex].ColType = ctPriority + } else { + return fmt.Errorf("unknown column type - %s", cell.rawValue) + } + } + // titleRow2 determines tuple type & property + for colIndex, cell := range dtable.titleRow2 { + if cell.rawValue == "" { + continue + } + tokens := strings.Split(cell.rawValue, ".") + if len(tokens) != 2 { + return fmt.Errorf("[%s] is not a valid tuple property representation", cell.rawValue) + } + tupleType := tokens[0] + propName := tokens[1] + tupleDesc := model.GetTupleDescriptor(model.TupleType(tupleType)) + if tupleDesc == nil { + return fmt.Errorf("tuple type[%s] is not recognised", tupleType) + } + propDesc := tupleDesc.GetProperty(propName) + if propDesc == nil { + return fmt.Errorf("property[%s] is not a valid property for the tuple type[%s]", propName, tupleType) + } + metaRow[colIndex].tupleDesc = tupleDesc + metaRow[colIndex].propDesc = propDesc + } + // process all rows + for _, row := range dtable.rows { + for colIndex, cell := range row { + if cell == nil { + continue + } + cell.metaCell = &metaRow[colIndex] + if cell.ColType == ctCondition { + cell.compileExpr() + } + } + } + return nil +} + +func (dtable *decisionTableImpl) Apply(ctx context.Context, tuples map[model.TupleType]model.Tuple) { + // process all rows + for _, row := range dtable.rows { + // process row conditions + rowTruthiness := true + for _, cell := range row { + if cell == nil { + continue + } + if cell.ColType == ctCondition { + cellTruthiness := evaluateExpression(cell.cdExpr, tuples) + rowTruthiness = rowTruthiness && cellTruthiness + if !rowTruthiness { + break + } + } + } + // process row actions if all row conditions are evaluated to true + if rowTruthiness { + for _, cell := range row { + if cell == nil { + continue + } + if cell.ColType == ctAction { + updateTuple(ctx, tuples, cell.tupleDesc.Name, cell.propDesc.Name, cell.rawValue) + } + } + } + } +} + +// print prints decision table into stdout - TO BE REMOVED +func (dtable *decisionTableImpl) Print() { + // title + for _, v := range dtable.titleRow1 { + fmt.Printf("| %v |", v.rawValue) + } + fmt.Println() + // meta title + for _, v := range dtable.titleRow2 { + fmt.Printf("| %v |", v.rawValue) + } + fmt.Println() + // data + for _, row := range dtable.rows { + for _, rv := range row { + // fmt.Printf("| %v--%v |", rv.cdExpr, rv.metaCell) + fmt.Print(rv) + } + fmt.Println() + } +} + +// evaluateExpression evaluates expr into boolean value in tuples scope +func evaluateExpression(expr string, tuples map[model.TupleType]model.Tuple) bool { + condExpr := NewExprCondition(expr) + result, err := condExpr.Evaluate("", "", tuples, "") + if err != nil { + return false + } + return result +} + +// updateTuple updates tuple's prop with toValue +func updateTuple(context context.Context, tuples map[model.TupleType]model.Tuple, tupleType string, prop string, toVlaue interface{}) { + tuple := tuples[model.TupleType(tupleType)] + if tuple == nil { + return + } + mutableTuple := tuple.(model.MutableTuple) + tds := mutableTuple.GetTupleDescriptor() + strVal := fmt.Sprintf("%v", toVlaue) + switch tds.GetProperty(prop).PropType { + case data.TypeString: + if strings.Compare(strVal, "") == 0 { + strVal = "" + } + mutableTuple.SetString(context, prop, strVal) + case data.TypeBool: + if strings.Compare(strVal, "") == 0 { + strVal = "false" + } + b, err := strconv.ParseBool(strVal) + if err == nil { + mutableTuple.SetBool(context, prop, b) + } + case data.TypeInt: + if strings.Compare(strVal, "") == 0 { + strVal = "0" + } + i, err := strconv.ParseInt(strVal, 10, 64) + if err == nil { + mutableTuple.SetInt(context, prop, int(i)) + } + case data.TypeInt32: + if strings.Compare(strVal, "") == 0 { + strVal = "0" + } + i, err := strconv.ParseInt(strVal, 10, 64) + if err == nil { + mutableTuple.SetInt(context, prop, int(i)) + } + case data.TypeInt64: + if strings.Compare(strVal, "") == 0 { + strVal = "0" + } + i, err := strconv.ParseInt(strVal, 10, 64) + if err == nil { + mutableTuple.SetLong(context, prop, i) + } + case data.TypeFloat32: + if strings.Compare(strVal, "") == 0 { + strVal = "0.0" + } + f, err := strconv.ParseFloat(strVal, 32) + if err == nil { + mutableTuple.SetDouble(context, prop, f) + } + case data.TypeFloat64: + if strings.Compare(strVal, "") == 0 { + strVal = "0.0" + } + f, err := strconv.ParseFloat(strVal, 64) + if err == nil { + mutableTuple.SetDouble(context, prop, f) + } + default: + mutableTuple.SetValue(context, prop, toVlaue) + + } +} diff --git a/ruleapi/dtable_test.go b/ruleapi/dtable_test.go new file mode 100644 index 0000000..6469a35 --- /dev/null +++ b/ruleapi/dtable_test.go @@ -0,0 +1,214 @@ +package ruleapi + +import ( + "testing" + + "github.com/project-flogo/rules/common/model" + "github.com/stretchr/testify/assert" +) + +const tupleDescriptor = `[ + { + "name":"applicant", + "properties":[ + { + "name":"name", + "pk-index":0, + "type":"string" + }, + { + "name":"gender", + "type":"string" + }, + { + "name":"age", + "type":"int" + }, + { + "name":"address", + "type":"string" + }, + { + "name":"hasDL", + "type":"bool" + }, + { + "name":"ssn", + "type":"long" + }, + { + "name":"income", + "type":"double" + }, + { + "name":"maritalStatus", + "type":"string" + }, + { + "name":"creditScore", + "type":"int" + }, + { + "name":"status", + "type":"string" + }, + { + "name":"eligible", + "type":"bool" + }, + { + "name":"creditLimit", + "type":"double" + } + ] + }, + { + "name":"processapplication", + "ttl":0, + "properties":[ + { + "name":"ssn", + "pk-index":0, + "type":"long" + }, + { + "name":"start", + "type":"bool" + } + ] + } +]` + +func TestCellCompileExpr(t *testing.T) { + err := model.RegisterTupleDescriptors(string(tupleDescriptor)) + assert.Nil(t, err) + + // test cases input + tupleType := "applicant" + propName := "name" + testcases := make(map[string]string) + testcases["foo"] = "$.applicant.name == foo" + testcases["123foo"] = "$.applicant.name == 123foo" + testcases["foo123"] = "$.applicant.name == foo123" + testcases["123"] = "$.applicant.name == 123" + testcases["123.123"] = "$.applicant.name == 123.123" + testcases[".123"] = "$.applicant.name == .123" + testcases["!foo"] = "!($.applicant.name == foo)" + testcases["==foo"] = "$.applicant.name == foo" + testcases["!=foo"] = "$.applicant.name != foo" + testcases[">foo"] = "$.applicant.name > foo" + testcases["!>foo"] = "!($.applicant.name > foo)" + testcases[">=foo"] = "$.applicant.name >= foo" + testcases["=foo&&bar"] = "($.applicant.name >= foo && $.applicant.name == bar)" + testcases["car&&jeep&&bus"] = "(($.applicant.name == car && $.applicant.name == jeep) && $.applicant.name == bus)" + testcases["foo||bar"] = "($.applicant.name == foo || $.applicant.name == bar)" + testcases["car||jeep||bus"] = "(($.applicant.name == car || $.applicant.name == jeep) || $.applicant.name == bus)" + testcases["car&&jeep||bus"] = "(($.applicant.name == car && $.applicant.name == jeep) || $.applicant.name == bus)" + testcases["!(car&&(jeep||bus))"] = "!(($.applicant.name == car && ($.applicant.name == jeep || $.applicant.name == bus)))" + testcases["car||jeep&&bus"] = "($.applicant.name == car || ($.applicant.name == jeep && $.applicant.name == bus))" + + // prepare cell + tupleDesc := model.GetTupleDescriptor(model.TupleType(tupleType)) + assert.NotNil(t, tupleDesc) + propDesc := tupleDesc.GetProperty(propName) + assert.NotNil(t, propDesc) + cell := &genCell{ + metaCell: &metaCell{ + ColType: ctCondition, + tupleDesc: tupleDesc, + propDesc: propDesc, + }, + } + + // run test cases + for k, v := range testcases { + t.Log(k, v) + cell.rawValue = k + cell.compileExpr() + assert.Equal(t, v, cell.cdExpr) + } +} + +func TestDtableXLSX(t *testing.T) { + err := model.RegisterTupleDescriptors(string(tupleDescriptor)) + assert.Nil(t, err) + + dtable, err := LoadDecisionTableFromFile("test_dtable.xlsx") + assert.Nil(t, err) + + err = dtable.Compile() + assert.Nil(t, err) + + vls := make(map[string]interface{}) + + vls["name"] = "test" + vls["age"] = 20 + vls["hasDL"] = true + vls["creditScore"] = 600 + vls["maritalStatus"] = "single" + vls["income"] = 11000 + vls["eligible"] = false + vls["creditLimit"] = 0 + + tpl, err := model.NewTuple("applicant", vls) + assert.Nil(t, err) + assert.NotNil(t, tpl) + + presentCreditLimit, err := tpl.GetDouble("creditLimit") + assert.Nil(t, err) + assert.Equal(t, float64(0), presentCreditLimit) + + tuples := make(map[model.TupleType]model.Tuple) + tuples[tpl.GetTupleType()] = tpl + + dtable.Apply(nil, tuples) + + newCreditLimit, err := tpl.GetDouble("creditLimit") + assert.Nil(t, err) + assert.NotEqual(t, newCreditLimit, presentCreditLimit) + +} + +func TestDtableCSV(t *testing.T) { + err := model.RegisterTupleDescriptors(string(tupleDescriptor)) + assert.Nil(t, err) + + dtable, err := LoadDecisionTableFromFile("test_dtable.csv") + assert.Nil(t, err) + + err = dtable.Compile() + assert.Nil(t, err) + + vls := make(map[string]interface{}) + + vls["name"] = "test" + vls["age"] = 20 + vls["hasDL"] = true + vls["creditScore"] = 600 + vls["maritalStatus"] = "single" + vls["income"] = 11000 + vls["eligible"] = false + vls["creditLimit"] = 0 + + tpl, err := model.NewTuple("applicant", vls) + assert.Nil(t, err) + assert.NotNil(t, tpl) + + presentCreditLimit, err := tpl.GetDouble("creditLimit") + assert.Nil(t, err) + assert.Equal(t, float64(0), presentCreditLimit) + + tuples := make(map[model.TupleType]model.Tuple) + tuples[tpl.GetTupleType()] = tpl + + dtable.Apply(nil, tuples) + + newCreditLimit, err := tpl.GetDouble("creditLimit") + assert.Nil(t, err) + assert.NotEqual(t, newCreditLimit, presentCreditLimit) + +} diff --git a/activity/dtable/expression.go b/ruleapi/expression.go similarity index 99% rename from activity/dtable/expression.go rename to ruleapi/expression.go index 104f075..3ada8a8 100644 --- a/activity/dtable/expression.go +++ b/ruleapi/expression.go @@ -1,4 +1,4 @@ -package dtable +package ruleapi // Type is a type of byte code type Type uint8 diff --git a/activity/dtable/expression.peg b/ruleapi/expression.peg similarity index 98% rename from activity/dtable/expression.peg rename to ruleapi/expression.peg index 1682e20..c753e59 100644 --- a/activity/dtable/expression.peg +++ b/ruleapi/expression.peg @@ -1,4 +1,4 @@ -package dtable +package ruleapi type Expr Peg { Expression diff --git a/activity/dtable/expression.peg.go b/ruleapi/expression.peg.go similarity index 99% rename from activity/dtable/expression.peg.go rename to ruleapi/expression.peg.go index 16d1122..8875c08 100644 --- a/activity/dtable/expression.peg.go +++ b/ruleapi/expression.peg.go @@ -1,4 +1,4 @@ -package dtable +package ruleapi // Code generated by peg expression.peg DO NOT EDIT. diff --git a/ruleapi/test_dtable.csv b/ruleapi/test_dtable.csv new file mode 100644 index 0000000..a887aee --- /dev/null +++ b/ruleapi/test_dtable.csv @@ -0,0 +1,3 @@ +Id,Condition (1),Condition (2),Condition (3),Condition (4),Condition (5),Action (6),Action (7),Action (8),Description,Priority +,applicant.age,applicant.creditScore,applicant.hasDL,applicant.maritalStatus,applicant.income,applicant.eligible,applicant.status,applicant.creditLimit,, +1,>= 16,>= 500,== true,== 'single',> 10000,true,VISA-Granted,2500.0,, \ No newline at end of file diff --git a/ruleapi/test_dtable.xlsx b/ruleapi/test_dtable.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..ae8aef92f233bcad640fd939db5e650c7da222e1 GIT binary patch literal 9220 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 V1^de7um~?c*Df}@FASIe{tZ-26*~X` literal 0 HcmV?d00001 From 51126ddac539c5d62cc65c972f8d8919733916bb Mon Sep 17 00:00:00 2001 From: ykalidin Date: Tue, 31 Mar 2020 15:49:55 +0530 Subject: [PATCH 124/125] Updating examples (#95) * Updateing examples * Update Test --- examples/flogo/creditcard/README.md | 4 +- examples/flogo/dtable/README.md | 4 +- examples/flogo/invokeservice/README.md | 2 +- examples/flogo/statemachine/README.md | 2 +- examples/{simple => rulesapp}/README.md | 2 +- examples/rulesapp/main.go | 93 +++++- examples/rulesapp/ruleapp_test.go | 1 + examples/simple/main.go | 362 ------------------------ examples/simple/rsconfig.json | 36 --- examples/simple/rulesapp.json | 22 -- 10 files changed, 99 insertions(+), 429 deletions(-) rename examples/{simple => rulesapp}/README.md (93%) delete mode 100644 examples/simple/main.go delete mode 100644 examples/simple/rsconfig.json delete mode 100644 examples/simple/rulesapp.json diff --git a/examples/flogo/creditcard/README.md b/examples/flogo/creditcard/README.md index 5105d10..dee8772 100644 --- a/examples/flogo/creditcard/README.md +++ b/examples/flogo/creditcard/README.md @@ -1,6 +1,6 @@ -# Decision Table Usage +# Credit Card example -This example demonstrates how to use decision table activity with credit card application example. +This example demonstrates how to use decision table activity for credit card application example. ## Setup and build Once you have the `flogo.json` file, you are ready to build your Flogo App diff --git a/examples/flogo/dtable/README.md b/examples/flogo/dtable/README.md index a9aa9b5..1f73076 100644 --- a/examples/flogo/dtable/README.md +++ b/examples/flogo/dtable/README.md @@ -30,14 +30,14 @@ cd bin ```sh docker run -p 6381:6379 -d redis -STORECONFIG=../../rsconfig.json ./dtable +STORECONFIG=../../rsconfig.json ./decisiontable ``` #### With keydb store ```sh docker run -p 6381:6379 -d eqalpha/keydb -STORECONFIG=../../rsconfig.json ./dtable +STORECONFIG=../../rsconfig.json ./decisiontable ``` ### Testing diff --git a/examples/flogo/invokeservice/README.md b/examples/flogo/invokeservice/README.md index d853d88..a921ca6 100644 --- a/examples/flogo/invokeservice/README.md +++ b/examples/flogo/invokeservice/README.md @@ -1,6 +1,6 @@ # Invoke service when rule fires -This example demonstrates how a rule can invoke rule `service`. A rule `service` is a `go function` or a `flogo action` or a `flogo activity`. +This example demonstrates how a rule can invoke rule `service`. A rule `service` can be a `go function` or a `flogo action` or a `flogo activity`. ## Setup and build Once you have the `flogo.json` file and a `functions.go` file, you are ready to build your Flogo App diff --git a/examples/flogo/statemachine/README.md b/examples/flogo/statemachine/README.md index c1b3f1f..1eae2f4 100644 --- a/examples/flogo/statemachine/README.md +++ b/examples/flogo/statemachine/README.md @@ -1,6 +1,6 @@ ## Flogo Rules based State Machine -This example demonstrates the capability of rules to to process package states using a flogo app. In this example three tuples are used, tuples description is given below. +This example demonstrates the capability of rules to process package states using a flogo app. In this example three tuples are used, tuples description is given below. ```json { diff --git a/examples/simple/README.md b/examples/rulesapp/README.md similarity index 93% rename from examples/simple/README.md rename to examples/rulesapp/README.md index d317b71..fb1fd7c 100644 --- a/examples/simple/README.md +++ b/examples/rulesapp/README.md @@ -25,7 +25,7 @@ Run the example: ``` docker run -p 6383:6379 -d redis go get -u github.com/project-flogo/rules/... -cd $GOPATH/src/github.com/project-flogo/rules/examples/simple +cd $GOPATH/src/github.com/project-flogo/rules/examples/rulesapp export name=Smith go run main.go ``` \ No newline at end of file diff --git a/examples/rulesapp/main.go b/examples/rulesapp/main.go index 8acf6c2..338d7d1 100644 --- a/examples/rulesapp/main.go +++ b/examples/rulesapp/main.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "os" "github.com/project-flogo/rules/common" "github.com/project-flogo/rules/common/model" @@ -88,6 +89,28 @@ func example(redis bool) error { return err } + // check for name in n1, match the env variable "name" + rule3 := ruleapi.NewRule("n1.name == envname") + err = rule3.AddCondition("c1", []string{"n1"}, checkSameEnvName, events) + if err != nil { + return err + } + serviceCfg3 := &config.ServiceDescriptor{ + Name: "checkSameEnvNameAction", + Function: checkSameEnvNameAction, + Type: "function", + } + aService3, err := ruleapi.NewActionService(serviceCfg3) + if err != nil { + return err + } + rule3.SetActionService(aService3) + rule3.SetContext(events) + err = rs.AddRule(rule3) + if err != nil { + return err + } + //set a transaction handler rs.RegisterRtcTransactionHandler(txHandler, nil) //Start the rule session @@ -133,6 +156,17 @@ func example(redis bool) error { return err } + //Now assert a "n1" tuple + t4, err := model.NewTupleWithKeyValues("n1", "Smith") + if err != nil { + return err + } + t4.SetString(nil, "name", "Smith") + err = rs.Assert(nil, t4) + if err != nil { + return err + } + //Retract tuples err = rs.Retract(nil, t1) if err != nil { @@ -146,6 +180,10 @@ func example(redis bool) error { if err != nil { return err } + err = rs.Retract(nil, t4) + if err != nil { + return err + } //delete the rule rs.DeleteRule(rule2.GetName()) @@ -153,8 +191,8 @@ func example(redis bool) error { //unregister the session, i.e; cleanup rs.Unregister() - if events["checkForBob"] != 4 { - return fmt.Errorf("checkForBob should have been called 4 times") + if events["checkForBob"] != 6 { + return fmt.Errorf("checkForBob should have been called 6 times") } if events["checkForBobAction"] != 1 { return fmt.Errorf("checkForBobAction should have been called once") @@ -165,12 +203,19 @@ func example(redis bool) error { if events["checkSameNamesAction"] != 1 { return fmt.Errorf("checkSameNamesAction should have been called once") } + if events["checkSameEnvName"] != 3 { + return fmt.Errorf("checkSameEnvName should have been called thrice") + } + if events["checkSameEnvNameAction"] != 1 { + return fmt.Errorf("checkSameEnvNameAction should have been called once") + } return nil } func checkForBob(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { //This conditions filters on name="Bob" + //fmt.Println("checkForBob") t1 := tuples["n1"] if t1 == nil { return false @@ -189,6 +234,7 @@ func checkForBob(ruleName string, condName string, tuples map[model.TupleType]mo } func checkForBobAction(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + //fmt.Println("checkForBobAction") t1 := tuples["n1"] if t1 == nil { return @@ -200,12 +246,14 @@ func checkForBobAction(ctx context.Context, rs model.RuleSession, ruleName strin if name == "" { return } + fmt.Println("Rule checkForBobAction is fired") events := ruleCtx.(map[string]int) count := events["checkForBobAction"] events["checkForBobAction"] = count + 1 } func checkSameNamesCondition(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + //fmt.Println("checkSameNamesCondition") t1 := tuples["n1"] t2 := tuples["n2"] if t1 == nil || t2 == nil { @@ -232,6 +280,7 @@ func checkSameNamesCondition(ruleName string, condName string, tuples map[model. } func checkSameNamesAction(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + //fmt.Println("checkSameNamesAction") t1 := tuples["n1"] t2 := tuples["n2"] if t1 == nil || t2 == nil { @@ -251,11 +300,51 @@ func checkSameNamesAction(ctx context.Context, rs model.RuleSession, ruleName st if name2 == "" { return } + fmt.Println("Rule checkSameNamesAction is fired") events := ruleCtx.(map[string]int) count := events["checkSameNamesAction"] events["checkSameNamesAction"] = count + 1 } +func checkSameEnvName(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { + //fmt.Println("checkSameEnvName") + t1 := tuples["n1"] + if t1 == nil { + return false + } + name, err := t1.GetString("name") + if err != nil { + return false + } + if name == "" { + return false + } + name1 := os.Getenv("name") + events := ctx.(map[string]int) + count := events["checkSameEnvName"] + events["checkSameEnvName"] = count + 1 + return name == name1 +} + +func checkSameEnvNameAction(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { + + t1 := tuples["n1"] + if t1 == nil { + return + } + name, err := t1.GetString("name") + if err != nil { + return + } + if name == "" { + return + } + fmt.Println("Rule checkSameEnvNameAction is fired") + events := ruleCtx.(map[string]int) + count := events["checkSameEnvNameAction"] + events["checkSameEnvNameAction"] = count + 1 +} + func getFileContent(filePath string) string { absPath := common.GetAbsPathForResource(filePath) return common.FileToString(absPath) diff --git a/examples/rulesapp/ruleapp_test.go b/examples/rulesapp/ruleapp_test.go index 1e195c2..633a283 100644 --- a/examples/rulesapp/ruleapp_test.go +++ b/examples/rulesapp/ruleapp_test.go @@ -47,6 +47,7 @@ func TestMain(m *testing.M) { } func TestRuleApp(t *testing.T) { + os.Setenv("name", "Smith") err := example(redis) assert.Nil(t, err) } diff --git a/examples/simple/main.go b/examples/simple/main.go deleted file mode 100644 index 338d7d1..0000000 --- a/examples/simple/main.go +++ /dev/null @@ -1,362 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - - "github.com/project-flogo/rules/common" - "github.com/project-flogo/rules/common/model" - "github.com/project-flogo/rules/config" - "github.com/project-flogo/rules/ruleapi" -) - -func main() { - err := example(true) - if err != nil { - panic(err) - } -} - -func example(redis bool) error { - //Load the tuple descriptor file (relative to GOPATH) - tupleDescAbsFileNm := common.GetPathForResource("examples/rulesapp/rulesapp.json", "./rulesapp.json") - tupleDescriptor := common.FileToString(tupleDescAbsFileNm) - - //First register the tuple descriptors - err := model.RegisterTupleDescriptors(tupleDescriptor) - if err != nil { - return err - } - - //Create a RuleSession - store := "" - if redis { - store = "rsconfig.json" - } - rs, err := ruleapi.GetOrCreateRuleSession("asession", store) - if err != nil { - return err - } - - events := make(map[string]int, 8) - //// check for name "Bob" in n1 - rule := ruleapi.NewRule("n1.name == Bob") - err = rule.AddCondition("c1", []string{"n1"}, checkForBob, events) - if err != nil { - return err - } - serviceCfg := &config.ServiceDescriptor{ - Name: "checkForBobAction", - Function: checkForBobAction, - Type: "function", - } - aService, err := ruleapi.NewActionService(serviceCfg) - if err != nil { - return err - } - rule.SetActionService(aService) - rule.SetContext(events) - err = rs.AddRule(rule) - if err != nil { - return err - } - - // check for name "Bob" in n1, match the "name" field in n2, - // in effect, fire the rule when name field in both tuples is "Bob" - rule2 := ruleapi.NewRule("n1.name == Bob && n1.name == n2.name") - err = rule2.AddCondition("c1", []string{"n1"}, checkForBob, events) - if err != nil { - return err - } - err = rule2.AddCondition("c2", []string{"n1", "n2"}, checkSameNamesCondition, events) - if err != nil { - return err - } - serviceCfg2 := &config.ServiceDescriptor{ - Name: "checkSameNamesAction", - Function: checkSameNamesAction, - Type: "function", - } - aService2, err := ruleapi.NewActionService(serviceCfg2) - if err != nil { - return err - } - rule2.SetActionService(aService2) - rule2.SetContext(events) - err = rs.AddRule(rule2) - if err != nil { - return err - } - - // check for name in n1, match the env variable "name" - rule3 := ruleapi.NewRule("n1.name == envname") - err = rule3.AddCondition("c1", []string{"n1"}, checkSameEnvName, events) - if err != nil { - return err - } - serviceCfg3 := &config.ServiceDescriptor{ - Name: "checkSameEnvNameAction", - Function: checkSameEnvNameAction, - Type: "function", - } - aService3, err := ruleapi.NewActionService(serviceCfg3) - if err != nil { - return err - } - rule3.SetActionService(aService3) - rule3.SetContext(events) - err = rs.AddRule(rule3) - if err != nil { - return err - } - - //set a transaction handler - rs.RegisterRtcTransactionHandler(txHandler, nil) - //Start the rule session - err = rs.Start(nil) - if err != nil { - return err - } - - //Now assert a "n1" tuple - t1, err := model.NewTupleWithKeyValues("n1", "Tom") - if err != nil { - return err - } - t1.SetString(nil, "name", "Tom") - err = rs.Assert(nil, t1) - if err != nil { - return err - } - t11 := rs.GetStore().GetTupleByKey(t1.GetKey()) - if t11 == nil { - return fmt.Errorf("Warn: Tuple should be in store[%s]", t11.GetKey()) - } - - //Now assert a "n1" tuple - t2, err := model.NewTupleWithKeyValues("n1", "Bob") - if err != nil { - return err - } - t2.SetString(nil, "name", "Bob") - err = rs.Assert(nil, t2) - if err != nil { - return err - } - - //Now assert a "n2" tuple - t3, err := model.NewTupleWithKeyValues("n2", "Bob") - if err != nil { - return err - } - t3.SetString(nil, "name", "Bob") - err = rs.Assert(nil, t3) - if err != nil { - return err - } - - //Now assert a "n1" tuple - t4, err := model.NewTupleWithKeyValues("n1", "Smith") - if err != nil { - return err - } - t4.SetString(nil, "name", "Smith") - err = rs.Assert(nil, t4) - if err != nil { - return err - } - - //Retract tuples - err = rs.Retract(nil, t1) - if err != nil { - return err - } - err = rs.Retract(nil, t2) - if err != nil { - return err - } - err = rs.Retract(nil, t3) - if err != nil { - return err - } - err = rs.Retract(nil, t4) - if err != nil { - return err - } - - //delete the rule - rs.DeleteRule(rule2.GetName()) - - //unregister the session, i.e; cleanup - rs.Unregister() - - if events["checkForBob"] != 6 { - return fmt.Errorf("checkForBob should have been called 6 times") - } - if events["checkForBobAction"] != 1 { - return fmt.Errorf("checkForBobAction should have been called once") - } - if events["checkSameNamesCondition"] != 1 { - return fmt.Errorf("checkSameNamesCondition should have been called once") - } - if events["checkSameNamesAction"] != 1 { - return fmt.Errorf("checkSameNamesAction should have been called once") - } - if events["checkSameEnvName"] != 3 { - return fmt.Errorf("checkSameEnvName should have been called thrice") - } - if events["checkSameEnvNameAction"] != 1 { - return fmt.Errorf("checkSameEnvNameAction should have been called once") - } - - return nil -} - -func checkForBob(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { - //This conditions filters on name="Bob" - //fmt.Println("checkForBob") - t1 := tuples["n1"] - if t1 == nil { - return false - } - name, err := t1.GetString("name") - if err != nil { - return false - } - if name == "" { - return false - } - events := ctx.(map[string]int) - count := events["checkForBob"] - events["checkForBob"] = count + 1 - return name == "Bob" -} - -func checkForBobAction(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { - //fmt.Println("checkForBobAction") - t1 := tuples["n1"] - if t1 == nil { - return - } - name, err := t1.GetString("name") - if err != nil { - return - } - if name == "" { - return - } - fmt.Println("Rule checkForBobAction is fired") - events := ruleCtx.(map[string]int) - count := events["checkForBobAction"] - events["checkForBobAction"] = count + 1 -} - -func checkSameNamesCondition(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { - //fmt.Println("checkSameNamesCondition") - t1 := tuples["n1"] - t2 := tuples["n2"] - if t1 == nil || t2 == nil { - return false - } - name1, err := t1.GetString("name") - if err != nil { - return false - } - if name1 == "" { - return false - } - name2, err := t2.GetString("name") - if err != nil { - return false - } - if name2 == "" { - return false - } - events := ctx.(map[string]int) - count := events["checkSameNamesCondition"] - events["checkSameNamesCondition"] = count + 1 - return name1 == name2 -} - -func checkSameNamesAction(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { - //fmt.Println("checkSameNamesAction") - t1 := tuples["n1"] - t2 := tuples["n2"] - if t1 == nil || t2 == nil { - return - } - name1, err := t1.GetString("name") - if err != nil { - return - } - if name1 == "" { - return - } - name2, err := t2.GetString("name") - if err != nil { - return - } - if name2 == "" { - return - } - fmt.Println("Rule checkSameNamesAction is fired") - events := ruleCtx.(map[string]int) - count := events["checkSameNamesAction"] - events["checkSameNamesAction"] = count + 1 -} - -func checkSameEnvName(ruleName string, condName string, tuples map[model.TupleType]model.Tuple, ctx model.RuleContext) bool { - //fmt.Println("checkSameEnvName") - t1 := tuples["n1"] - if t1 == nil { - return false - } - name, err := t1.GetString("name") - if err != nil { - return false - } - if name == "" { - return false - } - name1 := os.Getenv("name") - events := ctx.(map[string]int) - count := events["checkSameEnvName"] - events["checkSameEnvName"] = count + 1 - return name == name1 -} - -func checkSameEnvNameAction(ctx context.Context, rs model.RuleSession, ruleName string, tuples map[model.TupleType]model.Tuple, ruleCtx model.RuleContext) { - - t1 := tuples["n1"] - if t1 == nil { - return - } - name, err := t1.GetString("name") - if err != nil { - return - } - if name == "" { - return - } - fmt.Println("Rule checkSameEnvNameAction is fired") - events := ruleCtx.(map[string]int) - count := events["checkSameEnvNameAction"] - events["checkSameEnvNameAction"] = count + 1 -} - -func getFileContent(filePath string) string { - absPath := common.GetAbsPathForResource(filePath) - return common.FileToString(absPath) -} - -func txHandler(ctx context.Context, rs model.RuleSession, rtxn model.RtcTxn, handlerCtx interface{}) { - - store := rs.GetStore() - store.SaveTuples(rtxn.GetRtcAdded()) - - store.SaveModifiedTuples(rtxn.GetRtcModified()) - - store.DeleteTuples(rtxn.GetRtcDeleted()) - -} diff --git a/examples/simple/rsconfig.json b/examples/simple/rsconfig.json deleted file mode 100644 index fae556a..0000000 --- a/examples/simple/rsconfig.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "mode": "consistency", - "rs": { - "prefix": "x", - "store-ref": "redis" - }, - "rete": { - "jt-ref": "redis", - "idgen-ref": "redis", - "jt":"redis" - }, - "stores": { - "mem": { - }, - "redis": { - "network": "tcp", - "address": ":6383" - } - }, - "idgens": { - "mem": { - }, - "redis": { - "network": "tcp", - "address": ":6383" - } - }, - "jts": { - "mem": { - }, - "redis": { - "network": "tcp", - "address": ":6383" - } - } -} diff --git a/examples/simple/rulesapp.json b/examples/simple/rulesapp.json deleted file mode 100644 index 452c470..0000000 --- a/examples/simple/rulesapp.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - { - "name": "n1", - "properties": [ - { - "name": "name", - "type": "string", - "pk-index": 0 - } - ] - }, - { - "name": "n2", - "properties": [ - { - "name": "name", - "type": "string", - "pk-index": 0 - } - ] - } - ] \ No newline at end of file From 92dca6f3a46500088506ef4eb90ea661085211a8 Mon Sep 17 00:00:00 2001 From: ykalidin Date: Wed, 1 Apr 2020 12:44:40 +0530 Subject: [PATCH 125/125] Chore update readme (#96) * Updating Readme * Update Readme * Updating Readme --- README.md | 60 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 0d9c4be..524af2f 100644 --- a/README.md +++ b/README.md @@ -65,58 +65,88 @@ First we start off with loading the `TupleDescriptor`. It accepts a JSON string Next create a `RuleSession` and add all the `Rule`s with their `Condition`s and `Actions`s. //Create a RuleSession - rs, _ := ruleapi.GetOrCreateRuleSession("asession") + rs, _ := ruleapi.GetOrCreateRuleSession("asession", "") //// check for name "Bob" in n1 rule := ruleapi.NewRule("n1.name == Bob") - rule.AddCondition("c1", []string{"n1"}, checkForBob, nil) - rule.SetAction(checkForBobAction) - rule.SetContext("This is a test of context") + rule.AddCondition("c1", []string{"n1"}, checkForBob, events) + serviceCfg := &config.ServiceDescriptor{ + Name: "checkForBobAction", + Function: checkForBobAction, + Type: "function", + } + aService,_ := ruleapi.NewActionService(serviceCfg) + rule.SetActionService(aService) + rule.SetContext(events) rs.AddRule(rule) - fmt.Printf("Rule added: [%s]\n", rule.GetName()) // check for name "Bob" in n1, match the "name" field in n2, // in effect, fire the rule when name field in both tuples is "Bob" rule2 := ruleapi.NewRule("n1.name == Bob && n1.name == n2.name") - rule2.AddCondition("c1", []string{"n1"}, checkForBob, nil) - rule2.AddCondition("c2", []string{"n1", "n2"}, checkSameNamesCondition, nil) - rule2.SetAction(checkSameNamesAction) + rule2.AddCondition("c1", []string{"n1"}, checkForBob, events) + rule2.AddCondition("c2", []string{"n1", "n2"}, checkSameNamesCondition, events) + serviceCfg2 := &config.ServiceDescriptor{ + Name: "checkSameNamesAction", + Function: checkSameNamesAction, + Type: "function", + } + aService2,_ := ruleapi.NewActionService(serviceCfg2) + rule2.SetActionService(aService2) + rule2.SetContext(events) rs.AddRule(rule2) - fmt.Printf("Rule added: [%s]\n", rule2.GetName()) - - //Finally, start the rule session before asserting tuples - //Your startup function, if registered will be invoked here + + // check for name in n1, match the env variable "name" + rule3 := ruleapi.NewRule("n1.name == envname") + rule3.AddCondition("c1", []string{"n1"}, checkSameEnvName, events) + serviceCfg3 := &config.ServiceDescriptor{ + Name: "checkSameEnvNameAction", + Function: checkSameEnvNameAction, + Type: "function", + } + aService3,_ := ruleapi.NewActionService(serviceCfg3) + rule3.SetActionService(aService3) + rule3.SetContext(events) + rs.AddRule(rule3) + + //set a transaction handler + rs.RegisterRtcTransactionHandler(txHandler, nil) + //Start the rule session rs.Start(nil) Here we create and assert the actual `Tuple's` which will be evaluated against the `Rule's` `Condition's` defined above. //Now assert a "n1" tuple - fmt.Println("Asserting n1 tuple with name=Tom") t1, _ := model.NewTupleWithKeyValues("n1", "Tom") t1.SetString(nil, "name", "Tom") rs.Assert(nil, t1) //Now assert a "n1" tuple - fmt.Println("Asserting n1 tuple with name=Bob") t2, _ := model.NewTupleWithKeyValues("n1", "Bob") t2.SetString(nil, "name", "Bob") rs.Assert(nil, t2) //Now assert a "n2" tuple - fmt.Println("Asserting n2 tuple with name=Bob") t3, _ := model.NewTupleWithKeyValues("n2", "Bob") t3.SetString(nil, "name", "Bob") rs.Assert(nil, t3) + //Now assert a "n1" tuple + t4, _ := model.NewTupleWithKeyValues("n1", "Smith") + t4.SetString(nil, "name", "Smith") + rs.Assert(nil, t4) + Finally, once all `Rule` `Condition's` are evaluated and `Action's` are executed, we can `Retract` all the `Tuple's` from the `RuleSession` and unregister the RuleSession. //Retract tuples rs.Retract(nil, t1) rs.Retract(nil, t2) rs.Retract(nil, t3) + rs.Retract(nil, t4) //delete the rule rs.DeleteRule(rule.GetName()) + rs.DeleteRule(rule2.GetName()) + rs.DeleteRule(rule3.GetName()) //unregister the session, i.e; cleanup rs.Unregister()

      A-w-tLZ*A>vDL(D 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 ~P)T1dyny z!(jA`k19#gL&?4M?)*b0#krf2-y=sw&TIskZX)lO<|Y&<=mqXx7c#VnJt_a}NK(O*J|(fKSVp9<*scx|9> zcrVum4yPZ@+OiuJh(lk?mA(H6%VAUE0E;BK=8arQS9_XSEt)AJn;$mdm0ErNtJJ#} z$Gg($Zq>n;Q^DjXEPujJ(a!&na4CFv~qZP7BH@m4O` ze=KQ>g39t^WA4-94#cdyKZ`j6|4fW%A&DuAeZuOYoGkA(EZORGui!gn22+z&z8V&g zw{n7Q^T4(l*{v4g928Yx1;m;$^VptL56a_ht%%0=aw+6-21GZj-D~_&vJA&5T<;$@ zLsdM}t)7m3RPi$Jd;(|7HDaX5^1g}ppxU$FN2aSky|faY23X}EloA! z1ck_Pbb&9A&fpsYNp|Nq1k))vH-j2;MO#a2x2hMg^^!>lBun~vg}R0T)a&~? z)7@>&{9JfNYYKxvuOJ;f*+njk&?jA>PmT@LrUnJzA5oxBxj>&96DSL-Td~&7SLAPOYoMdOT%f(i z1i~|_2?01i3iN3g=+k2Yp&1Scz<)=9_I82x9usJR8kO{id^A$}pK)g-8*Xz8z~xb( z16-g3#sq5S&FDeFct48e4+~>q(J}Siw04%sU#HRfW5QTC&3IQ+(*G!mGtQco5vTqb@mMht;F~L&Z z$u3ldQUTc%1)C#an7_^WKqz!_y#lc<3N_b4&5b}oNwlEA4GlFcNvl3>^mG`Fw|w|t zZf7Kl*oB(fOI!D`#V4+BoAtKd#mC2W#oUj!2nzcz(VlRF8!>I2v?Sf@I!wZ7uFMmG zy=WppQ!;E)qBFR& zGst;rRFb~pLs(u!$CB=?^-}*CqaPLuyCBEtxlB7&lKR05wVd?f&NBf}jm>;)epRKH zh{6rfUwl9k8x@4n91?jmGPei^`^WWmk+-3|I z`WBoAAxR(KCkpXTfrw*ga7XN(H>nhzm5M375lMQ;z8-|bxUB`Y0JynJt1_Ya@HCH> z>KUF$7U!46a0q%4dw}Gf;TAB09ix`r6kS_zKeqpHQ;IwnQ0=;)K<a_}!Z38rk{( zugzMw7Zhma;`Sr<>1m#eH(p@I?^Z(vm0UN==;K!Ta2L*~pzQ3pSmeEOx+gaTeN{Yo zI;oo4Qj6g}t(L`BgOa3bhL==}c9A^JVU^X@(3VZ%W}nWqN=T}c4)9Yq!j~SumO(3G z)1)61SCFk5{Jsdd-hS;9tZhD~m7z1OW!LH)7P4bL?~4ElmAZM7)~!v9;F@&z@=l$& zUK0H^SK^^uiBZ&xddSBvJWa1rqmpjp7jiJ)7D;av*DDDg4JCx)9+^a4WY8l$60A#u z*4Eb2ysin=bwm&^Zc|{K6wKT z3DkhlyyeqUlT*K97IgTEDAg|~hbsiX+f&Pp0aK4soyU3dH?}p@)jQO>29*ir?z24Y z@Hq7pdj+KZXtduyOxBIrX^9TlGI|fFL@%pi3MQl+|-QLG+H!)l;70 zejB4N3}IJK(J%JcOi4}h05bwK7y=}OA;t?;D2S5f-r1zseXcJG%6;3EHz5dFy}H$J zr$kB$8OoAJ>JmwL`#ghy%UCex0HMOwOGQ*Itdb=Di?g=4#v=fe=0|{DF-JssrtnJC zHi=s^we4+j?0OcpIFqzN(|Om{W1v z)ja(|Z>J&y5;NV_L9RROi*`^1|MC}22ENR5QaIw2Gm-U>@O=5*8iHs~x6D>$lJpoK zg05Fx%sDe9b+I{R8A8dxAwTAA=|)vA5Vsv3mgY{QyEr>6x;FNO5-OBdI-!qwnYt#n zs!&&UH7IZ3*253zuVR%RQCI zP)Rb|WFsBsRvF>eT@m4Wk#LRc!n<6Ut^$`U1i2nuu9V;5%;~z)E4b6EJp6d0dYa+s zoD|hhJmk@mQm+TcOQ~MoYUZa}fX7*Y!v4!~z!B3+U9ObpKe7aT3YB}VTMOnLL}ebX zKU{Z8MUcf`2@7;PAI(8+bp+B!Y3SUImf?m%s=$T)caaR+3bhP$BWsOPKP)7+=*icr zAMu$B&jDaV(>Hsvc%_-QA5}uQZ}6ok0+pULoiSLDTGcDb z{}h5jl)7Tl$v84(TJ;r^ZU&w{P@?Zi(MgTfSiy`)+L=dq3MnFtYbQOdrGP>6TAmKl z?2yk9UOS02;Q@MC%&JlWTvy`*2YI}K^GF+w;B})2h_y9wzG2d>zvD8D<4!ov)84|| z$#DUBdcR>(`>hna2YS7`B?hORGHRQA;LvLp+j3Cd4Xir1&1*56vGPi(UXktkBSSKh z^O$Fi)=9pTCX+g8N`;T>iG8J}q;Iyc0Y#0&=zqBeOsi`r)olG@`%7=Ctek*>VZPV9S{Pa~;l?lpj zN995LgF%bq2K8imHHx}%LjFS@WL^;Bbgdw^=R!R-1>5FU*xz7e&DM5THQd3wl#iVZ&*-u8~Ys;p1cWWDp?|gR04OfzzY!igz zTYAMp4N4|%^fO}#;bo9qRg;q|IHw&FvcuZFycad2pS*FZ@w$Zp0U1X??>G z9I;Yc;%2bs&vWJTC>c~wl+Mx)Az80Y%pSX$ghXl&@# z4->(dE@TIF8R^St4qb%nYP-=%an!r`YCaV!FLEuxCoMGOvkae95oS_7D8LyCR`+gy z;{g)qF4wD%TP3JZ=--qj3!)!bbWx6qujlB4H2DA>mc`N4-L#dqBzIo0!6bRu8qLf6 z?IYQUh7P^wSO@h`7xhq$x*#{|TvaS+-;Saleh8MY zG-9zT4|h=yC)C3J`w(xePi}F}Q+)zg&>azW;heV}*o7|aLJj+b1ItT9dIPQ-jZVK| zC6Ige_|U7BT0%$v0*RMsXv{mlrgs8h>p=~PEzv3|v9fFAMYtJc41gs9{@oB0Xrz^r zn~7ViUeLSUY6JR260{qJus%sXdu>?OSf`e?L~FNG)PT3qJE^i!CbBBlsYP|*1v?$+ zMU@Hm$Jcp0q_vzGtUz^hG2cf?dpR?v8w_7Ugde1s2!f1$pI-zcNx0U3pC9`2*6^M% zs0eU-B|;3Y_l0TTrh8c+jJ_3TzK_yMxFB`}ss^TMaD*$AaD)hBxi82CvFn$RhMb|4 zwLt;|wZsUJKF$+_`jZ!&&K1frZ3WJzELt8sh+jnT|JFBx~?*krH!GiVVuJa(WTC^ zVteewySOgds*VMYE;ZFF$?G=y8sgSI66?`Lq5H>YL`BF2=jxe2B^cyJ%>7rLff31o zQ)#bg7n=e;JzMoqL`-?QXg)Hq*M4R5GuI^G4-l5%?pNMf!Omuy$r{*aNt)s%smDfO<)nc z*h9XUW%rPA+(4BG%$xwOoTqN6pgYsR3&Gaor(Pb(V8Rh787m|0fwAXJnFKGW?JUZn&ymp}yBoMdqrdhCg-bYN-~?vM2Y(2F)BfLvwlHszW&?iFaz`DcT0^ko%hn?vZtRZxuK$0F4f`B4>qtK6L0g+*>snD@#D!KvDz=WMi2htTB0ev?G6KENsUa3dL7R;DxRKbDTGiE%!L@F9 zN6~Z862pHJ@t51{Bml=_X&fuUf=lZD;U?UTiI)!PeirIPVHraV_Du>U570@Tx$Mi>ej0z7tA;gx!0-Z^C0@ML-#dH8p zSa3@nQdu20_CnlX=u50 z5)H;&b95FkZE1!5d@knYbluwuLFG;Fl2@n^VS4zFedaJjt9#fEZ7R+^lc@N;YK@MH zD-K=FRb_&3R|p9;=RQtqYR-N9qP+)1Qw=S$*4vO^ycfbEss7|i^(WD=Q&~*)n~AIH zxb>;Y7gGO9`h;`33cS3Ia!L|4wRMp+~{RVs;p zbD^hk_RfM=XH1RZru&BE6?<9l@lTq4^T70mY;-vJ&NTAfd^IeCBukU7 z@UsTlcbJ^8dUl9hZUGpOVoAI{3UbmEQ3r#ZG$mpKF_O@6HLCTbwqC*6|0fZlPBC~S zJ*)VXDWYN&Bj!J+2n%O|K`i*XU8&%`9>qP=;1Xdc4)vYs!=9NN7TyF>xJP%Tmh;l9 zBJ%#w#Z|E%3QP&^jKRW!Z!LT&P0_i0e<;R%ykQ-b?+;}IAf$VS7!;oS{4~P*N}ut*wjBHa#GvKb>}e zGAj1K6m3~7Ey6o2_%hQ^G8pJM^fc6mKQLwQNzAVZ3ZqZ$zQa)`U7m%D6Iut93G=NX zAprVUW(ZLB&lLMBp9DiT7&iFa%j${e?i~#o*42Oj&i|R8JFJ^}dH;!K(%9_-J%zlf zXPT_A-ktQOuT#gJtr=A&h=T!g1ur<*z*VgGVdSWIvDSPsKT;wePNzQo0J`c2x0(%E zI>NTF2fp2LM@?||z1~Ppe`91GS@3mU%AlrTDz)Ixdexx{2xVq%dkTeGDv9>I!Anej zkdM|^(CXo}OsP73fe>!0zebpJAftP=YSfSkfrLW!Gpk*yMDV`u7{7}#UG(O5`H)0c^$D1YefvglDgM#GJ6oYoSglke~mp)>Yo zsa_FNYdx(?*d0v}bd;RFcti}aWpTJQiS3M|hjb8v{Kfr_W3+=f=>1h*hOtw$ZuO*O zdI@Lxfk>uF#&6=bDja}n(UN!iBBHAH%Q5m+Xu!DNqlYX`7h?d;lh6WAW-?c!23*Rf zUZ8jQEOhGgBB`@&&D}jxd(~Z9s(0jXY%6`o$hFoskpkl)eO!t&yRHm`(v)7;S;s}I zhBVGYcYC=cob4{o_5g?SU_74H;5x7xmZTs4g%80xI8_`XN%N!9DHgho0evNljz`SW zD)upb85?)A;pqpTdekS|1$~9@6|>udg+0v)aq`kDn5PEVi*@IOUi6~auVK?NGwzFG zr~QL!jNjkUo6j0<#!Nad-6uE~gz(T_E;ax~-2lHhw&`78&yHx=y%-RHtD-=cFc8Z{ z=4zJGldG2dITB3`O7ataZFJ4~O`*<2!Pvx_Ih5grKJ&20@ z8t*9-_WvASKi9NDcdwca0UTn-JRyk@SCn8Xh<+k$>)8qHkGwTqmE zJg10;9XV_P5UW7?*@EOBi|J}5bZ#rINp`wpwhu2M<>itI$g7w1|3MOwdL9HLOMiWbct2qF(2KT{l^uoOQr#GccoTiJ*Kwghm74w#j#|w4>{` z$>(yTcHktd+^#UVQu9)!0<<;^h$4D`AsMkdvdUOsVyzYtcmM_BVRs>Y0*F^-U2XQql3c=c#B~k;z z{+}T+u--gziaC12IX!WTo6{4g=z?_DEbZ;Gm7t!}c{=k2FK@J!b=nl#9nyOS>hvjQ zm%?ln(5QVX^To70*g&9d1rvHj5K1HWsZ;c}f*hR$qF&!$PTLGn+$mG2dYC!XDO2=5 zgJapsp&Rl^qXtCun!ov3pzi(JDb$}7_T!l+?oiI3LjBsL{?SJ_V##q)BvT~*0B34a z|BFP!JAiz0WI*J{77A6at*lk-j*%N=)UI5WasA`k?fBM))`UYz@h+ZJb zBS4m5VrYV&u)(CT3#QP>iS01Ql`=PIE;?<0Pe`6G{=1iV-H+YNnT19%%-zdbMW3v* zG?Oeom?+*iMR#vFltCTry<*p)y>9fW_fEMI*+30Yx>MDFxXg>e-khFeiOT8z&O-ce zjp=6wMWEY4f}xvvc}kDd&AdG27vSMdmZhLQP(7C)60`@SsH0PA-{g4N0ZRxapyPN=j9pH1as|cz)Sq!MfkD^62CM9|!{TFNaMqOLzEDMFu4GD?Z8?>9pG36t&6UJZeiI7Dacw|kL2;FHPM33@NeM0ofcRg@ff1Els802ZfS&8}MsZ;e* zE5aTxJ`rGv0DbyB9}r6&^?f;(FX-Pl=n<^i4Ou=JQ6eD6`e3=}*|~kM(0%QFpLsVr zvctBCb0(gQC9LfeA zovEr&eM0or9i9{+V)9hZ=L716ELA}EFzRrs=)XD0<-Sx#2(OytCBSBDdpr9-S6}}x z3`A%K@h0^jbX|&#Iym)^r2856n1}ktCBLKZbG1vDuhTjfEeUnx>#{>&g=oQYpK6eH zur8e6`HUw)JjoKO+hdw3W<7e}YkO=N_J6_XTaJ)hVp_@t^k{QM_xf(O-`pxjtD%iS zFwJXjjqN>&5HJyJN+)>1bRi$eD(wHmadGd2&>d`mF6@8mxHxT9GV0~VQiRzVg%RZC zY!#xZQjvC$qqf9G4rL|^E_1qOCTYF{rz&*l1eO9!Oc6*%r3?b;kcFMYs_SD$mGKsJ z1{>jEVA{+Vn6`*bjxGq^#@;`Wa3gMR)3XW#Vu%|#4p(_GhFvc9jP3YNx3r;5Yhl*N zpci>hj^kXh5wBRIP`Ah_qvCjb7iLiE3_f#%Ll+zMi;`Q!Mqi2p=1+@O&)taUl3~ZC z8RXTXlRhVGtAuTlFw)aj(Gy+|<+K!bwS=9EFjV?V_k`AuqnQ-sj zh+aC7ocnB=ZzCsExL9{fw_+qOq03CKi536tLu8-*UFBYAS9v1}tU3Br{ zaa5bP@Nq&qBNktWQ?=|Y<`$_XPaH=xG*|&OQ<}HBmA<>YtA)=MsS;h)Z`sGI@?_~E zWyYS8vl7!Ogm&)`qHm3s%C6f}ll1K<-hzF~oTZRsZ0aeww25IY)dZ%?0D(YS?s$8o zK_kS;t_mDFrqg;uB2&@lJlWz6of~R5bgWLd$KxX?R@cek~)M9`QLc)O8F+8@=>~{B?n9Xx^c}kXyQxV4@rwNKk0_f$MpK zUa`NIFXnQa$K^JUi~bTWCMp+~St=pWchB%xaF>cvf)1emX7cyuQ)!QGGTq-@ld1co zQ#v%%-{nbimnVt-5=l&yBMFT}#A8_4|MUP~3_NiwMTbjfQe&PqHW29qo@EN~U>lnL z@@!}=hRdL!AN_fc3+KXI!Zlo85-#v~%GTnSq)yA?)qFaYGTWM7$18eOFZh2t(C6cg zKI&De=6li#`c-p6sF_YL@EV)~rB$%i;`cx(iKbg}zg-Mnuk%nZCRPO1200X4koLtioBKy4k8gfl?zjP`e+L3-ZSv z3O1AxHuTs`)@zK zh!pyJ5jY3aC8_mnbt)5XOXquB@z8D)JZlFhZgh1+0Nvv!^9!ztzbCPHQ$8&pw0l8k zMJTEE_TaEiXVT435{#`0VfKEADXQnCJIUFzmQwoA)lzO}m`}%;)|SOgyF1lUWe!5X zYroD&kNlOnD_88B%*u={AU6rR(@Su7k72AXm{;}+%tZ@4{qT5oZrqMn>WnyT+ z%mWL+y%TI+CeG0ni3PsF_={Z~Vfe+vaxnfSGp6eRNT+@kzlX(WwFQbXT84WXs>=l9 zurGR8tf=kr#WBi5LL2mc7dLxV4E>luFOf0+cajmyY5D<4dSixc^%%S12upap9Pq+@ zM1An*KyiV@8-)kzlZ#FP2S}Jlr#rCEuSNyq{4W`~1FU&2*1VI3sOCFZauH^Eb90tX zQK$hyc-lv@*>Do9>?A9r8WGaH5BHe6NxWBoN`&qoWgb1=3deaY0-<`uEqcp`_&& z9vqrbs$5G}BY$ICwK|#Sy#Y_ygzk+6>V-M--YB-bcw}B9N(kbXMLF{RpyVA7K;aC# z$*IoxcKNK=8Q&IrC|Aa}xfy>#GJcfK_%;^R+X)OZP8E|f-X~%`?Te>*z7+68#$WO> zuD|kNV=7J9BZ8@x;i0)0OyP{{GBC&#N2(ZHSW|t1k*xM|L|B6!*5KG! zO=+AUqW@R*g0wb@c&mqatK1rNS~AaR_%LLzq#h2TAomv(Qtd|`xhh{^_ViIhP<=0u z^Bj)$7}QRIl!1IU3Vo4*=3eU}xr7)NkN}rrVK*uOYro>_4l-Wm0bOQfwB^7;;k`@3 z>6P@~I3j{`rH6B69vlKw^^$t$r4f{$2}-=PUEL@ct+>@0`&slJqY+FM3(l=kydfhM zAchP?yt`f9ZNNhr&8%rjG<3JBA;B767LoD(Kt}1neqX5j#U>Oi>hk@DkARI_7#$K9wu~3u3 z)P(I0Np~MU+U41@!v5D`QXnTHJo|i^GY<0^0z1@ug#&B?x)})|Bk${u6lSNy$Skzc z&p@{F*d81@9uTP)H~P}I;pl|E7QbaPY(#&vH*MO~ALRp^teW~Z$9aoF`@Jgc ze;VFl*1!vHcdM54c1cA3VN5tpm2a2ZI&fT-O`%e%3n%>u0;4~qzTiSu0p=iBuXBbU z6PUN*M+QNJ@Ytk>wvl?_xTd=e`Bv|U-rn9!O8NVwGzM<+{2T8^&%-F&1!WW{6h<)H z1?I)gar93&PNLx+WrnY{>L&SmVl&xpv`}R3SsRk-#Z4UKN(F=C+79)S2mxE++OQBK zNE8+V20-4qyfA8QY0EBZ>%z-})y;GYFCpdD6qH}Jl9|u}?(lkm7&>evfK8)mB|LEA zICZKCSEg~{5GipoI6y*O!kh~09&?G9d%B(JE^TNYyF!E#3Cy9?tnTmM?i-V=b9KC;li;Xx)6_c-$qAvW_M~(KNo8xozTGD1X zg&CiK*9+)%%|1Btn$(Y%0Zk}0dAb?!T^76_4 zGQGW_hYJ4i6lj3zj!smnVFCDKE}(K3h(A1_wN2SHp1*DDPB!CaP7>F-$)#2@MGskz z2;OCDOrBZ5c+-YXmHF7t)Z|+A#4)*+eb}dUmHCE#2D{~zIFU?6lRx&&$y5XWL;M%F z{1)vMO%%ICYh=1;b;J_U>60XG3TE+ICY(EI{(o-|$&qTt6t$eWv#7f-~ zyPm})!w6=hx3HPon>aByejFBgCo5xCjS8FgjL)vI6}}PF-{d&yX~zr4VTU7&ixa1j z^J@)VNZqr3t;z^!+p#`4J2Lxz!G$|f&)a)EPFBF!c&fR8%2h&td8NniomY0j>7>AI z3t*RVUBdBl|%;Ii1HO-P!v z{Ir(3t2>FC7jy$P-6{MAgmzNa@WZ^?@H<@JWpVgnxk$@BvTC->5{P0+x_A{!m?z$8r4Di+(W(1i~*{{DA~mO zbOJLf$rpETb)S zT6SV=SS$1;Lf4Rwe)<9WP2L$ZY0G3W_If2{Mp9fKq};>j)Pa&rH?}ki{gBXoZmp4w z>EqL2)|&j#J=9$|HG$qj#|#GBcZ?z!rIPl{Fh)k*u+xlRg)q3Wtk!h^<82aiDGo9< zYd}y^>x@Kua)oHZjjelRbElPdVegVd=oi7bumu)2bP*<8P?>Ozy4Fe7h1uzD8(jm#JidAsc9|$ob zK)WAj@=!uTkaM*~LD2d>UO6 z`NM~ZwWq?CMml#wCr32DnX}ZOBrVwJOHU^^)Kxo&H}k^?s#wz1_~~7ViDzA^3*O`# zoL)E~2~M9_!r7`%5}fuePhzxLzT|4r13Dugr&q z`l?Ny3`H0&U*1LU{ljn5KX)PZ@$oDGU}$)G7d_FCnZV1th?#gyZnM>(aG7?z$Bj?d zTvH@G$gF;Kkv>1~^a8rT=-tVLe$>viRl%wZF!$wJ%cq}|I-JHHQ*95IgD+Oh4&;ZP zVid51fSl561Z1!{dGXv*Kwdm)k_G|amHm5kTB-KtTC6n@q?NEL4)>>BRk%)*BM|T{oZG3Ma%-6t@e^&LoCo*Pd-WgBeFPIJ~Z7^AqWbXvY_tVL<>OTu3gAw(mwds!3 zDr^#)fmwZ9=j{>s8{4W8p!xIUY6Ux#=Vc__2*v0_611ULQ|FsY1y+X`XR*DMkpr7X zkph0ie;P$jGh&WL$IJkN<;8&kK}ksJp6~c54UKR-Ff8|X>*B5IS36yIx2``J^nNMl zA5nkQ{X1#zqfTY-z?sYDX>A^Z9T-YW2U^JBhe@f>SDt9(<8;`L z!4>>Rc%#uYk4K`tdQMRE*8+Bn4{C+MXw$exBp4E+S>N>}K4me5%c^HFA*^TwX5yE5VY17Ctr3MyC;c|Zu?%$I;J zf-OsR(4ZRB*)2MIhsodA)){^Qty{gYr+G2mnGr5!LU~@0;s8HIGriNj4)0L(m}SC% zkS;pe=<-0Wv%FwjrXRO;8Ya#|^6@KSn2=QapK4O$Ml#wp4&B}_kE~-GwGkhB#=gXA z$U}mIS3!6%gfy*5{W13Q=ebO6V$!TD$FQWT|DG=g?})=IAsrQ?jg8n|rLOuC?`;M$TEm{YbzQTD#Qty9;;f!YKHtT>EN*9_=pSEZkoez=?Ep<-p zBCG=4RU3BLQCm&toRP%0_#jre#s>KF)?4Yi2N@E|Y+oJ`Ln&sfUZLF7KfV-mRIgCp zl`jP(!Ug}<6iT4oEXRg~_^o`2Q0;IUstaZqTKI~_)%Uef@5$fT){w^CG<>w)Bs^0m zoO_m+D|AA$)V5DZy~Nk-;0w3DK?s*uJ}-8aj>M$p>*rqK511lBRBq3X`v%<7v`r0t@IC5v*QlZuV)horlxp zXYHwF@q1OeF(IjkB-QTc=Hb-RjQNoemk7}YpO~hUPk%DPX!-$N>pEx*fKxQut`?A9 zNnCxN;dn6jJRNGbKG0kr?^>r0c=Hv~sG~INn_Per0&qqE#LxCvB~i0M`w{X!Av^qh z!?%k0VjzEUvJB)lZE(CsPPzz4nI!&}4?qKKY5j2Ixv=?=`E zkIFoMy3H!@Llq0}U4LX`sPTJpI$f6FJGU#dc%q6m%g4X)D21dcxxl1N=(NeM?$TsK zM-#02CT?ax^Hm?8Nb&Yvsl+cHFjyhFhAh8_rV>K%)qI34X~5M>>`nQoU0m7U8VY*?8w!D0;6L z19Eoh+y!uFrYTB)G6v<`(nIDeL-FQVlnZ7ZG8-<(G}gWHH?}p@G)U#M=FeFGL51W8 zSB=4b&g_HdFHi$Q()`mgNaxR+HwPLOlKEGUK{{*BtkQ!q7Z92$*Nj0sd;aWMbMc0O z&^$I4?Y!A@XD?WwMug_FYsbKyg(8@D@O(8WL~94fAf8=%$icJbVcI7|i?17lcm+3`DtF# zp4D(R&xTOh2~vYXdtJUX-|T=(Jo08SBou!PQ^FJlm!@h&!ashCE2z^qVhoe*EL9>j zPkFSCVZ_<@!uUvQ8Z{!c&)n+bJ89tXr$voQ?2a%M#U@)C8SX^ z{Nbo!vCz!$XdRx7a9IFfBzTxiC=&T78&X+5TVe>BK7f!snP-INqz|B7orWK?J|TK^ zD35+b0yLiyA=&lru}NtLBP4bCNLf+kwaZk!P;AOa3E$^%TZ8j6y+U!x2T@{Ey^C(; z3<}{RVJ}UJMf&nJww4kWBqe2-6dR z7_c^qg>3(AA#yE(J|a~rkw=G#4EsjBv|;&;zW*SN~rH&-P>x90FBH!_oQE>!n*pOjeeCZ|s#+f(sXEf=mV1TZus;~Wy6`e3iGz7P>s6UhR0foA z)!>@7SY-)`{dz!kWR$9|mCRSFUT8MvqfNshkLs1!%koh*wx_eHX57CPf(P;u@)ofg zmgu+gk+E!QyVl1v`5W6B%12LDyS$Y6@8zR|p`aW7Gx{WcYd$)DY+emW^bKQ@(U`7= zgyhkDq!`lL$eka~DGAA&`AD&PY$U(_>W@0tpOU|kT(84ynlM#~Q2ZnxWha~mGUcF9 zR6cELSV)sy-8cn9S>o5e6GFB*A30W?wdT}}>J_SieAG1kufPS3AcdiPI$sJ1gBR!3 zh{PBCDUaBogFOTrKgB|^F&`ynE8wq*dxhw*k^Hc_n^I*$aztLz4cTRM2Q?uC1NjI= z2~2E=Udcxc{VwBUOiDeYW!zu>Mrwn>)#Rl@_RW0c9dNwcmQAUQ5Nydu$eSdpPoi(m zM}~&qnNmX%UH<3%8f|FD15tcSN{H6wB}Q|_5w~86|3yB!bXHHQhlSvYe1uudfSQu% z1sa-hPy>f))rgS3n=c9WZQI-R1zEfZt8-N>eXqoT@{vtHpbfs0*v}Q6D4Q{Jx*5c=FE-=A z<_`G0(wccbfY^;jnxY1b?YlI&ZMce@oFj;{zoNQ=X0joux*cwmh1`u zJE*PrsrLY6qg}pmAwk2Yjtzlk1nlknpvMY6*(=%jD7MD{)RWa*sUm)Ge4<6&EPrEL zt;i=0QGO>RvWML(m^bCeCdrA-_#~&|Q$vDw_zO`b>k^uf#Ee-cSU2a#BuNQ=e3CjZ zS_}zh-G4>p-L9Qf$n}D_;KjViBsDP^pVW9qdo7Nv;~s38K;D@HntT&Xuv%E) zAp!hLeqfSWUdthuw-LN zEe`XQ^uVpboY{w!=yMxc{+>Vgu#&~thcsU$o7Avyt@&$|`|Tg*TU_I*H~$jvgz!8i zKX1x8i7|H0kC{K~8~>67eZqN%F9D9Vi*FLRyM+XAk6#i0vMB0rx&W5TU$=`C{-g~0 zIkqy)Xk$)pd)u0&z_Eug{l9lks zr`c>zQ@TYJ3*4^{MKF+Vm*PHe_0IhM}6A|DmR>i45M5$_EXnVuMb4k{sdSLDYfiHXJdB#vAg zQiB5B^v|g1#lM|!HHk9F2=Hfj>Vj0VP@K z=lEpp#s*WS2KVMwLLh&V1Nw-4CM>T79u&Z-g;ALWm}I7(*hISOntCSOKrCEf{;u@Q^_$4zCq@z1+Y&Qi#R9xVzF7~@$u*R z-ylh!NU$TnB%~D*YHY0>gA-ZSTou+tpa`R@ysA-CbV+68uk^~{65FR}+>7CVmt;nw z{dUSzm*83-suIaaYxHw$trFtdua%-99wy&!qD+{ z*l>mgZ}QI3LfPvhOxP8X!NwcgR5X6#Qd}On% zoULX4;AN%UR#67%f)F6}Ky)i*g8hSm9T2!#MZTO4ENMXBdl`>aD4XhNpd00?Oc2k@ zfqc`e6BanY9T2>?^W&1l#3DzT9u)PBJPJURD zmpF`DUcQ5)inY9(<*zI6bc?594F7oOxNPUF+M z+`{9^QX-&d=Kv?yWD``o3q2r^pWi(yw?LEJo&&U;hjEUXH8)sOibc3oFn{c0ho?f{ z8h;zj!DK+dQ+s$BuuSv2oHils=I{ebST07L+~!@yGG&=iefh z5$9fCELM`pLQ0~aAEYE&NRXHCVSzgQQ)A|IM)9~?F6=0RT_%{%``9e50Ft=$^8>`4 zGv^!4?dgVYTu>Pmuw(WbQ{Y1yb!3!GAOX!EgdRfTBg>pj-KPWdw z`MrYr(%9%XPb3cpdsHyb{;VfCS8V|#ap~s=hU6pt=DQ(byXSsh)|mZM z{?T!d-VxRPeN}}Y)M30F$)}+L~&Iv)e_uFW1R#hC?;IlijgZG~n6|*#Wa8h)vwN2oF2v z3SU@Tn=6>qnlqei200>(zQ~VhNHA7QEzFaV{8A!HkTY^Md-z;I?!K(zJ%wlr=4g(q z8_nSYLMliFcboXMx*(sHcq+Ux(tUKN;Y673?u84H?l0j8<`V2*FKt_EdJ~J-z^?{> zwI|h+npj=O!GI`FIU0wR;(gKWAW0YP?GvCeAdVC~InS2E(`&ZH#mdqm-+m z+YwOctel~8`>p;w$h|C9^F6MDWCa27*PJVOAgf})5ZL6m+bfKY{vcHE*VO3 z8}7k+GcSfv~uQCP(BEH%J7> z0nRj~72bpDF!gT7F=6dEf(M;BvoYwDNzU%fk;4~0@vpC9{{ND{A<0+u=c6Sk`J)n~ zpZ~@Zq{Cz3nIs-qamIQP=fNCeowWP^J8J(xp@v164}Br3D-nuxML#E~E2@uqDkr(D zC^<&2%H8SSx(0Zu#Sy${4Bm!fN+U4;lVU)cTrmkN5Nl9Rxdx$ zMv6rrZ8`MvhyOug{hzIsjA-M&{92*fLJEr3mZ@gl(O9a;LBNIgKlQsG{QeJz+VEK% ze%G(>*nZpJZ<`T>!$EDbW_jgT6}?)Zyx?V(%St&o77kv(!3E)9c*P@>!f-eocv1MI za5y$YD134_ocu>p#1t>0wr)jrO%+G%|b8ms(dWqDP3bq&+n940GkmZQKq)MhW4 zSh2hg^`oYeBOGLoSW#KNh$Czcbc$6gs_S$TTS;_Gf})>cAMqau!2XNhra*b@89}$-?lnmO&N5KnM)-r}|c@(-h zp|xn+9fclIEevg&qu`abD`qiBN#pu> zD0ulw#{PsCymE!^(suVkk3y?v;ywJ}#kF7K;61(IBkO|vf6|LUBVEk&pYnoNRo7H3 z;qbk@@M>KFKkWr3R_P98Z!fg=Xk8yZV?$9HlBk!9s&#QX6;@)FAE||LDlNsJgiIQz z-b#!~<1}K4S&8Qu{m~;SF=(b7+> zTPd*)ro;vnz`?9cEMJO&k+bWh;Nayeb^UkL&EZBgM?D;FdMBrKarlZwCZ^Zz2Q`*v zK^nWUFPPuHkPU)0N`Qqftn(Cv%y1;{< zm351wz2@?YBa(}%R+Q_$#$mENxdLO((xVy3DWv6jb3_jcw5MBYdOG-8n_rVVbk}#@ z)jf@CQr#7BL`m29DUr=g5Xa$GwK#Utv=~o1rn|t5<80q);}*fM zA>r9fVQz6mY6H(YD8d=-5OZWhdrvA?9G&IFW9IPm$8!}c25(g{3#*^h+J?p+tR$K$ z>0u|1v^6q_)Wu^33nH|-tp?yTur8fhf+-e5*-A`S_y%%JL>aLh((O&vZj5C?(=_s< zNe3|~jfKkM0cpy2RTmzOqbQq0sO4+qNt3l@gPY0K@e%NfTA6O=IGg_@YpLY=#axb7&+3OUTiHENp zc;lqhws6Q%Sv5AxEU=BEcb$R`r8MYl8l7&&&U|=JIN8%l2B}mNbFtM-M>xvSwYk#uTRlgy)-AUv05l8f80Hbv1^ zwDxrB9C<6CWew|>>1|j%r^PkbVMq~?#Eznp+;MQ_w)t~3pTy9`B+I+-xVNtPw*DrW z@v??YvX1tNgQ{%DO-m6(Ix}SwJ*9`n9rVw(U%ZaDzN(S7RXIx~wglTcm!?`8+7oH) zZs49b*<75a3Z0u6U-gLKcN{6y)=1jtB*ARuvbvKvT(jZC@@O#xi~kyP-WGv?_K9tDD+W0g21A0;W|IdOP`grBWZZ z#n94-o*L?@!@&kiu*Fu}md53IL-x@ILZ{qr+iM4G9Km-OMdpOMx4+RooqD63aaxsUUCdq4MtnIA_=~z4h8g7(`Q_tbyIy%6)cw zie11~+T%z*Oti2?S1TrQs{@lzUE~-$Z4%zLI&-|uyu@3+R_(@T0gdXFaJkTC%$6=+ zK1%|(20X>UD2|CrM%PxGfh1L7Z*F7xuhUUHMo2BU3DKSttsAob=vNYGExC+JY!Ufy zr}5HCt|M}eq?}|~LnpoNK#43fort_?a)hm*ofv|^hsQ=y!AZ90uvFL(M62%r(oTMp zH2bgBUfqJ)&AGJcT^gD%;%!x?u!5njV~F66A zU$|7)D%)I=HK>X#>D`V*wbjX$wX&n(1y>~(E`_O#6NDmG*4QYgS^}QReOY;}PUWy( zv9x+wbzQPtYuPUM*}tjo?6?tAZ)iIb3>myk*8@tPxGt!lq8u5k(rrz8jompjqSx?wP3Zm=+#zrQq+GsvP+OnLIRhXmlr2>wyTy#X#LXfP2Cs%sif$8li zl&q5DI~;CRovgz9i*2hhFNUoGXAksr%o&rbnrOm6QaNp@D%E)`ofUS|*y2rA(fc=8 z&d5~WPOl(cqglw?g3_U0Mo-8UYjXSTaHN~%{|raE`TmX*naHK?1W94|INDCQESxe+ zkQcw-;YVfd1bJ&2JM8pwoQeqA^f-y-J^Qn)M2yAGMZb6T>OiM*B zE~kq_OjWv)-bJEvbaa4-+7#ZlG9~47y(HC2aZE|6+@S+e)w22S4&imwELP~}H`%zU zdu>OJOo9Jsg*kcUh_Cc?WUMSsjyaav8_Wz2@N+6?>*AFJ@w(rT@dBfXg~7|%shYO1 zZJ9z>;P?)!#)?Y8wW&^B^R1v1>?+xoc{bG!voR-mWg8As1)}K)WE!37#7$Hnl$Egj<{V>b3?VrY3U2`hXg5C?Rn2 z&Im*u#J{(9SmyASB9S`Ii|kCb_>sN6#|N-RI(q64(%H>$kxs&``bauCzjK0xfpiF@ zRRyGb&vx7pOu`|U6AqbdxKjXfRofa=t<6TF>_BHVDNzd{8mrBn80fp-H4S>(Rdn~B zqdQ4l-KmXTITWaEq_r4-#8zn)ucI)c>zd^BkTBY&nz-*N9V1Sk%W&-#JOAd0s-A`}-O-IYV?0N3o7Hf(kI`ye z+SVZh%BY<#WN9d2ODHF@s^^HsUFnXT1*2nW=vv;%CIxCvp~P6tX49P&m|5h4u1WKB zG!)fM0}36q#5^$QOiP^Rw0*#1$UoA#4nzr&kD@(E_U2Z1CHcc{7v8cQb z2b3uPu(0zZj3HqRO?78`9cIXu#L?{Hl{FfOc*y!4aJr#5LIiY6)0pf? zEH**Kl;)SPspEL&S_96hSW3GF^^5NWKv zRMYIyvLKolgPhiGvj&koC*nwJBM2hhiL5Cf{OpM}H#%4Y*t2VHwAG>A zIu7D#EpH}vp9#9qAiBB}?1P1;(--IpLt7x)$UwsH6HZT8m}T#5-*6o5kaYQnDLxyf zS&Y}3+f%D>7o|t9Ej|~f(BlBBZohC`m9FaCmomk)D1}rZOS6BJ1{xp@Tbk)PXsDxE z+ORsM7a}vF6kH0Ti~}Ok6?ExTZ*|%B`T4QPf)3ydVJf@(bD$qphZkndq&nPvgKOiO zU@+*}o^+S9A4KV>!A@sZ=$*pAJ7OwCtgFPJJqs9VEe#4_1wW2nFN%6S!;#qBsj zBM}aGuW8FHCNKYZLy1{9E7b00m=agw!Bo7U+o>v%+@|I@HYJf-Ino(pC}9nbG&OOA z9YNM$$6_H|S}-RIM!{wdVNoP>0TbI%N`mE9fR7CjnZDa$N$6$KVHHH-Je?uM+M&n< zYqoY6GU3A4jz1OI1xQLLyEEE3I$V5;b1$WP%zY<>I%+`*#U*(FdgF* zb{wHlb27~i9u!*J-X=TxcFf>#o|H0-9VsLN-c&fkjt~^FvNPMzoN^CK*a3p#7qxZ4 z{9q5k*$$Xut18eY_3Yni&J$P z(t^G5{*E1f^pTY-YLC|YHKSObMIskfF6QjK=SLzLPSIo6rM$K>oZZ?bH8|pI655KD ztX;y2F#K$%hiuhh3$!MppW3>G$*QHx7e-H;Q_QN$^2pvzWp%QqdXd>`v>n>orfj-) zLtqIco86U<(*M~$nMf9G=;ZO3;`MUXRv&@TvmajQan3c_4qM(tcO>>(Hn7#qrLnpo z8gD0Cqu6#q1em>Gk&mHRAHyPlRRIK?tlt@AU^f=bNO{(Qfh$-wJ-duz`(mg3e>tuVV0%1*0r)-!p zlaBwZVqx0nGcQ67IG(tpJ`s!KHH$ss8O%*nRQvnu4gT>WR)Ei0!$xUWN*UPu4)OCg zP0O|=M*1r3zgcp)CFW#tOHAicXY1sc&exF1momhA>^d?TwLhg!iD`X3u_+FjPhsD= zh5g$$sa=bwFhQRXj4(l;5d3D-t_dA~j>Hdh{5cYT-zLR086^Gv zgwdct)P!s-sgYFz`? zYMiJ^#qJQ~sbT4{xz}Y-O9ZVoiuy}~iVXbHWFWu_D)4eVAxCAM5rn-feED#Ok`7wM zek+ALUDsywIkku?BJu|UUCS0w1^=yA`VUCy_XeeJlJBu=uP(d?rSJy0@E(-H``)JA zp8ukj=Z-Jwzi|=hA(p4Gzi)F~BzQ;?oxu?1>vWX=u7jG%4HLg^I`Q~CzC@P-E3v}c3(>Aqrb#IWw8A&v!I+FMq zNvvD;3k9ZS?{-s_rrohb|3#yIvA}eK@iq9M{((BTTrE;+zVI&-zEi&9^M}S>W=v|C zPHTC&K-WHMqN4@6k88Erquod9u(A7JEQr@GGsx%=G2}L^ORq_(T7oI;zv}onKi5@9 zqfkNrQKYmbzuJpB?KqteUHKV=7xoVxAJ1ZC&by8)bjqLnKa{--e4RznKmMcU^!4>^a&pofNzOT(bCMKqX=u|nFKH5z6ljGu+$$Gt6a}$J0g9I!ImkRDbSFo5%6*dst$=ei>1Tg^SAT z;qGX~Z=K)>kXzEwG@7h>1a)W)#wQk*|6vQwrRLM|&-5}Sw3Z?AxLaxbsG6oV(;&o_ z6R|UdSkvjrSvvn#h^KLel5W{bqk)Sh)5-bzsqya6cWG}8G80CT{;)^e+TNFHla4-; ztNbF1cDFTX!xJN`lB=08mP{xP9a4Oxft*oNBy&_qs!2XSYv6J5*RdP~`T_!qDw0 zh9;n1xzS-{22Ugt%EY_h9x>Chl;k*)JfNf=*#vv>0CUhdyM1%ZgrFZ%^xjP^+ePrd z&@NxB2Ht}0}5C5 z<13vAW#@{`Y0_}@9NDZcXda^S zPjaJ;v%3!Y-TQ=%yRLha&g@JmkdjDGppq<$>b@O$*0e3It|g+Ii2VP z?Ix4?uGCN0gwkg`IGn;T`d{*OsbLkzMl9r`3+eVY~8qZ|VmQ6(bJS~1RwpSBW z0Da%uNCVk8d&j1xG5RI^-*INkPA+g-@;)taD+@$4%cvo8&QDIt(!zVlg{X-ID!XV07lSEi*-PwdAdBPVwFQ(m0nSPiqOuIQln+{d^n2D3C|RPs#u6DuA-qHo^JK;>(PgHk6&Z|R=_p}|7YR3X)G$uHOtX8nxp&T_ zo9)(SVfrSMZZUo6ADB>G5+;`2$SS8yEE6U;8mHQ_cHQ`@WQCIdwQq*KEwOS3Yo%tC z#QybQN^+#-Xkus@_U2&ea?roh1eU11O6cLU9N&hZ69wr{!xEAMSxTXwR0&utxR1rA!Jr zm5j*mvN0tP*Vs6yCVQ3Ep%-Lmp3~roVxpeYAYCvsm6y>rt@yVz0d~Q((gHRpvd5Hl zRQ|2&=+zA}jD|LQc}cQGxVlUKHf+-#6Y-lO;y1A`EX3vCO8llQ@o^~f5ccOv^@QeB%I60QKW3-KL=K0Yk zf<4%Eqb+pfP0|glQ{0U<>Bez1cevD?**0af&9M0!PJf^=H9(iLVHgixdvF>C#5EKicC<8@0<}FjnKCkkO<~S((2zVySs(5{u!@>Ko2$#<^SgIhT~? z)65b@7MP=(l_MAgUlP1S4?f)}jvadjNrR(lEF-B!(y9!cIU6y;y8EmB^eBZI{@s4D zEFRuZ23n@%!~2Os(5TD5b=Ew*pH4~Q>393dsP8nRPFt4YWJa6++556=JaK?w-92V~h;w;tcXo=Em+`&-^0@zG4L&?L!@xl~bfNcWz1BP zE1LFeWq&)+M=B?Vw5)U={6I{f%+bdWG_u@zU@XfU4wOE$adS5u7=~2-%~5)JJ}bH5 zK;2-{%5%ekM!Gu>%u1L1|75T7^TQ9ue4*dpW=_0T-=&O*R^R0qp~p5F?$U-2ZpKe~ z$t!V?xujy>=GGDXX+CW&@|LlrNp(JJc3#vv9%^OdFPBB^=uqGEyfD-^KQ9`Uo1Q0P zI*!G3)ALA7q-JUp9N=KD2||lQ#>(Hrb;kj{mSDnJQTs zI?YinYnhw(Rd&IVd2(jlC&71VxxQ_VHnZF8J_-JImLXhdI7$uP%9{^XzilzTd^jb0 zgVv7-k?-nXm=Nj`oP3SEaiF;2Y@rF`@h|u2milg`df!zMtByLa;uv>Fp0?`LQk|{3 zn;`g0SDu&Y9ri~l<|nG6m_Eh)I$$X8DxtZ6{3=&^B^|sH4Czu1HjHFljTwf5IP#sC zP^hUt*N*c-lH@C6Xpw<0x4_PPZcOvlpKIZHydayearqVtr&k^r8pa4b7V`Q0acxT$ zA0xSRhuazjM~A|@zE=d53sMJ0;PP^&P7&Www$j>*unq7J7XC zL8vs{FN%OA&87Rd7>cJsxNyW^WqC!Pc?gpQ<6F(= z`tT682(j7HsTO5u4zj?Sdn@AKE!4bEY9!lNtd{j5?PvxLv={vo$uF^F+$3oVz)HEh z(uwy}+smS{Ta2nHj;F59Tr(LJTgf>kHS^@5H5o}WKNR6uH1k+69Xc;B z$PmkQx0ht_j9rmRxk5E3?-`$EriJ3FJ%ut4<5`(L=Ljcr?3@g;voqH#70=5M?d-{k zQk&@_%^9oko=g|=`C7L=qLFDXpp4y6ntcULB7a$0!d-4I&So0x%5~v^Z%uuDe;stW zhsE-~dMN4m)-0;CQ#yWr7FE#%+_N*NZpcf9AS21{Lb)syFV0pQaB8wOBQCP1i;sGP zSW`2_MPh1gxy=k&XNo2N`s#h-=|TFcXz(@9PzwDnlxq4Y%eDI~sFweu&ro2n+${#= zx_uQI)F=M8XA2a&3dKT)dqc<=cNIywvr|U(-PyY3Wfd`X7iMv8X?br3)ze;;qeSQ|})|K~U)VETt^h>(>xfxtl-=f-* zVX}I>nzD1UC}-txRVdYgVC2ru;A(xvqQ0X&lc6a{v5#kEu>B=m4_SodnA<#e_Ih zF%LcUq}bCbj!{@=h-^<@d_}XhV6ZOkdt$=9?Qbbn2I&q%p(OQUYVZaN#j1N3Dejj0 z^hlayzIUMH{IghSy@ld4w}$nfMG|}y;=NaS-=5;FPZqwItD>5QRR2qI*CZwDiVi`T z6x3ct+uIw#qaBJ1uloXhaX+#Z|*&c`;nSPeE^Er1{Rgv=-N-N9%NWsCKXw z{+%|bmh~u?)u>U`bT>fRKY~X1pjMuA^g>+BS2=!&uO2e<9$uw+m{>LI@vn|dKIMrL2$i}CTxgejaDSR-Xzn(9!OAxta)bKNot!*~GLSrA|tUjVxb z`eB_gG=TQ#Ml5 zr$2^qy0`O;aUakK)1JZp!yQg=4DGFo4`Z~K;qX$b-6dg&v@-}s(0zhRf#Kq^!3eVD z(v?o{Ng6d*lw)_sSVcv_PRp>SQ3I7gFvY?^#n&>*zy#^(#poj;kt9;z2+88YCqlBg z@P&{Vhi_AbXAALEpU2j^2g`$tk2&r*me=F6Mq-Sj=*|g@n=yVCwsoim+~5+%nZA3B zT)7M?D|Oo2E8>#=*=$@DrB6nN#Y5pGaelAKFxMXT?;)6@m9&}j(|yHTFghDQoaLa_ z+vPshb#At`T5oT;B+Jvc&E8ELblzcFf*oa%-#|ytPEPKr4_YM?w3u<&n`*#prFyxTQXC!;P$m)e<@7#X!GZ0 zx=dtNtZLhwz=V#~`FjoT}j-ckp?peH_Bh)XK2+*2Lw6)m|V(_$}S z`vm*V47S`Z*5Eyv`B`pjBd53B);>zGdv)KD`OXSVlq6n zyzDaXy)!K;AK<++BYk*rI3f`5<^QGDms-WF6=^ewfbXD-Q-qz4a59j~35#FKaPQ3R zd11)9=vC#gvIgdXfFoHgyj9f0_!wP-oqgr5^nOyI5w-Kf0Cx@Mm-p!Fz+sTO2AAQJ zTSCe-%}|+K!A5aeJOHiwdX}XPD!M?0agp-JYrHfCcTQZGn_L`V@X7PRh=|ys`1+te z!@MQcw0kBw92#H~?fggEtzxNG=#seJ*_K8ry<^7?5`oez6eFV23g#{TY?*UX7#gG% z4IijvoNFoXq!rdYh0A;hs^-8d-4wz}*Y**O8tH~;Ia9LIy&zS~NjLcHsM77EV++^t zrA{6@t28w~5>{k=9va+u`YN0wYs(H)95jq&0Ve|e$I?6Q?kltg*VR6V+0fqEMYCI! zJFr<0Y`$E{=ioO9y46p9L3iV;toY;|zG)~Sv7n5CFP1=_yBZi3OsR~I2NtR|jrNDB zCTM(#u(!SF-e#CdGmW#WQn9nWB08GJ*7ES>U{UvlE!Ns=g?_OY#_Ok+-I-ueltLGs z9QXA0_0(kFVYVrZ>#IQsodpeTM6BYVw}MALa_yOb3f1h)cl3!LW_u5~c>u(2>8VbS z4MW?(LhJ~wsEszWh$=Dfo(Vie=_Vf`izT=8q*#X$5VN0-c1o>5Jp@US!=7&NguZ;A zTH{E(U4!KkzQI}I|4$h&B8ow)Rb6a1T@wLxw^o}alB!h@5q95RPSvXRwCgyVeG%1h zhaXlg0qsji2Dxk>8QYxL`SgJJ58zsv_9L7#=pKEsQI|p&-gd!SmF|3TkUx1Ok~ZoL zcu&t@z8@1oaQ_Us0tFyzICYTq+^A3yYg>C2-|g+jx9GSfy!K%f+T)$F+ky8;6Ono% zSV1(Z#EaGLLYI&auDe5b*tUa}3uxc8olQ!+V=%P+4?NN8B^&UeZDVm1oilAmqGZN% zf%wz(Ev+xxTp06yv>o~gzMqQ#a+bD%g_ zsJh4&D#hdJ%#|BZli34ZpbskeFQ!smGaG>|bvh>`1$U-98r|Mff4+0D%bl}XLqN0$ zS(6j@SltO_gcb>9Uy&8?o{n8b!lsX{8kxknvL0JR;yqyyY*g^!cX41Mn?X+`^_CF0 zlwq|!E7OP0OM~rl9&}XDNk!m9Qhe;+CK+6(^S=+tqUf8_YH{9_!RD66J=D~B1XP3L$#o% z&LIke6MvR-VVP1i3h7YNR2?qhGjc;_vidq21z4Ejp;tfNeSemq|K9z7WNP8On49`l zww8-vgs_wA6i$S>5}u{vy72TP1MWB zLp&P4w<;0P`z8qm4=k&dc%G7*#04 znTPAwQ^<+GerSw;3tYy*_Tq`&e+@B2=7{mjps95wft@AHyR9DL7hGKmg!-xe_SWS5 z-mL2+DS$b2L`ItjAcW+Ys`t1_D&o0?EYeg7O&_zmO)b!VgZj)aKerKb(wIUqzq|(O zj0)iaMp?bI<`#6zV4Z?qgPpP%rqtG;b-l5s#MXyJ?2vhvni!r0fWu+n&a9X#oPhLh6mQgwRjv5>1TYsS}3L{O-W8Bf<=Zre`H z=*n^{%+$O!!ocIAVdX$)krc|Bi`t_5#;Ky&(|@f08htq0;Q?OWRq3_1;4nj&Y+~9v zW$@KfUnQ4M7kiZ@!)IkUH>H9sk|Qh@Z&9{m>eZWnR*m6?r_13mo5{t+)EBotC!)@E z4DI0&7gV5rUl!PMU6p*k!=?C`%}kkgpM|3CS?vTJ%-CByI28XddCN9Ws(_`TeYugW z46R*x279%)k&8NrTo@-CYh97-V!~3^1p-Eh@3Koa8C@1|IJ+jE(w5k+cV-mT%C=hR z)v>X5@iDa>|A*M>rLHdjNjQ8yJ4}H=yXr?>C{+r}FyUx*=dMK3D`V4+wU7~ZwHUNE z%Q`89uua)jz#Dod7YW51=sHV)uZH!Z&H@n7_0504*ERl;Z4Xgs3?rFlZ?>(YB$#Is z8&ySildo-^8m?W_Bd%ou*_$YIARiX7Plgw>EAA98E# zx<2T5iW<@H5?Z76N~>>avl)B|?C1NIqfZ^UY%-YZ#UV<2=O7f9wIZ19`o$y|_JN`Q}G9|l_HOthlJ^Qvl1W9Jf9q+RuzIx#U9}lx4Q?Ohbz)I?Py#J{SKJ*NQ5qYlR zQ=81duP(eK%zzA8Eh#w-KfK_sOTwJUl&uaRuMJliG=Mhkb}n+^BU<1l4o3Z-?BV?=99%8%&_1F5Z0|~Y9+oA=gc7^d-IiLdH?Cope2kf zmLj@AhKj{WfUK-3qUQUdK^|PElBcK+a~U*(G1WYR)5TgrDF&G{7IBt{(J<(Y9w_wm zc+rGKwYR-Ic+?V{h;xHx?IT1xw(ATJD~|)8h@g&`hZu(^sZR`1K@+BYCWQbHdshF(mEFmOm#SpnVE1P z)uR|9KFaE1^2RG7EFAYf*=QgmD}{cCv%>~IsT;ObB9m4RfIyC?oK4!4Od>`O zF^`H-HpD-1Ar)v*sL)nhYW~6PDp8LIp(wV*!(Jae^bgi&-KRY%t=; z+IL$M$3gytlH?fUz@Ch>?fanL2SlBrtW#e;8q2{#vA?~ifDfn%2bR6TLX;_==HU2z z#YUv#alB?1Jjw8Yon3$04`Ka9jg$i!`}HWBd{iPkG;Bp`xzV{*6^G8!eg!`SnNHPB2`9Is;cueT3O zL>mWC@W}YI$!uX+ph29z5F%uJ za`+$oD37fZ9tdjZO67Bv6U#NdjS%(_4&7Dgps>;L8W#;-UMNt>4>;o#ycvgHmM`Yr zUCbbTho!6DFfE!#niT*psSb9@xk7NFeC2AcD!CLv6CCdNQpZ~$xxqd@^yUEyYqTqb zzb14L?1abTAWeiDc4Hog@39n19epaeYyrF5&Wa^0rNPB=q^Vo7xkDlYvmUnbzD%BX z28<0mRfp{>!k(mE8IOdi@#+3qeV|PslMUPFVoy6ll2}9ao?F}XWfmNG4s`)ZYNFmPh8jdXhm5oVO>9b-&OMi&`{=u&X! z(#WDqVYQ4?Qxv#|!^HB6ct^*b)0lym{f^K$U&S80-iu5Oq^GEXHcHL7oH@!*<2w8F zMA6)36pHy=w?ydERYEFCn!(=oT24-ajl&?+IMl_xjYJF1EBSIyyGBAI9iVPiXuG67 zQY1pcA*8?*O)VTn=W;l>PmeF)MtGq|fP4FImzq+_5e|zNFeFZH*!UJD@^fG}gdyYS zI7CUa!R%TGTxi`k5kwq1T^j5Pg||md_q1boTW9N48pNlKdfLVDG%5qQ^;%MxTut9( zh~!$tX5J)kSP{lMt}q2k)|Uv@iogRBehj(*;J$jdFL>~VjkcAL&r-*Kk(hG52!0T- zmhlTL4`l{jFLWNdp)_b85)5^)hl=)jQ9&q*arr9Tc32t|+e-&LCKE8zE(-XVGLy(O zfD+h?^tw#8O!wmInFjD;OupjT5B5_T#39r&9G9k(3KdI%(m@m{>%FQ>^$H?|u6CyL z*i6?rUsFOBMyQoSX%N@(AhzwsRi-Q$* zOCRtq_1-$wjP+R)mJ3jYF-fntTs{O~EPDu_mOGFk%qKX^WH=ZuS3K4e4adU3BAi%2 zK72<(#>X<^aL8q-W!~ZmRmY2;_Q9}VYmqs21qDH4KaGDdrd-f7Qlr?^ajuQHlh$tao3v0H0pENvY$)nAkI&R8fnZ@l05LLLGw3b*%2DHl zm0{<)!AsYgJSvO|JB@%^E)GT5J}ByVTu6c0I zjWI~DvN+>C9?@9F9AXF5Vcjtyt|^|BW|s>g=7gOp3=6Ev!nQwwuOwbfOpy!w$~1)+7?m|6k)Z^ zE^kN{N1iHfB;alkXJW(9u_fdI3<=pFlPPJeN@RD?0We4rYFSh>Cmcjf%1~cXE|beh zC6LEsT8IXLaG2`V{4z1Ak&bIu_q|-W0-(^LhZKeYkL@6C=;Eeu#a%BnQ(o~kdSiiw zWK5CZ5Fio#H|9$)CZHaUF-4n1{IoU5Y#^foK89zej}Yha!+N0M9W%-n?~lfeKrKGJ z5s;CIG{6PIKG*2Jkb&jkg4l_&c+N6ulG+m#>B5?}Ow6rOVo#x~yxikMoFP>ANHgVq zWkRY2I=61)7<6)xdJ zBgR+=9Xi-Rs9q$U;e@cbkegXjLfyWqEQZECQDg}xd5&;KQ_H}5!Wq^H>j-D&5(e04 zuvtH(T}B5DHm;G!Tm6{H>L4h19XCFOuWoewyt=imj-OMvL`KKWqM_};jQ8Zj`wNEz zXTTOfoGqQHsagi@!NX(X$(YTARz9%`uRgubSR8CTG2rQE6o(6|M;Kg@3a358;b+>x z`BZq$9m$0ucbKxUlOg4GC@7~^>GGFzgFRIgq8je)(XbFGE}Y&5oh1~`t?1o24U%m3 z^2XP$mm|W^?~=!YNnlKFKpl9~5Y^~{PYlmi1QARn*HIkp`st1W%PK|I70KtsO*LIK z)Ca51F*ee%_;`^wQl~;u%4_iLm_?F=?^@U?cwEJGJ1ilL^9OV(hTYjos<_L&OR-;( z=&fkHUgcspW7flf#Quef|PxePN;ro^D6k5Tx0qashWDfvPFeIEd3% zSnNS6S1M|xGdh4}3=T?t7KI6SR%(bvBfF3@mHi1Hs_zO0*q1>z0gp}05np#j3@_XS zvnrpy(hAb4=5zG0meP%cKsR1FX;zVT?BiRnRzO|&c)Y(A6^RZv;c<37pz&3!nRp=2 zU44ujL1+YSybpJ$#ELicoL-vPD~XGQp+`DV_=0i%bzzc4EGxdlR?v`%)fjr}O-zpQ zJBXT_Xlby!4&VfvV$di`swM@FD+4Y)AyQkq{A+I!WQG#aB=CdXPWI@Tcr}+cP&Mve zC6Cwiu)7gWh^11=(-EKaBpx5$4`q%~s4dKXrv<4FW^Uk8aWnB#>Is*lTJm$pYFh7L zhkLXk22&6p`52nW6>bgS441BB`fT3d!x~Iadxu_W{+3xYrKX=_VW*=ti+Q|%#u5s9 zTScAV$K#b|?PE?t%scqvTC38hamC=ED8?l6FH94($E>f7kX^D3)0mhYMLIXau@NFS z*(W)uNzyw1yhNnNgu|W(*=nL2@hD(NWJV!eYayD1b2-%^m|!qr6z+^ifoht|MQ@He z31nwZ(u}^FspbbSY`(O?IEWvvw0E^G#UjC5gJyi%qAG-Q;}ham#Ghiq_}r19>IEI^18NpK=@0?i z1lE_YWoiaVhX|53$|xu4$f7V$Mh%Zn_auzM;b~MDh2Y9-gMJiR)0NC9EXd%@D7+>{ zBDCG!Tj(jM>zWb{;*`~8l;&7xmFcB$p;PSCC<5c<9rk3@2Vf$hnOi@VTCsZ{NB)eW zl|OULq8Q_8SStd8Fa}5!ZLhkgI_wHhm&e6QSf7_qZaVNz7VCp`<^|(UtgSD|n9gp! zlxn3Ym?Dps4RE`tpn@)+9I1I%|xm$j09~*aEjs zr*-8SYnD?hTgL6fX=dE=lAh+-T>xX=n(t6ntlY5{;O8s{{bSryz% z;M%?Ix>b0(PE|MB^(xlyNnaI->UfB=(1Yh`WDjEYih}iTB^b*g81sf`;HH}ErR)Rb zXHx>XOsp`rgvC9TZapE1tHt}fu%=Tbk8|6ou&zgFy+(YPHv`q~J{(|*{)HYWG;_qy zaD^J?3K*2mPXBp;nfIF>2`=Q0++<$W!!ycMQ8oFZG^WN3-gdz8VEUrT$ zaJ%YZ@Jy0k3HwkMjbOQK)ZSMQ=N^W*HH)-Y%`VHL!*dPSWszMy?HYW3e-_ytmh%6xPDlZ9aSJgw&6<4l!WgV>E47@sv@&{O3>)`UxQq4yGa28z- zBKT|;8xme`$RcaYPjH!Wx2Lf_RpQ(<#%tUIX$%axp8wbz#i^2ir5+|I`4w5T)z!^ul zJk>&%(bbP;t9B2>2}ieNi+F!%Hj8uG{Kq;d)#mN>P+prqQU|Nrd`%YRwfWjQIHS$m zvS_2tpUGl_Ht)%G1iq}&F6O~3 zP*h=G>2F+1QlHpv1ZRse?%9*z3ktYX8VUXqgK=-nkP5L$LE;=jc{2-Zsb{cV-JN$^ zmI2m}44F?FAtV9!$$zovytqQ$=W3{mKL24Be+FirmEp4P`Pd6%;-+60#26T@Q0fe_i3T?pzP^E}@Yhd)O-@;opmKvU%e(tNq*kztEWI6hIfuNj8TF3@xs$cUj@CGX9?D!6L|x0kW3 z8kyv0t9pZ!o`FzG<~Ac5fn8dr85Ut<0=W0Q4!2DgkvU6mzp%)I^N#YkNmZb5uY~IC zG#R8T>E!WprLZ?bC3{)@P}>fUA}hX1h-*<5V7vhf8*;@U<%3%Z`4ogeqM-jqS=i4p z21{E`-^&is5MU~~eq{GEt*|Nca0EkDIS#R0qY|RbssJINBh|=3mt5=+yC4kCKKb=a zZp&)-U}ZTy=+z~0oUo}!CC+TY;niR}-squ?1RmFPk;M?XvIVj7(M1mwqLh4ma72bM z1Q_;F7zcm2X6|kxgiJ&#UhH%t3a4Qn9zGiGUN>eZyg9mr)JCdo}XC$8ul5OCEB*XwQI%3|U)kHmcxrOic(C!}5oCoiUn5()Ehnn*cFy}APnR6Th z_v@!+;&B^rT``7durT1d5=k|r>)O=>rF|EpF<$OS8w)X5Snf#2WgQ8lKD8Xiw@*(a z!|DrgDZm4RSk3}C5XX8sh=pz$PVb!w2!>pcI@yD835;VjdTS_`!$Qfb5yC=*_IW$O z$`hCq{WR+;tbPtt=+yIppdlVrto4f}3kw$EWc^vf90WNM2CF;r&KO~ZzzCeoFv7kq zqjl)iCOp0wmSIMB9)olZu2~O3P2z)~ZqsExti_e}tj*{bf$4^*yPgq`$_agi-0|+n zKCC-biLOIra@PnUM@|&X(ydxpNin>+T!)V3jvb~gLb&(gu1q2PEtHE>h-L0#>&-i6 z%><)X=){7B^wD%3e4Wlk3VxQx;oaz3rMIAumHar3_CEf5GnkqT=FTi5mFm&Q9mA~L zn68DGy?cP+BN?22&FF>P!W(nwMqc~tlPDO8~-XX_tM!J)i;5`{i++982!$T66F zQU#m{YR;E2?~2fGGMdTB6*5;3q_}=?$5>W{>rIm^LK!l)gy}0k!JZ2pya-eSHag}~hw8SL2<0#`a&*4wjiynn7qb+NQj1>QP6XTUXM(Grpkx?hCj zDeduLS&BtX1<*%*F)THpgv8SauIX4>gha?kZ<`ftio4W{!`>3YU=UX{Ta z3wcEvsgKvcFAWct^HPgK{x|Qn&GF-pZupArKJLTt9@v!lak>OFBucOQ-VM=u)d$xH znE$IW21{P(0w(17|U_P+(J89baB;p_G|+LP)1-8xvu*qwE7 zSUtok|4tob5n)GtVD*kVXuNFIgO38RS=n6=kBl&6gNLBnbx8?R za{vngGXGRy)Icz$!1)C>ZkayI>GDsEc^x3DTGo~go#?B80iMUJPRo+ZrjjIVdh+~I zKLdla&S3!b6c^B_w0ZrSG3Y{jKR&X-$cMs017ttLOzJ|uy$i;_4cCvge(4O<;y$My~gS4o?f7iQ|u77%u~)JKL$o+%@<)A(FeiMZ63n?(KK1;#H$koe0AWt z(1UO}1Ak~s7I7Sfx7@K8qw;@_P;f{I7>ohZ2k9*!0*0_y9^Y6cNZ5zwk()?yWP(0w zDe^Z72pZy1D2wnT7k$DM#kNu2>XI=xmre3ONv@*m!&$*B39g?mPcTK}1Lu2|bgSYY z6@=2Wq{uhviKo{Z@KLVpA`7I4si@@Z3=p46&&ez^IXiexqysaJ3!7apfjRC)M1Ej; z3pnLc5cvBsxQYyR0^e=HMK`V53?uZY29;b8VDtr?zFrQ@5#}F;MlV(zu|pwbneXKl zl!}IUZyxrBjW&VrvlJ-#6-n^isL2n*)i->1y;Q<|Fq!*@2ZoBl;683h!DH|${>vC1 zCilZJ!m9rNS(NWs2(IF38NB$%jE0At-Fq|CuU}gNbUw*tLpCGI@{WYc{-@~0^{m8<* z_y#1=F}KT*u!^^*2pG;baj~T@7$)WxaEYu-v3|Oz) zim%)1c%4u>`j-e=@nnB&;B=b9jE5{~?B8G(5O`tu?``1{&OnP;Kee;Jv$*B84)yrL z0dBsJuI$Si^q473TT$)L;xKfS_$yMp$CMo?_4RbhWjqqFagvu)%#>i^a4)~Ss}C3N z2zaa~j39G+2gdS%yBd-(JqX$ZohE4Tq5!4K(F6?p0|0Z~2we~~cwq#s3nXutiUzh& zl-}qdU@+hS(l@LL80Hj!^acU}gX2U%r5RQQ0+zU7P!opL=_uE610l%(GWD)c!FX>%h2FgGPKmk(BP{As;{|a zxag}aF1)@LrduWmKBLw8prbNJi|0!c)+isAzN$2p2~+;{hm=m?2Sf z{lUx6yg_DSF_Om8d+5csw* zccL{c9RigD(L*9|_}=v0140AjOACh3Em{C^kx~tHc7s)`oEHcydni)Pm#A z(<`bI99*SW*2A^nol2!*CvRnFD8rtPQ6bf9Vbme=i`^gx51*=-W9bkQaLwpDlp#h# z2zr;yf447eja?m=p&Itbu9CxqO)}SUt`v1AY+DKCpGXdl1c7@HHPI^qVIxKe3<3FH zK4~dz=M@{JG#wpDh$`Z9%!j_4^6lOh|l{9Qh3GScx2reOk;j`(J17SNxaJ**V zrk~jYDn<{k%q~z7ZUP!-oH5%W12!k6oZoo}ci7>s#J7OjZ#Z`J8!UPEk z5#IMSdnrQrLl?6rA-MT!so4t{}VG)S|h_f&lbp4nk5S;RjbKPFBU zYC48YI5pICNE29Bq!*}(m?+UM-=I0f@Hj`(#2S^P6RC+Z69!KrI(Z=xYN7NE)CVh|3d+ea)hJCcnc&n_^TLx4w;jJK*fGj$b`$N0PP-nu;X+&XC@R# zXnclM*HaTnB1G@;h!=Cm&Wo8sd<3C>#+)Bx=mfb$4=ZZk#|1IE&u_W5#0Y}xqv`LC zF}!eMt(7lE;^oM=hhoW>(#!>Qw^V_dNdUumrLJM3p- zELj}4Fa2zc!n@lZ^|=_;-o>twe+cfD7+vdI?oqeKD0iQP1^K@*4!qRV!}xrRA@8@n z8~?}Ez7Qj!wabzHCFbz97>AR6gZyHQ?23?IijiO@Q2la@>xq!NVq{l@{7Q_Zt$QGQ zdkPt2zZzq^V(iyqES&-M=L6}l$7rP7#Q1N-cwFa*@!yQ`HNSDbBZY7a{kLK`K0v0X z?9Ld$>k-F<`c(6$YxqrW?R{*{A1@`Qtfc-$r1NF+tGQf>I7~>Hl!?j?`#Y6Q9ST26Kj{*=U zxA1@U@Nw|=;kXFJ(dd%}I#+&`!m_&g4aFn%ux^3*b&RYn^QPm`7{i+c;rBN&rk@rp z^>2S0qj1|n17g365u|KL>;R*MoXqIo#|3EK#RdKl7wE+uNcSU{#%SjEjQdx#u)fl^sx+Z%Zm8-x>$6FvHwV6MFzMKre^i2`gr0S zcVBUI$UEfvh45kaP{!T}r(I|Pm-FG11-oXFbjklDJ<#Kov*Lq7jt?s8>@5Ch4Uw;?~M^ih^Nn}a-FR) ziY!3|Urejk_@so3%*I8?7Nm>VS06|cWf&5nZyzB6rj{4*r7=y~Y{_ydxjUqrABcJBY`H}iXFdDwdRdEqHojaPxaC?k$-4f*0F;cmf z9qBbGobjFHQ`pzW1#nb)6u0=%7*6NMLh-s7Q80!}D1Iz1zz5%QT6290V~(+R#3;B$ zH6)lK=Oo{U^x`hL&U`~$2+=Y9%tKhbJ{}jL^U7Yg$NNN#>%xI9tvkWyesUiL{3{Lr z6Bj5;U_u?R8v}A`WaIGo>hkm?i!vlQ2)w#>d~%vG=^BMGfrpF>snIh&wx%vALexQ) zho;x|4xP?bX3VKKvsE2mdGgSzRg;CW;b~DIlc|s?A*RWk%ts~K$2M>yHewzX=o=dm zHf$h1D$z4CcJk=ZhGCJFNoR;k6^ExsQIH!m`5asZ6xL|r!8R51z!4Dw} zSw@L81qElge_uDGkP$q7?jOb!u2?qpDX z?qn2_54lu~!$p}(rDDtgo<+wO3Z$iwO{ElJE}bb3bC7Z7*{P6DrEN*VOtaA) z^k;w`O%}#O{uEb$|A`wFZe;{WL?gus329Uw=lKhHRE$G1`(Tl>pu`gh*;I^$ z#`F~Q`wC&wG0GhcgbXCwvFE@fCI)si6-}KBQ^Vu&fmz|xgpEAXD}eFyJ<`939zU-s z&Eg&%J}fAdjc zBK@22kqEiDn!IonNstkRa{tj-N@x+{E=O_I4)z=aY8r7J2^QVqi=F)0QkR++ z?7Fyr#ZlwOg;mBNhbQa1q?PGq!((vtQl`n(Y@b%QKMb&-D%1N!6;_k?4beQ7s>C;6cvGM7V)f@7s4(pC3 z6u`@WgI%32Uv1lFzwQ%4j#^wcnYoxwNDaVXKHBdmhso8ClXaOHVWRf)tptiUgc*ue z*?k~FL)Gth<6-voyAR`p>DFHz&}kfISU-pJQ?dq7gZE(~^;2>Q zmkUz|tcb@PCQCm)_w7Fr8^*T36ZdG{H$sB5)jtq=9el0+9{Okqw-uHQYHJC@!l)rm z;?m`VAOWHKDJ0z>NR&jL1_OiG0O{2?lY$}@ie=4cFzw*fNngTj^_qbv4Rb=#WSXKK z18mwn`#saFN8_Lo_XY;upCPmb8a3rLqR==H#_A5ycPE?5c{@V zp|93;oKP@amCDWu51f{Ia(%*75FOsn*DeLgm3)ta;BaeB%LT!qF76y~(?M|bNiGqG z5gbw&^f7zGVn-WS4HKv17=Z+iJ66>gy}d|L80dzi(SnyT{;#Yr{%(Q=~!~K!oqy%INFs9lcnPgu8lBLIu^gd zH4-LD$5F>QieaMQ%CCMGrOoSVt}0RrlFU^lMiRyk zwK)NuW57or+vcQxt^ps@M*p!8&a+@@QNZUL@EevacAQ*bz>ArP6yIXu%%Q;FZQ-WX z3k@9Iq5qhhi!9ufzu3T`slNOr7H-PF$G{;MU;e!TUO@bk+YD0--Z-3128LONd=cG5 zhGER((1eSHK_rkkTtU$YCOKoFS%RsJG>U~awMXATG@4a|-O}aBXckd$M_k6D61c37 zSsA0sJP2b(x%kN~>4Gt*>VSFk%=&Fw;$%<`V4B3nSUz98xZ(X@=)$3M{ubUVd9d2DSzIf5%w*P2- zZL#&Wqsg4<4HLtQ7OfqcTD$0NuYbqX^dzE*o&r zDQO-Mq6ML{AQj!JJi=ccUbv_@v~D<=QEJzFs*T*s)k(7W23!+n3h3ox%i4k?g+gU` zXjQUPYp(i%<4Y)> z_l0H?)mlrH8fSNI22CEaz(o6WGNH|Hz9+-Rl(HdhPf6Qt)b>{LA{Yl_d6M*Eqy9H9SoK5!KwLNmQhNH_mR~+&l?fgTo!%eA?RKv6YxPUsitS z&ENk%Y4%M0%R{`2o63$c%dRRmrB(So{bv#1!t3`euitaLLbbl94IkVLrT$)n)#lQ~ z@K`dV)JA_EQyrs8lKhP;@LX7&H2jU}!gR>w`kS_SFso&5a(D>iJfU>n{)>9_&Yt6E zC+M9$hX&NsJA00dz0f1>NYRrON@;9QOmX$-(3&Kr|uY{eM zGkY*tJ}POrNLy^h&!g$5w77tKuzKY5-1vHINGG(V|4p?>lQFqc>&$H)qaW0|vo`X} zH|9#VM$`7R$)O1t$z+#nO6s-0awKWVY*3lM&<-m9@MHQy5pe7L zElS}qrO-4A6Ue5tYTr4F?z z!M~(8a!VjVnOnt3^@=ii zWwwqfv8_sE^Y0>NnpP((sW@46cj)krcZqq}2s^T{J)gqzYSE3Lj$TWy*c z9~~V!2^L|RJebDW>o?1!ey1?f+OnP3B+RY#G6*YL_BhC9KP;>P!xJMD!#pU-jBNKxq(L~o7M*9Sb){Pl zRI*n(J1bV}$_BMsSX-{+qK&f<51X(n)d;HzyHa^v&@vB>EEMp11*9}EH#$Bwoa|Bd zPXA-XCRv$pI^|>M?whXb+}8z1k^mMiTFFFawDMJdj)?F=_I=m7itM`$KVy-BcQ{ku zbyQoH3bQ{@z>0XfU@2LZY*+GU{UvIq8UED+&SQ|p?*XO4Ucv*eA6WbzP%*b^FG$}P zE}CHSTb1ltk7dX|8k2ui$+PG5s3HHTl2_m9QS}Jo)=p#hXREey%3m|B{V8tkPqs6E zGOhioZfB;*rI}HxCp;cgr?sTvTs7(J#+>Wd5_MzFouh7ycrNZ|@LbMy3oOg9Fhw?} zIy^BnIW#>!nXJ$j*ZY?DXXCRnNuECdlgmpZEYI_0(;@cvq@k%{p4&58>laTrzPUEG zNo?`w%r4dWG2dD-{^6`x8523#qxFaW;piMdTwpdd)4Xq--MU#c<=?~%%xjuRp8Hi; z8CqYOA)|kjiQRmf{?n}#BV!}ei`tS2rTa@ye=gByiR4conNId9qWvky02gRp$xH5k zh?Bs@F_A_|4Xe{b(_NF}>#)^H%3AA}NjJ!n(3vJ=l^tSZxZcsU^c6+itD?t2$&}V#&OOBvns1AR{WT6b^54Dij!csipK% zQF$>xmLZycZCZPx4waW#DlfbLby%m=iCi-}e$t{vtJjaMT%5R?+YfYnJdafw=U*#V zk0tC9^X`xb>jl=By+ADzi;B!m;)uNX`HmRJj$AB=BTWbctj2UoTD8nWaaoT4=)$`e z6Zl^RmmImhO6D~$h?=(KD4ax;WsbucX4l49WAS%sloz^b-lnv^nxeUAdhH14G@K2C z!K(!P1LyLp-nc2XaFM9-ZY9z4!kE3fq`W4Z03G`2!(B<)QE7>(!S`;^IM=h3U7$Hn zY5j2iK53EwiF_Ra#+*VmeLyYQqXgHyC`(u)Y0r|`i0p)tyYQfWk|lnrg@4H}DWt3- zO04?gEK!|K&yrV9a)pw5-b?mLlK3R*Sw81t*iRb8aWxw;vit}s16INZbrI^86TL(r z0~HTxaAo}Gb-7ME9u?PXG>Bp|$Yg*cWX>S}K^H{J8Q|{|&un*P_&FB|W*k zzx}~<7?SIgvrQ(%5ZC@3szrMyx0R!f6IUdl510G24Wy4X>LRpB$Rp z;8Dybp{g6!ao|Cl5s?w1bNJK|1k@DX>R2iwTqS)|%shvoL0J8%D;EgF7}f`nu`iscG6@9(2aIuVy+N>>BxQ3;!xyv zrSO7lB1*ai(71lf3>>K@63KrMwK=mZAd;6X3Zf&gI5}v^{~-LMbe28~aZ#;BQ`19} z(^CT@(`$RiPaB@hVVmNKTof|}Z}XxC+ThHYUDJ$1pCq}!ZQPUyso8tRsb;Dk42eg8(R~HZ{G9e$sSAl)iat{pzT|f|i{m zo)Ls9s7T}N9jCPHA;`+){R-KAN=qxeBt#~Xr@04>v!6bt>A#bIDt^}~Eh`95EuF`E zKb$DRxZ=M#+?64o=ycVfRr$Z}{B6--zkT?J6oWEhH8fP!v49M)5E{Nv{77-1?b-g7}q#^FYQGu#6uN_rq${~#{sl_^KK;vG zd!%u&lQ_Jbj>rN_vLq>EZOYJP&2EbhvjEp-=%lISG41iLjTBw_vod;c)2g8j$={T% zog16hj<26gp48tvHu`@2S^Lq}v~FZ9`MVa_y0K~9_!xBhiN*!2L0dL9@h<)`9k;z_ zH0fc|V_IcqV-u;=KlJAl8%Pp=R!KB%7#^BT9#`C+4bUjK-@@FP1Vbjs1CS&F~ zq8oe8;E6o*95DxbH#Diqa0Paq(K18zHY6X=GLLLXT6Pj>U2>IDnK?6Q*-N0QWQT%w zpMeH3#hcbnCbwwK%Qq%1D-`x6g>46HE5RUV?d>lVb?e5ajpJj($c@&ezZmNr&*D=f^|2GWpbg&L$)L|B3x%uEJ{455Mbz)IPDF%uz%pSY1XD zaKyPi8#N{RyLl0Di67nAB-^zGFl3)<5}6_}J2|{AX}Gb8S|B@s=Hv%vBdqt0O|l)H z*D?_RnamkDSqTs%K%(87-~zz#|&_h&J{F_%0J{W%BU@MGby_hSzX9 zF2?ReEc zEh&1iEj`_^Om5BSkvV}N2@`2w4Ii`%@r`7cR(#cA8HTQE5RNDicvZvOaNL6Z7ueh0 zK$OXiWGi)*{-lf+THDr)GvPKRcx{UC(BvA-*amVQNpo=n55FXc;V97YOR`hz?K<2s zhJ^|?opeG&lw}+-CG*7)xM*^VHw6$~i_#TFTYSWl`Q(!VNBxtpj9O`?MKyVu<}UE5 z*30BnOGv#;8l`g@oIJlw4TlPVPg20yPNn(V6m2>qdxcQb8hmO2%b*3XT2P0X!z?q0 zDKnhcahNjGqzMU!%e0u$L#4x%yZPlKgzP=~mqociW)~7rTDFqvl6YSuuks|W9y=;TWS3Tc=|b0SPvpvo z$dxWFBSi$0^zG|!no~lHK~g`l<553TH!5P zp=lNM>l0db1uy&`*I&z>!Sy15YdO(74!3oyRzK|3al2^htJ;+IoJ_zXp2SI;7JkEP zGL;@~5Vx@j#{}*GYi;xTBoPFbv!?Y^$!{E+P-e`Thjn+;W}E65hIc)jEY&tPzBbd= zUTurW%fD&0WQu$1QOuvRerPnAQ9{#;G6er&2+F@b!GEL(LU_|_oliZZr25+;lI%;~ zz&jT@dRmtx*Ucf<4F-2C`M7Q-kmJKq-0NH-hRR?72XXKhT@x#m^rzw`$cTSq&g+fU z{YHuo4;!LYVCJv*+Fb4`Kj`I%jV`&2}=yq>_08J zPkFg@b4vpE176krN^5CL8v$0aSN=1tLJ?ZeGI`YVl+)+^R~?Rj?>I)Izc-EkL7ON; zMsV1FuRmMUgA_)5Dw(GZ9G+?jL-Vu_jY$0I{bGM93_0r#j)auLU0F&O?(YVf)bL{d zHD`7$&w`8em&Us;++Tccx1fndUbQOOtz^H{9!ms_ep^Xk^GWo-v%mUzO}v(VcYj$T zdES0|f3L={$?^+9p_U066Qtt(MC+E_cyRmG^(Q5}wB6r!M(sDPN*?3pIfob%$jLUn{PB=;&S+w&1i zWS`DC!1nW;14#a1y27+{QsSYjxTO#i06s}Rd4O1!rjv%(z?M96fb&?u?2U|ToP{q$ z`~8MbG7^GpqImYNwCEF?npdwM9VJrgCO6CO9_49`ps6yJ+@!RSwYDL-S${mTsd+<0 zc|kL8<$X)er!8VLw^iBycYG*~{%hFw9QuC>C5$JR&LwT2h>xhlrE}#>d+TOaKZnM# zIWJM~pDTjXIYTZGsX)@Iw6BQUXl4~lwlPsMyQF~lNp12T0rPMhljP&RY4R^vrTCGK z?Mmz~DWbH%C?CSrx0qlUKu?0ha!HT$`wac7{U<&6enjT`DKgB&jMjRnHu*R$U>k+9ISmhCiu#*#niCYJNAxw9ivJ;Os2$zCOXX)$hu2Q_(;H|R_iv+_ya z`i6yY!I3>!+G+c|aM2nZOKevf|5;axF_B)E^}i+Gx#5u zRuQXDKBxr|+7tg|T$q?gN#~SJ-7qfR2|^a6bJ_^Ti6Zj8(YB`P)00aZ#;{6KA_|9z zu4#A5UZF{_;_o3Hf8GD5#U-(bc4c_vGX`vmhclH9J4rN~TC(47jx(kUyaZF;q9v~LQp zuk6+a?-8f%xk z#`Y&;M4SDCyUfn*U>#1+{i>nqp+aflBHlkwXzlw>jHzI?TR0_D#_&(kfVoJ`dYx=z zHMX%(emNt#;7CXOq>(j8Cwr7=)7vt{kM_hHj@IN)xZK9}Cb&t}kyd&m$B1x7}C1*UAsT1F_hxBA5 z=@tq@&@g3GT@?&8wr@s7dLhLw0kL%dX zU~@2$3`&*VXYv|#x6ZkHHx8{K}UOz)30 z{Fzrl#Pp~DbAC>PR70X4`-^Pya8iU{2)^`7c%!Xl9>Rn;Y*;h0GI^`8^$0@SWrB_* z)8Z8D+1N5efU&V;J*f?1>l-O}xSoc#arUzt@nP0oQsXMM8GARj>?O!a$;b3a4YBR9 z0v0+0-Vm5Cf_;&Wzz9qFdgYZ^C}$QCl9(aMmEU$(}!IjWRcSeBg^kW%}G5@ z8^p?Wbke3||C%C?6O_lex>MuF)VjG17HTg^lJ&yW7X90>O-u`FKNZz}Dpfn~EcJ7% zl34uisGqz+`ylV;;W3@bQL76$7q)SB%eyEnc)=VBkpW4Ab%pNlw`^1mK3_NH^XQZ& z*{0|xaDqa)a39v+IEUFdGC{^ONu&mM8?Ms%8c0wXp08=R$IWj(UHCk0gZyRAE>q4f ze^)ZU^<9E!Kf@HbN(Xme<;&z4Z!1>cKj-S4yzi}Q# zV@*YNDG29H)VHtdPdXi?fm`2&H6)|C$xUC8cqeGI1@GX5*H4p~dotp=(rw;Z%XcDHvF3eF{U}csXn`HNTOH(~1vkGk=3J zKe1IiuIyf*M}Bu~YT4-owuKv^vtc%4Y`VAb(IX}MdlAtR+C}PBR(7|2Fk+k4{MyDZ zven%EP9BRZmGo{p54o~YwfvpX^6T{Y@bY(R*S2Zbn&{%%N40sjqVQg~=}D*_#%xou zEYQ)TbVZWtXtG^-dhv%HZwH_Q4P-VC6|?!JO-<7qlS64mqf_UWJx&a-ltA*9%}pDV zSCSlIZ;`aydXVas2AZFMl#!ZcSuPz*?H_Rv8%a4N$gFLg7syG6J(Io@GlcE13^dVo@-;?48c>B zHf_;(u~ScvJfv_L*^{hI4vkGEdz9X?k7nt%i4*q!i&3FkR%&0^H&x=68mPxD?I0H% z$)VXWI7+JTx|mTnIFAamyumetBcy8h<0-9B3I~3yJ|%nhkfEd@P)}^061i?4#QyP2 zY(>7cyJx*qDHX4;&)z&Fi9VwizHRXmci+6MG@g!W!Ku;JQ|_A|DI}lGKpW!84q@=X z8zM#>aSj%ddMb4qaQbIPz^lm?0EMuyyNU(W%;1X`$O%CbzJY#0I9u|AusZk3f0na$F?n2_(5s7a5bgq z7iZA)uB{>uP9t-;W;dqDm()eZ)L-h$Kz+2{%PhsjeMaJ-|IX&(`2MVZ&G#X#*-vrhIoaAugutzFuu3~K*Co)`u6=3H6J^y#!*+}Ew z99OT6w`)sM?4ws*@#Ir@cvHL1k>nJm-+%MAUuk2GiP|^^Wl7U!-goO)FW)4MoCftv z^I;S&)DHT1*5_}(Mr-3N&^dGoodi%4I{&9#fPP1KdM}j!Q{KaY5m#51dq-AIx|2hq z`iLr4FRts2K-1|&k>I<|{ZHwHOlaZY(%;;#BdZpi`PzBq?f4`L&n;dY7`W@f^B%$j z$ueu@un9Tg#_+!>jq?l3R%fGpwN72%X0BD05k{v#k=8jj1v8HwE)=0jgur7%q#IL= zyy-EWEsDg8Ndfsuyh_h}x|#v?S)C|^?id-vjPDsXnfu9 z8o{`+af*e(qrFbJs4+9+yEc3>ZkHe$Ooq=(aQNih`pHT6ilDYkcVhDxFe*7GX~eM6 z8A_B_Pj|_agYNzUF377_v*KA~i96MN9aUe1T`x@W6utN3i1c|EiIYna7U!*7@^ab) zu``EV)yrjbi3og5Pm(>50nQ;<=O(UDlABVbu?K$jn~aP2Y6%wBp}KliFHFYi3m4&_ z4GHDRj8giqk2`-(^2^q(Pm-hFX-FO=!OXeycW8}cv<9;JPD|b(C&X);#z$8r$H@y{3cHlZ z#!qIOUuBzL<(hv)YfNYjZvMZm6jnbADNLR=N{J9NO5~Iqv&~OQ^G)k$*gBA0IIVOA z!mF`@mJ6slylyC2KYb%bA=V0LU?@~9BSRH8>XRw$3bu{VL;W?crA&Km8q)I{RDLYX zv-Q`~XOiaRY3s+NL)wuOc4j-Yy&>JBtChC$&s}n`vgy+`4cSh)a^2c139QO?1Kn~z z=e5kZetpLEg!}a={-t@XX>IN5^z&qe>&Z=Vk0>|qn+@+W6UDriP>-H!+&rbTqYk~h zBYJmhytU3`CjMwJ5qCK`*{PKJZ_aXfUqtb~e@(GZ#&Lzx`dS^Dzf_vpNlt9>O9#x? zZmoOjr?Y(hHsb5I&&(H-RK{kM*e~l4{p&s%BTL;mJhhTW6%%5wRzB#njx#J+2vn`a zVZwTq>gha?AjG^kx< zA>D76W)Lx)O7eoxG~afS1p@v1mW6UqA%nD2TR;4k`dk#8y&|a>tket7Lh9NQzoC`1 zX%ipaM^nAFsb1Su@3Uy?G%~h3mGlR1t>2pyVws*`WqN{UY=-sdE&F|rAV_?>0i2!cHUvzd53N19nYd2nmGK>&vqsMg;X=74r@JC zL}}M*duhdk-;P@nse(i0_Zm7_h^Jlbq3g@=RtAU+Q6pE!m@# z^Ivc+Jcn!x-yBKiDGjzJ^K|hTgZt^UfCpPkwWj-ld-Z6kKo+d5)IV~YYh^!fg;u3w z99_inlVj8&Z1&E!r^WV^OA@WrS-K4(}a6jpGja&|?` z31%M)m^gqdl;Y@@vXoU#%1pUrDVLs+GS7kuC3yes5n-lvo?Ctpu{=*tIrX)Y^E6gU z2X+w8dAc+$nEyGYcjT9a9{2rxMVFKLS15eRE`xWEM?7LlGN5wuYRc6=R88OXl{>igBmOQC^;(jC;3ojwDf9fAR zlJW2lBh5cwsXwWInXx~sY;jv@{=#oavtQP~T;OpnfU~uh#JvkPqlc20BLxU|5dWe4 z;94Z^;pnj^-^;8wK>VEKgm&@&WA4l2t18a^Ckaa~LX6(1Xt&$;tuN3v4WPJni5rS_ ziQwM)HX#dK2+1`IggxR4Ziy=*+PG8^x2UZmR*h@Ht+8&1OVid`aE-NEq%OZ_`OZ0K z?!7rVNc#Ew_=B4>bLRUz^UO0d&-2W3zI-aPs%PXubFt0IoPmAU`QF8lXJB78me}kI z+(l{Q!Wmd`OBAgFnPDI!RXP*d*(`L{poz49ibjH`m9wq*%~|P>7`IvuY0SGdZ*anf z?a8pY72t(MqfxLHze4*qWS3=l2V!WMXO!stXRTOdec$A}f)p1=h68oibF$@67Nb%I z%52#udf9`rWltuTm&@k&k?93Dw{UDT3wx8rm0}2rV`L}cVbB|&eMz6M$pUj?5Qg5% z&H7RhiC+4GV{U0+6)lrb^x`TR{8Z2}m%1vu;$+{;HA~63<@v}7gH)!P&8`nWMAHy1 z)@a?9CJypSRsPeF-2Le5 zN;_-j;j#M0Da|Rm9ipl6z!Od~-{Xmk_TDUxck^XV)zno->6?61#*>dQ)JQiSm@e?K z$>pbDYO;yW`WbTopV_AQeRf4HZAvx=C>YjL5722cXwuuNnqB~&^bK2437(`eL9ZvP zOgY|}@4YL|M%ueAv>#^bY}1OD+S6YStm!%xZP|B%>A=f7eAD4ZF6_BXFV>k(^;E~k zt`_T)w^~e>OK0E~0&ia5xTg^=lgP5n=fE9YLH8`uC-(fHUY9Yqksz!x(t-TxX4i)( zwH4GlA}Ar3B7=ycStzvAx2y;>^d?8w_2eIILz``OK@5ckRvO%jyq)<1bachhCINs2 zX+W~l{4IW;Z+?{5C(?eBrt#xQnP_(!USIAN6NTVClEXf|K$J`<4uguh5H+-6OJF)s z{DCx>sm2s=NgF&YU`&#E5N5(&0?~z{ucko|Cp#(oTKAn)5@`?~Cp{?sc^V9HA~Lj` z?Ba@0NBx90!sYm-9V9#$fZ0P^QlKZ1e283jL(w=`{G!zt3p+Lk{h4 zv!fa3Z1Hn=LoSAA$@!TUndPm0?WhTj-U8WPnlLq4p@Hx(hiXC%E!BA=FoM&_I*P`p zx+eV$gC$LIdoPWaP=rx^Z>)aPfu{mZl{b;v+PWzC?s#1yW^$I6yp43OQcLiCd@nQ* zI}eaRCKr-_K3B(6w*>o^sfrd(^mxtlsBm(CtsTwLbR(hdu(bNa_Zt4Fxv6Gfv+D_u z(4E3)%&rI>H(0uuR@w+@GOzAgj0e7F6+`-QtfI1#mh#LEIlErTia$9vbu1mK)IGPy z#-@%lci^EcHg&wY2M@=P{ieIlDIm(j;bquV7)zDVzHcplG++IA8BU$jv3=hAM$cks z(D9GPdFFb&c~q>$T!p`njGNtQ4ifF^K;h$vwLU5~sSdk-yHK&)MrEzw3tk25%oE^Y zIy3k*~Kf;z=R@}Vy z8|ZWs%U)e2c4|pkzd+`-WD+t!rCEw=YOY@#FWW;>&PU31i{qV|auHH~!}}}gUmsQW zG^h+!zI<<|>U0xO&T{Lx(NQdOCAL+P{*ZCG9D6F`y-YP}R^Zjrc;ah*U2j2MZ|AyR z1;%bcU2g;+9hz$~*CO-g#qn|ms3ccSMYDQwyhl^+K+0{44>Xr1|~8V|ys zlRuV`J*B!9c0i7sNfWOy!6m=^@=@dF(ucR`1u+u_xjBurfLuR~_S(>Fu4Y7M8d?7h zAbj~y+8a&!>GCw1w5A|tNgAWH`<;`!(v!zaa!-1)elM#xJy|~^wIwZ?XC)5MnW2<-%*;^QL*0x9YNWANm}Ck$O~jewyv#I6&K&P$rnhcwedW}w0-K&h#zSef zZPBWJn3fEqE?s0aOS0T^U5078h6vccD6 zCuyI0FUTHykW0SC)cCj2-A5L;H zNjRlD|F7I0k<4rW!Hjv^ZI&%&#x~zP9 zkgz5wpg=>?MOqb22eCij3mFYN(UBEh3`gIqu~ip;`!#u#Tgt19p- z9sp~rT4+bkG3II|^QuBc6PKVz5ZT4_-UD&C1-It=u%)Q7I( z6ugIlzO2O*{peI>{>19pW9NjAU%7kSqE0v5Ecn%9bP_*F76DDy{dnv8%*p} zlinJi`8ZW{S)$t-uLoP>b&(=X3qtTS5L_ay@j8s|qTMT$z)JkG;NHNiXJ~7@f!1iF z)@ZY>@g`b>B=3!OZ-TYu+8S>Hh8mSX?bBJdHEcp_OT5dr#(Q3CG*l~JTkuP*vB}d4 z#@ht+v#B+jr|QegEA^N2-$(wLEk%!{XHPXBfYnZIjSukh>1nMYmV(X-3ADuH)0Bq& z&>szMvH=q}Lh>w3+J2c_{3tX1IEs=CvcrR{Q(ZO)Ysp1(0s3`-%39GCk}d0=HRg&z z+`(~KryZn5jiGJ&V)@&BkKF)xG-vz2^aA*9k88%1b+`6WU7Ih=KQToXl zE(+bsZ}xySL_FZZGdT8O3UvI{A5-WM)ik&!-N1TsW+vu0gP8K*VSY0MlfQ!kHUV|Q zEEkgBFy=ctF4G5Gh-*1QN6jEy%Y#y&)xz&6g;74&Xi6ng4)C|?(ummh-ZMyN&tOgD z!K1Th1|88V4jP(XfWJQ{jfm7_oKh&{br{t#q>PMJHq&%bV@}~IMh>UXs%Rn4(o|^z zTNp<6SJEIV_ePFW200S8AOFefrK_gnbeiUMe7bXYoT?^W9h*{?-fq-i@}sAz8+|z$ zR@`B$%ydn-jHrz@i8MZw`rsyc~liW*+OC|gZeiowAm^Qvl-yt>|xjfh=jd-1W; zl~`LJRY2#qR@x1Vfq?zU{ih4 zbb{q)&U9FpzPbs^d_LHd2$avyg zV$i&T_vz*VYP9D(UW;C6RX%>V#J0?Ivk_L^4J7?|nE#$cB7s@Y_s(x1Dm{#3K~mbwee++-_k1q+W0<$-B?q}D40hVU?ueL)_GJ- z+H90L;)vbOXEIC)DEvN+QWc#}F+X8^>nr_4yTZtLE7vV)eyY!9fr`j|(p^AW)Skin zVaj{fnjhFGenBfrjlU$L^s#}OPk#MrqDyzG)EU`*COb!F8*{Aor2Kd%GdX*#IZ-u= zz5wb|r7K;hH|bHJQjm#VkwLCb$ra2rrcN)+W@DI6C!L+6cYxCgV7f*^|889zYcO&r zOkO*k{GDS`+629HaF{u%G^e8!e~UDckfdcuqWB;+2;I-FSad0Lyo}%A#7mLb7OVhm z6r@|!C(-Rbjg7}mrv<1&us>?KTMcsQT5VX9dPA#(hS^P&4MVLki0OKACYLm)!=Mhf zX>D~4WK{z2OIEr>NGUI{zC=}ZEnUlTAk_<1n^)$T zOYzXXDBi`~oYq`VR~)EB=yoc;vG^)HlG7MB-^M#PA(5oc@2tY3?1AwoYtI4JX74~1 z9=$rFD)*x*%=!J02L%uBwO9{UF}^mesy_ql%}1vhLap zw0{TD{%z6XrQBdi^Enbuil;cC5^?d|_7hB?ho;w{x{4;SYW@~PHwd;~pF#MdJPZ&? za|s-@!%5{X6+4px_c|C}ghxkS^sxgmF-I5sezm3W)Qy&s?n0vJkuI}r^`Z*mKHG_c z+-K@$IqcBTxGAo5YmVbeUw}D|ZsGP_=~kH*sACp=fO<{3L8(#&&H=bHhK(BI^Q=SX z!SuOrI)8Bxt7fC=2G{d$cDWbFS)(8{*Rxl55UnZGJx%uBC)z%4C`=NpK__aED4^TB zsSR>Eb8;@?;dhky+Wb0(UG;Z6ZEXr@M~weI-}xR?D!&3p^Huz{ykyb}cDrwJ*cR_| z9ppgw-Vuv`0E-%+SO=-nt#`U;p7RG8IAwLp_T8Y&j%u!LN~Y*grlmCUHkTqFucIJe zFMw&qNK(d`50OH~8?o_@Wuu1f7kd|3G@z3nd5?4hD}^84fza|})9BUr@G9-&qYra= zk94yak76Gq_gq>y%*MU8I4wpeNp2~0Qcco}u_b&#j85{bC`}C&qWbq@gr!Czb$D3S9T7R($~Q8(8Vf{}0k)?tVZzg{b8n$-1gQtpCE z|Dn(_*b5>Y68#W=pXAdEc7ut@7CN^LJt_gwCHFhT<-(R5^7!Idxp>Q%t+^Wz9aNE= zgGdDJiY1Y3wIbooDRS}td2sdVdKYrJWE@|X$IG~MvkF~$uStjSrL2vn&BmB09?Mu( z0XcYU10L*ER9DfUbrDiJ#JJvw5W=f+KfZr;%p$rje-5=|JJ>jvO$D-VuLKWo(oJ8Y zM@#Yblh#GCXMbnT$Lm`b>a4xh>R`NE=dz1IoxB_(U@pe{ZET)U@Qn6w-3SUj3*#k{ zGS=LLq}LXbn^4Y)N!nee)*{~{)5b|+pL^dtAzLQirEmTjZ_14_c4VM_PKU{PH-X4d z+SIh65yw90G-d%=BfWXtd^A7NaKKO6)9B05&2)pbeWbe#MJANMMi?87Boi;2ebv29 zPOpfZzi?x|D^;Qi#dGOrad z^Op{?x~D>V51-vwN0D>spAR8{oK#ZAO9!EUddj#CaMWs68UHXymGQB3W$Xsl@K>be#t|U6e@TIuk|d%#OR@6_w=P$WlqFP zSeoqOT|LP6bglt6q`%Y~Zp2gTqIfyKJ)6FMq66uMh4D5?8EZO_)Xmz~rA>4b3U;ug z+N){LA&op;R^{hVYztjBm-ruP*bR7N)uMPAr>OKdLUQZ4%gr&bpj_|5q9-2L_I&aQ z{iS$`oO>2YykXhmNQFN#r75!Mf=-NRGWe^LdnafuO0a< z;iVQ9G}n>^#3#bJu=a#eoM`paOJ$9pK&LF^+G)C^+GkrA9yLbU23G&`v}Jh+)iB2o zs>rtoxHpk<{(xhWY$whlXZr`b3`j@TZ3 zy^73>qIJou1!8jWg6&A)R3v}p0SU@R0XF*+EO34jnwp&lDuB&cHR!(fH zuP{9z{gYSwsP_aN$V*~kMC_xQ+Bu)~sL>?%9>w9yQsr-g@+T9foY$iW8r^?K>r(r& zDNT0dLKWEr@T)f9M8#Y_Ut&&&=sHCEd=9(J(1*79s2P}H`$0z|Bd4kI+Ao^VJFIC@ z+R8<$E(}rCcZyV6>0VO5I*Dd0PwV*ghslsk@E>eWZc5iyorZK{@L4@!y?#(G`)T zMdl&YC82uskgba-_d|%!Z7|B-Ltt-ru=fz8-)(5-gH{lt+L1g!Q|DJi3g(%3;rFQF ztEHp-0VOxc6hv`Coz3NYW45m}uv=#5KKfXNvOnvcv}zdhrnCBjW7j9|U8< zpP~-y8lbYeTAbTn9xvmHCrt-9+0YPg<&;`;Eq1s)K`8Vg3Jpv@-a!4YiUSb0gRRFn zg>G%W3(t=d33DS}e}q;x{DGC)?NF0~jQ081krjUr@J}dpI^BeB%u~6l$!Ptj5crZ| zWD~fho1aA)oEJI6R_z6QaA?Vv=6p^jDCy9TSS%x=gEj?b!Nl9}# zlG+x>Q=CE~y8@f+zNCvCIcWxM{!?{j-d*kwGUecUpD)w6=jibDmSNG<>jj28zeGK* z&(o+!33TpySeXf}-t}JfM7#>+OfIB|rW9mTY2?Wpx=+ZFw5Hm>*&C9ure=|e<~Idd z1IB`MWP)`C!1Qkq%svLdbh?+tBa8KhnZ&}QxqK-9U!coi!*%e?r_~WNoVy%qT>}2hC!z1ASKbr)*BtcSGSDaSWL*Zp`_q64NtO;s ztg=3m(BgqmStA7xYRO|)h8pr*#;E*$TxY5$FOBW5^*>y}WX+5C^;<lQh%#9qKI$ zRsDc4ik66S%DIv>SsVV!Pq~ha^c70!L9|g8@prlnnm#LZ5uR3&Kli`f#Tw~`X*KoN zv4x%D#74SDS<^Zf#ydG>D(%Zxr`mzLH9*sA@)7k>!i-9XRFSNt5d9D70lpsGxQ#nAT8{qD_g%1XFh|5G;C2 ztteSEAHl+q=BF`69%xMTTTDt!rcIH;K?;-)D(I5~Y=j%^a)8t!mV=#?gF(C(d?#<) zEpZ?_>>Qm@B{-I)P$f*1fp~LKpG?S>zub1x7yba+F0g(bnc$T%mZM#iBUTWDO@eF! zkQpq+NyFmh+)nxtSggHuE{?ZxVuKmUmEbk0VABOz_hi@#wol!)PxeP^D}F=Sm)qs3 zg&g+r$~P_hqm})yvp`2PFFdme`?#Dw;kT)>b!cN{MXI`r`7-6;<>DXo$rEl`0#DRI$)Lgccy^x#ekLf`DM*dJ=rjLb4527}g?>*RI(0=^E zFMptXQdm(EDE(dtM?5K;yn-&}`{r3L4#G$KBWT0*sCf5=-1AJ=PX@ zU6fX^<-wWi0<2lE6hMCRBNt3KTw;x6VsVMAaI3BmkD8Uqq$o)#z-~TB% zT@KK|z#rgIusVS;y(9~RD=mYBywW>xBSO@yEN-#LlA%2j0D08dEEHA;D6DoVXqH3a z;EK9~Dk^85#zT-P0bct!7ng+j5Vjsrds9PMM}jW&A1y<~!yr^9-za_j3)6<6SCb2i=i3nS4u`<1{?Vi{dj7D&=r3ZKt{;?~}oH2%u)Y1D0AM zUdF6UH+LbSJd-$`sVfK3UH8f${$Soahk^J*z+mF`Zz2Am#5+Bmde9@@roHGf_VaN)65)l6s7G;0V4DUN*TNW~r&C(9! zM9`G(wQ{m?Qr%%XJs+85vwDB6>-+dc&nFfxmIxfVV9fmaIG)0|?Sr&^|4(^8-#e?~ z6OpT=fM|Yfr5WRq#kqvr>Ym-)X6(y%x`p__lx%FFUOt`v?C-#K25uMd&itvvOc0k3 zZ0p7?dk|bFLb8X(y0rr0fIiS>|?4T>HcyV;bgXrZz{Uj1P zbI*kO;eOnB<(~GlnS+(<+wlvE9$D!W0>5lxRyyA0?;ylg;y?ST(!Tq6Rq{HRrI34AR!aPFQywK> z+=d!uBU1?YL5D{n!}Vc`{naP4I)%W0B%IEN+ftoOD&0VTjEr3V*M~ZT3fG4J%&bkm z)i7Gala0AN(RLXxnc^qelAHT@odQHtWaW-we(a&h7gowRhJQ@WakN;8IvKyPK zQ)#*RUS{1Cj+N+j4R0E=dZ>SV4hIm;u2;T9I|v{NlWR<|IjlZWIjibNm9&|M?iEf< zs~=lDw&Z}s506g#u+}@qT5Js@Tejs0E6p64mB>ICr^bz%1mkq(BoiE@=_V4>ibBbs zXB0jZg=G^t*|LZ3MW>v-M!@~HiK3fZ>lY_B%j<_z8INA$lvD*R=BDuK z{zqmIT@>VaQ3l7!MzVxUQS8B^ZE=#C$>ERt$qjh4hQ#0Ro2h0OZP6h~{L)&#wl=@~ z2eBva30cFxR6>ck86$=soJsn2YzZewnO~V-4Ij-~NE30qoRepnIwe6{eeg=XzEU2j zbh44ApDwUkbZiF87a+<`J99V+k0;uGLtHP^W9}mhEY}O`SNL2nv|M*6*Emi`5s2;p z*R46<_1HGw7q%|~5!x*czT0yCO2E18zYH)wsnI0B?rW1BwwbkS;JW0;8mLKrox|c} zx1)h-dwmYG$xC?xe_RE{XRpa&eXs(*#y5+sawHoMZK2hRD!Q5aNcz?!ZB_))ZyujP z+&XAV{1y-|72+qHm+xn##(fD7bO$iM#;UIUiH3PGllE-6_cfPzhb+Akr-eK?JRGB1r2fB+>M@xct=DIjFMH2Gu(d^=s zew?GwPdKt!UP%N}Y9)ncKihDrl%%u zbVb69ph_LOdY$@eDFF})jVs~ zTr=wzboC>D?3p$|n97#R`GH~!HFUK2IQn>&nZDa)oJ8B-Yi66q<7vF-1GokCWO|;e zk-}I*c50g%QbGppgJi6q~d)m@#0|!B zyy6|pIi}x=#xgpTmf%^5Hc`-iI`fkkN|==m^fzzp{gzg%ZDB!UZIZu-H{!J)@thEA zqUi5WtF_LwTWW5~d{b*gyC-Iw0_B6 zXqUG%`+5Av8XL`M{s0~YU+5%q<=dKFjB4;Y&W{cwXkN}WMaws9wtbDW$K6f+Un2VP zk{{48Oi6ne2{Wap=+l2_fyXxKFX!$%>NXZB2lT8IM~xw)wYjosVzRonYIHH~*z5ok z4|p<_VTAVw-JW;QSn;Oi7|-qUT|5~W`#`Lyqc2)?0q6Bhyt^E{yKIch=a9c$SS;Ts z?Lq0u?`oBBF&gj5+>rO8a>bJ;JxSlAb;vo>=T51pp{Cs+CtE#r+K_iUsfEN)E#E?i zXMMgfk@j(xgUY;2YOh&pB7@iZ;F$sWgjXv7Ccc-!c8>$pW7$rtd{f0V#wkPji{H<{ zc@sEdJ8S|BuW`~zD?O6yG?7%5|1%xHI_?7plU4_N^O|YA;V&Ufui{wbKS;hD=Obai zYq_SC%6B1Gnq3`163?a_pr`b@;0*Xh5O9lSzm8@NaDNi7gKn|tv8Gz{DRRovNTp^@ zE92`A%R%Pkj~sH$@tK2EJ>=t^$B>F>s4}npR@$UXkLO||A@7=1w4+%%ZBmT;r;74S zIlfDxjF)qNBc7I`U?-oQ^8`>mM~at&k=36%43The8N_QaGD6DMvug+OLR4P+SqpiK zjq0Dd((b&Z3I96KIhPiT4dr0Hg{6!qRc*4|t`w$eg(Ti4Oind-f*K7oK9fJE4dKO8 z^k`ov7+kZ}VU&*>y)@`gG<=IKMY{Bo6@ZU3Y7C9~^|NNths9{ za^OadY2e`O=weoRZhZ@KQ5u*G9=BP50%G;H!94I=Q}Q;UM3N3xAJ|NRvD<+8ZZ_;Y z2UjU`jD4qrtxtVmQ+$W9dx3f6=dKWh@81ocK3?R=*uQhIe>a#Xv+UTm^-LK+zyGBR zPH6Y}Xru)94d&*{E@3EC2{01o2(p=2fHHr?8qL23>yl#`bNP{j`w_ANLy6aN5;w7E z`?;u$^dAT5KZ6nb&(cn^wWAaWOLAQPW}4H7u)gQg7b^($a|iYF!I)LDpdMJ8oK{Es z9~Ex33(HXF4)GL;M0oCyzM*6$R=iH&Jei5N%)wh0g2x@*bOYhNOr-N2r1Me#Y>kkr zr$`ljqlyk7X+3wyb;QT@ywE|raEKQ&v4f-6q&jGrLSXz)CRUq+rBB(_&JLPt(KebA zfPE5k7~~E7-u(V+;VrNIwFR6!%Um;r#~~iP@n(p-iYUR@BggW64ahm2h$CFwP` z8fl*ZU9LlW`@wUpxyh^;!X1Uz)^z%~R7z6~;@vzl&oUiDj-xJ13xNyc=7*HRY)8TUZLAih`z)g7~?%z)6$uWhOVztz_;*gZ75>> zK1ba5S(Pd}0*`w+&|=E8Ene3Q3I)7y#Gj%+X#C$(G?7D44HlmOO3vJPd-HV z5OpHjXiIgS`STF9y_Zv7lljw-k$bZ|+7=Wfjd{$nMR7gz{E&5fLBO+2&SOI^zf?v! z+csvsZ7$m3)MQ>8@*w3?b5MJ-P5+$Km)2_+#OQlrW&^-F>DYjI214$cA4^HAJPQH0 zF5p&v45BKUw}aO06a2Dlo)Fnk1JXyl>kBl0w?h4m(SCpPA_#ZT=8zkZF>z?~uAiua(oIxj-FCA({RJJMDst6EI22#XxGg#{#^w;-R9_-gfALv$$4m-B0H zLEASOuHaj!Agl2^P-7BIa}(6wd;rmIT39q~7bX3RT~P&+!6wLnc8bNzSxf2E7ns?y zAl}9)Df3THlOg1fAY|HvmaXDtF!XF)^Y?J-%zS?l;CsnXou|sQ_kCfjn?OtU@ZC{T`J7{N`SEvbqu|V zw4V1zixIza=sEJ34S7=I2^9^=imGI#=?3W3uRB#>@EZc)tA=uy^1yEx+V?1ZXe}N6 zOaboYZ)U*W>ImT0q1?r2ge!UxMSk=REy8uWV`#+$>B;2J+VrJ?{JW&i)FCOq4e7ms z{ND}TAmwQ{agj|g151p*X6P&XFg{=GWz$y&^6wv-E9L1jgDg5A^RDT^`~Q=0t4c%9 z!=eRLITq)`0dYP)blkp7myUXh3HtERFU88sW+n=igXT_>)0ib7a_P6zh)J$lKeS`6 zNRtV!4-kANjo^9^O!QSP3>F=rbJ=%YdNRV9=dAWJx#t4pUIaOs;iP`gfgHAtJtyiz z)hCt8nB`Gl?qC!7=D7}#KI?lf3v`@eyUS zRb^J>tLwtZ-*&=om>0cJ1W5E0 zJGJ4nDfj2|9B4xk_k)Zh8SzhQDE_ap2pFOkYvNcJlUjJ4*TQ5Vt;@FtWXe39{|y?5 zmrO_vxSAnCg{i@|A1lFI(GyE=fTei1+HHRmrpQtcMvrXRSmliI z1Wa^JaG9YSAfJh0Mr0HInHYLHk$x`5(PWqclCF2UY1r?DK{_4MC9P#v2^uZm%!?cHh)Hmr!_;ox zGB;krDK+NKVQ6o5^w`_ITdq?k?V3*x9p6y##xQObcK2d+?4|v77*82ghk3L}M9+T$ zmEL(ASaPQOELy?xHs)}Z@$N@DvZmFHocAD(k#spoQ=FQ|_N2ww)jO!QOcw+s z>20%0u(_YdQc1H0PaF|%sBfw#(N~+BQDhA-rmfroT~kJl_8o$bkUo*j;yo+@I_-SL z4l1&b7clx2Kwm|-NEDW^jGD~Vf*`?=Qcg;lYe24xJo2Qu4rTb734eU7siO^LqWP$(21Vb*F9qoz+(A!=Obn3oA}W)3 z^`BRnX(!TgD%=srJ3?mFc+5(rng51wq>cBO2t-aY`6C7zPp{J9uXj=#76LZ+gbes+ z1MtsU_*iqZ`IBU*%|?pyYzVD`_3NHV5Cf6dGKswuAofy_*hUa5*NVLqN^DlDg6{08 zr3GhD*yF^EYQ148i3#)u@B$OPY`sw7Cs2G{xeIfgF@Li#C)mG92WV7UA!i^Z^webj zu>()!vT1*|xqP?=>}Y{_wheS#HC&e`*j^`%`5U;Q4`Z{ECe79zbd2j>Kug1)0*&rg z>EvwM-jD2c3*tSZ=(EgcJE&+~|E^YO550gdS6SVzrOBQz|siSt+D zVZ(xWDQ73m^?>i3A5Tev8gs)4wGJpkOLt-a22Wjln}jcN86$Z$tQ2y+J59c_8usi6 zyPd6(R4qvpyLom5ToqBBXGgGBIcs7($2A@_7i(BufSov3YiuK{1kYbAczML-AF^FS z%LN>=-7tb3HQolp$1`8TnbjDGH`4366NEkkAanOi@;;G|cXH(!)NCA~8#<^9@$)1~ z&=y!uy-kJoXDhUY)T8YK-S1&5`Kr{BtR*_LJDzZ-o4=rhL}EFKP5>c^P7HT&3f0?u zAmY$fhoH1MXhSwi8H8}^`K~a?J3OvlzD{Ub3$Oi|jHL59$lYk}+EFD!#sb;MckZZa z**%XfExIOQN4?&trzMtTf7aZ#qb{SYqcA@;)=fKdXrH`dx`B5`l|t*>0`Ye>i)PM% zWpX><*Rhq@V3x^&b~(E_+r~x-QU{9??L{~3sPpl@MYqn=O6@Wqx=k0Ek}>?d9oI>P zTB&;+yHvC8#cxbsxo^ks9>jI-nP-ZAkF4)3b5I`G@%#h%l}+@@|03%G_mzit{H>sD zpjR$N))x1b$9Md=lu^j)E&3c;)#o{c*6(<-ys~8;o#5tXB{4oPb)@qJ3*w2rxTVbt zJE}Roip=MDIj6BqU&DK=Xl~H=UdMZ#{9d=bH`BZ)63lCVi7snzq$h2$E%W1rdkgsc zDAdJ;N;!=zzbz>Aq7>SKLgy}sw`!qJQD_YnD*D&iTBnKUqHbguo>!(Tt%kl)Jrw;_KMpXjyrTvNTw1La_CO&W{DVb5-u+JuFk z-C!a55M0e8E9P=d;~3ltkA$HIN%r#$UFf z#1GEpVXs6JdC22LjD@k8=8TcE50-AcAV&V;Gb7)UEqS!{kM5>4PmHupE+Z6;dl!t< zO?2IJW8_54sz{mhN9rya3)$(zF`w(vdodP?NNnTSlr{w9b#0OrREH{>}t{o8*S2Ge@e=@#x%mxxQh} zLVd{bFh!#}RFX5Vg|hMD+!cW3NpTG_{&O8(AP3M~j~Cj4i*rf#)hMV>3p@$5&jD^b zab5l-MaB6bjr)_ZM-#u_+@r?OcAyhz!m4i=UCi4XY4$G#&bzld?asG%8T0x`^-=N* z=yFgHn=guuw{a^_i8FR*%Yb&1#k)9#+HH4Cbc^G?oKj=X+MP`S z@?e~~zoxcgTCvzfy{JTfr>zX_=cBWawEOQEUQ4!N!eewhM?l7cLAQ(-qU4V=5vBwY z5yUbX)vBBt~}Y8 zq~L_#T<5c8?pAhA`fWN}7vwNsa(`=$xg2>}wek)UT+cfNRj~0Irzd0*K zMM$}B%bA~IK2D0I=re#gD2(lrx8R7p-%O$~v*yb{6{$ z+8^2qgex+URys&}vzUXVIxxAqvN}1NH;4n`*hgHZXs1ceBZIgL@a1mQ!5+mX%Qf_= zF4@JXtF-13N$2^9cd$QVH(&DXrt{|`gLM8Zp}~?M^Z3KA!VANJ2$6E z=OH_PmRUbYeehoL!JEwEgVd)Y*S+YH2bJHKyYx4NJpXVP?_Vtj?ECw}c5d<}Xn#kR{Kle^ZtvYBaK z8Em&5lGHZ{tG4TI7`)}JtX)O6W^@7S&1cdfHwh#K(&aS8KO9Vbq6RM-{iK##^AxJ3 zQRjCCtD#46r54(z$)b~k21WM0eL;_RZ=HeNTO=}`aX$50lr{SFAsS{TKbhLaEWy?$ zXEjNsq^n5104{jWtxydgP@wH~Y8I3--Q}Q5=S6tl!Ok#p3yFyvv4@O~F%lDPLo^gY z8+e$B%fJMk$s=)}Kg1j1c#e`MXNeN*$+f11VC$eix$MoP{bPY9eA&mOQS;>?xVkf4 zF?4__1J+Y7JD6Olw``@D(YJh1VpN}q(Rep^PLjtbkOzhHV8*>aM6GiQERqy957GTM zJk*dB-t(G5zOME$Bu4S|xY<_`&lsvRJB2D6&1XaO_zWHUA+T0#rrD%mr^}8335U$4 z%Sx9F)p3>Xn~awL?0kUr@&>!}@B%4Vqd8|NpDy49wv4Vbsg73YA(yjXapXhJ%4I`U z=B0#qxghe!3KG?&L%lX7`CmGe@1k?&2z1N}{`P!zgBW3LfUnr-kYTu+9JrgXUn*lV z^|4aGcqa{u?svF*C~Pzaz8k>4VvzKj$&;{5^#b4@ue(e&%BhxnEhmh1ug@d}s_zAv zgb%HCp451Bl0M=Iyo>+r;F3(=wlZbR%|0f{akC{CFXuMpe%5QHp4oD=Ev+_LWja9N z9+wiMFUb$K(h^yd&eBSP+sdD!`xLZTZhvqfx5$L3cCbJQ|(m+AH9>*bu z=fDyTJ+sV<$fu!WRxLHkODIhy0g1C4m7zJGxPR7Zh-ZZbs&%}L6_6$I2Eeu~ig$Bb zrP<_(D(Kf}i};%|dQiVVzvI+X^eGlp%X6_^Aw!@1jZ}a*{9Ol%D|54KE8vAVs>@9d z?oEB+mgzQoQwF#*-*;gtq!wcp=l!S*`-ublNeoe+qK{Di%J;N9jgjXJQ|syQqCzD8 z)MnP0Gl%i9PBHA#m19bE(?Q}Ww1Jm%KX7us0N~u- z44|z6psmCD@Dqq5#Od3B@U;&!kS-kVvvi>>MP(UXrpVdP1WG^rZwDo|XN!4w_~WAY zWO2(Ks#guyxs7z0?k6-?+Eo-fH&APy9S&buY?3P^kBAn`FvdP`$m_A;IEt#X=ZfLF zGnG~kNRn4rwq>D0pZGji;eM?1Ns0+wjTNCby7-ScrIlEPUm|!b z2$EwFZ{;F;HkscI*J*bZ&%t-%fe(pgG(Wxo2iZ*j)R7UheEx`FmpR|w(pmH+iY(ir zMQ9|uUs%8ypbnuD3_~sTfx%_U%0%@G~KpR)XAQcH5eEo ze{|^oaRlyi_2DZT8Y<=&d3 zjqu(cyz2^f9>&?cLw7Heqc4yO`DA4{q=lUxkn8!-mMn2YO z3;rSJ=cG8y2BIvr=8HmYFzPr>=5s{IY0P5d(|lTprB?}kf0~JYboCrMwxpon%*y~j zevT{dANZq976}mePRt=xKo>?e@op=#0@uEd z)LVR4?4(XUg{aA~rGPfQpbV67cUU0=WzMB_V}0Vg`g>UGXq31f%+s;ac;Wt()?)75 zNj)y=pnfayeO1*BO~<562^d?vlgpsom-(!4L*yD8Drw3DZ&)LLcBkWh$|1zS8aW^S zXT!P`V0*`1Pzo(K8guQ=s;u%&p=)<$ag$fh!oGfI9Ray5`91Aag<^bMx3jv;=S;in7n?Oq7;O$Dsma1xYyl5g1-}j9PlK1cRTcc3wmp0F1&kZ^|Y|x^zQ7$ ztv}uYvLkoRApdZX{G&ng4}&}fOGu9&amYWsv-;{F|1ii0`W@FD6ITLMr|gzN{TWN0 zmzJLeid6m?OP$s1nVq$pgWNOO$?42p4k{fZGpM}~r1oNv+6$nTU}`VyZ1+(~!@htQ z1C}ILPN#QErDyjHYOe;Vy%wbQYLMEiJL}Aky1kl7t#Wn~E3qZ9X%CkaY09Rcur>*T z^ht7$^vM|#2~S4(UI1z!$5cD$#RG-p8nM*}Y0c}+x1NHPcW@Xm0 z)8n2cLCvI3>8Og>38deDI|K3TAmZ5$qCTY~+7Y1(SZ{wP1M|Ef=6MdLKBZ%lK+)5C zf%f(P$v|xjqP97x`jn2!614*e;Ew!W2JB@)*vlMPeM*OwK?1mCz`JeF4BQn#+!YS4 zKBeO-qz*xyz&l`62JWwexW9IA^(h@!h8gf%3cRaFXW-rt#J$16)u(h^g~T8RyzhTM z1NY`2?#&LaKBeQ*(LY)sG9`do@dF2vt=qWTGh~eO6nke}&56Uwbn1+&u>srW_8{%s z9oqUtw2Rh&P#lEFFa9kcoxuImzCMV)-l?BHasAE!!l)8OkWE-m$;%R`_N|TT zM!rS9(Ih~tEsZ9*t1FAt()A1Ba-m}0e=*kBB7gFVlD$of&18u0!ge@KDl_n>vrrsF z8E1M>`tWhK(jJOj)~_h`6B^lh8E?u^dir<=g|>7znmyT3#a8h>ea7U^J{jA~qyY8X zz0x4*hN3b1AI6aM{YYcR_>l5vpOAZ-EReM}jkcG)Kra>B_zm-1;YZ6183E3ls+*vR{u1fc$!1#-XX zJ|wl_z2)ioC#+gGg{P=2^<;ag7D@7;i0DIrx-?FK9e#kM8FK~#WoOeg+ zgIHn|k^DjqQ#k&~MCx_N)Bv{xc*Q?)ab+9fl0w}^NDH71jQm7k*UgFY8dQ=z8S%&a;U|D{^$MQ6OTFhhECc_)yDO&!hY(@$$ z{>v489Vqv^pW6D+={&lk&kH!{+Os&Q2h$p(>B5V{e`X5|aSMzZgF|$q?J2rKy!soj zh|nk~ZkkRV(B#Y}MHI0Ro0)qkMvdW!y9d?@kV8u?eHZM4tYD;I)y2i!NX0fNPOa)) z6h!xRE82T$s1zE=;a&K|9_{_<_Y(K4*uB6!@<5m40QPAhL+YTDmA!-0ZwG8m8C@*v z4`smGoQ7#dx|BvBPox^*3Jek+7B52d0%GYwE_+#_xQ9D~g9qiuvfBVO`rz#a)HT== z;H=+1JeX(QK$&+)1n%I_I@jsjHnuwqfKz^`i^r{-t0zRaMRR~^(Vjo7PgK?!CXhvE zvS_<>VA6`4TDg#b33Z4e9`ohy0H3guX!vTYuChHhsLR4LL_I!Fcg#z3pxYhyAi$3)p7 zpnLX};OXInaa=yfUL+HwjLU>Uc}rjYX%bx+10pwv6U%HctcCQ^t!Rsd=>+8ZaA@tN z;mXC5{aTWq-%zywML&srCfON$6BQjhWV3Of* z+uGiNW8x_wpB+x2nI;=FIyolfAHy*TY^89zroW4vmNDCaedEB=aL1j*v0r@#1LkWFz&?or8am#I^=isg zw|2*Gx%}ho&$8R6qd>dOjV6i3u zJRzJwB#YH=e8*rd1@s96O2V~Q%Yi+6fN6Nz}4^X|i zO;?&^!6a>wAP5SRwF^+MM1jm=vO17#wDtgKYZSmNMr#mI%vLi+ReBHnrmM75=`4dn z5Ks-*1d#S00K|alS_-&R1^`x!*Kz>OivpO%cn#JZ=4&gURz!i!V!j3;eFL@wXltXO z_R~Z#VY>j;8x9#9$b9oMu&vcyp>~W|3;n(0vd_R!thAZmrxIes2E}B}*b?7TsTZ6{L#q$|=8d0&8D1hq{&N<7>S;O%gt zD~tdxudy?U&^2}o7+7L21mu`BXuH4$wp9pOme_sBDSAY!1VEFb0A?}q0)TAbl>uf( z6u2w~o)0JHT?#PEqQGSt&U`pE?%IH}db{xgrd=n%p4@IwG3>el^L7-tEQXz5XP9-p z02+3ZtBzT9CW~3;1Nuf?A+W|o!3=E}m~t zpQ1JAIzZ&UaAJL$b3URNb6tRWWxL_5Dc1v}tx*uOm~uXY0YlE5tO~X(e$$;XXK>15 z$oZgR#w7r=Ulh13W}FYFQOQ!^%nip2s#q|R*N2h`wv9@*0{XswNJNcFrd0uZM7k{c zDWM%YO_hUn35!Z*r8$w;(+ffG*l@aEH7e;7CJV6~H0FlW%51DGMz;^lCU+|kuM9`- z)8zKi*x>E}%GxMsp#!03Zg&A{<3K=?D0_gjbs+F0j`$1JyFKt5+RsDl%xDRq>=%x< zjV&E8vr7SdLO6j)wyEFvj*(ps=-C5G!Zop5fqnS^)9?)J4glX1P9VC0J&|w49XoDc z(*?w`U%CSp8^nAE?KW=RwvFK7Tijoq+9y66+)OHx!EJ#9hIcuj=cSRbMii>iui@=O z!vJpu;FVE;Lk#c$FdO0>fLj{{HpCG3VZ|Wt0^G(ZupwRFhs6$6;B6f++<<{@{tx37 ze$&S*r|lv!ErxmmaQjDr4KdVxS-@Z~1>h-BfI|#+AJ{kC%YivB3U(w}z<_TBU}rdR za2W5$fcM#V40%i7@8N{ftRJ6NNMtsBU>RL(4fjFHV>zqg zpGLzv0No%FD-YEZIxR)FJDUEt)MjcV{0&tfP&qc7T3S~K)-8%7kWp2pJ&_%Pa?rgt zoc{JY1U>^~&31y;`f!?=jTzz?_ypKN=mzrUa0>l82tF!13cWxZRuQThiW+I)^&Eym zK#mzmXcBb^@QxTrTsHa3061g7fEhIfw58#wUr{px4n-RX+!#)1n;Z&XSscfr6C|Ga zuaI&bjBe0)>%T}T*tHMYyom|!gIEtM|c z0eE&Zn9?@58506tMkkP;Orv1U3{)bbmqEyIGP;4cISOvS<_qJpkI@UXVO6d?vU1z6 zk3p#7ViW>xOcd092TDR!52FNdM+_8fz`-a3=8S>D7XKmzv}I9H`}HqK5O6QrfVVmd zZolpY;riZ1Cm^4U0v)C8;9PVA&s2wMT5xFh4gL`OjhJ_Q3yZ&ZIGr?u$|n`sw;;mS zwI~Ixlf!9hpO4Zv#WXAj+T4LcwT?wA09Ql-?$@y(IRyNQ4q)CB1>2h#{Nui^wM^LJ zR&)XFl_;qFx)p?~UPTY!cCU%7lR?WnTq|Ma>t({<{Vr~W8iUEXWv%dp)YX=IKLAwC%MW{ zx5dWHJAQ=4KP#M0ni1ubit0x&Ve3b~#fx^xGQ2I)KgjXT>5uG6NVK}k0E)c9)WIrN}wD=KbmNxx?`0Zl^84y2$>63w5 z22#mzl9>(JpC7^a>_?;kyfmCZG(Unt*^g)g*y<>tQHEO2kLU#AlLLrMV(tdq+XD#8 zCVwyRhSfzFG@=NCK??ykIvn(?=|sSfC;|S&a5~#=-LslFenc54)ciLnxqd_nB+mZt zkn;S9HsIeFPG=kah`mfF@Jj1ljbb~WzZAYbZp2>3qW(P9Cpvo(Oene+u~)!}Cy3eIO~odnf_i5m8|K^BxG+cOJ@sI3o&j z1S!CGNCB=R95y(V`v!gARvNMHxDFQn#c(odc9Tyjs_Q^>t>@4KQlEyCjOICTQ?TP; z8dL{&$8Wj;=8PT@y1Dfm62RL(3T}UX12G$L8%hCrLKNu!+y;Ucub~`ZbEAOv=QR+h zIt{HryDuCysEk3=I#eAUpP>r`KJAClSMnKbb>K3XMpc;u@!N;Wq^W+eUXfh}m$G;a zWuV#~PJKW;1|miEb#0oFs0~@!USq=+5#%q^P3JJuT7(1?M_reB@=QgS@v-;GF*i&;Jp$B zw?7*~aM@Sr0ov9msQvj0j4IB8nS=2QzgEgwWzgUF#i;5nBmlSnK*0vwg;HRiGEms! zFO&mqUKG^+`~?;Sl&lqaE27}`=P)p??=f@$a%~joC~XIqp$mAu;kdye-8ba>w$Ye* z$7itk-)nJuc$)5|8BRW_s6GP|woXF{XiW;IseL|5-xRN*3~0%LLbYx~3V_R^0QcuM zup9z@LmMz}jDqb=41Fv&Ft#`joj_Y31+_oNfl<|S=muPDZm6yVbuwsShij$dIuwG! zq<$!UCD*~L5PXL+5NQu5mevJ=HH+*!q>&cqp#x<95>9?VoCl^)rfn}s#pZ2eL-yxA zFh08vg#aEMPC$Ej6a{4ep#)$@L;;O5)Orp?84za-ATo(L1-NAc2+QVv8}L>S6fT2y z0&IOaXlCb*q7wlpq8s>ch11z~CxUg)@gjObVaWVYE&pniTsNW+B=-F8kn;S965vk^ zr?ZWI#J;8scs*&j>_&WdJ9jPZYb@$J3*3eXptBdjgra*9`v#ndUH}hU=n}9-45|># zi6CJ35QTsn9R;>OJB4A{g(v~o5m7+cp-_S^)>OiD`wrqe<1O7uB zAXg6%w73tQ0DCeDXn*blNdewNH{jlm0^6VWK(M~^&9PzbovaM<8b z?i=)dTWQ3)<2qRQ=5R7;c9Tyjs_Q^>t>@4RQdfqPjOICTQ}A*_2f*$dAW-W!bOCQ; z6x{y&24XhgHuM1UlPJ*rxeWv@UV~YzTDUuY)58$XDB7RVMxg36B!G5uIBHNCgQj(; zIyyc>IS5?Y523H*GuZ0DW#|Bnzl76DQ~hAQBD)MOW$_q#L3M0v=&&&$9s`jlOSTl0 zP7bG-*@*oahXl|5LOF2fhT})`7YLKxg;rp#h=LhmaP_=}4gjtlIA9WS7tl5i94edj zJ%HOfK(LHyPRBrj-#!M4ucQqDPay&L3E^b6-F_zpaU6wG5IEt#K*;qI%0XfFe}$6g zCbRy?DIrOzP9lPQ$gKdZ4`$1vQJA zosKGjdSGphf|2t%!n}#lTKOwZ>ux;O>h8+rM6kt=I*uR|W>tnua6< zfq;lrpJ`gwb^W}CA5!PT2a6XNKmu1%W@z@SDXG6d7o37sucUkN}4^1q< z1W@*mf|jMXcxY<-l>+9J?S%_iedWNKx4oER@3jJDMHI9w_MTT^yW0miYop+0vGhDV zKZe=`sEtt|!`cMF(;nayo|ApjhQ2$Ksg2tUn9=7()~#U7FuQgMw6$(afSl}yLR9P4tpK8*DS+P@P9#l*gEa|@er6*n z7HyX$w=`6GU)7@Z=#sHj2oj^iNd*T1tzH&e*285hwgiBa!h!p=Vm&anU&{b8BMMm9 z(C1mMDIhHy07MdF8(>xs034f^oxpi=d+`{o8z7s*!M3f90~Ts8aEB}lRo6%ss@Lp} zZCVKAJqMD4Yn7G&dg8#6@a)kt;MRoWr`e-{jnR$rJ{a_{G=~{ ~U1LQ}b#Ym}_zsDMCWQ)& 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 z z@AKN@rA~+F3uCKb#;IPPwQ*U;sgu2BS4sg5GVMVGeasSSltxhpxBTe|7>@eH>l}zA zxtFShphfWr+&}+^TVVJqd=12{jxCm#0et(n63;GI#~41`ymb)wpD1GGk`l7Dm+DZtkjxE$_+OY42#IcxdHP_nbS_45RKkO_(G=T8on*De_DYD;k z(;;uj%BXTDKn3=_=smJ%oM}w4e<^nDl~tLLdGaG(+1PUYf0SmoSE&nA>V z{mMAXOTN7qDFf1fjI0HpuwW&sU(i$UxRSWPDl|g$Z?&-v0Ug*7{Idx|blFcssMR8U z6{zcTJ;o4yH^*;?{Z*kBqSrnV+Yr!!4Z%N~FhpG0p*67N(aio zHcmA97U}@jgl5L{l_`2a@{)?^`?}U+a^l4NGTO&X+ZCsV%x6pLX9jBc1FrcBUxD3D z8<6WIB!^+-+bkx1;W|lLFJ(-X35^CG?$K`gr(@_{8_?GZH81uS32FtV-U2 z&caL88{m1@@Vo&Y+9#Uw`SF5v?Fkm<``{;skzl?L{yoHh5%^6!!v7rn^@jg*@RK8r zl4xbVl`o^Mij|4X$m}4}O@}aAt@TT$-{6YROshkRN#>wZt%{ zO17BCw&di>F4c#i)uVp?dSgQMO|y``*Khz$Z_%aPdq^K|^;f3{O4x$=>S-|0&c2i` zCyT02gC=I@PRuT%a|heN#J7Y+NHn!g{fkN}%6m*Hr|rci!M|Gx_j4P?W&O+!vWU)! zI}>Ts&E1)j>R>GNVRDR0brdD<1jWbc81fuIQguCtol(-blgsNIfe){)R*o6vhla9q z>0Xo?z+Qfe$u`lR;4=VETX{0NUmFIW;fCi1eUMaDl?9-ZbO#`PGrd3KX%A?T!dv4E>4ee(&k}L@g2T=%jCC>s1Uon%0jI^opRjBx-+|5jwQ9LpU32BvK<~ zYoye1x-5>1MY+0c<7`ImM}NG~*#!l~IVua7UAs83sT5})a*ddWt$Ka~vWQ`NQ1w(OW6|OXv)+QWL`J(FCYaM7L8- zP*Y#m&0wa~q6w1lwp7`t9jkkjWit~=KRndXLq(Alsb5%KD;06~&O}w4E_t>U&z%(l z>iD7~%B%^}a0Ua4hQUdmB)nzH5JdRyT!_n6Py1F3Pfg;mOzva1=+tscC^^ME(q&0rnfJUCg!06C zO`p7Hla8aCQpBc>GXhy0Li2aJ14Vkdoph2)z2a*iKsqT+D{$&<9~V=;Q-})Zt3ED| zd{s)ImZRoU+b<^3r&1$XpV6yei?@cjY{J8ueZCsLRG8AEcSX>%B)J=*U+))9*7NsI zZt;sIwX?#moS;+ll!N<><+xwor~6M{OwePJeXv*CBr*npYg@HUD(44DO`W2$M+ei* zGu?yHAf~$3FJ192>P&-FUlNNM@;)^5oaP9%u(&8xLTme~0jXVgnIqLw4_`qabgiz3 zPn!-+#s>BNHM)bUrIXo;Iv+Is^&>AC7GUZ z1GKotRL>WYv|6ivnvfM-L1`i8Y4nY$flM|?zN$kMKJI5*V`yk>XmUx1v|N16xDG~m z?Q(}PxW8MJARQhRSbi{_Mk(!LT9lAYcY9DnPUU_Pjw#|}42u$^#MVWY+Iw;+r-HuK zQV)5%vOIE^T5@ys_qUZ=n!rVo$~vcOSpqkX{1Hm(g&s)YJfMYUIx(OJ<4bW6lCIs* zrOZ52S)x=db);fxSSps9RL~hA8on<#=PCITd@6!QgB^CNs5oU(amp+Wjme)(JE$Zb z9YZEPG*;w2j-xBasIz|?#$(r!RFmk493AvizEo*X+fsr$NPm;T9b_sxNGd|UR2j#% zqnVKw;WIlGt?YyfgZbXVbO`>{sQ~vT7yU{GFeWajHG8EsBf`O7sFn{dJ`;hUCJK`_g5E@v8Z#rM4s$vTWZG%6`_jI5V2T?G`+)B zWdgF?1x?LCC@Duzv-N60NK6$SPN%C9C_7weMaq{i32Vs>O}-#oX>_Y5ol+w}4jJpn zO$I1#3c%ckmW;R`dNIn8yb7htjc(d<<&so83*73_6oo%v)d0*vc^>_y+1S&79W}9n zHkdZ@WdbN6-vc}P4M6D=+7%&{jF@Z3xeQBAGpvy4UKMn2GhEX6;_zlJj+~(aPo0C3;WxW69Lz z>GJlF@B6;e^6EK&9?0Xx=-- z@Yd`3uf8mh3|NmetO)Bw(CYN0ZD1-3B1@+_L?@Q0&$I|Wu@9M_Y8~|8pW28|^=r~7 zP%GWlmyoo6y7aYQ8a?P*pay24CUF;dYNrR1$8je|iqogo)o(uOmm6td#A(srfchJO z)Mb`jec{#2_lvK573Ue6H4PEPzkdyox62L z>Q#bA{8F050Vgm|0;T~MwF(cHw=6C>RG6Mi;N~%*dNGY5t8{5nThQzC-Iu0q;x0SC zz)DdAQZj0eBSmCw-#ZD`Ojo-^-${_pJWyxKDqVrV&LtkM1R6Q8xMWC0Io)x<9cDuH zk0jQTo7&L~)!;hFg2dUDWb%KJXM)FL<1if4{iS`@5Y1~(NrI|E%<}7u?t(xALK+Or z!40I+htKgbBoEg0^pDQB1^Ce}pRkLn1lZ#@IzSU?G{#$q%GBdN>~T*Fm6p;cFBnK4 zk`%EI8?nZ(M_;zj{iig_>>*l^;ghxP7JO_F<$g7{T_mRIF-|vaUBfQm768wuYiX&{XxYp6l=^ zPv^qhc%cLG65N)|$br)4n`q~MeQ^;$3b zVuP*=&SIEQ3eMs%VN`I;%jkAO$*F>vd%WUm9O7zBj!9~262$u#o*FYi(YFO+lu`rS zqwaB(58WC*5$11s0{#{l>j@ak)*olwgTP5%?7#~&a=S8G{m@MMo=3yl6~}BcZA?WG zZGbk>msv34#%Pb(9StdXy2qKX|NP+&Z?!Hu4;k4>^`Hc zmXg?>W>Ty5%$3f$CK{QmAu@|@WMa?EYDlDOJwub^Tn&X8wJ2;Loht?o%^PS4OAj)| z=TRE@09Rw=WLq(8Fb+%9Lbq>UaOo&x@(unKdQ84>YGQlGkb8swhSC*N1$=PJf+0JULwq83KB;e{rog&}4FX;pMY z^3G{+8_{y7g>CEurhNIK7HETB^6JS;9Q&uIU}ggA=IEHLDZ|I5sUD%D)k#TEZIG4@5hk$`rRn zLGyxlR++69nd~dzx4$MtdVMK>@n zbC1H~tXU>l@3YW*lUUk;>Z*h7onoP)oZ?)%=C#vk@)yzvQQ8DA_o)n|zJUa!slLU* zoWk5rnDz(97I#kToPJL5w86pC3Y6akkXDdiUS1IDO`p2RE=s$EF4XLNriJ&oWmP$| zK}$_tc%@1E!f3$V<#%eWw5uT|YGy?_fNKGG^s6SjEZ}YcrWUeWe6Wn!<#hT0v0%OK z#XLV;+4H3XrXDvQWeK<*fY-j}QMRY8Y|r>$S=lqsP*uQs=XDR}fN*68G(TuREK9%* z0POyz2l!%J*^A?YMK_ZHtgBp@x((1r^LdmN)0%yYft3Y}nUS#chpQuDjm{tIESZ&v z{*w!aN@h;D{c@rfAsf;zmW@EWa+}9^dA9NL;=n|i%mUEC^&Y^Ya5amf79Nk9R3MXq zw%LnXW~*5i2PR5mJ%9pld(@m3uI99;#m1v16~!RXroQ7ro$a&rob8JP6{V*VP)l5p zx+LBVzofS)+S*y=um%XvMnbwZDv}PH4ZKaK+a&cCQ1PC<4RX@HD^k0K`|f~+R0h*J`3NeSQ+ zKaN|B_N>J!zD_|%688;|u_%67O;i-8B-E7}z@PWYekLhKA$AHvlBl|5K*sCw%W9&! zI3=OR)C2zVPh%!&KTEY$X|e#jbARAlx_gvC*^}vpHyT6IS~U&OwZ9}L6BP+&Y95n` zj4U&Z&!`{c6-K%N#AjAE+Pr*)Pc=eoe%sW{0uj?vJL+9SJ@&XQE2xoMOr8Z=U4O=QR~R_B7poBUO(%r%hx5^g4MjjfgApWMv^tK)(ZP63) zwC<|{Sw-4swi%H~)98lL*_Ey`ke(;L_N41;lP=!Mr@l4=DZS+J4c3k`Xo2?|Esn3m zX-eE+RR=LIe&Z4MlMzRW_z5WpCY6VH zqN_fr421=>OyUo6WsjyD{mb}H0#$6`A~grgOHEstOE7Kif*xBNJ^I|{N9idb&UB@2 z19Wl{2XwXr+UiAWpLtIXjt!YtWVj90K?paU1A*dojhg+7$lkj6kr2z$U5hz_Nq)s>HHKc1a2E$E$-KF8$qO zkLwbU<6PjBx(?fZ*{2+>6{{$r?}>y)==z@p#O;50kZ%NX3?%Ih>rJL9p^uCMVZa^_ z5^a0dEi2s@&pVx^U5ce4r5UK1KnwlpL0#Fje$<6ft4WFNN!3%80ORbv9;}TQ%IQ=h4XS z0Q(g1nMC!sXfqnroExP?zin)wd;7Ps3>_<*#%RbAK-K@_OejIVZy;G~-}`Yu>h4+@ zsL~b}>k1O4<&OqZ_ai@=;+~`>{0Is3-2vtM5!ny$ zLX_`R_8UHEpkk~0-0yjQQDPb{KJe>ixUe99Y|&8OgscIYx!?Da4xu;SncY+OM06yE zfLgj4sn73Eo1cmVC)xeFS7>y_SRFIyeg*Zo?CtZ>xmkOVOkHFiEi%x8NSh+s!dC-} zWyCQ)wxXsD4bgr2Wg(ienE0PqmX{&%WJL#{*u|x10OaqIC$he3Y2#GT%*R>~sSAI@ z^U}y~azi~zEAqz`VJ5VC=g;SaA)2?C0$qRS$_MXR_3hq-FqXn}Jv(}DCH@N+Ti98@ zd-9vt@85^*W|jY~T?R zrv}=bdAL(6>crNV`2rhw4OFBRQq>Lp_jI<%`^z(WI#m06u}IL*j6;s=7%>nGAt zEIN!RM;_?31wHEeVNR@9%}nIco;kuuhuOps=F{+eG&nS=AMJUSjG-g!<;8kSsCr#1 z$v6C^Ty>Zvp}LJw%2sp4y>h1hT;%&EV`rrN4CX0@IX7Q@fOOD-{k%N-P&~HrW31YQ zMa^eVGhoOsk8&E6Z1|nY=7LbsSi0iU_Z{*%HHoY`K0A+&VKWypKaqxT{bm0shRt*k zg~A5$R+fPjLG6UXv<03UGJOu4pXphvQ^iJ;)5NbiTxbn z>6|5nk!y!FwUla`NF4}G#r8lF9VVSbt6}ONJ?T!U{(zn{=`iI!l${JNo0xg>+%}NK z#UWo=A33K_qjHFu4D!x>9&E%kwyspeXkj~)i>d`%?``L_xgILr>*w_yv=jIGQPXtW zaIass90olCY5!i{8A=^3cuPRThGQ!=s_7tB>K#aW)!C)_=!$~z{WMq&K#n`srKF;7 zVq*!4>j^z^MIkUrK&{)Yi+Nh13z{}dK{eep>qx$BDJ*E49TYlsu-DCpOp#H`tWePp2K(t zpH#+Yz)jznr$9iMK>EHsH4M*SYIGa*V2X+qGfGqPCL+`4*E9G9H*^ri=T-uh9GFkr zNu-BtY`Tl%&O}#6RR@cWxWH*SDqQoMg;m1&&8*(~dQ34Yv4M9!CI;Rb>CE~vuKg+t zg4Sm^1yT{Z1u~i_LK>%E#h5Xw4j8?Ac(4}6hoze(m5KO0dpU8)2=|8@;eLS11n&N3 z(h}(j-2HJHL1r|NGiiWR9@*eIhluAOB1oS;pBSS3i_bw!+a&VWJ*VeiJj9LN_KGLC z62i_O>=D{vgc4|j0S(kkvHAhoP0d6=(fL&>X^qe#!`p&6vplDuxGbbHAn}DE9?Ab0 z$$B3D50dLm&|qukS`U!#G96%v?QEQNQy71UmmD@uc`t^`dof(zi`pPD<@MCr*#J4c zhB@Vt&f9@OHl;(yg{3Z z`&w#48=8wRY_K<*3W~FHX$n^l313`dB>8FVqHp_Yg?^Rx61I|K@?17(n`$d-8-Ob( z7?UP`jlfzs!h_ky#%$xjY-25Cx-fYZqiTS(!G%a^ZpX@)F^qnQ)BsT5fnK&xHjPi} zfU|3)Q$w9iXP!49N;yOK$`EvfpLYz>=Li8YF~<^ool8)v^saL z$e=!La(VAUT2;!F6Cre276muh$CE|r3 z${{9S9iLyKLh`s`Mxch{jppm1de8;& zp->Sk#KZN9k}@@3*ys|lBAT$3ik9?601VeB%Io!XYPnvEuP80m=~-dVd z2%>A*nz9a-Wnq#l3mm3l3tZ=quRdy^3(3!!1&oA357q);1?ogkS=yow(yJ!57(Avb z%r2tc%hJK9WFRgtG04>RP-98$@Re3yO8%NFR~K=&OZ)geB_q#;X>MMu1>_SGJka@! zMrCx8EUT-078R+Hqwn5%(8bMP2h_?k2Rcnz%VmsAhtDVx#Ud##wY4_N0FEkm0CUGa z2L(xYQ}jJhorw6M^`7x8!2USN1An6_|4Bt8>_)7#>w(RU5Ef|UT-LtU+e~vYOPV!x zr&Y|0$QLF?sEk;by*AZJ*XI#cBNRJ)s!KtAl^$K0A?UCVnH(YpFpU-?Qd zRBe-rDhknbQX?bYEFNl5u*}lJLTTk=%os=XU?Q!B$Ry3&w8-5T-zW4b8q6nVa7o*0 zvM8M%s9kx*ZZHB_67&v&=qkuSl>}`^&<+Z!(?M`9HQ)t%?>Jm{Z!GmoNPTifAe|fn zlnt_>fUXcDj1mcwg@1giGOiMuJEbVQFh55-0Bey6d*?VZBZ+$0(rjIdtt%N?T= zGf;EKQCJ$Dgd(~+KM66^hfs58Ud7km>FKIm5!(Q9WpkaT%P&%DljZWF4Bh0AXO4Vh zw6h5Z=~Cm$I$e3$zN!*ZlWuTIr|R&s|ISBr8_Q+&We5+{a&ghk$(w$RK1_4+Hnh#4 z6807{cfXpGUQ(jie@W#d87lpGvqLrc2$L>T-{btwI_6YQ+!CRDen-x?z{vJuF+0+F?|0ztZ&j9t?eA@!S|*U|WP4;~(H zIJZF~ic53VUPOrOU5nDlYM)FljG}H4W9$G$A(dO}?!u|*0%L*Ou?>KB&%pnCyY>fh zV@8>Jd%}KH6kTTeaBYDqjZk6ct&aRLvhTOHAc$EWw3H*s_wTo4aS&HR)<1VTWG05U zMcd)>qB|=?Tnx;i;vioRh1O^Tc<3$=efo{Lq?e-GA7#)?ZMr@TrQS&3Mu`W2iQ(nw zZ9uFbpM6+&u^z#6>1J4WVM1v|LPq))`kO$nnI&DsHpHj36K*8*h8^Pt{D-;`?Jd`B zuJIIB(%03ny7WYXnO>2NpMZ@6d}|(Ee8md@y3Rij5wsm3P|xo~CH?LtysM%qMLYvH z83ypW7%Hz)cS+X#c#k8CBrpCzp`d$$`C<~K{;%*)*w;B5l5~|;f(-eBCYq;b@BN882>NG-l zYJ7LWW2W0DGqA8iX^$gv^{!@Nxq4SKsk4D});?b?gy!ohGqWV<9t3gzR&h|dy06*0 zde%ESplCKFe>gHpL5~k@^WXY3-q9k22Ia5J5hHj z!K7k$P&X^N&xa@Xd6Qg4_YhG+UP40dm=#Fp5M8CkpRT)vH?c`2L*9s0^_vg$8+~&d zQvVv#N*7^-)sEK@N)-||c^4tn%}9u@Deob4;jBQVOA}S!OeUCgA3>njdMV1N5Al+} z)99^a>Du+`>}I82)K&Jovv!C%-_u3Tdg2ydYsc?6nAPz^M0nJa5;d-XBbkHn3r68xu+^4{?)e zl>#;LCPboJ%dK|sChf^fsD6c5Gnlnp-Pv3gueQzzlyHb1ncUZ0Mkch}to=7?srGl0 z(`Od#>s;8J$2AGnuk%(;cN<6Nk;{w6mSz{KN|-3G)?q51)osvlG#jM_)eX)0Tt7`3 z0=1&iMy-**x`!8>>JRlPbn`v0=#~Jt%EG7ChUU@Cq0||2GOHzvIOAjO^R@bLygSgN zJgxWXudx}(uUydDvv>CCkFu}O6WN~Y>%NHwV2y~L@`SN~XsBCEbqXY_KxF zx5Vrtok1UYijWx&&7^8tiZw!^R%=~~QQnlP99x;u4rQ{TOprMQ2-gL<*6oIsfqMW>ZMIz4J^i(Lr++ZM+)fuCTrA^{b zwW{L%)T}4ZnaflV8YR{_G~-&QIcOsTYsbxp)I{SJYbc|P)DXLTe^6=GH@{KQo~x(a zbmIqIWjO{y?)AM{&Kx@?QYbHQt%keq(J~33&Dy^2HOq=}nmY1Q`&?fw<|=%so;Q|J zkoX3J)KJ|3gk@#o8=i}+HB$q;pBrNC15<4oHQG8@J?RUIDs*`yxnuiq20RD0#bp6% zvUoQA0zzs?Y9O6P3jq89;VUTI-OgBDsg?h9i7@ThGez*iBAFc~!_vJsJ1m}LS)6uo zpg`!VqW;ty-V&-e&g7NlTPcSNGp$uyg4~~3K}+Q*o)~}-Z~_#&nu1uCegy%My(wyPGw@F|L}^4 zi!2tPT%G}#SuQw2c;3HCH|Ek7KCiW=uE?WfI3V4hQ3<47E<{@7lKmg-1EU)=%mbY} z;Rj0=t*&g2>Tj@R=}R;kA*1e9r!h#@%SK<8WgfJwmm$lO5IX!kg=?X)G(h{j<^(58 zzZP!k*ZgNivoxcLnSn9Og+)oP_aE{Y`a0@z>W$hS?L;?1V;ckJ`q!PRQM%tY0Gw{~ zpws=fEr@LVb_^S9wY7n(ARy}vkDw331(^pe=tBtdB!OBv-c+lbP=olA3z?b?^i=9& zy?4l8?PnLuXc{w62=};pTtG>XCo1nALH+59!&17HxOZu3acP?VtLh;r^G%Z!f}$Z2 zjfXm(r~7#+=pE&_f!EcBsk@$zD1(x1p|#LxU{b0tPkzh@rY}#BA}Oj@t^~0JNH@C> zCu2gP^xf%%I2khs3Gi1plWymtXfpInFU=@c1^D}2VyK@R!U$SrDu_c^|41qe9CWA! z$`&u$&~P-pP#YZ$NvsFTcV4vN;b_C7=_WXO2Gt0R^lc`)kwN6HSC@uoj(&)wo1{^r z8I)gLXvCt_W#MT0ArhM2hokC6n_oeFix1#p<0-)06j|*))d!{Cl8ASMBBFH(gEaP1 zf^|0=Nd*@wH6%5FyXb9WC9RJemf95q8tbwn&stnqB6M?-2{B*)D{&F6Bn>v3s1cyo zzY{qv(n`l+A_GbGsRCB_cVlAyd;5qiZJZ3qc<*1yieMZ^E7$?eU<1%+z85(~!*yQ< zD_PepgxziC%4D=FdTLk+%%k6TV$(q(-(|^O@wJDR6*|<-B76L;_m!3Y$Q*;R%f`!zj(wg)jm|C$$?kD+>+8{~ z8^+pUZu2zr@MKSR<6Ps>$C5TgcDY8hGYz&iQW#H!q!CviK4_x7`MOh;JJBjDJ!vG? zVeIVItd_aeanCHC0Z!8~Zi#eQM8lBK@vxn@I@lwI zGWrTB&94}0wx^V`-;Cy%PKg1x#L?NgdbGe85$YAy?@*~eXlhxy(PL;jjX+!$!yXR< zl^HIOvPq34CA-xi4pxKv#Axpa>xB>fdWk2o6rbMH89uRw$FOk6O6Bqx0Sk>id2(;n z98aSNE|Q}eQBgfjhyml7x>fo-MF(7$@(SrNs|b3)B(H6THg3a7v9jdQg@+#Nb4O?~ zRMNkL-5}@?hZCc=ucxNSnBTl+jAn}Fl#2bIe2od$Wz}YFp^xs6l75sJn!Lk=W2Naa zrir!H>LY!GOaE`92~W6?PC2DHv_ZAaFC?lS%r$X}lDKL4p-`wl-`Qc}Kl*{``bDUp z^#b!H`eePT1Ey2p zmyumc7x+%3TPwM}>)*R~ZowhUNWS0FfF^U4=8|F>Td3ADU!et#fLIhK_gllh5uh`RODec)75G}f z&i>RSFdY6!OowPjc_a`6RpR5*rI{tlz`hc;5bG@HA6nfi{4zUzSB?Mov^*LtbS`m-Ev{`tluBL3s znz{e^Au0S#Puh8&J+`R0AYV7$dw$=3SIhAC1L$BY4bt_?RkbgF=!jgHU0RlxO&8`` zQV$M~n`2=QkVDujd{JJ@LF~E%!(W$44tMKWQBM7oRv=j=gf;2b>}Kp#+G^gWUx|;d z020g8#nGxeqNiEMUk2(`5(D(=x5m_x(KsY@u9Ql>kLg_5r%NB7$yh{lKK`(hWc#`N z$j)Ege4pz*RLOdWT_>eWL_Gwa_?<&A`R?UtIo(@Ki*YQ5cH}^t-lB~@vD6z|dHEAI zy;%Xr;-pKW>^IRHOVc6m`R^Tad4cvy6lQ%)XEY~+KLy7Et`q^3K;ELYk?bC!FZq%Oxd792R-r&tNNK%GaK`;0lv*+@(JiCT z=}t!I(x32lw3;lb%1GIUckRu)iE4^0?(xyTFgJtCcq4f8v@-e+-RnpPt^VAKzH1?U zu9UYEn@}K^_q#hq@j{(EpNhQ89YzS$+#7P-liNhw1`aNHWGMsjv z=$G!u{IXweX;11obs={N=GVP>ADVs}HUPGe`FmMW#gZ$dkniGP=&)IZjvUijqB)}a zO02hpPb;0Y6bW}r*sMVFH0>$TZ2jDh67?kI6ZaH6e5McQ<#nRtrt~3RI+Nap59CxK zD;wfy1|895Ky9Kw?lUDq#YZF~2hw!P$)7I|% z0@daxQn?ng6gBWoU0-NLrEeKz=kfs!6Z^UZhsN@y*noT~HiOzSUu&s_ZIkGds_dNn zavmLCekhMnlI6&0YiWAY`BSc~k3Ux0Pg@{=GN2FHT2(>P#RoVga{JX)+DO{|Hb!-Q zm>Mh^z|vwXMWa35A=-k%?H4Vv7O8VtHuV_zpxzmx9WK>BLsX$<^-3BFG4S~UOndXj z|S)n(vA0;|wIK=5@r2*(giAIVvE9%@c?Lt7`A}G%<40S4@ zZ_bTTZPro8Rs#ND90kDLOx0#ST+F;;D&^ndl7E#`b^a-J81k+xj~Whb^Ulb_DT!ry40qW95`UgMsNIFzX2>`5KBv^LV94xCzx?O{NWB97fx3a)uDvRf4v4iVo3Zxlxjc zx+#fVWOdLL5=394DJrIk5zptZl%N$;0+o!%Ewv-( z{h;iU4rzRKKt+kFMW!uHHJRt9Xg(^D7c}Z@qVk`oj^Xc|@Cl2=0zQ9vvb>^gs`NdW zb{&b2M&f1VO0;t&kRd@ANzhgbawU|v_0oqaxEY{Nk0NbrPBCfJGfH=y ziXum>I&l9JJC+=)RO(d0UIS{5U`Qjh4L7Jusods!sG99UtHAE;fzBP$bXQsjurFMo zG{pHzW0>{Suk8C_qax}d)bL`}2(WgiJFJnIO-=hV{!P=lZdT1q%GI}Hsnym(JPBRx zXT?bPHN~~BQYS!^Ga7AWddxC3)Hcxfh4?bOQ2!)+gfcZ!S0+Y;Y)|L zd${Km`j|1YP1OPYrfyE#j;3$&rO#x`Hq4}LlGG6E{H0Gk$>=&cF{h-WZ@;N(r~ifH zbxoU+l*7PIwjf>jkyA#W!Q)!SdqU}}WI1#?gL=LH`1^j$WvW|JSq@DSOF85nvK5_w zmBSrnYL~xnM^=IM7g7HGN9Ug|KwXvZKRWxUhSRAku3M$*NB^e;NHd?Qff^2x{Y*dk zrTN;jPma1R;(t0#Z3Dh%Ef%+ZiZ?@8iz}raK zwl!w0f;sd1JI$)UO+Pn*ha6L(|T4^5%3U@g0r;kN3>GF6?xUwTCM8KlpM(1(>Aq7s zLYa_Xsargg1CJ+24y>5QIWQ+dCbQHi&_`V7;QqvJDY@Mxm1ms?E9TR27isC%PYS3x zWWw?EF+eUkl$m70lwIIFz87N9^|v+pvPR0k$tizcAiPC|(blxRRJUI>IJ~pKJS4NcS0S$n0eVN2 z8X&-b40H%c%*j`8AZKXLea<-RiM$dd{Izi5<{>$8e7<@e#UwLv{5b0&gVlP?HUn)! zdJTt8_ zReU;+o+haeG($r5n(5M+zn{R3HV>aDd*4TC4ODT~YXtRCLLL=!Ixj|-g7IZI9|2%3 zT?@p6?~hTUc#E?u0lGJ$<5(r8j#onQ-&_eG(ZAajh1%rbQBgd-r*5GbQmISC2;)G_ zQL`dkG3u|*qDn`T^}RZW*lXi7uXdhjs1iUdnC-ADH8sndt*6$EW*dd3Wtpwd18~by z7%HU9p)IP{G@H_(EmTOYMYVq4ta{-_I*u`(9yvdnRo_I9C90~KPq(+Vljy#Pt%x?i z5F4Z)ZdU&h^VV;gl}MHQL4Cz2d4>u(Yi65^OLb2?8@JBTr)xQ<^3_U+t=UK)?dG6T z^+>bs1V?ir8ovdC*J-v5U|T_KnH*HAo&j4`VaoMclKv}43)LSWZ^rFGv+Ami3G(KE66xHxB|o;fVgciHffBHa_&R|!!Cn}d69k^@yzGR!HM`LhCb z974O&am1yPGd@z?87i%nsoYsmsCK?XF_TSqYdFrPF(JA!PN}<_AAg$fD7X z^s#q0mqAlou0;3aV9m{3hiaPttp`CFoT$6)L{ay8HxiZZRu>f?n(6_l(eiVbw$!k! zX?_um0K?fpI>(_)d4SlUT`SI}nReEc zgrzzI+8uhULrZtkbfwlCHdHb=E1WU7eQv>{EUR;<(vRNCt+57G(6z^^5*S~5u~&==@AiB6^|tqn+J;ysSkCgqTaSZ!&+vkaRCwPkfOjb(#K>g9o zHEQLM-g_du_-i1#-co@EI=s^Atjhdl>&%|Ya5Xx)lg7{zKYK-WYfJ6ckzBw?8#jzN zwaC+>9g-osy}68Th%Fi`7nvB;qgI;C*ShdpTRAsllBa(&=k9yKF&xgd6>5XRd zeXHr#a=toHJL2Y98?4zispi35l$=z-+iR@nU_9y!%jWGMDjhmxKIYNoR_KyM`(#3N zuRpEMU2!-Yt{Yu4O#Tac7B^v#d_%k~t_s@bCFu(nGzZU=^)_>7nem#sWb{B5ql_X; zS9uh=$0)=Te07hNdiEgGqvUEjlv!+p&}a7J9=(gV`piQ`UihM(Qg8Q=G z(UnlWX)>*rPwGRtR;E7o^`Fn(Dt&NXO7rJBMeKQzR%0W~m>kI9_8xit6`&hvfuNGZ z^VM#hcJ@`NlxLoiv`l+eXyTY6wbysnJeIU)YRaOG5LEn~v z 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