25 June 2007

Using Groovy to operate with OC4J Groups

Update: the client code referenced in this post, and a sample script to create an OC4J group and populate it with OC4J instances is available here:
The Oracle Application Server 10g Release 3 product exposes the concept of OC4J Groups. An OC4J group is a formal entity that represents a set of separate OC4J instances as a single unit. An OC4J group can be started, stopped; it can be configured and managed; deployment operations can be conducted against it. In all cases, the task performed on the group is is delegated out to the individual OC4J instances that reside within the group to be actioned.
To effectively support the management and configuration of an OC4J group, a cluster MBeanServer model is utilized in conjunction with a special MBean that represents an OC4J group.
The cluster MBeanServer is a specialized MBeanServer that works with the OPMN technology in Oracle Application Server so that it is always aware of all the individual MBeanServer instances running witihn the connected network topology.
The J2EEServerGroupMBean is an MBean that performs the task of exposing a group to MBean clients so that MBeans can be interacted with. The J2EEServerGroupMBean closely resembles the MBeanServer interface and enables clients to perform operations, get and set attributes on a specified MBean. When the tasks are executed on the J2EEServerGroupMBean, the J2EEServerGroupMBean will federate the operations to all the running MBeanServers in the OC4J instances that reside in the group.
A distinct difference from the standard MBeanServer interface is that tasks that have a return value will return a Map instead of an Object. The Map contains the results of the task execution from each of the OC4J instances that participated in the requested task. The OC4J instance name is used as the key for each entry in the Map and the value is the result of the executed task.
http://download-west.oracle.com/docs/cd/B31017_01/web.1013/e10288/oracle/oc4j/admin/management/farm/mbeans/J2EEServerGroupMBean.html
To use the J2EEServerGroupMBean, a client first connects to the OC4J cluster MBeanServer. The respective J2EEServerGroup MBean is located for the target group. Once a reference to the J2EEServerGroup MBean is obtained, it is used to execute operations or set and get attributes against any specific MBean that resides within the MBeanServers of the OC4J instances within the group.
Unfortunately, since the J2EEServerGroupMBean is an actual MBean itself that exposes methods to work on other MBeans, using it either from either direct Java code, or using it from Groovy as an GroovyMBean means that you need to resort to using the primitive invoke, setAttribute, getAttribute methods to perform tasks on a specified MBean.
Ideally, it’d be more productive and suit the scripting approach more naturally to be able to perform a task on an MBean through the simplified GroovyMBean approach but have it performed at the group level. When the tasks are executed on a specified MBean, the J2EEServerGroupMBean would be used under the covers to execute the invoke, setAttribute or getAttribute methods.
Making a Group GroovyMBean

It turns out that using the power of the Groovy meta-data model and Java subclassing, it’s possible to create an extension of the standard GroovyMBean (called OC4JgroupGroovyMBean in here) that does just that.
By passing a GroovyMBean object that wrappers the target MBean into an OC4JGroupGroovyMBean object, the OC4JGroupGroovyMBean can be made to look exactly like the GroovyMBean thus directly exposing all the attributes and operations from the underlying MBean.
Through overriding specific methods such as setProperty, getProperty and invokeMethod, the tasks are conducted through the specified J2EEServerGroupMBean instead of the standard MBeanServer connection and are therefore conducted against the J2EEServerGroup.

The example OC4JGroupGroovyMBean has a constructor of the following form:
public OC4JGroupGroovyMBean(MBeanServerConnection clusterConnection, ObjectName groupMBeanName,GroovyMBean mbean)
The first parameter contains the connection to an OC4J Cluster MBeanServer.
The second parameter is the ObjectName that represents the target J2EEServerGroup on which to execute the tasks.
The third parameter is a GroovyBean that has been created for the target MBean that is to be operated on.
At this point, it’s more illustrative to present a simple example. Lets consider that we have a two-instance installation of Oracle Application Server, on which we have a group called COLORS in which reside two OC4J instances, RED and BLUE.
Across the COLORS group, for each OC4J instance we wish to view the vendor and version of the JDK that is being used, and report the current memory that has been consumed by the running instance.
As a starting point, the OC4J MBean that contains the required information is the JVMMBean.
This can be obtained from an OC4J MBeanServer using the ObjectName “oc4j:j2eeType=JVM,name=single,J2EEServer=standalone”.
The target J2EEServerGroup will have an ObjectName of the form “ias:j2eeType=J2EEServerGroup,name=
import demo.oc4j.jmx.*;
// First create a GroovyMBean for the target MBean
client = new OC4JClient()
client.connect("service:jmx:rmi:///opmn://localhost/home", "oc4jadmin","welcome1")
jvm = client.helper.createGroovyMBean("oc4j:j2eeType=JVM,name=single,J2EEServer=standalone")
// Now create an OC4JGroupGroovyMBean around it
clusterClient = new OC4JClient();
clusterClient.connect("service:jmx:rmi:///opmn://localhost/cluster", "oc4jadmin","welcome1")
def group_jvm =
new OC4JGroupGroovyMBean(clusterClient.connection,
”ias:j2eeType=J2EEServerGroup,name=default_group",
jvm);
def memory = group_jvm.freeMemory;
memory.keySet().each() {
def mem = memory.get(it) / 1024 / 1024
println "$it\n\t-->free $mem MB\n"
}
// close the clients
client.close()
clusterClient.close()
Which when executed produces the following output:

