/* * 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 swaggerapimgr import ( "encoding/json" "errors" "io/ioutil" "net/url" "os" "sort" "strings" log "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger" mq "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-mq" ) const apiDir = "/api" const userApiDir = "/user-api" const modulePlatformCtrl = "meep-platform-ctrl" const moduleSandboxCtrl = "meep-sandbox-ctrl" // MQ payload fields const mqFieldModule = "module" const mqFieldMepName = "mep" const mqFieldApiList = "apilist" type SwaggerApiList struct { isMep bool Apis []SwaggerApi UserApis []SwaggerApi } type SwaggerApi struct { Name string Url string } type SwaggerApiMgr struct { moduleName string sandboxName string mepName string isProvider bool isAggregator bool svcUrl string msgQueue *mq.MsgQueue handlerId int apiMap map[string]*SwaggerApiList } // NewSwaggerApiMgr - Creates and initialize a Swagger API Manager instance func NewSwaggerApiMgr(moduleName string, sandboxName string, mepName string, msgQueue *mq.MsgQueue) (sam *SwaggerApiMgr, err error) { // Create new Swagger API Manager instance sam = new(SwaggerApiMgr) sam.moduleName = moduleName sam.sandboxName = sandboxName sam.mepName = mepName sam.msgQueue = msgQueue sam.handlerId = -1 sam.apiMap = make(map[string]*SwaggerApiList) // Get Host Url hostUrl, err := url.Parse(strings.TrimSpace(os.Getenv("MEEP_HOST_URL"))) if err != nil { hostUrl = new(url.URL) } log.Info("MEEP_HOST_URL: ", hostUrl) // Get Service Path svcPath := strings.TrimSpace(os.Getenv("MEEP_SVC_PATH")) log.Info("MEEP_SVC_PATH: ", svcPath) // Create full service url sam.svcUrl = hostUrl.String() if sandboxName != "" { sam.svcUrl += "/" + sandboxName } if mepName != "" { sam.svcUrl += "/" + mepName } sam.svcUrl += svcPath log.Info("Service URL: ", sam.svcUrl) log.Info("Created Swagger API Manager") return sam, nil } // NewSwaggerApiMgr - Creates and initialize a Swagger API Manager instance for UE application and/or MEC application func NewSwaggerApiMgr_1(moduleName string, mepName string, msgQueue *mq.MsgQueue, appHostUrl string, appSvcPath string) (sam *SwaggerApiMgr, err error) { // Create new Swagger API Manager instance sam = new(SwaggerApiMgr) sam.moduleName = moduleName sam.sandboxName = "" sam.mepName = mepName sam.msgQueue = msgQueue sam.handlerId = -1 sam.apiMap = make(map[string]*SwaggerApiList) // Get Host Url hostUrl, err := url.Parse(appHostUrl) if err != nil { hostUrl = new(url.URL) } log.Info("MEEP_HOST_URL: ", hostUrl) // Get Service Path svcPath := appSvcPath log.Info("MEEP_SVC_PATH: ", svcPath) // Create full service url sam.svcUrl = hostUrl.String() if mepName != "" { sam.svcUrl += "/" + mepName } sam.svcUrl += svcPath log.Info("Service URL: ", sam.svcUrl) log.Info("Created Swagger API Manager") return sam, nil } // Start - Start Swagger API message Handler func (sam *SwaggerApiMgr) Start(isProvider bool, isAggregator bool) error { var err error sam.isProvider = isProvider sam.isAggregator = isAggregator // Make sure handler is not running if sam.handlerId != -1 { return errors.New("Handler already running") } // Register Message Queue handler handler := mq.MsgHandler{Handler: sam.msgHandler, UserData: nil} sam.handlerId, err = sam.msgQueue.RegisterHandler(handler) if err != nil { log.Error("Failed to listen for sandbox updates: ", err.Error()) return err } // If aggregator, send API update Request to providers if sam.isAggregator { _ = sam.sendApiRequest() } return nil } // Stop - Stop Swagger API message Handler func (sam *SwaggerApiMgr) Stop() error { // Make sure handler is running if sam.handlerId == -1 { return errors.New("Handler not running") } // Unregister Message Queue handler sam.msgQueue.UnregisterHandler(sam.handlerId) sam.handlerId = -1 return nil } // FlushMepApis - Flush all MEP APIs func (sam *SwaggerApiMgr) FlushMepApis() error { // Remove MEP APIs from API map for key, apiList := range sam.apiMap { if apiList.isMep { delete(sam.apiMap, key) } } // Refresh API lists sam.refreshApiLists() return nil } // AddApis - Send message to inform listeners of added APIs func (sam *SwaggerApiMgr) AddApis() error { // Get service APIs from filesystem apiFiles, err := ioutil.ReadDir(apiDir) if err != nil { log.Error("Failed to read API dir with error: ", err.Error()) // Should never happen. For UT, return nil. return nil } userApiFiles, err := ioutil.ReadDir(userApiDir) if err != nil { log.Error("Failed to read API dir with error: ", err.Error()) // Should never happen. For UT, return nil. return nil } // Get MEP prefix mepPrefix := "" if sam.mepName != "" { mepPrefix = sam.mepName + " - " } // Populate API lists var apiList SwaggerApiList for _, file := range apiFiles { var api SwaggerApi api.Name = mepPrefix + file.Name() api.Url = sam.svcUrl + "/api/" + file.Name() apiList.Apis = append(apiList.Apis, api) } for _, file := range userApiFiles { var api SwaggerApi api.Name = mepPrefix + file.Name() api.Url = sam.svcUrl + "/user-api/" + file.Name() apiList.UserApis = append(apiList.UserApis, api) } // Publish new API list return sam.publishApiUpdate(&apiList) } // RemoveApis - Send message to inform listeners of removed APIs func (sam *SwaggerApiMgr) RemoveApis() error { // Publish empty API list return sam.publishApiUpdate(nil) } func (sam *SwaggerApiMgr) publishApiUpdate(apiList *SwaggerApiList) error { // Populate API list apiListStr := "" if apiList != nil { apiListStr = convertSwaggerApiListToJson(apiList) } // Send message to inform listeners of API update var msg *mq.Msg if sam.sandboxName == "" { msg = sam.msgQueue.CreateMsg(mq.MsgApiUpdate, modulePlatformCtrl, mq.TargetAll) } else { msg = sam.msgQueue.CreateMsg(mq.MsgApiUpdate, moduleSandboxCtrl, sam.sandboxName) } msg.Payload[mqFieldModule] = sam.moduleName msg.Payload[mqFieldMepName] = sam.mepName msg.Payload[mqFieldApiList] = apiListStr log.Debug("TX MSG: ", mq.PrintMsg(msg)) err := sam.msgQueue.SendMsg(msg) if err != nil { log.Error("Failed to send message. Error: ", err.Error()) return err } return nil } func (sam *SwaggerApiMgr) sendApiRequest() error { var msg *mq.Msg if sam.sandboxName == "" { msg = sam.msgQueue.CreateMsg(mq.MsgApiRequest, mq.TargetAll, mq.TargetAll) } else { msg = sam.msgQueue.CreateMsg(mq.MsgApiRequest, mq.TargetAll, sam.sandboxName) } log.Debug("TX MSG: ", mq.PrintMsg(msg)) err := sam.msgQueue.SendMsg(msg) if err != nil { log.Error("Failed to send message. Error: ", err.Error()) return err } return nil } // Message Queue handler func (sam *SwaggerApiMgr) msgHandler(msg *mq.Msg, userData interface{}) { switch msg.Message { case mq.MsgApiUpdate: if sam.isAggregator { log.Debug("RX MSG: ", mq.PrintMsg(msg)) moduleName := msg.Payload[mqFieldModule] mepName := msg.Payload[mqFieldMepName] apiListStr := msg.Payload[mqFieldApiList] apiList := convertJsonToSwaggerApiList(apiListStr) if apiList == nil { apiList = new(SwaggerApiList) } sam.processApiUpdate(moduleName, mepName, apiList) } case mq.MsgApiRequest: if sam.isProvider { log.Debug("RX MSG: ", mq.PrintMsg(msg)) sam.processApiRequest() } default: } } func (sam *SwaggerApiMgr) processApiRequest() { // Retieve & send APIs _ = sam.AddApis() } func (sam *SwaggerApiMgr) processApiUpdate(moduleName string, mepName string, apiList *SwaggerApiList) { // Validate params if moduleName == "" { log.Error("Invalid module name") return } if apiList == nil { log.Error("apiList == nil") return } // Update module API list mepPrefix := "" apiList.isMep = false if mepName != "" { mepPrefix = mepName + "-" apiList.isMep = true } sam.apiMap[mepPrefix+moduleName] = apiList // Refresh API lists sam.refreshApiLists() } func (sam *SwaggerApiMgr) refreshApiLists() { // Get ordered api lists var orderedApiList []SwaggerApi var orderedUserApiList []SwaggerApi for _, apiList := range sam.apiMap { orderedApiList = append(orderedApiList, apiList.Apis...) orderedUserApiList = append(orderedUserApiList, apiList.UserApis...) } sortApiList(orderedApiList) sortApiList(orderedUserApiList) // Update index.html file API lists _ = updateApiListInFile("/swagger/index.html", orderedApiList) _ = updateApiListInFile("/user-swagger/index.html", orderedUserApiList) } func updateApiListInFile(filename string, apiList []SwaggerApi) error { matchPrefix := " url" apiListNone := " url: \"\"," apiListPrefix := " urls: [" apiListPostfix := " ]," // Generate API list string to replace in file apiListStr := apiListNone if len(apiList) > 0 { apiListStr = apiListPrefix for _, api := range apiList { // Replace http/https prefix from URL with browser location protocol url := strings.TrimPrefix(api.Url, "https:") url = strings.TrimPrefix(url, "http:") apiListStr += "{\"name\": \"" + api.Name + "\", \"url\": location.protocol + \"" + url + "\"}," } apiListStr += apiListPostfix } log.Debug("apiListStr: ", apiListStr) // Read input file input, err := ioutil.ReadFile(filename) if err != nil { log.Error("Failed to read file with err: ", err.Error()) return err } // Replace API list line lines := strings.Split(string(input), "\n") apiListUpdated := false for i, line := range lines { if strings.HasPrefix(line, matchPrefix) { lines[i] = apiListStr apiListUpdated = true break } } // Write file if updated if apiListUpdated { output := strings.Join(lines, "\n") err = ioutil.WriteFile(filename, []byte(output), 0644) if err != nil { log.Error("Failed to write file with err: ", err.Error()) return err } } return nil } func convertSwaggerApiListToJson(apiList *SwaggerApiList) string { jsonInfo, err := json.Marshal(*apiList) if err != nil { log.Error(err.Error()) return "" } return string(jsonInfo) } func convertJsonToSwaggerApiList(jsonInfo string) *SwaggerApiList { var obj SwaggerApiList err := json.Unmarshal([]byte(jsonInfo), &obj) if err != nil { log.Error(err.Error()) return nil } return &obj } func sortApiList(apiList []SwaggerApi) { sort.Slice(apiList, func(i, j int) bool { return apiList[i].Name < apiList[j].Name }) }