Monday, September 8, 2008

A Third Look at the JMX Web Services Connector

In previous blog entries, I used the JMX Web Services Connector (JSR-262) with JConsole as the client and with WinRM as the client and, in both entries, accessed the JMX Web Services Connector sample provided with the ws-jmx-connector download. In this third look at using the JMX Web Services Connector, I will use WinRM and JConsole to access attributes and operations of a custom MBean via the JMX Web Services Connector. This will demonstrate how easy it is to apply the JMX Web Services Connector to custom MBeans and will also demonstrate some additional syntax that can be used in WinRM to use the JMX Web Services Connector.

For the examples in this blog entry, I will be using the MBean prescribed by the following interface (CalculatorMXBean) and implementation class (Calculator).


CalculatorMXBean.java

package css2008.dustin.jmx.jmxws;

import css2008.dustin.jmx.core.CalculatorOperationTypeEnum;

/**
* Interface for Calculator MXBean used with JMX Web Services Connector example.
*
* @author Dustin
*/
public interface CalculatorMXBean
{
/**
* Provide the remainder from the last invoked integer division.
*
* @return Remainder from the last invoked integer division.
*/
public int getLastRemainder();

/**
* Describe which operation type was last executed.
*
* @return Type of operation that was last executed.
*/
public CalculatorOperationTypeEnum getLastOperationType();

/**
* Same as getLastOperationType(), but will be seen by JMX client as an
* operation rather than as an attribute getter.
*
* @return Type of operation that was last executed.
*/
public CalculatorOperationTypeEnum provideLastOperation();

/**
* Calculate the sum of the augend and the addend.
*
* @param augend First integer to be added.
* @param addend Second integer to be added.
* @return Sum of augend and addend.
*/
public int add(final int augend, final int addend);

/**
* Calculate the difference between the minuend and subtrahend.
*
* @param minuend Minuend in subtraction operation.
* @param subtrahend Subtrahend in subtraction operation.
* @return Difference of minuend and subtrahend.
*/
public int subtract(final int minuend, final int subtrahend);

/**
* Calculate the product of the two provided factors.
*
* @param factor1 First integer factor.
* @param factor2 Second integer factor.
* @return Product of provided factors.
*/
public int multiply(final int factor1, final int factor2);

/**
* Calculate the quotient of the dividend divided by the divisor.
*
* @param dividend Integer dividend.
* @param divisor Integer divisor.
* @return Quotient of dividend divided by divisor.
*/
public double divide(final int dividend, final int divisor);
}



Calculator.java

package css2008.dustin.jmx.jmxws;

import css2008.dustin.jmx.core.CalculatorOperationTypeEnum;

/**
* Class being used as an illustration of an MXBean.
*
* @author Dustin Marx
*/
public class Calculator implements CalculatorMXBean
{
/** Remainder for the last executed integer division operation. */
private int lastRemainder = 0;

/** Last calculator operation executed. */
private CalculatorOperationTypeEnum lastOperationType;

/**
* Provide the remainder from the last invoked integer division.
*
* @return Remainder from the last invoked integer division.
*/
@Override
public int getLastRemainder()
{
return this.lastRemainder;
}

/**
* Describe which operation type was last executed.
*
* @return Type of operation that was last executed.
*/
@Override
public CalculatorOperationTypeEnum getLastOperationType()
{
return this.lastOperationType;
}

/**
* Same as getLastOperationType(), but will be seen by JMX client as an
* operation rather than as an attribute getter.
*
* @return Type of operation that was last executed.
*/
@Override
public CalculatorOperationTypeEnum provideLastOperation()
{
return this.lastOperationType;
}

/**
* Calculate the sum of the augend and the addend.
*
* @param augend First integer to be added.
* @param addend Second integer to be added.
* @return Sum of augend and addend.
*/
@Override
public int add(final int augend, final int addend)
{
this.lastOperationType = CalculatorOperationTypeEnum.ADDITION;
return augend + addend;
}

/**
* Calculate the difference between the minuend and subtrahend.
*
* @param minuend Minuend in subtraction operation.
* @param subtrahend Subtrahend in subtraction operation.
* @return Difference of minuend and subtrahend.
*/
@Override
public int subtract(final int minuend, final int subtrahend)
{
this.lastOperationType = CalculatorOperationTypeEnum.SUBTRACTION;
return minuend - subtrahend;
}

/**
* Calculate the product of the two provided factors.
*
* @param factor1 First integer factor.
* @param factor2 Second integer factor.
* @return Product of provided factors.
*/
@Override
public int multiply(final int factor1, final int factor2)
{
this.lastOperationType = CalculatorOperationTypeEnum.MULTIPLICATION;
return factor1 * factor2;
}

/**
* Calculate the quotient of the dividend divided by the divisor.
*
* @param dividend Integer dividend.
* @param divisor Integer divisor.
* @return Quotient of dividend divided by divisor.
*/
@Override
public double divide(final int dividend, final int divisor)
{
this.lastOperationType = CalculatorOperationTypeEnum.DIVISON;
this.lastRemainder = dividend % divisor;
return dividend / divisor;
}
}


