| package apidAnalytics |
| |
| import ( |
| "database/sql" |
| "fmt" |
| "sync" |
| ) |
| |
| // Cache for scope uuid to org, env and tenantId information |
| var tenantCache map[string]tenant |
| |
| // RW lock for tenant map cache since the cache can be |
| // read while its being written to and vice versa |
| var tenantCachelock = sync.RWMutex{} |
| |
| // Cache for apiKey~tenantId to developer related information |
| var developerInfoCache map[string]developerInfo |
| |
| // RW lock for developerInfo map cache since the cache can be |
| // read while its being written to and vice versa |
| var developerInfoCacheLock = sync.RWMutex{} |
| |
| // Load data scope information into an in-memory cache so that |
| // for each record a DB lookup is not required |
| func createTenantCache() error { |
| // Lock before writing to the map as it has multiple readers |
| tenantCachelock.Lock() |
| defer tenantCachelock.Unlock() |
| tenantCache = make(map[string]tenant) |
| var org, env, tenantId, id string |
| |
| db := getDB() |
| rows, error := db.Query("SELECT env, org, scope, id FROM DATA_SCOPE") |
| |
| if error != nil { |
| return fmt.Errorf("Count not get datascope from "+ |
| "DB due to: %v", error) |
| } else { |
| defer rows.Close() |
| for rows.Next() { |
| rows.Scan(&env, &org, &tenantId, &id) |
| tenantCache[id] = tenant{Org: org, |
| Env: env, |
| TenantId: tenantId} |
| } |
| } |
| |
| log.Debugf("Count of data scopes in the cache: %d", len(tenantCache)) |
| return nil |
| } |
| |
| // Load data scope information into an in-memory cache so that |
| // for each record a DB lookup is not required |
| func createDeveloperInfoCache() error { |
| // Lock before writing to the map as it has multiple readers |
| developerInfoCacheLock.Lock() |
| defer developerInfoCacheLock.Unlock() |
| developerInfoCache = make(map[string]developerInfo) |
| var apiProduct, developerApp, developerEmail, developer sql.NullString |
| var tenantId, apiKey string |
| |
| db := getDB() |
| sSql := "SELECT mp.tenant_id, mp.appcred_id, ap.name," + |
| " a.name, d.username, d.email " + |
| "FROM APP_CREDENTIAL_APIPRODUCT_MAPPER as mp " + |
| "INNER JOIN API_PRODUCT as ap ON ap.id = mp.apiprdt_id " + |
| "INNER JOIN APP AS a ON a.id = mp.app_id " + |
| "INNER JOIN DEVELOPER as d ON d.id = a.developer_id;" |
| rows, error := db.Query(sSql) |
| |
| if error != nil { |
| return fmt.Errorf("Count not get developerInfo "+ |
| "from DB due to: %v", error) |
| } else { |
| defer rows.Close() |
| for rows.Next() { |
| rows.Scan(&tenantId, &apiKey, &apiProduct, |
| &developerApp, &developer, &developerEmail) |
| |
| keyForMap := getKeyForDeveloperInfoCache(tenantId, apiKey) |
| apiPrd := getValuesIgnoringNull(apiProduct) |
| devApp := getValuesIgnoringNull(developerApp) |
| dev := getValuesIgnoringNull(developer) |
| devEmail := getValuesIgnoringNull(developerEmail) |
| |
| developerInfoCache[keyForMap] = developerInfo{ |
| ApiProduct: apiPrd, |
| DeveloperApp: devApp, |
| DeveloperEmail: devEmail, |
| Developer: dev} |
| } |
| } |
| |
| log.Debugf("Count of apiKey~tenantId combinations "+ |
| "in the cache: %d", len(developerInfoCache)) |
| return nil |
| } |
| |
| // Returns Tenant Info given a scope uuid from the cache or by querying |
| // the DB directly based on useCachig config |
| func getTenantForScope(scopeuuid string) (tenant, dbError) { |
| if config.GetBool(useCaching) { |
| // acquire a read lock as this cache has 1 writer as well |
| tenantCachelock.RLock() |
| defer tenantCachelock.RUnlock() |
| ten, exists := tenantCache[scopeuuid] |
| if !exists { |
| reason := "No tenant found for this scopeuuid: " + scopeuuid |
| errorCode := "UNKNOWN_SCOPE" |
| // Incase of unknown scope, try to refresh the |
| // cache ansynchronously incase an update was missed or delayed |
| go createTenantCache() |
| return tenant{}, dbError{ |
| ErrorCode: errorCode, |
| Reason: reason} |
| } else { |
| return ten, dbError{} |
| } |
| } else { |
| var org, env, tenantId string |
| |
| db := getDB() |
| error := db.QueryRow("SELECT env, org, scope FROM DATA_SCOPE"+ |
| " where id = ?", scopeuuid).Scan(&env, &org, &tenantId) |
| |
| switch { |
| case error == sql.ErrNoRows: |
| reason := "No tenant found for this scopeuuid: " + scopeuuid |
| errorCode := "UNKNOWN_SCOPE" |
| return tenant{}, dbError{ |
| ErrorCode: errorCode, |
| Reason: reason} |
| case error != nil: |
| reason := error.Error() |
| errorCode := "INTERNAL_SEARCH_ERROR" |
| return tenant{}, dbError{ |
| ErrorCode: errorCode, |
| Reason: reason} |
| } |
| return tenant{ |
| Org: org, |
| Env: env, |
| TenantId: tenantId}, dbError{} |
| } |
| } |
| |
| // Returns Dveloper related info given an apiKey and tenantId |
| // from the cache or by querying the DB directly based on useCachig config |
| func getDeveloperInfo(tenantId string, apiKey string) developerInfo { |
| if config.GetBool(useCaching) { |
| keyForMap := getKeyForDeveloperInfoCache(tenantId, apiKey) |
| // acquire a read lock as this cache has 1 writer as well |
| tenantCachelock.RLock() |
| defer tenantCachelock.RUnlock() |
| devInfo, exists := developerInfoCache[keyForMap] |
| if !exists { |
| log.Warnf("No data found for for tenantId = %s"+ |
| " and apiKey = %s", tenantId, apiKey) |
| // Incase of unknown apiKey~tenantId, |
| // try to refresh the cache ansynchronously incase an update was missed or delayed |
| go createDeveloperInfoCache() |
| return developerInfo{} |
| } else { |
| return devInfo |
| } |
| } else { |
| var apiProduct, developerApp, developerEmail sql.NullString |
| var developer sql.NullString |
| |
| db := getDB() |
| sSql := "SELECT ap.name, a.name, d.username, d.email " + |
| "FROM APP_CREDENTIAL_APIPRODUCT_MAPPER as mp " + |
| "INNER JOIN API_PRODUCT as ap ON ap.id = mp.apiprdt_id " + |
| "INNER JOIN APP AS a ON a.id = mp.app_id " + |
| "INNER JOIN DEVELOPER as d ON d.id = a.developer_id " + |
| "where mp.tenant_id = ? and mp.appcred_id = ?;" |
| error := db.QueryRow(sSql, tenantId, apiKey). |
| Scan(&apiProduct, &developerApp, |
| &developer, &developerEmail) |
| |
| switch { |
| case error == sql.ErrNoRows: |
| log.Debugf("No data found for for tenantId = %s "+ |
| "and apiKey = %s", tenantId, apiKey) |
| return developerInfo{} |
| case error != nil: |
| log.Debugf("No data found for for tenantId = %s and "+ |
| "apiKey = %s due to: %v", tenantId, apiKey, error) |
| return developerInfo{} |
| } |
| |
| apiPrd := getValuesIgnoringNull(apiProduct) |
| devApp := getValuesIgnoringNull(developerApp) |
| dev := getValuesIgnoringNull(developer) |
| devEmail := getValuesIgnoringNull(developerEmail) |
| return developerInfo{ApiProduct: apiPrd, |
| DeveloperApp: devApp, |
| DeveloperEmail: devEmail, |
| Developer: dev} |
| } |
| } |
| |
| // Helper method to handle scanning null values in DB to empty string |
| func getValuesIgnoringNull(sqlValue sql.NullString) string { |
| if sqlValue.Valid { |
| return sqlValue.String |
| } else { |
| return "" |
| } |
| } |
| |
| // Build Key as a combination of tenantId and apiKey for the developerInfo Cache |
| func getKeyForDeveloperInfoCache(tenantId string, apiKey string) string { |
| return tenantId + "~" + apiKey |
| } |