/* 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 Affero 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see */ package utils import ( "errors" "fmt" "reflect" "strconv" "strings" "sync" "time" ) // DataStorage is the DataProvider that can be updated type DataStorage interface { DataProvider Set(fldPath []string, val any) error Remove(fldPath []string) error GetKeys(nested bool, nesteedLimit int, prefix string) []string } // MapStorage is the basic dataStorage type MapStorage map[string]any // String returns the map as json string func (ms MapStorage) String() string { return ToJSON(ms) } // FieldAsInterface returns the value from the path func (ms MapStorage) FieldAsInterface(fldPath []string) (val any, err error) { if len(fldPath) == 0 { err = errors.New("empty field path") return } opath, sindx := GetPathIndexString(fldPath[0]) var has bool if val, has = ms[opath]; !has { err = ErrNotFound return } if len(fldPath) == 1 { if sindx == nil { return } switch rv := val.(type) { case []string: var indx int if indx, err = strconv.Atoi(*sindx); err != nil { return } if len(rv) <= indx { return nil, ErrNotFound } val = rv[indx] return case []any: var indx int if indx, err = strconv.Atoi(*sindx); err != nil { return } if len(rv) <= indx { return nil, ErrNotFound } val = rv[indx] return case DataProvider: return rv.FieldAsInterface(append([]string{*sindx}, fldPath[1:]...)) case map[string]any: return MapStorage(rv).FieldAsInterface(append([]string{*sindx}, fldPath[1:]...)) default: } var indx int if indx, err = strconv.Atoi(*sindx); err != nil { return } // only if all above fails use reflect: vr := reflect.ValueOf(val) if vr.Kind() == reflect.Ptr { vr = vr.Elem() } if vr.Kind() != reflect.Slice && vr.Kind() != reflect.Array { return nil, ErrNotFound } if indx >= vr.Len() { return nil, ErrNotFound } return vr.Index(indx).Interface(), nil } if sindx == nil { switch dp := ms[fldPath[0]].(type) { case DataProvider: return dp.FieldAsInterface(fldPath[1:]) case map[string]any: return MapStorage(dp).FieldAsInterface(fldPath[1:]) default: err = ErrWrongPath return } } switch dp := ms[opath].(type) { case DataProvider: return dp.FieldAsInterface(append([]string{*sindx}, fldPath[1:]...)) case map[string]any: return MapStorage(dp).FieldAsInterface(append([]string{*sindx}, fldPath[1:]...)) case []DataProvider: var indx int if indx, err = strconv.Atoi(*sindx); err != nil { return } if len(dp) <= indx { return nil, ErrNotFound } return dp[indx].FieldAsInterface(fldPath[1:]) case []MapStorage: var indx int if indx, err = strconv.Atoi(*sindx); err != nil { return } if len(dp) <= indx { return nil, ErrNotFound } return dp[indx].FieldAsInterface(fldPath[1:]) case []map[string]any: var indx int if indx, err = strconv.Atoi(*sindx); err != nil { return } if len(dp) <= indx { return nil, ErrNotFound } return MapStorage(dp[indx]).FieldAsInterface(fldPath[1:]) case []any: var indx int if indx, err = strconv.Atoi(*sindx); err != nil { return } if len(dp) <= indx { return nil, ErrNotFound } switch ds := dp[indx].(type) { case DataProvider: return ds.FieldAsInterface(fldPath[1:]) case map[string]any: return MapStorage(ds).FieldAsInterface(fldPath[1:]) default: } default: } err = ErrNotFound // xml compatible val = nil return } // FieldAsString returns the value from path as string func (ms MapStorage) FieldAsString(fldPath []string) (str string, err error) { var val any if val, err = ms.FieldAsInterface(fldPath); err != nil { return } return IfaceAsString(val), nil } // Set sets the value at the given path func (ms MapStorage) Set(fldPath []string, val any) (err error) { if len(fldPath) == 0 { return ErrWrongPath } if len(fldPath) == 1 { ms[fldPath[0]] = val return } if _, has := ms[fldPath[0]]; !has { nMap := MapStorage{} ms[fldPath[0]] = nMap return nMap.Set(fldPath[1:], val) } switch dp := ms[fldPath[0]].(type) { case DataStorage: return dp.Set(fldPath[1:], val) case map[string]any: return MapStorage(dp).Set(fldPath[1:], val) default: return ErrWrongPath } } // GetKeys returns all the keys from map func (ms MapStorage) GetKeys(nested bool, nestedLimit int, prefix string) (keys []string) { if prefix != EmptyString { prefix += NestingSep } if !nested { // this is a special case for the filter matching where we have the full map: /* ms:=MapStorage{ "*req":MapStorage{ ... }, "*opts":MapStorage{ ... }, } when nested is false we should stiil look inside `*req` and `*opts` but only in the first level of them */ if nestedLimit <= 1 { keys = make([]string, 0, len(ms)) for k := range ms { keys = append(keys, prefix+k) } return } for k, v := range ms { // in case of nested on false we take in consideraton the nestedLimit //keys = append(keys, prefix+k) switch rv := v.(type) { // and for performance we only take in consideration a limited set of types for nested false case DataStorage: keys = append(keys, rv.GetKeys(nested, nestedLimit-1, prefix+k)...) case map[string]any: keys = append(keys, MapStorage(rv).GetKeys(nested, nestedLimit-1, prefix+k)...) case nil, int, int32, int64, uint32, uint64, bool, float32, float64, []uint8, time.Duration, time.Time, string: keys = append(keys, prefix+k) default: keys = append(keys, prefix+k) } } return } for k, v := range ms { //keys = append(keys, prefix+k) switch rv := v.(type) { case DataStorage: keys = append(keys, rv.GetKeys(nested, nestedLimit, prefix+k)...) case map[string]any: keys = append(keys, MapStorage(rv).GetKeys(nested, nestedLimit, prefix+k)...) case []MapStorage: for i, dp := range rv { pref := prefix + k + fmt.Sprintf("[%v]", i) // keys = append(keys, pref) keys = append(keys, dp.GetKeys(nested, nestedLimit, pref)...) } case []DataStorage: for i, dp := range rv { pref := prefix + k + fmt.Sprintf("[%v]", i) // keys = append(keys, pref) keys = append(keys, dp.GetKeys(nested, nestedLimit, pref)...) } case []map[string]any: for i, dp := range rv { pref := prefix + k + fmt.Sprintf("[%v]", i) // keys = append(keys, pref) keys = append(keys, MapStorage(dp).GetKeys(nested, nestedLimit, pref)...) } case []any: for i := range rv { keys = append(keys, prefix+k+fmt.Sprintf("[%v]", i)) } case []string: for i := range rv { keys = append(keys, prefix+k+fmt.Sprintf("[%v]", i)) } case nil, int, int32, int64, uint32, uint64, bool, float32, float64, []uint8, time.Duration, time.Time, string: keys = append(keys, prefix+k) default: // ToDo:should not be called keys = append(keys, getPathFromValue(reflect.ValueOf(v), prefix+k+NestingSep)...) } } return } // Remove removes the item at path func (ms MapStorage) Remove(fldPath []string) (err error) { if len(fldPath) == 0 { return ErrWrongPath } var val any var has bool if val, has = ms[fldPath[0]]; !has { return // ignore (already removed) } if len(fldPath) == 1 { delete(ms, fldPath[0]) return } switch dp := val.(type) { case DataStorage: return dp.Remove(fldPath[1:]) case map[string]any: return MapStorage(dp).Remove(fldPath[1:]) default: return ErrWrongPath } } func (ms MapStorage) Clone() (msClone MapStorage) { if ms == nil { return } msClone = make(MapStorage, len(ms)) for k, v := range ms { msClone[k] = v } return } // NewSecureMapStorage constructs a new SecureMapStorage func NewSecureMapStorage() *SecureMapStorage { return &SecureMapStorage{ ms: make(MapStorage), } } // SecureMapStorage is a MapStorage with secure read/writes // useful for example as cache for various parts type SecureMapStorage struct { sync.RWMutex ms MapStorage } // String returns the JNSON representation of MapStorage func (sm *SecureMapStorage) String() (s string) { sm.RLock() s = sm.ms.String() sm.RUnlock() return } // FieldAsInterface returns the value at the path specified func (sm *SecureMapStorage) FieldAsInterface(fldPath []string) (val any, err error) { sm.RLock() val, err = sm.ms.FieldAsInterface(fldPath) sm.RUnlock() return } // FieldAsString returns the value at the path specified casted as string func (sm *SecureMapStorage) FieldAsString(fldPath []string) (s string, err error) { sm.RLock() s, err = sm.ms.FieldAsString(fldPath) sm.RUnlock() return } // Set will set the value at path func (sm *SecureMapStorage) Set(fldPath []string, val any) (err error) { sm.Lock() err = sm.ms.Set(fldPath, val) sm.Unlock() return } // GetKeys returns a list of keys for the prefix func (sm *SecureMapStorage) GetKeys(nested bool, nestedLimit int, prefix string) (keys []string) { sm.RLock() keys = sm.ms.GetKeys(nested, nestedLimit, prefix) sm.RUnlock() return } // Remove will remove based on field path func (sm *SecureMapStorage) Remove(fldPath []string) (err error) { sm.Lock() err = sm.ms.Remove(fldPath) sm.Unlock() return } // Clone returns a clone of sm func (sm *SecureMapStorage) Clone() (smClone *SecureMapStorage) { sm.RLock() smClone = new(SecureMapStorage) smClone.ms = sm.ms.Clone() sm.RUnlock() return } // used only in extreme cases where the dataprovider is an object that doesn't implement the dataStorage interface func getPathFromValue(in reflect.Value, prefix string) (out []string) { switch in.Kind() { case reflect.Ptr: return getPathFromValue(in.Elem(), prefix) case reflect.Array, reflect.Slice: prefix = strings.TrimSuffix(prefix, NestingSep) for i := 0; i < in.Len(); i++ { pref := fmt.Sprintf("%s[%v]", prefix, i) // out = append(out, pref) out = append(out, getPathFromValue(in.Index(i), pref+NestingSep)...) } case reflect.Map: iter := in.MapRange() for iter.Next() { pref := prefix + iter.Key().String() // out = append(out, pref) out = append(out, getPathFromValue(iter.Value(), pref+NestingSep)...) } case reflect.Struct: inType := in.Type() for i := 0; i < in.NumField(); i++ { pref := prefix + inType.Field(i).Name // out = append(out, pref) out = append(out, getPathFromValue(in.Field(i), pref+NestingSep)...) } case reflect.Invalid, reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.String, reflect.Chan, reflect.Func, reflect.UnsafePointer, reflect.Interface: out = append(out, strings.TrimSuffix(prefix, NestingSep)) default: } return }