The MBean described by the interface and implementation class listed above is relatively simple, but it does have attributes and operations that will be used by the clients (JConsole and WinRM).

In the blog entry Remote JMX: Connectors and Adaptors, I demonstrated how the same code base could be used to set up a JMX Web Services Connector (both client and server side) as would be used for any other compliant JMX connector (including the required RMI connector and the optional JMXMP connector). The only thing that differed between each type of connector used was the JMXServiceURL provided. In that blog entry, I included the code for setting up any of these compliant connectors on both the server and client side, but I will provide the relevant server-side of the JMX Web Services Connector here for convenience.


JmxConnectorServer useJmxServiceUrlBasedConnector Method

/**
* Use the platform MBean server in conjunction with the JMX connector
* specified in the provided JMXServiceURL.
*
* @param serviceUrl JMXServiceURL to be used in connector.
* @param connectorMBeanName MBean registration name for the connector.
* @param connectorServers Collection to which my instantiated JMX Connector
* Server should be added.
*/
public static boolean useJmxServiceUrlBasedConnector(
final String serviceUrl,
final String connectorMBeanName,
final List connectorServers)
{
boolean success = true;
final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
printOutputHeader(
"Setting up connector for JMXServiceURL " + serviceUrl,
System.out);
try
{
final JMXServiceURL jmxServiceUrl = new JMXServiceURL(serviceUrl);
final JMXConnectorServer connectorServer =
JMXConnectorServerFactory.newJMXConnectorServer(
jmxServiceUrl,
null,
mbs);
connectorServer.start();
registerProvidedMBean(connectorServer, connectorMBeanName);
connectorServers.add(connectorServer);
}
catch (MalformedURLException badJmxServiceUrl)
{
System.err.print(
"ERROR trying to create JMX server connector with service URL "
+ serviceUrl + ":\n" + badJmxServiceUrl.getMessage() );
success = false;
}
catch (IOException ioEx)
{
System.err.println(
"ERROR trying to access server connector.\n"
+ ioEx.getMessage() );
success = false;
}

if ( success )
{
System.out.println(
connectorMBeanName
+ " registered in MBean server as connector with JMXServiceURL of "
+ serviceUrl + ".");
}
else
{
System.out.println("\n\nERROR encountered.");
}
System.out.println("\n\n");

return success;
}



The next source code listing shown the contents of the ExampleServer class. This class registers our custom MBean (Calculator), sets up the JMX Web Services Connector server, and provides a wait method that allows the application to keep running so that it can be monitored and managed rather than simply finishing execution.

ExampleServer.java

package css2008.dustin.jmx.jmxws;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.List;
import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorServer;

