Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

JavaWorld Daily Brew

When is something not ready for Prime-Time?

 

When you can't do something simple with it.

Check out this absurdly simple Groovy script:

import javax.management.ObjectName

import javax.management.MBeanServerConnection

import javax.management.remote.JMXConnectorFactory as JMXFactory

import javax.management.remote.JMXServiceURL as JMXUrl

def serverUrl = 'service:jmx:rmi:///jndi/rmi://127.0.0.1:9004/jmxrmi'

def serv = JMXFactory.connect(new JMXUrl(serverUrl))

def on = new ObjectName('Catalina:type=Server')

def gmb = new GroovyMBean(serv, new ObjectName('Catalina:type=Server')).serverInfo

For those of you not up on your JMX, this is a simple connection via an RMI connector
to the JMX server running at port 9004 (which happens to be my local Tomcat installation). This
is straight off the Groovy-JMX documentation page
, but slimmed down because the
GroovyMBean constructor throws an exception, claiming that the desired constructor
cannot be found:

C:\Projects\Exploration\Groovy>groovy GroovyJMX.groovy

Caught: groovy.lang.GroovyRuntimeException: Could not find matching constructor

for: groovy.util.GroovyMBean(javax.management.remote.rmi.RMIConnector, javax.management.ObjectName)

at GroovyJMX.run(GroovyJMX.groovy:9)

at GroovyJMX.main(GroovyJMX.groovy)

C:\Projects\Exploration\Groovy>

C'mon, folks. I have no idea what the problem is, and debugging this is a nightmare.
I've looked around various Groovy forums, and nobody appears to have any real idea
what's going on. Either nobody is really using Groovy as a JMX client (in which case,
just remove the GroovyMBean from the library), or else Groovy has a bug within it
(thus reducing its efficicacy as a production-ready language).

I'm fully willing to accept that the problem is with me or my environment somehow.
The challenge, however, is for somebody to take a stock JDK 1.6 and Groovy 1.0 download
(oh, and 1.1-rc2 fails with the same error, so that's not the issue, either), run
the above 8-line script, and tell me why mine isn't working. (I've already done the
suggested step of removing the mx4j jar out of the groovy-1.0/lib directory, so that
doesn't help, either.)

Oh, and if you're going to write in claiming that this is a ClassLoader issue or something,
you'd be wrong--all of the JMX types being loaded are coming out of rt.jar (which
I verified using -verbose:class, doing which required me to edit the Groovy launcher
scripts, which I find to be just silly--don't make it hard for me to
use the basic management & monitoring facilities of the JVM). The only ClassLoader
player I don't know for certain is the org.codehaus ClassLoader that's established
by Groovy itself, so if the problem is in ClassLoaders, it's inside of the Groovy
implementation, which means it's a Groovy problem, not mine.

I've even taken the step to compile the Groovy code into .class files, and run those:

C:\Projects\Exploration\Groovy>groovyc -d compiled
GroovyJMX.groovy

C:\Projects\Exploration\Groovy>cd compiled

C:\Projects\Exploration\Groovy\compiled>java
-classpath .;libg\groovy-1.0.jar;li

bg\asm-2.2.jar GroovyJMX

Exception in thread "main" groovy.lang.GroovyRuntimeException: Could not find ma

tching constructor for: groovy.util.GroovyMBean(javax.management.remote.rmi.RMIC

onnector, javax.management.ObjectName)

at groovy.lang.MetaClassImpl.invokeConstructor(MetaClassImpl.java:776)

at groovy.lang.MetaClassImpl.invokeConstructor(MetaClassImpl.java:688)

at org.codehaus.groovy.runtime.Invoker.invokeConstructorOf(Invoker.java:

163)

at org.codehaus.groovy.runtime.InvokerHelper.invokeConstructorOf(Invoker

Helper.java:140)

at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeNewN(ScriptBy

tecodeAdapter.java:243)

at GroovyJMX.run(GroovyJMX.groovy:9)

at gjdk.GroovyJMX_GroovyReflector.invoke(Unknown Source)

at groovy.lang.MetaMethod.invoke(MetaMethod.java:115)

at org.codehaus.groovy.runtime.MetaClassHelper.doMethodInvoke(MetaClassH

elper.java:713)

at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:560)

at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:450)

at org.codehaus.groovy.runtime.Invoker.invokeMethod(Invoker.java:131)

at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.

java:111)

at org.codehaus.groovy.runtime.InvokerHelper.runScript(InvokerHelper.jav

a:408)

at gjdk.org.codehaus.groovy.runtime.InvokerHelper_GroovyReflector.invoke

(Unknown Source)

at groovy.lang.MetaMethod.invoke(MetaMethod.java:115)

at org.codehaus.groovy.runtime.MetaClassHelper.doMethodInvoke(MetaClassH

elper.java:713)

at groovy.lang.MetaClassImpl.invokeStaticMethod(MetaClassImpl.java:664)

at org.codehaus.groovy.runtime.Invoker.invokeMethod(Invoker.java:111)

at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.

java:111)

at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN(Scrip

tBytecodeAdapter.java:187)

at GroovyJMX.main(GroovyJMX.groovy)

C:\Projects\Exploration\Groovy\compiled>

