Add Sonic container scan with manual multi-select after system setup.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-29 09:03:01 +02:00
co-authored by Cursor
parent 09b31a4ac9
commit f7e9bd43e2
7 changed files with 1160 additions and 406 deletions
@@ -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<ContainerScanResult> 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<string> standardOutputTask =
process.StandardOutput.ReadToEndAsync();
Task<string> 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<string> 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<string> Containers { get; init; } = [];
public static ContainerScanResult Successful(
IReadOnlyList<string> 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
};
}
}
@@ -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<CompanyOption> 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<SonicCredentialProfile> 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<ContainerOption> 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<string> selected = _lstContainers.CheckedItems
.Cast<object>()
.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<ContainerOption> existing =
await _coordinator.LoadContainersAsync(
_system.SonicConnectionId);
HashSet<string> 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);
}
}
@@ -381,7 +381,9 @@ public sealed class SetupWizardForm : Form
Panel toolbar = CreateToolbar( Panel toolbar = CreateToolbar(
"+ Container hinzufügen", "+ Container hinzufügen",
async () => await AddContainerAsync()); async () => await AddContainerAsync(),
secondaryText: "Von Sonic scannen",
onSecondary: async () => await ScanContainersForSelectedSystemAsync());
_lvContainers = SettingsUi.CreateListView(); _lvContainers = SettingsUi.CreateListView();
_lvContainers.Dock = DockStyle.Fill; _lvContainers.Dock = DockStyle.Fill;
@@ -418,7 +420,9 @@ public sealed class SetupWizardForm : Form
private Panel CreateToolbar( private Panel CreateToolbar(
string addButtonText, string addButtonText,
Func<Task> onAdd) Func<Task> onAdd,
string? secondaryText = null,
Func<Task>? onSecondary = null)
{ {
Panel toolbar = new() Panel toolbar = new()
{ {
@@ -433,6 +437,16 @@ public sealed class SetupWizardForm : Form
add.Width = 230; add.Width = 230;
add.Click += async (_, _) => await onAdd(); 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); toolbar.Controls.Add(add);
return toolbar; return toolbar;
} }
@@ -692,6 +706,78 @@ public sealed class SetupWizardForm : Form
"Prüfung OK", "Prüfung OK",
MessageBoxButtons.OK, MessageBoxButtons.OK,
MessageBoxIcon.Information); 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() private async Task AddContainerAsync()
@@ -1,68 +1,72 @@
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;
import javax.management.ObjectName; import javax.management.ObjectName;
/** /**
* Sonic-MfApi-Hilfsprogramm. * Sonic-MfApi-Hilfsprogramm.
* *
* Unterstützte Befehle: * Befehle:
* * validate - Anmeldung + lesender Container-Zugriff
* validate * restart - Container neu starten (stop/restart)
* Prüft Anmeldung und lesenden Zugriff auf den Container. * list - Container der Domain auflisten
* Es wird kein Neustart ausgeführt.
*
* restart
* Startet den angegebenen Container neu.
*
* Kompilierung mit Java 8:
* *
* javac -source 8 -target 8 SonicMfContainerTool.java * javac -source 8 -target 8 SonicMfContainerTool.java
*/ */
public final class SonicMfConta*nerTool public final class SonicMfContainerTool
{ {
public static final *tring TOOL_VERSION = public static final String TOOL_VERSION =
"2026*07-27-validate-and-restart"; "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 try
* {
Args parsedArgu*ents = Args.parse(args);
* if (!"validate".equals(parsedAr*uments.command)
&&*!"restart".equals(parsedArguments.*ommand))
{ {
* fail( Args parsedArguments = Args.parse(args);
"Usa*e: SonicMfContainerTool "
* + "<validate|restart> "* + "--domain D *
+ "--url U "
* + "--user U "
* + "--container C*"
+ "[--timeout SEC]");
return; if (!"validate".equals(parsedArguments.command)
* } && !"restart".equals(parsedArguments.command)
&& !"list".equals(parsedArguments.command))
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( fail(
* "Das Sonic-Kennwort wurde nicht über " "Usage: SonicMfContainerTool "
+ "ESB_SONIC_PASSWORD bereitgestellt."); + "<validate|restart|list> "
+ "--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; return;
} }
@@ -78,7 +82,13 @@ public final class SonicMfConta*nerTool
try try
{ {
if ("validate".equals( if ("list".equals(parsedArguments.command))
{
listContainers(
connector,
parsedArguments.domain);
}
else if ("validate".equals(
parsedArguments.command)) parsedArguments.command))
{ {
validateConnection( validateConnection(
@@ -102,7 +112,6 @@ public final class SonicMfConta*nerTool
catch (Throwable throwable) catch (Throwable throwable)
{ {
Throwable root = rootOf(throwable); Throwable root = rootOf(throwable);
String message = root.getMessage(); String message = root.getMessage();
if (message == null) 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( private static Object connect(
String url, String url,
String user, String user,
@@ -126,17 +244,9 @@ public final class SonicMfConta*nerTool
{ {
Hashtable environment = new Hashtable(); Hashtable environment = new Hashtable();
environment.put( environment.put("ConnectionURLs", url);
"ConnectionURLs", environment.put("DefaultUser", user);
url); environment.put("DefaultPassword", password);
environment.put(
"DefaultUser",
user);
environment.put(
"DefaultPassword",
password);
Class addressClass = Class.forName( Class addressClass = Class.forName(
"com.sonicsw.mf.jmx.client.JMSConnectorAddress"); "com.sonicsw.mf.jmx.client.JMSConnectorAddress");
@@ -160,18 +270,14 @@ public final class SonicMfConta*nerTool
.getConstructor(new Class[] {}) .getConstructor(new Class[] {})
.newInstance(new Object[] {}); .newInstance(new Object[] {});
trySetTimeout( trySetTimeout(connector, timeoutMilliseconds);
connector,
timeoutMilliseconds);
Method connectMethod = Method connectMethod = findConnect(clientClass);
findConnect(clientClass);
if (connectMethod == null) if (connectMethod == null)
{ {
throw new IllegalStateException( throw new IllegalStateException(
"JMSConnectorClient.connect(...) " "JMSConnectorClient.connect(...) wurde nicht gefunden.");
+ "wurde nicht gefunden.");
} }
Class[] parameterTypes = Class[] parameterTypes =
@@ -202,33 +308,18 @@ public final class SonicMfConta*nerTool
return connector; 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( private static void validateConnection(
Object connector, Object connector,
String domain, String domain,
String container) String container)
throws Exception throws Exception
{ {
String shortName = String shortName = shortContainer(container);
shortContainer(container);
ObjectName objectName = ObjectName objectName = new ObjectName(
new ObjectName( domain + "." + shortName + ":ID=AGENT");
domain
+ "."
+ shortName
+ ":ID=AGENT");
info( info("ValidatingObjectName=" + objectName);
"ValidatingObjectName="
+ objectName);
Method getMBeanInfoMethod = Method getMBeanInfoMethod =
findMethod( findMethod(
@@ -242,9 +333,7 @@ public final class SonicMfConta*nerTool
if (getMBeanInfoMethod == null) if (getMBeanInfoMethod == null)
{ {
throw new IllegalStateException( throw new IllegalStateException(
"Die lesende MfApi-Methode " "getMBeanInfo(ObjectName) wurde nicht gefunden.");
+ "getMBeanInfo(ObjectName) "
+ "wurde nicht gefunden.");
} }
Object result = getMBeanInfoMethod.invoke( Object result = getMBeanInfoMethod.invoke(
@@ -278,61 +367,34 @@ public final class SonicMfConta*nerTool
String container) String container)
throws Exception throws Exception
{ {
String shortName = String shortName = shortContainer(container);
shortContainer(container);
ObjectName objectName = ObjectName objectName = new ObjectName(
new ObjectName( domain + "." + shortName + ":ID=AGENT");
domain
+ "."
+ shortName
+ ":ID=AGENT");
info( info("ObjectName=" + objectName);
"ObjectName="
+ objectName);
StringBuffer attempts = StringBuffer attempts = new StringBuffer();
new StringBuffer(); String[] operations = new String[]
String[] operations =
new String[]
{ {
"stop", "stop",
"restart" "restart"
}; };
for (int index = 0; for (int index = 0; index < operations.length; index++)
index < operations.length;
index++)
{ {
String operation = String operation = operations[index];
operations[index];
try try
{ {
info( info("Trying MBean.invoke(" + operation + ")");
"Trying MBean.invoke(" invokeNoArgs(connector, objectName, operation);
+ operation okRestart("MBean." + operation, shortName, domain);
+ ")");
invokeNoArgs(
connector,
objectName,
operation);
okRestart(
"MBean." + operation,
shortName,
domain);
return; return;
} }
catch (Throwable throwable) catch (Throwable throwable)
{ {
Throwable root = Throwable root = rootOf(throwable);
rootOf(throwable);
String line = String line =
"MBean." "MBean."
+ operation + operation
@@ -342,30 +404,12 @@ public final class SonicMfConta*nerTool
+ root.getMessage(); + root.getMessage();
warn(line); warn(line);
attempts.append(line).append('\n');
attempts
.append(line)
.append('\n');
if ("stop".equals(operation)
&& looksLikeStopSideEffect(root))
{
okRestart(
"MBean.stop(side-effect)",
shortName,
domain);
return;
}
} }
} }
throw new IllegalStateException( throw new IllegalStateException(
"Neustart fehlgeschlagen. " "Neustart fehlgeschlagen:\n" + attempts.toString());
+ "ToolVersion="
+ TOOL_VERSION
+ "\nAttempts:\n"
+ attempts.toString());
} }
private static void invokeNoArgs( private static void invokeNoArgs(
@@ -375,7 +419,8 @@ public final class SonicMfConta*nerTool
throws Exception throws Exception
{ {
Method invokeMethod = Method invokeMethod =
connector.getClass().getMethod( findMethod(
connector.getClass(),
"invoke", "invoke",
new Class[] new Class[]
{ {
@@ -385,86 +430,34 @@ public final class SonicMfConta*nerTool
String[].class String[].class
}); });
if (invokeMethod == null)
{
throw new IllegalStateException(
"invoke(...) wurde nicht gefunden.");
}
invokeMethod.invoke( invokeMethod.invoke(
connector, connector,
new Object[] new Object[]
{ {
objectName, objectName,
operation, operation,
new Object[0], null,
new String[0] 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( private static void okRestart(
String method, String path,
String shortName, String container,
String domain) String domain)
{ {
System.out.println( System.out.println(
"OK:RestartInvoked" "OK:RestartInvoked"
+ " method=" + " path="
+ method + path
+ " container=" + " container="
+ shortName + container
+ " domain=" + " domain="
+ domain + domain
+ " tool=" + " tool="
@@ -473,44 +466,25 @@ public final class SonicMfConta*nerTool
System.out.flush(); System.out.flush();
} }
private static Method findConnect( private static void disconnect(Object connector)
Class clientClass)
{ {
Method[] methods = try
clientClass.getMethods();
Method bestMethod = null;
for (int index = 0;
index < methods.length;
index++)
{ {
Method method = Method method =
methods[index]; findMethod(
connector.getClass(),
"disconnect",
new Class[] {});
if (!"connect".equals( if (method != null)
method.getName()))
{ {
continue; method.invoke(connector, new Object[] {});
} }
}
Class[] parameterTypes = catch (Throwable ignored)
method.getParameterTypes();
if (parameterTypes.length == 1
|| parameterTypes.length == 2)
{ {
bestMethod = method;
if (parameterTypes.length == 2)
{
return method;
} }
} }
}
return bestMethod;
}
private static void trySetTimeout( private static void trySetTimeout(
Object connector, Object connector,
@@ -518,238 +492,197 @@ public final class SonicMfConta*nerTool
{ {
try try
{ {
connector Method method =
.getClass() findMethod(
.getMethod( connector.getClass(),
"setTimeout", "setTimeout",
new Class[] new Class[]
{ {
Long.TYPE Long.TYPE
}) });
.invoke(
if (method != null)
{
method.invoke(
connector, connector,
new Object[] new Object[]
{ {
Long.valueOf( Long.valueOf(timeoutMilliseconds)
timeoutMilliseconds)
}); });
} }
catch (Exception ignored) }
catch (Throwable ignored)
{
}
}
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()))
{
continue;
}
Class[] types = method.getParameterTypes();
if (types.length == 1 || types.length == 2)
{
return method;
}
}
return null;
}
private static Method findMethod(
Class type,
String name,
Class[] parameterTypes)
{ {
try try
{ {
connector return type.getMethod(name, parameterTypes);
.getClass()
.getMethod(
"setRequestTimeout",
new Class[]
{
Long.TYPE
})
.invoke(
connector,
new Object[]
{
Long.valueOf(
timeoutMilliseconds)
});
} }
catch (Exception ignoredSecond) catch (NoSuchMethodException exception)
{ {
// Optional, nicht jede Sonic-Version return null;
// besitzt diese Methode.
}
} }
} }
private static Object boxTimeout( private static Object boxTimeout(
Class type, Class parameterType,
long timeoutMilliseconds) long timeoutMilliseconds)
{ {
if (type == Integer.TYPE if (parameterType == Integer.TYPE
|| type == Integer.class) || parameterType == Integer.class)
{ {
return Integer.valueOf( long seconds =
(int) Math.min( Math.max(1L, timeoutMilliseconds / 1000L);
Integer.MAX_VALUE,
timeoutMilliseconds)); if (seconds > Integer.MAX_VALUE)
{
seconds = Integer.MAX_VALUE;
} }
return Long.valueOf( return Integer.valueOf((int) seconds);
timeoutMilliseconds);
} }
private static String shortContainer( return Long.valueOf(timeoutMilliseconds);
String container)
{
String value =
container.trim();
int dotIndex =
value.lastIndexOf('.');
return dotIndex >= 0
? value.substring(dotIndex + 1)
: value;
} }
private static Throwable rootOf( private static String shortContainer(String container)
Throwable throwable)
{ {
Throwable current = String value = container.trim();
throwable; int colon = value.indexOf(':');
if (colon > 0)
{
value = value.substring(0, colon);
}
int dot = value.lastIndexOf('.');
if (dot >= 0 && dot < value.length() - 1)
{
value = value.substring(dot + 1);
}
return value;
}
private static Throwable rootOf(Throwable throwable)
{
Throwable current = throwable;
while (current.getCause() != null while (current.getCause() != null
&& current.getCause() != current) && current.getCause() != current)
{ {
current = current = current.getCause();
current.getCause();
} }
return current; return current;
} }
private static boolean isBlank( private static boolean isBlank(String value)
String value)
{ {
return value == null return value == null || value.trim().length() == 0;
|| value.trim().length() == 0;
} }
private static void info( private static void info(String message)
String message)
{ {
System.out.println( System.err.println("INFO: " + message);
"INFO:" + message);
System.out.flush();
} }
private static void warn( private static void warn(String message)
String message)
{ {
System.out.println( System.err.println("WARN: " + message);
"WARN:" + message);
System.out.flush();
} }
private static void fail( private static void fail(String message)
String message)
{ {
System.err.println( System.err.println("ERROR: " + message);
"ERROR:" + message);
System.err.flush();
System.exit(1); System.exit(1);
} }
private static final class Args private static final class Args
{ {
String command; private String command;
String domain; private String domain;
String url; private String url;
String user; private String user;
String password; private String password;
String container; private String container;
private int timeoutSec = 60;
int timeoutSec = 120; private static Args parse(String[] args)
static Args parse(
String[] arguments)
{ {
Args parsedArguments = Args result = new Args();
new Args();
if (arguments == null if (args == null || args.length == 0)
|| arguments.length == 0)
{ {
return parsedArguments; fail("Kein Befehl angegeben.");
} }
parsedArguments.command = result.command = args[0];
arguments[0]; result.password =
System.getenv("ESB_SONIC_PASSWORD");
for (int index = 1; for (int index = 1; index < args.length; index++)
index < arguments.length;
index++)
{ {
String key = String token = args[index];
arguments[index];
String value = if ("--domain".equals(token) && index + 1 < args.length)
index + 1 < arguments.length
? arguments[index + 1]
: null;
if ("--domain".equals(key)
&& value != null)
{ {
parsedArguments.domain = value; result.domain = args[++index];
index++;
} }
else if ("--url".equals(key) else if ("--url".equals(token) && index + 1 < args.length)
&& value != null)
{ {
parsedArguments.url = value; result.url = args[++index];
index++;
} }
else if ("--user".equals(key) else if ("--user".equals(token) && index + 1 < args.length)
&& value != null)
{ {
parsedArguments.user = value; result.user = args[++index];
index++;
} }
else if ("--password".equals(key) else if ("--container".equals(token)
&& value != null) && index + 1 < args.length)
{ {
parsedArguments.password = value; result.container = args[++index];
index++;
} }
else if ("--container".equals(key) else if ("--timeout".equals(token)
&& value != null) && index + 1 < args.length)
{ {
parsedArguments.container = value; result.timeoutSec =
index++; Integer.parseInt(args[++index]);
}
else if ("--timeout".equals(key)
&& value != null)
{
try
{
parsedArguments.timeoutSec =
Integer.parseInt(value);
}
catch (Exception ignored)
{
// Standardwert beibehalten.
}
index++;
} }
} }
if (isBlank( return result;
parsedArguments.password))
{
String environmentPassword =
System.getenv(
"ESB_SONIC_PASSWORD");
if (!isBlank(
environmentPassword))
{
parsedArguments.password =
environmentPassword;
} }
} }
return parsedArguments;
}
}
private SonicMfContainerTool()
{
}
} }