/**
* Simple application that uses WS-JMX Connector Server to make an example MBean
* available to potentially non-JMX or even non-Java clients.
*
* @author Dustin
*/
public class ExampleServer
{
/**
* Register an example MBean that can be monitored and managed via JMX WS
* connector.
*/
public static void registerExampleMBean()
{
final String objectNameStr = "css2008-calculator-jmxws:type=mxbean";
final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
try
{
final ObjectName objectName = new ObjectName(objectNameStr);
final Calculator calculator = new Calculator();
mbs.registerMBean(calculator, objectName);
}
catch (MalformedObjectNameException badObjectNameEx)
{
System.err.println( objectNameStr + " is a bad ObjectName:\n"
+ badObjectNameEx.getMessage() );
}
catch (InstanceAlreadyExistsException redundantMBeanEx)
{
System.err.println(
"An MBean is already registered with ObjectName " + objectNameStr
+ ":\n" + redundantMBeanEx.getMessage() );
}
catch (NotCompliantMBeanException nonCompliantMBeanEx)
{
System.err.println(
objectNameStr + " is a non-compliant MBean:\n"
+ nonCompliantMBeanEx.getMessage() );
}
catch (MBeanRegistrationException regEx)
{
System.err.println(
"ERROR trying to register MBean with name " + objectNameStr + ":\n"
+ regEx.getMessage() );
}
catch (MBeanException mbeanEx)
{
System.err.println("An MBean exception occurred: \n"
+ mbeanEx.getMessage() );
}
}

/**
* Set up the JMX WS Connector Server.
*/
public static void setUpJmxWsConnectorServer()
{
List connectorServers =
new ArrayList();
JmxConnectorServer.useJmxServiceUrlBasedConnector(
"service:jmx:ws://localhost:1097/jmxws",
"connector:type=jsr262_jmxws",
connectorServers);
}

/**
* Hold application so that it can be monitored and managed.
*/
public static void waitForInput()
{
try
{
System.in.read();
}
catch (IOException ioEx)
{
System.err.println("Error trying to read input.\n" + ioEx.getMessage() );
}
}

/**
* Run JMX WS connector with registered MBean for JMX WS clients to interact
* with.
*
* @param arguments
*/
public static void main(final String[] arguments)
{
registerExampleMBean();
setUpJmxWsConnectorServer();
waitForInput();
}
}


To run the ExampleServer with its JMX Web Services Connector server, I used the following command (note that the JSR-262-provided libraries are included on the classpath and that I take advantage of the Java SE 6 support for wildcard JAR specification with the asterisk):


java -cp C:\css2008\jmx\MBeanExamples\dist\MBeanExamples.jar;C:\jmx-ws-262\jsr262-ri\lib\* css2008.dustin.jmx.jmxws.ExampleServer


