diff --git a/config/config.go b/config/config.go index 4fba376..ec9429e 100644 --- a/config/config.go +++ b/config/config.go @@ -5,10 +5,18 @@ import ( "encoding/json" "strconv" + "github.com/project-flogo/core/data/metadata" "github.com/project-flogo/rules/common/model" ) // 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"` +} + type RuleSessionDescriptor struct { Rules []*RuleDescriptor `json:"rules"` } @@ -102,3 +110,10 @@ func (c *ConditionDescriptor) MarshalJSON() ([]byte, error) { return buffer.Bytes(), nil } + +//metadata support +type DefinitionConfig struct { + Name string `json:"name"` + Metadata *metadata.IOMetadata `json:"metadata"` + Rules []*RuleDescriptor `json:"rules"` +} diff --git a/config/manager.go b/config/manager.go index d70a441..ccb6054 100644 --- a/config/manager.go +++ b/config/manager.go @@ -15,18 +15,18 @@ const ( ) type ResourceManager struct { - configs map[string]*RuleSessionDescriptor + configs map[string]*RuleActionDescriptor } func NewResourceManager() *ResourceManager { manager := &ResourceManager{} - manager.configs = make(map[string]*RuleSessionDescriptor) + manager.configs = make(map[string]*RuleActionDescriptor) return manager } func (m *ResourceManager) LoadResource(resConfig *resource.Config) (*resource.Resource, error) { - var rsConfig *RuleSessionDescriptor + var rsConfig *RuleActionDescriptor err := json.Unmarshal(resConfig.Data, &rsConfig) if err != nil { return nil, fmt.Errorf("error unmarshalling rulesession resource with id '%s', %s", resConfig.ID, err.Error()) @@ -42,9 +42,41 @@ 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 + } + + return nil, errors.New("cannot find RuleSession: " + uri) +} + +func (m *ResourceManager) GetRuleActionDescriptor(uri string) (*RuleActionDescriptor, error) { + if strings.HasPrefix(uri, uriSchemeRes) { return m.configs[uri[len(uriSchemeRes):]], nil } 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/README.md b/examples/flogo/simple/README.md index 44a5152..7bf091e 100644 --- a/examples/flogo/simple/README.md +++ b/examples/flogo/simple/README.md @@ -30,8 +30,7 @@ Below is the `flogo.json` file used in this example application. We will use thi "port": "7777" }, "handlers": [ - { - "name": "n1", + { "settings": { "method": "GET", "path": "/test/n1" @@ -39,20 +38,14 @@ Below is the `flogo.json` file used in this example application. We will use thi "actions": [ { "id": "simple_rule", - "mappings": { - "input": [ - { - "mapTo": "values", - "type": "assign", - "value": "$.queryParams" - } - ] + "input": { + "tupletype": "n1", + "values": "=$.queryParams" } } ] }, { - "name": "n2", "settings": { "method": "GET", "path": "/test/n2" @@ -60,14 +53,9 @@ Below is the `flogo.json` file used in this example application. We will use thi "actions": [ { "id": "simple_rule", - "mappings": { - "input": [ - { - "mapTo": "values", - "type": "assign", - "value": "$.queryParams" - } - ] + "input": { + "tupletype": "n2", + "values": "=$.queryParams" } } ] @@ -110,6 +98,24 @@ Below is the `flogo.json` file used in this example application. We will use thi { "id": "rulesession:simple", "data": { + "metadata": { + "input": [ + { + "name": "values", + "type": "string" + }, + { + "name": "tupletype", + "type": "string" + } + ], + "output": [ + { + "name": "outputData", + "type": "any" + } + ] + }, "rules": [ { "name": "n1.name == Bob", @@ -177,18 +183,17 @@ The `actionFunction` is your rule's action. It means that when the `evaluator` c Again, this is a unique string whose value binds to a Go function at runtime (explained later) ## Configure the trigger handler -Flogo users are perhaps already familiar with the trigger configurations. For rules, you have to additionally configure the `name` and -`action` of the handler -In this example, we configured two handlers. In the first, we have `handler`/`name` as `n1` and `path` as`/test/n1` -In the second, we have we have `handler`/`name` as `n2` and `path` as`/test/n2` +Flogo users are perhaps already familiar with the trigger configurations. +In this example, we configured two handlers. In the first, we have `tupletype` as `n1` and `path` as`/test/n1` +In the second, we have we have `tupletype` as `n2` and `path` as`/test/n2` What this means is that when data arrives on URI `test/n1` we map its data to tuple type `n1` and -when it arrives on `/test/n2` we map its data to tuple type `n2` Note that the handler names should be one of the tuple type names as defined in the `tds` section +when it arrives on `/test/n2` we map its data to tuple type `n2`. Note that the `tupletype` should be one of the tuple type names defined in the `tds` section ##Mapping data from the handler to tuples -To do that, we simply configure the `actions/mappings/input/values` to `$.queryParams` +To do that, we simply configure the `actions/input/values` to `$.queryParams` ##How it all comes together -When data arrives on a trigger/handler, a new `Tuple` of type=handler's name is created +When data arrives on a trigger/handler, a new `Tuple` of `handlers/actions/input/tupletype` is created The tuple values are initialized with the HTTP query parameters and the tuple is asserted to the rules session ##Binding Go functions for actions and conditions to the string tokens defined in the descriptor @@ -252,7 +257,7 @@ First, inspect the `flogo.json` and the `functions.go` to understand the rules/c For our test app `simplerules`, from the command line, `simplerules/bin/simplerules` Then from another command line, send a curl request -`curl localhost:7777/test/n2?name=Bob` +`curl localhost:7777/test/n1?name=Bob` You should see this o/p on the console ``` Rule fired: [n1.name == Bob] diff --git a/examples/flogo/simple/flogo.json b/examples/flogo/simple/flogo.json index adaff78..41e8ca1 100644 --- a/examples/flogo/simple/flogo.json +++ b/examples/flogo/simple/flogo.json @@ -12,8 +12,7 @@ "port": "7777" }, "handlers": [ - { - "name": "n1", + { "settings": { "method": "GET", "path": "/test/n1" @@ -21,20 +20,14 @@ "actions": [ { "id": "simple_rule", - "mappings": { - "input": [ - { - "mapTo": "values", - "type": "assign", - "value": "$.queryParams" - } - ] + "input": { + "tupletype": "n1", + "values": "=$.queryParams" } } ] }, { - "name": "n2", "settings": { "method": "GET", "path": "/test/n2" @@ -42,14 +35,9 @@ "actions": [ { "id": "simple_rule", - "mappings": { - "input": [ - { - "mapTo": "values", - "type": "assign", - "value": "$.queryParams" - } - ] + "input": { + "tupletype": "n2", + "values": "=$.queryParams" } } ] @@ -92,6 +80,24 @@ { "id": "rulesession:simple", "data": { + "metadata": { + "input": [ + { + "name": "values", + "type": "string" + }, + { + "name": "tupletype", + "type": "string" + } + ], + "output": [ + { + "name": "outputData", + "type": "any" + } + ] + }, "rules": [ { "name": "n1.name == Bob", diff --git a/ruleaction/action.go b/ruleaction/action.go index f6accb6..8fbc103 100644 --- a/ruleaction/action.go +++ b/ruleaction/action.go @@ -4,31 +4,36 @@ import ( "context" "encoding/json" "fmt" - "github.com/project-flogo/core/data/metadata" "runtime/debug" - "github.com/project-flogo/core/app/resource" + "github.com/project-flogo/core/data/metadata" + "github.com/project-flogo/core/action" + "github.com/project-flogo/core/app/resource" "github.com/project-flogo/core/data" - "github.com/project-flogo/core/trigger" "github.com/project-flogo/core/support/log" "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" ) + const ( sRuleSession = "rulesession" sTupleDescFile = "tupleDescriptorFile" - ivValues = "queryParams" + ivValues = "values" ) + var actionMetadata = action.ToMetadata(&Settings{}) + var manager *config.ResourceManager +//var resManager *config.ResManager + type Settings struct { - RuleSessionURI string `json:"ruleSessionURI"` - TupleDescFile string `json:"tupleDescriptorFile"` - Tds []model.TupleDescriptor `json:"tds"` + RuleSessionURI string `json:"ruleSessionURI"` + TupleDescFile string `json:"tupleDescriptorFile"` + Tds []model.TupleDescriptor `json:"tds"` } func init() { @@ -43,8 +48,10 @@ func (f *ActionFactory) Initialize(ctx action.InitContext) error { if manager != nil { return nil } + manager = config.NewResourceManager() resource.RegisterLoader(config.RESTYPE_RULESESSION, manager) + return nil } @@ -60,7 +67,7 @@ func (f *ActionFactory) New(cfg *action.Config) (action.Action, error) { return nil, err } - rsCfg, err := manager.GetRuleSessionDescriptor(settings.RuleSessionURI) + rsCfg, err := manager.GetRuleActionDescriptor(settings.RuleSessionURI) if err != nil { return nil, err } @@ -89,11 +96,22 @@ func (f *ActionFactory) New(cfg *action.Config) (action.Action, error) { } ruleAction := &RuleAction{} - ruleCollectionJSON, err := json.Marshal(rsCfg) + 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()) + } + ruleCollectionJSON, err := json.Marshal(ruleSessionDescriptor) + if err != nil { return nil, fmt.Errorf("failed to marshall RuleSessionDescriptor : %s", err.Error()) } - ruleAction.rs, _ = 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()) + } + + ruleAction.ioMetadata = rsCfg.IOMetadata //start the rule session here, calls the startup rule function err = ruleAction.rs.Start(nil) @@ -103,7 +121,8 @@ func (f *ActionFactory) New(cfg *action.Config) (action.Action, error) { // RuleAction wraps RuleSession type RuleAction struct { - rs model.RuleSession + rs model.RuleSession + ioMetadata *metadata.IOMetadata } func (a *RuleAction) Metadata() *action.Metadata { @@ -111,8 +130,9 @@ func (a *RuleAction) Metadata() *action.Metadata { } func (a *RuleAction) IOMetadata() *metadata.IOMetadata { - return actionMetadata.IOMetadata + return a.ioMetadata } + // Run implements action.Action.Run func (a *RuleAction) Run(ctx context.Context, inputs map[string]interface{}) (map[string]interface{}, error) { @@ -126,20 +146,34 @@ func (a *RuleAction) Run(ctx context.Context, inputs map[string]interface{}) (ma }() - h, _ok := trigger.HandlerFromContext(ctx) - if !_ok { + tupleTypeData, exists := inputs["tupletype"] + + if !exists { + log.RootLogger().Debugf("No tuple name recieved") + //no input, should we return an error? return nil, nil } - tupleType := model.TupleType(h.Name) + str, _ := tupleTypeData.(string) + tupleType := model.TupleType(str) valAttr, exists := inputs[ivValues] + if !exists { log.RootLogger().Debugf("No values recieved") //no input, should we return an error? return nil, nil } - strMap := valAttr.(map[string]string) + val, _ := valAttr.(string) + valuesMap := make(map[string]interface{}) + + //metadata section allows receiving 'values' string as json format. (i.e. 'name=Bob' as '{"Name":"Box"}') + err := json.Unmarshal([]byte(val), &valuesMap) + + if err != nil { + log.RootLogger().Warnf("values for [%s] are malformed:\n %v\n", string(tupleType), val) + return nil, nil + } td := model.GetTupleDescriptor(tupleType) if td == nil { @@ -148,31 +182,25 @@ func (a *RuleAction) Run(ctx context.Context, inputs map[string]interface{}) (ma } for _, keyProp := range td.GetKeyProps() { - _, found := strMap[keyProp] + _, found := valuesMap[keyProp] if !found { //set unique ids to string key properties, if not present in the payload if td.GetProperty(keyProp).PropType == data.TypeString { uid, err := common.GetUniqueId() if err == nil { - strMap[keyProp] = uid + valuesMap[keyProp] = uid } else { log.RootLogger().Warnf("Failed to generate a unique id, discarding event [%s]\n", string(tupleType)) - return nil, nil + return nil, nil } } } } - valuesMap := map[string]interface{}{} - for k, v := range strMap { - valuesMap[k] = v - } - tuple, _ := model.NewTuple(tupleType, valuesMap) - err := a.rs.Assert(ctx, tuple) + err = a.rs.Assert(ctx, tuple) if err != nil { return nil, err } return nil, nil } - diff --git a/ruleaction/action.json b/ruleaction/action.json index b0d9203..90bd52e 100644 --- a/ruleaction/action.json +++ b/ruleaction/action.json @@ -25,8 +25,12 @@ ], "input": [ { - "name": "data", - "type": "object" + "name": "tupletype", + "type": "string" + }, + { + "name": "values", + "type": "string" } ] }