using CredentialManager.Models; using DataConnection.EF; using DataConnection.Interfaces; using DataConnection.REST.Interfaces; using DataConnection.REST.Implementations; using DataConnection.REST.Configuration; using DataConnection.CredentialManagement.Interfaces; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System.Security.Cryptography; using System.Text; namespace Data_Coupler.Services { /// /// Classe per tracciare i client REST cached con le loro credenziali /// internal class CachedRestClient { public IRestServiceClient Client { get; set; } = null!; public string CredentialsHash { get; set; } = string.Empty; public DateTime CachedAt { get; set; } } /// /// Factory service per creare istanze di DatabaseManager e RestClient /// public interface IDataConnectionFactory { /// /// Crea un DatabaseManager per la credenziale specificata /// Task CreateDatabaseManagerAsync(string credentialName); /// /// Crea un RestServiceClient per la credenziale specificata /// Task CreateRestServiceClientAsync(string credentialName); /// /// Crea un RestMetadataDiscovery per la credenziale specificata /// Task CreateRestMetadataDiscoveryAsync(string credentialName); /// /// Pulisce la cache del client REST per una credenziale specifica /// void ClearRestClientCache(string credentialName); /// /// Pulisce tutta la cache dei client REST /// void ClearAllRestClientCache(); } public class DataConnectionFactory : IDataConnectionFactory { private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; private readonly IDataConnectionCredentialService _credentialService; // Cache intelligente per client REST che mantiene lo stato di autenticazione ma verifica le credenziali private readonly Dictionary _restClientCache = new(); public DataConnectionFactory(IServiceProvider serviceProvider, ILogger logger, IDataConnectionCredentialService credentialService) { _serviceProvider = serviceProvider; _logger = logger; _credentialService = credentialService; }public async Task CreateDatabaseManagerAsync(string credentialName) { try { var credential = await _credentialService.GetDatabaseCredentialAsync(credentialName); if (credential == null) { throw new ArgumentException($"Credenziale database '{credentialName}' non trovata"); } // Per ODBC, usa OdbcDatabaseManager direttamente (EF Core non supporta ODBC) if (credential.DatabaseType == DatabaseType.Odbc) { var connectionString = CredentialManager.Models.ConnectionStringBuilder.BuildConnectionString(credential); _logger.LogInformation("Creando OdbcDatabaseManager con connection string per {CredentialName}", credentialName); return new DataConnection.DB.OdbcDatabaseManager(connectionString); } // Per OLE DB, usa OleDbDatabaseManager direttamente (EF Core non supporta OLE DB) if (credential.DatabaseType == DatabaseType.OleDb) { var connectionString = CredentialManager.Models.ConnectionStringBuilder.BuildConnectionString(credential); _logger.LogInformation("Creando OleDbDatabaseManager con connection string per {CredentialName}", credentialName); return new DataConnection.DB.OleDbDatabaseManager(connectionString); } // Per altri database, usa EFCoreDatabaseManager var dbManagerOptions = await _credentialService.GetDbManagerOptionsAsync(credential.Name); return new EFCoreDatabaseManager(dbManagerOptions); } catch (Exception ex) { _logger.LogError(ex, "Errore nella creazione del DatabaseManager per {CredentialName}", credentialName); throw; } } public async Task CreateRestServiceClientAsync(string credentialName) { try { // SEMPRE recupera le credenziali fresh dal database per assicurare che siano aggiornate var credential = await _credentialService.GetRestApiCredentialAsync(credentialName); if (credential == null) { throw new ArgumentException($"Credenziale REST API '{credentialName}' non trovata"); } // Calcola l'hash delle credenziali correnti var currentCredentialsHash = CalculateCredentialsHash(credential); // Controlla se abbiamo un client cached con le stesse credenziali if (_restClientCache.TryGetValue(credentialName, out var cachedClient)) { if (cachedClient.CredentialsHash == currentCredentialsHash) { _logger.LogInformation("Utilizzando client REST cached con credenziali verificate per {CredentialName}", credentialName); return cachedClient.Client; } else { _logger.LogInformation("Credenziali cambiate, rimuovendo client cached per {CredentialName}", credentialName); _restClientCache.Remove(credentialName); } } var options = new RestServiceOptions { BaseUrl = credential.BaseUrl, Username = credential.Username, Password = credential.Password, TimeoutSeconds = credential.TimeoutSeconds, IgnoreSslErrors = credential.IgnoreSslErrors }; // Mapping specifico per tipo di servizio switch (credential.ServiceType) { case RestServiceType.Salesforce: // Per Salesforce usiamo i campi specifici ClientId e ClientSecret options.ApiKey = credential.ClientId; // ClientId -> ApiKey options.AuthToken = credential.ClientSecret; // ClientSecret -> AuthToken options.SalesforceGrantType = credential.GrantType; _logger.LogInformation("Salesforce mapping - GrantType: {GrantType}, ClientId: '{ClientId}', ClientSecret: {HasSecret}, Username: '{Username}', Password: {HasPassword}", credential.GrantType, credential.ClientId, !string.IsNullOrEmpty(credential.ClientSecret), credential.Username, !string.IsNullOrEmpty(credential.Password)); break; case RestServiceType.SapB1ServiceLayer: // Per SAP B1 usiamo il CompanyDatabase come ApiKey options.ApiKey = credential.CompanyDatabase; options.AuthToken = credential.AuthToken; _logger.LogInformation("SAP B1 mapping - CompanyDB: {CompanyDB}, Username: {Username}", credential.CompanyDatabase, credential.Username); break; default: // Per servizi generici usiamo ApiKey e AuthToken direttamente options.ApiKey = credential.ApiKey; options.AuthToken = credential.AuthToken; _logger.LogInformation("Generic mapping - ApiKey: {ApiKey}, AuthToken: {HasToken}, Username: {Username}", credential.ApiKey, !string.IsNullOrEmpty(credential.AuthToken), credential.Username); break; } var client = credential.ServiceType switch { RestServiceType.SapB1ServiceLayer => new SapB1ServiceClient(options), RestServiceType.Salesforce => CreateSalesforceClient(options), RestServiceType.Generic => CreateGenericRestClient(options), _ => throw new NotSupportedException($"Tipo di servizio REST non supportato: {credential.ServiceType}") }; // Salva il client nella cache con l'hash delle credenziali _restClientCache[credentialName] = new CachedRestClient { Client = client, CredentialsHash = currentCredentialsHash, CachedAt = DateTime.UtcNow }; _logger.LogInformation("Client REST creato e cached con credenziali aggiornate per {CredentialName}", credentialName); return client; } catch (Exception ex) { _logger.LogError(ex, "Errore nella creazione del RestServiceClient per {CredentialName}", credentialName); throw; } } public async Task CreateRestMetadataDiscoveryAsync(string credentialName) { try { // Crea un nuovo client REST con credenziali aggiornate var restClient = await CreateRestServiceClientAsync(credentialName); // I service client giĆ  implementano IRestMetadataDiscovery if (restClient is IRestMetadataDiscovery metadataDiscovery) { _logger.LogInformation("Utilizzando client REST per metadata discovery con credenziali aggiornate per {CredentialName}", credentialName); return metadataDiscovery; } var credential = await _credentialService.GetRestApiCredentialAsync(credentialName); throw new NotSupportedException($"Il servizio REST {credential?.ServiceType} non supporta il metadata discovery"); } catch (Exception ex) { _logger.LogError(ex, "Errore nella creazione del RestMetadataDiscovery per {CredentialName}", credentialName); throw; } } private IRestServiceClient CreateGenericRestClient(RestServiceOptions options) { var httpClientFactory = _serviceProvider.GetRequiredService(); var httpClient = httpClientFactory.CreateClient(); return new BaseRestServiceClient(httpClient, options); } private IRestServiceClient CreateSalesforceClient(RestServiceOptions options) { var httpClientFactory = _serviceProvider.GetRequiredService(); var httpClient = httpClientFactory.CreateClient(); var loggerFactory = _serviceProvider.GetService(); var sfLogger = loggerFactory?.CreateLogger(); return new DataConnection.REST.Implementations.SalesforceServiceClient(httpClient, options, sfLogger); } /// /// Calcola un hash delle credenziali per rilevare cambiamenti /// private string CalculateCredentialsHash(RestApiCredential credential) { var credentialString = $"{credential.BaseUrl}|{credential.Username}|{credential.Password}|{credential.ApiKey}|{credential.AuthToken}|{credential.ClientId}|{credential.ClientSecret}|{credential.CompanyDatabase}"; var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(credentialString)); return Convert.ToBase64String(bytes); } /// /// Pulisce la cache del client REST per una credenziale specifica. /// public void ClearRestClientCache(string credentialName) { if (_restClientCache.Remove(credentialName)) { _logger.LogInformation("Cache del client REST rimossa per {CredentialName}", credentialName); } } /// /// Pulisce tutta la cache dei client REST. /// public void ClearAllRestClientCache() { _restClientCache.Clear(); _logger.LogInformation("Cache di tutti i client REST pulita"); } } }