03 September 2007

Accessing Return Values from EJB Interceptors

Continuing from my last posting regarding the application of EJB3 interceptors to existing applications, there's another interesting tidbit regarding how to access the return value of an EJB method call in an interceptor.

Thanks to some sage advice from members of our EJB team (who have authored a simply outstanding book in my opinion) turns out that you can use a simple pattern like this in your interceptor method:

public Object intercept(InvocationContext ctx) throws Exception {

// do stuff as pre-invoke
...

// Execute the bean method, or next interceptor in the chain
Object result = ctx.proceed();


// do stuff as post-invoke
...

// return the result from the handler
return result;
}


Using this pattern, you have access to the pre and post invoke states of the method call on the bean.

30 August 2007

EJB External Interceptors

Came across a situation quite recently which had a requirement for the invocation of various methods calls of an EJB to be able to logged for future auditing purposes.

The simplest answer is to use EJB3 and its Interceptor functionality in the guise of an @AroundInvoke method which gets called on every method invocation of the bean class.

Now this sounds OK if you are already using EJB3 and have access to the bean source code to modify it and add the interceptor method and the annotation.

If you don't have access to the original bean source code, you can still do it by using an external interceptor class. In this technique, you create an class external which contains interceptor method(s) and use the ejb-jar.xml file to declare the external interceptor class and bind it to the bean(s) and methods you want to apply it to.

By way of example, here's an external interceptor class:
package sab.demo.interceptors;

import javax.interceptor.InvocationContext;

public class AuditInterceptor {
public AuditInterceptor() {
}

/**
* Interceptor method which prints method call
*/
public Object logMethodCall(InvocationContext ic) throws Exception {

// The InvocationContext contains the context of the intercepted call

System.out.printf("ExternalInterceptor\n\tMethod: %s\n",
ic.getMethod().getName());

// Print out the parameter values if they exist
if(ic.getParameters().length!=0) {
System.out.printf("\tParameters: ");
boolean first = true;
String sep = ", ";
for(Object o : ic.getParameters()) {
System.out.printf(" %s", o.toString());
if(!first) {
System.out.printf("%c", sep);
} else {
first = false;
}
}
}
System.out.println();

// Carry on
return ic.proceed();
}
}
To apply this to an existing bean class, create a partial ejb-jar.xml file with the entries pertinent to the interceptor class:
<?xml version="1.0" encoding="windows-1252" ?>
<ejb-jar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd"
xmlns="http://java.sun.com/xml/ns/javaee" version="3.0">
<interceptors>
<interceptor>
<interceptor-class>sab.demo.interceptors.AuditInterceptor</interceptor-class>
<around-invoke>
<method-name>logMethodCall</method-name>
</around-invoke>
</interceptor>
</interceptors>
<assembly-descriptor>
<interceptor-binding>
<ejb-name>SomeBusiness</ejb-name>
<interceptor-class>sab.demo.interceptors.AuditInterceptor</interceptor-class>
<method>
<method-name>*</method-name>
</method>
</interceptor-binding>
</assembly-descriptor>
</ejb-jar>
When the bean is called from a client, the external interceptor is invoked and the method calls are printed to stdout:
ExternalInterceptor
Method: someBusinessMethod
Parameters: 1188441131015
The external interceptor class can be packaged within the EJB-JAR file, or alternatively, it can be packaged in a separate JAR file and included in the EAR file as a library. Using JEE5, the library can be referenced using the new <libraries ... > tag and putting the JAR file into the specified directory in the EAR file.

<application version="5">
<library-directory>libraries</library-directory>
<module>
<ejb>ejb.modulejar</ejb>
</module>
</application>
What about EJB 2.1?

Turns out this works for EJB 2.1 applications as well with some minor tweaks to the existing descriptor file.

In this case, you only really need to alter the existing ejb-jar.xml so the version is specified as "3.0" and add the interceptors tags. Whereupon OC4J (10.1.3.x) will run the bean as EJB 3.0, and apply the interceptors as specified.

Here's an example of an EJB 2.1 being converted to EJB 3.0 and the interceptors added.


<?xml version = '1.0' encoding = 'windows-1252'?>
<!--
<ejb-jar
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd"
xmlns="http://java.sun.com/xml/ns/j2ee"
version="2.1" >
-->

<ejb-jar version="3.0">
<interceptors>
<interceptor>
<interceptor-class>sab.demo.interceptors.AuditInterceptor</interceptor-class>
<around-invoke>
<method-name>logMethodCall</method-name>
</around-invoke>
</interceptor>
</interceptors>
<enterprise-beans>
<session>
<description>Session Bean ( Stateless )</description>
<display-name>SomeBusiness</display-name>
<ejb-name>SomeBusiness</ejb-name>
<home>sab.demo.ejb21.SomeBusinessHome</home>
<remote>sab.demo.ejb21.SomeBusiness</remote>
<local-home>sab.demo.ejb21.SomeBusinessLocalHome</local-home>
<local>sab.demo.ejb21.SomeBusinessLocal</local>
<ejb-class>sab.demo.ejb21.SomeBusinessBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
</session>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>SomeBusiness</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<interceptor-binding>
<ejb-name>SomeBusiness</ejb-name>
<interceptor-class>sab.demo.interceptors.AuditInterceptor</interceptor-class>
<method>
<method-name>*</method-name>
</method>
</interceptor-binding>
</assembly-descriptor>
</ejb-jar>