ias:j2eeType=JVMProxy,name=1,J2EEServerGroup=COLORS,J2EEServer=RED,ASInstance=j2ee_server.SERVER
-->free 454.37 MB
ias:j2eeType=JVMProxy,name=1,J2EEServerGroup=COLORS,J2EEServer=BLUE,ASInstance=j2ee_server.SERVER
-->free 453.977 MB

The code for the OC4JGroupGroovyMBean is quite simple and just overrides a couple of the methods on the standard GroovyMBean.

The listing for OC4JGroupGroovyMBean example is below.
package demo.oc4j.jmx;

// Use wide imports just for saliency 
import groovy.lang.*;
import groovy.util.*;
import java.io.*;
import java.util.*;
import java.util.logging.*;
import javax.management.*;
import oracle.oc4j.admin.management.farm.mbeans.proxies.*;

/**
 * This class is derived from the work done by the official 
 * Groovy development team at "The Codehaus - http://groovy.codehaus.org/"
 * 
 * The purpose is to use the same simple model of interacting with MBeans
 * from Groovy as if they were local object, but now at the OC4J Group level
 * via the J2EEServerGroupMBean.
 * 
 * This is specific to the OC4J Group mechanism and not general purpose.
 * 
 */
public class OC4JGroupGroovyMBean extends GroovyMBean {

    static Logger logger = Logger.getLogger(OC4JGroupGroovyMBean.class.getName());

    J2EEServerGroupMBeanProxy groupProxy = null;
    MBeanServerConnection clusterConnection = null;
    ObjectName groupMBeanName = null;
    
    // This overrides the operations map from the parent class because its a 
    // private field and it can't be accessed from here
    Map operations = new HashMap();

    /**
     * Construct an OC4JGroupGroovyMBean that represents the named MBean at 
     * the OC4J Group level.
     * @param clusterConnection -- connection to OC4J cluster domain
     * @param groupMBeanName -- string form of the J2EEServerGroupMBean name
     * @param mbean -- the GroovyMBean to perform operations on
     * @throws JMException
     * @throws IOException
     */
    public OC4JGroupGroovyMBean(MBeanServerConnection clusterConnection,
                                String groupMBeanName, 
                                GroovyMBean mbean) throws JMException, 
                                                          IOException {
        this(clusterConnection, new ObjectName(groupMBeanName), mbean);
    }
    
    /**
     * Construct an OC4JGroupGroovyMBean that represents the named MBean at 
     * the OC4J Group level.
     * @param clusterConnection -- connection to OC4J cluster domain
     * @param groupMBeanName -- ObjectName for the J2EEServerGroupMBean name
     * @param mbean -- the GroovyMBean to perform operations on
     * @throws JMException
     * @throws IOException
     */
    public OC4JGroupGroovyMBean(MBeanServerConnection clusterConnection,
                                ObjectName groupMBeanName,
                                GroovyMBean mbean) throws JMException, IOException {
        
        // Make this object look like the GroovyMBean for the specified MBean
        super(mbean.server(), mbean.name());
        
        this.clusterConnection = clusterConnection;
        this.groupMBeanName = groupMBeanName;

        // Populate the operations map needed to conduct the invoke operation
        try {
            operations = populateOperations(mbean);
        } catch (IntrospectionException e) {
            throw new JMException(e.getMessage());
        }
        
        // Create a dynamic proxy for the J2EEServerGroup
        groupProxy = (J2EEServerGroupMBeanProxy)
                MBeanServerInvocationHandler.newProxyInstance(
                clusterConnection, 
                groupMBeanName,
                J2EEServerGroupMBeanProxy.class,
                false);
    }

    /**
     * Get a set of properties from an MBean via a J2EEServerGroup
     * @param props
     * @return Map where v = instancename, t = String[] of values
     */
    public Map getProperties(Collection props) {
        String[] properties = (String[]) props.toArray(new String[0]);
        logger.log(Level.FINE, "getProperty, properties = " + properties);        
        Map ret = new HashMap();
        try {
            Map data = groupProxy.getAttributes(this.name(), properties);
            for(ObjectName id: data.keySet()) {
                AttributeList attributes = data.get(id);
                List values = new ArrayList();
                for (int i = 0; i <>
                    Attribute attribute= (Attribute) attributes.get(i);
                    values.add(attribute.getValue());
                }
                ret.put(id, values);
            }
        }
        catch (MBeanException e) {
            throw new GroovyRuntimeException("Could not access property: " + properties + ". Reason: " + e, e.getTargetException());
        }
        catch (Exception e) {
            throw new GroovyRuntimeException("Could not access property: " + properties + ". Reason: " + e, e);
        }
        return ret;
    }
    
