Welcome!

Java Authors: Maureen O'Gara, Bruce Armstrong, Liz McMillan, Walter H. Pinson, III, Yakov Werde

Related Topics: Java

Java: Article

JavaCaller: The Last Session Bean

A poweful and universal EJB

Most Enterprise JavaBeans (EJBs) serve a definite purpose, performing a specific set of actions on behalf of client applications. The ubiquitous Bank Account bean, which supports basic account transactions such as withdrawal and deposit, appears in almost every J2EE tutorial. Students are persuaded that real-life EJBs, though more advanced, are as practical and particular as those developed in the classroom.

This article presents an unusual stateful session bean called JavaCaller, whose interface is entirely general, providing client applications with the ability to run arbitrary Java code on the application server. Because of its generality, JavaCaller can be made to do anything that any other session bean can do; to use the language of computability theory, any problem that can be solved by a session bean can be solved by JavaCaller.

What distinguishes JavaCaller from other session beans is that its execution of actions is completely controlled by client applications. Different clients can make JavaCaller do different things because the client manages the control flow. Supporting a simple reflection interface, JavaCaller exposes methods that let clients create objects and classes, invoke class or object methods, and get or set object fields, all on the server side. As Figure 1 shows, JavaCaller is like a robotic arm, controlled remotely by client applications to manipulate classes and objects in the application server's JVM.

Not that the tutorials are wrong; the specificity of the typical EJB is a virtue. Lack of specificity implies lack of focus and gets flagged during design review; an EJB should do exactly what it is required to do. However, JavaCaller's purpose is atypical: it is a hook into the server JVM, or, alternatively, a shell that runs scripts and jobs on the server. It opens up the application server to users to the degree that operating system shells open up the operating system. True, application servers provide management consoles and open management APIs such as SNMP and JMX, but only for the administration of managed objects, such as applications and resources. The shell is an interface to all the nonmanaged objects too.

JavaCaller API
JavaCaller is a stateful session EJB that can run on any J2EE application server supporting EJB 2.0. (It has been tested on BEA WebLogic 7.0 and 8.1.) JavaCaller's home interface is a create() method with no arguments. Its remote interface, shown in Figure 2, provides client applications with the ability to manipulate objects and classes in the server's address space.

JavaCaller can get or set static fields (getClassField(), setClassField()) and call static methods (callClassMethod()), as well as get and set object-level fields (setField(), getField()) and call object-level methods (callMethod()). JavaCaller can also instantiate a class (instantiate()), add a new class to JavaCaller's class loader (createClass()), and return to the client a serialized copy of an object (getObject()).

JavaCaller stores object and class references in its object cache. This cache, which constitutes the client state of the bean, is empty when the client's session begins and builds up as the client uses objects and classes. JavaCaller is stateful because it manages an object cache for the client. The population of the cache is described in Table 1.

In keeping with the notion of "remote control," the client never uses server objects directly, but has JavaCaller use them on its behalf. The client normally resides in a separate address space anyway, making direct manipulation impossible. JavaCaller's getObject() method returns to the client a serialized copy of a server object, which provides the client with a snapshot of the object's state and perhaps the ability to play with it locally. However, because the object is only a copy, nothing the client does with it has any server-side effect. If the client wants to perform successive operations on the same server-side object, it calls successive JavaCaller methods passing the same numeric object reference, representing an index to JavaCaller's object cache. Note: Objects maintained by JavaCaller need not be serializable, but getObject() works only for serializable objects.

Most JavaCaller methods return either an integer or a Union. The integer is a cache reference identifier that the client can use to specify an object or class when invoking subsequent JavaCaller methods. The concept underlying the Union type comes from the "C" union type: it represents a value of one type selected from a set of possible types. In the context of JavaCaller, these types are:

  • Primitive types, such as int and double, which Union stores as Java wrapper types such as java.lang.Integer and java.lang.Double.
  • Cache object references.
  • Objects - a convenience for clients that can be used to pass an object argument, rather than an object reference, when instantiating an object, calling a method of an object or class, or setting a field of an object or a class. This option should be used only for serializable objects, such as java.lang.String.
A Union is returned from the getField(), getClassField(), callMethod(), and callClassMethod() JavaCaller methods. If the given field or method return value is an object, JavaCaller sets the object reference field in the Union; otherwise, it sets the primitive field in the Union.

The Union type is also used as an argument type in JavaCaller methods. In setField() and setClassField(), the client uses it to specify a new object or primitive value for the specified field. In callMethod(), callClassMethod(), and instantiate(), the client uses it to specify object or primitive argument values for the given method or constructor. In this case, the client also specifies the string name of the argument type (for example, "int" for a primitive integer type, "java.util.Date" for the Date object type). This is required because classes or objects can overload methods and constructors. Argument types are required to distinguish methods or constructors with the same name.

The createClass() method allows the client to add a new class on the server side. The client passes a buffer of bytes, representing the contents of a Java class file on the client side. JavaCaller dynamically adds a new class on the server side based on the specified byte code and adds a reference to the class in its object class. The client can then manipulate the new class, or create instances of it and manipulate those instances.

Examples
Example 1: WebLogic Monitoring

This simple example shows how a client application uses WebLogic server objects to monitor the idle thread count of a WebLogic server. As shown in Listing 1, this example gets the idle thread count of the server using the weblogic.kernel package. It then prints out this result in the standard output of the server. (Listings 1 and 2 can be downloaded from www.sys-con.com/java/sourcec.cfm.)

The main steps are as follows:

  1. In the constructor (lines 18-36), get the initial context and acquire a reference to the JavaCaller home interface.
  2. Call the create() method of the home interface (line 42) to create a new instance of JavaCaller called "caller."
  3. In lines 43-95, use caller to affect object interactions on the server side. Table 2 maps server code to equivalent client-side code.
  4. Remove the caller, cleaning up the cache (line 99).
Example 2: The Last Web Service
The second example shows how to write a Web service, called PourJavaOnServer, that loads and runs classes on the server. The Web service is written in WebLogic Workshop, a graphical Web service development tool from BEA.

PourJavaOnServer has one method, runObject(), that does the following:

  1. Loads a class into the server JVM, based on the fully qualified class name and byte code (passed to the Web service in Base 64 encoding) specified by the user. The class is required to have a default constructor and a method called exec() whose signature is:

    String exec(String)

  2. Instantiates the class with its default constructor.
  3. Calls the constructed object's exec() method, passing an argument specified by the Web service user.
  4. Returns the exec() return string to the Web service user.

More Stories By Michael Havey

Michael Havey is a Chordiant consultant with 10 years of industry experience, mostly with application integration. Michael's book Essential Business Process Modeling was published by O'Reilly in August 2005.

Comments (2) View Comments

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.


Most Recent Comments
John Jaster 02/04/05 01:35:11 PM EST

Articles like this point to the fact that this magazine is for tech weenies instead of enterprize level developers. Any serious Architect/Developer would laugh at the absurdity of such an approach.

David 01/07/05 08:50:26 AM EST

You're joking, right?