In this case, if you don't want to change the application.xml file to be versioned at JEE 5.0 and use its libraries inclusion facility, then to include the library containing the external interceptor class you can use the <library> element of the orion-application.xml file to specify the library to load:
<orion-application xsi="http://www.w3.org/2001/XMLSchema-instance" nonamespaceschemalocation="http://xmlns.oracle.com/oracleas/schema/orion-application-10_0.xsd">
<library path="InterceptorLibrary.jar"/>
</orion-application>

19 July 2007

Le Tour on SBS

It's that time of the year, where here down under, for 3 weeks, many people are staying up late at night to watch the stages of the Tour de France, live.

A very big thanks to SBS television here for the superb broadcast they put on, covering every stage. Their TdF website has video podcasts of each stage, check it out @ SBS TdF Podcasts.

Sad to see Stuey and Mick Rogers go down, but Cadel Evans is looking strong, competing well and has a good chance of getting on the podium, if not better.

Have to admit that I'm glad I'm not in Germany for the next couple of weeks, where they've just cut the broadcasts because of the latest doping infraction. Now I can see that it prevents a big audience from getting exposure to the sport/event/sponsors .... but .. and perhaps I'm a little naive but I can't grok how really punishing the general viewing public achieves anything substantive. It'll make them pissed off perhaps, but how that will prevent a dodgy rider from taking the doping route, I don't get it.

Ride on!

10 July 2007

Doc bug in RemoveSharedLibrary ant task

There's a known documentation bug for the <oracle:removeSharedLibrary ... > task.

The documentation describes the attribute name for the version of the library to use as "version". Whereas, in reality, the attribute name should be "libraryVersion" as its defined on the ant task.

You can view the various attributes/properties of an ant task by running javap on the class and looking for the accessors.
>javap -public -classpath j2ee\utilities\ant-oracle-classes.jar
oracle.ant.taskdefs.deploy.JSR88RemoveSharedLibrary

Compiled from "JSR88RemoveSharedLibrary.java"
public class oracle.ant.taskdefs.deploy.JSR88RemoveSharedLibrary
extends oracle.ant.taskdefs.deploy.JSR88BaseTask {
public oracle.ant.taskdefs.deploy.JSR88RemoveSharedLibrary();
public void execute() throws org.apache.tools.ant.BuildException;
public void setLibraryName(java.lang.String);
public java.lang.String getLibraryName();
public void setLibraryVersion(java.lang.String);
public java.lang.String getLibraryVersion();
}
The correct use of the task would be:
<oracle:removeSharedLibrary
deployerUri="deployer:oc4j:opmn://localhost/home"
userid="oc4jadmin"
password="welcome1"
libraryName="apache.xml"
libraryVersion="2.7"/>

27 June 2007

Accessing j_username in OC4J form based authentication failures

A question on the OC4J OTN forum recently asked about how the error page used in form based authentication could get access to the username that was provided so an audit trail could be established.

Intuitively you'd expect that the form fields passed in from the logon form would be passed through to the error page when an authentication fails and the forward is done. However this is not the case. The request parameter map is empty.

The solution is to use an OC4J proprietary feature called a Form Auth Filter. This is a standard Servlet Filter that can be injected into the request path when form based authentication is performed. The filter will be called before the authentication is performed and has access to the full set of request parameters passed in from the j_security_check form.

http://download-west.oracle.com/docs/cd/B32110_01/web.1013/b28959/filters.htm#sthref150

To accomplish the task of making the supplied j_username available in the error page, a form auth filter can extract the j_username parameter and store it in the request as an attribute.

Then in the error handler defined for the form-auth (jsp, struts action, etc.) simply extract the request attribute and do what you want with it.

Here's a step by step example.

1. Create a web application that uses form-based-authentication

<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>web-inf/logon.jsp</form-login-page>
<form-error-page>web-inf/error.jsp</form-error-page>
</form-login-config>
</login-config>

2. Create a ServletFilter to remap the j_username request parameter

package demo.sab.otn.formauthfilter;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class FormAuthFilter implements Filter {
private FilterConfig _filterConfig = null;

public void init(FilterConfig filterConfig) throws ServletException {
_filterConfig = filterConfig;
}

public void destroy() {
_filterConfig = null;
}

public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse)response;
HttpServletRequest req = (HttpServletRequest)request;

String j_username = req.getParameter("j_username") ;
if(j_username!= null) {
req.setAttribute("j_username", j_username);
}
chain.doFilter(req, res);
}
}

3. Create an orion-web.xml file to specify the ServletFilter as a FORMAUTH filter

<?xml version = '1.0' encoding = 'windows-1252'?>
<orion-web-app>
<web-app>
<filter-mapping>
<filter-name>FormAuthFilter</filter-name>
<dispatcher>FORMAUTH</dispatcher>
</filter-mapping>
</web-app>
<security-role-mapping name="secure" impliesAll="false">
<group name="oc4j-administrators"/>
</security-role-mapping>
</orion-web-app>
3. Access the j_username attribute from the error.jsp page

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%@ page contentType="text/html;charset=windows-1252"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252"/>
<title>Proxy Authentication</title>
<link href="../css/blaf.css" rel="stylesheet" media="screen"/>
</head>
<body>
<h2>Error!</h2>
<%
String username = request.getAttribute("j_username")==null?
"" : (String)request.getAttribute("j_username");
%>
Error logging on as <%=username%>&nbsp;Try again.
<p>
<form method="POST" action="j_security_check">
<input type="text" name="j_username" value="<%=username%>"/>
<input type="password" name="j_password"/>
<input type="submit" value="logon"/>
</form>

</p>
</body>
</html>


Now give it a spin. When an authentication failure occurs, the error.jsp page displays the username that was last provided.


This simple example just demonstrates how to get access to the supplied j_username, what you then decide to do with it is up to you!

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