Gerenciando o servidor com Server MBean e JMX

O JMX é uma tecnologia que permite o gerenciamento de recursos como aplicações, dispositivos, serviços, etc. A maioria dos servidores de aplicações utiliza essa tecnologia que já vem por padrão no J2SE 5 e 6. O único problema é que é complicado realizar as operações. O primeiro passo é realizar um conexão com o servidor que possui os MBeans, criar uma interface, carregar um proxy e executar métodos por essa interface. O procedimento passo a passo, é descrito aqui ou nesse tutorial.

Resolvi encapsular o lógica de execução em uma classe, mas queria evitar de ter que implementar uma interface para cada MBean a ser utilizado. E esse pensamento resultou na seguinte classe:

package org.gvlabs.weblogic.mbean;

import java.io.IOException;
import java.util.Hashtable;
import java.util.Map;

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.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import javax.naming.Context;

/**
 * MBean Connector
 *
 * @author Thiago Galbiatti Vespa
 *
 */
public class MBeanConnector {
	private static final int TIMEOUT = 10000;
	private MBeanServerConnection connection;
	private String hostname;
	private int port;
	private String username;
	private String password;

	/**
	 * Constructor with connection info
	 *
	 * @param hostname
	 *            hostname to connect
	 * @param port
	 *            port fo the server to connect
	 * @param username
	 *            username of the server
	 * @param password
	 *            username's password
	 */
	public MBeanConnector(String hostname, int port, String username,
			String password) {
		super();
		this.hostname = hostname;
		this.port = port;
		this.username = username;
		this.password = password;
	}

	/**
	 * Connect to server
	 *
	 * @throws IOException
	 * @throws MalformedURLException
	 */
	protected void connect() throws IOException {

		String protocol = "t3"; // t3, t3s, http, https, iiop, iiops
		String jndiroot = "/jndi/";
		/**
		 * Domain Runtime MBean Server - weblogic.management.mbeanservers.domainruntime
		 * Runtime MBean Server - weblogic.management.mbeanservers.runtime
		 * Edit MBean Server - weblogic.management.mbeanservers.edit
		 */
		String mserver = "weblogic.management.mbeanservers.domainruntime";
		JMXServiceURL serviceURL = new JMXServiceURL(protocol, hostname, port,
				jndiroot + mserver);

		Map<String, Object> h = new Hashtable<String, Object>();
		h.put(Context.SECURITY_PRINCIPAL, username);
		h.put(Context.SECURITY_CREDENTIALS, password);
		h.put(JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES,
				"weblogic.management.remote");
		h.put("jmx.remote.x.request.waiting.timeout", TIMEOUT);
		JMXConnector connector = JMXConnectorFactory.connect(serviceURL, h);
		connection = connector.getMBeanServerConnection();
	}

	/**
	 * Retrieve connection
	 *
	 * @return connection
	 * @throws IOException
	 * @throws MalformedURLException
	 * @throws Exception
	 */
	public MBeanServerConnection getConnection() throws IOException {
		if (connection == null) {
			connect();
		}
		return connection;
	}

	protected ObjectName initMBean(String mbName)
			throws MalformedObjectNameException {
		// TODO: Create a cache?
		return new ObjectName(mbName);
	}

	/**
	 * Get the value of an attribute of a MBean
	 *
	 * @param mbName
	 *            MBean name
	 * @param attributeName
	 *            attribute name
	 * @return value
	 * @throws MalformedObjectNameException
	 * @throws IOException
	 * @throws MalformedURLException
	 * @throws ReflectionException
	 * @throws MBeanException
	 * @throws InstanceNotFoundException
	 * @throws AttributeNotFoundException
	 * @throws Exception
	 */
	public Object getAttribute(String mbName, String attributeName)
			throws MalformedObjectNameException, AttributeNotFoundException,
			InstanceNotFoundException, MBeanException, ReflectionException,
			IOException {
		ObjectName service = initMBean(mbName);
		return getConnection().getAttribute(service, attributeName);

	}

	/**
	 * Invoke a MBean method
	 *
	 * @param mbName
	 *            MBean name
	 * @param methodName
	 *            method name
	 * @param signature
	 *            method signature
	 * @param params
	 *            method params
	 * @return method return value
	 * @throws MalformedObjectNameException
	 * @throws IOException
	 * @throws MalformedURLException
	 * @throws ReflectionException
	 * @throws MBeanException
	 * @throws InstanceNotFoundException
	 * @throws Exception
	 */
	public Object invokeMethod(String mbName, String methodName,
			String[] signature, Object[] params)
			throws MalformedObjectNameException, InstanceNotFoundException,
			MBeanException, ReflectionException, IOException {
		ObjectName service = initMBean(mbName);
		return getConnection().invoke(service, methodName, params, signature);
	}

