using System.Diagnostics; using System.Text; using ZA.CoreService.ESBCertificateManager.Models; namespace ZA.CoreService.ESBCertificateManager.Services; /// /// Startet nur den Container-Restart über SonicMfContainerTool (Java + MF-Client-JARs). /// public sealed class SonicMfApiExecutor { private readonly SonicConnection _connection; public SonicMfApiExecutor(SonicConnection connection) { _connection = connection; } public (string SonicHome, string? JavaExe) ResolveRuntimePaths() { string sonicHome = string.IsNullOrWhiteSpace(_connection.SonicHome) ? "(leer)" : _connection.SonicHome.Trim(); return (sonicHome, ResolveJavaExe()); } public async Task<(bool Success, string? Output, string? Error)> RestartAsync( string containerName, CancellationToken cancellationToken = default) { (bool prepared, string? javaExe, string? classDir, string? classpath, string? prepareError) = PrepareTool(); if (!prepared) { return (false, null, prepareError); } ProcessStartInfo psi = new() { FileName = javaExe!, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8, WorkingDirectory = classDir! }; psi.ArgumentList.Add("-cp"); psi.ArgumentList.Add(classpath!); psi.ArgumentList.Add("SonicMfContainerTool"); psi.ArgumentList.Add("restart"); psi.ArgumentList.Add("--domain"); psi.ArgumentList.Add(_connection.DomainName); psi.ArgumentList.Add("--url"); psi.ArgumentList.Add(_connection.ConnectionUrl); psi.ArgumentList.Add("--user"); psi.ArgumentList.Add(_connection.Username); psi.ArgumentList.Add("--container"); psi.ArgumentList.Add(containerName); psi.ArgumentList.Add("--timeout"); psi.ArgumentList.Add(Math.Clamp(_connection.TimeoutSeconds, 5, 600).ToString()); psi.Environment["ESB_SONIC_PASSWORD"] = _connection.Password ?? string.Empty; using Process process = new() { StartInfo = psi }; if (!process.Start()) { return (false, null, "Java-Prozess konnte nicht gestartet werden."); } using CancellationTokenSource timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); timeoutCts.CancelAfter(TimeSpan.FromSeconds(Math.Clamp(_connection.TimeoutSeconds, 10, 600))); Task stdoutTask = process.StandardOutput.ReadToEndAsync(cancellationToken); Task stderrTask = process.StandardError.ReadToEndAsync(cancellationToken); try { await process.WaitForExitAsync(timeoutCts.Token); } catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { try { process.Kill(entireProcessTree: true); } catch { /* ignore */ } return (false, null, $"MfApi Timeout nach {_connection.TimeoutSeconds}s."); } string stdout = (await stdoutTask).Trim(); string stderr = (await stderrTask).Trim(); string combined = string.Join("\n", new[] { stdout, stderr }.Where(s => s.Length > 0)); bool ok = process.ExitCode == 0 && combined.Contains("OK:RestartInvoked", StringComparison.OrdinalIgnoreCase) && !combined.Contains("ERROR:", StringComparison.OrdinalIgnoreCase); if (ok) { return (true, combined, null); } string err = ExtractError(combined) ?? $"SonicMfContainerTool ExitCode={process.ExitCode}"; if (err.Contains("ClassNotFoundException", StringComparison.OrdinalIgnoreCase) || combined.Contains("JMSConnectorAddress", StringComparison.OrdinalIgnoreCase)) { err += "\n\nClasspath unvollständig. MfClientLibPath auf Ordner mit mgmt_client.jar / mfcontext.jar setzen."; } string detail = string.IsNullOrWhiteSpace(combined) ? err : err + "\n\n--- Tool-Output ---\n" + combined; return (false, combined.Length > 0 ? combined : null, detail); } private (bool Ok, string? JavaExe, string? ClassDir, string? Classpath, string? Error) PrepareTool() { string? javaExe = ResolveJavaExe(); if (javaExe is null) { return (false, null, null, null, "Java nicht gefunden. In appsettings JavaPath auf java.exe (Java 8+) setzen."); } (string? libClasspath, string? libError) = ResolveSonicClasspath(); if (libClasspath is null) { return (false, null, null, null, libError); } string? toolsDir = ResolveToolsDir(); if (toolsDir is null) { return (false, null, null, null, "Tools\\SonicMfContainerTool.class nicht gefunden. Projekt neu bauen."); } string classpath = toolsDir + Path.PathSeparator + libClasspath; return (true, javaExe, toolsDir, classpath, null); } private string? ResolveJavaExe() { foreach (string? candidate in EnumerateJavaCandidates()) { if (!string.IsNullOrWhiteSpace(candidate) && File.Exists(candidate)) { return Path.GetFullPath(candidate); } } return null; } private IEnumerable EnumerateJavaCandidates() { if (!string.IsNullOrWhiteSpace(_connection.JavaPath)) { yield return _connection.JavaPath.Trim(); } if (!string.IsNullOrWhiteSpace(_connection.JavaHome)) { yield return Path.Combine(_connection.JavaHome.Trim(), "bin", "java.exe"); } string? javaHome = Environment.GetEnvironmentVariable("JAVA_HOME"); if (!string.IsNullOrWhiteSpace(javaHome)) { yield return Path.Combine(javaHome.Trim(), "bin", "java.exe"); } string? jreHome = Environment.GetEnvironmentVariable("JRE_HOME"); if (!string.IsNullOrWhiteSpace(jreHome)) { yield return Path.Combine(jreHome.Trim(), "bin", "java.exe"); } yield return "java.exe"; // PATH } private (string? Classpath, string? Error) ResolveSonicClasspath() { List libDirs = []; if (!string.IsNullOrWhiteSpace(_connection.MfClientLibPath)) { libDirs.Add(_connection.MfClientLibPath.Trim()); } if (!string.IsNullOrWhiteSpace(_connection.SonicHome)) { string home = _connection.SonicHome.Trim(); libDirs.Add(Path.Combine(home, "lib")); libDirs.Add(home); } HashSet jars = new(StringComparer.OrdinalIgnoreCase); foreach (string dir in libDirs.Where(Directory.Exists)) { foreach (string jar in Directory.EnumerateFiles(dir, "*.jar", SearchOption.TopDirectoryOnly)) { jars.Add(jar); } } if (jars.Count == 0) { return (null, "Keine Sonic-Client-JARs gefunden. MfClientLibPath oder SonicHome\\lib setzen " + "(mgmt_client.jar, mfcontext.jar, …)."); } // Explizite Kern-JARs zuerst, falls vorhanden string[] preferred = [ "mgmt_client.jar", "mfcontext.jar", "sonic_Client.jar", "sonic_Crypto.jar", "mf_common.jar" ]; List ordered = []; foreach (string name in preferred) { string? hit = jars.FirstOrDefault(j => string.Equals(Path.GetFileName(j), name, StringComparison.OrdinalIgnoreCase)); if (hit is not null) { ordered.Add(hit); } } foreach (string jar in jars.OrderBy(Path.GetFileName, StringComparer.OrdinalIgnoreCase)) { if (!ordered.Contains(jar, StringComparer.OrdinalIgnoreCase)) { ordered.Add(jar); } } return (string.Join(Path.PathSeparator, ordered), null); } private static string? ResolveToolsDir() { string[] dirs = [ Path.Combine(AppContext.BaseDirectory, "Tools"), Path.Combine(Directory.GetCurrentDirectory(), "Tools"), AppContext.BaseDirectory ]; foreach (string dir in dirs.Where(Directory.Exists)) { if (File.Exists(Path.Combine(dir, "SonicMfContainerTool.class"))) { return dir; } } return null; } private static string? ExtractError(string combined) { foreach (string line in combined.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries)) { if (line.StartsWith("ERROR:", StringComparison.OrdinalIgnoreCase)) { return line["ERROR:".Length..].Trim(); } } return null; } }