Fix UnsupportedClassVersionError: ship Java 8 SonicMfContainerTool.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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<int> 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 = [];
|
||||
|
||||
Binary file not shown.
@@ -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 */ }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user