	/**
	 * Invoke a MBean method
	 *
	 * @param mbName
	 *            MBean name
	 * @param methodName
	 *            method name
	 * @param params
	 *            method params
	 * @return method return value
	 * @throws IOException
	 * @throws MalformedURLException
	 * @throws ReflectionException
	 * @throws MBeanException
	 * @throws InstanceNotFoundException
	 * @throws MalformedObjectNameException
	 * @throws Exception
	 */
	public Object invokeMethod(String mbName, String methodName,
			Object... params) throws MalformedObjectNameException,
			InstanceNotFoundException, MBeanException, ReflectionException,
			IOException {
		if (params == null) {
			return this.invokeMethod(mbName, methodName, new String[] {},
					new Object[] {});
		}
		String[] signature = new String[params.length];
		for (int sigIdx = 0; sigIdx < signature.length; sigIdx++) {
			signature[sigIdx] = params[sigIdx].getClass().getCanonicalName();
		}
		return this.invokeMethod(mbName, methodName, signature, params);
	}

	/**
	 * Invoke a MBean method
	 *
	 * @param mbName
	 *            MBean name
	 * @param methodName
	 *            method name
	 * @return method return value
	 * @throws IOException
	 * @throws MalformedURLException
	 * @throws ReflectionException
	 * @throws MBeanException
	 * @throws InstanceNotFoundException
	 * @throws MalformedObjectNameException
	 * @throws Exception
	 */
	public Object invokeMethod(String mbName, String methodName)
			throws MalformedObjectNameException, InstanceNotFoundException,
			MBeanException, ReflectionException, IOException {
		return this.invokeMethod(mbName, methodName, (Object[]) null);
	}

}

A conexão aqui está específica para o WebLogic que é o meu servidor alvo. Caso você tenha interesse, pode modificar para qualquer outro servidor. Para utilizar essa classe, existe algumas informações que você vai precisar, como os nomes dos MBeans :). No WebLogic, acesse o Enterprise Manager, vá em WebLogic Domain e selecione o domínio alvo. No menu superior, escolha WebLogic Domain, System MBean Browser. No meu caso, eu tenho um servidor SOA e gostaria de saber quais são as partições e repositórios que existem nele. Para isso vou escolher o MBean que está localizado em Application Defined MBeans em oracle.mds.lcm, Domain:soa_domain, MDSDomainRuntime, MDSDomainRuntime:

System MBean Browser
System MBean Browser

Ao lado irão abrir as informações dos atributos, métodos e notificações. A parte mais importante nesse momento é um "+ Show MBean Information" que existe na parte superior, clique nele e copie a informação sobre o nome do MBean:

MDSDomainRuntime
MDSDomainRuntime

Escolhe um atributo ou operação e agora é só utilizar a classe da seguinte forma:

		MBeanConnector mbConn = new MBeanConnector("localhost", 7001,
				"weblogic", "weblogic1");
		String[] repositories = (String[]) mbConn.invokeMethod(
				"oracle.mds.lcm:name=MDSDomainRuntime,type=MDSDomainRuntime",
				"listRepositories");

No array repositories você terá a lista de repositórios MDS, pois invoquei o método listRepositories do MBean com o nome que recuperei do EM: oracle.mds.lcm:name=MDSDomainRuntime,type=MDSDomainRuntime.

Se precisar passar parâmetros, você pode executar o seguinte código:

		String[] p = (String[]) mbConn
					.invokeMethod(
							"oracle.mds.lcm:name=MDSDomainRuntime,type=MDSDomainRuntime",
							"listPartitions", "mds-soa");

Você pode executar qualquer método ou recuperar a informação de qualquer atributo de um MBean de maneira simples. Agora é só se aventurar.

Sobre: Thiago Galbiatti Vespa

Thiago Galbiatti Vespa é mestre em Ciências da Computação e Matemática Computacional pela USP e bacharel em Ciências da Computação pela UNESP. Coordenador de projetos do JavaNoroeste, membro do JCP (Java Community Process), consultor Oracle, arquiteto de software de empresas de médio e grande porte, palestrante de vários eventos e colaborador de projetos open source. Possui as certificações: Oracle Certified Master, Java EE 5 Enterprise Architect – Step 1, 2 and 3; Oracle WebCenter Portal 11g Certified Implementation Specialist; Oracle Service Oriented Architecture Infrastructure Implementation Certified Expert; Oracle Certified Professional, Java EE 5 Web Services Developer; Oracle Certified Expert, NetBeans Integrated Development Environment 6.1 Programmer; Oracle Certified Professional, Java Programmer; Oracle Certified Associate, Java SE 5/SE 6