CORBA
CORBA
In large, distributed applications, your needs might
not be satisfied by the preceding approaches. For example, you might want to
interface with legacy data stores, 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 CORBA services), 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:
|
CORBA IDL
|
Java
|
C++
|
|
Module
|
Package
|
Namespace
|
|
Interface
|
Interface
|
Pure abstract class
|
|
Method
|
Method
|
Member function
|
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 are 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 an 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
ExactTime server:
//:
c15:corba:ExactTime.idl
//#
You must install idltojava.exe from
//#
java.sun.com and adjust the settings to use
//#
your local C preprocessor in order to compile
//#
This file. See docs at java.sun.com.
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 that 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
remotetime.idl
This will automatically 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.
//:
c15:corba:RemoteTimeServer.java
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 {
// Throw exceptions to
console:
public
static
void
main(String[] args)
throws
Exception {
// 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();
}
}
}
///:~
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:
//:
c15:corba:RemoteTimeClient.java
import
remotetime.*;
import
org.omg.CosNaming.*;
import
org.omg.CORBA.*;
public
class
RemoteTimeClient {
// Throw exceptions to
console:
public
static
void
main(String[] args)
throws
Exception {
// 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);
}
}
///:~
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
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
|