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;
    }
    

}


20 June 2007

Published: Javadoc for OC4J MBeans

A Javadoc set has just been published on the OTN documentation site for the OC4J MBeans:

Oracle® Application Server JMX MBean Java API Reference 10g Release 3 (10.1.3)

We also published a Javadoc set for the relevant portions of our deployment area/JSR88 implementation:

Oracle® Application Server Deployment Java API Reference 10g Release 3 (10.1.3)

13 June 2007

Groovy + JMX documentation

Got a note recently from one of the Groovy developers that he'd authored some documentation around the use of JMX and Groovy. Very nicely he included examples of connecting to OC4J.

Good job Paul!

http://groovy.codehaus.org/Groovy+and+JMX

** Small update: I just tried the script and I couldn't get the code example for OC4J to work as it was listed on the site. I had to make a few minor alterations to get it to work for me. Here's the groovy script I have now which works against Groovy 1.0.

The script does work as it is shown against Groovy 1.1 which has an enhanced GroovyMBean constructor that can now takes the target MBean name in String form in addition to the earlier ObjectName form.

import oracle.oc4j.admin.jmx.remote.api.*
import javax.management.remote.*
import javax.management.*

def serverUrl = new JMXServiceURL('service:jmx:rmi://localhost:23791')
def serverPath = 'oc4j:j2eeType=J2EEServer,name=standalone'
def jvmPath = 'oc4j:j2eeType=JVM,name=single,J2EEServer=standalone'
def provider = 'oracle.oc4j.admin.jmx.remote'

def credentials = [
(JMXConnectorConstant.CREDENTIALS_LOGIN_KEY): 'oc4jadmin',
(JMXConnectorConstant.CREDENTIALS_PASSWORD_KEY): 'welcome1'
]
def env = [
(JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES): provider,
(JMXConnector.CREDENTIALS): credentials
]
//def MBeanServerConnection server = (MBeanServerConnection)JMXConnectorFactory.connect(serverUrl, env).mBeanServerConnection
def server = JMXConnectorFactory.connect(serverUrl, env).mBeanServerConnection
def serverInfo = new GroovyMBean(server, new ObjectName(serverPath))
def jvmInfo = new GroovyMBean(server, new ObjectName(jvmPath))
println """Connected to $serverInfo.node. \
Server started ${new Date(serverInfo.startTime)}.
OC4J version: $serverInfo.serverVersion from $serverInfo.serverVendor
JVM version: $jvmInfo.javaVersion from $jvmInfo.javaVendor
Memory usage: $jvmInfo.freeMemory bytes free, \
$jvmInfo.totalMemory bytes total
"""

def query = new ObjectName('oc4j:*')
String[] allNames = server.queryNames(query, null)
def dests = allNames.findAll{ name ->
name.contains('j2eeType=JMSDestinationResource')
}.collect{ new GroovyMBean(server, new ObjectName(it)) }

println "Found ${dests.size()} JMS destinations. Listing ..."
dests.each{ d -> println "$d.name: $d.location" }

Pasman has a blog

A work buddy of mine from Oracle Support has just setup a new blog. Judging from the types of questions he sends to me, he sees a lot of things that happen in the real world, so chances are he's going to have some interesting things to write about when time permits.

http://theblasfrompas.blogspot.com/

08 June 2007

admin_client.jar versus admin.jar

A pretty good question was asked recently on the OTN forum for OC4J about the two outwardly similar command line utilities we have in OC4J/OracleAS 10.1.3.x.

I thought I'd post the question and answer here in case anyone else was wondering about this.

Question:

OC4J provide two command-line utility: admin.jar and admin_client.jar for performing configuration tasks on OC4J.

admin_client.jar can work in Oracle Application Server clustered environment as well as on a standalone OC4J server, but admin.jar only works in a standalone OC4J installation.

Do they have any other difference?


If used in a standalone OC4J installation, are they the same?


Answer:

In a nutshell the situation is this:

admin.jar is the old command line utility we kept in the release for backwards compatibility. It uses a set of internal/proprietrary APIs to work against OC4J. It works only against OC4J standalone and does not allow you to perform any of its operations against an OracleAS instance.

