Java MBeans - Complex Objects - Composite Data

An MBean is a managed Java object. They are generally used to expose monitoring statistics of a java application, management capability on it. JDK itself has a default set of MBeans to monitor JVM statistics etc.

It follows the JMX specification. You can use JMX protocol to invoke MBeans. It is possible to connect to MBeans from same machine insecurely, but if you access from a different machine, credentials needs to be provided.

It is often a requirement to receive and view several properties of a set of objects (objects are rows and properties are columns if you put it to a table). This can be achieved using CompositeData under Open MBeans.

This tutorial shows how to define MBean with CompositeData as well as how to access and receive information.

Scenario 


Expose a set of customer objects. A customer has following properties those need to be exposed.

  • name (String)
  • address (String)
  • age (Integer)
  • amount paid (Long)
  • is married (Boolean)

Define a Customer object

Let us define a customer object as below. 


package mBeanServer;

public class Customer {

    private String name;
    private String address;
    private int age;
    private long amountPaid;
    private boolean isMarried;

    public Customer(String name, String address, int age, long amountPaid, boolean isMarried) {
        this.name = name;
        this.address = address;
        this.age = age;
        this.amountPaid = amountPaid;
        this.isMarried = isMarried;
    }

    public String getName() {
        return name;
    }

    public String getAddress() {
        return address;
    }

    public int getAge() {
        return age;
    }

    public long getAmountPaid() {
        return amountPaid;
    }

    public boolean isMarried() {
        return isMarried;
    }

    public String toString() {
        return "name: " + name
        + "address:" + address
        + "age:" + age
        + "amount paid:" + amountPaid
        + "is married: " + isMarried;
    }
}


Create a Object factory to create customers

Following class is used to create some mock customer objects


package mBeanServer;

import java.util.ArrayList;
import java.util.List;

public class CustomerFactory {

    public static List generate() {

        Customer customer1 = new Customer("Hasitha", "Kandy, Sri Lanka", 30, 2000, false);
        Customer customer2 = new Customer("Asanka", "Bandarawatte, Kaluthara, Sri Lanka,", 27, 3000, false);
        Customer customer3 = new Customer("Perera", "Colombo, Sri Lanka", 25, 400, false);

        List customers = new ArrayList<>();
        customers.add(customer1);
        customers.add(customer2);
        customers.add(customer3);

        return customers;
    }
}


Create an interface for MBean


Java follows some standards here. If concrete implementation is "Y" interface name should be "YMBean". Both should be in same package.

package ;
public interface YMBean {...}
package ;
public class Y implements YMBean {...}

Following the convention let us define the MBean provider interface as follows.



package mBeanServer;

import javax.management.MBeanException;
import javax.management.openmbean.CompositeData;

public interface CustomerInfoProviderMBean {

    String NAME = "name";
    String ADDRESS = "address";
    String AGE = "age";
    String AMOUNT_PAID = "amountPaid";
    String IS_MARRIED = "isMarried";

    CompositeData[] getCustomerInformation() throws MBeanException;

}

Implement MBean provider interface

To follow MBean conventions, implementation class name should be as below

        public class CustomerInfoProvider implements CustomerInfoProviderMBean {...}

Create CompositeDataSupport objects and add to CompositeData[]

Each CompositeData object will represent a customer. To create one, you need to give CompositeType (described below), set of attribute names and set of attribute values of a customer.

    @Override
    public CompositeData[] getCustomerInformation() throws MBeanException {
        List compositeDataList = new ArrayList<>();
        List listOfCustomers = CustomerFactory.generate();
        try {
            for (Customer customer : listOfCustomers) {
                Object[] itemValues = new Object[]{customer.getName(),
                        customer.getAddress(),
                        customer.getAge(),
                        customer.getAmountPaid(),
                        customer.isMarried()};
                CompositeDataSupport support = new CompositeDataSupport(getCustomerDataType(), customerAttributeNames(),
                        itemValues);
                compositeDataList.add(support);
            }
        } catch (OpenDataException e) {
            throw new MBeanException(e, "Error occurred when getting customer information via JMX");
        }
        return compositeDataList.toArray(new CompositeData[compositeDataList.size()]);
    }

getCustomerDataType(), customerAttributeNames()


We need to define CompositeType with attribute names, attribute descriptions and attribute types. Here let us use attribute names as descriptions for simplicity. 

    private static CompositeType getCustomerDataType() throws OpenDataException {
        CompositeType customerDataType = new CompositeType("customer data type",
                "dta type for customer information",
                customerAttributeNames(),
                customerAttributeDescriptions(),
                customerAttributeTypes());
        return customerDataType;
    }

    private static String[] customerAttributeNames() {

        String[] attributeNames = {CustomerInfoProviderMBean.NAME,
                CustomerInfoProviderMBean.ADDRESS,
                CustomerInfoProviderMBean.AGE,
                CustomerInfoProviderMBean.AMOUNT_PAID,
                CustomerInfoProviderMBean.IS_MARRIED};

        return attributeNames;
    }

    private static String[] customerAttributeDescriptions() {
        return customerAttributeNames();
    }

    private static OpenType[] customerAttributeTypes() {

        OpenType[] attributeTypes = new OpenType[5];

        attributeTypes[0] = SimpleType.STRING;  //name
        attributeTypes[1] = SimpleType.STRING;  //address
        attributeTypes[2] = SimpleType.INTEGER; //age
        attributeTypes[3] = SimpleType.LONG;    //amount paid
        attributeTypes[4] = SimpleType.BOOLEAN; //for marital status

        return attributeTypes;
    }

