diff --git a/ZA.CoreService.ESBCertificateManager/Form1.cs b/ZA.CoreService.ESBCertificateManager/Form1.cs index c69bb80..0084b62 100644 --- a/ZA.CoreService.ESBCertificateManager/Form1.cs +++ b/ZA.CoreService.ESBCertificateManager/Form1.cs @@ -40,7 +40,6 @@ namespace ZA.CoreService.ESBCertificateManager private TextBox txtCertificatePath = null!; private DataGridView dgvTargets = null!; private ComboBox cmbSonicConnection = null!; - private Button btnLoadFromSonic = null!; private Label lblSonicStatus = null!; private Panel navProgressPanel = null!; @@ -1302,14 +1301,12 @@ namespace ZA.CoreService.ESBCertificateManager if (_loadedTargets.Count == 0) { SetStatus( - "Keine KnownContainers in appsettings. SonicConnections[].KnownContainers setzen oder „Container laden“.", + "Keine KnownContainers in appsettings. SonicConnections[].KnownContainers setzen.", isError: true); } else { - SetStatus( - $"{_loadedTargets.Count} Container aus appsettings. Optional: „Container laden“ für Live-Liste.", - isError: false); + SetStatus($"{_loadedTargets.Count} Container aus appsettings.", isError: false); } } catch (Exception ex) @@ -1408,10 +1405,6 @@ namespace ZA.CoreService.ESBCertificateManager dgvTargets.Enabled = idle; } - if (btnLoadFromSonic is not null) - { - btnLoadFromSonic.Enabled = idle && _sonicDiscovery.HasConnections; - } } private void SetStatus(string message, bool isError) @@ -1555,24 +1548,16 @@ namespace ZA.CoreService.ESBCertificateManager SetTargetRowStatus(targetResult.TargetId, targetResult.StatusText); } - string details = string.Join( - Environment.NewLine + Environment.NewLine, - runResult.TargetResults.Select(r => - $"{r.TargetName}: {r.StatusText}" + - (string.IsNullOrWhiteSpace(r.Detail) ? string.Empty : Environment.NewLine + r.Detail))); - SetStatus( runResult.OverallSuccess ? $"Neustart verifiziert ({runResult.TargetResults.Count} Ziel(e))." : "Neustart fehlgeschlagen oder nicht verifiziert.", isError: !runResult.OverallSuccess); - MessageBox.Show( - this, - details, - runResult.OverallSuccess ? "Neustart verifiziert" : "Neustart fehlgeschlagen", - MessageBoxButtons.OK, - runResult.OverallSuccess ? MessageBoxIcon.Information : MessageBoxIcon.Warning); + if (runResult.OverallSuccess) + { + UpdateToNextStep(4); + } } catch (OperationCanceledException) { @@ -1693,7 +1678,7 @@ namespace ZA.CoreService.ESBCertificateManager } // --------------------------------------------------------------- - // Sonic Management Console – Container-Discovery + // Sonic – Status (KnownContainers aus appsettings) // --------------------------------------------------------------- private void BuildSonicDiscoveryRow(Panel card) @@ -1723,19 +1708,13 @@ namespace ZA.CoreService.ESBCertificateManager cmbSonicConnection.Items.Add(conn.Name); } - btnLoadFromSonic = CreateSecondaryButton("Container laden"); - btnLoadFromSonic.Location = new Point(392, 103); - btnLoadFromSonic.Size = new Size(148, 28); - btnLoadFromSonic.Enabled = _sonicDiscovery.HasConnections; - btnLoadFromSonic.Click += async (_, _) => await LoadFromSonicAsync(); - lblSonicStatus = new Label { ForeColor = MutedTextColor, Font = new Font("Segoe UI", 8.5f), AutoSize = true, MaximumSize = new Size(720, 0), - Location = new Point(552, 100) + Location = new Point(392, 108) }; cmbSonicConnection.SelectedIndexChanged += (_, _) => RefreshSonicStatusLine(); @@ -1751,11 +1730,10 @@ namespace ZA.CoreService.ESBCertificateManager card.Controls.Add(sonicLabel); card.Controls.Add(cmbSonicConnection); - card.Controls.Add(btnLoadFromSonic); card.Controls.Add(lblSonicStatus); } - private void RefreshSonicStatusLine(string? suffix = null) + private void RefreshSonicStatusLine() { if (!_sonicDiscovery.HasConnections) { @@ -1769,134 +1747,13 @@ namespace ZA.CoreService.ESBCertificateManager .FirstOrDefault(c => string.Equals(c.Name, selectedName, StringComparison.OrdinalIgnoreCase)) ?? _sonicDiscovery.Connections[0]; - string sonicHome = string.IsNullOrWhiteSpace(conn.SonicHome) ? "(leer)" : conn.SonicHome.Trim(); (string home, string? javaExe) = new SonicMfApiExecutor(conn).ResolveRuntimePaths(); string javaDisplay = string.IsNullOrWhiteSpace(javaExe) - ? "java=? (unter SonicHome/JRE setzen)" + ? "java=? (JavaPath in appsettings setzen)" : $"java={javaExe}"; - string baseText = - $"{conn.ManagementModeDisplay} | SonicHome={home} | {javaDisplay}"; - if (!string.IsNullOrWhiteSpace(suffix)) - { - baseText = $"{suffix} | {baseText}"; - } - - lblSonicStatus.Text = baseText; + lblSonicStatus.Text = $"{conn.ManagementModeDisplay} | SonicHome={home} | {javaDisplay}"; 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; - - string? selectedConnection = cmbSonicConnection?.SelectedItem?.ToString(); - if (string.IsNullOrWhiteSpace(selectedConnection)) return; - - btnLoadFromSonic.Enabled = false; - lblSonicStatus.Text = $"Verbinde mit {selectedConnection}…"; - lblSonicStatus.ForeColor = GoldColor; - - try - { - using CancellationTokenSource cts = new(TimeSpan.FromSeconds(30)); - SonicDiscoveryResult result = await _sonicDiscovery.DiscoverAsync( - selectedConnection, - _loadedTargets, - cts.Token); - - if (!result.Success) - { - RefreshSonicStatusLine($"Fehler: {TruncateStatus(result.ErrorMessage)}"); - lblSonicStatus.ForeColor = RedColor; - SetStatus($"Sonic-Discovery fehlgeschlagen: {result.ErrorMessage}", isError: true); - return; - } - - // Grid mit entdeckten + konfigurierten Containern befüllen - _loadedTargets = result.DiscoveredTargets; - BindTargetsToGrid(result.DiscoveredTargets); - - int total = result.DiscoveredTargets.Count; - int remote = result.RawContainerNames.Count; - string domain = string.IsNullOrWhiteSpace(result.DomainName) - ? selectedConnection - : result.DomainName; - - if (total == 0) - { - RefreshSonicStatusLine($"Verb. ok – 0 Container '{domain}'"); - lblSonicStatus.ForeColor = GoldColor; - SetStatus( - result.ErrorMessage - ?? $"Sonic Domain '{domain}': keine Container. KnownContainers/SonicHome in appsettings prüfen.", - isError: true); - } - else if (remote == 0) - { - RefreshSonicStatusLine($"Verb. ok – 0 remote, {total} Config '{domain}'"); - lblSonicStatus.ForeColor = GoldColor; - SetStatus( - $"Domain '{domain}': Remote leer – {total} Ziel(e) aus appsettings/KnownContainers. " - + (result.ErrorMessage ?? string.Empty), - isError: false); - } - else - { - RefreshSonicStatusLine($"Domain '{domain}': {remote} remote, {total} gesamt"); - SetStatus($"Sonic Domain '{domain}': {total} Ziel(e) geladen ({remote} remote).", isError: false); - } - - MarkUnconfiguredRows(); - } - catch (OperationCanceledException) - { - RefreshSonicStatusLine("Timeout"); - lblSonicStatus.ForeColor = RedColor; - SetStatus($"Sonic-Discovery Timeout ({selectedConnection})", isError: true); - } - catch (Exception ex) - { - RefreshSonicStatusLine($"Fehler: {TruncateStatus(ex.Message)}"); - lblSonicStatus.ForeColor = RedColor; - SetStatus($"Sonic-Discovery Fehler: {ex.Message}", isError: true); - } - finally - { - btnLoadFromSonic.Enabled = _sonicDiscovery.HasConnections && !_isOperationRunning; - RefreshActionButtonStates(); - } - } - - /// - /// Färbt Zeilen mit unvollständiger Konfiguration (kein TargetDirectory) orange ein - /// damit erkennbar ist, dass diese Container noch konfiguriert werden müssen. - /// - private void MarkUnconfiguredRows() - { - foreach (DataGridViewRow row in dgvTargets.Rows) - { - if (row.Tag is not DeploymentTarget target) continue; - - if (string.IsNullOrWhiteSpace(target.TargetDirectory)) - { - row.DefaultCellStyle.ForeColor = GoldColor; - if (row.Cells["Status"] is DataGridViewCell statusCell) - { - statusCell.Value = "⚠ Pfad fehlt"; - } - } - } - } } } diff --git a/ZA.CoreService.ESBCertificateManager/Models/SonicConnection.cs b/ZA.CoreService.ESBCertificateManager/Models/SonicConnection.cs index 97622fe..0c7cba4 100644 --- a/ZA.CoreService.ESBCertificateManager/Models/SonicConnection.cs +++ b/ZA.CoreService.ESBCertificateManager/Models/SonicConnection.cs @@ -1,390 +1,39 @@ namespace ZA.CoreService.ESBCertificateManager.Models; /// -/// Verbindungskonfiguration für eine Progress Sonic ESB Management Instanz. -/// Name = Verbindungs-Alias (hier oft gleich ContainerName, z.B. "DE-Test"). -/// DomainName = Sonic-Domain (z.B. "proalpha-test"). -/// Container wird in KnownContainers / DeploymentTarget.ContainerName geführt (z.B. "DE-Test"). +/// Sonic-Verbindung für MfApi-Container-Restart (wie SMC). /// public sealed class SonicConnection { - /// - /// Alias der Verbindung; referenziert von . - /// + /// Alias; referenziert von DeploymentTarget.SonicConnectionName. public required string Name { get; init; } - /// Sonic-Domain, z.B. "proalpha-test" (nicht der Containername). + /// Sonic-Domain, z.B. proalpha-test. public required string DomainName { get; init; } - /// - /// Dieselbe Broker-/Management-URL wie in der Sonic Management Console (SMC), - /// z.B. "tcp://dekun-painwbdet:13070". Keine separate Domain-Console nötig. - /// + /// Broker-/Management-URL wie in der SMC, z.B. tcp://host:13070. public required string ConnectionUrl { get; init; } - /// Login wie in der Sonic Management Console (nicht für WinRM). public required string Username { get; init; } - - /// Passwort wie in der Sonic Management Console (nicht für WinRM). public required string Password { get; init; } - /// - /// Windows-Konto für WinRM (Invoke-Command -Credential). - /// Leer = aktueller Prozess-Benutzer ohne explizite Credentials. - /// Nicht mit / (Sonic SMC) verwechseln. - /// - public string WinRmUsername { get; init; } = string.Empty; - - /// Windows-Passwort für WinRM; nur relevant wenn gesetzt ist. - public string WinRmPassword { get; init; } = string.Empty; - - /// - /// MfApi = Sonic MF Management Runtime API über ConnectionUrl + SMC-Logins (Standard); - /// WinRm / LocalCmd = optionaler Fallback über stopcontainer/startcontainer; - /// HttpApi = REST (falls vorhanden). - /// - /// - /// Standard: MfApi = Management Application API (wie SMC): - /// JMSConnectorClient + MFProxyFactory.createAgentProxy + IAgentProxy.restart. - /// Laut CX-Messenger-Doku-Index: Docs2017/api/mgmt_api (nicht stopcontainer.bat). - /// LocalCmd nur wenn Server-Scripts existieren (bei reiner SMC oft nicht der Fall). - /// - public SonicManagementMode ManagementMode { get; init; } = SonicManagementMode.MfApi; - - /// - /// Effektiver Modus: bei WinRm und lokalem ConnectionUrl-Host wird LocalCmd erzwungen. - /// MfApi bleibt unverändert (nutzt Domain-Manager-Verbindung). - /// - public SonicManagementMode EffectiveManagementMode - => ManagementMode == SonicManagementMode.WinRm && IsConnectionHostLocal(ConnectionUrl) - ? SonicManagementMode.LocalCmd - : ManagementMode; - - /// True wenn WinRm konfiguriert war, aber wegen lokalem Host auf LocalCmd umgestellt wurde. - public bool IsLocalCmdAutoForced - => ManagementMode == SonicManagementMode.WinRm - && EffectiveManagementMode == SonicManagementMode.LocalCmd; - - /// Anzeigetext für UI. - public string ManagementModeDisplay => "MfApi (SMC Management Application API)"; - - /// - /// True wenn der Host aus ConnectionUrl dieser Maschine entspricht - /// (localhost / 127.0.0.1 / ::1 / Computername). - /// - public static bool IsConnectionHostLocal(string? connectionUrl) - { - if (string.IsNullOrWhiteSpace(connectionUrl)) - { - return false; - } - - string host; - try - { - host = new Uri(connectionUrl).Host; - } - catch - { - host = connectionUrl.Trim(); - } - - if (string.IsNullOrWhiteSpace(host)) - { - return false; - } - - if (host.Equals("localhost", StringComparison.OrdinalIgnoreCase) - || host.Equals("127.0.0.1", StringComparison.OrdinalIgnoreCase) - || host.Equals("::1", StringComparison.OrdinalIgnoreCase) - || host.Equals("[::1]", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - string machine = Environment.MachineName; - if (host.Equals(machine, StringComparison.OrdinalIgnoreCase) - || host.StartsWith(machine + ".", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - try - { - string dnsName = System.Net.Dns.GetHostName(); - if (!string.IsNullOrWhiteSpace(dnsName) - && (host.Equals(dnsName, StringComparison.OrdinalIgnoreCase) - || host.StartsWith(dnsName + ".", StringComparison.OrdinalIgnoreCase))) - { - return true; - } - } - catch - { - // DNS optional - } - - return false; - } - - /// - /// Sonic-/MQ-/ESB-Installationsroot, z.B. "C:\Sonic\MQ10.0" (Client-JARs oft unter lib). - /// Bei ESB-Home: ggf. MfClientLibPath auf MQ\lib setzen. - /// + /// Installationsroot (optional; Libs oft unter lib). 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/JRE_HOME, setenv.bat, rekursiv unter SonicHome, Registry, …). - /// + /// Optional: JRE/JDK-Home. public string JavaHome { get; init; } = string.Empty; - /// - /// Optionaler direkter Pfad zu java.exe (überschreibt JavaHome wenn gesetzt). - /// + /// Optional: direkter Pfad zu java.exe. public string JavaPath { get; init; } = string.Empty; - /// - /// Optionaler Pfad zu Sonic-Client-JARs für MfApi (mgmt_client.jar etc.). - /// Leer = SonicHome\lib, MQ_HOME\lib, ESB_HOME\lib bzw. nested MQ*/lib. - /// + /// Ordner mit mgmt_client.jar / mfcontext.jar / … public string MfClientLibPath { get; init; } = string.Empty; - /// - /// Container-Namen (kurz), z.B. ["DE-Test"]. Nicht die Domain. - /// + /// Container-Kurznamen, z.B. ct-ZADBService. public List KnownContainers { get; init; } = []; - public int ManagementHttpPort { get; init; } = 8080; - public string ApiBasePath { get; init; } = "/api/v1"; - public string ContainerListPath { get; init; } = string.Empty; - public string ContainerRestartPath { get; init; } = string.Empty; - public string ContainerStopPath { get; init; } = string.Empty; - public string ContainerStartPath { get; init; } = string.Empty; - - public int WinRmPort { get; init; } = 5985; - public string WinRmRestartScript { get; init; } = string.Empty; - public string WinRmContainerListScript { get; init; } = string.Empty; - public string WinRmXapiImportScript { get; init; } = string.Empty; - public int TimeoutSeconds { get; init; } = 120; public int PostRestartDelaySeconds { get; init; } = 20; - public string ResolveRestartScript() - => string.IsNullOrWhiteSpace(WinRmRestartScript) - ? DefaultRestartScript - : WinRmRestartScript; - - public string ResolveContainerListScript() - => string.IsNullOrWhiteSpace(WinRmContainerListScript) - ? DefaultContainerListScript - : WinRmContainerListScript; - - /// - /// Offizieller MF-Container-Neustart laut Aurea CX Messenger Doku: - /// SonicHome\bin\stopcontainer.bat Domain.Container - /// SonicHome\bin\startcontainer.bat Domain.Container - /// Danach Prozess-Verifikation (alte PIDs weg, neue PIDs da). - /// - public const string DefaultRestartScript = - """ - $ErrorActionPreference = 'Stop' - $sonicHome = '{sonicHome}' - $domain = '{domain}' - $container = '{container}' - - $bin = Join-Path $sonicHome 'bin' - $stopBat = Join-Path $bin 'stopcontainer.bat' - $startBat = Join-Path $bin 'startcontainer.bat' - - # Kurzname = Container (z.B. DE-Test), Full = Domain.Container (z.B. proalpha-test.DE-Test) - $shortName = if ($container -like '*.*') { ($container -split '\.', 2)[1] } else { $container } - $fullName = if ($container -like '*.*') { $container } else { "$domain.$container" } - $markers = @($shortName, $fullName, "$domain.$shortName") | Select-Object -Unique - - function Get-ContainerPids { - $pids = @() - Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | - Where-Object { - $cmd = $_.CommandLine - if (-not $cmd) { return $false } - if ($_.Name -notmatch 'java|javaw|sonic') { return $false } - foreach ($m in $markers) { - if ($cmd -like "*$m*") { return $true } - } - return $false - } | - ForEach-Object { $pids += [int]$_.ProcessId } - return @($pids | Select-Object -Unique) - } - - function Wait-PidsGone([int[]]$pids, [int]$seconds) { - $deadline = (Get-Date).AddSeconds($seconds) - while ((Get-Date) -lt $deadline) { - $alive = @($pids | Where-Object { Get-Process -Id $_ -ErrorAction SilentlyContinue }) - if ($alive.Count -eq 0) { return $true } - Start-Sleep -Seconds 2 - } - return $false - } - - function Wait-NewPids([int[]]$oldPids, [int]$seconds) { - $deadline = (Get-Date).AddSeconds($seconds) - while ((Get-Date) -lt $deadline) { - $now = @(Get-ContainerPids) - $fresh = @($now | Where-Object { $oldPids -notcontains $_ }) - if ($fresh.Count -gt 0) { return ,$fresh } - Start-Sleep -Seconds 2 - } - return ,@() - } - - function Invoke-ContainerBat([string]$bat, [string]$name) { - if (-not (Test-Path -LiteralPath $bat)) { - throw "Sonic BAT fehlt: $bat (SonicHome pruefen)" - } - $arg = '/c "' + $bat + '" "' + $name + '"' - $p = Start-Process -FilePath 'cmd.exe' -ArgumentList $arg -Wait -PassThru -NoNewWindow - return [int]$p.ExitCode - } - - Write-Output "INFO:Domain=$domain Container=$shortName Full=$fullName" - Write-Output "INFO:RestartVia=stopcontainer/startcontainer (MF Container)" - $before = @(Get-ContainerPids) - Write-Output "INFO:PIDsBefore=$($before -join ',')" - - if ($before.Count -eq 0) { - Write-Output "WARN:No running Java/Sonic process for '$shortName' - starting anyway" - } - - # 1) Offiziell: stopcontainer.bat Domain.Container (danach Kurzname als Fallback) - $stopOk = $false - foreach ($n in @($fullName, $shortName)) { - Write-Output "INFO:stopcontainer $n" - $code = Invoke-ContainerBat -bat $stopBat -name $n - Write-Output "INFO:stopcontainer ExitCode=$code Name=$n" - if ($code -eq 0) { $stopOk = $true; break } - } - if (-not $stopOk) { - Write-Output "WARN:stopcontainer non-zero; will force-stop remaining PIDs if any" - } - - Start-Sleep -Seconds 3 - $still = @(Get-ContainerPids) - - # 2) Force-Stop falls BAT den Prozess nicht beendet (sonst kein echter Restart in SMC) - if ($still.Count -gt 0) { - Write-Output "INFO:Force-Stop PIDs=$($still -join ',')" - foreach ($procId in $still) { - Stop-Process -Id $procId -Force -ErrorAction SilentlyContinue - } - } - - if ($before.Count -gt 0) { - if (-not (Wait-PidsGone -pids $before -seconds 45)) { - throw "Container process still running after stop (PIDs=$($before -join ','))" - } - Write-Output "INFO:Old PIDs gone" - } - - Start-Sleep -Seconds 3 - - # 3) Offiziell: startcontainer.bat Domain.Container - $started = $false - foreach ($n in @($fullName, $shortName)) { - Write-Output "INFO:startcontainer $n" - $code = Invoke-ContainerBat -bat $startBat -name $n - Write-Output "INFO:startcontainer ExitCode=$code Name=$n" - if ($code -eq 0) { $started = $true; break } - } - - if (-not $started) { - throw "startcontainer failed for $fullName / $shortName" - } - - $after = @(Wait-NewPids -oldPids $before -seconds 60) - if ($after.Count -eq 0) { - $any = @(Get-ContainerPids) - if ($any.Count -eq 0) { - throw "After start no process for container '$shortName'. Check SMC / SonicHome / ContainerName." - } - Write-Output "INFO:PIDsAfter=$($any -join ',')" - } - else { - Write-Output "INFO:PIDsAfter=$($after -join ',')" - } - - Write-Output "OK:ContainerRestartVerified Domain=$domain Container=$shortName" - """; - - public const string DefaultContainerListScript = - """ - $ErrorActionPreference = 'Continue' - $sonicHome = '{sonicHome}' - $domain = '{domain}' - $names = New-Object System.Collections.Generic.List[string] - - function Add-Name([string]$n) { - if ([string]::IsNullOrWhiteSpace($n)) { return } - $n = $n.Trim() - if (-not $names.Contains($n)) { [void]$names.Add($n) } - } - - if (-not (Test-Path -LiteralPath $sonicHome)) { - Write-Output "WARN:SonicHomeNichtGefunden:$sonicHome" - } - else { - Write-Output "INFO:SonicHomeOk:$sonicHome Domain=$domain" - - Get-ChildItem -LiteralPath $sonicHome -Directory -ErrorAction SilentlyContinue | - ForEach-Object { - if ($_.Name -like '*.cache') { - Add-Name ($_.Name -replace '\.cache$', '') - } - Get-ChildItem -LiteralPath $_.FullName -Directory -ErrorAction SilentlyContinue | - Where-Object { $_.Name -like '*.cache' } | - ForEach-Object { Add-Name ($_.Name -replace '\.cache$', '') } - } - - Get-ChildItem -LiteralPath $sonicHome -Recurse -Filter 'container.xml' -File -ErrorAction SilentlyContinue | - Select-Object -First 40 | - ForEach-Object { - Add-Name (Split-Path $_.DirectoryName -Leaf) - } - } - - Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | - Where-Object { $_.CommandLine -match 'sonic|mf\.framework|container\.xml' } | - ForEach-Object { - if ($_.CommandLine -match '([A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+)\.cache') { - Add-Name $Matches[1] - } - elseif ($_.CommandLine -match '\\([A-Za-z0-9_\-]+)\\container\.xml') { - Add-Name $Matches[1] - } - } - - # Kurzform Domain.Container → Container - foreach ($n in @($names.ToArray())) { - if ($n -like '*.*') { - $parts = $n -split '\.', 2 - if ($parts.Count -eq 2) { Add-Name $parts[1] } - } - } - - if ($names.Count -eq 0) { - Write-Output "WARN:KeineContainerGefunden Domain=$domain SonicHome=$sonicHome" - } - - $names | Sort-Object -Unique - """; -} - -public enum SonicManagementMode -{ - HttpApi = 0, - WinRm = 1, - LocalCmd = 2, - /// Sonic MF Management API über Domain-Manager (ConnectionUrl + SMC-Credentials). - MfApi = 3 + public string ManagementModeDisplay => "MfApi (SMC Restart)"; } diff --git a/ZA.CoreService.ESBCertificateManager/Services/SonicContainerDiscovery.cs b/ZA.CoreService.ESBCertificateManager/Services/SonicContainerDiscovery.cs index 739a156..e41ef1c 100644 --- a/ZA.CoreService.ESBCertificateManager/Services/SonicContainerDiscovery.cs +++ b/ZA.CoreService.ESBCertificateManager/Services/SonicContainerDiscovery.cs @@ -3,8 +3,7 @@ using ZA.CoreService.ESBCertificateManager.Models; namespace ZA.CoreService.ESBCertificateManager.Services; /// -/// Fragt konfigurierte Sonic-Verbindungen nach Containern ab -/// und merged Remote-Treffer mit KnownContainers / Sample-Zielen. +/// Liefert Ziele nur aus appsettings KnownContainers (kein Live-Ping/List). /// public sealed class SonicContainerDiscovery { @@ -18,9 +17,6 @@ public sealed class SonicContainerDiscovery public bool HasConnections => _connections.Count > 0; public IReadOnlyList Connections => _connections; - /// - /// Ziele nur aus appsettings KnownContainers (kein Offline-JSON). - /// public IReadOnlyList BuildTargetsFromConfig() { List result = []; @@ -49,252 +45,4 @@ public sealed class SonicContainerDiscovery return result; } - - public async Task DiscoverAsync( - string connectionName, - IReadOnlyList knownTargets, - CancellationToken cancellationToken = default) - { - SonicConnection? connection = _connections - .FirstOrDefault(c => string.Equals(c.Name, connectionName, StringComparison.OrdinalIgnoreCase)); - - if (connection is null) - { - return SonicDiscoveryResult.Failed(connectionName, - $"Sonic-Verbindung '{connectionName}' ist nicht in AppSettings konfiguriert."); - } - - using SonicManagementClient client = new(connection); - - (bool reachable, string? pingError, string? resolvedPath) = await client.CheckConnectionAsync(cancellationToken); - - List diagnostics = []; - List remoteContainers = []; - bool listOk = false; - string? listError = null; - - if (!reachable) - { - // Trotzdem KnownContainers nutzen – Neustart kann über SonicBin (stop/startcontainer) gehen. - diagnostics.Add($"Management-Ping: {pingError}"); - if (connection.KnownContainers.Count == 0 - && !knownTargets.Any(t => - string.Equals(t.SonicConnectionName, connection.Name, StringComparison.OrdinalIgnoreCase) - && !string.IsNullOrWhiteSpace(t.ContainerName))) - { - return SonicDiscoveryResult.Failed(connectionName, - $"Management-Konsole nicht erreichbar: {pingError}"); - } - } - else - { - (listOk, IReadOnlyList remoteNames, listError) = - await client.GetContainersAsync(cancellationToken); - - foreach (string line in remoteNames) - { - if (line.StartsWith("WARN:", StringComparison.OrdinalIgnoreCase) - || line.StartsWith("INFO:", StringComparison.OrdinalIgnoreCase)) - { - diagnostics.Add(line); - continue; - } - - remoteContainers.Add(line); - } - - if (!listOk) - { - diagnostics.Add($"Listen-Fehler: {listError}"); - remoteContainers = []; - } - } - - // Fallback: explizit in appsettings hinterlegte Container - List mergedNames = MergeContainerNames( - remoteContainers, - connection.KnownContainers, - knownTargets - .Where(t => string.Equals(t.SonicConnectionName, connection.Name, StringComparison.OrdinalIgnoreCase)) - .Select(t => t.ContainerName) - .Where(n => !string.IsNullOrWhiteSpace(n))); - - List discovered = BuildTargets(connection, mergedNames, knownTargets); - - string? hint = BuildHint(connection, remoteContainers.Count, connection.KnownContainers.Count, diagnostics, listOk, listError); - - return new SonicDiscoveryResult - { - ConnectionName = connectionName, - DomainName = connection.DomainName, - Success = true, - ErrorMessage = hint, - DiscoveredTargets = discovered, - RawContainerNames = remoteContainers, - ResolvedPath = resolvedPath - }; - } - - private static List MergeContainerNames( - IEnumerable remote, - IEnumerable knownConfigured, - IEnumerable knownFromTargets) - { - List result = []; - - foreach (string name in remote.Concat(knownConfigured).Concat(knownFromTargets)) - { - if (string.IsNullOrWhiteSpace(name)) - { - continue; - } - - if (!result.Any(existing => ContainerNamesMatch(existing, name, domain: null))) - { - result.Add(name.Trim()); - } - } - - return result; - } - - private static string? BuildHint( - SonicConnection connection, - int remoteCount, - int knownConfigCount, - List diagnostics, - bool listOk, - string? listError) - { - List parts = []; - - if (!listOk && !string.IsNullOrWhiteSpace(listError)) - { - parts.Add(listError!); - } - - if (remoteCount == 0) - { - parts.Add( - $"Remote hat 0 Container geliefert (Domain '{connection.DomainName}', SonicHome='{connection.SonicHome}')."); - - if (knownConfigCount > 0) - { - parts.Add($"Fallback: {knownConfigCount} KnownContainers aus appsettings."); - } - else - { - parts.Add("Tipp: KnownContainers in appsettings setzen oder SonicHome korrigieren."); - } - } - - foreach (string d in diagnostics.Take(3)) - { - parts.Add(d); - } - - return parts.Count == 0 ? null : string.Join(" ", parts); - } - - private static List BuildTargets( - SonicConnection connection, - IReadOnlyList containerNames, - IReadOnlyList knownTargets) - { - List result = []; - int syntheticId = -1; - - foreach (string containerName in containerNames) - { - DeploymentTarget? existing = knownTargets.FirstOrDefault(t => - string.Equals(t.SonicConnectionName, connection.Name, StringComparison.OrdinalIgnoreCase) - && ContainerNamesMatch(t.ContainerName, containerName, connection.DomainName)); - - if (existing is not null) - { - if (!result.Any(r => r.Id == existing.Id)) - { - result.Add(existing); - } - } - else - { - result.Add(new DeploymentTarget - { - Id = syntheticId--, - Name = $"{connection.Name} / {containerName}", - Environment = connection.DomainName, - IsActive = true, - TargetDirectory = string.Empty, - CertificateFileName = string.Empty, - ContainerName = containerName, - RestartType = RestartType.SonicContainer, - SonicConnectionName = connection.Name, - SortOrder = result.Count * 10 - }); - } - } - - foreach (DeploymentTarget known in knownTargets) - { - if (!string.Equals(known.SonicConnectionName, connection.Name, StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - bool alreadyAdded = result.Any(r => - ContainerNamesMatch(r.ContainerName, known.ContainerName, connection.DomainName) - || r.Id == known.Id); - - if (!alreadyAdded) - { - result.Add(known); - } - } - - return result; - } - - internal static bool ContainerNamesMatch(string? a, string? b, string? domain) - { - if (string.IsNullOrWhiteSpace(a) || string.IsNullOrWhiteSpace(b)) - { - return false; - } - - if (string.Equals(a, b, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - string aShort = StripDomainPrefix(a, domain); - string bShort = StripDomainPrefix(b, domain); - return string.Equals(aShort, bShort, StringComparison.OrdinalIgnoreCase); - } - - private static string StripDomainPrefix(string name, string? domain) - { - if (!string.IsNullOrWhiteSpace(domain) - && name.StartsWith(domain + ".", StringComparison.OrdinalIgnoreCase)) - { - return name[(domain.Length + 1)..]; - } - - int dot = name.IndexOf('.'); - return dot > 0 ? name[(dot + 1)..] : name; - } -} - -public sealed class SonicDiscoveryResult -{ - public required string ConnectionName { get; init; } - public string DomainName { get; init; } = string.Empty; - public bool Success { get; init; } - public string? ErrorMessage { get; init; } - public string? ResolvedPath { get; init; } - public IReadOnlyList DiscoveredTargets { get; init; } = []; - public IReadOnlyList RawContainerNames { get; init; } = []; - - public static SonicDiscoveryResult Failed(string connectionName, string error) - => new() { ConnectionName = connectionName, Success = false, ErrorMessage = error }; } diff --git a/ZA.CoreService.ESBCertificateManager/Services/SonicManagementClient.cs b/ZA.CoreService.ESBCertificateManager/Services/SonicManagementClient.cs index 56bb6c3..9552a80 100644 --- a/ZA.CoreService.ESBCertificateManager/Services/SonicManagementClient.cs +++ b/ZA.CoreService.ESBCertificateManager/Services/SonicManagementClient.cs @@ -3,7 +3,7 @@ using ZA.CoreService.ESBCertificateManager.Models; namespace ZA.CoreService.ESBCertificateManager.Services; /// -/// Sonic MfApi (wie SMC-Neustart) – ohne WinRM/HTTP/XApi. +/// Schlanke Fassade: nur Container-Neustart über MfApi. /// public sealed class SonicManagementClient : IDisposable { @@ -16,45 +16,6 @@ public sealed class SonicManagementClient : IDisposable _mfApi = new SonicMfApiExecutor(connection); } - public async Task<(bool Success, string? Error, string? ResolvedPath)> CheckConnectionAsync( - CancellationToken cancellationToken = default) - { - (bool ok, string? error) = await _mfApi.TestConnectionAsync(cancellationToken); - if (ok) - { - return (true, null, $"MfApi {_connection.ConnectionUrl} Domain={_connection.DomainName}"); - } - - if (_connection.KnownContainers.Count > 0 - && await IsTcpReachableAsync(_connection.ConnectionUrl, cancellationToken)) - { - return (true, null, - $"MfApi-Ping fehlgeschlagen, KnownContainers/TCP ok. Hinweis: {Truncate(error)}"); - } - - return (false, error ?? "MfApi-Verbindung fehlgeschlagen", null); - } - - public async Task<(bool Success, IReadOnlyList ContainerNames, string? Error)> GetContainersAsync( - CancellationToken cancellationToken = default) - { - (bool ok, IReadOnlyList names, string? error) = - await _mfApi.ListContainersAsync(cancellationToken); - - if (ok && names.Count > 0) - { - return (true, names, null); - } - - if (_connection.KnownContainers.Count > 0) - { - return (true, _connection.KnownContainers, - ok ? null : $"Liste leer/fehlerhaft – KnownContainers. {error}"); - } - - return (false, [], error ?? "Keine Container gefunden."); - } - public async Task<(bool Success, string Status, string? Detail)> RestartContainerAsync( string containerName, CancellationToken cancellationToken = default) @@ -77,27 +38,6 @@ public sealed class SonicManagementClient : IDisposable $"Fehler: {error}\n\n{output}"); } - private static async Task IsTcpReachableAsync(string connectionUrl, CancellationToken cancellationToken) - { - try - { - Uri uri = new(connectionUrl); - using System.Net.Sockets.TcpClient tcp = new(); - using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - cts.CancelAfter(TimeSpan.FromSeconds(5)); - await tcp.ConnectAsync(uri.Host, uri.Port > 0 ? uri.Port : 2506, cts.Token); - return true; - } - catch - { - return false; - } - } - - private static string Truncate(string? s, int max = 180) - => string.IsNullOrWhiteSpace(s) ? string.Empty - : s.Length <= max ? s : s[..max] + "…"; - public void Dispose() { } diff --git a/ZA.CoreService.ESBCertificateManager/Services/SonicMfApiExecutor.cs b/ZA.CoreService.ESBCertificateManager/Services/SonicMfApiExecutor.cs index 034f21c..f495fd9 100644 --- a/ZA.CoreService.ESBCertificateManager/Services/SonicMfApiExecutor.cs +++ b/ZA.CoreService.ESBCertificateManager/Services/SonicMfApiExecutor.cs @@ -1,23 +1,14 @@ using System.Diagnostics; using System.Text; -using System.Text.RegularExpressions; -using Microsoft.Win32; using ZA.CoreService.ESBCertificateManager.Models; namespace ZA.CoreService.ESBCertificateManager.Services; /// -/// Führt Sonic-Container-Operationen über die offizielle MF Management Runtime API aus -/// (JMSConnectorClient + MFProxyFactory + IAgentProxy.restart), per Java-Hilfsprogramm -/// und Client-JARs unter SonicHome/lib bzw. MfClientLibPath. -/// Nutzt ConnectionUrl + Username/Password aus appsettings (SMC / Domain Manager). +/// Startet nur den Container-Restart über SonicMfContainerTool (Java + MF-Client-JARs). /// 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) @@ -25,88 +16,25 @@ 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); - - public async Task<(bool Success, IReadOnlyList Containers, string? Error)> ListContainersAsync( - CancellationToken cancellationToken = default) - { - (bool ok, string? output, string? error) = await RunRawAsync("list", null, cancellationToken); - if (!ok) - { - return (false, [], error ?? output); - } - - List names = (output ?? string.Empty) - .Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) - .Where(l => !l.StartsWith("OK:", StringComparison.OrdinalIgnoreCase)) - .Where(l => !l.StartsWith("INFO:", StringComparison.OrdinalIgnoreCase)) - .Where(l => !l.StartsWith("ERROR:", StringComparison.OrdinalIgnoreCase)) - .Where(l => !l.StartsWith("WARN:", StringComparison.OrdinalIgnoreCase)) - .Where(l => !l.StartsWith("Usage:", StringComparison.OrdinalIgnoreCase)) - .ToList(); - - return (true, names, null); + return (sonicHome, ResolveJavaExe()); } public async Task<(bool Success, string? Output, string? Error)> RestartAsync( string containerName, CancellationToken cancellationToken = default) - { - (bool ok, string? output, string? error) = await RunRawAsync("restart", containerName, cancellationToken); - if (!ok) - { - return (false, output, error); - } - - bool invoked = (output ?? string.Empty) - .Contains("OK:RestartInvoked", StringComparison.OrdinalIgnoreCase); - return invoked - ? (true, output, error) - : (false, output, error ?? "Kein OK:RestartInvoked von SonicMfContainerTool."); - } - - private async Task<(bool Success, string? Error)> RunAsync( - string command, - string? container, - CancellationToken cancellationToken) - { - (bool ok, string? output, string? error) = await RunRawAsync(command, container, cancellationToken); - if (ok) - { - return (true, null); - } - - return (false, error ?? output); - } - - private async Task<(bool Success, string? Output, string? Error)> RunRawAsync( - string command, - string? container, - CancellationToken cancellationToken) { (bool prepared, string? javaExe, string? classDir, string? classpath, string? prepareError) = - await PrepareToolAsync(cancellationToken); + PrepareTool(); if (!prepared) { return (false, null, prepareError); } - // ArgumentList statt manueller Quotes: sonst landet \"...\" im Classpath - // und Java findet JMSConnectorAddress trotz vorhandener JARs nicht. ProcessStartInfo psi = new() { FileName = javaExe!, @@ -122,22 +50,18 @@ public sealed class SonicMfApiExecutor psi.ArgumentList.Add("-cp"); psi.ArgumentList.Add(classpath!); psi.ArgumentList.Add("SonicMfContainerTool"); - psi.ArgumentList.Add(command); + 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()); - if (!string.IsNullOrWhiteSpace(container)) - { - psi.ArgumentList.Add("--container"); - psi.ArgumentList.Add(container); - } - psi.Environment["ESB_SONIC_PASSWORD"] = _connection.Password ?? string.Empty; using Process process = new() { StartInfo = psi }; @@ -167,8 +91,11 @@ public sealed class SonicMfApiExecutor string stderr = (await stderrTask).Trim(); string combined = string.Join("\n", new[] { stdout, stderr }.Where(s => s.Length > 0)); - if (process.ExitCode == 0 - && !combined.Contains("ERROR:", StringComparison.OrdinalIgnoreCase)) + bool ok = process.ExitCode == 0 + && combined.Contains("OK:RestartInvoked", StringComparison.OrdinalIgnoreCase) + && !combined.Contains("ERROR:", StringComparison.OrdinalIgnoreCase); + + if (ok) { return (true, combined, null); } @@ -180,1737 +107,157 @@ public sealed class SonicMfApiExecutor || combined.Contains("JMSConnectorAddress", StringComparison.OrdinalIgnoreCase)) { err += - "\n\nClasspath unvollständig: com.sonicsw.mf.jmx.client.JMSConnectorAddress fehlt.\n" + - "Bitte in appsettings MfClientLibPath auf den Ordner mit mgmt_client.jar / mfcontext.jar / sonic_Client.jar setzen\n" + - $"(aktuell SonicHome='{_connection.SonicHome}').\n" + - "Hinweis: Container-Kurzname laut SMC ist 'ct-ZADBService' (nicht der Verbindungs-Alias DE-Test)."; + "\n\nClasspath unvollständig. MfClientLibPath auf Ordner mit mgmt_client.jar / mfcontext.jar setzen."; } - if (!combined.Contains("ToolVersion=", StringComparison.OrdinalIgnoreCase)) - { - err += - "\n\nWARN: Ausgabe enthält kein INFO:ToolVersion=… – vermutlich alte SonicMfContainerTool.class.\n" + - "App neu bauen (Debug+Release), Tools-Ordner im Output prüfen, Temp esb-sonic-mf* löschen."; - } - - // Volle Ausgabe mitliefern (INFO/WARN), nicht nur die ERROR-Zeile string detail = string.IsNullOrWhiteSpace(combined) ? err : err + "\n\n--- Tool-Output ---\n" + combined; return (false, combined.Length > 0 ? combined : null, detail); } - private static void TryDeleteTempToolCache() + private (bool Ok, string? JavaExe, string? ClassDir, string? Classpath, string? Error) PrepareTool() { - try - { - string dir = Path.Combine(Path.GetTempPath(), "esb-sonic-mf-j8"); - if (Directory.Exists(dir)) - { - Directory.Delete(dir, recursive: true); - } - } - catch - { - /* ignore */ - } - } - - private async Task<(bool Ok, string? JavaExe, string? ClassDir, string? Classpath, string? Error)> - PrepareToolAsync(CancellationToken cancellationToken) - { - // Tool-Bytecode ist Java 8 (major 52). Sonic-mitgelieferte JREs sind oft Java 6/7 → unbrauchbar. - // Deshalb: Runtime mit major>=52 bevorzugen (PATH/JAVA_HOME/Registry), Libs weiter aus SonicHome. - const int minClassMajor = 52; - - JavaDiscoveryResult javaResult = DiscoverJava(); - string? javaExe = javaResult.JavaExe; - int runtimeMajor = javaExe is null - ? 0 - : await GetJavaClassMajorAsync(javaExe, cancellationToken); - - if (javaExe is null || runtimeMajor < minClassMajor) - { - (string? newerJava, int newerMajor, List probed) = - await FindJavaWithMinimumMajorAsync(minClassMajor, cancellationToken); - if (newerJava is not null) - { - javaExe = newerJava; - runtimeMajor = newerMajor; - javaResult.CheckedPaths.Add($"UPGRADE:RuntimeJava8+={newerJava} major={newerMajor}"); - } - } - + string? javaExe = ResolveJavaExe(); if (javaExe is null) { - 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)); - return (false, null, null, null, - "Java nicht gefunden.\n" + - $"Verwendetes SonicHome: '{sonicHome}'\n" + - "Konfiguration: JavaPath/JavaHome auf eine Java-8+-Runtime setzen.\n" + - "Geprüfte Pfade:\n - " + checkedList); + "Java nicht gefunden. In appsettings JavaPath auf java.exe (Java 8+) setzen."); } - if (runtimeMajor < minClassMajor) - { - return (false, null, null, null, - "Gefundene Java-Runtime ist zu alt für SonicMfContainerTool (braucht Java 8 / major 52).\n" + - $"Aktuell: {javaExe} (class major={runtimeMajor}).\n" + - "Die JRE unter SonicHome ist oft Java 6/7 – bitte Java 8+ installieren und in appsettings setzen:\n" + - " \"JavaPath\": \"C:\\\\Program Files\\\\Java\\\\jre1.8.0_xxx\\\\bin\\\\java.exe\"\n" + - "Sonic-Client-JARs bleiben unter SonicHome\\lib."); - } - - (string? libClasspath, string? libError) = ResolveSonicClasspathDetailed(); + (string? libClasspath, string? libError) = ResolveSonicClasspath(); if (libClasspath is null) { return (false, null, null, null, libError); } - // Vorcompilierte .class nur nutzen, wenn Class-Version passt. - // Tool-Verzeichnis ZUERST im Classpath, damit keine alte Klasse aus Temp/JAR gewinnt. - if (TryResolvePrebuiltTool(runtimeMajor, out string? prebuiltDir, out string? prebuiltEntry, out string? prebuiltError) - && prebuiltDir is not null && prebuiltEntry is not null) - { - // Veraltete Temp-Kopien entfernen (früheres Compile-Cache). - TryDeleteTempToolCache(); - string classpath = prebuiltEntry + Path.PathSeparator + libClasspath; - return (true, javaExe, prebuiltDir, classpath, null); - } - - // Zur Laufzeit aus .java für Java 8 kompilieren (--release 8 / -source 1.8 -target 1.8). - string? sourcePath = ResolveToolSourcePath(); - if (sourcePath is null) + string? toolsDir = ResolveToolsDir(); + if (toolsDir is null) { return (false, null, null, null, - (prebuiltError ?? "Kein SonicMfContainerTool gefunden.") + - " Tools\\SonicMfContainerTool.java/.class fehlen.\n" + - $"Runtime-Java: {javaExe} (class major max={runtimeMajor})."); + "Tools\\SonicMfContainerTool.class nicht gefunden. Projekt neu bauen."); } - TryDeleteTempToolCache(); - string workDir = Path.Combine(Path.GetTempPath(), "esb-sonic-mf-j8"); - Directory.CreateDirectory(workDir); - - string javaTarget = Path.Combine(workDir, "SonicMfContainerTool.java"); - string classFile = Path.Combine(workDir, "SonicMfContainerTool.class"); - - File.Copy(sourcePath, javaTarget, overwrite: true); - - string compileClasspath = libClasspath; - // workDir zuerst - string runtimeClasspath = workDir + Path.PathSeparator + libClasspath; - - bool classOk = File.Exists(classFile) - && ReadClassFileMajorVersion(classFile) is int existingMajor - && existingMajor <= runtimeMajor - && File.GetLastWriteTimeUtc(classFile) >= File.GetLastWriteTimeUtc(sourcePath); - - if (!classOk) - { - string? javac = ResolveJavacExecutable(javaExe); - if (javac is null) - { - return (false, null, null, null, - "SonicMfContainerTool muss für Java 8 gebaut werden, aber javac fehlt.\n" + - "Tools\\SonicMfContainerTool.class (major<=52) mit ausliefern oder JDK installieren.\n" + - $"Gefundene java.exe: {javaExe}"); - } - - // Wichtig: ohne --release 8 erzeugt modernes javac major 65 → UnsupportedClassVersionError auf Sonic-JRE 8. - string releaseArgs = SupportsJavacRelease8(javac) - ? "--release 8" - : "-source 1.8 -target 1.8"; - - ProcessStartInfo compilePsi = new() - { - FileName = javac, - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true, - StandardOutputEncoding = Encoding.UTF8, - StandardErrorEncoding = Encoding.UTF8 - }; - - if (releaseArgs.StartsWith("--release", StringComparison.Ordinal)) - { - compilePsi.ArgumentList.Add("--release"); - compilePsi.ArgumentList.Add("8"); - } - else - { - compilePsi.ArgumentList.Add("-source"); - compilePsi.ArgumentList.Add("1.8"); - compilePsi.ArgumentList.Add("-target"); - compilePsi.ArgumentList.Add("1.8"); - } - - compilePsi.ArgumentList.Add("-encoding"); - compilePsi.ArgumentList.Add("UTF-8"); - compilePsi.ArgumentList.Add("-cp"); - compilePsi.ArgumentList.Add(compileClasspath); - compilePsi.ArgumentList.Add("-d"); - compilePsi.ArgumentList.Add(workDir); - compilePsi.ArgumentList.Add(javaTarget); - - using Process compile = new() { StartInfo = compilePsi }; - if (!compile.Start()) - { - return (false, null, null, null, "javac konnte nicht gestartet werden."); - } - - string cOut = await compile.StandardOutput.ReadToEndAsync(cancellationToken); - string cErr = await compile.StandardError.ReadToEndAsync(cancellationToken); - await compile.WaitForExitAsync(cancellationToken); - - int? builtMajor = File.Exists(classFile) ? ReadClassFileMajorVersion(classFile) : null; - if (compile.ExitCode != 0 || builtMajor is null || builtMajor > runtimeMajor) - { - return (false, null, null, null, - "Kompilieren von SonicMfContainerTool für Java 8 fehlgeschlagen.\n" + - Truncate((cErr + "\n" + cOut).Trim()) + - $"\nErzeugt major={builtMajor?.ToString() ?? "?"}, Runtime erlaubt <={runtimeMajor}.\n" + - $"Classpath: {Truncate(compileClasspath, 240)}"); - } - } - - return (true, javaExe, workDir, runtimeClasspath, null); + string classpath = toolsDir + Path.PathSeparator + libClasspath; + return (true, javaExe, toolsDir, classpath, null); } - private bool TryResolvePrebuiltTool( - int runtimeMajor, - out string? workDir, - out string? classpathEntry, - out string? error) + private string? ResolveJavaExe() { - workDir = null; - classpathEntry = null; - error = null; + foreach (string? candidate in EnumerateJavaCandidates()) + { + if (!string.IsNullOrWhiteSpace(candidate) && File.Exists(candidate)) + { + return Path.GetFullPath(candidate); + } + } - string[] searchDirs = + 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 searchDirs.Where(Directory.Exists)) + foreach (string dir in dirs.Where(Directory.Exists)) { - string classFile = Path.Combine(dir, "SonicMfContainerTool.class"); - if (File.Exists(classFile)) + if (File.Exists(Path.Combine(dir, "SonicMfContainerTool.class"))) { - int? major = ReadClassFileMajorVersion(classFile); - // Alte Builds waren ~7KB; MBean-Stop-Tool ist >10KB. - long len = new FileInfo(classFile).Length; - if (major is not null && major <= runtimeMajor && len >= 10_000) - { - workDir = dir; - classpathEntry = dir; - return true; - } - - if (major is not null && major <= runtimeMajor && len < 10_000) - { - error = - $"Tools\\SonicMfContainerTool.class ist veraltet ({len} Bytes). Bitte neu bauen/pullen (erwartet >=10000)."; - continue; - } - - error = - $"Tools\\SonicMfContainerTool.class ist zu neu (major={major}, braucht <={runtimeMajor} / Java 8)."; - continue; - } - - // JAR absichtlich nicht blind nutzen – oft noch mit Java 21 gebaut. - // Nur .class mit geprüftem major<=runtimeMajor. - } - - error ??= "Tools\\SonicMfContainerTool.class (Java 8) nicht gefunden."; - return false; - } - - private static int? ReadClassFileMajorVersion(string classFile) - { - try - { - byte[] header = new byte[8]; - using FileStream fs = File.OpenRead(classFile); - if (fs.Read(header, 0, 8) < 8) - { - return null; - } - - // CA FE BA BE | minor | major - if (header[0] != 0xCA || header[1] != 0xFE || header[2] != 0xBA || header[3] != 0xBE) - { - return null; - } - - return (header[6] << 8) | header[7]; - } - catch - { - return null; - } - } - - private static async Task GetJavaClassMajorAsync(string javaExe, CancellationToken cancellationToken) - { - try - { - ProcessStartInfo psi = new() - { - FileName = javaExe, - Arguments = "-version", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - }; - - using Process p = new() { StartInfo = psi }; - if (!p.Start()) - { - return 0; - } - - string err = await p.StandardError.ReadToEndAsync(cancellationToken); - string output = await p.StandardOutput.ReadToEndAsync(cancellationToken); - await p.WaitForExitAsync(cancellationToken); - return ParseJavaVersionToClassMajor(err + "\n" + output); - } - catch - { - return 0; - } - } - - private static int ParseJavaVersionToClassMajor(string versionText) - { - string text = versionText.ToLowerInvariant(); - - // 1.8.x → 52, 1.7.x → 51, 1.6.x → 50 - Match legacy = Regex.Match(text, @"version\s+""1\.(\d+)"); - if (legacy.Success && int.TryParse(legacy.Groups[1].Value, out int minor)) - { - return 44 + minor; // 1.6→50, 1.7→51, 1.8→52 - } - - Match modern = Regex.Match(text, @"version\s+""(\d+)"); - if (modern.Success && int.TryParse(modern.Groups[1].Value, out int major) && major >= 9) - { - return 44 + major; // 9→53 … 21→65 - } - - return 0; - } - - /// - /// Sucht eine java.exe mit mindestens der gewünschten Class-Major-Version - /// (für Tool-Bytecode Java 8 = 52), unabhängig von der alten Sonic-JRE. - /// - private async Task<(string? JavaExe, int Major, List Probed)> FindJavaWithMinimumMajorAsync( - int minMajor, - CancellationToken cancellationToken) - { - List probed = []; - List candidates = []; - - void AddCandidate(string? path) - { - if (string.IsNullOrWhiteSpace(path) || !File.Exists(path)) - { - return; - } - - string full = Path.GetFullPath(path); - if (!candidates.Contains(full, StringComparer.OrdinalIgnoreCase)) - { - candidates.Add(full); - } - } - - foreach (string? configured in new[] { _connection.JavaPath, _connection.JavaHome }) - { - if (string.IsNullOrWhiteSpace(configured)) continue; - AddCandidate(ResolveToolFromConfiguredPath(configured, JavaExeName, probed)); - } - - foreach (string envName in new[] { "JAVA_HOME", "JRE_HOME", "JDK_HOME" }) - { - AddCandidate(ResolveJavaFromHomeDir(Environment.GetEnvironmentVariable(envName), probed, envName)); - } - - AddCandidate(FindOnPath(JavaExeName, probed)); - AddCandidate(FindOnPath("java", probed)); - AddCandidate(FindJavaInWindowsRegistry(probed)); - AddCandidate(FindJavaViaWhereExe(probed)); - AddCandidate(FindJavaInCommonInstallLocations(probed)); - - // Program Files Java/Adoptium/Temurin grob scannen - foreach (string root in new[] - { - Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), - Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), - @"C:\Program Files\Eclipse Adoptium", - @"C:\Program Files\Java", - @"C:\Program Files\Microsoft", - @"C:\Program Files\Amazon Corretto" - }) - { - if (!Directory.Exists(root)) continue; - try - { - foreach (string javaExe in Directory.EnumerateFiles(root, "java.exe", SearchOption.AllDirectories) - .Take(40)) - { - AddCandidate(javaExe); - } - } - catch - { - // ignore - } - } - - foreach (string candidate in candidates) - { - int major = await GetJavaClassMajorAsync(candidate, cancellationToken); - probed.Add($"{candidate} => major={major}"); - if (major >= minMajor) - { - return (candidate, major, probed); - } - } - - return (null, 0, probed); - } - - private static bool SupportsJavacRelease8(string javacPath) - { - try - { - using Process p = Process.Start(new ProcessStartInfo - { - FileName = javacPath, - Arguments = "-version", - UseShellExecute = false, - RedirectStandardError = true, - RedirectStandardOutput = true, - CreateNoWindow = true - })!; - string text = (p.StandardError.ReadToEnd() + p.StandardOutput.ReadToEnd()).ToLowerInvariant(); - p.WaitForExit(5000); - // javac 9+ supports --release - return !text.Contains("1.8") && !text.Contains("1.7") && !text.Contains("1.6"); - } - catch - { - return false; - } - } - - /// - /// Baut einen Classpath mit echten Sonic-Management-Client-JARs - /// (inkl. com.sonicsw.mf.jmx.client.JMSConnectorAddress). - /// - private (string? Classpath, string? Error) ResolveSonicClasspathDetailed() - { - List<(string Path, string Label)> searchRoots = []; - - if (!string.IsNullOrWhiteSpace(_connection.MfClientLibPath)) - { - searchRoots.Add((_connection.MfClientLibPath.Trim().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)) - { - searchRoots.Add((mqHome.Trim().Trim('"'), "MQ_HOME")); - } - - if (!string.IsNullOrWhiteSpace(esbHome)) - { - searchRoots.Add((esbHome.Trim().Trim('"'), "ESB_HOME/SONIC_HOME")); - } - - if (!string.IsNullOrWhiteSpace(_connection.SonicHome)) - { - string sonicHome = _connection.SonicHome.Trim().Trim('"'); - searchRoots.Add((sonicHome, "SonicHome")); - string? parent = Directory.GetParent(sonicHome)?.FullName; - if (!string.IsNullOrWhiteSpace(parent)) - { - searchRoots.Add((parent, "SonicHome-Parent")); - } - } - - List checkedPaths = []; - HashSet seenDirs = new(StringComparer.OrdinalIgnoreCase); - List markerHits = []; - - foreach ((string root, string label) in searchRoots) - { - if (string.IsNullOrWhiteSpace(root) || !Directory.Exists(root)) - { - checkedPaths.Add($"{label}='{root}' (fehlt)"); - continue; - } - - checkedPaths.Add($"{label}='{root}'"); - - // 1) Bekannte Lib-Ordner - List libCandidates = [root, Path.Combine(root, "lib"), .. EnumerateLikelyLibRoots(root)]; - - // 2) Rekursiv nach Marker-JARs (SMC-only oft unter MQ*/lib oder Client/lib) - foreach (string jar in EnumerateMarkerJars(root, maxDepth: 5, maxFiles: 400)) - { - string? dir = Path.GetDirectoryName(jar); - if (!string.IsNullOrWhiteSpace(dir)) - { - libCandidates.Add(dir); - markerHits.Add(jar); - } - } - - foreach (string libDir in libCandidates) - { - if (string.IsNullOrWhiteSpace(libDir) || !seenDirs.Add(libDir) || !Directory.Exists(libDir)) - { - continue; - } - - if (!DirectoryHasRequiredSonicClientJars(libDir, out string? detail)) - { - if (!string.IsNullOrWhiteSpace(detail)) - { - checkedPaths.Add($" skip '{libDir}': {detail}"); - } - - continue; - } - - string? classpath = BuildClasspathFromLibDirs(CollectRelatedLibDirs(libDir, root)); - if (!string.IsNullOrWhiteSpace(classpath)) - { - return (classpath, null); - } - } - } - - string sonicHomeVal = _connection.SonicHome ?? string.Empty; - string markerHint = markerHits.Count == 0 - ? "Keine Marker-JARs (mgmt_client/mfcontext/sonic_Client) gefunden." - : "Marker-Treffer (unvollständig):\n - " + string.Join("\n - ", markerHits.Distinct(StringComparer.OrdinalIgnoreCase).Take(12)); - - return (null, - "Sonic-Client-JARs für MfApi fehlen (Class JMSConnectorAddress).\n" + - $"Verwendetes SonicHome: '{(string.IsNullOrWhiteSpace(sonicHomeVal) ? "(leer)" : sonicHomeVal)}'\n" + - "Benötigt u.a.: mgmt_client.jar, mfcontext.jar, sonic_Client.jar (Ordner mit *.jar).\n" + - "Lösung: MfClientLibPath auf den Ordner setzen, in dem diese JARs liegen\n" + - " (oft ...\\MQ10.0\\lib oder SMC-Client\\lib – nicht nur das JRE).\n" + - markerHint + "\n" + - "Geprüft:\n - " + string.Join("\n - ", checkedPaths.Take(40))); - } - - private static IEnumerable EnumerateMarkerJars(string root, int maxDepth, int maxFiles) - { - if (!Directory.Exists(root)) - { - yield break; - } - - string[] markers = - [ - "mgmt_client", "mfcontext", "sonic_Client", "sonic_client", - "mf_client", "mf-client", "mgmt-client", "sonic_Crypto", "sonic_XA" - ]; - - Queue<(string Dir, int Depth)> queue = new(); - queue.Enqueue((root, 0)); - int yielded = 0; - HashSet seen = new(StringComparer.OrdinalIgnoreCase); - - while (queue.Count > 0 && yielded < maxFiles) - { - (string dir, int depth) = queue.Dequeue(); - if (!seen.Add(dir)) - { - continue; - } - - IEnumerable files; - try - { - files = Directory.EnumerateFiles(dir, "*.jar", SearchOption.TopDirectoryOnly); - } - catch - { - continue; - } - - foreach (string file in files) - { - string name = Path.GetFileNameWithoutExtension(file); - if (markers.Any(m => name.Contains(m, StringComparison.OrdinalIgnoreCase))) - { - yielded++; - yield return file; - if (yielded >= maxFiles) - { - yield break; - } - } - } - - if (depth >= maxDepth) - { - continue; - } - - IEnumerable children; - try - { - children = Directory.EnumerateDirectories(dir); - } - catch - { - continue; - } - - foreach (string child in children) - { - string leaf = Path.GetFileName(child); - // Skip große / irrelevante Bäume - if (leaf.Equals("docs", StringComparison.OrdinalIgnoreCase) - || leaf.Equals("documentation", StringComparison.OrdinalIgnoreCase) - || leaf.Equals("samples", StringComparison.OrdinalIgnoreCase) - || leaf.Equals("log", StringComparison.OrdinalIgnoreCase) - || leaf.Equals("logs", StringComparison.OrdinalIgnoreCase) - || leaf.Equals("tmp", StringComparison.OrdinalIgnoreCase) - || leaf.Equals("temp", StringComparison.OrdinalIgnoreCase) - || leaf.StartsWith("jre", StringComparison.OrdinalIgnoreCase) - || leaf.Equals("jdk", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - queue.Enqueue((child, depth + 1)); - } - } - } - - private static List CollectRelatedLibDirs(string primaryLibDir, string searchRoot) - { - HashSet dirs = new(StringComparer.OrdinalIgnoreCase) { primaryLibDir }; - - // Sibling-Libs oft: MQ/lib + ESB/lib + common/lib - string? parent = Directory.GetParent(primaryLibDir)?.FullName; - if (!string.IsNullOrWhiteSpace(parent)) - { - foreach (string sibling in new[] - { - Path.Combine(parent, "lib"), - Path.Combine(parent, "common", "lib"), - Path.Combine(parent, "client", "lib") - }) - { - if (Directory.Exists(sibling)) - { - dirs.Add(sibling); - } - } - - string? grand = Directory.GetParent(parent)?.FullName; - if (!string.IsNullOrWhiteSpace(grand)) - { - foreach (string nested in EnumerateLikelyLibRoots(grand)) - { - dirs.Add(nested); - } - } - } - - if (Directory.Exists(searchRoot)) - { - foreach (string nested in EnumerateLikelyLibRoots(searchRoot)) - { - dirs.Add(nested); - } - } - - return dirs.Where(Directory.Exists).ToList(); - } - - private static string? BuildClasspathFromLibDirs(IEnumerable libDirs) - { - HashSet jarFiles = new(StringComparer.OrdinalIgnoreCase); - - foreach (string dir in libDirs) - { - if (!Directory.Exists(dir)) - { - continue; - } - - try - { - foreach (string jar in Directory.EnumerateFiles(dir, "*.jar", SearchOption.TopDirectoryOnly)) - { - jarFiles.Add(jar); - } - } - catch - { - // ignore unreadable dirs - } - } - - List entries = jarFiles - .OrderBy(j => j, StringComparer.OrdinalIgnoreCase) - .ToList(); - - if (entries.Count == 0) - { - return null; - } - - // Classpath-Länge begrenzen: zu viele Einträge → CreateProcess-Fehler - if (entries.Count > 250) - { - entries = entries.Take(250).ToList(); - } - - return string.Join(Path.PathSeparator, entries); - } - - 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; - } - } - } - } - - /// - /// Strikt: mindestens ein MF/Management-Client-JAR (nicht irgendein JAR-Ordner). - /// - private static bool DirectoryHasRequiredSonicClientJars(string path, out string? detail) - { - detail = null; - try - { - string[] jars = Directory.EnumerateFiles(path, "*.jar", SearchOption.TopDirectoryOnly).ToArray(); - if (jars.Length == 0) - { - detail = "keine *.jar"; - return false; - } - - string[] strong = - [ - "mgmt_client", "mfcontext", "sonic_Client", "sonic_client", "mf_client", "mf-client" - ]; - - bool hasStrong = jars.Any(j => - strong.Any(m => Path.GetFileName(j).Contains(m, StringComparison.OrdinalIgnoreCase))); - - if (!hasStrong) - { - detail = $"{jars.Length} JARs, aber ohne mgmt_client/mfcontext/sonic_Client"; - return false; - } - - return true; - } - catch (Exception ex) - { - detail = ex.Message; - return false; - } - } - - private static string? ResolveToolSourcePath() - { - string[] candidates = - [ - Path.Combine(AppContext.BaseDirectory, "Tools", "SonicMfContainerTool.java"), - Path.Combine(Directory.GetCurrentDirectory(), "Tools", "SonicMfContainerTool.java"), - Path.Combine(AppContext.BaseDirectory, "SonicMfContainerTool.java") - ]; - - return candidates.FirstOrDefault(File.Exists); - } - - 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 }) - { - if (string.IsNullOrWhiteSpace(configured)) - { - continue; - } - - string? fromConfig = ResolveToolFromConfiguredPath(configured, JavaExeName, checkedPaths); - if (fromConfig is not null) - { - result.JavaExe = fromConfig; - return result; - } - } - - foreach (string envName in new[] { "JAVA_HOME", "JRE_HOME", "JDK_HOME" }) - { - 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, checkedPaths) ?? FindOnPath("java", checkedPaths); - if (fromPath is not null) - { - result.JavaExe = fromPath; - return result; - } - - string? sonicHome = string.IsNullOrWhiteSpace(_connection.SonicHome) - ? null - : _connection.SonicHome.Trim(); - - if (!string.IsNullOrWhiteSpace(sonicHome)) - { - string? fromScripts = FindJavaFromSonicScripts(sonicHome, checkedPaths); - if (fromScripts is not null) - { - result.JavaExe = fromScripts; - return result; - } - - 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) - { - 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; - } - } - } - } - - 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)) - { - string sibling = Path.Combine(dir, javacName); - if (File.Exists(sibling)) - { - return sibling; - } - } - - foreach (string? configured in new[] { _connection.JavaPath, _connection.JavaHome }) - { - string? fromConfig = ResolveToolFromConfiguredPath(configured, javacName, ignored); - if (fromConfig is not null) - { - return fromConfig; - } - } - - foreach (string envName in new[] { "JAVA_HOME", "JDK_HOME" }) - { - string? javaHome = Environment.GetEnvironmentVariable(envName); - if (!string.IsNullOrWhiteSpace(javaHome)) - { - string candidate = Path.Combine(javaHome.Trim().Trim('"'), "bin", javacName); - if (File.Exists(candidate)) - { - return candidate; - } - } - } - - if (!string.IsNullOrWhiteSpace(_connection.SonicHome)) - { - foreach (string root in EnumerateJdkRootsUnderSonicHome(_connection.SonicHome)) - { - string candidate = Path.Combine(root, "bin", javacName); - if (File.Exists(candidate)) - { - return candidate; - } - } - } - - return FindOnPath(javacName, ignored) ?? FindOnPath("javac", ignored); - } - - private static string? ResolveToolFromConfiguredPath( - string? configured, - string exeName, - List checkedPaths) - { - if (string.IsNullOrWhiteSpace(configured)) - { - return null; - } - - string path = configured.Trim().Trim('"'); - NoteChecked(checkedPaths, $"config:{path}"); - - if (File.Exists(path)) - { - if (path.EndsWith(exeName, StringComparison.OrdinalIgnoreCase)) - { - return path; - } - - string? parent = Path.GetDirectoryName(path); - if (!string.IsNullOrWhiteSpace(parent)) - { - string sibling = Path.Combine(parent, exeName); - NoteChecked(checkedPaths, sibling); - if (File.Exists(sibling)) - { - return sibling; - } - } - - string fileName = Path.GetFileName(path); - if (fileName.Equals(Path.GetFileNameWithoutExtension(exeName), StringComparison.OrdinalIgnoreCase) - || fileName.Equals(exeName, StringComparison.OrdinalIgnoreCase)) - { - return path; - } - } - - string fromHome = Path.Combine(path, "bin", exeName); - NoteChecked(checkedPaths, fromHome); - return File.Exists(fromHome) ? fromHome : null; - } - - private static string? ResolveJavaFromHomeDir(string? home, List checkedPaths, string label) - { - if (string.IsNullOrWhiteSpace(home)) - { - NoteChecked(checkedPaths, $"{label}=(leer)"); - return 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? 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; - } - - foreach (string root in EnumerateJdkRootsUnderSonicHome(sonicHome)) - { - string candidate = Path.Combine(root, "bin", JavaExeName); - NoteChecked(checkedPaths, candidate); - if (File.Exists(candidate)) - { - return candidate; - } - } - - return null; - } - - private static IEnumerable EnumerateJdkRootsUnderSonicHome(string sonicHome) - { - if (!Directory.Exists(sonicHome)) - { - yield break; - } - - string[] preferredNames = - [ - "jre", "jdk", "JRE", "JDK", "jre64", "jdk64", "Java", "java" - ]; - - foreach (string name in preferredNames) - { - string path = Path.Combine(sonicHome, name); - if (Directory.Exists(path)) - { - yield return path; - string nestedJre = Path.Combine(path, "jre"); - if (Directory.Exists(nestedJre)) - { - yield return nestedJre; - } - } - } - - IEnumerable children; - try - { - children = Directory.EnumerateDirectories(sonicHome); - } - catch - { - yield break; - } - - foreach (string child in children) - { - foreach (string leaf in new[] { "jre", "jdk", "JRE", "JDK", "jre64", "jdk64" }) - { - string nested = Path.Combine(child, leaf); - if (Directory.Exists(nested)) - { - yield return nested; - } - } - - string leafName = Path.GetFileName(child); - if (leafName.StartsWith("jre", StringComparison.OrdinalIgnoreCase) - || leafName.StartsWith("jdk", StringComparison.OrdinalIgnoreCase)) - { - yield return child; - } - } - } - - 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 = - [ - Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), - Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), - @"C:\Program Files", - @"C:\Program Files (x86)" - ]; - - string[] vendorFolders = - [ - "Java", - "Eclipse Adoptium", - "AdoptOpenJDK", - "Microsoft", - "Amazon Corretto", - "Zulu", - "BellSoft", - "Semeru", - "Progress", - "Aurea" - ]; - - foreach (string root in roots.Where(r => !string.IsNullOrWhiteSpace(r)).Distinct(StringComparer.OrdinalIgnoreCase)) - { - foreach (string vendor in vendorFolders) - { - string vendorDir = Path.Combine(root, vendor); - if (!Directory.Exists(vendorDir)) - { - continue; - } - - string direct = Path.Combine(vendorDir, "bin", JavaExeName); - NoteChecked(checkedPaths, direct); - if (File.Exists(direct)) - { - return direct; - } - - try - { - foreach (string sub in Directory.EnumerateDirectories(vendorDir) - .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; - } - } - } - catch - { - // ignore inaccessible vendor dirs - } - } - } - - 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, List checkedPaths) - { - string? pathEnv = Environment.GetEnvironmentVariable("PATH"); - if (string.IsNullOrWhiteSpace(pathEnv)) - { - NoteChecked(checkedPaths, "PATH=(leer)"); - return null; - } - - foreach (string dir in pathEnv.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries)) - { - try - { - string candidate = Path.Combine(dir.Trim().Trim('"'), fileName); - NoteChecked(checkedPaths, $"PATH:{candidate}"); - if (File.Exists(candidate)) - { - return candidate; - } - } - catch - { - // ignore bad PATH entries + return dir; } } @@ -1927,29 +274,6 @@ public sealed class SonicMfApiExecutor } } - return string.IsNullOrWhiteSpace(combined) ? null : Truncate(combined); + return null; } - - private static string Quote(string value) - => "\"" + value.Replace("\"", "\\\"") + "\""; - - private static string EscapeArg(string value) - { - if (value.Length == 0) - { - return "\"\""; - } - - bool needsQuotes = value.Contains(' ') || value.Contains('\t') || value.Contains('"') - || value.Contains('*') || value.Contains(';'); - if (!needsQuotes) - { - return value; - } - - return "\"" + value.Replace("\"", "\\\"") + "\""; - } - - private static string Truncate(string s, int max = 800) - => s.Length <= max ? s : s[..max] + "…"; } diff --git a/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool$Args.class b/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool$Args.class index d1c1955..980efa7 100644 Binary files a/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool$Args.class and b/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool$Args.class differ diff --git a/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.class b/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.class index 105c868..8898afe 100644 Binary files a/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.class and b/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.class differ diff --git a/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.java b/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.java index 2f1caaa..a96c522 100644 --- a/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.java +++ b/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.java @@ -1,27 +1,15 @@ import java.lang.reflect.Method; import java.util.Hashtable; -import java.util.Iterator; -import java.util.Set; -import javax.management.MBeanOperationInfo; -import javax.management.MBeanInfo; import javax.management.ObjectName; /** - * Sonic Management Application API helper (same restart intent as SMC). - * - * TOOL_VERSION is printed first so we can verify the deployed build. - * - * Remote JMSConnectorClient is "unbounded". IAgentProxy.restart() often throws - * "Operation unsupported for unbounded client connector". SMC Restart typically - * stops the agent via JMX; Launch Daemon brings it back. - * + * Minimaler Sonic-Restart (wie SMC): JMSConnectorClient + MBean stop/restart. * Compile: javac --release 8 SonicMfContainerTool.java */ public final class SonicMfContainerTool { - /** Bump when restart logic changes – must appear in app output. */ - public static final String TOOL_VERSION = "2026-07-24c-mbean-stop"; + public static final String TOOL_VERSION = "2026-07-24d-restart-only"; public static void main(String[] args) { @@ -29,21 +17,14 @@ public final class SonicMfContainerTool try { Args a = Args.parse(args); - if (a.command == null) + if (!"restart".equals(a.command)) { - fail("Usage: SonicMfContainerTool ping|list|restart --domain D --url U --user U [--password P] [--container C] [--timeout SEC]"); + fail("Usage: SonicMfContainerTool restart --domain D --url U --user U --container C [--timeout SEC]"); return; } - - if (isBlank(a.domain) || isBlank(a.url) || isBlank(a.user)) + if (isBlank(a.domain) || isBlank(a.url) || isBlank(a.user) || isBlank(a.container)) { - fail("domain, url und user sind Pflicht."); - return; - } - - if ("restart".equals(a.command) && isBlank(a.container)) - { - fail("restart erfordert --container."); + fail("domain, url, user und container sind Pflicht."); return; } @@ -51,22 +32,7 @@ public final class SonicMfContainerTool Object connector = connect(a.url, a.user, a.password, timeoutMs); try { - if ("ping".equals(a.command)) - { - ping(connector, a.domain); - } - else if ("list".equals(a.command)) - { - list(connector, a.domain); - } - else if ("restart".equals(a.command)) - { - restart(connector, a.domain, a.container); - } - else - { - fail("Unbekannter Befehl: " + a.command); - } + restart(connector, a.domain, a.container); } finally { @@ -86,8 +52,7 @@ public final class SonicMfContainerTool } } - private static Object connect(String url, String user, String password, long timeoutMs) - throws Exception + private static Object connect(String url, String user, String password, long timeoutMs) throws Exception { Hashtable env = new Hashtable(); env.put("ConnectionURLs", url); @@ -99,8 +64,6 @@ public final class SonicMfContainerTool Class clientCl = Class.forName("com.sonicsw.mf.jmx.client.JMSConnectorClient"); Object connector = clientCl.getConstructor(new Class[] {}).newInstance(new Object[] {}); - - // Optional: Request-Timeout setzen (falls vorhanden) trySetTimeout(connector, timeoutMs); Method connect = findConnect(clientCl); @@ -111,199 +74,34 @@ public final class SonicMfContainerTool } Class[] pts = connect.getParameterTypes(); - Object[] callArgs; if (pts.length == 2) { - callArgs = new Object[] { address, boxTimeout(pts[1], timeoutMs) }; - } - else if (pts.length == 1) - { - callArgs = new Object[] { address }; + connect.invoke(connector, new Object[] { address, boxTimeout(pts[1], timeoutMs) }); } else { - callArgs = new Object[] { address, boxTimeout(pts[1], timeoutMs) }; + connect.invoke(connector, new Object[] { address }); } - - connect.invoke(connector, callArgs); - info("Connected url=" + url + " user=" + user); return connector; } - private static Method findConnect(Class clientCl) - { - Method best = null; - Method[] methods = clientCl.getMethods(); - for (int i = 0; i < methods.length; i++) - { - Method m = methods[i]; - if (!"connect".equals(m.getName())) - { - continue; - } - if (m.getParameterTypes().length == 2) - { - return m; - } - if (m.getParameterTypes().length == 1) - { - best = m; - } - } - return best; - } - - private static void trySetTimeout(Object connector, long timeoutMs) - { - String[] names = new String[] { "setTimeout", "setRequestTimeout", "setDefaultTimeout" }; - for (int i = 0; i < names.length; i++) - { - try - { - Method m = connector.getClass().getMethod(names[i], new Class[] { Long.TYPE }); - m.invoke(connector, new Object[] { Long.valueOf(timeoutMs) }); - info("Set " + names[i] + "=" + timeoutMs); - return; - } - catch (NoSuchMethodException ignore) - { - try - { - Method m = connector.getClass().getMethod(names[i], new Class[] { Integer.TYPE }); - m.invoke(connector, new Object[] { Integer.valueOf((int) Math.min(Integer.MAX_VALUE, timeoutMs)) }); - info("Set " + names[i] + "=" + timeoutMs); - return; - } - catch (Exception ignore2) - { - /* next */ - } - } - catch (Exception ignore) - { - /* next */ - } - } - } - - private static Object boxTimeout(Class type, long timeoutMs) - { - if (type == Long.TYPE || type == Long.class) - { - return Long.valueOf(timeoutMs); - } - if (type == Integer.TYPE || type == Integer.class) - { - return Integer.valueOf((int) Math.min(Integer.MAX_VALUE, timeoutMs)); - } - return Long.valueOf(timeoutMs); - } - - private static void ping(Object connector, String domain) throws Exception - { - Set names = queryAgents(connector); - int matched = 0; - if (names != null) - { - for (Iterator it = names.iterator(); it.hasNext(); ) - { - ObjectName on = (ObjectName) it.next(); - if (extractContainer(on, domain) != null) - { - matched++; - } - } - } - System.out.println("OK:MfApiPing domain=" + domain + " agents=" + matched); - System.out.flush(); - } - - private static void list(Object connector, String domain) throws Exception - { - Set names = queryAgents(connector); - int count = 0; - if (names != null) - { - for (Iterator it = names.iterator(); it.hasNext(); ) - { - ObjectName on = (ObjectName) it.next(); - String container = extractContainer(on, domain); - if (container != null) - { - System.out.println(container); - count++; - } - } - } - if (count == 0) - { - info("KeineAgentContainer Domain=" + domain); - } - System.out.println("OK:List count=" + count); - System.out.flush(); - } - private static void restart(Object connector, String domain, String container) throws Exception { String shortName = shortContainer(container); - // Zuerst Preferred-ObjectName OHNE queryNames – query kann schon scheitern/irreführen - ObjectName preferred = new ObjectName(domain + "." + shortName + ":ID=AGENT"); - info("PreferredObjectName=" + preferred); - - ObjectName on = preferred; - try - { - ObjectName resolved = resolveAgentObjectName(connector, domain, shortName); - if (resolved != null) - { - on = resolved; - } - } - catch (Throwable t) - { - warn("resolveAgentObjectName: " + rootOf(t).getClass().getName() + ": " + rootOf(t).getMessage() - + " – nutze PreferredObjectName"); - } + ObjectName on = new ObjectName(domain + "." + shortName + ":ID=AGENT"); info("ObjectName=" + on); - dumpOpsSafe(connector, on); - StringBuffer attempts = new StringBuffer(); - - // 1) JMX invoke stop/restart/shutdown (remote / wie SMC) - if (tryMBeanLifecycle(connector, on, shortName, domain, attempts)) - { - return; - } - - // 2) IAgentProxy stop/restart - if (tryAgentProxyLifecycle(connector, on, shortName, domain, attempts)) - { - return; - } - - // 3) Manager-MBeans mit Container-Namen - if (tryManagerContainerOps(connector, domain, shortName, attempts)) - { - return; - } - - fail("Kein Neustart-Weg erfolgreich. ToolVersion=" + TOOL_VERSION + "\nAttempts:\n" + attempts.toString()); - } - - private static boolean tryMBeanLifecycle( - Object connector, ObjectName on, String shortName, String domain, StringBuffer attempts) - { - String[] ops = new String[] { "stop", "restart", "shutdown" }; + String[] ops = new String[] { "stop", "restart" }; for (int i = 0; i < ops.length; i++) { String op = ops[i]; try { - info("Trying MBean.invoke(" + op + ") on " + on); + info("Trying MBean.invoke(" + op + ")"); invokeNoArgs(connector, on, op); okRestart("MBean." + op, shortName, domain); - return true; + return; } catch (Throwable t) { @@ -311,162 +109,23 @@ public final class SonicMfContainerTool String line = "MBean." + op + " => " + root.getClass().getName() + ": " + root.getMessage(); warn(line); attempts.append(line).append('\n'); - - // stop kann die Verbindung reissen – wenn Message darauf hindeutet, als Erfolg werten if ("stop".equals(op) && looksLikeStopSideEffect(root)) { - warn("Treat stop side-effect as success (agent going down)"); okRestart("MBean.stop(side-effect)", shortName, domain); - return true; + return; } } } - return false; + + fail("Neustart fehlgeschlagen. ToolVersion=" + TOOL_VERSION + "\nAttempts:\n" + attempts.toString()); } - private static boolean tryAgentProxyLifecycle( - Object connector, ObjectName on, String shortName, String domain, StringBuffer attempts) + private static void invokeNoArgs(Object connector, ObjectName on, String op) throws Exception { - try - { - Class factoryCl = Class.forName("com.sonicsw.mf.mgmtapi.runtime.MFProxyFactory"); - Method create = factoryCl.getMethod("createAgentProxy", new Class[] { - Class.forName("com.sonicsw.mf.jmx.client.JMSConnectorClient"), - ObjectName.class - }); - Object agent = create.invoke(null, new Object[] { connector, on }); - info("StateBefore=" + safeState(agent)); - - String[] methods = new String[] { "stop", "restart", "shutdown" }; - for (int i = 0; i < methods.length; i++) - { - String mName = methods[i]; - try - { - info("Trying IAgentProxy." + mName); - agent.getClass().getMethod(mName).invoke(agent); - okRestart("IAgentProxy." + mName, shortName, domain); - return true; - } - catch (Throwable t) - { - Throwable root = rootOf(t); - String line = "IAgentProxy." + mName + " => " + root.getClass().getName() + ": " + root.getMessage(); - warn(line); - attempts.append(line).append('\n'); - if ("stop".equals(mName) && looksLikeStopSideEffect(root)) - { - okRestart("IAgentProxy.stop(side-effect)", shortName, domain); - return true; - } - } - } - } - catch (Throwable t) - { - Throwable root = rootOf(t); - String line = "IAgentProxy path => " + root.getClass().getName() + ": " + root.getMessage(); - warn(line); - attempts.append(line).append('\n'); - } - return false; - } - - private static boolean tryManagerContainerOps( - Object connector, String domain, String shortName, StringBuffer attempts) - { - String[] candidates = new String[] { - domain + ".DomainManager:ID=DomainManager", - domain + ".DomainManager:ID=AGENT", - domain + ".dm:ID=AGENT" - }; - - for (int i = 0; i < candidates.length; i++) - { - try - { - ObjectName pattern = new ObjectName(candidates[i]); - Set found = queryNames(connector, pattern); - if (found == null || found.isEmpty()) - { - continue; - } - ObjectName real = (ObjectName) found.iterator().next(); - info("Manager=" + real); - dumpOpsSafe(connector, real); - - String[] ops = new String[] { "restartContainer", "stopContainer", "startContainer" }; - for (int o = 0; o < ops.length; o++) - { - try - { - info("Trying " + ops[o] + "(\"" + shortName + "\") on " + real); - invokeStringArg(connector, real, ops[o], shortName); - okRestart("Manager." + ops[o], shortName, domain); - return true; - } - catch (Throwable t) - { - Throwable root = rootOf(t); - String line = ops[o] + " => " + root.getClass().getName() + ": " + root.getMessage(); - warn(line); - attempts.append(line).append('\n'); - } - } - } - catch (Throwable t) - { - warn("Manager candidate " + candidates[i] + ": " + rootOf(t).getMessage()); - } - } - - // Scan AGENT-MBeans nach Container-Ops - try - { - Set agents = queryAgents(connector); - if (agents == null) - { - return false; - } - for (Iterator it = agents.iterator(); it.hasNext(); ) - { - ObjectName on = (ObjectName) it.next(); - String jmxDomain = on.getDomain(); - if (jmxDomain == null) - { - continue; - } - String lower = jmxDomain.toLowerCase(); - if (!(lower.contains("domainmanager") || lower.contains(".dm") - || lower.contains("collector") || lower.contains("host") - || lower.contains("launch"))) - { - continue; - } - info("Scan manager agent " + on); - dumpOpsSafe(connector, on); - String[] ops = new String[] { "restartContainer", "stopContainer" }; - for (int i = 0; i < ops.length; i++) - { - try - { - invokeStringArg(connector, on, ops[i], shortName); - okRestart(ops[i] + "@" + on, shortName, domain); - return true; - } - catch (Throwable t) - { - attempts.append(ops[i]).append('@').append(on).append(" => ") - .append(rootOf(t).getMessage()).append('\n'); - } - } - } - } - catch (Throwable t) - { - warn("Manager scan: " + rootOf(t).getMessage()); - } - return false; + Method invoke = connector.getClass().getMethod("invoke", new Class[] { + ObjectName.class, String.class, Object[].class, String[].class + }); + invoke.invoke(connector, new Object[] { on, op, new Object[0], new String[0] }); } private static boolean looksLikeStopSideEffect(Throwable root) @@ -479,8 +138,7 @@ public final class SonicMfContainerTool return m.indexOf("disconnect") >= 0 || m.indexOf("closed") >= 0 || m.indexOf("not connected") >= 0 - || m.indexOf("connection lost") >= 0 - || m.indexOf("broker") >= 0 && m.indexOf("down") >= 0; + || m.indexOf("connection lost") >= 0; } private static void okRestart(String method, String shortName, String domain) @@ -491,172 +149,75 @@ public final class SonicMfContainerTool System.out.flush(); } - private static void invokeNoArgs(Object connector, ObjectName on, String op) throws Exception + private static Method findConnect(Class clientCl) { - Method invoke = connector.getClass().getMethod("invoke", new Class[] { - ObjectName.class, String.class, Object[].class, String[].class - }); - invoke.invoke(connector, new Object[] { on, op, new Object[0], new String[0] }); + Method[] methods = clientCl.getMethods(); + Method best = null; + for (int i = 0; i < methods.length; i++) + { + Method m = methods[i]; + if (!"connect".equals(m.getName())) + { + continue; + } + Class[] pts = m.getParameterTypes(); + if (pts.length == 1 || pts.length == 2) + { + best = m; + if (pts.length == 2) + { + return m; + } + } + } + return best; } - private static void invokeStringArg(Object connector, ObjectName on, String op, String arg) throws Exception - { - Method invoke = connector.getClass().getMethod("invoke", new Class[] { - ObjectName.class, String.class, Object[].class, String[].class - }); - invoke.invoke(connector, new Object[] { - on, op, new Object[] { arg }, new String[] { "java.lang.String" } - }); - } - - private static void dumpOpsSafe(Object connector, ObjectName on) + private static void trySetTimeout(Object connector, long timeoutMs) { try { - Method getInfo = connector.getClass().getMethod("getMBeanInfo", new Class[] { ObjectName.class }); - MBeanInfo infoBean = (MBeanInfo) getInfo.invoke(connector, new Object[] { on }); - if (infoBean == null || infoBean.getOperations() == null) + connector.getClass().getMethod("setTimeout", new Class[] { Long.TYPE }) + .invoke(connector, new Object[] { Long.valueOf(timeoutMs) }); + } + catch (Exception ignore) + { + try { - info("Ops(" + on + ")=(null)"); - return; + connector.getClass().getMethod("setRequestTimeout", new Class[] { Long.TYPE }) + .invoke(connector, new Object[] { Long.valueOf(timeoutMs) }); } - StringBuffer sb = new StringBuffer(); - MBeanOperationInfo[] ops = infoBean.getOperations(); - for (int i = 0; i < ops.length; i++) + catch (Exception ignore2) { - String name = ops[i].getName(); - String lower = name.toLowerCase(); - if (lower.indexOf("restart") >= 0 || lower.indexOf("stop") >= 0 - || lower.indexOf("start") >= 0 || lower.indexOf("shutdown") >= 0 - || lower.indexOf("container") >= 0) - { - if (sb.length() > 0) - { - sb.append(','); - } - sb.append(name); - } - } - info("Ops(" + on + ")=" + (sb.length() == 0 ? "(keine lifecycle)" : sb.toString())); - } - catch (Throwable t) - { - warn("getMBeanInfo(" + on + "): " + rootOf(t).getClass().getName() + ": " + rootOf(t).getMessage()); - } - } - - private static Set queryAgents(Object connector) throws Exception - { - return queryNames(connector, new ObjectName("*:ID=AGENT")); - } - - private static Set queryNames(Object connector, ObjectName pattern) throws Exception - { - Method query = connector.getClass().getMethod("queryNames", new Class[] { - ObjectName.class, javax.management.QueryExp.class - }); - return (Set) query.invoke(connector, new Object[] { pattern, null }); - } - - private static ObjectName resolveAgentObjectName(Object connector, String domain, String container) - throws Exception - { - ObjectName preferred = new ObjectName(domain + "." + container + ":ID=AGENT"); - Set exact = queryNames(connector, preferred); - if (exact != null && !exact.isEmpty()) - { - return (ObjectName) exact.iterator().next(); - } - - Set names = queryAgents(connector); - if (names != null) - { - for (Iterator it = names.iterator(); it.hasNext(); ) - { - ObjectName on = (ObjectName) it.next(); - String c = extractContainer(on, domain); - if (c != null && c.equalsIgnoreCase(container)) - { - return on; - } + /* optional */ } } - return preferred; } - private static String extractContainer(ObjectName on, String domain) + private static Object boxTimeout(Class type, long timeoutMs) { - if (on == null || isBlank(domain)) + if (type == Integer.TYPE || type == Integer.class) { - return null; + return Integer.valueOf((int) Math.min(Integer.MAX_VALUE, timeoutMs)); } - String jmxDomain = on.getDomain(); - if (jmxDomain == null) - { - return null; - } - String prefix = domain + "."; - if (jmxDomain.regionMatches(true, 0, prefix, 0, prefix.length())) - { - return jmxDomain.substring(prefix.length()); - } - return null; + return Long.valueOf(timeoutMs); } private static String shortContainer(String container) { - if (container == null) - { - return ""; - } - int dot = container.indexOf('.'); - if (dot >= 0 && dot < container.length() - 1) - { - return container.substring(dot + 1); - } - return container; - } - - private static String safeState(Object agent) - { - try - { - Object s = agent.getClass().getMethod("getStateString").invoke(agent); - return s == null ? "?" : String.valueOf(s); - } - catch (Throwable t) - { - return "?"; - } + String c = container.trim(); + int dot = c.lastIndexOf('.'); + return dot >= 0 ? c.substring(dot + 1) : c; } private static Throwable rootOf(Throwable t) { - Throwable root = t; - while (root != null && root.getCause() != null && root.getCause() != root) + Throwable cur = t; + while (cur.getCause() != null && cur.getCause() != cur) { - root = root.getCause(); + cur = cur.getCause(); } - return root == null ? t : root; - } - - private static void info(String message) - { - System.out.println("INFO:" + message); - System.out.flush(); - } - - private static void warn(String message) - { - System.out.println("WARN:" + message); - System.out.flush(); - } - - private static void fail(String message) - { - System.err.println("ERROR:" + message); - System.err.flush(); - System.exit(2); + return cur; } private static boolean isBlank(String s) @@ -664,6 +225,25 @@ public final class SonicMfContainerTool return s == null || s.trim().length() == 0; } + private static void info(String msg) + { + System.out.println("INFO:" + msg); + System.out.flush(); + } + + private static void warn(String msg) + { + System.out.println("WARN:" + msg); + System.out.flush(); + } + + private static void fail(String msg) + { + System.err.println("ERROR:" + msg); + System.err.flush(); + System.exit(1); + } + private static final class Args { String command; @@ -681,27 +261,22 @@ public final class SonicMfContainerTool { return a; } - - a.command = args[0].toLowerCase(); + a.command = args[0]; for (int i = 1; i < args.length; i++) { - String key = args[i]; - if (!key.startsWith("--") || i + 1 >= args.length) + String k = args[i]; + String v = (i + 1 < args.length) ? args[i + 1] : null; + if ("--domain".equals(k) && v != null) { a.domain = v; i++; } + else if ("--url".equals(k) && v != null) { a.url = v; i++; } + else if ("--user".equals(k) && v != null) { a.user = v; i++; } + else if ("--password".equals(k) && v != null) { a.password = v; i++; } + else if ("--container".equals(k) && v != null) { a.container = v; i++; } + else if ("--timeout".equals(k) && v != null) { - continue; - } - String val = args[++i]; - if ("--domain".equals(key)) a.domain = val; - else if ("--url".equals(key)) a.url = val; - else if ("--user".equals(key)) a.user = val; - else if ("--password".equals(key)) a.password = val; - else if ("--container".equals(key)) a.container = val; - else if ("--timeout".equals(key)) - { - try { a.timeoutSec = Integer.parseInt(val); } catch (Exception ignore) { /* keep */ } + try { a.timeoutSec = Integer.parseInt(v); } catch (Exception ignore) { /* keep default */ } + i++; } } - if (isBlank(a.password)) { String env = System.getenv("ESB_SONIC_PASSWORD"); @@ -713,4 +288,8 @@ public final class SonicMfContainerTool return a; } } + + private SonicMfContainerTool() + { + } } diff --git a/ZA.CoreService.ESBCertificateManager/appsettings.json b/ZA.CoreService.ESBCertificateManager/appsettings.json index f807e9d..c04690e 100644 --- a/ZA.CoreService.ESBCertificateManager/appsettings.json +++ b/ZA.CoreService.ESBCertificateManager/appsettings.json @@ -7,7 +7,6 @@ "ConnectionUrl": "tcp://dekun-painwbdet:13070", "Username": "Administrator", "Password": "Administrator", - "ManagementMode": "MfApi", "SonicHome": "C:\\DEV\\MQ10.0", "JavaHome": "C:\\Program Files (x86)\\Java\\jre1.8.0_501", "JavaPath": "C:\\Program Files (x86)\\Java\\jre1.8.0_501\\bin\\java.exe",