From f937f4ffbe4950e2050f1695cf0320476ffb4977 Mon Sep 17 00:00:00 2001 From: Myst <1592048+LeMyst@users.noreply.github.com> Date: Wed, 19 Jun 2024 11:34:54 +0200 Subject: [PATCH] Avoid usage of jdk.jconsole module in Java 9+ Copied from PR https://github.com/jiaqi/jmxterm/pull/113 Co-Authored-By: nyg <784863+nyg@users.noreply.github.com> --- .../jmxterm/jdk9/Jdk9JavaProcess.java | 63 +++++++++++++++++++ .../jmxterm/jdk9/Jdk9JavaProcessManager.java | 63 +++++++++++++++++++ .../jmxterm/jdk9/StaticVirtualMachine.java | 16 +++++ .../jmxterm/jdk9/VirtualMachine.java | 25 ++++++++ .../jdk9/VirtualMachineDescriptor.java | 17 +++++ .../jmxterm/jdk9/package-info.java | 6 ++ .../jmxterm/utils/WeakCastUtils.java | 33 ++++++++-- .../jdk9/Jdk9JavaProcessManagerTest.java | 29 +++++++++ 8 files changed, 246 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/cyclopsgroup/jmxterm/jdk9/Jdk9JavaProcess.java create mode 100644 src/main/java/org/cyclopsgroup/jmxterm/jdk9/Jdk9JavaProcessManager.java create mode 100644 src/main/java/org/cyclopsgroup/jmxterm/jdk9/StaticVirtualMachine.java create mode 100644 src/main/java/org/cyclopsgroup/jmxterm/jdk9/VirtualMachine.java create mode 100644 src/main/java/org/cyclopsgroup/jmxterm/jdk9/VirtualMachineDescriptor.java create mode 100644 src/main/java/org/cyclopsgroup/jmxterm/jdk9/package-info.java create mode 100644 src/test/java/org/cyclopsgroup/jmxterm/jdk9/Jdk9JavaProcessManagerTest.java diff --git a/src/main/java/org/cyclopsgroup/jmxterm/jdk9/Jdk9JavaProcess.java b/src/main/java/org/cyclopsgroup/jmxterm/jdk9/Jdk9JavaProcess.java new file mode 100644 index 0000000..a621982 --- /dev/null +++ b/src/main/java/org/cyclopsgroup/jmxterm/jdk9/Jdk9JavaProcess.java @@ -0,0 +1,63 @@ +package org.cyclopsgroup.jmxterm.jdk9; + +import java.io.IOException; +import org.apache.commons.lang3.Validate; +import org.cyclopsgroup.jmxterm.JavaProcess; +import org.cyclopsgroup.jmxterm.utils.WeakCastUtils; + +/** + * JDK9 specific implementation of {@link JavaProcess} + * + * @author nyg + */ +public class Jdk9JavaProcess implements JavaProcess { + + private final StaticVirtualMachine staticVirtualMachine; + private final VirtualMachineDescriptor vmd; + private final String address; + + /** + * @param staticVm Static VirtualMachine proxy + * @param vmd Local VM + * @param address Connector address, if any + */ + Jdk9JavaProcess(StaticVirtualMachine staticVm, VirtualMachineDescriptor vmd, String address) { + Validate.notNull(vmd, "StaticVirtualMachine can't be NULL"); + Validate.notNull(vmd, "VirtualMachineDescriptor can't be NULL"); + this.staticVirtualMachine = staticVm; + this.vmd = vmd; + this.address = address; + } + + @Override + public String getDisplayName() { + return vmd.displayName(); + } + + @Override + public int getProcessId() { + return Integer.parseInt(vmd.id()); + } + + @Override + public boolean isManageable() { + return address != null; + } + + @Override + public void startManagementAgent() throws IOException { + Object vm = staticVirtualMachine.attach(vmd.id()); + try { + Class originalVirtualMachine = Class.forName(VirtualMachine.ORIGINAL_CLASS_NAME); + VirtualMachine vmProxy = WeakCastUtils.cast(originalVirtualMachine, VirtualMachine.class); + vmProxy.startLocalManagementAgent(); + } catch (ClassNotFoundException | SecurityException | NoSuchMethodException e) { + throw new RuntimeException("Can't cast " + vm + " to VirtualMachineDescriptor", e); + } + } + + @Override + public String toUrl() { + return address; + } +} diff --git a/src/main/java/org/cyclopsgroup/jmxterm/jdk9/Jdk9JavaProcessManager.java b/src/main/java/org/cyclopsgroup/jmxterm/jdk9/Jdk9JavaProcessManager.java new file mode 100644 index 0000000..2f61353 --- /dev/null +++ b/src/main/java/org/cyclopsgroup/jmxterm/jdk9/Jdk9JavaProcessManager.java @@ -0,0 +1,63 @@ +package org.cyclopsgroup.jmxterm.jdk9; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import org.apache.commons.lang3.Validate; +import org.cyclopsgroup.jmxterm.JavaProcess; +import org.cyclopsgroup.jmxterm.JavaProcessManager; +import org.cyclopsgroup.jmxterm.utils.WeakCastUtils; + +/** + * JDK9 specific java process manager + * + * @author nyg + */ +public class Jdk9JavaProcessManager extends JavaProcessManager { + private final StaticVirtualMachine staticVirtualMachine; + private final Class originalVirtualMachine; + + public Jdk9JavaProcessManager(ClassLoader classLoader) + throws SecurityException, NoSuchMethodException, ClassNotFoundException { + Validate.notNull(classLoader, "ClassLoader can't be NULL"); + originalVirtualMachine = classLoader.loadClass(VirtualMachine.ORIGINAL_CLASS_NAME); + staticVirtualMachine = + WeakCastUtils.staticCast(originalVirtualMachine, StaticVirtualMachine.class); + } + + @Override + public JavaProcess get(int pid) { + return list().stream().filter(process -> process.getProcessId() == pid).findAny().orElse(null); + } + + @Override + public List list() { + List vmDescriptors = staticVirtualMachine.list(); + List result = new ArrayList<>(vmDescriptors.size()); + + for (Object vmd : vmDescriptors) { + VirtualMachineDescriptor vmdProxy = null; + VirtualMachine vmProxy = null; + + try { + vmdProxy = WeakCastUtils.cast(vmd, VirtualMachineDescriptor.class); + Object vm = staticVirtualMachine.attach(vmdProxy.id()); + vmProxy = WeakCastUtils.cast(originalVirtualMachine, vm, VirtualMachine.class); + + Properties agentProps = vmProxy.getAgentProperties(); + String address = (String) agentProps.get(VirtualMachine.LOCAL_CONNECTOR_ADDRESS_PROP); + result.add(new Jdk9JavaProcess(staticVirtualMachine, vmdProxy, address)); + } catch (SecurityException | NoSuchMethodException e) { + throw new RuntimeException("Error casting object", e); + } catch (Exception e) { + // could not attach or some other exception + result.add(new Jdk9JavaProcess(staticVirtualMachine, vmdProxy, null)); + } finally { + if (vmProxy != null) { + vmProxy.detach(); + } + } + } + return result; + } +} diff --git a/src/main/java/org/cyclopsgroup/jmxterm/jdk9/StaticVirtualMachine.java b/src/main/java/org/cyclopsgroup/jmxterm/jdk9/StaticVirtualMachine.java new file mode 100644 index 0000000..db62d00 --- /dev/null +++ b/src/main/java/org/cyclopsgroup/jmxterm/jdk9/StaticVirtualMachine.java @@ -0,0 +1,16 @@ +package org.cyclopsgroup.jmxterm.jdk9; + +import java.util.List; + +/** + * Static interface of com.sun.tools.attach.VirtualMachine + * + * @author nyg + */ +public interface StaticVirtualMachine { + /** @return List of all virtual machines running on local */ + List list(); + + /** Attaches to a Java virtual machine. */ + Object attach(String id); +} diff --git a/src/main/java/org/cyclopsgroup/jmxterm/jdk9/VirtualMachine.java b/src/main/java/org/cyclopsgroup/jmxterm/jdk9/VirtualMachine.java new file mode 100644 index 0000000..596995c --- /dev/null +++ b/src/main/java/org/cyclopsgroup/jmxterm/jdk9/VirtualMachine.java @@ -0,0 +1,25 @@ +package org.cyclopsgroup.jmxterm.jdk9; + +import java.util.Properties; + +/** + * Reflect class com.sun.tools.attach.VirtualMachine + * + * @author nyg + */ +public interface VirtualMachine { + /** Name of original class this interface reflects */ + String ORIGINAL_CLASS_NAME = "com.sun.tools.attach.VirtualMachine"; + + /** Property for the local connector address */ + String LOCAL_CONNECTOR_ADDRESS_PROP = "com.sun.management.jmxremote.localConnectorAddress"; + + /** Detach from the virtual machine. */ + void detach(); + + /** @return The current agent properties in the target virtual machine. */ + Properties getAgentProperties(); + + /** Starts the local JMX management agent in the target virtual machine. */ + void startLocalManagementAgent(); +} diff --git a/src/main/java/org/cyclopsgroup/jmxterm/jdk9/VirtualMachineDescriptor.java b/src/main/java/org/cyclopsgroup/jmxterm/jdk9/VirtualMachineDescriptor.java new file mode 100644 index 0000000..6fac160 --- /dev/null +++ b/src/main/java/org/cyclopsgroup/jmxterm/jdk9/VirtualMachineDescriptor.java @@ -0,0 +1,17 @@ +package org.cyclopsgroup.jmxterm.jdk9; + +/** + * Reflect class com.sun.tools.attach.VirtualMachineDescriptor + * + * @author nyg + */ +public interface VirtualMachineDescriptor { + /** Name of original class this interface reflects */ + String ORIGINAL_CLASS_NAME = "com.sun.tools.attach.VirtualMachineDescriptor"; + + /** @return The display name component of this descriptor */ + String displayName(); + + /** @return The identifier component of this descriptor */ + String id(); +} diff --git a/src/main/java/org/cyclopsgroup/jmxterm/jdk9/package-info.java b/src/main/java/org/cyclopsgroup/jmxterm/jdk9/package-info.java new file mode 100644 index 0000000..b2db6fb --- /dev/null +++ b/src/main/java/org/cyclopsgroup/jmxterm/jdk9/package-info.java @@ -0,0 +1,6 @@ +/** + * Classes to implement {@link org.cyclopsgroup.jmxterm.JavaProcessManager} in JDK9 specific way + * + * @author nyg + */ +package org.cyclopsgroup.jmxterm.jdk9; diff --git a/src/main/java/org/cyclopsgroup/jmxterm/utils/WeakCastUtils.java b/src/main/java/org/cyclopsgroup/jmxterm/utils/WeakCastUtils.java index dcc73c5..4ced7c7 100644 --- a/src/main/java/org/cyclopsgroup/jmxterm/utils/WeakCastUtils.java +++ b/src/main/java/org/cyclopsgroup/jmxterm/utils/WeakCastUtils.java @@ -19,6 +19,7 @@ public final class WeakCastUtils { /** * Cast object into multiple interfaces * + * @param original The interface of the from instance * @param from Object to cast * @param interfaces Interfaces to cast to * @param classLoader ClassLoader to load methods for invocation @@ -26,7 +27,11 @@ public final class WeakCastUtils { * @throws SecurityException Allows exception related to security. * @throws NoSuchMethodException Allows exception due to wrong method. */ - public static Object cast(final Object from, final Class[] interfaces, ClassLoader classLoader) + public static Object cast( + final Class original, + final Object from, + final Class[] interfaces, + ClassLoader classLoader) throws SecurityException, NoSuchMethodException { Validate.notNull(from, "Invocation target can't be NULL"); Validate.notNull(interfaces, "Interfaces can't be NULL"); @@ -35,8 +40,7 @@ public static Object cast(final Object from, final Class[] interfaces, ClassL for (Class interfase : interfaces) { Validate.isTrue(interfase.isInterface(), interfase + " is not an interface"); for (Method fromMethod : interfase.getMethods()) { - Method toMethod = - from.getClass().getMethod(fromMethod.getName(), fromMethod.getParameterTypes()); + Method toMethod = original.getMethod(fromMethod.getName(), fromMethod.getParameterTypes()); methodMap.put(fromMethod, toMethod); } } @@ -74,7 +78,23 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl */ public static T cast(Object from, Class interfase) throws SecurityException, NoSuchMethodException { - return cast(from, interfase, interfase.getClassLoader()); + return cast(from.getClass(), from, interfase, interfase.getClassLoader()); + } + + /** + * Cast object to one given interface using ClassLoader of interface + * + * @param original The interface of the from instance + * @param Type of interface + * @param from Object to cast + * @param interfase Interface to cast to + * @return Result that implements interface + * @throws SecurityException Allows exception related to security. + * @throws NoSuchMethodException Allows exception due to wrong method. + */ + public static T cast(Class original, Object from, Class interfase) + throws SecurityException, NoSuchMethodException { + return cast(original, from, interfase, interfase.getClassLoader()); } /** @@ -89,10 +109,11 @@ public static T cast(Object from, Class interfase) * @throws NoSuchMethodException Allows exception due to wrong method. */ @SuppressWarnings("unchecked") - public static T cast(Object from, Class interfase, ClassLoader classLoader) + public static T cast( + Class original, Object from, Class interfase, ClassLoader classLoader) throws SecurityException, NoSuchMethodException { Validate.notNull(interfase, "Interface can't be NULL"); - return (T) cast(from, new Class[] {interfase}, classLoader); + return (T) cast(original, from, new Class[] {interfase}, classLoader); } /** diff --git a/src/test/java/org/cyclopsgroup/jmxterm/jdk9/Jdk9JavaProcessManagerTest.java b/src/test/java/org/cyclopsgroup/jmxterm/jdk9/Jdk9JavaProcessManagerTest.java new file mode 100644 index 0000000..ef72e0e --- /dev/null +++ b/src/test/java/org/cyclopsgroup/jmxterm/jdk9/Jdk9JavaProcessManagerTest.java @@ -0,0 +1,29 @@ +package org.cyclopsgroup.jmxterm.jdk9; + +import static org.junit.Assert.assertFalse; + +import java.util.List; +import org.apache.commons.lang3.JavaVersion; +import org.apache.commons.lang3.SystemUtils; +import org.cyclopsgroup.jmxterm.JavaProcess; +import org.cyclopsgroup.jmxterm.pm.JConsoleClassLoaderFactory; +import org.junit.Test; + +/** + * Test case of {@link Jdk9JavaProcessManager} + * + * @author nyg + */ +public class Jdk9JavaProcessManagerTest { + + @Test + public void testConstruction() throws Exception { + if (!SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_9)) { + return; + } + Jdk9JavaProcessManager jpm = + new Jdk9JavaProcessManager(JConsoleClassLoaderFactory.getClassLoader()); + List ps = jpm.list(); + assertFalse(ps.isEmpty()); + } +}