diff --git a/config/dataprovider.go b/config/dataprovider.go
index 351da2a65..49af848d2 100644
--- a/config/dataprovider.go
+++ b/config/dataprovider.go
@@ -19,6 +19,7 @@ along with this program. If not, see
package config
import (
+ "fmt"
"net"
"strings"
@@ -52,13 +53,22 @@ func GetDynamicString(dnVal string, dP DataProvider) (string, error) {
//NewObjectDP constructs a DataProvider
func NewObjectDP(obj interface{}) (dP DataProvider) {
- dP = &ObjectDP{obj: obj, cache: NewNavigableMap(nil)}
+ dP = &ObjectDP{obj: obj, cache: make(map[string]interface{})}
return
}
type ObjectDP struct {
obj interface{}
- cache *NavigableMap
+ cache map[string]interface{}
+}
+
+func (objDp *ObjectDP) setCache(path string, val interface{}) {
+ objDp.cache[path] = val
+}
+
+func (objDp *ObjectDP) getCache(path string) (val interface{}, has bool) {
+ val, has = objDp.cache[path]
+ return
}
// String is part of engine.DataProvider interface
@@ -70,16 +80,59 @@ func (objDP *ObjectDP) String() string {
// FieldAsInterface is part of engine.DataProvider interface
func (objDP *ObjectDP) FieldAsInterface(fldPath []string) (data interface{}, err error) {
// []string{ BalanceMap *monetary[0] Value }
- if data, err = objDP.cache.FieldAsInterface(fldPath); err == nil ||
- err != utils.ErrNotFound { // item found in cache
+ var has bool
+ if data, has = objDP.getCache(strings.Join(fldPath, ".")); has {
return
}
- err = nil // cancel previous err
- // for _, fld := range fldPath {
- // //process each field
- // }
- objDP.cache.Set(fldPath, data, false, false)
+ var prevFld string
+ for _, fld := range fldPath {
+ var slctrStr string
+ if splt := strings.Split(fld, "["); len(splt) != 1 { // check if we have selector
+ fld = splt[0]
+ if splt[1][len(splt[1])-1:] != "]" {
+ return nil, fmt.Errorf("filter rule <%s> needs to end in ]", splt[1])
+ }
+ slctrStr = splt[1][:len(splt[1])-1] // also strip the last ]
+ }
+ if prevFld == utils.EmptyString {
+ prevFld += fld
+ } else {
+ prevFld += "." + fld
+ }
+
+ // check if we take the current path from cache
+ if data, has = objDP.getCache(prevFld); !has {
+ if data, err = utils.ReflectFieldMethodInterface(objDP.obj, fld); err != nil { // take the object the field for current path
+ // in case of error set nil for the current path and return err
+ objDP.setCache(prevFld, nil)
+ return nil, err
+ }
+ // add the current field in prevFld so we can set in cache the full path with it's data
+ objDP.setCache(prevFld, data)
+ }
+
+ // change the obj to be the current data and continue the processing
+ objDP.obj = data
+ if slctrStr != utils.EmptyString { //we have selector so we need to do an aditional get
+ prevFld += "[" + slctrStr + "]"
+ // check if we take the current path from cache
+ if data, has = objDP.getCache(prevFld); !has {
+ if data, err = utils.ReflectFieldMethodInterface(objDP.obj, slctrStr); err != nil { // take the object the field for current path
+ // in case of error set nil for the current path and return err
+ objDP.setCache(prevFld, nil)
+ return nil, err
+ }
+ // add the current field in prevFld so we can set in cache the full path with it's data
+ objDP.setCache(prevFld, data)
+ }
+ // change the obj to be the current data and continue the processing
+ objDP.obj = data
+ }
+
+ }
+ //add in cache the initial path
+ objDP.setCache(strings.Join(fldPath, "."), data)
return
}
diff --git a/engine/filters.go b/engine/filters.go
index ccb0db6fe..8685bb439 100644
--- a/engine/filters.go
+++ b/engine/filters.go
@@ -126,7 +126,7 @@ func (fS *FilterS) Pass(tenant string, filterIDs []string,
continue
}
for _, fltr := range f.Rules {
- fieldNameDP, err = fS.getFieldNameDataProvider(ev, fltr.FieldName, tenant)
+ fieldNameDP, err = fS.getFieldNameDataProvider(ev, &fltr.FieldName, tenant)
if err != nil {
return pass, err
}
@@ -186,10 +186,10 @@ func (f *Filter) Compile() (err error) {
}
var supportedFiltersType *utils.StringSet = utils.NewStringSet([]string{utils.MetaString, utils.MetaPrefix, utils.MetaSuffix,
- utils.MetaTimings, utils.MetaRSR, utils.MetaStatS, utils.MetaDestinations,
+ utils.MetaTimings, utils.MetaRSR, utils.MetaDestinations,
utils.MetaEmpty, utils.MetaExists, utils.MetaLessThan, utils.MetaLessOrEqual,
- utils.MetaGreaterThan, utils.MetaGreaterOrEqual, utils.MetaResources, utils.MetaEqual,
- utils.MetaAccount, utils.MetaNotEqual})
+ utils.MetaGreaterThan, utils.MetaGreaterOrEqual, utils.MetaEqual,
+ utils.MetaNotEqual})
var needsFieldName *utils.StringSet = utils.NewStringSet([]string{utils.MetaString, utils.MetaPrefix,
utils.MetaSuffix, utils.MetaTimings, utils.MetaDestinations, utils.MetaLessThan,
utils.MetaEmpty, utils.MetaExists, utils.MetaLessOrEqual, utils.MetaGreaterThan,
@@ -290,24 +290,6 @@ func (rf *FilterRule) CompileValues() (err error) {
FilterValue: valSplt[2],
}
}
- case utils.MetaAccount:
- //value for filter of type *accounts needs to be in the following form:
- //*gt:AccountID:ValueOfUsage
- rf.accountItems = make([]*itemFilter, len(rf.Values))
- for i, val := range rf.Values {
- valSplt := strings.Split(val, utils.InInFieldSep)
- if len(valSplt) != 3 {
- return fmt.Errorf("Value %s needs to contain at least 3 items", val)
- }
- // valSplt[0] filter type
- // valSplt[1] id of the Resource
- // valSplt[2] value to compare
- rf.accountItems[i] = &itemFilter{
- FilterType: valSplt[0],
- ItemID: valSplt[1],
- FilterValue: valSplt[2],
- }
- }
}
return
}
@@ -597,38 +579,6 @@ func (fltr *FilterRule) passGreaterThan(fielNameDP config.DataProvider, fieldVal
// return true, nil
// }
-// func (fltr *FilterRule) passAccountS(dP config.DataProvider,
-// accountS rpcclient.RpcClientConnection, tenant string) (bool, error) {
-// if accountS == nil || reflect.ValueOf(accountS).IsNil() {
-// return false, errors.New("Missing AccountS information")
-// }
-// for _, accItem := range fltr.accountItems {
-// //split accItem.ItemID in two accountID and actual filter
-// //AccountID.BalanceMap.*monetary[0].Value
-// splittedString := strings.SplitN(accItem.ItemID, utils.NestingSep, 2)
-// accID := splittedString[0]
-// filterID := splittedString[1]
-// var reply Account
-// if err := accountS.Call(utils.ApierV2GetAccount,
-// &utils.AttrGetAccount{Tenant: tenant, Account: accID}, &reply); err != nil {
-// return false, err
-// }
-// //compose the newFilter
-// fltr, err := NewFilterRule(accItem.FilterType,
-// utils.DynamicDataPrefix+filterID, []string{accItem.FilterValue})
-// if err != nil {
-// return false, err
-// }
-// dP, _ := reply.AsNavigableMap(nil)
-// if val, err := fltr.Pass(dP, nil, tenant); err != nil || !val {
-// //in case of error return false and error
-// //and in case of not pass return false and nil
-// return false, err
-// }
-// }
-// return true, nil
-// }
-
func (fltr *FilterRule) passEqualTo(fielNameDP config.DataProvider, fieldValuesDP []config.DataProvider) (bool, error) {
fldIf, err := config.GetDynamicInterface(fltr.FieldName, fielNameDP)
if err != nil {
@@ -654,41 +604,39 @@ func (fltr *FilterRule) passEqualTo(fielNameDP config.DataProvider, fieldValuesD
return false, nil
}
-func (fS *FilterS) getFieldNameDataProvider(initialDP config.DataProvider, fieldName string, tenant string) (dp config.DataProvider, err error) {
+func (fS *FilterS) getFieldNameDataProvider(initialDP config.DataProvider, fieldName *string, tenant string) (dp config.DataProvider, err error) {
switch {
- case strings.HasPrefix(fieldName, utils.MetaAccounts):
- //construct dataProvider from account and set it furthder
+ case strings.HasPrefix(*fieldName, utils.DynamicDataPrefix+utils.MetaAccounts):
+ //same of fieldName : ~*accounts.1001.BalanceMap.*monetary[0].Value
+ // split the field name in 3 parts
+ // fieldNameType (~*accounts), accountID(1001) and quried part (BalanceMap.*monetary[0].Value)
+ splitFldName := strings.SplitN(*fieldName, ".", 3)
+ if len(splitFldName) != 3 {
+ return nil, fmt.Errorf("invalid fieldname <%s>", *fieldName)
+ }
var account *Account
- //extract the AccountID from fieldName
if err = fS.ralSConns.Call(utils.ApierV2GetAccount,
- &utils.AttrGetAccount{Tenant: tenant, Account: "completeHereWithID"}, &account); err != nil {
+ &utils.AttrGetAccount{Tenant: tenant, Account: splitFldName[1]}, &account); err != nil {
return
}
+ //construct dataProvider from account and set it furthder
dp = config.NewObjectDP(account)
- case strings.HasPrefix(fieldName, utils.MetaResources):
- case strings.HasPrefix(fieldName, utils.MetaStats):
+ // remove from fieldname the fielNameType and the AccountID
+ *fieldName = utils.DynamicDataPrefix + splitFldName[2]
+ case strings.HasPrefix(*fieldName, utils.DynamicDataPrefix+utils.MetaResources):
+ case strings.HasPrefix(*fieldName, utils.DynamicDataPrefix+utils.MetaStats):
default:
dp = initialDP
}
return
}
-func (fS *FilterS) getFieldValueDataProviders(initialDP config.DataProvider, values []string, tenant string) (dp []config.DataProvider, err error) {
+func (fS *FilterS) getFieldValueDataProviders(initialDP config.DataProvider,
+ values []string, tenant string) (dp []config.DataProvider, err error) {
dp = make([]config.DataProvider, len(values))
- for i, val := range values {
- switch {
- case strings.HasPrefix(val, utils.MetaAccounts):
- var account *Account
- //extract the AccountID from fieldName
- if err = fS.ralSConns.Call(utils.ApierV2GetAccount,
- &utils.AttrGetAccount{Tenant: tenant, Account: "completeHereWithID"}, &account); err != nil {
- return
- }
- dp[i] = config.NewObjectDP(account)
- case strings.HasPrefix(val, utils.MetaResources):
- case strings.HasPrefix(val, utils.MetaStats):
- default:
- dp[i] = initialDP
+ for i := range values {
+ if dp[i], err = fS.getFieldNameDataProvider(initialDP, &values[i], tenant); err != nil {
+ return
}
}
return
diff --git a/general_tests/filters_it_test.go b/general_tests/filters_it_test.go
index 62c93027f..c0ffea8df 100644
--- a/general_tests/filters_it_test.go
+++ b/general_tests/filters_it_test.go
@@ -48,11 +48,11 @@ var sTestsFltr = []func(t *testing.T){
testV1FltrStartEngine,
testV1FltrRpcConn,
testV1FltrLoadTarrifPlans,
- testV1FltrAddStats,
- testV1FltrPupulateThreshold,
- testV1FltrGetThresholdForEvent,
- testV1FltrGetThresholdForEvent2,
- testV1FltrPopulateResources,
+ //testV1FltrAddStats,
+ //testV1FltrPupulateThreshold,
+ //testV1FltrGetThresholdForEvent,
+ //testV1FltrGetThresholdForEvent2,
+ //testV1FltrPopulateResources,
testV1FltrAccounts,
testV1FltrStopEngine,
}
@@ -519,15 +519,17 @@ func testV1FltrAccounts(t *testing.T) {
} else if resp != utils.OK {
t.Error("Unexpected reply returned", resp)
}
- //Add a filter of type *accounts and check if *monetary balance of account 1001 is minim 9 ( greater than 9)
- //we expect that the balance to be 10 so the filter should pass (10 > 9)
+ // Add a filter with fieldName taken value from account 1001
+ // and check if *monetary balance is minim 9 ( greater than 9)
+ // we expect that the balance to be 10 so the filter should pass (10 > 9)
filter := &engine.Filter{
Tenant: "cgrates.org",
ID: "FLTR_TH_Accounts",
Rules: []*engine.FilterRule{
{
- Type: "*account",
- Values: []string{"*gt:1001.BalanceMap.*monetary[0].Value:9"},
+ Type: "*gt",
+ FieldName: "~*accounts.1001.BalanceMap.*monetary[0].Value",
+ Values: []string{"9"},
},
},
}
@@ -538,7 +540,16 @@ func testV1FltrAccounts(t *testing.T) {
} else if result != utils.OK {
t.Error("Unexpected reply returned", result)
}
-
+ // Add a log action
+ attrsAA := &utils.AttrSetActions{ActionsId: "LOG", Actions: []*utils.TPAction{
+ {Identifier: utils.LOG},
+ }}
+ if err := fltrRpc.Call("ApierV2.SetActions", attrsAA, &result); err != nil && err.Error() != utils.ErrExists.Error() {
+ t.Error("Got error on ApierV2.SetActions: ", err.Error())
+ } else if result != utils.OK {
+ t.Errorf("Calling ApierV2.SetActions received: %s", result)
+ }
+ time.Sleep(10 * time.Millisecond)
//Add a threshold with filter from above and an inline filter for Account 1010
tPrfl := &engine.ThresholdProfile{
Tenant: "cgrates.org",
@@ -581,15 +592,17 @@ func testV1FltrAccounts(t *testing.T) {
}
// update the filter
- //Add a filter of type *accounts and check if *monetary balance of account 1001 is minim 11 ( greater than 11)
- //we expect that the balance to be 10 so the filter should not pass (10 > 11)
+ // Add a filter with fieldName taken value from account 1001
+ // and check if *monetary balance is is minim 11 ( greater than 11)
+ // we expect that the balance to be 10 so the filter should not pass (10 > 11)
filter = &engine.Filter{
Tenant: "cgrates.org",
ID: "FLTR_TH_Accounts",
Rules: []*engine.FilterRule{
{
- Type: "*account",
- Values: []string{"*gt:1001.BalanceMap.*monetary[0].Value:11"},
+ Type: "*gt",
+ FieldName: "~*accounts.1001.BalanceMap.*monetary[0].Value",
+ Values: []string{"11"},
},
},
}
diff --git a/general_tests/objectdp_test.go b/general_tests/objectdp_test.go
new file mode 100644
index 000000000..0126c9fe1
--- /dev/null
+++ b/general_tests/objectdp_test.go
@@ -0,0 +1,86 @@
+/*
+Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
+Copyright (C) ITsysCOM GmbH
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see
+*/
+package general_tests
+
+import (
+ "testing"
+
+ "github.com/cgrates/cgrates/config"
+ "github.com/cgrates/cgrates/engine"
+ "github.com/cgrates/cgrates/utils"
+)
+
+func TestAccountNewObjectDPFieldAsInterface(t *testing.T) {
+ acc := &engine.Account{
+ ID: "cgrates.org:1001",
+ BalanceMap: map[string]engine.Balances{
+ utils.MONETARY: []*engine.Balance{
+ {
+ Value: 20,
+ Weight: 10,
+ },
+ },
+ },
+ }
+ accDP := config.NewObjectDP(acc)
+ if data, err := accDP.FieldAsInterface([]string{"BalanceMap", "*monetary[0]", "Value"}); err != nil {
+ t.Error(err)
+ } else if data != 20. {
+ t.Errorf("Expected: %+v ,recived: %+v", 20., data)
+ }
+ if _, err := accDP.FieldAsInterface([]string{"BalanceMap", "*monetary[1]", "Value"}); err == nil ||
+ err.Error() != "index out of range" {
+ t.Error(err)
+ }
+ if _, err := accDP.FieldAsInterface([]string{"BalanceMap", "*monetary[0]", "InexistentField"}); err == nil ||
+ err != utils.ErrNotFound {
+ t.Error(err)
+ }
+}
+
+func TestAccountNewObjectDPFieldAsInterfaceFromCache(t *testing.T) {
+ acc := &engine.Account{
+ ID: "cgrates.org:1001",
+ BalanceMap: map[string]engine.Balances{
+ utils.MONETARY: []*engine.Balance{
+ {
+ Value: 20,
+ Weight: 10,
+ },
+ },
+ },
+ }
+ accDP := config.NewObjectDP(acc)
+
+ if data, err := accDP.FieldAsInterface([]string{"BalanceMap", "*monetary[0]", "Value"}); err != nil {
+ t.Error(err)
+ } else if data != 20. {
+ t.Errorf("Expected: %+v ,recived: %+v", 20., data)
+ }
+ // the value should be taken from cache
+ if data, err := accDP.FieldAsInterface([]string{"BalanceMap", "*monetary[0]", "Value"}); err != nil {
+ t.Error(err)
+ } else if data != 20. {
+ t.Errorf("Expected: %+v ,recived: %+v", 20., data)
+ }
+ if data, err := accDP.FieldAsInterface([]string{"BalanceMap", "*monetary[0]"}); err != nil {
+ t.Error(err)
+ } else if data != acc.BalanceMap[utils.MONETARY][0] {
+ t.Errorf("Expected: %+v ,recived: %+v", acc.BalanceMap[utils.MONETARY][0], data)
+ }
+}
diff --git a/utils/reflect.go b/utils/reflect.go
index 4cfdf32aa..79338b1b5 100644
--- a/utils/reflect.go
+++ b/utils/reflect.go
@@ -562,8 +562,8 @@ func ReflectFieldMethodInterface(obj interface{}, fldName string) (retIf interfa
if err != nil {
return nil, err
}
- if idx > v.Len() {
- return nil, fmt.Errorf("out of range")
+ if idx > v.Len()-1 {
+ return nil, fmt.Errorf("index out of range")
}
field = v.Index(idx)
default:
@@ -582,12 +582,17 @@ func ReflectFieldMethodInterface(obj interface{}, fldName string) (retIf interfa
if field.Type().NumOut() > 2 {
return nil, fmt.Errorf("invalid function called")
}
- errorInterface := reflect.TypeOf((*error)(nil)).Elem()
- if !field.Type().Out(1).Implements(errorInterface) {
- return nil, fmt.Errorf("invalid function called")
+ // the function have two parameters in return and check if the second is of type error
+ if field.Type().NumOut() == 2 {
+ errorInterface := reflect.TypeOf((*error)(nil)).Elem()
+ if !field.Type().Out(1).Implements(errorInterface) {
+ return nil, fmt.Errorf("invalid function called")
+ }
}
fields := field.Call([]reflect.Value{})
- //verify if error is not nil
+ if len(fields) == 2 && !fields[1].IsNil() {
+ return fields[0].Interface(), fields[1].Interface().(error)
+ }
return fields[0].Interface(), nil
}
}
diff --git a/utils/reflect_test.go b/utils/reflect_test.go
index f5bb50ff3..e4419c93b 100644
--- a/utils/reflect_test.go
+++ b/utils/reflect_test.go
@@ -699,6 +699,8 @@ type TestA struct {
StrField string
}
+type TestASlice []*TestA
+
func (_ *TestA) TestFunc() string {
return "This is a test function on a structure"
}
@@ -708,10 +710,10 @@ func (_ *TestA) TestFuncWithParam(param string) string {
}
func (_ *TestA) TestFuncWithError() (string, error) {
- return "TestFunction", nil
+ return "TestFuncWithError", nil
}
func (_ *TestA) TestFuncWithError2() (string, error) {
- return "TestFunction", ErrPartiallyExecuted
+ return "TestFuncWithError2", ErrPartiallyExecuted
}
func TestReflectFieldMethodInterface(t *testing.T) {
@@ -735,7 +737,11 @@ func TestReflectFieldMethodInterface(t *testing.T) {
ifValue, err = ReflectFieldMethodInterface(a, "TestFuncWithError")
if err != nil {
t.Error(err)
- } else if ifValue != "TestFunction" {
- t.Errorf("Expecting: TestFunction, received: %+v", ifValue)
+ } else if ifValue != "TestFuncWithError" {
+ t.Errorf("Expecting: TestFuncWithError, received: %+v", ifValue)
+ }
+ ifValue, err = ReflectFieldMethodInterface(a, "TestFuncWithError2")
+ if err == nil || err != ErrPartiallyExecuted {
+ t.Error(err)
}
}