And here we can obviously see, by the light of the .jars passed in, that the mx4j
jars are nowhere to be found (unless, of course, Groovy is explicitly searching the
hard drive for them, because I deleted them entirely out of Groovy's lib directory).

I've asked a couple of the Groovy heavyweights (not mentioning anybody by name) if
they know what's up. Silence. Not a good sign.

I'm about to go off and try the same thing using JRuby. If it works, out of the box,
then the problem is with Groovy, not with me. If you're a Groovy expert, or know someone
who is, then have them email me the solution (assuming they can find one), because
this is a point where I'm about to close the door on Groovy forever: if you can't
do something simple with it, it's not ready for prime-time
. It may be OK to whip
up dirt-simple websites where 90% of the stuff is pre-generated, but if I can't use
it for something like being a JMX client, then it ain't worth my time.

'Nuff said.

 

Update: OK, I may have to eat my words.

Playing around with some JRuby/JMX stuff (which is absurdly simple, even without jmx4r,
which I'm trying to get gem to install right now), I went back and decided to try
a little JMX without using GroovyMBean:

import javax.management.*

import javax.management.remote.*

import java.lang.management.*

def serverUrl = 'service:jmx:rmi:///jndi/rmi://127.0.0.1:9004/jmxrmi'

def connector = JMXConnectorFactory.connect(new JMXServiceURL(serverUrl))

def mbsc = connector.mBeanServerConnection

def memory_mbean =


ManagementFactory.newPlatformMXBeanProxy(mbsc, "java.lang:type=Memory", MemoryMXBean.class)

This, by the way, is almost a straight port of Jeff's
corresponding JRuby/JMX example
code. Works just fine. From there, I thought,
"Let's see if I can get past the problem I was having with GroovyMBean a few minutes
ago." So I add the one-line GroovyMBean constructor (taking the mBeanServerConnection
as the first parameter):

def gmb = new GroovyMBean(mbsc, new ObjectName('Catalina:type=Server')).serverInfo

Voila! Success! A quick execution test verifies that I'm all good:

def query = new ObjectName('Catalina:*')

String[] allNames = mbsc.queryNames(query, null)

def modules = allNames.findAll{ name ->

    name.contains('j2eeType=WebModule')

}.collect{ new GroovyMBean(mbsc, it) }

println "Found ${modules.size()} web modules. Processing
..."

modules.each{ m ->

    println "Found ${m.name()} at ${m.path} (${m.processingTime})"

}

returns:

C:\Projects\Exploration\Groovy>groovy GroovyJMX.groovy

Found 5 web modules. Processing ...

Found Catalina:j2eeType=WebModule,name=//localhost/docs,J2EEApplication=none,J2E

EServer=none at /docs (0)

Found Catalina:j2eeType=WebModule,name=//localhost/host-manager,J2EEApplication=

none,J2EEServer=none at /host-manager (0)

Found Catalina:j2eeType=WebModule,name=//localhost/examples,J2EEApplication=none

,J2EEServer=none at /examples (0)

Found Catalina:j2eeType=WebModule,name=//localhost/,J2EEApplication=none,J2EESer

ver=none at (0)

Found Catalina:j2eeType=WebModule,name=//localhost/manager,J2EEApplication=none,

J2EEServer=none at /manager (0)

C:\Projects\Exploration\Groovy>

Now if that ain't cool, I don't know what is.

Now for the hard part: What happened? Why'd my earlier code fail?

Looking back at my example script above, I clearly see that somewhere along the way
in my debugging/exploration, I accidentally left out the call to obtain the MBeanServerConnection
from the Connector and pass that in to the GroovyMBean constructor. Ugh.

Formal apologies to the Groovy crowd. However, I stipulate, for your own consideration,
that

  1. Originally my script was not so--I copied it line-for-line from the Groovy/JMX page,
    and I got the GroovyCastException that led me down this path. (You decide whether
    you believe me or not. :-) )
  2. The GroovyMBean constructor could (and should) be overloaded to take a Connector and
    extract the MBeanServerConnection from it and proceed. In fact, I'd suggest that the
    GroovyMBean should be written to take a JMXServiceURL as a parameter and handle all
    the details of connecting to the remote server.
  3. Exploration tests would have yielded a better record of my efforts, and maybe
    found the point where my coding got led astray. It would also help track down the
    problem for others, if there was a language issue at stake here. (There may be--one
    noticeable difference is that in the working version, I don't use the import-redeclaration
    feature, whereas in the non-working version I did. More research is required.) At
    the time, I was just trying to debug a simple script problem, but at some point, greater
    rigor on my part should have kicked in so I could have better results to work from.
    Sigh.
  4. Debugging this was way too hard. As we move into an era of more languages-atop-VMs,
    better debugging/spelunking facilities has to be a top priority.
  5. Groovy (and JRuby) need to make sure they honor--somehow--the various flags I want
    to pass to the Java VM, such as -verbose or -client/-server, or even -ms or -mx. Java
    does have the JAVA_TOOLS_OPTIONS environment variable that will be picked up by the
    Java launcher (java.exe), but that only works if the language in question uses the
    launcher itself as part of its startup script. Both Groovy and JRuby seem to fail
    this test (though I admit I didn't look super-hard for undocumented/underdocumented
    ways to do it).





Enterprise consulting, mentoring or instruction. Java, C++, .NET or XML services.
1-day or multi-day workshops available. Contact
me for details
.