/* * Copyright (c) 2021 InterDigital Communications, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package applications import ( "errors" "strconv" "strings" "sync" dkm "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-data-key-mgr" log "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger" redis "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-redis" ) const redisTable int = 0 const appMgrKey string = "apps:" const ( fieldId string = "id" fieldName string = "name" fieldMep string = "mep" fieldType string = "type" fieldPersist string = "persist" ) const ( EventAdd string = "EVENT-ADD" EventRemove string = "EVENT-REMOVE" EventFlush string = "EVENT-FLUSH" ) const ( TypeUser string = "USER" TypeSystem string = "SYSTEM" ) type Application struct { Id string Name string Mep string Type string Persist bool } type ApplicationStoreCfg struct { Name string Namespace string UpdateCb func(eventType string, eventData interface{}) RedisAddr string } type ApplicationStore struct { apps map[string]*Application rc *redis.Connector keyRoot string updateCb func(eventType string, eventData interface{}) mutex sync.Mutex } var SysAppNames []string = []string{"meep-app-enablement", "meep-ams", "meep-loc-serv", "meep-rnis", "meep-wais"} // NewApplicationStore - Creates and initialize an Application Store instance func NewApplicationStore(cfg *ApplicationStoreCfg) (as *ApplicationStore, err error) { // Validate params if cfg.Namespace == "" { return nil, errors.New("Missing namespace") } // Create new Application Store instance as = new(ApplicationStore) as.apps = make(map[string]*Application) as.keyRoot = dkm.GetKeyRoot(cfg.Namespace) + appMgrKey as.updateCb = cfg.UpdateCb // Connect to Redis DB as.rc, err = redis.NewConnector(cfg.RedisAddr, redisTable) if err != nil { log.Error("Failed connection to Application Store Redis DB. Error: ", err) return nil, err } log.Info("Connected to Application Store Redis DB") // Refresh app list as.Refresh() log.Info("Created Application Store") return as, nil } // Set - Create or update app entry in DB func (as *ApplicationStore) Set(app *Application) error { // Validate application if app == nil { return errors.New("nil application") } if app.Id == "" { return errors.New("Missing App Instance ID") } if app.Name == "" { return errors.New("Missing App Name") } if app.Mep == "" { return errors.New("Missing MEP Name") } if app.Type == "" { return errors.New("Missing App Type") } // Set entry err := as.setEntry(app) if err != nil { return err } // Invoke application update callback if as.updateCb != nil { as.updateCb(EventAdd, app.Id) } return nil } // Get - Return application with provided name func (as *ApplicationStore) Get(id string) (*Application, error) { as.mutex.Lock() defer as.mutex.Unlock() app, found := as.apps[id] if !found { return nil, errors.New("Entry not found") } return app, nil } // GetAll - Return all applications func (as *ApplicationStore) GetAll() ([]*Application, error) { as.mutex.Lock() defer as.mutex.Unlock() // Get list of apps var appList []*Application for _, app := range as.apps { appList = append(appList, app) } return appList, nil } // Del - Remove application with provided id func (as *ApplicationStore) Del(id string) error { // Delete entry err := as.deleteEntry(id) if err != nil { return err } // Invoke application update callback if as.updateCb != nil { as.updateCb(EventRemove, id) } return nil } // FlushAll - Remove all Application Store entries func (as *ApplicationStore) FlushNonPersistent() { // Get app list appList, err := as.GetAll() if err != nil { log.Error(err.Error()) return } // Delete all nonpersistent entries for _, app := range appList { if !app.Persist { _ = as.deleteEntry(app.Id) } } // Invoke application update callback if as.updateCb != nil { flushPersistent := false as.updateCb(EventFlush, flushPersistent) } } // FlushAll - Remove all Application Store entries func (as *ApplicationStore) Flush() { // Delete all entries _ = as.deleteAllEntries() // Invoke application update callback if as.updateCb != nil { flushPersistent := true as.updateCb(EventFlush, flushPersistent) } } // Refresh - Sync application cache with DB func (as *ApplicationStore) Refresh() { var appList []*Application as.mutex.Lock() defer as.mutex.Unlock() // Clear cache as.apps = make(map[string]*Application) // Get all applications from DB keyMatchStr := as.keyRoot + "*" err := as.rc.ForEachEntry(keyMatchStr, getApplication, &appList) if err != nil { log.Error("Failed to get all entries with error: ", err.Error()) return } // Fill cache for _, app := range appList { as.apps[app.Id] = app } } func (as *ApplicationStore) IsSysApp(appName string) bool { name := appName[strings.LastIndex(appName, "/")+1:] for _, sysAppName := range SysAppNames { if sysAppName == name { return true } } return false } func (as *ApplicationStore) setEntry(app *Application) error { as.mutex.Lock() defer as.mutex.Unlock() // Prepare data entry := make(map[string]interface{}) entry[fieldId] = app.Id entry[fieldName] = app.Name entry[fieldMep] = app.Mep entry[fieldType] = app.Type entry[fieldPersist] = strconv.FormatBool(app.Persist) // Update entry in DB key := as.keyRoot + app.Id err := as.rc.SetEntry(key, entry) if err != nil { log.Error("Failed to set entry with error: ", err.Error()) return err } // Cache entry as.apps[app.Id] = app return nil } func (as *ApplicationStore) deleteEntry(id string) error { as.mutex.Lock() defer as.mutex.Unlock() // Remove from cache delete(as.apps, id) // Remove from DB key := as.keyRoot + id err := as.rc.DelEntry(key) if err != nil { log.Error("Failed to delete entry for ", id, " with err: ", err.Error()) return err } return nil } func (as *ApplicationStore) deleteAllEntries() error { as.mutex.Lock() defer as.mutex.Unlock() // Clear cache as.apps = make(map[string]*Application) // Flush DB return as.rc.DBFlush(as.keyRoot) } func getApplication(key string, entry map[string]string, userData interface{}) error { appList := userData.(*[]*Application) app := createApplication(entry) *appList = append(*appList, app) return nil } func createApplication(entry map[string]string) *Application { app := new(Application) app.Id = entry[fieldId] app.Name = entry[fieldName] app.Mep = entry[fieldMep] app.Type = entry[fieldType] persist, err := strconv.ParseBool(entry[fieldPersist]) if err != nil { app.Persist = false } else { app.Persist = persist } return app }