CORBA
In
large, distributed applications, your needs might not be satisfied by the
preceding approaches. For example, you might want to interface with legacy
datastores, or you might need services from a server object regardless of its
physical location. These situations require some form of Remote Procedure Call
(RPC), and possibly language independence. This is where CORBA can help.
CORBA
is not a language feature; it’s an integration technology. It’s a
specification that vendors can follow to implement CORBA-compliant integration
products. CORBA is part of the OMG’s effort to define a standard
framework for distributed, language-independent object interoperability.
CORBA
supplies the ability to make remote procedure calls into Java objects and
non-Java objects, and to interface with legacy systems in a
location-transparent way. Java adds networking support and a nice
object-oriented language for building graphical and non-graphical applications.
The Java and OMG
object model map nicely to each other; for example, both Java and CORBA
implement the interface concept and a reference object model.
CORBA
Fundamentals
The
object interoperability specification developed by the OMG is commonly referred
to as the Object Management Architecture (OMA). The OMA defines two components:
the Core Object Model and the OMA Reference Architecture. The Core Object Model
states the basic concepts of object, interface, operation, and so on. (CORBA is
a refinement of the Core Object Model.) The OMA Reference Architecture defines
an underlying infrastructure of services and mechanisms that allow objects to
interoperate. The OMA Reference Architecture includes the Object Request Broker
(ORB), Object Services (also known as CORBAservices), and common facilities.
The
ORB is the communication bus by which objects can request services from other
objects, regardless of their physical location. This means that what looks like
a method call in the client code is actually a complex operation. First, a
connection with the server object must exist, and to create a connection the
ORB must know where the server implementation code resides. Once the connection
is established, the method arguments must be marshaled, i.e. converted in a
binary stream to be sent across a network. Other information that must be sent
are the server machine name, the server process, and the identity of the server
object inside that process. Finally, this information is sent through a
low-level wire protocol, the information is decoded on the server side, and the
call is executed. The ORB hides all of this complexity from the programmer and
makes the operation almost as simple as calling a method on local object.
There
is no specification for how an ORB Core should be implemented, but to provide a
basic compatibility among different vendors’ ORBs, the OMG defines a set
of services that are accessible through standard interfaces.
CORBA
Interface Definition Language (IDL)
CORBA
is designed for language transparency: a client object can call methods on a
server object of different class, regardless of the language they are
implemented with. Of course, the client object must know the names and
signatures of methods that the server object exposes. This is where IDL comes
in. The CORBA IDL is a language-neutral way to specify data types, attributes,
operations, interfaces, and more. The IDL syntax is similar to the C++ or Java
syntax. The following table shows the correspondence between some of the
concepts common to three languages that can be specified through CORBA IDL:
The
inheritance concept is supported as well, using the colon operator as in C++.
The programmer writes an IDL description of the attributes, methods, and
interfaces that will be implemented and used by the server and clients. The IDL
is then compiled by a vendor-provided IDL/Java compiler, which reads the IDL
source and generates Java code.
The
IDL compiler is an extremely useful tool: it doesn’t just generate a Java
source equivalent of the IDL, it also generates the code that will be used to
marshal method arguments and to make remote calls. This code, called the stub
and skeleton code, is organized in multiple Java source files and is usually
part of the same Java package.
The
naming service
The
naming service is one of the fundamental CORBA services. A CORBA object is
accessed through a reference, a piece of information that’s not
meaningful for the human reader. But references can be assigned
programmer-defined, string names. This operation is known as
stringifying
the reference
,
and one of the OMA components, the Naming Service, is devoted to performing
string-to-object and object-to-string conversion and mapping. Since the Naming
Service acts as a telephone directory that both servers and clients can consult
and manipulate, it runs as a separate process. Creating an object-to-string
mapping is called
binding
an object
,
and removing the mapping is called
unbinding.
Getting an object reference passing a string is called
resolving
the name
. For
example, on startup, a server application could create a server object, bind
the object into the name service, and then wait for clients to make requests. A
client first obtains a server object reference, resolving the string name, and
then can make calls into the server using the reference.
Again,
the Naming Service specification is part of CORBA, but the application that
implements it is provided by the ORB vendor. The way you get access to the
Naming Service functionality can vary from vendor to vendor.
An
example
The
code shown here will not be elaborate because different ORBs have different
ways to access CORBA services, so examples are vendor specific. (The example
below uses JavaIDL, a free product from Sun that comes with a light-weight ORB,
a naming service, and a IDL-to-Java compiler.) In addition, since Java is young
and still evolving, not all CORBA features are present in the various
Java/CORBA products.
We
want to implement a server, running on some machine, that can be queried for
the exact time. We also want to implement a client that asks for the exact
time. In this case we’ll be implementing both programs in Java, but we
could also use two different languages (which often happens in real situations).
Writing
the IDL source
The
first step is to write an IDL description of the services provided. This is
usually done by the server programmer, who is then free to implement the server
in any language in which a CORBA IDL compiler exists. The IDL file is
distributed to the client side programmer and becomes the bridge between
languages.
The
example below shows the IDL description of our exact time server:
module RemoteTime {
interface ExactTime {
string getTime();
};
};
This
is a declaration of the
ExactTime
interface inside the
RemoteTime
namespace. The interface is made up of one single method the gives back the
current time in
string
format.
Creating
stubs and skeletons
The
second step is to compile the IDL to create the Java stub and skeleton code
that we’ll use for implementing the client and the server. The tool that
comes with the JavaIDL product is idltojava:
idltojava
–fserver –fclient RemoteTime.idl
The
two flags tell
idltojava
to generate code for both the stub and the skeleton.
Idltojava
generates a Java package named after the IDL module,
RemoteTime,
and the generated Java files are put in the
RemoteTime
subdirectory.
_ExactTimeImplBase.java
is
the skeleton that we’ll use to implement the server object, and
_ExactTimeStub.java
will be used for the client. There are Java representations of the IDL
interface in
ExactTime.java
and a couple of other support files used, for example, to facilitate access to
the naming service operations.
Implementing
the server and the client
Below
you can see the code for the server side. The server object implementation is
in the
ExactTimeServer
class. The
RemoteTimeServer
is the application that creates a server object, registers it with the ORB,
gives a name to the object reference, and then sits quietly waiting for client
requests.
import RemoteTime.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;
import java.util.*;
import java.text.*;
// Server object implementation
class ExactTimeServer extends _ExactTimeImplBase{
public String getTime(){
return DateFormat.
getTimeInstance(DateFormat.FULL).
format(new Date(
System.currentTimeMillis()));
}
}
// Remote application implementation
public class RemoteTimeServer {
public static void main(String args[]) {
try {
// ORB creation and initialization:
ORB orb = ORB.init(args, null);
// Create the server object and register it:
ExactTimeServer timeServerObjRef =
new ExactTimeServer();
orb.connect(timeServerObjRef);
// Get the root naming context:
org.omg.CORBA.Object objRef =
orb.resolve_initial_references(
"NameService");
NamingContext ncRef =
NamingContextHelper.narrow(objRef);
// Assign a string name to the
// object reference (binding):
NameComponent nc =
new NameComponent("ExactTime", "");
NameComponent path[] = {nc};
ncRef.rebind(path, timeServerObjRef);
// Wait for client requests:
java.lang.Object sync =
new java.lang.Object();
synchronized(sync){
sync.wait();
}
}
catch (Exception e) {
System.out.println(
"Remote Time server error: " + e);
e.printStackTrace(System.out);
}
}
}
As
you can see, implementing the server object is simple; it’s a regular
Java class that inherits from the skeleton code generated by the IDL compiler.
Things get a bit more complicated when it comes to interacting with the ORB and
other CORBA services.
Some
CORBA services
This
is a short description of what the JavaIDL-related code is doing (primarily
ignoring the part of the CORBA code that is vendor dependent). The first line in
main( )
starts up the ORB, and of course, this is because our server object will need
to interact with it. Right after the ORB initialization, a server object is
created. Actually, the right term would be a
transient
servant object
:
an object that receives requests from clients, and whose lifetime is the same
as the process that creates it. Once the transient servant object is created,
it is registered with the ORB, which means that the ORB knows of its existence
and can now forward requests to it.
Up
to this point, all we have is
timeServerObjRef,
an object reference that is known only inside the current server process. The
next step will be to assign a stringified name to this servant object; clients
will use that name to locate the servant object. We accomplish this operation
using the Naming Service. First, we need an object reference to the Naming
Service; the call to
resolve_initial_references( )
takes the stringified object reference of the Naming Service that is
“NameService,” in JavaIDL, and returns an object reference. This is
cast to a specific
NamingContext
reference using the
narrow( )
method. We can use now the naming services.
To
bind the servant object with a stringified object reference, we first create a
NameComponent
object, initialized with “ExactTime,” the name string we want to
bind to the servant object. Then, using the
rebind( )
method, the stringified reference is bound to the object reference. We use
rebind( )
to assign a reference, even if it already exists, whereas
bind( )
raises an exception if the reference already exists. A name is made up in CORBA
by a sequence of NameContexts – that’s why we use an array to bind
the name to the object reference.
The
servant object is finally ready for use by clients. At this point, the server
process enters a wait state. Again, this is because it is a transient servant,
so its lifetime is confined to the server process. JavaIDL does not currently
support persistent objects – objects that survive the execution of the
process that creates them.
Now
that we have an idea of what the server code is doing, let’s look at the
client code:
import RemoteTime.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;
public class RemoteTimeClient {
public static void main(String args[]) {
try {
// ORB creation and initialization:
ORB orb = ORB.init(args, null);
// Get the root naming context:
org.omg.CORBA.Object objRef =
orb.resolve_initial_references(
"NameService");
NamingContext ncRef =
NamingContextHelper.narrow(objRef);
// Get (resolve) the stringified object
// reference for the time server:
NameComponent nc =
new NameComponent("ExactTime", "");
NameComponent path[] = {nc};
ExactTime timeObjRef =
ExactTimeHelper.narrow(
ncRef.resolve(path));
// Make requests to the server object:
String exactTime = timeObjRef.getTime();
System.out.println(exactTime);
} catch (Exception e) {
System.out.println(
"Remote Time server error: " + e);
e.printStackTrace(System.out);
}
}
}
The
first few lines do the same as they do in the server process: the ORB is
initialized and a reference to the naming service server is resolved. Next, we
need an object reference for the servant object, so we pass the stringified
object reference to the
resolve( )
method, and we cast the result into an
ExactTime
interface reference using the
narrow( )
method. Finally, we call
getTime( ).
Activating
the name service process
Finally
we have a server and a client application ready to interoperate. You’ve
seen that both need the naming service to bind and resolve stringified object
references. You must start the naming service process before running either the
server or the client. In JavaIDL, the naming service is a Java application that
comes with the product package, but it can be different with other products.
The JavaIDL naming service runs inside an instance of the JVM and listens by
default to network port 900.
Activating
the server and the client
Now
you are ready to start your server and client application (in this order, since
our server is transient). If everything is set up correctly, what you’ll
get is a single output line on the client console window, giving you the
current time. Of course, this might be not very exciting by itself, but you
should take one thing into account: even if they are on the same physical
machine, the client and the server application are running inside different
virtual machines and they can communicate via an underlying integration layer,
the ORB and the Naming Service.
This
is a simple example, designed to work without a network, but an ORB is usually
configured for location transparency. When the server and the client are on
different machines, the ORB can resolve remote stringified references using a
component known as the
Implementation
Repository
.
Although the Implementation Repository is part of CORBA, there is almost no
specification, so it differs from vendor to vendor.
As
you can see, there is much more to CORBA than what has been covered here, but
you should get the basic idea. If you want more information about CORBA, the
place to start is the OMG Web site, at http://www.omg.org
http://www.omg.org.
There you’ll find documentation, white papers, proceedings, and
references to other CORBA sources and products.
Java
Applets and CORBA
Java
applets can act as CORBA clients. This way, an applet can access remote
information and services exposed as CORBA objects. But an applet can connect
only with the server from which it was downloaded, so all the CORBA objects the
applet interacts with must be on that server. This is the opposite of what
CORBA tries to do: give you complete location transparency.
This
is an issue of network security. If you’re on an Intranet, one solution
is to loosen the security restrictions on the browser. Or, set up a firewall
policy for connecting with external servers.
Some
Java ORB products offer proprietary solutions to this problem. For example,
some implement what is called HTTP Tunneling, while others have their special
firewall features.
This
is too complex a topic to be covered in an appendix, but it is definitely
something you should be aware of.
CORBA
vs. RMI
You
saw that one of the main CORBA features is RPC support, which allows your local
objects to call methods in remote objects. Of course, there already is a native
Java feature that does exactly the same thing: RMI (see Chapter 15). While RMI
makes RPC possible between Java objects, CORBA makes RPC possible between
objects implemented in any language. It’s a big difference.
However,
RMI
can be used to call services on remote, non-Java code. All you need is some
kind of wrapper Java object around the non-Java code on the server side. The
wrapper object connects externally to Java clients via RMI, and internally
connects to the non-Java code using one of the techniques shown above, such as
JNI or J/Direct.
This
approach requires you to write a kind of integration layer, which is exactly
what CORBA does for you, but then you don’t need a third-party ORB.