diff --git a/ZA.CoreService.ESBCertificateManager/Services/SonicContainerScanner.cs b/ZA.CoreService.ESBCertificateManager/Services/SonicContainerScanner.cs new file mode 100644 index 0000000..3f10221 --- /dev/null +++ b/ZA.CoreService.ESBCertificateManager/Services/SonicContainerScanner.cs @@ -0,0 +1,303 @@ +using System.Diagnostics; +using System.Text; +using ZA.CoreService.ESBCertificateManager.Models; + +namespace ZA.CoreService.ESBCertificateManager.Services; + +public sealed class SonicContainerScanner +{ + private readonly RuntimeSettings _runtimeSettings; + + public SonicContainerScanner(RuntimeSettings runtimeSettings) + { + _runtimeSettings = runtimeSettings + ?? throw new ArgumentNullException(nameof(runtimeSettings)); + } + + public async Task ScanAsync( + SonicSystemOption system, + string userName, + string secret, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(system); + + if (string.IsNullOrWhiteSpace(userName)) + { + return ContainerScanResult.Failed( + "Der Sonic-Benutzername fehlt."); + } + + if (string.IsNullOrEmpty(secret)) + { + return ContainerScanResult.Failed( + "Das Sonic-Kennwort fehlt."); + } + + string javaPath = + PathResolver.ResolvePath( + _runtimeSettings.JavaExecutablePath); + + string libraryPath = + PathResolver.ResolvePath( + _runtimeSettings.SonicClientLibraryPath); + + string? toolsPath = ResolveToolsDirectory(); + + if (!File.Exists(javaPath)) + { + return ContainerScanResult.Failed( + $"Java wurde nicht gefunden: {javaPath}"); + } + + if (!Directory.Exists(libraryPath)) + { + return ContainerScanResult.Failed( + $"Der Sonic-JAR-Ordner wurde nicht gefunden: {libraryPath}"); + } + + if (toolsPath is null) + { + return ContainerScanResult.Failed( + "SonicMfContainerTool.class wurde nicht gefunden."); + } + + string[] jars = + Directory.GetFiles( + libraryPath, + "*.jar", + SearchOption.TopDirectoryOnly); + + if (jars.Length == 0) + { + return ContainerScanResult.Failed( + "Im Sonic-Client-Ordner wurden keine JAR-Dateien gefunden."); + } + + string classpath = + toolsPath + + Path.PathSeparator + + string.Join( + Path.PathSeparator, + jars.OrderBy( + Path.GetFileName, + StringComparer.OrdinalIgnoreCase)); + + ProcessStartInfo startInfo = new() + { + FileName = javaPath, + WorkingDirectory = toolsPath, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + StandardOutputEncoding = Encoding.UTF8, + StandardErrorEncoding = Encoding.UTF8, + CreateNoWindow = true + }; + + startInfo.ArgumentList.Add("-cp"); + startInfo.ArgumentList.Add(classpath); + startInfo.ArgumentList.Add("SonicMfContainerTool"); + startInfo.ArgumentList.Add("list"); + startInfo.ArgumentList.Add("--domain"); + startInfo.ArgumentList.Add(system.DomainName); + startInfo.ArgumentList.Add("--url"); + startInfo.ArgumentList.Add(system.ConnectionUrl); + startInfo.ArgumentList.Add("--user"); + startInfo.ArgumentList.Add(userName.Trim()); + startInfo.ArgumentList.Add("--timeout"); + startInfo.ArgumentList.Add("45"); + + startInfo.Environment["ESB_SONIC_PASSWORD"] = secret; + + using Process process = new() + { + StartInfo = startInfo + }; + + try + { + if (!process.Start()) + { + return ContainerScanResult.Failed( + "Der Java-Prozess konnte nicht gestartet werden."); + } + } + catch (Exception ex) + { + return ContainerScanResult.Failed( + $"Java konnte nicht gestartet werden: {ex.Message}"); + } + + Task standardOutputTask = + process.StandardOutput.ReadToEndAsync(); + + Task standardErrorTask = + process.StandardError.ReadToEndAsync(); + + using CancellationTokenSource timeoutSource = + CancellationTokenSource.CreateLinkedTokenSource( + cancellationToken); + + timeoutSource.CancelAfter(TimeSpan.FromSeconds(50)); + + try + { + await process.WaitForExitAsync(timeoutSource.Token); + } + catch (OperationCanceledException) + when (!cancellationToken.IsCancellationRequested) + { + TryKill(process); + + return ContainerScanResult.Failed( + "Der Container-Scan hat das Zeitlimit überschritten."); + } + catch (OperationCanceledException) + { + TryKill(process); + throw; + } + + string standardOutput = + (await standardOutputTask).Trim(); + + string standardError = + (await standardErrorTask).Trim(); + + string combined = + string.Join( + Environment.NewLine, + new[] { standardOutput, standardError } + .Where(value => !string.IsNullOrWhiteSpace(value))); + + bool success = + process.ExitCode == 0 + && combined.Contains( + "OK:ContainerListEnd", + StringComparison.OrdinalIgnoreCase) + && !combined.Contains( + "ERROR:", + StringComparison.OrdinalIgnoreCase); + + if (!success) + { + return ContainerScanResult.Failed( + ExtractError(combined) + ?? $"Container-Scan fehlgeschlagen, ExitCode={process.ExitCode}."); + } + + List containers = []; + + foreach (string line in standardOutput.Split( + ['\r', '\n'], + StringSplitOptions.RemoveEmptyEntries)) + { + const string prefix = "CONTAINER="; + + if (!line.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + string name = line[prefix.Length..].Trim(); + + if (name.Length > 0 + && !containers.Contains(name, StringComparer.OrdinalIgnoreCase)) + { + containers.Add(name); + } + } + + containers.Sort(StringComparer.OrdinalIgnoreCase); + + return ContainerScanResult.Successful(containers); + } + + private static string? ResolveToolsDirectory() + { + string baseDirectory = AppContext.BaseDirectory; + + string[] candidates = + [ + Path.Combine(baseDirectory, "Tools"), + Path.Combine(baseDirectory, "..", "..", "..", "Tools"), + Path.Combine( + Directory.GetCurrentDirectory(), + "Tools") + ]; + + foreach (string candidate in candidates) + { + string fullPath = Path.GetFullPath(candidate); + string classFile = Path.Combine( + fullPath, + "SonicMfContainerTool.class"); + + if (File.Exists(classFile)) + { + return fullPath; + } + } + + return null; + } + + private static string? ExtractError(string output) + { + foreach (string line in output.Split( + ['\r', '\n'], + StringSplitOptions.RemoveEmptyEntries)) + { + if (line.StartsWith("ERROR:", StringComparison.OrdinalIgnoreCase)) + { + return line["ERROR:".Length..].Trim(); + } + } + + return string.IsNullOrWhiteSpace(output) ? null : output; + } + + private static void TryKill(Process process) + { + try + { + if (!process.HasExited) + { + process.Kill(entireProcessTree: true); + } + } + catch + { + } + } +} + +public sealed class ContainerScanResult +{ + public bool Success { get; init; } + + public string Message { get; init; } = string.Empty; + + public IReadOnlyList Containers { get; init; } = []; + + public static ContainerScanResult Successful( + IReadOnlyList containers) + { + return new ContainerScanResult + { + Success = true, + Message = $"{containers.Count} Container gefunden.", + Containers = containers + }; + } + + public static ContainerScanResult Failed(string message) + { + return new ContainerScanResult + { + Success = false, + Message = message + }; + } +} diff --git a/ZA.CoreService.ESBCertificateManager/Setup/SelectContainersForm.cs b/ZA.CoreService.ESBCertificateManager/Setup/SelectContainersForm.cs new file mode 100644 index 0000000..0c03cea --- /dev/null +++ b/ZA.CoreService.ESBCertificateManager/Setup/SelectContainersForm.cs @@ -0,0 +1,432 @@ +using ZA.CoreService.ESBCertificateManager.Models; +using ZA.CoreService.ESBCertificateManager.Services; + +namespace ZA.CoreService.ESBCertificateManager.Setup; + +public sealed class SelectContainersForm : Form +{ + private readonly SetupCoordinator _coordinator; + private readonly SonicSystemOption _system; + private readonly SonicContainerScanner _scanner; + + private readonly ComboBox _cmbCompany; + private readonly CheckedListBox _lstContainers; + private readonly TextBox _txtManual; + private readonly TextBox _txtUser; + private readonly TextBox _txtPassword; + private readonly Label _lblStatus; + private readonly Button _btnScan; + private readonly Button _btnSave; + private bool _running; + + public int SavedCount { get; private set; } + + public SelectContainersForm( + SetupCoordinator coordinator, + SonicSystemOption system) + { + _coordinator = coordinator; + _system = system; + _scanner = new SonicContainerScanner(coordinator.Settings.Runtime); + + Text = "Container auswählen"; + StartPosition = FormStartPosition.CenterParent; + FormBorderStyle = FormBorderStyle.FixedDialog; + MaximizeBox = false; + MinimizeBox = false; + ShowInTaskbar = false; + ClientSize = new Size(640, 620); + BackColor = SettingsUi.Background; + ForeColor = SettingsUi.Text; + Font = new Font("Segoe UI", 10f); + + Label title = new() + { + Text = "Container für System", + Location = new Point(28, 20), + AutoSize = true, + Font = new Font("Segoe UI Semibold", 18f, FontStyle.Bold), + ForeColor = SettingsUi.Text + }; + + Label subtitle = new() + { + Text = + $"{system.ConnectionName} · {system.ConnectionUrl}" + + Environment.NewLine + + "Scan von Sonic oder manuell eintragen, dann übernehmen.", + Location = new Point(30, 54), + Size = new Size(580, 40), + ForeColor = SettingsUi.Muted + }; + + Panel card = SettingsUi.CreateCard(); + card.Location = new Point(28, 110); + card.Size = new Size(584, 430); + card.Paint += SettingsUi.PaintCardBorder; + + _cmbCompany = SettingsUi.CreateComboBox(); + _lstContainers = new CheckedListBox + { + BackColor = Color.FromArgb(12, 28, 48), + ForeColor = SettingsUi.Text, + BorderStyle = BorderStyle.FixedSingle, + CheckOnClick = true, + Font = new Font("Segoe UI", 10f) + }; + + _txtManual = SettingsUi.CreateTextBox(); + _txtUser = SettingsUi.CreateTextBox(); + _txtPassword = SettingsUi.CreateTextBox(); + _txtPassword.UseSystemPasswordChar = true; + + AddField(card, "Company", _cmbCompany, 18, 16, 540); + AddField(card, "Gefundene / gewählte Container", _lstContainers, 18, 76, 540, 150); + + Label manualLabel = SettingsUi.CreateFieldLabel("Manuell hinzufügen"); + manualLabel.Location = new Point(18, 240); + _txtManual.Location = new Point(18, 262); + _txtManual.Width = 400; + + Button btnAddManual = SettingsUi.CreateGhostButton("+"); + btnAddManual.Location = new Point(430, 260); + btnAddManual.Size = new Size(50, 32); + btnAddManual.Click += (_, _) => AddManualContainer(); + + Button btnSuggest = SettingsUi.CreateGhostButton("ct-ZADBService"); + btnSuggest.Location = new Point(490, 260); + btnSuggest.Size = new Size(68, 32); + btnSuggest.Font = new Font("Segoe UI", 7.5f, FontStyle.Bold); + btnSuggest.Click += (_, _) => + { + _txtManual.Text = "ct-ZADBService"; + AddManualContainer(); + }; + + AddField(card, "Sonic-Benutzer (für Scan)", _txtUser, 18, 308, 250); + AddField(card, "Kennwort", _txtPassword, 288, 308, 270); + + _btnScan = SettingsUi.CreatePrimaryButton("Von Sonic scannen"); + _btnScan.Location = new Point(18, 375); + _btnScan.Size = new Size(200, 36); + _btnScan.Click += async (_, _) => await ScanAsync(); + + _lblStatus = new Label + { + Location = new Point(230, 380), + Size = new Size(330, 30), + ForeColor = SettingsUi.Muted + }; + + card.Controls.Add(manualLabel); + card.Controls.Add(_txtManual); + card.Controls.Add(btnAddManual); + card.Controls.Add(btnSuggest); + card.Controls.Add(_btnScan); + card.Controls.Add(_lblStatus); + + _btnSave = SettingsUi.CreatePrimaryButton("Auswahl speichern"); + _btnSave.Location = new Point(352, 555); + _btnSave.Size = new Size(260, 42); + _btnSave.Click += async (_, _) => await SaveAsync(); + + Button skip = SettingsUi.CreateGhostButton("Später"); + skip.Location = new Point(230, 555); + skip.Size = new Size(110, 42); + skip.Click += (_, _) => + { + DialogResult = DialogResult.Cancel; + Close(); + }; + + Controls.Add(title); + Controls.Add(subtitle); + Controls.Add(card); + Controls.Add(skip); + Controls.Add(_btnSave); + + Shown += async (_, _) => await InitializeAsync(); + } + + private async Task InitializeAsync() + { + try + { + IReadOnlyList companies = + await _coordinator.LoadCompaniesAsync(); + + _cmbCompany.Items.Clear(); + + foreach (CompanyOption company in companies) + { + _cmbCompany.Items.Add(company); + } + + if (_cmbCompany.Items.Count > 0) + { + int deIndex = 0; + + for (int index = 0; index < companies.Count; index++) + { + if (string.Equals( + companies[index].CompanyCode, + "DE", + StringComparison.OrdinalIgnoreCase)) + { + deIndex = index; + break; + } + } + + _cmbCompany.SelectedIndex = deIndex; + } + + IReadOnlyList profiles = + await _coordinator.LoadCredentialsAsync( + _system.SonicConnectionId); + + SonicCredentialProfile? profile = + profiles.FirstOrDefault(item => item.IsDefault) + ?? profiles.FirstOrDefault(); + + if (profile is not null) + { + _txtUser.Text = profile.UserName; + _txtPassword.Text = profile.Secret; + _lblStatus.Text = + "Profil geladen — Scan möglich."; + _lblStatus.ForeColor = SettingsUi.Green; + } + else + { + _lblStatus.Text = + "Kein Profil — Benutzer/Kennwort für Scan eingeben oder manuell."; + _lblStatus.ForeColor = SettingsUi.Gold; + } + + // Bereits vorhandene Container vorselektieren + IReadOnlyList existing = + await _coordinator.LoadContainersAsync( + _system.SonicConnectionId); + + foreach (ContainerOption container in existing) + { + AddContainerName(container.ContainerName, check: true); + } + } + catch (Exception ex) + { + _lblStatus.Text = ex.Message; + _lblStatus.ForeColor = SettingsUi.Red; + } + } + + private void AddManualContainer() + { + string name = _txtManual.Text.Trim(); + + if (name.Length == 0) + { + return; + } + + AddContainerName(name, check: true); + _txtManual.Clear(); + _txtManual.Focus(); + } + + private void AddContainerName(string name, bool check) + { + for (int index = 0; index < _lstContainers.Items.Count; index++) + { + if (string.Equals( + _lstContainers.Items[index]?.ToString(), + name, + StringComparison.OrdinalIgnoreCase)) + { + _lstContainers.SetItemChecked(index, check); + return; + } + } + + int added = _lstContainers.Items.Add(name); + _lstContainers.SetItemChecked(added, check); + } + + private async Task ScanAsync() + { + if (_running) + { + return; + } + + _running = true; + _btnScan.Enabled = false; + _btnSave.Enabled = false; + Cursor = Cursors.WaitCursor; + _lblStatus.Text = "Scanne Domain…"; + _lblStatus.ForeColor = SettingsUi.Muted; + + try + { + ContainerScanResult result = + await _scanner.ScanAsync( + _system, + _txtUser.Text, + _txtPassword.Text); + + if (!result.Success) + { + _lblStatus.Text = result.Message; + _lblStatus.ForeColor = SettingsUi.Red; + return; + } + + foreach (string container in result.Containers) + { + AddContainerName(container, check: true); + } + + _lblStatus.Text = + result.Containers.Count == 0 + ? "Scan OK, aber keine Container gefunden — bitte manuell." + : $"{result.Containers.Count} Container gescannt und vorausgewählt."; + + _lblStatus.ForeColor = + result.Containers.Count == 0 + ? SettingsUi.Gold + : SettingsUi.Green; + } + catch (Exception ex) + { + _lblStatus.Text = ex.Message; + _lblStatus.ForeColor = SettingsUi.Red; + } + finally + { + _running = false; + _btnScan.Enabled = true; + _btnSave.Enabled = true; + Cursor = Cursors.Default; + } + } + + private async Task SaveAsync() + { + if (_running) + { + return; + } + + if (_cmbCompany.SelectedItem is not CompanyOption company) + { + MessageBox.Show( + this, + "Bitte eine Company wählen.", + "Container", + MessageBoxButtons.OK, + MessageBoxIcon.Warning); + return; + } + + List selected = _lstContainers.CheckedItems + .Cast() + .Select(item => item.ToString() ?? string.Empty) + .Where(name => name.Length > 0) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + if (selected.Count == 0) + { + MessageBox.Show( + this, + "Bitte mindestens einen Container angehakt lassen " + + "oder manuell hinzufügen.", + "Container", + MessageBoxButtons.OK, + MessageBoxIcon.Warning); + return; + } + + _running = true; + _btnSave.Enabled = false; + Cursor = Cursors.WaitCursor; + + try + { + IReadOnlyList existing = + await _coordinator.LoadContainersAsync( + _system.SonicConnectionId); + + HashSet existingNames = existing + .Select(item => item.ContainerName) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + + int created = 0; + + foreach (string containerName in selected) + { + if (existingNames.Contains(containerName)) + { + continue; + } + + await _coordinator.AddSonicContainerAsync( + company.CompanyId, + _system.SonicConnectionId, + containerName, + containerDisplayName: null, + restartTimeoutSeconds: 180); + + created++; + } + + SavedCount = created; + + MessageBox.Show( + this, + created == 0 + ? "Alle gewählten Container waren bereits vorhanden." + : $"{created} Container gespeichert und geprüft.", + "Prüfung OK", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + + DialogResult = DialogResult.OK; + Close(); + } + catch (Exception ex) + { + MessageBox.Show( + this, + ex.Message, + "Container", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + } + finally + { + _running = false; + _btnSave.Enabled = true; + Cursor = Cursors.Default; + } + } + + private static void AddField( + Control parent, + string label, + Control input, + int x, + int y, + int width, + int height = 30) + { + Label fieldLabel = SettingsUi.CreateFieldLabel(label); + fieldLabel.Location = new Point(x, y); + input.Location = new Point(x, y + 22); + input.Width = width; + input.Height = height; + parent.Controls.Add(fieldLabel); + parent.Controls.Add(input); + } +} diff --git a/ZA.CoreService.ESBCertificateManager/Setup/SetupWizardForm.cs b/ZA.CoreService.ESBCertificateManager/Setup/SetupWizardForm.cs index d6aaebb..cfa5235 100644 --- a/ZA.CoreService.ESBCertificateManager/Setup/SetupWizardForm.cs +++ b/ZA.CoreService.ESBCertificateManager/Setup/SetupWizardForm.cs @@ -381,7 +381,9 @@ public sealed class SetupWizardForm : Form Panel toolbar = CreateToolbar( "+ Container hinzufügen", - async () => await AddContainerAsync()); + async () => await AddContainerAsync(), + secondaryText: "Von Sonic scannen", + onSecondary: async () => await ScanContainersForSelectedSystemAsync()); _lvContainers = SettingsUi.CreateListView(); _lvContainers.Dock = DockStyle.Fill; @@ -418,7 +420,9 @@ public sealed class SetupWizardForm : Form private Panel CreateToolbar( string addButtonText, - Func onAdd) + Func onAdd, + string? secondaryText = null, + Func? onSecondary = null) { Panel toolbar = new() { @@ -433,6 +437,16 @@ public sealed class SetupWizardForm : Form add.Width = 230; add.Click += async (_, _) => await onAdd(); + if (secondaryText is not null && onSecondary is not null) + { + Button secondary = SettingsUi.CreateGhostButton(secondaryText); + secondary.Dock = DockStyle.Left; + secondary.Width = 180; + secondary.Margin = new Padding(8, 0, 0, 0); + secondary.Click += async (_, _) => await onSecondary(); + toolbar.Controls.Add(secondary); + } + toolbar.Controls.Add(add); return toolbar; } @@ -692,6 +706,78 @@ public sealed class SetupWizardForm : Form "Prüfung OK", MessageBoxButtons.OK, MessageBoxIcon.Information); + + SonicSystemOption? createdSystem = _systems + .FirstOrDefault(system => + system.SonicConnectionId == createdId); + + if (createdSystem is null + && _lvSystems.SelectedItems.Count > 0) + { + createdSystem = + _lvSystems.SelectedItems[0].Tag as SonicSystemOption; + } + + if (createdSystem is null) + { + return; + } + + DialogResult next = MessageBox.Show( + this, + "Container jetzt per Sonic-Scan oder manuell zuordnen?", + "Container", + MessageBoxButtons.YesNo, + MessageBoxIcon.Question); + + if (next != DialogResult.Yes) + { + return; + } + + using SelectContainersForm selectForm = + new(_coordinator, createdSystem); + + if (selectForm.ShowDialog(this) == DialogResult.OK) + { + await LoadContainersListAsync(); + await RefreshAllAsync(); + } + } + + private async Task ScanContainersForSelectedSystemAsync() + { + SonicSystemOption? system = null; + + if (_lvSystems.SelectedItems.Count > 0) + { + system = _lvSystems.SelectedItems[0].Tag as SonicSystemOption; + } + + system ??= _systems.FirstOrDefault(); + + if (system is null) + { + // Systeme-Liste ggf. nachladen + await LoadSystemsListAsync(); + system = _systems.FirstOrDefault(); + } + + if (system is null) + { + ShowWarning( + "Bitte zuerst ein Sonic-System anlegen oder auswählen."); + return; + } + + using SelectContainersForm selectForm = + new(_coordinator, system); + + if (selectForm.ShowDialog(this) == DialogResult.OK) + { + await LoadContainersListAsync(); + await RefreshAllAsync(); + } } private async Task AddContainerAsync() diff --git a/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool$Args.class b/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool$Args.class index 980efa7..729a334 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 8898afe..59a4fa6 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.jar b/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.jar index 593d2b0..e944331 100644 Binary files a/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.jar and b/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.jar differ diff --git a/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.java b/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.java index 5edd744..35b014f 100644 --- a/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.java +++ b/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.java @@ -1,68 +1,72 @@ import java.lang.reflect.Method; import java.util.Hashtable; +import java.util.Iterator; +import java.util.Set; import javax.management.ObjectName; /** * Sonic-MfApi-Hilfsprogramm. * - * Unterstützte Befehle: - * - * validate - * Prüft Anmeldung und lesenden Zugriff auf den Container. - * Es wird kein Neustart ausgeführt. - * - * restart - * Startet den angegebenen Container neu. - * - * Kompilierung mit Java 8: + * Befehle: + * validate - Anmeldung + lesender Container-Zugriff + * restart - Container neu starten (stop/restart) + * list - Container der Domain auflisten * * javac -source 8 -target 8 SonicMfContainerTool.java */ -public final class SonicMfConta*nerTool +public final class SonicMfContainerTool { - public static final *tring TOOL_VERSION = - "2026*07-27-validate-and-restart"; + public static final String TOOL_VERSION = + "2026-07-29-validate-restart-list"; - *ublic static void main(String[] ar*s) + public static void main(String[] args) { - info("ToolVersion*" + TOOL_VERSION); + info("ToolVersion=" + TOOL_VERSION); try - * { - Args parsedArgu*ents = Args.parse(args); + { + Args parsedArguments = Args.parse(args); - * if (!"validate".equals(parsedAr*uments.command) - &&*!"restart".equals(parsedArguments.*ommand)) + if (!"validate".equals(parsedArguments.command) + && !"restart".equals(parsedArguments.command) + && !"list".equals(parsedArguments.command)) { - * fail( - "Usa*e: SonicMfContainerTool " - * + " "* + "--domain D * - + "--url U " -* + "--user U " - * + "--container C*" - + "[--timeout SEC]"); - - return; -* } - - if (isBl*nk(parsedArguments.domain) - * || isBlank(parsedArguments*url) - || isBlank(pa*sedArguments.user) - *|| isBlank(parsedArguments.contain*r)) - { - *ail( - "domain, *rl, user und container sind Pflich*."); - - return; - * } - - if (isBlank(*arsedArguments.password)) - * { fail( - * "Das Sonic-Kennwort wurde nicht über " - + "ESB_SONIC_PASSWORD bereitgestellt."); + "Usage: SonicMfContainerTool " + + " " + + "--domain D " + + "--url U " + + "--user U " + + "[--container C] " + + "[--timeout SEC]"); + return; + } + if (isBlank(parsedArguments.domain) + || isBlank(parsedArguments.url) + || isBlank(parsedArguments.user)) + { + fail("domain, url und user sind Pflicht."); + return; + } + + boolean needsContainer = + "validate".equals(parsedArguments.command) + || "restart".equals(parsedArguments.command); + + if (needsContainer + && isBlank(parsedArguments.container)) + { + fail("container ist Pflicht fuer validate/restart."); + return; + } + + if (isBlank(parsedArguments.password)) + { + fail( + "Das Sonic-Kennwort wurde nicht ueber " + + "ESB_SONIC_PASSWORD bereitgestellt."); return; } @@ -78,7 +82,13 @@ public final class SonicMfConta*nerTool try { - if ("validate".equals( + if ("list".equals(parsedArguments.command)) + { + listContainers( + connector, + parsedArguments.domain); + } + else if ("validate".equals( parsedArguments.command)) { validateConnection( @@ -102,7 +112,6 @@ public final class SonicMfConta*nerTool catch (Throwable throwable) { Throwable root = rootOf(throwable); - String message = root.getMessage(); if (message == null) @@ -117,6 +126,115 @@ public final class SonicMfConta*nerTool } } + private static void listContainers( + Object connector, + String domain) + throws Exception + { + Method queryNamesMethod = + findMethod( + connector.getClass(), + "queryNames", + new Class[] + { + ObjectName.class, + javax.management.QueryExp.class + }); + + if (queryNamesMethod == null) + { + throw new IllegalStateException( + "queryNames(ObjectName, QueryExp) wurde nicht gefunden."); + } + + ObjectName pattern = + new ObjectName("*:ID=AGENT"); + + Object rawResult = queryNamesMethod.invoke( + connector, + new Object[] + { + pattern, + null + }); + + if (!(rawResult instanceof Set)) + { + throw new IllegalStateException( + "queryNames lieferte kein Set."); + } + + Set names = (Set) rawResult; + String domainPrefix = domain + "."; + int count = 0; + + System.out.println("OK:ContainerListBegin"); + + Iterator iterator = names.iterator(); + + while (iterator.hasNext()) + { + Object entry = iterator.next(); + + if (!(entry instanceof ObjectName)) + { + continue; + } + + ObjectName objectName = (ObjectName) entry; + String canonical = objectName.getCanonicalName(); + + if (canonical == null) + { + continue; + } + + // Erwartet: domain.containerName:ID=AGENT + if (!canonical.regionMatches( + true, + 0, + domainPrefix, + 0, + domainPrefix.length())) + { + continue; + } + + int colon = canonical.indexOf(':'); + + if (colon <= domainPrefix.length()) + { + continue; + } + + String containerName = + canonical.substring( + domainPrefix.length(), + colon); + + if (isBlank(containerName)) + { + continue; + } + + System.out.println( + "CONTAINER=" + + containerName.trim()); + + count++; + } + + System.out.println( + "OK:ContainerListEnd count=" + + count + + " domain=" + + domain + + " tool=" + + TOOL_VERSION); + + System.out.flush(); + } + private static Object connect( String url, String user, @@ -126,17 +244,9 @@ public final class SonicMfConta*nerTool { Hashtable environment = new Hashtable(); - environment.put( - "ConnectionURLs", - url); - - environment.put( - "DefaultUser", - user); - - environment.put( - "DefaultPassword", - password); + environment.put("ConnectionURLs", url); + environment.put("DefaultUser", user); + environment.put("DefaultPassword", password); Class addressClass = Class.forName( "com.sonicsw.mf.jmx.client.JMSConnectorAddress"); @@ -160,18 +270,14 @@ public final class SonicMfConta*nerTool .getConstructor(new Class[] {}) .newInstance(new Object[] {}); - trySetTimeout( - connector, - timeoutMilliseconds); + trySetTimeout(connector, timeoutMilliseconds); - Method connectMethod = - findConnect(clientClass); + Method connectMethod = findConnect(clientClass); if (connectMethod == null) { throw new IllegalStateException( - "JMSConnectorClient.connect(...) " - + "wurde nicht gefunden."); + "JMSConnectorClient.connect(...) wurde nicht gefunden."); } Class[] parameterTypes = @@ -202,33 +308,18 @@ public final class SonicMfConta*nerTool return connector; } - /** - * Führt ausschließlich eine lesende Prüfung aus. - * - * Die erfolgreiche Verbindung bestätigt bereits die Anmeldung. - * Zusätzlich wird versucht, die MBean-Information des Containers - * zu lesen. Es wird keine stop-, start- oder restart-Operation - * aufgerufen. - */ private static void validateConnection( Object connector, String domain, String container) throws Exception { - String shortName = - shortContainer(container); + String shortName = shortContainer(container); - ObjectName objectName = - new ObjectName( - domain - + "." - + shortName - + ":ID=AGENT"); + ObjectName objectName = new ObjectName( + domain + "." + shortName + ":ID=AGENT"); - info( - "ValidatingObjectName=" - + objectName); + info("ValidatingObjectName=" + objectName); Method getMBeanInfoMethod = findMethod( @@ -242,9 +333,7 @@ public final class SonicMfConta*nerTool if (getMBeanInfoMethod == null) { throw new IllegalStateException( - "Die lesende MfApi-Methode " - + "getMBeanInfo(ObjectName) " - + "wurde nicht gefunden."); + "getMBeanInfo(ObjectName) wurde nicht gefunden."); } Object result = getMBeanInfoMethod.invoke( @@ -278,61 +367,34 @@ public final class SonicMfConta*nerTool String container) throws Exception { - String shortName = - shortContainer(container); + String shortName = shortContainer(container); - ObjectName objectName = - new ObjectName( - domain - + "." - + shortName - + ":ID=AGENT"); + ObjectName objectName = new ObjectName( + domain + "." + shortName + ":ID=AGENT"); - info( - "ObjectName=" - + objectName); + info("ObjectName=" + objectName); - StringBuffer attempts = - new StringBuffer(); - - String[] operations = - new String[] - { - "stop", - "restart" - }; - - for (int index = 0; - index < operations.length; - index++) + StringBuffer attempts = new StringBuffer(); + String[] operations = new String[] { - String operation = - operations[index]; + "stop", + "restart" + }; + + for (int index = 0; index < operations.length; index++) + { + String operation = operations[index]; try { - info( - "Trying MBean.invoke(" - + operation - + ")"); - - invokeNoArgs( - connector, - objectName, - operation); - - okRestart( - "MBean." + operation, - shortName, - domain); - + info("Trying MBean.invoke(" + operation + ")"); + invokeNoArgs(connector, objectName, operation); + okRestart("MBean." + operation, shortName, domain); return; } catch (Throwable throwable) { - Throwable root = - rootOf(throwable); - + Throwable root = rootOf(throwable); String line = "MBean." + operation @@ -342,30 +404,12 @@ public final class SonicMfConta*nerTool + root.getMessage(); warn(line); - - attempts - .append(line) - .append('\n'); - - if ("stop".equals(operation) - && looksLikeStopSideEffect(root)) - { - okRestart( - "MBean.stop(side-effect)", - shortName, - domain); - - return; - } + attempts.append(line).append('\n'); } } throw new IllegalStateException( - "Neustart fehlgeschlagen. " - + "ToolVersion=" - + TOOL_VERSION - + "\nAttempts:\n" - + attempts.toString()); + "Neustart fehlgeschlagen:\n" + attempts.toString()); } private static void invokeNoArgs( @@ -375,7 +419,8 @@ public final class SonicMfConta*nerTool throws Exception { Method invokeMethod = - connector.getClass().getMethod( + findMethod( + connector.getClass(), "invoke", new Class[] { @@ -385,86 +430,34 @@ public final class SonicMfConta*nerTool String[].class }); + if (invokeMethod == null) + { + throw new IllegalStateException( + "invoke(...) wurde nicht gefunden."); + } + invokeMethod.invoke( connector, new Object[] { objectName, operation, - new Object[0], - new String[0] + null, + null }); } - private static void disconnect( - Object connector) - { - if (connector == null) - { - return; - } - - try - { - connector - .getClass() - .getMethod("disconnect") - .invoke(connector); - } - catch (Exception ignored) - { - // Die eigentliche Prüfung oder der Neustart - // darf durch einen Disconnect-Fehler nicht - // überschrieben werden. - } - } - - private static Method findMethod( - Class targetClass, - String methodName, - Class[] parameterTypes) - { - try - { - return targetClass.getMethod( - methodName, - parameterTypes); - } - catch (Exception ignored) - { - return null; - } - } - - private static boolean looksLikeStopSideEffect( - Throwable root) - { - if (root == null - || root.getMessage() == null) - { - return false; - } - - String message = - root.getMessage().toLowerCase(); - - return message.indexOf("disconnect") >= 0 - || message.indexOf("closed") >= 0 - || message.indexOf("not connected") >= 0 - || message.indexOf("connection lost") >= 0; - } - private static void okRestart( - String method, - String shortName, + String path, + String container, String domain) { System.out.println( "OK:RestartInvoked" - + " method=" - + method + + " path=" + + path + " container=" - + shortName + + container + " domain=" + domain + " tool=" @@ -473,43 +466,24 @@ public final class SonicMfConta*nerTool System.out.flush(); } - private static Method findConnect( - Class clientClass) + private static void disconnect(Object connector) { - Method[] methods = - clientClass.getMethods(); - - Method bestMethod = null; - - for (int index = 0; - index < methods.length; - index++) + try { Method method = - methods[index]; + findMethod( + connector.getClass(), + "disconnect", + new Class[] {}); - if (!"connect".equals( - method.getName())) + if (method != null) { - continue; - } - - Class[] parameterTypes = - method.getParameterTypes(); - - if (parameterTypes.length == 1 - || parameterTypes.length == 2) - { - bestMethod = method; - - if (parameterTypes.length == 2) - { - return method; - } + method.invoke(connector, new Object[] {}); } } - - return bestMethod; + catch (Throwable ignored) + { + } } private static void trySetTimeout( @@ -518,238 +492,197 @@ public final class SonicMfConta*nerTool { try { - connector - .getClass() - .getMethod( + Method method = + findMethod( + connector.getClass(), "setTimeout", new Class[] { Long.TYPE - }) - .invoke( + }); + + if (method != null) + { + method.invoke( connector, new Object[] { - Long.valueOf( - timeoutMilliseconds) + Long.valueOf(timeoutMilliseconds) }); + } } - catch (Exception ignored) + catch (Throwable ignored) { - try + } + } + + private static Method findConnect(Class clientClass) + { + Method[] methods = clientClass.getMethods(); + + for (int index = 0; index < methods.length; index++) + { + Method method = methods[index]; + + if (!"connect".equals(method.getName())) { - connector - .getClass() - .getMethod( - "setRequestTimeout", - new Class[] - { - Long.TYPE - }) - .invoke( - connector, - new Object[] - { - Long.valueOf( - timeoutMilliseconds) - }); + continue; } - catch (Exception ignoredSecond) + + Class[] types = method.getParameterTypes(); + + if (types.length == 1 || types.length == 2) { - // Optional, nicht jede Sonic-Version - // besitzt diese Methode. + return method; } } + + return null; + } + + private static Method findMethod( + Class type, + String name, + Class[] parameterTypes) + { + try + { + return type.getMethod(name, parameterTypes); + } + catch (NoSuchMethodException exception) + { + return null; + } } private static Object boxTimeout( - Class type, + Class parameterType, long timeoutMilliseconds) { - if (type == Integer.TYPE - || type == Integer.class) + if (parameterType == Integer.TYPE + || parameterType == Integer.class) { - return Integer.valueOf( - (int) Math.min( - Integer.MAX_VALUE, - timeoutMilliseconds)); + long seconds = + Math.max(1L, timeoutMilliseconds / 1000L); + + if (seconds > Integer.MAX_VALUE) + { + seconds = Integer.MAX_VALUE; + } + + return Integer.valueOf((int) seconds); } - return Long.valueOf( - timeoutMilliseconds); + return Long.valueOf(timeoutMilliseconds); } - private static String shortContainer( - String container) + private static String shortContainer(String container) { - String value = - container.trim(); + String value = container.trim(); + int colon = value.indexOf(':'); - int dotIndex = - value.lastIndexOf('.'); + if (colon > 0) + { + value = value.substring(0, colon); + } - return dotIndex >= 0 - ? value.substring(dotIndex + 1) - : value; + int dot = value.lastIndexOf('.'); + + if (dot >= 0 && dot < value.length() - 1) + { + value = value.substring(dot + 1); + } + + return value; } - private static Throwable rootOf( - Throwable throwable) + private static Throwable rootOf(Throwable throwable) { - Throwable current = - throwable; + Throwable current = throwable; while (current.getCause() != null && current.getCause() != current) { - current = - current.getCause(); + current = current.getCause(); } return current; } - private static boolean isBlank( - String value) + private static boolean isBlank(String value) { - return value == null - || value.trim().length() == 0; + return value == null || value.trim().length() == 0; } - private static void info( - String message) + private static void info(String message) { - System.out.println( - "INFO:" + message); - - System.out.flush(); + System.err.println("INFO: " + message); } - private static void warn( - String message) + private static void warn(String message) { - System.out.println( - "WARN:" + message); - - System.out.flush(); + System.err.println("WARN: " + message); } - private static void fail( - String message) + private static void fail(String message) { - System.err.println( - "ERROR:" + message); - - System.err.flush(); - + System.err.println("ERROR: " + message); System.exit(1); } private static final class Args { - String command; - String domain; - String url; - String user; - String password; - String container; + private String command; + private String domain; + private String url; + private String user; + private String password; + private String container; + private int timeoutSec = 60; - int timeoutSec = 120; - - static Args parse( - String[] arguments) + private static Args parse(String[] args) { - Args parsedArguments = - new Args(); + Args result = new Args(); - if (arguments == null - || arguments.length == 0) + if (args == null || args.length == 0) { - return parsedArguments; + fail("Kein Befehl angegeben."); } - parsedArguments.command = - arguments[0]; + result.command = args[0]; + result.password = + System.getenv("ESB_SONIC_PASSWORD"); - for (int index = 1; - index < arguments.length; - index++) + for (int index = 1; index < args.length; index++) { - String key = - arguments[index]; + String token = args[index]; - String value = - index + 1 < arguments.length - ? arguments[index + 1] - : null; - - if ("--domain".equals(key) - && value != null) + if ("--domain".equals(token) && index + 1 < args.length) { - parsedArguments.domain = value; - index++; + result.domain = args[++index]; } - else if ("--url".equals(key) - && value != null) + else if ("--url".equals(token) && index + 1 < args.length) { - parsedArguments.url = value; - index++; + result.url = args[++index]; } - else if ("--user".equals(key) - && value != null) + else if ("--user".equals(token) && index + 1 < args.length) { - parsedArguments.user = value; - index++; + result.user = args[++index]; } - else if ("--password".equals(key) - && value != null) + else if ("--container".equals(token) + && index + 1 < args.length) { - parsedArguments.password = value; - index++; + result.container = args[++index]; } - else if ("--container".equals(key) - && value != null) + else if ("--timeout".equals(token) + && index + 1 < args.length) { - parsedArguments.container = value; - index++; - } - else if ("--timeout".equals(key) - && value != null) - { - try - { - parsedArguments.timeoutSec = - Integer.parseInt(value); - } - catch (Exception ignored) - { - // Standardwert beibehalten. - } - - index++; + result.timeoutSec = + Integer.parseInt(args[++index]); } } - if (isBlank( - parsedArguments.password)) - { - String environmentPassword = - System.getenv( - "ESB_SONIC_PASSWORD"); - - if (!isBlank( - environmentPassword)) - { - parsedArguments.password = - environmentPassword; - } - } - - return parsedArguments; + return result; } } - - private SonicMfContainerTool() - { - } -} \ No newline at end of file +}