Fix Java 8 resolution for MfApi restart, refresh targets after settings, and copy errors to clipboard.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-29 09:34:04 +02:00
co-authored by Cursor
parent f584fd68fe
commit dcceb7b719
14 changed files with 358 additions and 97 deletions
+42 -10
View File
@@ -172,13 +172,12 @@ namespace ZA.CoreService.ESBCertificateManager
coordinator, coordinator,
isSettingsMode: true); isSettingsMode: true);
if (settingsForm.ShowDialog(this) != DialogResult.OK) // Nach dem Schließen immer neu laden:
{ // Pfade/Container können auch ohne „Profil übernehmen“ angelegt worden sein.
return; _ = settingsForm.ShowDialog(this);
}
SetStatus( SetStatus(
"Einstellungen übernommen. Anwendung wird aktualisiert ...", "Einstellungen geschlossen. Ziele werden aktualisiert ...",
isError: false); isError: false);
await InitializeApplicationAsync(); await InitializeApplicationAsync();
@@ -1938,6 +1937,24 @@ namespace ZA.CoreService.ESBCertificateManager
{ {
UpdateToNextStep(4); UpdateToNextStep(4);
} }
else
{
string details = string.Join(
Environment.NewLine + Environment.NewLine,
runResult.TargetResults
.Where(result => !result.Success)
.Select(result =>
$"{result.TargetName}: {result.StatusText}"
+ Environment.NewLine
+ (result.Detail ?? string.Empty)));
CopyableErrorDialog.Show(
this,
"Neustart fehlgeschlagen (MfApi)",
string.IsNullOrWhiteSpace(details)
? "Neustart fehlgeschlagen (keine Details)."
: details);
}
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@@ -1946,7 +1963,10 @@ namespace ZA.CoreService.ESBCertificateManager
catch (Exception ex) catch (Exception ex)
{ {
SetStatus($"Neustart fehlgeschlagen: {ex.Message}", isError: true); SetStatus($"Neustart fehlgeschlagen: {ex.Message}", isError: true);
MessageBox.Show(this, ex.Message, "ESB neu starten", MessageBoxButtons.OK, MessageBoxIcon.Error); CopyableErrorDialog.Show(
this,
"Neustart fehlgeschlagen (MfApi)",
ex.ToString());
} }
finally finally
{ {
@@ -2051,6 +2071,20 @@ namespace ZA.CoreService.ESBCertificateManager
SetStatus( SetStatus(
"Deployment für mindestens ein Ziel fehlgeschlagen.", "Deployment für mindestens ein Ziel fehlgeschlagen.",
isError: true); isError: true);
string details = string.Join(
Environment.NewLine + Environment.NewLine,
runResult.TargetResults
.Where(result => !result.Success)
.Select(result =>
$"{result.TargetName}: {result.StatusText}"
+ Environment.NewLine
+ (result.Detail ?? string.Empty)));
CopyableErrorDialog.Show(
this,
"Deployment fehlgeschlagen",
details);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -2063,12 +2097,10 @@ namespace ZA.CoreService.ESBCertificateManager
$"Deployment fehlgeschlagen: {ex.Message}", $"Deployment fehlgeschlagen: {ex.Message}",
isError: true); isError: true);
MessageBox.Show( CopyableErrorDialog.Show(
this, this,
ex.Message,
"Deployment fehlgeschlagen", "Deployment fehlgeschlagen",
MessageBoxButtons.OK, ex.ToString());
MessageBoxIcon.Error);
} }
finally finally
{ {
@@ -46,7 +46,7 @@ public sealed class DatabaseSettings
public sealed class RuntimeSettings public sealed class RuntimeSettings
{ {
public string JavaExecutablePath { get; set; } = public string JavaExecutablePath { get; set; } =
@"Runtime\bin\java.exe"; @"C:\Program Files (x86)\Java\jre1.8.0_481\bin\java.exe";
public string SonicClientLibraryPath { get; set; } = public string SonicClientLibraryPath { get; set; } =
@"Runtime\SonicClient\lib"; @"Runtime\SonicClient\lib";
@@ -72,10 +72,18 @@ public sealed class DatabaseSonicConnection
"Bitte die Einstellungen öffnen und ein Benutzerprofil hinterlegen."); "Bitte die Einstellungen öffnen und ein Benutzerprofil hinterlegen.");
} }
string javaExecutablePath = string? javaExecutablePath =
PathResolver.ResolvePath( JavaRuntimeLocator.Resolve(
runtimeSettings.JavaExecutablePath); runtimeSettings.JavaExecutablePath);
if (javaExecutablePath is null)
{
throw new InvalidOperationException(
"Java 8 wurde nicht gefunden. " +
"Bitte Runtime:JavaExecutablePath auf " +
@"jre1.8*\bin\java.exe setzen.");
}
string sonicClientLibraryPath = string sonicClientLibraryPath =
PathResolver.ResolvePath( PathResolver.ResolvePath(
runtimeSettings.SonicClientLibraryPath); runtimeSettings.SonicClientLibraryPath);
@@ -0,0 +1,135 @@
namespace ZA.CoreService.ESBCertificateManager.Services;
/// <summary>
/// Fehlerdialog mit Text und Button „In Zwischenablage kopieren“.
/// Kopiert den Fehlertext beim Öffnen automatisch.
/// </summary>
public static class CopyableErrorDialog
{
public static void Show(
IWin32Window? owner,
string title,
string details)
{
string text = string.IsNullOrWhiteSpace(details)
? "(keine Fehlerdetails)"
: details.Trim();
TryCopy(text);
using Form dialog = new()
{
Text = title,
StartPosition = FormStartPosition.CenterParent,
FormBorderStyle = FormBorderStyle.FixedDialog,
MaximizeBox = false,
MinimizeBox = false,
ShowInTaskbar = false,
ClientSize = new Size(640, 420),
BackColor = Color.FromArgb(16, 28, 48),
ForeColor = Color.FromArgb(241, 245, 249),
Font = new Font("Segoe UI", 9.5f)
};
Label hint = new()
{
Text =
"Fehlertext ist in der Zwischenablage — "
+ "einfach hier einfügen (Strg+V).",
Location = new Point(16, 12),
AutoSize = false,
Size = new Size(608, 28),
ForeColor = Color.FromArgb(208, 171, 57)
};
TextBox box = new()
{
Location = new Point(16, 44),
Size = new Size(608, 310),
Multiline = true,
ReadOnly = true,
ScrollBars = ScrollBars.Both,
WordWrap = false,
Text = text,
BackColor = Color.FromArgb(12, 22, 38),
ForeColor = Color.FromArgb(241, 245, 249),
BorderStyle = BorderStyle.FixedSingle,
Font = new Font("Consolas", 9f)
};
Label copyStatus = new()
{
Text = "✓ In Zwischenablage kopiert",
Location = new Point(16, 368),
AutoSize = true,
ForeColor = Color.FromArgb(90, 200, 140)
};
Button btnCopy = new()
{
Text = "Nochmal kopieren",
Location = new Point(280, 362),
Size = new Size(160, 36),
FlatStyle = FlatStyle.Flat,
BackColor = Color.FromArgb(39, 52, 73),
ForeColor = Color.FromArgb(241, 245, 249),
Cursor = Cursors.Hand
};
btnCopy.FlatAppearance.BorderColor =
Color.FromArgb(36, 74, 117);
btnCopy.Click += (_, _) =>
{
if (TryCopy(text))
{
copyStatus.Text = "✓ Erneut kopiert";
copyStatus.ForeColor =
Color.FromArgb(90, 200, 140);
}
else
{
copyStatus.Text = "Kopieren fehlgeschlagen";
copyStatus.ForeColor =
Color.FromArgb(220, 100, 100);
}
};
Button btnClose = new()
{
Text = "Schließen",
Location = new Point(456, 362),
Size = new Size(168, 36),
FlatStyle = FlatStyle.Flat,
BackColor = Color.FromArgb(0, 110, 182),
ForeColor = Color.White,
Cursor = Cursors.Hand,
DialogResult = DialogResult.OK
};
btnClose.FlatAppearance.BorderSize = 0;
dialog.Controls.Add(hint);
dialog.Controls.Add(box);
dialog.Controls.Add(copyStatus);
dialog.Controls.Add(btnCopy);
dialog.Controls.Add(btnClose);
dialog.AcceptButton = btnClose;
dialog.CancelButton = btnClose;
dialog.ShowDialog(owner);
}
private static bool TryCopy(string text)
{
try
{
Clipboard.SetText(text);
return true;
}
catch
{
return false;
}
}
}
@@ -0,0 +1,121 @@
namespace ZA.CoreService.ESBCertificateManager.Services;
/// <summary>
/// Findet eine nutzbare Java-8-Runtime für Sonic MfApi.
/// </summary>
public static class JavaRuntimeLocator
{
public static string? Resolve(
string? configuredPath = null)
{
foreach (string? candidate in EnumerateCandidates(configuredPath))
{
if (string.IsNullOrWhiteSpace(candidate))
{
continue;
}
string? resolved = TryResolveExisting(candidate);
if (resolved is not null)
{
return resolved;
}
}
return null;
}
private static IEnumerable<string?> EnumerateCandidates(
string? configuredPath)
{
if (!string.IsNullOrWhiteSpace(configuredPath))
{
yield return configuredPath.Trim();
}
yield return
@"C:\Program Files (x86)\Java\jre1.8.0_481\bin\java.exe";
yield return
@"C:\Program Files (x86)\Java\jre1.8.0_501\bin\java.exe";
yield return
@"C:\Program Files\Java\jre1.8.0_481\bin\java.exe";
yield return
@"C:\Program Files (x86)\Common Files\Oracle\Java\java8path\java.exe";
foreach (string root in new[]
{
@"C:\Program Files (x86)\Java",
@"C:\Program Files\Java"
})
{
if (!Directory.Exists(root))
{
continue;
}
foreach (string directory in Directory
.EnumerateDirectories(root, "jre1.8*")
.OrderByDescending(path => path, StringComparer.OrdinalIgnoreCase))
{
yield return Path.Combine(directory, "bin", "java.exe");
}
foreach (string directory in Directory
.EnumerateDirectories(root, "jdk1.8*")
.OrderByDescending(path => path, StringComparer.OrdinalIgnoreCase))
{
yield return Path.Combine(directory, "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";
}
private static string? TryResolveExisting(string candidate)
{
try
{
string resolved =
Path.IsPathRooted(candidate)
? Path.GetFullPath(candidate)
: Path.GetFullPath(
Path.Combine(
AppContext.BaseDirectory,
candidate));
return File.Exists(resolved)
? resolved
: null;
}
catch
{
return null;
}
}
}
@@ -27,7 +27,9 @@ public sealed class RestartExecutor
if (connection is null) if (connection is null)
{ {
return (false, "Sonic-Verbindung nicht gefunden", return (false, "Sonic-Verbindung nicht gefunden",
$"Ziel '{target.Name}': SonicConnection '{target.SonicConnectionName}' fehlt in appsettings."); $"Ziel '{target.Name}': SonicConnection '{target.SonicConnectionName}' "
+ "ist nicht geladen. Meist fehlt ein Benutzerprofil "
+ "(Einstellungen → Profil anlegen und übernehmen).");
} }
using SonicManagementClient client = using SonicManagementClient client =
@@ -11,8 +11,8 @@ public sealed class RuntimeEnvironmentValidator
{ {
List<string> issues = []; List<string> issues = [];
string javaExecutablePath = string? javaExecutablePath =
PathResolver.ResolvePath( JavaRuntimeLocator.Resolve(
settings.JavaExecutablePath); settings.JavaExecutablePath);
string sonicClientLibraryPath = string sonicClientLibraryPath =
@@ -21,10 +21,11 @@ public sealed class RuntimeEnvironmentValidator
string javaVersion = string.Empty; string javaVersion = string.Empty;
if (!File.Exists(javaExecutablePath)) if (javaExecutablePath is null)
{ {
issues.Add( issues.Add(
$"Java wurde nicht gefunden: {javaExecutablePath}"); "Java 8 wurde nicht gefunden. "
+ "Runtime:JavaExecutablePath oder installierte jre1.8* prüfen.");
} }
else else
{ {
@@ -80,7 +81,7 @@ public sealed class RuntimeEnvironmentValidator
return new RuntimeValidationResult return new RuntimeValidationResult
{ {
IsValid = issues.Count == 0, IsValid = issues.Count == 0,
JavaExecutablePath = javaExecutablePath, JavaExecutablePath = javaExecutablePath ?? string.Empty,
SonicClientLibraryPath = sonicClientLibraryPath, SonicClientLibraryPath = sonicClientLibraryPath,
JavaVersion = javaVersion, JavaVersion = javaVersion,
Issues = issues Issues = issues
@@ -44,8 +44,8 @@ public sealed class SonicCredentialTester
"Für den Verbindungstest fehlt ein Container."); "Für den Verbindungstest fehlt ein Container.");
} }
string javaPath = string? javaPath =
PathResolver.ResolvePath( JavaRuntimeLocator.Resolve(
_runtimeSettings.JavaExecutablePath); _runtimeSettings.JavaExecutablePath);
string libraryPath = string libraryPath =
@@ -55,10 +55,12 @@ public sealed class SonicCredentialTester
string? toolsPath = string? toolsPath =
ResolveToolsDirectory(); ResolveToolsDirectory();
if (!File.Exists(javaPath)) if (javaPath is null)
{ {
return CredentialTestResult.Failed( return CredentialTestResult.Failed(
$"Java wurde nicht gefunden: {javaPath}"); "Java 8 wurde nicht gefunden. "
+ "Runtime:JavaExecutablePath prüfen "
+ @"(z. B. C:\Program Files (x86)\Java\jre1.8.0_481\bin\java.exe).");
} }
if (!Directory.Exists(libraryPath)) if (!Directory.Exists(libraryPath))
@@ -356,84 +356,35 @@ public sealed class SonicMfApiExecutor
private string? ResolveJavaExe() private string? ResolveJavaExe()
{ {
foreach (string? candidate // Explizite Verbindungs-Pfade zuerst, dann Java-8-Suche
in EnumerateJavaCandidates()) // (Runtime\bin\java.exe fehlt oft; JAVA_HOME zeigt oft auf JDK 21).
string? fromConnection = JavaRuntimeLocator.Resolve(
!string.IsNullOrWhiteSpace(_connection.JavaPath)
? _connection.JavaPath
: null);
if (fromConnection is not null)
{ {
if (string.IsNullOrWhiteSpace(candidate)) return fromConnection;
{
continue;
}
string resolvedCandidate;
try
{
resolvedCandidate =
Path.IsPathRooted(candidate)
? Path.GetFullPath(candidate)
: Path.GetFullPath(
Path.Combine(
AppContext.BaseDirectory,
candidate));
}
catch
{
continue;
}
if (File.Exists(resolvedCandidate))
{
return resolvedCandidate;
}
} }
return null; if (!string.IsNullOrWhiteSpace(_connection.JavaHome))
}
private IEnumerable<string?>
EnumerateJavaCandidates()
{
if (!string.IsNullOrWhiteSpace(
_connection.JavaPath))
{ {
yield return string fromHome = Path.Combine(
_connection.JavaPath.Trim();
}
if (!string.IsNullOrWhiteSpace(
_connection.JavaHome))
{
yield return Path.Combine(
_connection.JavaHome.Trim(), _connection.JavaHome.Trim(),
"bin", "bin",
"java.exe"); "java.exe");
string? resolvedHome =
JavaRuntimeLocator.Resolve(fromHome);
if (resolvedHome is not null)
{
return resolvedHome;
}
} }
string? javaHome = return JavaRuntimeLocator.Resolve();
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";
} }
private ( private (
@@ -67,6 +67,15 @@ public sealed class SetupWizardForm : Form
ShowPanel(_panelStatus, _navButtons[0]); ShowPanel(_panelStatus, _navButtons[0]);
Shown += async (_, _) => await RefreshAllAsync(); Shown += async (_, _) => await RefreshAllAsync();
FormClosing += (_, e) =>
{
// X-Button: in Einstellungen trotzdem als „geschlossen mit Änderungen möglich“
if (_isSettingsMode && DialogResult == DialogResult.None)
{
DialogResult = DialogResult.OK;
}
};
} }
private void BuildShell() private void BuildShell()
@@ -1138,10 +1147,10 @@ public sealed class SetupWizardForm : Form
{ {
if (_isSettingsMode) if (_isSettingsMode)
{ {
SetupCheckResult check = await _coordinator.CheckAsync(); // Katalog-Änderungen (Pfade/Container) sollen die Hauptansicht
DialogResult = check.IsReady // immer aktualisieren unabhängig davon, ob das Profil schon „ready“ ist.
? DialogResult.OK _ = await _coordinator.CheckAsync();
: DialogResult.Cancel; DialogResult = DialogResult.OK;
} }
else else
{ {
@@ -6,7 +6,7 @@
"ConnectionString": "Server=VWEBDEV01;Database=EsbZertifikatManager;Integrated Security=True;Encrypt=True;Column Encryption Setting=Enabled;Application Name=ZA.CoreService.ESBCertificateManager;TrustServerCertificate=True;" "ConnectionString": "Server=VWEBDEV01;Database=EsbZertifikatManager;Integrated Security=True;Encrypt=True;Column Encryption Setting=Enabled;Application Name=ZA.CoreService.ESBCertificateManager;TrustServerCertificate=True;"
}, },
"Runtime": { "Runtime": {
"JavaExecutablePath": "Runtime\\bin\\java.exe", "JavaExecutablePath": "C:\\Program Files (x86)\\Java\\jre1.8.0_481\\bin\\java.exe",
"SonicClientLibraryPath": "Runtime\\SonicClient\\lib", "SonicClientLibraryPath": "Runtime\\SonicClient\\lib",
"SetupCompleted": true "SetupCompleted": true
}, },