When the above command is run, the application prints out its JMXServiceURL (service:jmx:ws://localhost:1097/jmxws) to make connection for clients easier. Here is how it appears in a Command Prompt console:



All of the above set up the custom MBean, registered it with the platform MBean server, and associated a JMX Web Services Connector with that MBean server. With that in mind, it is time now to look at how this MBean server and the Calculator MBean can be accessed remotely with JConsole and WinRM.

As discussed in my first blog entry on the JMX Web Services Connector, it is necessary to provide certain additional libraries on the classpath for JConsole to use to connect using JMX Web Services Connector. The next listing shows the JSR-262 RI libraries on JConsole's classpath. Note that I am once again taking advantage of the ability in Java SE 6 to specify that all JARs within a particular directory are on the classpath using the asterisk. This is much easier than spelling out all the necessary JAR files in that directory. It is also important to note that I have the environment variable JAVA_HOME set and am using that here.


java -cp "C:\jmx-ws-262\jsr262-ri\lib\*;%JAVA_HOME%\lib\jconsole.jar;%JAVA_HOME%\lib\tools.jar" sun.tools.jconsole.JConsole


The next screen snapshot shows JConsole's entry screen and shows the JMXServiceURL provided above specified in the Remote tab.



Once I have connected remotely via the JMX Web Services Connector, I can use JConsole's MBeans tab to interact with the remote JMX Web Services Connector server. The next two screen snapshots show access of an operation (add) and of an attribute (last operation type).






It is one thing to connect to the JMX Web Services connector server with JConsole, but it can be even more impressive to see a non-Java, non-JMX client communicate with our MXBean. In the remainder of this blog entry, I will use WinRM to connect remotely to the MBean server via the JMX Web Services Connector and will manage and monitor the Calculator MXBean using WinRM.

For my examples, I will be using WinRM's scripting language directly to interact with the JMX MBean server through the JMX Web Services Connector. In the first example, I will invoke the "add" operation. This is done in WinRM with the following command:


winrm invoke Invoke http://jsr262.dev.java.net/DynamicMBeanResource?ObjectName=css2008-calculator-jmxws:type=mxbean -file:C:\css2008\jmx\jmxwsinput\AddOperation.xml -r:http://127.0.0.1:1097/jmxws -a:None


I explained several of the pieces of the above WinRM command in my Second Look at JMX Web Services Connector blog entry. The important new things to focus on in this particular command are the use of Invoke and the specification of a file with the actual description of what should be invoked. In this case, the XML file (called AddOperation.xml), looks like this (note that it will be adding 13 and 4):

AddOperation.xml

<ManagedResourceOperation xmlns="http://jsr262.dev.java.net/jmxconnector"
name="add">
<Input>
<Param name="augend"><Int>13</Int></Param>
<Param name="addend"><Int>4</Int></Param>
</Input>
</ManagedResourceOperation>


The XML above is of a format prescribed by the XML Schema file jsr262.xsd that currently gets shipped with the ws-jmx-connector implementation of JSR-262 (subject to change as the JSR progresses). Note that WinRM reports an obvious error message if the provided XML is invalid ("The WS-Management service cannot process the request because the XML is invalid."). However, when the XML is valid, but does not match what is expected, the less descriptive and less helpful error message "The service cannot comply with the request due to internal processing errors." Another message that indicates things are not well is "The WS-Management service does not support format mismatch." and that is again a difficult one to track down exactly the cause of the problem. These error messages indicate problems with the XML, but it is not always intuitive what the problem really is. WinRM allows you to request more details about a particular error code with the syntax "winrm helpmsg <<errorCode>>", but I found that this provided no additional detail above what the error message itself provided.

In a Windows Command Prompt terminal, running this command with a valid XML file looks like this:



The addition operation shown above demonstrates using WinRM to invoke a JMX operation via the JMX Web Services Connector. Another example of calling an operation is shown next with the divide operation on Calculator. The WinRM command and XML file with the parameters for the operation are shown next.


winrm invoke Invoke http://jsr262.dev.java.net/DynamicMBeanResource?ObjectName=css2008-calculator-jmxws:type=mxbean -file:C:\css2008\jmx\jmxwsinput\DivideOperation.xml -r:http://127.0.0.1:1097/jmxws -a:


DivideOperation.xml

<ManagedResourceOperation xmlns="http://jsr262.dev.java.net/jmxconnector"
name="divide">
<Input>
<Param name="dividend"><Int>12</Int></Param>
<Param name="divisor"><Int>4</Int></Param>
</Input>
</ManagedResourceOperation>


The screen snapshot for invoking the division operation from WinRM is shown next:



We can also access attributes in the MXBean from WinRM. One way to do this is to expose the attribute via an operation and an example of this is requesting the value of the last operation, which should be division, using the provideLastOperation() method of the Calculator class. This is done as shown in the following WinRM command and associated XML file with the actual invocation details.


winrm invoke Invoke http://jsr262.dev.java.net/DynamicMBeanResource?ObjectName=css2008-calculator-jmxws:type=mxbean -file:C:\css2008\jmx\jmxwsinput\LastOperationTypeOperation.xml -r:http://127.0.0.1:1097/jmxws -a:None


LastOperationTypeOperation.xml

<jmx:ManagedResourceOperation name="provideLastOperation"
xmlns:jmx="http://jsr262.dev.java.net/jmxconnector">
<jmx:Input/>
</jmx:ManagedResourceOperation>


The WinRM command to invoke this operation to get this attribute and the results of that command are shown in the next screen snapshot:



While we can access attributes using operations written specifically for that purpose, it is more common to simply use the "get" method associated with an attribute. The next example will demonstrate this.

When acquiring an attribute's value directly with its "getter" method rather than as a non-get operation, the WinRM command is a little different than what we've seen so far for invoking operations. That command and the output from running that command are shown next (note there is no XML file this time).


winrm get http://jsr262.dev.java.net/DynamicMBeanResource?ObjectName=css2008-calculator-jmxws:type=mxbean -fragment://:Property[@name="LastOperationType"]/*/text() -r:http://127.0.0.1:1097/jmxws -a:None




Acquiring an attribute uses WinRM's "get" rather than operations' "invoke" and doesn't require the extra specification of input parameter data we provided on the operations examples.

In this blog entry, a custom MBean (MXBean in this case) was made available to remote clients via the JMX Web Services Connector. Both the Java-knowledgeable/JMX-knowledgeable JConsole and the Java-agnostic/JMX-agnostic WinRM clients were used to show the flexibility and openness provided by the JMX Web Services Connector. While the JMX Web Services Connector is still a work-in-progress, it looks likely that it will be available with Java SE 7. I also suspect that other WS-Management aware tools (which is how WinRM "knows" how to talk the JMX Web Services Connector) will become more popular in the future.




UPDATE (15 September 2008): Based on feedback (below) and to provide completeness, I have added the code listing for the enum CalculatorOperationTypeEnum:

CalculatorOperationTypeEnum.java

package css2008.dustin.jmx.core;

/**
* Simple enum to use with different types of JMX MBeans to demonstrate their
* support or lack of support for custom classes/enums.
*
* @author Dustin
*/
public enum CalculatorOperationTypeEnum
{
ADDITION,
DIVISION,
MULTIPLICATION,
SUBTRACTION
}

7 comments:

rogerlittin said...

Hi dustin,

The blogs about jsr262 are great. I have been looking for something like this for a long time now.

I may be missing something but the import css2008.dustin.jmx.core.CalculatorOperationTypeEnum is not here and Example Server.java throws errors about missing classes.

Can you help so that I can get this working.

Roger.

Dustin said...

Roger,

Thanks for the feedback. You didn't miss anything; the class CalculatorOperationTypeEnum is not currently shown in this blog entry. I will add it to the blog entry later today. Note that I have fixed the spelling of "DIVISION" in the enum since originally posting the blog (when it was spelled "DIVISON" and which can be seen in the screen snapshot toward the end).

chemsky said...

Hello Dustin,
I'm new in jmx and need some help!!
should i describe all my MBeans in jsr262.xsd or every MBean should be in seperate xsd file? can i generate them automaticlly?
thanks in advance
Regards
walid

Dustin said...

chemsky:

The XML files are only necessary to use WinRM as the client that talks to the MXBean exposed via the web services connector. If you are using JConsole, for example, you don't need these XML files. In my example, I used an XML file per operation so that it was easily specified on the command-line. The XML file is named whatever makes most sense to me, but its XML content must obviously be well-formed XML and must comply with the W3C XML schema definition provided by jsr262.xsd. There is only one XSD file (jsr262.xsd), but you would have an XML file compliant with this for each operation. I hope that helps.

chemsky said...

Thank you Dustin for your response,
Also me i need jsr262.xsd file, because i will use an external system that need xsd file(s) to inderstand xml files !!!

But my problem that I want to have a result of my operations, getters or setters in an xml file for the external system!! Is it possible ? and how to do it?

Zeid said...

I came across your while researching how to connect interoperate with JMX using winrm. It looks like the JSR hasn't been touched since 2008.

When I try to run a sample app with jmx enabled and connect to it using winrm I get an error stating

WSManFault
Message = WS-Management cannot process the request. The element a:RelatesTo
is missing from the response sent by the destination computer.

I sniffed the traffic and the response looks correct, but doesn't conform to the ws-management spec.

Do you know what the status of the JSR is?

Thanks!

Dustin said...

Zeid,

Sadly, I think you're correct that the Web Services Connector for JMX JSR (262) has stagnated (reported as Inactive on the main page). The JSR seems to me to have lost momentum when the final results were so divided. What really cost all things JMX was reduced funding/staffing at Sun and the announcement that JMX 2.0 would not be in Java SE 7. I don't recall seeing anything about Oracle plans for JMX 2 for Java SE 7 or even Java SE 8.

Dustin