admin_client.jar is a new command line utility added in 10.1.3.x that is based on JMX and its associated set of specifications to manage and deploy to a J2EE container. It can connect to and manage all variants of OC4J -- standalone, single AS instance, clustered AS instance. It has many more resource configuration options added to it.

For simplicity, we maintained the "feel" of admin.jar so the command structure is very similar to what was provided in admin.jar, but the way it works is completely different under the covers.

While admin.jar still works and is supported, you should use admin_client.jar whenever you want to do command line operations with 10.1.3.x. I was considering putting a note to that effect on stdout whenever admin.jar was used, but we didn't end up doing it.

Another two nice aspects about admin_client.jar are

1. The common library it uses to perform its operations is also used by the Oracle Ant tasks -- so in effect there are two faces to it -- admin_client.jar and Ant tasks. There's a near 100% compatibility between the two meaning what you can do with admin_client.jar you can most likely do with a set of Ant tasks from a development environment.

2. We have isolated the dependencies for admin_client.jar and produced a separate, small distribution called oc4j_admin_client.zip (which is 6MB from memory) that can be unzipped onto any remote server and you have the full admin_client.jar utility -- meaning that to perform administration options against ant of the OC4J variants (standalone, single and clustered AS instance) you only need this one, very small distribution.

The documentation on the OC4J management tools covers this to some degree:
http://download-west.oracle.com/docs/cd/B32110_01/web.1013/b28950/admin.htm#CEGHHGGB

29 May 2007

Configuring DBTable LoginModule

From the OC4J 10.1.3.1 release onwards, we now provide a fully supported login module that authenticates users against entries in a database table.

This is documented here in the J2EE Security Guide for 10.1.3.1: http://download-west.oracle.com/docs/cd/B32110_01/web.1013/b28957/loginmod.htm#sthref417

What the doc mentions, but doesn't show is that you can configure this login module as the security provider using Application Server Control and the Customer Login Module option. Granted, it'd be nice to have a more concise dialog to configure this specific user module, but you can still do it using by supplying the class name yourself and setting all the required properties to configure it.

Here's a screen shot of ASC that shows the configurartion screen with the basic set of required properties.



To reach this screen, click into the application you want to configure, click the Administration tab and then the Security Provider task.

Configuring DB

01 May 2007

Getting an MBeanServer reference with OC4J

Based on some investigative work done this week, a little nugget of information has popped out regarding the best way to get a reference to an MBeanServer from an application deployed to OC4J.

There as two standard methods that can be used to get a reference to an MBeanServer:
When using OC4J, the best approach is to use the newMBeanServer method in preference to the createMBeanServer method.

The newMBeanServer method can be used in two ways:

MBeanServerFactory.newMBeanServer() ;

This will return a reference to the OC4J global MBeanServer that is wrapped with an interceptor that will ensure that all MBeans registered by the application are registered under a domain named after the application, and that all accessed MBeans are either application MBeans, or dependent resources.

MBeanServerFactory.newMBeanServer(String domain);

Will create an application specific MBeanServer with the supplied domain name.

27 April 2007

Start OC4J in remote debug mode

Note for self: starting OC4J in remote debug mode

>java -server -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=4000 -jar oc4j.jar

02 March 2007

Oracle Instant Client is Instant!

I needed a SQLPlus environment recently to access the RAC DB I have running in a VMWare image.

I'd heard of the Oracle Instant Client, so I checked it out and man, it is dead simple. There's nothing quite like something just working as it is intended to.

To run SQLPlus as a client on Windows:

Download two zip files

* Instant Client Package - Basic Lite

http://download.oracle.com/otn/nt/instantclient/instantclient-basiclite-win32-10.2.0.3-20061115.zip

* Instant Client Package - SQL*Plus

http://download.oracle.com/otn/nt/instantclient/instantclient-sqlplus-win32-10.2.0.3-20061115.zip

Once downloaded, extract them into the same directory -- which defaults to instantclient_10_2.

Then set PATH to include the directory.

And finally, set TNS_ADMIN to the directory where you have created and saved the tnsnames.ora file. I chucked my tnsnames.ora into the instantclient_10_2 directory so it was all contained.

Then it just works.

Magic!