diff --git a/ZA.CoreService.ESBCertificateManager/Form1.cs b/ZA.CoreService.ESBCertificateManager/Form1.cs index a2809db..ede2546 100644 --- a/ZA.CoreService.ESBCertificateManager/Form1.cs +++ b/ZA.CoreService.ESBCertificateManager/Form1.cs @@ -1781,11 +1781,6 @@ namespace ZA.CoreService.ESBCertificateManager cmbSonicConnection.Items.Add(conn.Name); } - if (cmbSonicConnection.Items.Count > 0) - { - cmbSonicConnection.SelectedIndex = 0; - } - btnLoadFromSonic = CreateSecondaryButton("Container laden"); btnLoadFromSonic.Location = new Point(392, 103); btnLoadFromSonic.Size = new Size(148, 28); @@ -1797,20 +1792,19 @@ namespace ZA.CoreService.ESBCertificateManager ForeColor = MutedTextColor, Font = new Font("Segoe UI", 8.5f), AutoSize = true, - Location = new Point(552, 109) + MaximumSize = new Size(720, 0), + Location = new Point(552, 100) }; - if (!_sonicDiscovery.HasConnections) + cmbSonicConnection.SelectedIndexChanged += (_, _) => RefreshSonicStatusLine(); + + if (cmbSonicConnection.Items.Count > 0) { - lblSonicStatus.Text = "Keine Sonic-Verbindung in appsettings konfiguriert"; - lblSonicStatus.ForeColor = RedColor; + cmbSonicConnection.SelectedIndex = 0; } else { - SonicConnection first = _sonicDiscovery.Connections[0]; - lblSonicStatus.Text = - $"{_sonicDiscovery.Connections.Count} Verb. | {first.ManagementModeDisplay} | {first.DomainName} | {first.ConnectionUrl}"; - lblSonicStatus.ForeColor = GreenColor; + RefreshSonicStatusLine(); } card.Controls.Add(sonicLabel); @@ -1819,6 +1813,44 @@ namespace ZA.CoreService.ESBCertificateManager card.Controls.Add(lblSonicStatus); } + private void RefreshSonicStatusLine(string? suffix = null) + { + if (!_sonicDiscovery.HasConnections) + { + lblSonicStatus.Text = "Keine Sonic-Verbindung in appsettings konfiguriert"; + lblSonicStatus.ForeColor = RedColor; + return; + } + + string? selectedName = cmbSonicConnection?.SelectedItem?.ToString(); + SonicConnection conn = _sonicDiscovery.Connections + .FirstOrDefault(c => string.Equals(c.Name, selectedName, StringComparison.OrdinalIgnoreCase)) + ?? _sonicDiscovery.Connections[0]; + + (string sonicHome, string? javaExe) = new SonicMfApiExecutor(conn).ResolveRuntimePaths(); + string javaDisplay = string.IsNullOrWhiteSpace(javaExe) ? "java=?" : javaExe; + string baseText = + $"{conn.ManagementModeDisplay} | SonicHome={sonicHome} | {javaDisplay}"; + if (!string.IsNullOrWhiteSpace(suffix)) + { + baseText = $"{suffix} | {baseText}"; + } + + lblSonicStatus.Text = baseText; + lblSonicStatus.ForeColor = string.IsNullOrWhiteSpace(javaExe) ? GoldColor : GreenColor; + } + + private static string TruncateStatus(string? text, int max = 120) + { + if (string.IsNullOrWhiteSpace(text)) + { + return "unbekannt"; + } + + string oneLine = text.Replace('\r', ' ').Replace('\n', ' ').Trim(); + return oneLine.Length <= max ? oneLine : oneLine[..max] + "…"; + } + private async Task LoadFromSonicAsync() { if (_isOperationRunning) return; @@ -1840,7 +1872,7 @@ namespace ZA.CoreService.ESBCertificateManager if (!result.Success) { - lblSonicStatus.Text = $"Fehler: {result.ErrorMessage}"; + RefreshSonicStatusLine($"Fehler: {TruncateStatus(result.ErrorMessage)}"); lblSonicStatus.ForeColor = RedColor; SetStatus($"Sonic-Discovery fehlgeschlagen: {result.ErrorMessage}", isError: true); return; @@ -1858,7 +1890,7 @@ namespace ZA.CoreService.ESBCertificateManager if (total == 0) { - lblSonicStatus.Text = $"Verb. ok – 0 Container in Domain '{domain}'"; + RefreshSonicStatusLine($"Verb. ok – 0 Container '{domain}'"); lblSonicStatus.ForeColor = GoldColor; SetStatus( result.ErrorMessage @@ -1867,7 +1899,7 @@ namespace ZA.CoreService.ESBCertificateManager } else if (remote == 0) { - lblSonicStatus.Text = $"Verb. ok – Domain '{domain}': 0 remote, {total} aus Config"; + RefreshSonicStatusLine($"Verb. ok – 0 remote, {total} Config '{domain}'"); lblSonicStatus.ForeColor = GoldColor; SetStatus( $"Domain '{domain}': Remote leer – {total} Ziel(e) aus appsettings/KnownContainers. " @@ -1876,8 +1908,7 @@ namespace ZA.CoreService.ESBCertificateManager } else { - lblSonicStatus.Text = $"Domain '{domain}': {remote} remote, {total} gesamt"; - lblSonicStatus.ForeColor = GreenColor; + RefreshSonicStatusLine($"Domain '{domain}': {remote} remote, {total} gesamt"); SetStatus($"Sonic Domain '{domain}': {total} Ziel(e) geladen ({remote} remote).", isError: false); } @@ -1885,13 +1916,13 @@ namespace ZA.CoreService.ESBCertificateManager } catch (OperationCanceledException) { - lblSonicStatus.Text = "Timeout – Verbindung nicht erreichbar"; + RefreshSonicStatusLine("Timeout"); lblSonicStatus.ForeColor = RedColor; SetStatus($"Sonic-Discovery Timeout ({selectedConnection})", isError: true); } catch (Exception ex) { - lblSonicStatus.Text = $"Fehler: {ex.Message}"; + RefreshSonicStatusLine($"Fehler: {TruncateStatus(ex.Message)}"); lblSonicStatus.ForeColor = RedColor; SetStatus($"Sonic-Discovery Fehler: {ex.Message}", isError: true); } diff --git a/ZA.CoreService.ESBCertificateManager/Models/SonicConnection.cs b/ZA.CoreService.ESBCertificateManager/Models/SonicConnection.cs index d9fad35..e85fadc 100644 --- a/ZA.CoreService.ESBCertificateManager/Models/SonicConnection.cs +++ b/ZA.CoreService.ESBCertificateManager/Models/SonicConnection.cs @@ -127,12 +127,15 @@ public sealed class SonicConnection return false; } - /// Sonic-Installationsroot, z.B. "C:\Sonic\MQ10.0" (enthält lib\*.jar für MfApi). + /// + /// Sonic-/MQ-/ESB-Installationsroot, z.B. "C:\Sonic\MQ10.0" (Client-JARs oft unter lib). + /// Bei ESB-Home: ggf. MfClientLibPath auf MQ\lib setzen. + /// public string SonicHome { get; init; } = @"C:\Sonic\MQ10.0"; /// /// Optional: JRE/JDK-Home (z.B. C:\Sonic\MQ10.0\jre) oder Pfad zu java.exe. - /// Leer = automatische Suche (JAVA_HOME, PATH, SonicHome\jre, Program Files\…). + /// Leer = automatische Suche (JAVA_HOME/JRE_HOME, setenv.bat, rekursiv unter SonicHome, Registry, …). /// public string JavaHome { get; init; } = string.Empty; @@ -143,7 +146,7 @@ public sealed class SonicConnection /// /// Optionaler Pfad zu Sonic-Client-JARs für MfApi (mgmt_client.jar etc.). - /// Leer = SonicHome\lib. + /// Leer = SonicHome\lib, MQ_HOME\lib, ESB_HOME\lib bzw. nested MQ*/lib. /// public string MfClientLibPath { get; init; } = string.Empty; diff --git a/ZA.CoreService.ESBCertificateManager/Services/SonicMfApiExecutor.cs b/ZA.CoreService.ESBCertificateManager/Services/SonicMfApiExecutor.cs index 0984f6d..17cd4fd 100644 --- a/ZA.CoreService.ESBCertificateManager/Services/SonicMfApiExecutor.cs +++ b/ZA.CoreService.ESBCertificateManager/Services/SonicMfApiExecutor.cs @@ -1,5 +1,7 @@ using System.Diagnostics; using System.Text; +using System.Text.RegularExpressions; +using Microsoft.Win32; using ZA.CoreService.ESBCertificateManager.Models; namespace ZA.CoreService.ESBCertificateManager.Services; @@ -12,6 +14,10 @@ namespace ZA.CoreService.ESBCertificateManager.Services; /// public sealed class SonicMfApiExecutor { + private const int MaxJavaSearchDepth = 6; + private const int MaxJavaFilesToScan = 80; + private const int MaxCheckedPathsInError = 40; + private readonly SonicConnection _connection; public SonicMfApiExecutor(SonicConnection connection) @@ -19,6 +25,19 @@ public sealed class SonicMfApiExecutor _connection = connection; } + /// + /// Liefert den konfigurierten SonicHome-Pfad und die gefundene java.exe (oder null). + /// Für UI-Status und Diagnose. + /// + public (string SonicHome, string? JavaExe) ResolveRuntimePaths() + { + JavaDiscoveryResult result = DiscoverJava(); + string sonicHome = string.IsNullOrWhiteSpace(_connection.SonicHome) + ? "(leer)" + : _connection.SonicHome.Trim(); + return (sonicHome, result.JavaExe); + } + public Task<(bool Success, string? Error)> TestConnectionAsync(CancellationToken cancellationToken = default) => RunAsync("ping", container: null, cancellationToken); @@ -159,17 +178,30 @@ public sealed class SonicMfApiExecutor private async Task<(bool Ok, string? JavaExe, string? ClassDir, string? Classpath, string? Error)> PrepareToolAsync(CancellationToken cancellationToken) { - string? javaExe = ResolveJavaExecutable(); + JavaDiscoveryResult javaResult = DiscoverJava(); + string? javaExe = javaResult.JavaExe; if (javaExe is null) { - string sonicHint = string.IsNullOrWhiteSpace(_connection.SonicHome) - ? "" - : $" Erwartet z.B. '{Path.Combine(_connection.SonicHome, "jre", "bin", "java.exe")}'."; + string sonicHome = string.IsNullOrWhiteSpace(_connection.SonicHome) + ? "(SonicHome leer / nicht gesetzt)" + : _connection.SonicHome.Trim(); + string checkedList = javaResult.CheckedPaths.Count == 0 + ? "(keine Kandidaten gesammelt)" + : string.Join("\n - ", javaResult.CheckedPaths.Take(MaxCheckedPathsInError)); + if (javaResult.CheckedPaths.Count > MaxCheckedPathsInError) + { + checkedList += $"\n - … (+{javaResult.CheckedPaths.Count - MaxCheckedPathsInError} weitere)"; + } + return (false, null, null, null, - "Java nicht gefunden. Gesucht: SonicConnection.JavaHome/JavaPath, JAVA_HOME, PATH, " + - "SonicHome\\jre|jdk\\bin\\java.exe, Program Files\\Java|Eclipse Adoptium." + - sonicHint + - " Für MfApi JavaHome in appsettings setzen oder JRE unter SonicHome nutzen."); + "Java nicht gefunden.\n" + + $"Verwendetes SonicHome: '{sonicHome}'\n" + + $"SonicHome existiert: {(Directory.Exists(sonicHome) ? "ja" : "nein")}\n" + + "Konfiguration: SonicConnection.JavaHome / JavaPath, sonst Auto-Suche " + + "(JAVA_HOME/JRE_HOME, setenv.bat, rekursiv unter SonicHome, MQ/ESB-Nachbarn, Registry, where).\n" + + "Geprüfte Pfade:\n - " + checkedList + "\n" + + "Tipp: JavaHome oder JavaPath in appsettings auf die echte java.exe setzen " + + "(z.B. C:\\Sonic\\MQ10.0\\jre\\bin\\java.exe)."); } (string? libDir, string? libError) = ResolveLibDirectoryDetailed(); @@ -295,59 +327,184 @@ public sealed class SonicMfApiExecutor if (!string.IsNullOrWhiteSpace(_connection.MfClientLibPath)) { - candidates.Add((_connection.MfClientLibPath, "MfClientLibPath")); + candidates.Add((_connection.MfClientLibPath.Trim(), "MfClientLibPath")); + } + + string? mqHome = Environment.GetEnvironmentVariable("MQ_HOME"); + string? esbHome = Environment.GetEnvironmentVariable("ESB_HOME") + ?? Environment.GetEnvironmentVariable("SONIC_HOME") + ?? Environment.GetEnvironmentVariable("SONICMQ_HOME"); + + if (!string.IsNullOrWhiteSpace(mqHome)) + { + candidates.Add((Path.Combine(mqHome.Trim().Trim('"'), "lib"), "MQ_HOME\\lib")); + } + + if (!string.IsNullOrWhiteSpace(esbHome)) + { + string esb = esbHome.Trim().Trim('"'); + candidates.Add((Path.Combine(esb, "lib"), "ESB_HOME\\lib")); + candidates.Add((Path.Combine(esb, "MQ", "lib"), "ESB_HOME\\MQ\\lib")); + candidates.Add((Path.Combine(esb, "mq", "lib"), "ESB_HOME\\mq\\lib")); } if (!string.IsNullOrWhiteSpace(_connection.SonicHome)) { - candidates.Add((Path.Combine(_connection.SonicHome, "lib"), "SonicHome\\lib")); - candidates.Add((_connection.SonicHome, "SonicHome")); + string sonicHome = _connection.SonicHome.Trim(); + candidates.Add((Path.Combine(sonicHome, "lib"), "SonicHome\\lib")); + candidates.Add((sonicHome, "SonicHome")); + + // ESB-Home zeigt oft auf Broker/ESB; Client-JARs liegen unter MQ*/lib oder sibling MQ. + foreach (string nested in EnumerateLikelyLibRoots(sonicHome)) + { + candidates.Add((nested, "SonicHome-nested\\lib")); + } + + string? parent = Directory.GetParent(sonicHome)?.FullName; + if (!string.IsNullOrWhiteSpace(parent)) + { + candidates.Add((Path.Combine(parent, "lib"), "SonicHome-Parent\\lib")); + foreach (string nested in EnumerateLikelyLibRoots(parent)) + { + candidates.Add((nested, "SonicHome-Parent-nested\\lib")); + } + } } List checkedPaths = []; + HashSet seen = new(StringComparer.OrdinalIgnoreCase); foreach ((string path, string label) in candidates) { + if (string.IsNullOrWhiteSpace(path) || !seen.Add(path)) + { + continue; + } + checkedPaths.Add($"{label}='{path}'"); if (!Directory.Exists(path)) { continue; } - if (Directory.EnumerateFiles(path, "*.jar", SearchOption.TopDirectoryOnly).Any()) + if (DirectoryHasSonicClientJars(path)) { return (path, null); } } - string sonicHome = _connection.SonicHome ?? string.Empty; - bool sonicHomeExists = !string.IsNullOrWhiteSpace(sonicHome) && Directory.Exists(sonicHome); - string libPath = string.IsNullOrWhiteSpace(sonicHome) + string sonicHomeVal = _connection.SonicHome ?? string.Empty; + bool sonicHomeExists = !string.IsNullOrWhiteSpace(sonicHomeVal) && Directory.Exists(sonicHomeVal); + string libPath = string.IsNullOrWhiteSpace(sonicHomeVal) ? "(SonicHome leer)" - : Path.Combine(sonicHome, "lib"); + : Path.Combine(sonicHomeVal, "lib"); if (sonicHomeExists && Directory.Exists(libPath) && !Directory.EnumerateFiles(libPath, "*.jar", SearchOption.TopDirectoryOnly).Any()) { return (null, - $"Sonic-Client-JARs fehlen unter '{libPath}' (Ordner existiert, aber keine *.jar). " + - "MfApi braucht u.a. mgmt_client.jar / mfcontext.jar / sonic_Client.jar. " + - "MfClientLibPath auf den korrekten Lib-Ordner setzen."); + $"Sonic-Client-JARs fehlen unter '{libPath}' (Ordner existiert, aber keine *.jar).\n" + + $"Verwendetes SonicHome: '{sonicHomeVal}'\n" + + "MfApi braucht u.a. mgmt_client.jar / mfcontext.jar / sonic_Client.jar.\n" + + "Falls SonicHome auf ESB zeigt: auf MQ-Home setzen oder MfClientLibPath auf den MQ\\lib-Ordner.\n" + + $"Geprüft: {string.Join("; ", checkedPaths)}"); } if (sonicHomeExists && !Directory.Exists(libPath)) { return (null, - $"SonicHome='{sonicHome}' gefunden, aber Lib-Ordner fehlt: '{libPath}'. " + + $"SonicHome='{sonicHomeVal}' gefunden, aber Lib-Ordner fehlt: '{libPath}'.\n" + "Sonic-Client-Installation prüfen oder MfClientLibPath setzen " + - "(erwartet *.jar, u.a. mgmt_client.jar)."); + "(erwartet *.jar, u.a. mgmt_client.jar).\n" + + $"Geprüft: {string.Join("; ", checkedPaths)}"); } return (null, - "Sonic-Client-JARs nicht gefunden. SonicHome\\lib oder MfClientLibPath setzen " + - $"(geprüft: {string.Join("; ", checkedPaths)}). " + + "Sonic-Client-JARs nicht gefunden. SonicHome\\lib, MQ_HOME\\lib, ESB_HOME\\lib oder MfClientLibPath setzen.\n" + + $"Verwendetes SonicHome: '{(string.IsNullOrWhiteSpace(sonicHomeVal) ? "(leer)" : sonicHomeVal)}'\n" + + $"Geprüft:\n - {string.Join("\n - ", checkedPaths)}\n" + "Benötigt u.a. mgmt_client.jar / mfcontext.jar / sonic_Client.jar."); } + private static IEnumerable EnumerateLikelyLibRoots(string root) + { + if (!Directory.Exists(root)) + { + yield break; + } + + string[] preferred = + [ + Path.Combine(root, "MQ", "lib"), + Path.Combine(root, "mq", "lib"), + Path.Combine(root, "ESB", "lib"), + Path.Combine(root, "esb", "lib"), + Path.Combine(root, "client", "lib"), + Path.Combine(root, "Client", "lib") + ]; + + foreach (string path in preferred) + { + if (Directory.Exists(path)) + { + yield return path; + } + } + + IEnumerable children; + try + { + children = Directory.EnumerateDirectories(root); + } + catch + { + yield break; + } + + foreach (string child in children) + { + string name = Path.GetFileName(child); + if (name.StartsWith("MQ", StringComparison.OrdinalIgnoreCase) + || name.StartsWith("ESB", StringComparison.OrdinalIgnoreCase) + || name.Contains("Sonic", StringComparison.OrdinalIgnoreCase)) + { + string lib = Path.Combine(child, "lib"); + if (Directory.Exists(lib)) + { + yield return lib; + } + } + } + } + + private static bool DirectoryHasSonicClientJars(string path) + { + try + { + string[] jars = Directory.EnumerateFiles(path, "*.jar", SearchOption.TopDirectoryOnly).ToArray(); + if (jars.Length == 0) + { + return false; + } + + // Bevorzugt echte Sonic-Client-Libs; sonst jeder JAR-Ordner als Fallback. + string[] markers = + [ + "mgmt_client", "mfcontext", "sonic_Client", "sonic_client", "mf_client", "Sonic" + ]; + if (jars.Any(j => markers.Any(m => + Path.GetFileName(j).Contains(m, StringComparison.OrdinalIgnoreCase)))) + { + return true; + } + + return jars.Length > 0; + } + catch + { + return false; + } + } + private static string BuildClasspath(string libDir, string? extraEntry) { // Java-Classpath-Wildcard für alle JARs im Lib-Ordner @@ -369,62 +526,151 @@ public sealed class SonicMfApiExecutor return candidates.FirstOrDefault(File.Exists); } - private string? ResolveJavaExecutable() + private sealed class JavaDiscoveryResult { + public string? JavaExe { get; set; } + public List CheckedPaths { get; } = []; + } + + private string? ResolveJavaExecutable() => DiscoverJava().JavaExe; + + private JavaDiscoveryResult DiscoverJava() + { + JavaDiscoveryResult result = new(); + List checkedPaths = result.CheckedPaths; + foreach (string? configured in new[] { _connection.JavaPath, _connection.JavaHome }) { - string? fromConfig = ResolveJavaFromConfiguredPath(configured); + if (string.IsNullOrWhiteSpace(configured)) + { + continue; + } + + string? fromConfig = ResolveToolFromConfiguredPath(configured, JavaExeName, checkedPaths); if (fromConfig is not null) { - return fromConfig; + result.JavaExe = fromConfig; + return result; } } - string? javaHome = Environment.GetEnvironmentVariable("JAVA_HOME"); - string? fromEnv = ResolveJavaFromHomeDir(javaHome); - if (fromEnv is not null) + foreach (string envName in new[] { "JAVA_HOME", "JRE_HOME", "JDK_HOME" }) { - return fromEnv; + string? home = Environment.GetEnvironmentVariable(envName); + string? fromEnv = ResolveJavaFromHomeDir(home, checkedPaths, envName); + if (fromEnv is not null) + { + result.JavaExe = fromEnv; + return result; + } } - string? fromPath = FindOnPath(JavaExeName) ?? FindOnPath("java"); + string? fromPath = FindOnPath(JavaExeName, checkedPaths) ?? FindOnPath("java", checkedPaths); if (fromPath is not null) { - return fromPath; + result.JavaExe = fromPath; + return result; } - if (!string.IsNullOrWhiteSpace(_connection.SonicHome)) + string? sonicHome = string.IsNullOrWhiteSpace(_connection.SonicHome) + ? null + : _connection.SonicHome.Trim(); + + if (!string.IsNullOrWhiteSpace(sonicHome)) { - string sonicHome = _connection.SonicHome; - string[] direct = - [ - Path.Combine(sonicHome, "jre", "bin", JavaExeName), - Path.Combine(sonicHome, "jdk", "bin", JavaExeName), - Path.Combine(sonicHome, "JRE", "bin", JavaExeName), - Path.Combine(sonicHome, "JDK", "bin", JavaExeName) - ]; - foreach (string candidate in direct) + string? fromScripts = FindJavaFromSonicScripts(sonicHome, checkedPaths); + if (fromScripts is not null) { - if (File.Exists(candidate)) - { - return candidate; - } + result.JavaExe = fromScripts; + return result; } - // Häufige Layouts: SonicHome\MQ*\jre, SonicHome\jre64, SonicHome\Java\jre, ... - string? nested = FindJavaUnderSonicHome(sonicHome); + string? direct = TryKnownJavaLayouts(sonicHome, checkedPaths); + if (direct is not null) + { + result.JavaExe = direct; + return result; + } + + string? nested = FindJavaUnderSonicHome(sonicHome, checkedPaths); if (nested is not null) { - return nested; + result.JavaExe = nested; + return result; + } + + string? recursive = FindJavaRecursive(sonicHome, checkedPaths, MaxJavaSearchDepth); + if (recursive is not null) + { + result.JavaExe = recursive; + return result; + } + + // Parent / sibling MQ / ESB Layouts + string? parent = Directory.GetParent(sonicHome)?.FullName; + if (!string.IsNullOrWhiteSpace(parent)) + { + string? fromParent = TryKnownJavaLayouts(parent, checkedPaths) + ?? FindJavaUnderSonicHome(parent, checkedPaths) + ?? FindJavaRecursive(parent, checkedPaths, depth: 3); + if (fromParent is not null) + { + result.JavaExe = fromParent; + return result; + } + + foreach (string siblingRoot in EnumerateSiblingMqEsbRoots(parent, sonicHome)) + { + string? fromSibling = TryKnownJavaLayouts(siblingRoot, checkedPaths) + ?? FindJavaUnderSonicHome(siblingRoot, checkedPaths); + if (fromSibling is not null) + { + result.JavaExe = fromSibling; + return result; + } + } } } - return FindJavaInCommonInstallLocations(); + foreach (string commonRoot in EnumerateCommonSonicRoots()) + { + string? found = TryKnownJavaLayouts(commonRoot, checkedPaths) + ?? FindJavaUnderSonicHome(commonRoot, checkedPaths); + if (found is not null) + { + result.JavaExe = found; + return result; + } + } + + string? fromRegistry = FindJavaInWindowsRegistry(checkedPaths); + if (fromRegistry is not null) + { + result.JavaExe = fromRegistry; + return result; + } + + string? fromWhere = FindJavaViaWhereExe(checkedPaths); + if (fromWhere is not null) + { + result.JavaExe = fromWhere; + return result; + } + + string? fromCommon = FindJavaInCommonInstallLocations(checkedPaths); + if (fromCommon is not null) + { + result.JavaExe = fromCommon; + return result; + } + + return result; } private string? ResolveJavacExecutable(string javaExe) { string javacName = OperatingSystem.IsWindows() ? "javac.exe" : "javac"; + List ignored = []; string? dir = Path.GetDirectoryName(javaExe); if (!string.IsNullOrWhiteSpace(dir)) @@ -438,20 +684,23 @@ public sealed class SonicMfApiExecutor foreach (string? configured in new[] { _connection.JavaPath, _connection.JavaHome }) { - string? fromConfig = ResolveToolFromConfiguredPath(configured, javacName); + string? fromConfig = ResolveToolFromConfiguredPath(configured, javacName, ignored); if (fromConfig is not null) { return fromConfig; } } - string? javaHome = Environment.GetEnvironmentVariable("JAVA_HOME"); - if (!string.IsNullOrWhiteSpace(javaHome)) + foreach (string envName in new[] { "JAVA_HOME", "JDK_HOME" }) { - string candidate = Path.Combine(javaHome, "bin", javacName); - if (File.Exists(candidate)) + string? javaHome = Environment.GetEnvironmentVariable(envName); + if (!string.IsNullOrWhiteSpace(javaHome)) { - return candidate; + string candidate = Path.Combine(javaHome.Trim().Trim('"'), "bin", javacName); + if (File.Exists(candidate)) + { + return candidate; + } } } @@ -467,13 +716,13 @@ public sealed class SonicMfApiExecutor } } - return FindOnPath(javacName) ?? FindOnPath("javac"); + return FindOnPath(javacName, ignored) ?? FindOnPath("javac", ignored); } - private static string? ResolveJavaFromConfiguredPath(string? configured) - => ResolveToolFromConfiguredPath(configured, JavaExeName); - - private static string? ResolveToolFromConfiguredPath(string? configured, string exeName) + private static string? ResolveToolFromConfiguredPath( + string? configured, + string exeName, + List checkedPaths) { if (string.IsNullOrWhiteSpace(configured)) { @@ -481,6 +730,7 @@ public sealed class SonicMfApiExecutor } string path = configured.Trim().Trim('"'); + NoteChecked(checkedPaths, $"config:{path}"); if (File.Exists(path)) { @@ -489,18 +739,17 @@ public sealed class SonicMfApiExecutor return path; } - // Konfiguriert: ...\bin\java.exe → sibling javac.exe (oder umgekehrt) string? parent = Path.GetDirectoryName(path); if (!string.IsNullOrWhiteSpace(parent)) { string sibling = Path.Combine(parent, exeName); + NoteChecked(checkedPaths, sibling); if (File.Exists(sibling)) { return sibling; } } - // java ohne Endung (Unix) oder exakter Treffer string fileName = Path.GetFileName(path); if (fileName.Equals(Path.GetFileNameWithoutExtension(exeName), StringComparison.OrdinalIgnoreCase) || fileName.Equals(exeName, StringComparison.OrdinalIgnoreCase)) @@ -510,31 +759,78 @@ public sealed class SonicMfApiExecutor } string fromHome = Path.Combine(path, "bin", exeName); + NoteChecked(checkedPaths, fromHome); return File.Exists(fromHome) ? fromHome : null; } - private static string? ResolveJavaFromHomeDir(string? home) + private static string? ResolveJavaFromHomeDir(string? home, List checkedPaths, string label) { if (string.IsNullOrWhiteSpace(home)) { + NoteChecked(checkedPaths, $"{label}=(leer)"); return null; } - string candidate = Path.Combine(home.Trim().Trim('"'), "bin", JavaExeName); - return File.Exists(candidate) ? candidate : null; + string trimmed = home.Trim().Trim('"'); + string candidate = Path.Combine(trimmed, "bin", JavaExeName); + NoteChecked(checkedPaths, $"{label}:{candidate}"); + if (File.Exists(candidate)) + { + return candidate; + } + + // Manchmal zeigt JAVA_HOME bereits auf ...\bin + string asBin = Path.Combine(trimmed, JavaExeName); + NoteChecked(checkedPaths, $"{label}:{asBin}"); + return File.Exists(asBin) ? asBin : null; } - private static string? FindJavaUnderSonicHome(string sonicHome) + private static string? TryKnownJavaLayouts(string root, List checkedPaths) + { + if (string.IsNullOrWhiteSpace(root)) + { + return null; + } + + string[] relatives = + [ + Path.Combine("jre", "bin", JavaExeName), + Path.Combine("jdk", "bin", JavaExeName), + Path.Combine("JRE", "bin", JavaExeName), + Path.Combine("JDK", "bin", JavaExeName), + Path.Combine("jre64", "bin", JavaExeName), + Path.Combine("jdk64", "bin", JavaExeName), + Path.Combine("Java", "jre", "bin", JavaExeName), + Path.Combine("Java", "jdk", "bin", JavaExeName), + Path.Combine("java", "bin", JavaExeName), + Path.Combine("bin", JavaExeName) + ]; + + foreach (string rel in relatives) + { + string candidate = Path.Combine(root, rel); + NoteChecked(checkedPaths, candidate); + if (File.Exists(candidate)) + { + return candidate; + } + } + + return null; + } + + private static string? FindJavaUnderSonicHome(string sonicHome, List checkedPaths) { if (!Directory.Exists(sonicHome)) { + NoteChecked(checkedPaths, $"SonicHome-missing:{sonicHome}"); return null; } - // Direkte Unterordner mit jre/jdk foreach (string root in EnumerateJdkRootsUnderSonicHome(sonicHome)) { string candidate = Path.Combine(root, "bin", JavaExeName); + NoteChecked(checkedPaths, candidate); if (File.Exists(candidate)) { return candidate; @@ -570,7 +866,6 @@ public sealed class SonicMfApiExecutor } } - // {SonicHome}\*\jre und {SonicHome}\*\jdk IEnumerable children; try { @@ -583,7 +878,7 @@ public sealed class SonicMfApiExecutor foreach (string child in children) { - foreach (string leaf in new[] { "jre", "jdk", "JRE", "JDK" }) + foreach (string leaf in new[] { "jre", "jdk", "JRE", "JDK", "jre64", "jdk64" }) { string nested = Path.Combine(child, leaf); if (Directory.Exists(nested)) @@ -592,7 +887,6 @@ public sealed class SonicMfApiExecutor } } - // z.B. ...\jre1.8.0_xxx direkt als Kind string leafName = Path.GetFileName(child); if (leafName.StartsWith("jre", StringComparison.OrdinalIgnoreCase) || leafName.StartsWith("jdk", StringComparison.OrdinalIgnoreCase)) @@ -602,7 +896,445 @@ public sealed class SonicMfApiExecutor } } - private static string? FindJavaInCommonInstallLocations() + private static string? FindJavaFromSonicScripts(string sonicHome, List checkedPaths) + { + if (!Directory.Exists(sonicHome)) + { + return null; + } + + string[] scriptDirs = + [ + Path.Combine(sonicHome, "bin"), + sonicHome, + Path.Combine(sonicHome, "MQ", "bin"), + Path.Combine(sonicHome, "mq", "bin") + ]; + + string[] scriptNames = + [ + "setenv.bat", "setenv.sh", "setenv.cmd", + "sonic_env.bat", "sonic_env.sh", "sonic_env.cmd", + "startcontainer.bat", "startcontainer.sh", + "startmc.bat", "startmc.sh", + "startbroker.bat", "startbroker.sh" + ]; + + List scripts = []; + foreach (string dir in scriptDirs.Where(Directory.Exists)) + { + foreach (string name in scriptNames) + { + string path = Path.Combine(dir, name); + if (File.Exists(path)) + { + scripts.Add(path); + } + } + + try + { + foreach (string path in Directory.EnumerateFiles(dir, "sonic_env*", SearchOption.TopDirectoryOnly)) + { + scripts.Add(path); + } + + foreach (string path in Directory.EnumerateFiles(dir, "setenv*", SearchOption.TopDirectoryOnly)) + { + scripts.Add(path); + } + } + catch + { + // ignore + } + } + + foreach (string script in scripts.Distinct(StringComparer.OrdinalIgnoreCase)) + { + NoteChecked(checkedPaths, $"script:{script}"); + string? found = ParseJavaFromEnvScript(script, sonicHome, checkedPaths); + if (found is not null) + { + return found; + } + } + + return null; + } + + private static string? ParseJavaFromEnvScript(string scriptPath, string sonicHome, List checkedPaths) + { + string text; + try + { + text = File.ReadAllText(scriptPath); + } + catch + { + return null; + } + + // JAVA_HOME / JRE_HOME / JDK_HOME Assignments + Regex homeAssign = new( + @"(?im)(?:set\s+)?(?JAVA_HOME|JRE_HOME|JDK_HOME)\s*=\s*(?[^\r\n]+)"); + foreach (Match match in homeAssign.Matches(text)) + { + string raw = ExpandScriptPath(match.Groups["val"].Value, sonicHome); + string? resolved = ResolveJavaFromHomeDir(raw, checkedPaths, Path.GetFileName(scriptPath)); + if (resolved is not null) + { + return resolved; + } + } + + // Direkte java.exe-Pfade in Scripten + Regex javaExeRef = new( + @"(?i)(?(?:[A-Za-z]:\\|/)[^\s""']+?[/\\]java(?:\.exe)?)"); + foreach (Match match in javaExeRef.Matches(text)) + { + string raw = ExpandScriptPath(match.Groups["path"].Value, sonicHome); + NoteChecked(checkedPaths, $"script-java:{raw}"); + if (File.Exists(raw)) + { + return raw; + } + } + + return null; + } + + private static string ExpandScriptPath(string raw, string sonicHome) + { + string value = raw.Trim().Trim('"', '\'', '`'); + + // Zuerst Variablen expandieren, danach Rauschen (Kommentare/Operatoren) abschneiden. + value = Regex.Replace(value, "%~dp0", sonicHome + Path.DirectorySeparatorChar, + RegexOptions.IgnoreCase); + value = Regex.Replace(value, "%SONIC_HOME%", sonicHome, RegexOptions.IgnoreCase); + value = Regex.Replace(value, "%MQ_HOME%", + Environment.GetEnvironmentVariable("MQ_HOME") ?? sonicHome, RegexOptions.IgnoreCase); + value = Regex.Replace(value, "%ESB_HOME%", + Environment.GetEnvironmentVariable("ESB_HOME") ?? sonicHome, RegexOptions.IgnoreCase); + value = Regex.Replace(value, "%JAVA_HOME%", + Environment.GetEnvironmentVariable("JAVA_HOME") ?? string.Empty, RegexOptions.IgnoreCase); + value = Regex.Replace(value, "%JRE_HOME%", + Environment.GetEnvironmentVariable("JRE_HOME") ?? string.Empty, RegexOptions.IgnoreCase); + + value = Regex.Replace(value, @"\$\{?SONIC_HOME\}?", sonicHome, RegexOptions.IgnoreCase); + value = Regex.Replace(value, @"\$\{?MQ_HOME\}?", + Environment.GetEnvironmentVariable("MQ_HOME") ?? sonicHome, RegexOptions.IgnoreCase); + value = Regex.Replace(value, @"\$\{?JAVA_HOME\}?", + Environment.GetEnvironmentVariable("JAVA_HOME") ?? string.Empty, RegexOptions.IgnoreCase); + value = Regex.Replace(value, @"\$\{?JRE_HOME\}?", + Environment.GetEnvironmentVariable("JRE_HOME") ?? string.Empty, RegexOptions.IgnoreCase); + + // trailing " & rem ..." / " && ..." entfernen + int cut = value.IndexOfAny(['&', '|', '>', '<', '`']); + if (cut >= 0) + { + value = value[..cut]; + } + + int rem = value.IndexOf(" rem ", StringComparison.OrdinalIgnoreCase); + if (rem >= 0) + { + value = value[..rem]; + } + + if (value.StartsWith('.') || value.StartsWith("..")) + { + try + { + value = Path.GetFullPath(Path.Combine(sonicHome, value)); + } + catch + { + // keep as-is + } + } + + return value.Trim().Trim('"', '\''); + } + + private static string? FindJavaRecursive(string root, List checkedPaths, int depth) + { + if (!Directory.Exists(root) || depth < 1) + { + return null; + } + + List preferred = []; + List others = []; + int scanned = 0; + + void Walk(string dir, int remaining) + { + if (scanned >= MaxJavaFilesToScan || remaining < 0) + { + return; + } + + try + { + string candidate = Path.Combine(dir, JavaExeName); + if (File.Exists(candidate)) + { + scanned++; + string normalized = candidate.Replace('/', Path.DirectorySeparatorChar); + if (normalized.Contains($"{Path.DirectorySeparatorChar}jre{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}", + StringComparison.OrdinalIgnoreCase) + || normalized.Contains($"{Path.DirectorySeparatorChar}jdk{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}", + StringComparison.OrdinalIgnoreCase)) + { + preferred.Add(candidate); + } + else + { + others.Add(candidate); + } + } + + if (remaining == 0 || scanned >= MaxJavaFilesToScan) + { + return; + } + + foreach (string child in Directory.EnumerateDirectories(dir)) + { + string name = Path.GetFileName(child); + if (name.Equals("tmp", StringComparison.OrdinalIgnoreCase) + || name.Equals("temp", StringComparison.OrdinalIgnoreCase) + || name.Equals("logs", StringComparison.OrdinalIgnoreCase) + || name.Equals("log", StringComparison.OrdinalIgnoreCase) + || name.Equals(".git", StringComparison.OrdinalIgnoreCase) + || name.Equals("node_modules", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + Walk(child, remaining - 1); + if (scanned >= MaxJavaFilesToScan) + { + return; + } + } + } + catch + { + // inaccessible directory + } + } + + Walk(root, depth); + + string? best = preferred.FirstOrDefault() ?? others.FirstOrDefault(); + if (best is not null) + { + NoteChecked(checkedPaths, $"recursive-hit:{best}"); + return best; + } + + NoteChecked(checkedPaths, $"recursive-miss:{root} (depth<={depth}, scanned={scanned})"); + return null; + } + + private static IEnumerable EnumerateSiblingMqEsbRoots(string parent, string currentSonicHome) + { + if (!Directory.Exists(parent)) + { + yield break; + } + + IEnumerable children; + try + { + children = Directory.EnumerateDirectories(parent); + } + catch + { + yield break; + } + + foreach (string child in children) + { + if (string.Equals(child, currentSonicHome, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + string name = Path.GetFileName(child); + if (name.StartsWith("MQ", StringComparison.OrdinalIgnoreCase) + || name.StartsWith("ESB", StringComparison.OrdinalIgnoreCase) + || name.Contains("Sonic", StringComparison.OrdinalIgnoreCase)) + { + yield return child; + } + } + } + + private static IEnumerable EnumerateCommonSonicRoots() + { + string[] roots = + [ + @"C:\Sonic", + @"C:\Program Files\Progress", + @"C:\Program Files\Aurea", + @"C:\Program Files (x86)\Progress", + @"C:\Program Files (x86)\Aurea", + @"D:\Sonic", + Environment.GetEnvironmentVariable("MQ_HOME") ?? string.Empty, + Environment.GetEnvironmentVariable("ESB_HOME") ?? string.Empty, + Environment.GetEnvironmentVariable("SONIC_HOME") ?? string.Empty + ]; + + foreach (string root in roots.Where(r => !string.IsNullOrWhiteSpace(r)).Distinct(StringComparer.OrdinalIgnoreCase)) + { + if (Directory.Exists(root)) + { + yield return root; + } + + string? parentExists = Directory.Exists(root) ? root : null; + if (parentExists is null) + { + continue; + } + + IEnumerable children; + try + { + children = Directory.EnumerateDirectories(parentExists); + } + catch + { + continue; + } + + foreach (string child in children) + { + string name = Path.GetFileName(child); + if (name.StartsWith("MQ", StringComparison.OrdinalIgnoreCase) + || name.StartsWith("ESB", StringComparison.OrdinalIgnoreCase) + || name.Contains("Sonic", StringComparison.OrdinalIgnoreCase)) + { + yield return child; + } + } + } + } + + private static string? FindJavaInWindowsRegistry(List checkedPaths) + { + if (!OperatingSystem.IsWindows()) + { + return null; + } + + string[] keyPaths = + [ + @"SOFTWARE\JavaSoft\Java Runtime Environment", + @"SOFTWARE\JavaSoft\JDK", + @"SOFTWARE\JavaSoft\Java Development Kit", + @"SOFTWARE\Eclipse Adoptium\JRE", + @"SOFTWARE\Eclipse Adoptium\JDK", + @"SOFTWARE\WOW6432Node\JavaSoft\Java Runtime Environment", + @"SOFTWARE\WOW6432Node\JavaSoft\JDK" + ]; + + foreach (string keyPath in keyPaths) + { + try + { + using RegistryKey? baseKey = Registry.LocalMachine.OpenSubKey(keyPath); + if (baseKey is null) + { + NoteChecked(checkedPaths, $"registry-miss:HKLM\\{keyPath}"); + continue; + } + + NoteChecked(checkedPaths, $"registry:HKLM\\{keyPath}"); + + string? current = baseKey.GetValue("CurrentVersion") as string; + if (!string.IsNullOrWhiteSpace(current)) + { + using RegistryKey? verKey = baseKey.OpenSubKey(current); + string? javaHome = verKey?.GetValue("JavaHome") as string; + string? resolved = ResolveJavaFromHomeDir(javaHome, checkedPaths, $"registry:{current}"); + if (resolved is not null) + { + return resolved; + } + } + + foreach (string subName in baseKey.GetSubKeyNames().OrderByDescending(s => s)) + { + using RegistryKey? verKey = baseKey.OpenSubKey(subName); + string? javaHome = verKey?.GetValue("JavaHome") as string + ?? verKey?.GetValue("Path") as string; + string? resolved = ResolveJavaFromHomeDir(javaHome, checkedPaths, $"registry:{subName}"); + if (resolved is not null) + { + return resolved; + } + } + } + catch + { + NoteChecked(checkedPaths, $"registry-error:HKLM\\{keyPath}"); + } + } + + return null; + } + + private static string? FindJavaViaWhereExe(List checkedPaths) + { + if (!OperatingSystem.IsWindows()) + { + return null; + } + + try + { + ProcessStartInfo psi = new() + { + FileName = "where.exe", + Arguments = "java", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + + using Process process = new() { StartInfo = psi }; + if (!process.Start()) + { + NoteChecked(checkedPaths, "where.exe:start-failed"); + return null; + } + + string output = process.StandardOutput.ReadToEnd(); + process.WaitForExit(3000); + foreach (string line in output.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + { + NoteChecked(checkedPaths, $"where:{line}"); + if (File.Exists(line) + && Path.GetFileName(line).Equals(JavaExeName, StringComparison.OrdinalIgnoreCase)) + { + return line; + } + } + } + catch (Exception ex) + { + NoteChecked(checkedPaths, $"where.exe:error:{ex.GetType().Name}"); + } + + return null; + } + + private static string? FindJavaInCommonInstallLocations(List checkedPaths) { string[] roots = [ @@ -621,7 +1353,9 @@ public sealed class SonicMfApiExecutor "Amazon Corretto", "Zulu", "BellSoft", - "Semeru" + "Semeru", + "Progress", + "Aurea" ]; foreach (string root in roots.Where(r => !string.IsNullOrWhiteSpace(r)).Distinct(StringComparer.OrdinalIgnoreCase)) @@ -634,8 +1368,8 @@ public sealed class SonicMfApiExecutor continue; } - // vendor\bin\java.exe string direct = Path.Combine(vendorDir, "bin", JavaExeName); + NoteChecked(checkedPaths, direct); if (File.Exists(direct)) { return direct; @@ -647,12 +1381,14 @@ public sealed class SonicMfApiExecutor .OrderByDescending(d => d, StringComparer.OrdinalIgnoreCase)) { string candidate = Path.Combine(sub, "bin", JavaExeName); + NoteChecked(checkedPaths, candidate); if (File.Exists(candidate)) { return candidate; } string nestedJre = Path.Combine(sub, "jre", "bin", JavaExeName); + NoteChecked(checkedPaths, nestedJre); if (File.Exists(nestedJre)) { return nestedJre; @@ -669,13 +1405,27 @@ public sealed class SonicMfApiExecutor return null; } + private static void NoteChecked(List checkedPaths, string path) + { + if (checkedPaths.Count >= MaxCheckedPathsInError * 2) + { + return; + } + + if (!checkedPaths.Contains(path, StringComparer.OrdinalIgnoreCase)) + { + checkedPaths.Add(path); + } + } + private static string JavaExeName => OperatingSystem.IsWindows() ? "java.exe" : "java"; - private static string? FindOnPath(string fileName) + private static string? FindOnPath(string fileName, List checkedPaths) { string? pathEnv = Environment.GetEnvironmentVariable("PATH"); if (string.IsNullOrWhiteSpace(pathEnv)) { + NoteChecked(checkedPaths, "PATH=(leer)"); return null; } @@ -684,6 +1434,7 @@ public sealed class SonicMfApiExecutor try { string candidate = Path.Combine(dir.Trim().Trim('"'), fileName); + NoteChecked(checkedPaths, $"PATH:{candidate}"); if (File.Exists(candidate)) { return candidate; diff --git a/ZA.CoreService.ESBCertificateManager/appsettings.json b/ZA.CoreService.ESBCertificateManager/appsettings.json index c108d53..0e5a457 100644 --- a/ZA.CoreService.ESBCertificateManager/appsettings.json +++ b/ZA.CoreService.ESBCertificateManager/appsettings.json @@ -14,8 +14,9 @@ "Username": "Administrator", "Password": "Administrator", // Standard: Sonic MF Management API (IAgentProxy.restart) – kein WinRM. - // Benötigt JRE + Client-JARs unter SonicHome\\lib (oder MfClientLibPath). - // Java: oft unter SonicHome\\jre – sonst JavaHome/JavaPath setzen (kein systemweites JAVA_HOME nötig). + // Benötigt JRE + Client-JARs unter SonicHome\\lib (oder MfClientLibPath / MQ_HOME). + // Java-Suche: JavaPath/JavaHome, JAVA_HOME/JRE_HOME, setenv.bat, rekursiv unter SonicHome + // (nicht nur SonicHome\\jre\\bin\\java.exe), Registry, where.exe. Optional JavaHome/JavaPath setzen. // Optional-Fallback: "WinRm" / "LocalCmd" (stopcontainer/startcontainer). "ManagementMode": "MfApi", "SonicHome": "C:\\Sonic\\MQ10.0",