I have been playing with a programmatic remote JMX client, connecting to a WebSphere Application Server's MBean server. So far so good, I can connect using an appropriate JMXServiceURL and subscribe to notifications from my beans.
But - if a bean sends a notification that includes a class not on my local classpath, it blows up with a nice stack trace:
SEVERE: Failed to fetch notification, stopping thread. Error is: java.rmi.RemoteException: CORBA NO_IMPLEMENT 1330646337 No; nested exception is:
org.omg.CORBA.NO_IMPLEMENT: The sender's class RMI:com.mycompany.MyWeirdClass:143EC4C84209B825:EAD08F0965BC6044 is not present on the local classpath, and the class is not marked as truncatable, so it cannot be unmarshaled. vmcid: OMG minor code: 1 completed: No
java.rmi.RemoteException: CORBA NO_IMPLEMENT
... more frames ...
which is ultimately caused by:
Caused by: java.lang.ClassNotFoundException: com.mycompany.MyWeirdClass
at com.ibm.rmi.util.RepositoryId.loadClass(RepositoryId.java:675)
at com.ibm.rmi.util.RepositoryId.checkClassCache(RepositoryId.java:644)
...
Looks like it's exploding deep inside IBM's code, and there doesn't seem to be anywhere that I can do anything; the problematic notification never gets to my NotificationListener.
So; what can I do to handle this scenario without it preventing me from receiving further notifications?
I can think of 4 ways to address this, in ascending complexity order (I think):
Add the Class Library to the Client Classpath
Stating the obvious, but trying to be comprehensive.
Modify the Notification
Consider modifying the server generated notification to swap out the com.mycompany.MyWeirdClass instance to a formatted string. If the object is complex, consider using XML or JSON. If you can modify the notification sender to marshal the instance, that's probably the simplest. If not, you can modify the com.mycompany.MyWeirdClass class (which should be Serializable, right ?) and add a writeReplace method that returns the String representation of the instance.
Remote ClassLoader
Implement an HTTP server in your WebSphere app that returns the JAR containing the com.mycompany.MyWeirdClass class and all it's dependencies. (Technically, the HTTP server could be anywhere, as long as it serves the correct class.) Let's assume the JAR is available at http://classloader.mycompany.com/Weird.jar. Now start your client with an added system property like this:
java ..... -D-Djava.rmi.server.codebase=http://classloader.mycompany.com/Weird.jar ...
There's an example of an HTTP server implementation to provide dynamic classloading here.
Implement the class as a JMX OpenType
The most common way to do this is to make the class an MXBean, or you can implement CompositeData or extend CompositeDataSupport. If you don't mind using com.sun classes, the java runtime (1.6+) contains DefaultMXBeanMappingFactory which will create a composite data instance for you, so this useful utility can be used in concert with one of the above solutions. For example, you implement the writeReplace track like this:
import com.sun.jmx.mbeanserver.DefaultMXBeanMappingFactory;
import com.sun.jmx.mbeanserver.MXBeanMapping;
final MXBeanMapping mapping =
DefaultMXBeanMappingFactory.DEFAULT.mappingForType(
com.mycompany.MyWeirdClass.class,
DefaultMXBeanMappingFactory.DEFAULT
);
private Object writeReplace() throws ObjectStreamException {
try {
return mapping.toOpenValue(this);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}