Liberty has always exposed a JMX REST connector Java client to be used remotely. The REST APIs used by that Java client were made public so that non-Java languages can access Liberty’s JMX data!

To get started, install Liberty (V8.5.5.4 or above) and configure a Liberty server with the restConnector-1.0 feature:

<server>
  <featureManager>
    <feature>restConnector-1.0</feature>
  </featureManager>
  <quickStartSecurity userName="theUser" userPassword="thePassword"/>
  <keyStore id="defaultKeyStore" password="Liberty"/>
</server>

Start the Liberty server and now you’re ready to use the JMX REST connector’s direct APIs!

Accessing Liberty’s JMX REST APIs

If you’re using Liberty V8.5.5.8 or above, you can follow the steps below or you can use the apiDiscovery-1.0 feature to view the Swagger documentation.

If you’re using an older Liberty (V8.5.5.4 or above):

  1. To get a list of the APIs just point your browser to: https://<host>:<https_port>/IBMJMXConnectorREST/api
  2. After being prompted for the username and password (defined above in server.xml), you can use the drop-down menu at the top-left corner of the page to navigate the different sections of the API.
  3. You can navigate some of the APIs, such as /IBMJMXConnectorREST/mbeans/{objectName}, simply by using the browser’s URL box. For others, such as POST/PUT/DELETE, you can invoke them using any REST client, such as browser add-ons like Postman, or any programming language that allows HTTPS access.

The main sections of the API are:

  • General: documents the JSON structure for representing POJOs, and domain-related queries.
  • MBeans: manage MBean instances (create, delete, query, invoke).
  • Attributes: query and modify MBean attributes.
  • Notification Registry: create and delete a notification container, to be able to register/unregister notifications.
  • Client-Listener Notifications: manage notifications that are pushed into the container’s inbox.
  • MBean-Listener Notifications: manage notifications that are pushed directly into another MBean.
  • File Transfer: download, upload or delete files.
  • Routing: route any of the above APIs through the Collective Controller to a Collective Member or route a file transfer through the Collective Controller to any host registered with the Collective.
  • Collective multi-host upload: an advanced set of APIs that manages an upload to multiple hosts registered with the Collective. This API enables deployment of Liberty archives to automatically be joined into an existing Collective, and therefore is the core API for executing Liberty’s Intelligent Management capabilities.

Let’s take a look at some actual examples from a popular non-Java client: JQuery

Examples from JQuery

To make subsequent Ajax calls easier, you can make a call to ajaxSetup to set your username and password:

$.ajaxSetup({
      username: jmxUserName,
      password: jmxPassword,
});

Similarly, if you wish to route your next few calls to a collective member, you can pre-set your routing context headers:

$.ajaxSetup({
      headers: {"com.ibm.websphere.jmx.connector.rest.routing.hostName" : hostName,
                "com.ibm.websphere.jmx.connector.rest.routing.serverUserDir" : encodeURIComponent(userDir),
                "com.ibm.websphere.jmx.connector.rest.routing.serverName" : serverName}
});

If you just want to work directly with a Liberty instance, you don’t need the routing headers shown above.

Once the setup is done, here’s how you could invoke the top-level URL:

$.ajax({
      //replace "hostName" and "httpsPort" with your values
      url: "https://" + hostName + ':' + httpsPort + "/IBMJMXConnectorREST",
      type: 'GET',
      success: function(data, textStatus, jqXHR ) {
                  console.log("success:" + jqXHR.responseText);
                  // Do something with the result, such as populate some fields
                  mbeansURL = data.mbeans;
                  mbeanCountURL = data.mbeanCount;
                  notificationURL = data.notifications;
                  fileTransferURL = data.fileTransfer;
      },
      error: function(result) {
                  console.log("error:" + result.statusText);
                  // Do something with the error
      }
});

The above snippet shows an important characteristic of these REST APIs; they can be navigated through a dynamic self-discovery of URLs. This means that you can start with the top-level URL and gain the knowledge of the other URLs by exploring each returned link, one at a time.

However, you can jump directly into most of the URLs if you know what the URL is, either from experience or by consulting the API manual. I say “most” because some URLs have a dependency on another URL; for example, you cannot use the notification registration URL (/IBMJMXConnectorREST/notifications/{clientID}/registrations) before first doing a POST to the notifications URL (/IBMJMXConnectorREST/notifications) to obtain a clientID.

