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 aad6253..955a408 100644 Binary files a/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.class and b/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.class differ diff --git a/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.java b/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.java index 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 */ } } }