Now that we’ve seen how to implement a CORBA remote object and register it with an ORB, we can turn our attention to the heart of the matter: client applications that want to use the object. As we said earlier, every CORBA process needs a reference to an ORB. Once a client application has access to an ORB, next it must find remote objects to interact with. But before we can discuss finding remote objects, we need to talk a bit about what remote object references look like in the CORBA environment.
The whole point of CORBA is to be able to distribute objects across the network and use them from any point. In order for a local process to make requests of a remote object, it needs to have some kind of reference to that remote object. This object reference needs to contain enough information for the local ORB to find the ORB serving the target object and send the request to the remote ORB using an agreed-upon protocol.
In most situations, a CORBA client has a reference to a remote object in the form of an object stub. The stub encapsulates the actual object reference, providing what seems like a direct interface to the remote object within the local environment. If the client is implemented in C++, Java, or some other object-oriented language, the object stub is a native object in that language. Other nonobject languages represent remote object references in whatever way is dictated in the CORBA language binding for that language.
CORBA
includes its own root object class, because some object-programming
languages may have different inheritance structures. In the Java
binding for CORBA, all CORBA object references (local or remote)
implement the org.omg.CORBA.Object
interface.
So, when a client of a remote CORBA object receives a stub for the
object, it actually gets an org.omg.CORBA.Object
that serves as a proxy for the remote object. The
org.omg.CORBA.portable.ObjectImpl
class provides
default client-side implementations for the methods defined on
org.omg.CORBA.Object
. Java stubs (generated from
the IDL-to-Java compiler, as discussed earlier) for CORBA objects are
actually subclassed from the ObjectImpl
class.
Internally, ObjectImpl
deals with delegating
client requests on the object stub to the proper target object,
whether it’s a local or remote object.
ObjectImpl
implements the
org.omg.CORBA.Object
interface and inherently
extends the java.lang.Object
class, so it
provides a joining point between the CORBA and Java object
environments.[14]
A reference to an
org.omg.CORBA.Object
instance that is connected
to a remote object is actually all a client needs to invoke methods
on a remote object. Using the Dynamic Invocation Interface defined by
the CORBA standard, you can create method requests and send them to
the remote object directly through the generic
Object
interface, as we’ll
discuss later in this chapter. If your client has the actual Java
interface for the remote object available at compile time, however,
you probably want to convert the Object
reference into a reference of that type, so you can use the interface
to call remote methods directly.
Converting an
org.omg.COBRA.Object
to a specific remote
interface is done by narrowing the object reference to the
corresponding interface type, using type-specific helper classes.
We’ve already seen how the Java IDL compiler,
idlj, creates a helper class from an IDL
interface (e.g., AccountHelper
). The helper
class includes a narrow( )
method that safely
converts an org.omg.CORBA.Object
reference to a
reference of the given type. If the object reference you pass into
the narrow( )
method is not the type the helper
expects, an org.omg.CORBA.BAD_PARAM
exception is
thrown. This is a RuntimeException
, so it
doesn’t have to be caught by your code, unless
you’re trying to test the type of a CORBA reference
for some
reason.
This narrow( )
operation highlights one of the key differences between
RMI and CORBA. In the Java environment, class bytecodes are portable,
and all remote object types are objects that can be specified by
their full class names. An RMI client can automatically download the
bytecodes for a remote stub from the object server, if the class for
the stub can’t be found locally (see Chapter 3 for more details on the mechanics of remote
class loading). CORBA is a language-independent remote object scheme,
so there is no portable way to specify a remote
object’s native type when a client obtains a stub
reference. In Java you could do this using the same remote
classloading scheme as RMI, but there isn’t a
similar scheme available in C or C++, for example. As a result, the
stub reference is initially represented by a basic object type that
knows how to forward method requests to its server object, but
doesn’t necessarily satisfy the mapped interface for
the remote object type the client is expecting. The client
application is forced to “cast”
this stub to the correct local type, using the appropriate
narrow( )
method. In the Java mapping of IDL,
this means calling the narrow( )
method on the
corresponding helper class. The narrow( )
method
converts the reference, making a type-specific stub interface that
also includes the remote object reference information needed to
forward method requests to the actual object implementation.
With that background material out of the way, let’s discuss actually finding remote object references. There are many ways that an object reference can find its way through the ORB into a client application, but they all boil down to one of these methods:
Getting an initial reference directly from the ORB
Getting an object reference through a method call on another remote object reference
Using a stringified object reference obtained through a secondary channel and converting it to a live object reference
In addition to providing core object
communication services, an ORB can also provide additional services,
such as a Naming Service, a Trading Service, a Security Service, etc.
These services are represented as CORBA objects and are typically
available through the ORB directly, based on how it is configured.
The ORB interface provides the resolve_initial_references( )
method for obtaining references to the objects that
represent these services. Each CORBA service is represented by one or
more object interfaces, and these objects can be asked for using
standard names. As we saw earlier when we registered CORBA objects,
the standard name for the Naming Service is
“NameService,” and the object
representing access to the Naming Service is a
NamingContext
or
NamingContextExt
object. These represent the
root of the naming hierarchy.
Once
you’ve initialized your ORB reference (as described
earlier in Initializing the ORB), you can ask the ORB for a
list of the names of the initial objects it has available, using the
list_initial_references( )
method:
String names[] = myORB.list_initial_references( );
This method returns an array of
String
objects that contain the names of all
initial objects in the ORB. These names can then be used to get
references to the objects through the
resolve_initial_references( )
method.
Here’s how we used
resolve_initial_references( )
to obtain a
reference to the Naming Service in our earlier examples:
ORB myORB = ORB.init(...); org.omg.CORBA.Object nameRef = myORB.resolve_initial_references("NameService");
Although the list_initial_references( )
and
resolve_initial_references( )
methods are a
standard element of the ORB interface, how a particular ORB
implements these initial object references is not standardized.
Sun’s Java IDL implementation, for example, stores
an ORB’s initial object references as root objects
in its internal Naming Service.
In addition to getting remote objects directly from an ORB reference, a client can obtain remote objects from other remote objects. The most common variation on this approach is to get a reference to a Naming Service object and then look up objects in the naming directory by name. Another variation (that we won’t cover in detail in this section) is to obtain an application-specific object reference, either directly from the ORB or through the Naming Service, and use this initial reference to request other objects. An object used in this way in a distributed application is sometimes called a factory object.
Once you
have a NamingContext
reference that points to a
position (either the root or a subdirectory) in a Naming Service, you
can look up objects within the context by passing names to its
resolve( )
method. As before, when we were
binding objects, a name is represented by an ordered array of
NameComponent
objects. Each
NameComponent
(both the id
field and the kind
field) must exactly match the
path to an object within the context in order to successfully find
the object. If an object is not found at a specified name, an
org.omg.CosNaming.NamingContextPackage.NotFound
exception is thrown.
So, if a client wants to find the account for Mary Jones, we stored in the last binding example, which needs to do the following:
// Set up the full path to the account NameComponent comp1 = new NameComponent("bankBranches", ""); NameComponent comp2 = new NameComponent("Cambridge", ""); NameComponent acctName = new NameComponent("Jones,M", ""); NameComponent acctPath[] = { comp1, comp2, acctName }; // Find the object in the directory org.omg.CORBA.Object acctRef = rootNC.resolve(acctPath); Account acct = AccountHelper.narrow(acctRef);
We’re assuming that rootNC
is a
reference to the root context of the Naming Service holding the
Account
object references. Note the use of the
narrow( )
method on
AccountHelper
to
“cast” the generic object reference
to an Account
object.
In addition to finding references to
objects (leaf nodes) in the Naming Service, you can also use the
resolve( )
method on a
NamingContext
to get a reference to a subcontext
(a subdirectory). Just use the path to the context itself and
narrow( )
it to a
NamingContext
reference:
NameComponent cambridgePath[] = { comp1, comp2 }; org.omg.CORBA.Object ncRef = rootNC.resolve(cambridgePath); NamingContext cambridgeContext = NamingContextHelper.narrow(ncRef);
As we’ve seen, Sun’s implementation of Java IDL provides a nonstandard way to initialize an ORB to reference a remote Naming Service, so that one of the ORB’s initial references is to the root context of the remote Naming Service. But what do you do if you want an object from a remote Naming Service and your Java IDL implementation doesn’t provide a way to directly initialize a reference to the remote service? Or, worse yet, what if the object that you want isn’t stored in a Naming Service or available through any other CORBA service? How can your client get a reference to the object?
The CORBA standard comes to the rescue again. CORBA defines several ways to represent stringified object references, or remote object URLs. In other words, you can fully specify a specific CORBA server object using a URL syntax, which is very similar to the URL syntax used to specify Java RMI objects in an RMI registry (see Chapter 3 for more details on RMI registry URLs). These object URLs are called “stringified object references” in the CORBA specifications, because that’s exactly what they are: strings that can be sent around the network (in email, in configuration files, etc.), and used by clients to obtain a living reference to the object that the URL specifies. So these object URLs are called “stringified object references” in the same way that you could call HTML document URLs “stringified document references.”
In these URL schemes, the objects being referenced need to be hosted behind an ORB someplace on the network, and they must have a server process running that is listening for remote method requests for that object. In some cases; the objects and their request servers can just be connected directly to the ORB, in other cases they have to be bound to a name in a Naming Service, with the Naming Service acting as the method request mediator.
There are three basic forms of object URLs defined in the CORBA standards:
- Interoperable Object References (IORs)
These URLs are based on a syntax for representing a remote object reference in the form of a printable, but not human-readable, string of characters. This stringified object reference includes enough information for a remote CORBA client to locate the object’s home ORB and convert the string to a runtime stub reference to the object. Two methods on the
ORB
interface,object_to_string( )
andstring_to_object( )
, let you convert a CORBA object reference to an IOR and back into an object reference. An object doesn’t need to be bound to a name in a Naming Service in order to have an IOR; it simply needs to be exported through an ORB using an object adaptor, like the POA.- corbaloc URLs
These URLs are a human-readable URL format for object references. They are similar in purpose to IORs: an object needs to be exported through an ORB for remote access in order to have a corbaloc URL associated with it. So corbaloc URLs point to a single object accessible directly through an ORB, without the help of a service of some kind. The same restrictions apply for making servants accessible through corbaloc URLs as for IORs -- the servant object needs to be connected to an ORB through some kind of object adaptor, like the POA.
- corbaname URLs
These URLs are extensions to the corbaloc syntax to allow for references to objects that are registered with a CORBA Naming Service. The URL format includes additional information that specifies the object’s location (name) in the Naming Service.
The full syntax for all three of these CORBA object URL schemes is spelled out fully in Chapter 15. But the following examples should demonstrate their basic use.
Example 4-7 shows AccountInit
,
a server that creates an instance of our
AccountImplPOA
and makes it available for
clients to access. In its main( )
method, it
initializes the ORB, then creates an instance of our
Account
servant. We’re assuming
that a POA-compliant version of Java IDL is being used, so the next
thing to do is get a reference to the root POA (using
resolve_initial_references( )
) and activate the
POA manager, then ask the POA to activate our servant object. By
activating our servant object this way, we’re
guaranteed that any “direct” IIOP
requests (through either an IOR or a corbaloc URL) will be accepted
immediately. Note that in order for the generated IOR to be
acceptable to another ORB, both your ORB and the remote ORB have to
be using the same inter-ORB communication protocol (IIOP, DCE-CIOP,
etc.). In this example, we’ll assume that our client
and object server are both running IIOP.
Example 4-7. Initializing an Account/Getting Stringified References
// // AccountInit: Initialize an Account object, register it with a naming // service, and generate URLs for the object. // import org.omg.CORBA.*; import org.omg.CosNaming.*; import org.omg.PortableServer.POA; import java.net.InetAddress; public class AccountInit { public static void main(String[] args) { try { // Initialize an ORB reference ORB myORB = ORB.init(args, null); // Create an instance of an Account server implementation AccountImplPOA acct = new AccountImplPOA(args[0]); // Get the root Portable Object Adapter (POA) POA rootPOA = (POA)myORB.resolve_initial_references("RootPOA"); // Activate the POA manager rootPOA.the_POAManager().activate( ); // Activate our servant, so that corbaloc and IOR requests will work // immediately rootPOA.activate_object(acct); // Get an object reference from the implementation org.omg.CORBA.Object obj = rootPOA.servant_to_reference(acct); Account acctRef = (Account)AccountHelper.narrow(obj); // Get the root name context (use the INS interface, so that we can use // the simpler name construction process) org.omg.CORBA.Object objRef = myORB.resolve_initial_references("NameService"); NamingContextExt nc = NamingContextExtHelper.narrow(objRef); // Register the local object with the Name Service // Use the Interoperable Naming Service interface to simplify matters, // and to support URL-formatted names (e.g. "JohnS", // "corbaname://orbhost.com#JohnS", etc.) NameComponent[] name = nc.to_name(args[0]); nc.rebind(name, acctRef); System.out.println("Registered account under name " + args[0]); System.out.println("New account created and registered. URLs are: "); System.out.println("\nIOR"); System.out.println("\t" + myORB.object_to_string(acctRef)); System.out.println("\ncorbaname"); System.out.println("\tcorbaname:iiop:" + InetAddress.getLocalHost().getHostName( ) + "#" + args[0]); // Go into a wait state, waiting for clients to connect myORB.run( ); } catch (Exception e) { System.out.println( "An error occurred while initializing server object:"); e.printStackTrace( ); } } }
Next, we ask the POA to generate an Account
reference for us from the servant object; we use this reference to
register the Account
with the Naming Service.
The request to convert the reference to an
Account
reference is necessary in POA-compliant
environments, because the object implementation class
doesn’t directly extend the mapped object interface
(in POA server skeletons we saw how the generated
skeleton extends the AccountOperations
interface,
not the Account
interface).
Now that the server object is both connected to the ORB through the
POA and registered with a Naming Service, we can generate various
URLs for this object that clients can use to get direct references to
the object. First we get the IOR of the object, using the
object_to_string( )
method on the ORB. Then we
create the corbaname URL for the object, using the local hostname,
the port number of the ORB, and the name the user passed in on the
command line and used to register the object with the Naming Service.
Each of these is printed to the console, and we can distribute these
URLs to the clients that need them. Note that we
aren’t creating a corbaloc URL for our object. This
is because the server-side creation of corbaloc URLs is not
standardized; CORBA vendors currently provide nonstandard ways to
generate these in their implementations.
Notice we’re only showing how to initialize the server object in a POA-compliant environment. In the pre-POA versions of Java IDL, instead of activating the object using the POA interface, you would simply “connect” the object to the local ORB:
Account acctRef = new AccountImplPrePOA(...); myORB.connect(acctRef);
You also wouldn’t need to perform the conversion
into an Account
reference, since in the
ImplBase
inheritance model, the server skeleton
_AccountImplBase
directly extends the generated
Account
interface, and can be used to register
the object with the Naming Service.
To demonstrate using these URLs, Example 4-8 shows a
simple client for our Account
server. This
client uses a command-line interface and allows the user to perform
transactions on a given Account
, by specifying
the Account
to use (using its object URL), a
transaction type (“deposit”,
“withdrawal” or
“balance”), and an optional amount
(for deposits and withdrawals). After doing the standard ORB
initialization, the client first checks the name of the
Account
object (the first command-line
argument). If the name of the Account
is an
object URL (IOR, corbalo, or corbaname), then the client simply uses
the string_to_object( )
method on the ORB to get
a reference to the specified Account
object, and
narrows the returned Object
to an
Account
reference using the
AccountHelper
. If some other string is passed
in, the client assumes the string is a name to be used to do a lookup
in the Naming Service accessible from the ORB, and performs the
lookup using the standard Naming Service operations (described
earlier). Once the client has the reference to
the remote Account
object, the rest is
easy -- just parse the remaining command-line options given by the
user, and perform the requested transaction on the account.
Example 4-8. A Client for the Account Servant
import org.omg.CORBA.*; import org.omg.CORBA.ORBPackage.*; import org.omg.CosNaming.*; import java.util.*; /** * AccountClient: A client that looks up the account "named" on the * command-line, then performs the requested transaction on the account. */ public class AccountClient { public static void main(String args[]) { // Initialize the ORB ORB orb = ORB.init(args, null); org.omg.CORBA.Object ref = null; // The object name passed in on the command line String name = args[0]; Account acct = null; // See if a stringified object reference/URL was provided if (name.startsWith("corbaname") || name.startsWith("corbaloc") || name.startsWith("IOR")) { System.out.println("Attempting to lookup " + args[0]); ref = orb.string_to_object(args[0]); acct = AccountHelper.narrow(ref); } // Otherwise, do a traditional Naming Service lookup using the // services being referenced by our local ORB else { try { ref = orb.resolve_initial_references("NameService"); } catch (InvalidName invN) { System.out.println("Couldn't locate a Naming Service"); System.exit(1); } NamingContext nameContext = NamingContextHelper.narrow(ref); NameComponent comp = new NameComponent(args[0], ""); NameComponent path[] = {comp}; try { ref = nameContext.resolve(path); System.out.println("ref = " + ref); acct = AccountHelper.narrow(ref); } catch (Exception e) { System.out.println("Error resolving name against Naming Service"); e.printStackTrace( ); } } if (acct != null) { // We managed to get a reference to the named account, now check the // requested transaction from the command-line String action = args[1]; float amt = 0.0f; if (action.equals("deposit") || action.equals("withdrawal")) { amt = Float.parseFloat(args[2]); } System.out.println("Got account, performing transaction..."); try { // Did user ask to do a deposit? if (action.equals("deposit")) { acct.deposit(amt); System.out.println("Deposited " + amt + " to account."); System.out.println("New balance = " + acct.getBalance( )); } // Did user ask to do a withdrawal? else if (action.equals("withdrawal")) { acct.withdraw(amt); System.out.println("Withdrew " + amt + " from account."); System.out.println("New balance = " + acct.getBalance( )); } // Assume a balance inquiry if no deposit or withdrawal was given else { System.out.println( "Current account balance = " + acct.getBalance( )); } } catch (InsufficientFundsException ife) { System.out.println("Insufficient funds for transaction."); System.out.println("Current account balance = " + acct.getBalance( )); } catch (Exception e) { System.out.println("Error occurred while performing transaction:"); e.printStackTrace( ); } } else { System.out.println("Null account returned."); System.exit(1); } } }
To run this example, we first have to have a Naming Service running
somewhere, then initialize an Account
object for
the client to use:
orbhost% tnameserv & Initial Naming Context: IOR:000000000000002b49444c3a6f6d672e6f72672f436f734e616d696e672f4e616d696 e67436f6e746578744578743a312e30000000000001000000000000007c00010200000000 0a3132372e302e302e3100041a00000035afabcb0000000020363e1ad7000000010000000 0000000010000000d544e616d655365727669636500000000000000040000000003000000 0000000100000001000000200000000000010001000000020501000100010020000101090 000000100010100 TransientNameServer: setting port for initial object references to: 900 Ready. orbhost% java oreilly.jent.corba.AccountInit JimF Registered account under name JimF New account created and registered. URLs are: IOR IOR:000000000000002349444c3a6f7265696c6c792f6a656e742f636f7262612f4163636 f756e743a312e300000000000010000000000000068000102000000000a3132372e302e30 2e3100046600000021afabcb0000000020363f810b0000000100000000000000000000000 4000000000300000000000001000000010000002000000000000100010000000205010001 0001002000101090000000100010100 corbaname corbaname:iiop:orbhost#JimF
To perform transactions on this newly
created and registered Account
object, we run
the AccountClient
, passing in one of these
object URLs as the “name” of the
Account
. Here we’ll use the
corbaname URL.
myclient% java oreilly.jent.corba.AccountClientcorbaname:iiop:orbhost#JimF Attempting to lookup corbaname:iiop:orbhost#JimF Got account, performing transaction... Deposited 1000.0 to account. New balance = 1000.0
[14] In pre-POA versions of Java IDL (JDK
1.2 and 1.3), the server skeletons generated for CORBA interfaces
were also subclassed from ObjectImpl
. In
POA-compliant versions, the skeletons are subclassed from
org.omg.PortableServer.Servant
, which provides
an alternative implementation of the
org.omg.CORBA.Object
interface, as well as a
number of methods specific to servant objects.
Get Java Enterprise in a Nutshell, Second Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.