Invoking an operation for an MBean

Now let’s take a look at a function that invokes a specific operation for an MBean.

In this case, we’re assuming operationID points to a JQuery ID in the HTML graph that has a series (1 or more) of child text boxes with hidden attributes called myParamType. myParamType describes the simple type of the parameter (such java.lang.String). The result is written to an HTML paragraph section underneath operationID. The exact operationURL can be obtained from the MBeanInfo query, but as per the API its general format is: /IBMJMXConnectorREST/mbeans/{objectName}/operations/{operation}.

function invokeOperation(operationURL, operationID) {
      console.log("got a request to invoke: " + operationURL + " with selector: " + operationID);
      console.log($("#" + operationID).children("[type='text']"));
	  	  
      //Build the invocation payload
      var invokePayload = {};
      invokePayload.params = [];
      invokePayload.signature = [];
  
      //Loop each parameter input box
      $("#" + operationID).children("[type='text']").each(function (index) {
            console.log(index + " : " + $(this).val() + " : " + $(this).attr("myParamType"));
	  
            var pojo = {};
            pojo.value = $(this).val();
            pojo.type = $(this).attr("myParamType");
            invokePayload.params[index] = pojo;
            invokePayload.signature[index] = $(this).attr("myParamType");
      }); 
      console.log("all done.  invokePayload: " + JSON.stringify(invokePayload));
	  
      //Invoke
      $.ajax({
            url: getJMXURL("https://" + hostName + ':' + httpsPort + operationURL),
            type: 'POST',
            data : JSON.stringify(invokePayload),
            dataType: "json",
            contentType: "application/json",
            success: function(data, textStatus, jqXHR ) {
                        console.log("success:" + jqXHR.responseText);
                        if (data.value != null && data.type != null) {
                              $("#" + operationID + "> p").text("Result: " + jqXHR.responseText);
                        }
            },
            error: function(result) {
                        console.log("error:" + result.statusText);
            }
      });  
}

Creating a notification registry

In this next example, I’ll demonstrate how to create a new notification registry (a notification client ID) and register a notification for the given MBean (URL encoded ObjectName):

function registerMBeanNotification(mbeanName) {
      console.log("registering notification for ObjectName: " + mbeanName);

       var notificationSettings = {};
      notificationSettings.deliveryInterval = "0"; //no long-lived GETs
      notificationSettings.inboxExpiry = "300000";
  		
      $.ajax({
            url: "https://" + hostName + ':' + httpsPort + notificationURL,
            type: 'POST',
            async : false,
            data : JSON.stringify(notificationSettings),
            dataType: "json",
            contentType: "application/json",
            success: function(data, textStatus, jqXHR ) {
                        console.log("success:" + jqXHR.responseText);
                        notificationArea.registrations = data.registrations;
                        notificationArea.serverRegistrations = data.serverRegistrations;
                        notificationArea.inbox = data.inbox;
            },
            error: function(result) {
                        console.log("error:" + result.statusText);
            }
      });  
  	  	
      //NotificationRegistration
      var notificationRegistration = {};
      notificationRegistration.objectName = mbeanName;
      notificationRegistration.filters = [];
  	
      $.ajax({
            url: "https://" + hostName + ':' + httpsPort + notificationArea.registrations,
            type: 'POST',
            data : JSON.stringify(notificationRegistration),
            dataType: "json",
            contentType: "application/json",
            success: function(data, textStatus, jqXHR ) {
                        console.log("success:" + jqXHR.responseText);
            },
            error: function(result) {
                        console.log("error:" + result.statusText);
            }
      });  
}

Periodically polling the notification inbox

As a final step, you can periodically poll the notification inbox. The following example creates a new HTML table to display the notifications, which is attached to an HTML division called mainDiv:

function notifications() {
      $.ajax({
            url: "https://" + hostName + ':' + httpsPort + notificationArea.inbox,
            type: 'GET',
            success: function(data, textStatus, jqXHR ) {
                        console.log("success:" + jqXHR.responseText);
                        $("#mainDiv").append("There are " + data.length + " notifications available.<br><br>");
                        for (var i = 0; i < data.length; i++) {
                              var myNotification = data[i];
                              var myNotificationText =  "<table>" + 
                                    "<tr><td><b>className:</b></td><td>" + myNotification.className + "</td></tr>" + 
                                    "<tr><td><b>type:</b></td><td>" + myNotification.type + "</td></tr>" + 
                                    "<tr><td><b>source:</b></td><td>" + myNotification.source + "</td></tr>" + 
                                    "<tr><td><b>sequenceNumber: </b></td><td>" + myNotification.sequenceNumber + "</td></tr>" + 
                                    "<tr><td><b>timeStamp:</b></td><td>" + new Date(parseInt(myNotification.timeStamp)) + "</td></tr>" + 
                                    "<tr><td><b>message:</b></td><td>" + myNotification.message + "</td></tr>" + 
                                    "<tr><td><b>attributeName:</b></td><td>" + myNotification.attributeName + "</td></tr>" + 
                                    "<tr><td><b>Old Value:</b></td><td>" + myNotification.oldValue.value + "</td></tr>" + 
                                    "<tr><td><b>New Value:</b></td><td>" + myNotification.newValue.value + "</td></tr>";
						
                                    //Handle routed notification, if applicable			
                                    if (myNotification.hostName != undefined) {
                                          myNotificationText = myNotificationText + 
                                                "<tr><td><b>hostName:</b></td><td>" + myNotification.hostName + "</td></tr>" + 
                                                "<tr><td><b>serverUserDir:</b></td><td>" + myNotification.serverUserDir + "</td></tr>" + 
                                                "<tr><td><b>serverName:</b></td><td>" + myNotification.serverName + "</td></tr>"; 
                                    }
                              myNotificationText = myNotificationText + "</table><br>";								
                              $("#mainDiv").append("<p><b>Notification[" + i + "]</b>:</p>" + myNotificationText);
                        }
            },
            error: function(result) {
                        console.log("error:" + result.statusText);
                        // Do something with the result
                        $("#mainDiv").append(result);
                  }
      });  
}

I encourage you to continue exploring the other available REST APIs, such as file transfer and collective routing. Stay tuned for another article that will explore the advanced collective deployment APIs using Node.js!

(Updated October 2016 for V8.5.5.8 instead of the beta instructions.)

3 comments on"Accessing Liberty’s JMX REST APIs"

  1. […] Like traditional WAS, Liberty users can either use tools to visualize these metrics, or directly obtain the metrics themselves by invoking the appropriate MBean through JMX. To find out more, see the Knowledge Center docs. Unlike traditional WAS, Liberty also allows users (and tools) to obtain these metrics using RESTful interfaces as described in a devWorks article. […]

  2. Arthur De Magalhaes May 31, 2017

    Hey. Yup, here’s an example. You have the signature portion correct, but under “params” the type of the map needs to be an actual map implementation classname, and needs some special fields:

    {
    “params”:[
    {
    “value”:”hello”,
    “type”:”java.lang.String”
    },
    {
    “value”:{
    “hello”:”world”
    },
    “type”:{
    “className”:”java.util.HashMap”,
    “simpleKey”:true,
    “entries”:[
    {
    “key”:”hello”,
    “keyType”:”java.lang.String”,
    “value”:”java.lang.String”
    }
    ]
    }
    }
    ],
    “signature”:[
    “java.lang.String”,
    “java.util.Map”
    ]
    }

  3. Do you have an example of using this API with registerHost specifically the use of java.util.Map ?

    I tried passing this in

    {
    “params”: [
    {
    “name”: “hostName”,
    “value”: “hello”,
    “type”: “java.lang.String”
    },
    {
    “name”: “hostAuthInfo”,
    “value”: {“hello”: “world” },
    “type”: “java.util.Map”
    }

    ],
    “signature”: [ “java.lang.String”, “java.util.Map” ]
    }

    But got

    “: “com.ibm.ws.jmx.connector.datatypes.ConversionException: readPOJOValue() received an unknown class name.\n\tjava.util.Map\n\tat com.ibm.ws.jmx.connector.converter …

Join The Discussion

Your email address will not be published. Required fields are marked *