    /**
     * Get a property from an MBean via a J2EEServerGroup
     * @param property
     * @return Map of property values from each OC4J instance in the Group
     */
    public Map getProperty(String property) {
        logger.log(Level.FINE, "getProperty, property = " + property);        

        Map ret = null;
        try {
            ret = groupProxy.getAttribute(this.name(), property);
        }
        catch (MBeanException e) {
            throw new GroovyRuntimeException("Could not access property: " + property + ". Reason: " + e, e.getTargetException());
        }
        catch (Exception e) {
            throw new GroovyRuntimeException("Could not access property: " + property + ". Reason: " + e, e);
        }
        return ret;
    }
    
    /**
     * Set a property on an MBean via the J2EEServerGroupMBean
     * @param property
     * @param value
     */
    public void setProperty(String property, Object value) {
        logger.log(Level.FINE,"setProperty, property = " + property + ", value = " + value);        
        try {
            groupProxy.setAttribute(this.name(), new Attribute(property, value));
        }
        catch (MBeanException e) {
            throw new GroovyRuntimeException("Could not set property: " + property + ". Reason: " + e, e.getTargetException());
        }
        catch (Exception e) {
            throw new GroovyRuntimeException("Could not set property: " + property + ". Reason: " + e, e);
        }
    }

    /**
     * Set properties on the MBean via the J2EEServerGroupMBean 
     * @param properties
     * @return Map of results from each instance of setting the properties 
     */
    public Map setProperties(Map properties) {
        try {
            AttributeList attributes = new AttributeList();
            for(String key: properties.keySet()) {
                Attribute attribute = new Attribute(key, properties.get(key));
                attributes.add(attribute);
            }
            return groupProxy.setAttributes(this.name(), attributes);
        }
        catch (MBeanException e) {
            throw new GroovyRuntimeException("Could not set properties: " + properties+ ". Reason: " + e, e.getTargetException());
        }
        catch (Exception e) {
            throw new GroovyRuntimeException("Could not set properties: " + properties  + ". Reason: " + e, e);
        }
    }

    /**
     * Invoke a method on the J2EEServerGroupProxy
     * @param method
     * @param args
     * @return
     */
    public Object invokeMethod(String method, Object args) {
        logger.log(Level.FINE,"invokeMethod, method = " + method + ", args = " + args);        
        Object[] argArray = null;
        if (args instanceof Object[]) {
            argArray = (Object[]) args;
        } else {
            argArray = new Object[]{args};
        }
        // Locate the specific method based on the name and number of parameters
        String operationKey = createOperationKey(method, argArray.length);
        String[] signature = (String[]) operations.get(operationKey);
        
        if (signature != null) {
            try {
                return groupProxy.invoke(this.name(), method, argArray, signature);
            }
            catch (MBeanException e) {
                throw new GroovyRuntimeException("Could not invoke method: " + method + ". Reason: " + e, e.getTargetException());
            }
            catch (Exception e) {
                throw new GroovyRuntimeException("Could not invoke method: " + method + ". Reason: " + e, e);
            }
        } else {
            //todo: validate when/why this occurs, don't think we ever want to do this!
            return super.invokeMethod(method, args);
        }
    }
    

    /**
     * Populate a local operations Map with details from the target MBean
     * @param mbean
     * @return
     * @throws IOException
     * @throws IntrospectionException
     * @throws InstanceNotFoundException
     * @throws ReflectionException
     */
    private Map populateOperations(GroovyMBean mbean) throws IOException, 
                                                             IntrospectionException, 
                                                             InstanceNotFoundException, 
                                                             ReflectionException {
        Map operations = new HashMap();
            
        MBeanInfo beanInfo = mbean.server().getMBeanInfo(mbean.name());
        MBeanOperationInfo[] operationInfos = beanInfo.getOperations();
        for (int i = 0; i <>
            MBeanOperationInfo info = operationInfos[i];
            String signature[] = createSignature(info);
            String operationKey = createOperationKey(info.getName(), signature.length);
            operations.put(operationKey, signature);
         }
         return operations;
    }
    

}


2 comments:

sam said...

Is OC4JClient() your code? Can't seem to find it in any Jar. I am trying to create a groovy script to create OC4J containers and groups. Is that possible with Groovy?

Buttso said...

Hey Sam -- yes that code is something I was developing which never saw the true light of day. I've posted a link to the jar file above if you want to try it out. The code was an attempt to simplify the client connection code used with groovy, plus some helper classes to access the various MBeans using some topical knowledge of the hierarchy.

I've also uploaded a sample script I had which creates a new OC4J Group and populates it with OC4J instances.

Let me know how you get on. I can make the src code available as well if you want.

cheers
-steve-