import java.lang.reflect.Method; import java.util.Hashtable; import java.util.Iterator; import java.util.Set; import javax.management.MBeanOperationInfo; import javax.management.MBeanInfo; import javax.management.ObjectName; /** * Sonic Management Application API helper (same restart intent as SMC). * * TOOL_VERSION is printed first so we can verify the deployed build. * * Remote JMSConnectorClient is "unbounded". IAgentProxy.restart() often throws * "Operation unsupported for unbounded client connector". SMC Restart typically * stops the agent via JMX; Launch Daemon brings it back. * * Compile: javac --release 8 SonicMfContainerTool.java */ public final class SonicMfContainerTool { /** Bump when restart logic changes – must appear in app output. */ public static final String TOOL_VERSION = "2026-07-24c-mbean-stop"; public static void main(String[] args) { info("ToolVersion=" + TOOL_VERSION); try { Args a = Args.parse(args); if (a.command == null) { fail("Usage: SonicMfContainerTool ping|list|restart --domain D --url U --user U [--password P] [--container C] [--timeout SEC]"); return; } if (isBlank(a.domain) || isBlank(a.url) || isBlank(a.user)) { fail("domain, url und user sind Pflicht."); return; } if ("restart".equals(a.command) && isBlank(a.container)) { fail("restart erfordert --container."); return; } long timeoutMs = Math.max(5_000L, a.timeoutSec * 1000L); Object connector = connect(a.url, a.user, a.password, timeoutMs); try { if ("ping".equals(a.command)) { ping(connector, a.domain); } else if ("list".equals(a.command)) { list(connector, a.domain); } else if ("restart".equals(a.command)) { restart(connector, a.domain, a.container); } else { fail("Unbekannter Befehl: " + a.command); } } finally { try { connector.getClass().getMethod("disconnect").invoke(connector); } catch (Exception ignore) { /* ignore */ } } } catch (Throwable t) { fail(rootOf(t).getClass().getName() + ": " + rootOf(t).getMessage()); } } private static Object connect(String url, String user, String password, long timeoutMs) throws Exception { Hashtable env = new Hashtable(); env.put("ConnectionURLs", url); env.put("DefaultUser", user); env.put("DefaultPassword", password == null ? "" : password); 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[] {}); // Optional: Request-Timeout setzen (falls vorhanden) trySetTimeout(connector, timeoutMs); Method connect = findConnect(clientCl); if (connect == null) { fail("JMSConnectorClient.connect(...) nicht gefunden."); return null; } Class[] pts = connect.getParameterTypes(); Object[] callArgs; if (pts.length == 2) { callArgs = new Object[] { address, boxTimeout(pts[1], timeoutMs) }; } else if (pts.length == 1) { callArgs = new Object[] { address }; } else { callArgs = new Object[] { address, boxTimeout(pts[1], timeoutMs) }; } connect.invoke(connector, callArgs); info("Connected url=" + url + " user=" + user); return connector; } private static Method findConnect(Class clientCl) { Method best = null; Method[] methods = clientCl.getMethods(); for (int i = 0; i < methods.length; i++) { Method m = methods[i]; if (!"connect".equals(m.getName())) { continue; } if (m.getParameterTypes().length == 2) { return m; } if (m.getParameterTypes().length == 1) { best = m; } } return best; } private static void trySetTimeout(Object connector, long timeoutMs) { String[] names = new String[] { "setTimeout", "setRequestTimeout", "setDefaultTimeout" }; for (int i = 0; i < names.length; i++) { try { Method m = connector.getClass().getMethod(names[i], new Class[] { Long.TYPE }); m.invoke(connector, new Object[] { Long.valueOf(timeoutMs) }); info("Set " + names[i] + "=" + timeoutMs); return; } catch (NoSuchMethodException ignore) { try { Method m = connector.getClass().getMethod(names[i], new Class[] { Integer.TYPE }); m.invoke(connector, new Object[] { Integer.valueOf((int) Math.min(Integer.MAX_VALUE, timeoutMs)) }); info("Set " + names[i] + "=" + timeoutMs); return; } catch (Exception ignore2) { /* next */ } } catch (Exception ignore) { /* next */ } } } private static Object boxTimeout(Class type, long timeoutMs) { if (type == Long.TYPE || type == Long.class) { return Long.valueOf(timeoutMs); } if (type == Integer.TYPE || type == Integer.class) { return Integer.valueOf((int) Math.min(Integer.MAX_VALUE, timeoutMs)); } return Long.valueOf(timeoutMs); } private static void ping(Object connector, String domain) throws Exception { Set names = queryAgents(connector); int matched = 0; if (names != null) { for (Iterator it = names.iterator(); it.hasNext(); ) { ObjectName on = (ObjectName) it.next(); if (extractContainer(on, domain) != null) { matched++; } } } System.out.println("OK:MfApiPing domain=" + domain + " agents=" + matched); System.out.flush(); } private static void list(Object connector, String domain) throws Exception { Set names = queryAgents(connector); int count = 0; if (names != null) { for (Iterator it = names.iterator(); it.hasNext(); ) { ObjectName on = (ObjectName) it.next(); String container = extractContainer(on, domain); if (container != null) { System.out.println(container); count++; } } } if (count == 0) { info("KeineAgentContainer Domain=" + domain); } System.out.println("OK:List count=" + count); System.out.flush(); } private static void restart(Object connector, String domain, String container) throws Exception { String shortName = shortContainer(container); // Zuerst Preferred-ObjectName OHNE queryNames – query kann schon scheitern/irreführen ObjectName preferred = new ObjectName(domain + "." + shortName + ":ID=AGENT"); info("PreferredObjectName=" + preferred); ObjectName on = preferred; try { ObjectName resolved = resolveAgentObjectName(connector, domain, shortName); if (resolved != null) { on = resolved; } } catch (Throwable t) { warn("resolveAgentObjectName: " + rootOf(t).getClass().getName() + ": " + rootOf(t).getMessage() + " – nutze PreferredObjectName"); } info("ObjectName=" + on); dumpOpsSafe(connector, on); StringBuffer attempts = new StringBuffer(); // 1) JMX invoke stop/restart/shutdown (remote / wie SMC) if (tryMBeanLifecycle(connector, on, shortName, domain, attempts)) { return; } // 2) IAgentProxy stop/restart if (tryAgentProxyLifecycle(connector, on, shortName, domain, attempts)) { return; } // 3) Manager-MBeans mit Container-Namen if (tryManagerContainerOps(connector, domain, shortName, attempts)) { return; } fail("Kein Neustart-Weg erfolgreich. ToolVersion=" + TOOL_VERSION + "\nAttempts:\n" + attempts.toString()); } private static boolean tryMBeanLifecycle( Object connector, ObjectName on, String shortName, String domain, StringBuffer attempts) { String[] ops = new String[] { "stop", "restart", "shutdown" }; for (int i = 0; i < ops.length; i++) { String op = ops[i]; try { info("Trying MBean.invoke(" + op + ") on " + on); invokeNoArgs(connector, on, op); okRestart("MBean." + op, shortName, domain); return true; } catch (Throwable t) { Throwable root = rootOf(t); String line = "MBean." + op + " => " + root.getClass().getName() + ": " + root.getMessage(); warn(line); attempts.append(line).append('\n'); // stop kann die Verbindung reissen – wenn Message darauf hindeutet, als Erfolg werten if ("stop".equals(op) && looksLikeStopSideEffect(root)) { warn("Treat stop side-effect as success (agent going down)"); okRestart("MBean.stop(side-effect)", shortName, domain); return true; } } } return false; } private static boolean tryAgentProxyLifecycle( Object connector, ObjectName on, String shortName, String domain, StringBuffer attempts) { try { 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 }); info("StateBefore=" + safeState(agent)); String[] methods = new String[] { "stop", "restart", "shutdown" }; for (int i = 0; i < methods.length; i++) { String mName = methods[i]; try { info("Trying IAgentProxy." + mName); agent.getClass().getMethod(mName).invoke(agent); okRestart("IAgentProxy." + mName, shortName, domain); return true; } catch (Throwable t) { Throwable root = rootOf(t); String line = "IAgentProxy." + mName + " => " + root.getClass().getName() + ": " + root.getMessage(); warn(line); attempts.append(line).append('\n'); if ("stop".equals(mName) && looksLikeStopSideEffect(root)) { okRestart("IAgentProxy.stop(side-effect)", shortName, domain); return true; } } } } catch (Throwable t) { Throwable root = rootOf(t); String line = "IAgentProxy path => " + root.getClass().getName() + ": " + root.getMessage(); warn(line); attempts.append(line).append('\n'); } return false; } private static boolean tryManagerContainerOps( Object connector, String domain, String shortName, StringBuffer attempts) { String[] candidates = new String[] { domain + ".DomainManager:ID=DomainManager", domain + ".DomainManager:ID=AGENT", domain + ".dm:ID=AGENT" }; for (int i = 0; i < candidates.length; i++) { try { ObjectName pattern = new ObjectName(candidates[i]); Set found = queryNames(connector, pattern); if (found == null || found.isEmpty()) { continue; } ObjectName real = (ObjectName) found.iterator().next(); info("Manager=" + real); dumpOpsSafe(connector, real); String[] ops = new String[] { "restartContainer", "stopContainer", "startContainer" }; for (int o = 0; o < ops.length; o++) { try { info("Trying " + ops[o] + "(\"" + shortName + "\") on " + real); invokeStringArg(connector, real, ops[o], shortName); okRestart("Manager." + ops[o], shortName, domain); return true; } catch (Throwable t) { Throwable root = rootOf(t); String line = ops[o] + " => " + root.getClass().getName() + ": " + root.getMessage(); warn(line); attempts.append(line).append('\n'); } } } catch (Throwable t) { warn("Manager candidate " + candidates[i] + ": " + rootOf(t).getMessage()); } } // Scan AGENT-MBeans nach Container-Ops try { Set agents = queryAgents(connector); if (agents == null) { return false; } for (Iterator it = agents.iterator(); it.hasNext(); ) { ObjectName on = (ObjectName) it.next(); String jmxDomain = on.getDomain(); if (jmxDomain == null) { continue; } String lower = jmxDomain.toLowerCase(); if (!(lower.contains("domainmanager") || lower.contains(".dm") || lower.contains("collector") || lower.contains("host") || lower.contains("launch"))) { continue; } info("Scan manager agent " + on); dumpOpsSafe(connector, on); String[] ops = new String[] { "restartContainer", "stopContainer" }; for (int i = 0; i < ops.length; i++) { try { invokeStringArg(connector, on, ops[i], shortName); okRestart(ops[i] + "@" + on, shortName, domain); return true; } catch (Throwable t) { attempts.append(ops[i]).append('@').append(on).append(" => ") .append(rootOf(t).getMessage()).append('\n'); } } } } catch (Throwable t) { warn("Manager scan: " + rootOf(t).getMessage()); } return false; } private static boolean looksLikeStopSideEffect(Throwable root) { if (root == null || root.getMessage() == null) { return false; } String m = root.getMessage().toLowerCase(); return m.indexOf("disconnect") >= 0 || m.indexOf("closed") >= 0 || m.indexOf("not connected") >= 0 || m.indexOf("connection lost") >= 0 || m.indexOf("broker") >= 0 && m.indexOf("down") >= 0; } private static void okRestart(String method, String shortName, String domain) { System.out.println("OK:RestartInvoked method=" + method + " container=" + shortName + " domain=" + domain + " tool=" + TOOL_VERSION); System.out.flush(); } private static void invokeNoArgs(Object connector, ObjectName on, String op) throws Exception { Method invoke = connector.getClass().getMethod("invoke", new Class[] { ObjectName.class, String.class, Object[].class, String[].class }); invoke.invoke(connector, new Object[] { on, op, new Object[0], new String[0] }); } private static void invokeStringArg(Object connector, ObjectName on, String op, String arg) throws Exception { Method invoke = connector.getClass().getMethod("invoke", new Class[] { ObjectName.class, String.class, Object[].class, String[].class }); invoke.invoke(connector, new Object[] { on, op, new Object[] { arg }, new String[] { "java.lang.String" } }); } private static void dumpOpsSafe(Object connector, ObjectName on) { try { Method getInfo = connector.getClass().getMethod("getMBeanInfo", new Class[] { ObjectName.class }); MBeanInfo infoBean = (MBeanInfo) getInfo.invoke(connector, new Object[] { on }); if (infoBean == null || infoBean.getOperations() == null) { info("Ops(" + on + ")=(null)"); return; } StringBuffer sb = new StringBuffer(); MBeanOperationInfo[] ops = infoBean.getOperations(); for (int i = 0; i < ops.length; i++) { String name = ops[i].getName(); String lower = name.toLowerCase(); if (lower.indexOf("restart") >= 0 || lower.indexOf("stop") >= 0 || lower.indexOf("start") >= 0 || lower.indexOf("shutdown") >= 0 || lower.indexOf("container") >= 0) { if (sb.length() > 0) { sb.append(','); } sb.append(name); } } info("Ops(" + on + ")=" + (sb.length() == 0 ? "(keine lifecycle)" : sb.toString())); } catch (Throwable t) { warn("getMBeanInfo(" + on + "): " + rootOf(t).getClass().getName() + ": " + rootOf(t).getMessage()); } } private static Set queryAgents(Object connector) throws Exception { return queryNames(connector, new ObjectName("*:ID=AGENT")); } private static Set queryNames(Object connector, ObjectName pattern) throws Exception { Method query = connector.getClass().getMethod("queryNames", new Class[] { ObjectName.class, javax.management.QueryExp.class }); return (Set) query.invoke(connector, new Object[] { pattern, null }); } private static ObjectName resolveAgentObjectName(Object connector, String domain, String container) throws Exception { ObjectName preferred = new ObjectName(domain + "." + container + ":ID=AGENT"); Set exact = queryNames(connector, preferred); if (exact != null && !exact.isEmpty()) { return (ObjectName) exact.iterator().next(); } Set names = queryAgents(connector); if (names != null) { for (Iterator it = names.iterator(); it.hasNext(); ) { ObjectName on = (ObjectName) it.next(); String c = extractContainer(on, domain); if (c != null && c.equalsIgnoreCase(container)) { return on; } } } return preferred; } private static String extractContainer(ObjectName on, String domain) { if (on == null || isBlank(domain)) { return null; } String jmxDomain = on.getDomain(); if (jmxDomain == null) { return null; } String prefix = domain + "."; if (jmxDomain.regionMatches(true, 0, prefix, 0, prefix.length())) { return jmxDomain.substring(prefix.length()); } return null; } private static String shortContainer(String container) { if (container == null) { return ""; } int dot = container.indexOf('.'); if (dot >= 0 && dot < container.length() - 1) { return container.substring(dot + 1); } return container; } private static String safeState(Object agent) { try { Object s = agent.getClass().getMethod("getStateString").invoke(agent); return s == null ? "?" : String.valueOf(s); } catch (Throwable t) { return "?"; } } private static Throwable rootOf(Throwable t) { Throwable root = t; while (root != null && root.getCause() != null && root.getCause() != root) { root = root.getCause(); } return root == null ? t : root; } private static void info(String message) { System.out.println("INFO:" + message); System.out.flush(); } private static void warn(String message) { System.out.println("WARN:" + message); System.out.flush(); } private static void fail(String message) { System.err.println("ERROR:" + message); System.err.flush(); System.exit(2); } private static boolean isBlank(String s) { return s == null || s.trim().length() == 0; } private static final class Args { String command; String domain; String url; String user; String password; String container; int timeoutSec = 120; static Args parse(String[] args) { Args a = new Args(); if (args == null || args.length == 0) { return a; } a.command = args[0].toLowerCase(); for (int i = 1; i < args.length; i++) { String key = args[i]; if (!key.startsWith("--") || i + 1 >= args.length) { continue; } String val = args[++i]; if ("--domain".equals(key)) a.domain = val; else if ("--url".equals(key)) a.url = val; else if ("--user".equals(key)) a.user = val; else if ("--password".equals(key)) a.password = val; else if ("--container".equals(key)) a.container = val; else if ("--timeout".equals(key)) { try { a.timeoutSec = Integer.parseInt(val); } catch (Exception ignore) { /* keep */ } } } if (isBlank(a.password)) { String env = System.getenv("ESB_SONIC_PASSWORD"); if (!isBlank(env)) { a.password = env; } } return a; } } }