Ronald Mak is a software developer, architect, and instructor in Silicon Valley. He can be contacted at [email protected].
JAVA AND CORBA are a powerful combination for developing client-server software. But CORBA
objects, even ones written in Java, can be a challenge to debug. Many Java debuggers have difficulty
single-stepping from local application code into code that is running in a remote object.
A bipolar CORBA object is one written in Java in such a way that a Java client application can use it
either remotely or locally. The client application can bind to the object on the CORBA bus, or it
can use the new operator to instantiate the object in the local address space. Once the application
has a reference to a bipolar object, it is transparent to the application, whether the object is
remote or local. The exact same method code in the object will run in either case.
By making the remote objects bipolar, we can ease program development. A locally instantiated bipolar
object does not require CORBA runtime code. Not only will this simplify debugging the object, but we've
also temporarily eliminated one major domain where a program can fail.
Figure 1. The inheritance approach.
Another justification is performance and flexibility. Multiple client applications can share remote
objects that run on a server. There may also be server applications that want to use those objects,
and if they run on the same machine as the server objects, we can improve performance if the server
applications create the shared objects locally on the server and thus bypass CORBA.
For a bipolar object to be useful, it must meet several criteria:
- It should be easy to make a remote object bipolar, and we must not violate any CORBA programming
conventions.
- Once a client application has a reference to a bipolar object, it shouldn't need to know whether
the object is running remotely or locally. We don't want to sprinkle
if(local) and if(remote) tests
throughout the program.
- The exact same code in the bipolar object must run whether the object is running remotely or
locally. We don't want to debug two separate sets of code.
A SIMPLE EXAMPLE
Let's look at a simple Hello object that would normally be accessed
remotely via the CORBA bus. Its implementation is class HelloImpl
in the hello package. When we run it from the command line on the
server, we pass it two string arguments, a hello string and a
goodbye string, such as:
java hello.HelloImpl "Guten Tag" "Auf Wiedersehen"
(It shall be a very remote object, indeed.)
The object includes two public methods:
public String sayHelloTo(String name)
public String sayGoodbyeTo(String name)
When a client application calls the first method with a name string, such as
sayHelloTo("Ron"), the method returns the hello string
concatenated with the name, as in "Guten Tag, Ron!". Similarly,
the second method returns the goodbye string concatenated with the name, as in
"Auf Wiedersehen, Ron!"
There is a third public method:
public void initServer(
String helloString, String goodbyeString)
As we'll soon see, this method helps in making the object bipolar. Listing 1 shows
the IDL file hello.idl for this example.
THE "tie" APPROACH
The key to making a CORBA object bipolar is the "tie" or delegation
approach. To understand this, the UML diagram in Figure 1 displays the class hierarchy when using the
standard inheritance approach. The diagram distinguishes three types of classes and interfaces:
- Fixed classes and interfaces that never change. These come from Java
(e.g., java.lang.Object) or from CORBA
(e.g., org.omg.CORBA.Object).
- Classes and interfaces that the idl2java compiler generates from the hello.idl
file (e.g., _HelloImplBase; for this article, we use the compiler
from Inprise's Visibroker.)
- Classes that we must write (e.g., HelloImpl).
We write the public class HelloImpl to implement
the Hello object. This class must extend the generated abstract
class _HelloImplBase, which contains code to do CORBA operations
specific to the Hello object, such as marshaling the string
parameters. The abstract class implements the generated Hello
interface. This interface, shown in Listing 2,
specifies the signatures of the three public methods initServer(),
sayHelloTo(), and sayGoodbyeTo(),
and it extends the CORBA interface org.omg.CORBA.Object.
Figure 2. The "tie" approach.
Figure 2 shows the class hierarchy when we use the tie approach.
In both figures, the shaded portion highlights the classes and interfaces that are common to both
the inheritance and tie approaches.
The class we write, HelloImpl, now only needs to implement the
generated interface HelloOperations, which, like the
Hello interface, specifies the signatures of the three public
methods. But unlike the Hello interface,
HelloOperations does not extend
org.omg. CORBA.Object. Listing 3
shows source file HelloImpl.java for the tie approach.
The tie approach generates an intermediary class
_tie_Hello. This tie class
extends _HelloImplBase
(see Listing 4).
The tie constructors set the private member variable
_delegate to refer to the actual
HelloImpl object. Each of the public methods
initServer(), sayHelloTo(), and
sayGoodbyeTo() invokes the corresponding
HelloImpl method via _delegate.
The main() method of HelloImpl
(Listing 3) is called whenever we start the program on a remote server. It first creates a
HelloImpl object and calls its
initServer() method to set the hello
and goodbye strings that were passed in as command-line parameters.
Then it creates a _tie_Hello object, passing the
HelloImpl object to the constructor to set up its delegate reference.
Finally, main() initializes the object request broker and
puts the _tie_Hello object (using the instance
name "HelloInstance") onto the CORBA bus to await requests from
the client applications.
The primary advantage of the tie method is that it allows
HelloImpl to be extended from another class, if necessary.
A secondary advantage is that it enables us to make our Hello object
bipolar.
THE LocalHello CLASS
To make our Hello object bipolar, we write a new class,
LocalHello, that mimics the generated
tie class _tie_Hello.
In fact, we can see in Listing 5
that we copy code from _tie_Hello into the body of
LocalHello.
In the constructor, we set the private variable _delegate to
refer to a local instance of the Hello object created
with the new operator.
The latter part of LocalHello "stubs out" the methods of
org.omg.CORBA.Object. These stubs will never be called, but they
do allow the class to implement the generated Hello interface,
as shown in Figure 2.
The crucial fact that allows bipolarity is that both the _tie_Hello class and the LocalHello class
implement the Hello interface.
THE CLIENT APPLICATION
Listing 6 shows a sample
client application ClientApp that can access our
Hello object either remotely via the CORBA bus or locally in the
same address space.
The program takes two command-line parameters. The first parameter is the
letter "r," to indicate that we want to access the
Hello object remotely; anything else says we want local access.
The second parameter is a person's name.
For example, for remote access, we type:
java hello.ClientApp r Ron
and for local access, we type:
java hello.ClientApp x Ron
If the first command-line parameter of ClientApp
is "r", then main() initializes
the object request broker and sets variable helloObject by binding
to the remote _tie_Hello object using the instance
name "HelloInstance." However, if the first program parameter
is not "r," then main() instead
sets helloObject to a local instance of the
Hello object with new LocalHello().
When we started the Hello object as a remote CORBA object, we passed
it the hello and goodbye strings on the command line. In the local
case, the client application calls the Hello object's public
initServer() method to pass it these strings. In Listing 6,
they are "Howdy" and "Bye-bye"
(in a very local dialect).
The first command line below tells ClientApp to bind to the
remote Hello object, and we see the two output lines from the
remote object. The second command line below tells ClientApp to bind
to a local Hello object, and we see the two output lines from the
local object.
java hello.ClientApp r Ron
Guten Tag, Ron!
Auf Wiedersehen, Ron!
java hello.ClientApp x Ron
Howdy, Ron!
Bye-bye, Ron!
In the client, helloObject.sayHelloTo() and
helloObject.sayGoodbyeTo() work whether variable
helloObject refers to a remote or to a local
Hello object. In either case, the exact same code in
HelloImpl runs. Of course, if we only want local access
to the Hello object, there is no need to run
hello.HelloImpl from the command line to put an instance of the
remote object on the CORBA bus.
DEBUG LOCALLY; EXECUTE REMOTELY
Let us review how our example bipolar object helps us during program development. We want to debug the
interactions between ClientApp and the
Hello object.
We begin testing with a local Hello object. If there are any
problems, we know the bug must be either in ClientApp or
in HelloImpl because CORBA is not yet in the picture. A
debugger should have no difficulty single-stepping between the two objects which are both
running in the same address space.
After we've debugged the interactions between the two objects, we start up the
Hello object remotely. Now, if testing reveals any problems,
we can concentrate our debugging efforts on the CORBA connection. We've already debugged
HelloImpl when it was running locally, and the same code
runs even when we start it remotely.
The way we wrote ClientApp makes it easy to switch between
running the Hello object locally and remotely. If we find
a problem when the object is running remotely, we simply switch to running it locally for
debugging. After fixing the bug, we start the new version of HelloImpl
on the server and we can quickly switch back to running the object remotely for further testing.
Suppose we have another application, ServerApp, for example,
that needs to use the Hello object, and the application
happens to run on the same machine as the object. Then ServerApp
can bind locally to the object and the two will interact without the performance penalties of CORBA.
IT'S NOT A FREE LUNCH
The benefits of creating bipolar objects outweigh the costs, but we should consider what we have to pay.
Bipolar objects force us to employ the tie approach, which means a bit more development work and
a few more source files for a project that could otherwise use the inheritance approach. The use
of delegates exacts a small runtime performance penalty. Of course, the tie approach, as
described by the CORBA documentation, has its own advantages independent of the creation of bipolar objects.
We need to maintain an extra source file, the one we called LocalHello.java in our example.
To create this file, we copy lines from the source files of the tie class
(_tie_Hello in our example) and the class
org.omg.CORBA.Object. This can be done with a script that
automatically runs whenever either source file changes.
CONCLUSION
Debugging client-server applications is difficult if bugs can occur in the client code, the server
code, or in the CORBA connections. We've seen how simple it is to make the remote objects bipolar,
so that we can debug them initially in the local address space without CORBA getting in the way.
Only after we've debugged the client-server interactions do we switch to running the server
objects remotely on the CORBA bus.