Now the MBean implementation is ready.

Start a MBean Server on a given port

Start MBean server and register the MBean we prepared above.


package mBeanServer;

import java.io.IOException;
import java.lang.management.*;
import java.rmi.registry.LocateRegistry;
import javax.management.*;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;

public class Main {

    public static void main (String[] args) throws MalformedObjectNameException, NotCompliantMBeanException,
            InstanceAlreadyExistsException, MBeanRegistrationException, IOException {
        MBeanServer mbs = startMBeanServer();
        ObjectName name = new ObjectName("mBeanServer:type=Customer");
        CustomerInfoProvider mbean = new CustomerInfoProvider();
        mbs.registerMBean(mbean, name);

        System.out.println("Waiting forever...");
        try {
            Thread.sleep(Long.MAX_VALUE);
        } catch (InterruptedException e) {
            //ignore
        }
    }

    private static MBeanServer startMBeanServer() throws IOException {
        int jmxPort = 1919;
        LocateRegistry.createRegistry(jmxPort);
        MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
        JMXServiceURL jmxUrl
                = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:" + jmxPort + "/jmxrmi");
        JMXConnectorServer connectorServer
                = JMXConnectorServerFactory.newJMXConnectorServer(jmxUrl, null, beanServer);

        connectorServer.start();
        return beanServer;
    }
}


Test and Verify if MBean is exposed correctly


Compile and run above java program. 
Find process id of the java program using "jps" command.
type "jconsole " in terminal and open jconsole. You will see MBean exposed as below.



Get customer information using a JMX client


It is possible to write below program to connect and receive MBean information. All three customer information is printed. Note that instance name and attribute name can be picked up from jconsole. 



package jmxClient;

import mBeanServer.Customer;
import mBeanServer.CustomerInfoProviderMBean;

import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.openmbean.CompositeData;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.util.ArrayList;
import java.util.List;

public class CustomerInfoRetriever {

    public static void main(String args[]) throws Exception {
        CustomerInfoRetriever customerInfoRetriever = new CustomerInfoRetriever();
        List customers = customerInfoRetriever.getCustomers();
        //print information, this can be printed as a table
        for (Customer customer : customers) {
            System.out.println(customer);
        }
    }

    public List getCustomers() throws Exception {
        List customerList = new ArrayList<>();
        try {

            ObjectName objectName =
                    new ObjectName("mBeanServer:type=Customer");
            Object result = getMBeanConnector().getAttribute(objectName, "CustomerInformation");
            if (result != null) {
                CompositeData[] customerInformationList = (CompositeData[]) result;
                for (CompositeData queueData : customerInformationList) {
                    String name = (String) queueData.get(CustomerInfoProviderMBean.NAME);
                    String address = (String) queueData.get(CustomerInfoProviderMBean.ADDRESS);
                    Integer age = (Integer) queueData.get(CustomerInfoProviderMBean.AGE);
                    Long paidAmount = (Long) queueData.get(CustomerInfoProviderMBean.AMOUNT_PAID);
                    Boolean isMarried = (Boolean) queueData.get(CustomerInfoProviderMBean.IS_MARRIED);

                    Customer queue = new Customer(name, address, age, paidAmount, isMarried);

                    customerList.add(queue);
                }
            }
        } catch (MalformedObjectNameException | ReflectionException | MBeanException | InstanceNotFoundException e) {
            throw new Exception("Cannot access mBean operations to get customer information:", e);
        } catch (AttributeNotFoundException e) {
            throw new Exception("Cannot access mBean operations for message counts. Attribute not found", e);
        }
        return customerList;
    }

    private static MBeanServerConnection getMBeanConnector() {

        MBeanServerConnection remote = null;

        try {
            int jmxPort = 1919;
            JMXServiceURL target = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + "127.0.0.1" + ":" + jmxPort +
                    "/jmxrmi");
            JMXConnector connector = JMXConnectorFactory.connect(target);
            remote = connector.getMBeanServerConnection();
        } catch (Exception e) {
            System.out.println("Error when connecting to MBean " + e);
        }
        return remote;
    }
}





Running the client you will see customer information is printed to the console. This way, complex object information can be transferred using MBeans using CompositeData.


Hasitha Hiranya

No comments:

Post a Comment

Instagram