using System; using System.Collections.Generic; using System.Linq; using System.Text; using Sdaleo; using System.Net; using Yaulw.Xml; using Yaulw.File; using CrossProduct.Core; using System.Data; using Sdaleo.Systems.SQLServer; using Sdaleo.Systems.Advantage; using Sdaleo.Systems; using Pluto.Api; namespace PlutoServer.MSL.Connectors { /// /// Responsible for cachine SystemAPIKeys to physical Databases/Connections /// Since we are now seperated from the instance, the practice needs to be looked up /// in the practice list table. The Practice List Table will contain UserApiKeys that we /// have to convert to SystemApiKeys. This cache will save us a lookup, as well as do the work for us /// public static class DBCache { private static Dictionary _LytecConnetions = new Dictionary(); private static Dictionary _MedisoftConnections = new Dictionary(); // Static Instance of the Current Configuration Data private static DBCacheDataStore s_DBCredentialCacheDataStore = new DBCacheDataStore(); private static XSerializer s_xmlserializer = new XSerializer(); private static ISReadWrite s_ISReadWrite = new ISReadWrite("DBCache.enc"); #region Construction static DBCache() { } #endregion #region Mobile Test Settings /// /// Is this computer a computer used for mobile testing? /// public static bool IsMachine_Used_ForTesting = false; /// /// Used for Unit Testing Purposes /// public static SQLServerCredential TestLytecUserDBCredential = null; /// /// Used for Unit Testing Purposes /// public static AdvantageCredential TestMedisoftDBCredential = null; #endregion #region AddSharedDataConnections (Public for Test Framework) /// /// Add a new SQL Server Credential to the datastore if it doesn't already exist /// /// true to save to the DataStore File public static void AddSharedDataConnectionToDataStore(SQLServerCredential credential, bool bSaveToFile) { // Check if exists DBCacheDataStore.Credential[] existing_creds = s_DBCredentialCacheDataStore.SQLServerCredentials.Credentials; foreach (DBCacheDataStore.Credential existing_cred in existing_creds) { if (String.Compare(credential.Server, existing_cred.SqlServer, true) == 0 && String.Compare(credential.Instance, existing_cred.SqlInstance, true) == 0) return; } // else Add to persisten store DBCacheDataStore.Credential cred = new DBCacheDataStore.Credential(); cred.SqlServer = credential.Server; cred.SqlInstance = credential.Instance; cred.SqlUser = credential.User; cred.SqlPassword = ConnStr.RetrieveValue("PASSWORD", credential.ConnectionString); s_DBCredentialCacheDataStore.SQLServerCredentials.AddCredential(cred); // Log this MSLSpecific.Logger.Info("Adding a new SQL Server Credential to the Datastore for Server:{0} Instance:{1}", cred.SqlServer, cred.SqlInstance); // Save to File if (bSaveToFile) SaveDataStore(); // Load all User Api Keys for new Connection IterateSharedConnectionAndLoadUserApiKeyConnections(credential); } /// /// Add a new Advantage Credential * Shared Credential * to the datastore if it doesn't already exist /// /// true to save to the DataStore File public static void AddSharedDataConnectionToDataStore(AdvantageCredential credential, bool bSaveToFile) { // Check if exists DBCacheDataStore.Credential[] existing_creds = s_DBCredentialCacheDataStore.AdvantageCredentials.Credentials; foreach (DBCacheDataStore.Credential existing_cred in existing_creds) { if (String.Compare(credential.DataSource, existing_cred.AdvDataSource, true) == 0) return; } // else Add to persisten store DBCacheDataStore.Credential cred = new DBCacheDataStore.Credential(); cred.AdvDataSource = credential.DataSource; cred.AdvIsRemote = (ConnStr.RetrieveValue("SERVERTYPE", credential.ConnectionString) == "REMOTE"); s_DBCredentialCacheDataStore.AdvantageCredentials.AddCredential(cred); // Log this MSLSpecific.Logger.Info("Adding a new Advantage Credential to the Datastore DataSource:{0} Remote:{1}", cred.AdvDataSource, cred.AdvIsRemote); // Save to File if (bSaveToFile) SaveDataStore(); // Load all User Api Keys for new Connection IterateSharedConnectionAndLoadUserApiKeyConnections(credential); } #endregion #region Persistent DBCache File Store (Internal) /// /// Read from the DataStore into user connections * Reads in all credentials from a file into SQLServer & Medisoft Connections * /// Additionally, pulls out UserApiKeys from the Practice List of the corresponding Databases /// /// true if file doesn't exist or if successfull read occured, false otherwise internal static bool ReadInDataStore() { try { string ISFileContent = s_ISReadWrite.ReadFromIS(); if (!String.IsNullOrEmpty(ISFileContent)) { // Ran into the issue that the data in the DBCache is total nonsense after decrypting, // not sure yet how to doeal with yet * however if that is the case * what else can we // do but ignore the input? try { ISFileContent = Encryption.DecryptText(ISFileContent); s_DBCredentialCacheDataStore = s_xmlserializer.ReadFromString(ISFileContent); } catch (Exception e) { // error occured parsing the file, ignore the read! PlutoService.AppLogError(String.Format("DBCache File is invalid. Ignoring and erasing the cache: {0}", e.Message)); s_ISReadWrite.WriteToIS(String.Empty); return true; } // Test to see if a single successful read occured bool bASuccessFullLytecReadOccured = false; // Iterate thru all the SQL Connections _LytecConnetions.Clear(); bool bLytecConnectionsFound = false; int nLytecCount = 0; foreach (DBCacheDataStore.Credential credential in s_DBCredentialCacheDataStore.SQLServerCredentials.Credentials) { if (credential != null) { bLytecConnectionsFound = true; SQLServerCredential tempCred = new SQLServerCredential(credential.SqlServer, credential.SqlInstance, "Lytec SharedData", credential.SqlUser, credential.SqlPassword); bool bSuccess = IterateSharedConnectionAndLoadUserApiKeyConnections(tempCred); if (bSuccess) { bASuccessFullLytecReadOccured = true; nLytecCount++; } } } if (bLytecConnectionsFound && !bASuccessFullLytecReadOccured) PlutoService.AppLogInfo("Not one valid Lytec Connection was found in the DataStore"); if(nLytecCount > 0) PlutoService.AppLogInfo(String.Format("Loaded {0} Lytec Shared Connection",nLytecCount)); // Test to see if a single successful read occured bool bASuccessFullMedisoftReadOccured = false; // Iterate thru all the Advantage Connections _MedisoftConnections.Clear(); bool bMedisoftConnectionsFound = false; int nMedisoftCount = 0; foreach (DBCacheDataStore.Credential credential in s_DBCredentialCacheDataStore.AdvantageCredentials.Credentials) { if (credential != null) { bMedisoftConnectionsFound = true; AdvantageCredential tempCred = new AdvantageCredential(credential.AdvDataSource, "SharedDataUser", "AndPassword", credential.AdvIsRemote ? AdvantageCredential.ServerType.REMOTE : AdvantageCredential.ServerType.LOCAL); bool bSuccess = IterateSharedConnectionAndLoadUserApiKeyConnections(tempCred); if (bSuccess) { bASuccessFullMedisoftReadOccured = true; nMedisoftCount++; } } } if (bMedisoftConnectionsFound && !bASuccessFullMedisoftReadOccured) PlutoService.AppLogInfo("Not one valid Medisoft Connection was found in the DataStore"); if (nMedisoftCount > 0) PlutoService.AppLogInfo(String.Format("Loaded {0} Medisoft Shared Connection", nMedisoftCount)); return (bASuccessFullLytecReadOccured || bASuccessFullMedisoftReadOccured); } } catch (Exception e) { PlutoService.AppLogError(String.Format("Error thrown reading DataStore: {0}", e.Message)); return false; } // No File exists, always return true return true; } /// /// Save user connnections to the DataStore any existing connections * Writes in all SQLServer & Medisoft Connectios to a file * /// Note: UserApiKeys are never saved only the paths/credentials of the Connection /// internal static void SaveDataStore() { string ISFileContent = s_xmlserializer.WriteToString(s_DBCredentialCacheDataStore); ISFileContent = Encryption.EncryptText(ISFileContent); s_ISReadWrite.WriteToIS(ISFileContent); } /// /// Load all SQL Server User Credentials from a Shared Connection /// /// internal static bool IterateSharedConnectionAndLoadUserApiKeyConnections(SQLServerCredential credential) { bool bSuccess = false; if (credential != null) { DBRetVal retVal = LytecConnector.GetPracticeList(credential); if (retVal.IsValid) { bSuccess = true; foreach (DataRow row in retVal.GetDataTableRetVal().Rows) { string UserApiKey = DataRet.Retrieve(row["UserAPIKey"]); if (!String.IsNullOrEmpty(UserApiKey)) { SystemAccessVerifier verifier = new SystemAccessVerifier(UserApiKey); if (!String.IsNullOrEmpty(verifier.SystemApiKey)) { string UserDB = DataRet.Retrieve(row["Database Name"]); string Password = ConnStr.RetrieveValue("PASSWORD", credential.ConnectionString); // We must ensure uniqueness. Somehow both Medisoft & Lytec can create scenarios // where they are not unique amongst themselves! strange, but true // Don't overwrite connections that are already loaded (invalid state) if (!_LytecConnetions.ContainsKey(verifier.SystemApiKey)) { MSLSpecific.Logger.Info("Loading ApiKey Connection for DataBase:{0}", UserDB); _LytecConnetions[verifier.SystemApiKey] = new SQLServerCredential(credential.Server, credential.Instance, UserDB, credential.User, Password); } } } } } } return bSuccess; } /// /// Load all Advantage User Credentials from a Shared Connection /// /// internal static bool IterateSharedConnectionAndLoadUserApiKeyConnections(AdvantageCredential credential) { bool bSuccess = false; if (credential != null) { DBRetVal retVal = MedisoftConnector.GetPracticeList(credential); if (retVal.IsValid) { bSuccess = true; foreach (DataRow row in retVal.GetDataTableRetVal().Rows) { string UserApiKey = DataRet.Retrieve(row["UserAPIKey"]); if (!String.IsNullOrEmpty(UserApiKey)) { SystemAccessVerifier verifier = new SystemAccessVerifier(UserApiKey); if (!String.IsNullOrEmpty(verifier.SystemApiKey)) { string UserDB = DataRet.Retrieve(row["Data Path"]); bool bIsRemote = (ConnStr.RetrieveValue("SERVERTYPE", credential.ConnectionString) == "REMOTE"); // We must ensure uniqueness. Somehow both Medisoft & Lytec can create scenarios // where they are not unique amongst themselves! strange, but true // Don't overwrite connections that are already loaded (invalid state) if(!_MedisoftConnections.ContainsKey(verifier.SystemApiKey)) { MSLSpecific.Logger.Info("Loading ApiKey Connection for DataBase:{0}", UserDB); _MedisoftConnections[verifier.SystemApiKey] = new AdvantageCredential(UserDB + "\\mwddf.add", "user", "password", bIsRemote ? AdvantageCredential.ServerType.REMOTE : AdvantageCredential.ServerType.LOCAL); } } } } } } return bSuccess; } #endregion #region Get Specific Connection internal static SQLServerCredential GetSQLServerConnection(string SystemApiKey) { IConnectDb connection = GetConnectionForSystemApiKey(SystemApiKey); if (connection is SQLServerCredential) return (SQLServerCredential)connection; else return null; } internal static AdvantageCredential GetAdvantageConnection(string SystemApiKey) { IConnectDb connection = GetConnectionForSystemApiKey(SystemApiKey); if (connection is AdvantageCredential) return (AdvantageCredential)connection; else return null; } internal static AdvantageCredential GetAdvantageConnection(string SystemApiKey, string User) { AdvantageCredential credentialSystem = GetAdvantageConnection(SystemApiKey); if (credentialSystem != null && !String.IsNullOrEmpty(User)) { // First Retrieve the Password for that user string SQL = String.Format("SELECT [Password] FROM [mwSEC] WHERE upper([Code]) = '{0}'", User.Replace("'", "''").ToUpper()); DB db = DB.Create(credentialSystem); DBRetVal retVal = db.ExecuteScalar(SQL); if (retVal.IsValid) { // Now Create a new Connection with the User and Password for Advantage // ~Medisoft attaches 'mwmw' to the username for DB Connections string pwd = retVal.GetScalarRetVal().Trim(); AdvantageCredential credentialUser = new AdvantageCredential(credentialSystem.DataSource, (User + "mwmw").ToUpper(), pwd.ToUpper(), credentialSystem.Type); return credentialUser; } } // Something Failed return null; } #endregion #region Get General Connection /// /// Looks up the System string in the Local User DB Connection cache and retrieves it, /// if found. otherwise throws System not properly installed error for (release builds, /// of this service). On Test machines it will always return something. /// /// SystemKey to check /// private static IConnectDb GetConnectionForSystemApiKey(string SystemApiKey) { if (!String.IsNullOrEmpty(SystemApiKey)) { if (_LytecConnetions.ContainsKey(SystemApiKey)) return _LytecConnetions[SystemApiKey]; if (_MedisoftConnections.ContainsKey(SystemApiKey)) return _MedisoftConnections[SystemApiKey]; // * Setup Any Debug/Test Connections * Used Internally Only if (IsMachine_Used_ForTesting) { if (ProductType.IsKeyLytec(SystemApiKey) && TestLytecUserDBCredential != null) return TestLytecUserDBCredential; else if (ProductType.IsKeyMedisoft(SystemApiKey) && TestMedisoftDBCredential != null) return TestMedisoftDBCredential; else throw new Exception("Failed to retrieve DBCredential for ApiKey. Test Machine not properly configured."); } else { // not internally recognized, something went wrong or just Lytec / Medisoft // calling in the wrong order * either way * nothing to return // returning null will just mean something else blows up, so might as well // throw an error and let the pluto api return false PlutoService.AppLogError("Failed to retrieve ApiKey. System not properly configured."); throw new Exception("Failed to retrieve ApiKey. System not properly configured."); } } return null; } #endregion } }