From d6cf49532b5829abbbd89321152a2692d2388584 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 24 Jul 2026 09:33:35 +0200 Subject: [PATCH] Fix UnsupportedClassVersionError: ship Java 8 SonicMfContainerTool. Co-authored-by: Cursor --- .../Services/SonicMfApiExecutor.cs | 181 +++++++++++++++--- .../Tools/SonicMfContainerTool.class | Bin 6684 -> 7413 bytes .../Tools/SonicMfContainerTool.java | 144 +++++++++----- 3 files changed, 251 insertions(+), 74 deletions(-) diff --git a/ZA.CoreService.ESBCertificateManager/Services/SonicMfApiExecutor.cs b/ZA.CoreService.ESBCertificateManager/Services/SonicMfApiExecutor.cs index 17cd4fd..3f10624 100644 --- a/ZA.CoreService.ESBCertificateManager/Services/SonicMfApiExecutor.cs +++ b/ZA.CoreService.ESBCertificateManager/Services/SonicMfApiExecutor.cs @@ -210,24 +210,28 @@ public sealed class SonicMfApiExecutor return (false, null, null, null, libError); } - // Bevorzugt: vorcompilierte .jar/.class aus Tools\ (kein javac nötig). - if (TryResolvePrebuiltTool(out string? prebuiltDir, out string? prebuiltEntry, out string? prebuiltError) + int runtimeMajor = await GetJavaClassMajorAsync(javaExe, cancellationToken); + // Sonic/SMC nutzt typisch Java 8 (major 52). Nie mit höherer Class-Version starten. + + // Vorcompilierte .class/.jar nur nutzen, wenn Class-Version zur Runtime passt. + if (TryResolvePrebuiltTool(runtimeMajor, out string? prebuiltDir, out string? prebuiltEntry, out string? prebuiltError) && prebuiltDir is not null && prebuiltEntry is not null) { string classpath = BuildClasspath(libDir, prebuiltEntry); return (true, javaExe, prebuiltDir, classpath, null); } - // Fallback: zur Laufzeit aus .java kompilieren (braucht javac + Sonic-Libs). + // Zur Laufzeit aus .java für Java 8 kompilieren (--release 8 / -source 1.8 -target 1.8). string? sourcePath = ResolveToolSourcePath(); if (sourcePath is null) { return (false, null, null, null, - (prebuiltError ?? "Kein vorcompilierter SonicMfContainerTool gefunden.") + - " Tools\\SonicMfContainerTool.java/.class/.jar fehlen im Ausgabeverzeichnis."); + (prebuiltError ?? "Kein SonicMfContainerTool gefunden.") + + " Tools\\SonicMfContainerTool.java/.class fehlen.\n" + + $"Runtime-Java: {javaExe} (class major max={runtimeMajor})."); } - string workDir = Path.Combine(Path.GetTempPath(), "esb-sonic-mf"); + string workDir = Path.Combine(Path.GetTempPath(), "esb-sonic-mf-j8"); Directory.CreateDirectory(workDir); string javaTarget = Path.Combine(workDir, "SonicMfContainerTool.java"); @@ -237,24 +241,33 @@ public sealed class SonicMfApiExecutor string compileClasspath = BuildClasspath(libDir, null); string runtimeClasspath = BuildClasspath(libDir, workDir); - bool needsCompile = !File.Exists(classFile) - || File.GetLastWriteTimeUtc(classFile) < File.GetLastWriteTimeUtc(sourcePath); - if (needsCompile) + bool classOk = File.Exists(classFile) + && ReadClassFileMajorVersion(classFile) is int existingMajor + && existingMajor <= runtimeMajor + && File.GetLastWriteTimeUtc(classFile) >= File.GetLastWriteTimeUtc(sourcePath); + + if (!classOk) { string? javac = ResolveJavacExecutable(javaExe); if (javac is null) { return (false, null, null, null, - "Vorcompilierter SonicMfContainerTool fehlt und javac wurde nicht gefunden. " + - "JDK installieren oder Tools\\SonicMfContainerTool.jar/.class mit ausliefern. " + + "SonicMfContainerTool muss für Java 8 gebaut werden, aber javac fehlt.\n" + + "Tools\\SonicMfContainerTool.class (major<=52) mit ausliefern oder JDK installieren.\n" + $"Gefundene java.exe: {javaExe}"); } + // Wichtig: ohne --release 8 erzeugt modernes javac major 65 → UnsupportedClassVersionError auf Sonic-JRE 8. + string releaseArgs = SupportsJavacRelease8(javac) + ? "--release 8" + : "-source 1.8 -target 1.8"; + ProcessStartInfo compilePsi = new() { FileName = javac, - Arguments = $"-encoding UTF-8 -cp {Quote(compileClasspath)} -d {Quote(workDir)} {Quote(javaTarget)}", + Arguments = + $"{releaseArgs} -encoding UTF-8 -cp {Quote(compileClasspath)} -d {Quote(workDir)} {Quote(javaTarget)}", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, @@ -273,19 +286,25 @@ public sealed class SonicMfApiExecutor string cErr = await compile.StandardError.ReadToEndAsync(cancellationToken); await compile.WaitForExitAsync(cancellationToken); - if (compile.ExitCode != 0 || !File.Exists(classFile)) + int? builtMajor = File.Exists(classFile) ? ReadClassFileMajorVersion(classFile) : null; + if (compile.ExitCode != 0 || builtMajor is null || builtMajor > runtimeMajor) { return (false, null, null, null, - "Kompilieren von SonicMfContainerTool fehlgeschlagen.\n" + + "Kompilieren von SonicMfContainerTool für Java 8 fehlgeschlagen.\n" + Truncate((cErr + "\n" + cOut).Trim()) + - $"\nClasspath-Lib: {libDir}"); + $"\nErzeugt major={builtMajor?.ToString() ?? "?"}, Runtime erlaubt <={runtimeMajor}.\n" + + $"Classpath-Lib: {libDir}"); } } return (true, javaExe, workDir, runtimeClasspath, null); } - private bool TryResolvePrebuiltTool(out string? workDir, out string? classpathEntry, out string? error) + private bool TryResolvePrebuiltTool( + int runtimeMajor, + out string? workDir, + out string? classpathEntry, + out string? error) { workDir = null; classpathEntry = null; @@ -300,27 +319,133 @@ public sealed class SonicMfApiExecutor foreach (string dir in searchDirs.Where(Directory.Exists)) { - string jar = Path.Combine(dir, "SonicMfContainerTool.jar"); - if (File.Exists(jar)) - { - workDir = dir; - classpathEntry = jar; - return true; - } - string classFile = Path.Combine(dir, "SonicMfContainerTool.class"); if (File.Exists(classFile)) { - workDir = dir; - classpathEntry = dir; - return true; + int? major = ReadClassFileMajorVersion(classFile); + if (major is not null && major <= runtimeMajor) + { + workDir = dir; + classpathEntry = dir; + return true; + } + + error = + $"Tools\\SonicMfContainerTool.class ist zu neu (major={major}, braucht <={runtimeMajor} / Java 8)."; + continue; + } + + string jar = Path.Combine(dir, "SonicMfContainerTool.jar"); + if (File.Exists(jar)) + { + // JAR ohne Version-Check nur wenn Runtime >= 52; bei Unsicherheit neu kompilieren. + if (runtimeMajor >= 52) + { + workDir = dir; + classpathEntry = jar; + return true; + } } } - error = "Tools\\SonicMfContainerTool.jar/.class nicht gefunden."; + error ??= "Tools\\SonicMfContainerTool.class (Java 8) nicht gefunden."; return false; } + private static int? ReadClassFileMajorVersion(string classFile) + { + try + { + byte[] header = new byte[8]; + using FileStream fs = File.OpenRead(classFile); + if (fs.Read(header, 0, 8) < 8) + { + return null; + } + + // CA FE BA BE | minor | major + if (header[0] != 0xCA || header[1] != 0xFE || header[2] != 0xBA || header[3] != 0xBE) + { + return null; + } + + return (header[6] << 8) | header[7]; + } + catch + { + return null; + } + } + + private static async Task GetJavaClassMajorAsync(string javaExe, CancellationToken cancellationToken) + { + try + { + ProcessStartInfo psi = new() + { + FileName = javaExe, + Arguments = "-version", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + + using Process p = new() { StartInfo = psi }; + if (!p.Start()) + { + return 52; // Sonic-Default Java 8 + } + + string err = await p.StandardError.ReadToEndAsync(cancellationToken); + string output = await p.StandardOutput.ReadToEndAsync(cancellationToken); + await p.WaitForExitAsync(cancellationToken); + string text = (err + "\n" + output).ToLowerInvariant(); + + if (text.Contains("version \"1.8") || text.Contains("version \"8")) + { + return 52; + } + + Match m = Regex.Match(text, @"version ""(\d+)"); + if (m.Success && int.TryParse(m.Groups[1].Value, out int major) && major >= 9) + { + // Java 9+ class major = 44 + version => 9→53, 11→55, 17→61, 21→65 + return 44 + major; + } + } + catch + { + // ignore + } + + return 52; + } + + private static bool SupportsJavacRelease8(string javacPath) + { + try + { + using Process p = Process.Start(new ProcessStartInfo + { + FileName = javacPath, + Arguments = "-version", + UseShellExecute = false, + RedirectStandardError = true, + RedirectStandardOutput = true, + CreateNoWindow = true + })!; + string text = (p.StandardError.ReadToEnd() + p.StandardOutput.ReadToEnd()).ToLowerInvariant(); + p.WaitForExit(5000); + // javac 9+ supports --release + return !text.Contains("1.8") && !text.Contains("1.7") && !text.Contains("1.6"); + } + catch + { + return false; + } + } + private (string? Dir, string? Error) ResolveLibDirectoryDetailed() { List<(string Path, string Label)> candidates = []; diff --git a/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.class b/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.class index aad62536065a44a5c4ecb64eac4e0613f567d34f..955a408ee77d23f631576b746768f4cfa628b08f 100644 GIT binary patch literal 7413 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 MsjJm0wHl$n0U$`g4gdfE diff --git a/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.java b/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.java index 973304d..8e6376c 100644 --- a/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.java +++ b/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.java @@ -1,33 +1,26 @@ +import java.lang.reflect.Method; import java.util.Hashtable; import java.util.Iterator; import java.util.Set; import javax.management.ObjectName; -import com.sonicsw.mf.jmx.client.JMSConnectorAddress; -import com.sonicsw.mf.jmx.client.JMSConnectorClient; -import com.sonicsw.mf.mgmtapi.runtime.IAgentProxy; -import com.sonicsw.mf.mgmtapi.runtime.MFProxyFactory; - /** - * Sonic MF Management Runtime API helper (Aurea CX Messenger / Progress Sonic). + * Sonic Management Application API helper (same restart as SMC). * - * Documented path (Management Application API): - * Hashtable env with ConnectionURLs / DefaultUser / DefaultPassword + * Uses reflection so this file compiles with Java 8 without Sonic jars on the + * compile classpath. At runtime SonicHome\\lib must provide: + * mgmt_client / mfcontext / sonic_Client (etc.) + * + * Flow (CX Messenger Management Application API): + * Hashtable ConnectionURLs/DefaultUser/DefaultPassword * -> JMSConnectorAddress / JMSConnectorClient.connect * -> MFProxyFactory.createAgentProxy(connector, ObjectName) - * -> IAgentProxy.restart() (same lifecycle action as SMC Restart) + * -> IAgentProxy.restart() * - * ObjectName pattern: {domain}.{container}:ID=AGENT + * ObjectName: {domain}.{container}:ID=AGENT * - * Usage: - * java -cp "...libs...;." SonicMfContainerTool ping|list|restart - * --domain proalpha-test - * --url tcp://host:13070 - * --user Administrator - * --password *** (or env ESB_SONIC_PASSWORD) - * [--container DE-Test] (required for restart) - * [--timeout 120] + * Compile: javac --release 8 SonicMfContainerTool.java */ public final class SonicMfContainerTool { @@ -55,7 +48,7 @@ public final class SonicMfContainerTool } long timeoutMs = Math.max(5_000L, a.timeoutSec * 1000L); - JMSConnectorClient connector = connect(a.url, a.user, a.password, timeoutMs); + Object connector = connect(a.url, a.user, a.password, timeoutMs); try { if ("ping".equals(a.command)) @@ -77,16 +70,28 @@ public final class SonicMfContainerTool } finally { - try { connector.disconnect(); } catch (Exception ignore) { /* ignore */ } + try + { + connector.getClass().getMethod("disconnect").invoke(connector); + } + catch (Exception ignore) + { + /* ignore */ + } } } catch (Throwable t) { - fail(t.getClass().getSimpleName() + ": " + t.getMessage()); + Throwable root = t; + while (root.getCause() != null && root.getCause() != root) + { + root = root.getCause(); + } + fail(root.getClass().getName() + ": " + root.getMessage()); } } - private static JMSConnectorClient connect(String url, String user, String password, long timeoutMs) + private static Object connect(String url, String user, String password, long timeoutMs) throws Exception { Hashtable env = new Hashtable(); @@ -94,23 +99,59 @@ public final class SonicMfContainerTool env.put("DefaultUser", user); env.put("DefaultPassword", password == null ? "" : password); - JMSConnectorAddress address = new JMSConnectorAddress(env); - JMSConnectorClient connector = new JMSConnectorClient(); - connector.connect(address, timeoutMs); + Class addressCl = Class.forName("com.sonicsw.mf.jmx.client.JMSConnectorAddress"); + Object address = addressCl.getConstructor(new Class[] { Hashtable.class }).newInstance(new Object[] { env }); + + Class clientCl = Class.forName("com.sonicsw.mf.jmx.client.JMSConnectorClient"); + Object connector = clientCl.getConstructor(new Class[] {}).newInstance(new Object[] {}); + + Method connect = null; + Method[] methods = clientCl.getMethods(); + for (int i = 0; i < methods.length; i++) + { + Method m = methods[i]; + if ("connect".equals(m.getName()) && m.getParameterTypes().length == 2) + { + connect = m; + break; + } + } + if (connect == null) + { + fail("JMSConnectorClient.connect(address, timeout) nicht gefunden."); + return null; + } + + Class[] pts = connect.getParameterTypes(); + Object timeoutArg; + if (pts[1] == Long.TYPE || pts[1] == Long.class) + { + timeoutArg = Long.valueOf(timeoutMs); + } + else if (pts[1] == Integer.TYPE || pts[1] == Integer.class) + { + timeoutArg = Integer.valueOf((int) Math.min(Integer.MAX_VALUE, timeoutMs)); + } + else + { + timeoutArg = Long.valueOf(timeoutMs); + } + + connect.invoke(connector, new Object[] { address, timeoutArg }); System.out.println("INFO:Connected url=" + url + " user=" + user); return connector; } - private static void ping(JMSConnectorClient connector, String domain) throws Exception + private static void ping(Object connector, String domain) throws Exception { - Set names = connector.queryNames(new ObjectName("*:ID=AGENT"), null); + Set names = queryAgents(connector); int matched = 0; if (names != null) { for (Iterator it = names.iterator(); it.hasNext(); ) { ObjectName on = (ObjectName) it.next(); - if (belongsToDomain(on, domain)) + if (extractContainer(on, domain) != null) { matched++; } @@ -119,9 +160,9 @@ public final class SonicMfContainerTool System.out.println("OK:MfApiPing domain=" + domain + " agents=" + matched); } - private static void list(JMSConnectorClient connector, String domain) throws Exception + private static void list(Object connector, String domain) throws Exception { - Set names = connector.queryNames(new ObjectName("*:ID=AGENT"), null); + Set names = queryAgents(connector); int count = 0; if (names != null) { @@ -143,32 +184,49 @@ public final class SonicMfContainerTool System.out.println("OK:List count=" + count); } - private static void restart(JMSConnectorClient connector, String domain, String container) throws Exception + private static void restart(Object connector, String domain, String container) throws Exception { String shortName = shortContainer(container); ObjectName on = resolveAgentObjectName(connector, domain, shortName); System.out.println("INFO:ObjectName=" + on); - IAgentProxy agent = MFProxyFactory.createAgentProxy(connector, on); + Class factoryCl = Class.forName("com.sonicsw.mf.mgmtapi.runtime.MFProxyFactory"); + Method create = factoryCl.getMethod("createAgentProxy", new Class[] { + Class.forName("com.sonicsw.mf.jmx.client.JMSConnectorClient"), + ObjectName.class + }); + Object agent = create.invoke(null, new Object[] { connector, on }); + String stateBefore = safeState(agent); System.out.println("INFO:StateBefore=" + stateBefore); - agent.restart(); + agent.getClass().getMethod("restart").invoke(agent); System.out.println("OK:RestartInvoked container=" + shortName + " domain=" + domain + " stateBefore=" + stateBefore); } - private static ObjectName resolveAgentObjectName(JMSConnectorClient connector, String domain, String container) + private static Set queryAgents(Object connector) throws Exception + { + Method query = connector.getClass().getMethod("queryNames", new Class[] { + ObjectName.class, javax.management.QueryExp.class + }); + return (Set) query.invoke(connector, new Object[] { new ObjectName("*:ID=AGENT"), null }); + } + + private static ObjectName resolveAgentObjectName(Object connector, String domain, String container) throws Exception { ObjectName preferred = new ObjectName(domain + "." + container + ":ID=AGENT"); - Set exact = connector.queryNames(preferred, null); + Method query = connector.getClass().getMethod("queryNames", new Class[] { + ObjectName.class, javax.management.QueryExp.class + }); + Set exact = (Set) query.invoke(connector, new Object[] { preferred, null }); if (exact != null && !exact.isEmpty()) { return (ObjectName) exact.iterator().next(); } - Set names = connector.queryNames(new ObjectName("*:ID=AGENT"), null); + Set names = queryAgents(connector); if (names != null) { for (Iterator it = names.iterator(); it.hasNext(); ) @@ -182,15 +240,9 @@ public final class SonicMfContainerTool } } - // Fallback: documented canonical name even if not yet in query result return preferred; } - private static boolean belongsToDomain(ObjectName on, String domain) - { - return extractContainer(on, domain) != null; - } - private static String extractContainer(ObjectName on, String domain) { if (on == null || isBlank(domain)) @@ -224,12 +276,12 @@ public final class SonicMfContainerTool return container; } - private static String safeState(IAgentProxy agent) + private static String safeState(Object agent) { try { - String s = agent.getStateString(); - return s == null ? "?" : s; + Object s = agent.getClass().getMethod("getStateString").invoke(agent); + return s == null ? "?" : String.valueOf(s); } catch (Throwable t) { @@ -282,7 +334,7 @@ public final class SonicMfContainerTool else if ("--container".equals(key)) a.container = val; else if ("--timeout".equals(key)) { - try { a.timeoutSec = Integer.parseInt(val); } catch (Exception ignore) { /* keep default */ } + try { a.timeoutSec = Integer.parseInt(val); } catch (Exception ignore) { /* keep */ } } }