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/common/model/types.go b/common/model/types.go index 2258a93..eb2807d 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 + AddIdrsToRule(idrs []TupleType) } //Condition interface to maintain/get various condition properties type Condition interface { GetName() string - 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 @@ -71,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 @@ -92,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 { @@ -103,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/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 ) 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/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 7ae71e3..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...) @@ -34,9 +39,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 +57,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..872826c --- /dev/null +++ b/ruleapi/exprcondition.go @@ -0,0 +1,159 @@ +package ruleapi + +import ( + "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 +var resolver resolve.CompositeResolver +var factory expression.Factory + +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{}, + }) + factory = script.NewExprFactory(resolver) +} + +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) { + if name == "" { + cndIdx := len(rule.GetConditions()) + 1 + name = "c_" + strconv.Itoa(cndIdx) + } + 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 != "" { + //e, err := expression.ParseExpression(cnd.cExpr) + exprn, err := factory.NewExpr(cnd.cExpr) + if err != nil { + return result, err + } + + scope := tupleScope{tuples} + res, err := exprn.Eval(&scope) + if err != nil { + return false, err + } else if reflect.TypeOf(res).Kind() == reflect.Bool { + result = res.(bool) + } + } + + return result, nil +} + +////////////////////////////////////////////////////////// +type tupleScope struct { + tuples map[model.TupleType]model.Tuple +} + +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 +func (ts *tupleScope) SetAttrValue(name string, value interface{}) error { + return nil +} + +/////////////////////////////////////////////////////////// +type tuplePropertyResolver struct { +} + +//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 + +} + +func (*tuplePropertyResolver) GetResolverInfo() *resolve.ResolverInfo { + return resolve.NewResolverInfo(false, false) +} diff --git a/ruleapi/rule.go b/ruleapi/rule.go index c4fc1af..9bd2d30 100644 --- a/ruleapi/rule.go +++ b/ruleapi/rule.go @@ -2,6 +2,7 @@ package ruleapi import ( "fmt" + "regexp" "strings" "github.com/project-flogo/rules/common/model" @@ -74,6 +75,47 @@ 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) + + 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 +145,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 +180,149 @@ 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) + refs := getRefs(cstr) + + 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 nil + +} + +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 { + 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 +//} + +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 keys2 +} 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/common.go b/ruleapi/tests/common.go index c1cf504..8dfa92b 100644 --- a/ruleapi/tests/common.go +++ b/ruleapi/tests/common.go @@ -35,6 +35,7 @@ func falseCondition(ruleName string, condName string, tuples map[model.TupleType return false } 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) { @@ -60,3 +61,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..478cfa6 --- /dev/null +++ b/ruleapi/tests/expr_1_test.go @@ -0,0 +1,54 @@ +package tests + +import ( + "context" + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/ruleapi" + "testing" +) + +//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) + + 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") + + 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") + + 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 +} diff --git a/ruleapi/tests/expr_2_test.go b/ruleapi/tests/expr_2_test.go new file mode 100644 index 0000000..5f5d7fe --- /dev/null +++ b/ruleapi/tests/expr_2_test.go @@ -0,0 +1,55 @@ +package tests + +import ( + "context" + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/ruleapi" + "testing" +) + +//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) + + 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() + 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 new file mode 100644 index 0000000..37d6361 --- /dev/null +++ b/ruleapi/tests/expr_3_test.go @@ -0,0 +1,54 @@ +package tests + +import ( + "context" + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/ruleapi" + "testing" +) + +//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) + + 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() + 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 new file mode 100644 index 0000000..c3cd7f8 --- /dev/null +++ b/ruleapi/tests/expr_4_test.go @@ -0,0 +1,54 @@ +package tests + +import ( + "context" + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/ruleapi" + "testing" +) + +//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) + + 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() + 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 new file mode 100644 index 0000000..411a50b --- /dev/null +++ b/ruleapi/tests/expr_5_test.go @@ -0,0 +1,54 @@ +package tests + +import ( + "context" + "github.com/project-flogo/rules/common/model" + "github.com/project-flogo/rules/ruleapi" + "testing" +) + +//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) + + 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") + + ctx = context.WithValue(context.TODO(), TestKey{}, t) + rs.Assert(ctx, t1) + + t2, _ := model.NewTupleWithKeyValues("t2", "t2") + 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) + 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 +} 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)