Now that you understand the various parts of the CORBA architecture, let's walk through the creation of CORBA objects using Java IDL. In order to distribute a Java object over the network using CORBA, you have to define your own CORBA-enabled interface and it implementation. This involves doing the following:
Writing an interface in the CORBA Interface Definition Language
Generating a Java base interface, plus a Java stub and skeleton class, using an IDL-to-Java compiler
Writing a server-side implementation of the Java interface in Java
This section provides a quick overview of writing a CORBA interface in IDL. A full reference on IDL syntax is provided in Chapter 10, "IDL Reference", if you need more details.
The syntax of both Java and IDL were modeled to some extent on C++, so there are a lot of similarities between the two in terms of syntax. Interfaces in IDL are declared much like classes in C++ and, thus, classes or interfaces in Java. The major differences between IDL and Java are:
IDL is a declaration language. In IDL, you declare only the names and types for interfaces, data members, methods, method parameters, etc. Method implementations are created in the implementation language you choose (in this case Java), after you've used an IDL compiler to convert your IDL interface to your target language.
IDL, like C++, includes non-class data structure definitions, like structs, unions, and enumerations.
Method parameters in IDL include modifiers that specify whether they are input, output, or input/output variables. In Java, all primitive data types are passed by value, and all object data types are passed by reference.
An IDL file can include multiple public interfaces. Java allows multiple inner classes within a single public class definition and multiple nonpublic classes per file, but only a single public class can be defined in a given Java file.
Modules, which are similar to Java packages, can be nested within other modules in the same IDL file, and interfaces in multiple distinct modules can be defined in the same IDL file. In Java, you can define a class only within a single package in a single Java file.
Modules are declared in IDL using the module keyword, followed by a name for the module and an opening brace that starts the module scope. Everything defined within the scope of this module (interfaces, constants, other modules) falls within the module and is referenced in other IDL modules using the syntax modulename::x. Suppose that you want all your classes to be contained in a module called corba, which is part of a larger module called jen (an acronym for the title of this book). In IDL this is declared as follows:
// IDL module jen { module corba { interface NeatExample ... }; };
If you want to reference the NeatExample interface in other IDL files, use the syntax jen::corba::NeatExample, which may look familiar to readers who have done C++ programming. Java programmers should note the semicolons following the closing braces on the module definitions, which are required in IDL but not in Java. A semicolon is also required after the close of an interface definition.
Interfaces declared in IDL are mapped into classes or interfaces in Java. As I mentioned before, IDL is used only to declare modules, interfaces, and their methods. Methods on IDL interfaces are always left abstract, to be defined in the programming language you use to implement the interfaces.
The declaration of an interface includes an interface header and an interface body. The header specifies the name of the interface and the interfaces it inherits from (if any). Here is an IDL interface header:
interface PrintServer : Server { ...
This header starts the declaration of an interface called PrintServer that inherits all the methods and data members from the Server interface. An IDL interface can inherit from multiple interfaces; simply separate the interface names with commas in the inheritance part of the header.
The interface body declares all the data members (or attributes) and methods of an interface. Data members are declared using the attribute keyword. At a minimum, the declaration includes a name and a type (see Chapter 10, "IDL Reference" for a complete list of the basic data types available in IDL and the mapping to Java types). The declaration can optionally specify whether the attribute is read-only or not, using the readonly keyword. By default, every attribute you declare is readable and writable (for Java, this means that the IDL compiler generates public read and write methods for it). Here is an example declaration for a read-only string attribute:
readonly attribute string myString;
You declare a method by specifying its name, return type, and parameters, at a minimum. You can also optionally declare exceptions the method might raise, the invocation semantics of the method, and the context for the method call (see Chapter 10, "IDL Reference" for more details). Here is the declaration for a simple method that returns a string:
string parseString(in string buffer);
This declares a method called parseString() that accepts a single string argument and returns a string value.
Now let's tie all these basic elements together. Here's a complete IDL example that declares a module within another module, which itself contains several interfaces:
module OS { module services { interface Server { readonly attribute string serverName; boolean init(in string sName); }; interface Printable { boolean print(in string header); }; interface PrintServer : Server { boolean printThis(in Printable p); }; }; };
The first interface, Server, has a single read-only string attribute and an init() method that accepts a string and returns a boolean. The Printable interface has a single print() method that accepts a string header. Finally, the PrintServer interface extends the Server interface (hence inheriting all its methods and attributes) and adds a printThis() method that accepts a Printable object and returns a boolean. In all cases, we've declared our method arguments as input-only (i.e., pass-by-value), using the in keyword.
Once you've described your remote interfaces in IDL, you need to generate Java classes that act as a starting point for implementing those remote interfaces in Java using an IDL-to-Java compiler. Every standard IDL-to-Java compiler generates the following Java classes from an IDL interface:
A Java interface with the same name as the IDL interface. This can act as the basis for a Java implementation of the interface (but you have to write it, since IDL doesn't provide any details about method implementations).
A helper class whose name is the name of the IDL interface with "Helper" appended to it (e.g., ServerHelper). The primary purpose of this class is to provide a static narrow() method that can safely cast CORBA Object references to the Java interface type. The helper class also provides other useful static methods, such as read() and write() methods that allow you to read and write an object of the corresponding type using I/O streams.
A holder class whose name is the name of the IDL interface with "Holder" appended to it (e.g., ServerHolder). This class is used when objects with this interface are used as out or inout arguments in remote CORBA methods. Instead of being passed directly into the remote method, the object is wrapped with its holder before being passed. When a remote method has parameters that are declared as out or inout, the method has to be able to update the argument it is passed and return the updated value. The only way to guarantee this, even for primitive Java data types, is to force out and inout arguments to be wrapped in Java holder classes, which are filled with the output value of the argument when the method returns.
The idltojava tool provided by Sun[3] can also generate two other classes:
[3] Although Java IDL is a standard part of Java 1.2, Sun still offers only the early-access version of its idltojava compiler, which you have to download separately from http://developer.java.sun.com/developer/earlyAccess/jdk12/idltojava.html.
A client stub class, called _interface-nameStub, that acts as a client-side implementation of the interface and knows how to convert method requests into ORB requests that are forwarded to the actual remote object. The stub class for an interface named Server is called _ServerStub.
A server skeleton class, called _interface-nameImplBase, that is a base class for a server-side implementation of the interface. The base class can accept requests for the object from the ORB and channel return values back through the ORB to the remote client. The skeleton class for an interface named Server is called _ServerImplBase.
So, in addition to generating a Java mapping of the IDL interface and some helper classes for the Java interface, the idltojava compiler also creates subclasses that act as an interface between a CORBA client and the ORB and between the server-side implementation and the ORB. Chapter 12, "Java IDL Tools", provides a complete reference for Sun's idltojava compiler. We use this IDL-to-Java tool in the examples in this chapter. Remember, though, that any Java mapping of the CORBA standard should include its own IDL-to-Java compiler to generate these Java classes from the IDL interfaces you write. In addition, the Java that these tools generate should be compliant with the standard IDL mapping for Java, published by the OMG in the CORBA standard.
The IDL interface shown in Example 4-1 is the IDL equivalent of the Java class we defined in Example 4-3 in the RMI chapter. The interface, named ThisOrThatServer, declares two methods, doThis() and doThat(). As in the earlier RMI example, each method accepts a string that specifies what to do and returns a string that indicates what was done. Since this is IDL, the string data type is string, and the parameters are declared as in arguments, since we want them to be passed into the remote method by value.
interface ThisOrThatServer { string doThis(in string what); string doThat(in string what); };
We can run the idltojava compiler on this IDL interface using the following command line (Windows version):
D:\>idltojava -fno-cpp ThisOrThatServer.idl
This command creates the five Java classes I just described: a Java version of the interface, a helper class, a holder class, a client stub, and a server skeleton. I had to use the -fno-cpp option on my machine because I don't have a C preprocessor installed for idltojava to use; this option tells the IDL compiler to use an alternate parsing scheme while it converts the IDL to Java (see Chapter 12, "Java IDL Tools" for complete details on the command-line arguments for idltojava).
The compiler creates the Java interface shown in Example 4-2, in a file named ThisOrThatServer.java. The mapping is fairly straightforward for this simple example. The interface declaration is mapped directly to a Java interface declaration, with the interface extending the org.omg.CORBA.Object interface. If we had included any module definitions in our IDL specification, they would have been mapped into a package statement at the beginning of the Java file. The IDL string type is converted into the Java String type, and, since they don't require any special handling in a remote method call, the in method parameters in IDL are mapped into regular Java input arguments.
/* * File: ./THISORTHATSERVER.JAVA * From: THISORTHATSERVER.IDL * Date: Thu Apr 15 21:42:40 1999 * By: C:\JDK12~1.1\BIN\IDLTOJ~1.EXE Java IDL 1.2 Aug 18 1998 16:25:34 */ public interface ThisOrThatServer extends org.omg.CORBA.Object { String doThis(String what) ; String doThat(String what) ; }
You might notice that the IDL compiler has put the semicolons following the method declarations on separate lines. To my knowledge, there's no good reason for this; it's just a quirk of the idltojava tool provided by Sun.
The compiler also generates a helper class, called ThisOrThatServerHelper, as shown in Example 4-3. As I mentioned earlier, the helper class has methods that let you read and write ThisOrThatServer objects to and from CORBA I/O streams, get the TypeCode for a ThisOrThatServer object, and, most importantly, safely narrow a CORBA Object reference into a ThisOrThatServer reference.
/* * File: ./THISORTHATSERVERHELPER.JAVA * From: THISORTHATSERVER.IDL * Date: Thu Apr 15 21:42:40 1999 * By: C:\JDK12~1.1\BIN\IDLTOJ~1.EXE Java IDL 1.2 Aug 18 1998 16:25:34 */ public class ThisOrThatServerHelper { // It is useless to have instances of this class private ThisOrThatServerHelper() { } public static void write(org.omg.CORBA.portable.OutputStream out, ThisOrThatServer that) { out.write_Object(that); } public static ThisOrThatServer read(org.omg.CORBA.portable.InputStream in) { return ThisOrThatServerHelper.narrow(in.read_Object()); } public static ThisOrThatServer extract(org.omg.CORBA.Any a) { org.omg.CORBA.portable.InputStream in = a.create_input_stream(); return read(in); } public static void insert(org.omg.CORBA.Any a, ThisOrThatServer that) { org.omg.CORBA.portable.OutputStream out = a.create_output_stream(); write(out, that); a.read_value(out.create_input_stream(), type()); } private static org.omg.CORBA.TypeCode _tc; synchronized public static org.omg.CORBA.TypeCode type() { if (_tc == null) _tc = org.omg.CORBA.ORB.init().create_interface_tc(id(), "ThisOrThatServer"); return _tc; } public static String id() { return "IDL:ThisOrThatServer:1.0"; } public static ThisOrThatServer narrow(org.omg.CORBA.Object that) throws org.omg.CORBA.BAD_PARAM { if (that == null) return null; if (that instanceof ThisOrThatServer) return (ThisOrThatServer) that; if (!that._is_a(id())) { throw new org.omg.CORBA.BAD_PARAM(); } org.omg.CORBA.portable.Delegate dup = ((org.omg.CORBA.portable.ObjectImpl)that)._get_delegate(); ThisOrThatServer result = new _ThisOrThatServerStub(dup); return result; } }
In the implementation of the narrow() method, we can see how the helper class converts a CORBA Object reference to a reference to a specific type. First, the narrow() method checks to see if the Object parameter is already a ThisOrThatServer object (using the Java instanceof operator), then it checks to see if the object passed in is a null pointer. If neither case is true, the Object should contain a delegate of a ThisOrThatServer object. Every CORBA stub for a remote object contains an internal Delegate object (from the org.omg.CORBA.portable package) that's used by the stub to invoke remote requests. If the object's delegate is a ThisOrThatServer (checked using the objects's _is_a() method), the delegate is used to create a new ThisOrThatServer stub. We'll take a look at the ThisOrThatServer stub class in a bit. If the object doesn't contain a delegate, the is_a() method returns false, and the narrow() method throws a BAD_PARAM exception.
The compiler generates a holder class for the ThisOrThatServer class, as shown in Example 4-4. The holder class, called ThisOrThatServerHolder, is a wrapper used when ThisOrThatServer objects are called for as out or inout arguments in an IDL method. All holder classes implement the Streamable interface from the org.omg.CORBA.portable package. An ORB knows to pass Streamable objects in method calls using the _read() and _write() methods of the Streamable object; these methods handle whatever serialization the object needs.
/* * File: ./THISORTHATSERVERHOLDER.JAVA * From: THISORTHATSERVER.IDL * Date: Thu Apr 15 21:42:40 1999 * By: C:\JDK12~1.1\BIN\IDLTOJ~1.EXE Java IDL 1.2 Aug 18 1998 16:25:34 */ public final class ThisOrThatServerHolder implements org.omg.CORBA.portable.Streamable{ // instance variable public ThisOrThatServer value; // constructors public ThisOrThatServerHolder() { this(null); } public ThisOrThatServerHolder(ThisOrThatServer __arg) { value = __arg; } public void _write(org.omg.CORBA.portable.OutputStream out) { ThisOrThatServerHelper.write(out, value); } public void _read(org.omg.CORBA.portable.InputStream in) { value = ThisOrThatServerHelper.read(in); } public org.omg.CORBA.TypeCode _type() { return ThisOrThatServerHelper.type(); } }
A holder contains a single instance of a CORBA object (a ThisOrThatServer, in this example). When a holder object is passed into a remote method call as an inout argument, its _write() method is invoked. This method takes the object contained by the holder class, serializes it, and streams it through the ORB to the remote object server. When the remote method call returns, the holder's _read() method is invoked to read the (possibly updated) object from the remote object server, and the holder object replaces its internal value with the updated object.
As an example of using the holder class, let's define another IDL interface that includes a method that uses a ThisOrThatServer as an inout parameter:
// IDL interface ServerManager { boolean updateServer(inout ThisOrThatServer server); };
The Java interface generated from this IDL interface uses the holder class for the ThisOrThatServer as the type for the corresponding Java method parameter:
// Java public interface ServerManager extends org.omg.CORBA.Object { boolean updateServer(ThisOrThatServerHolder server) ; }
The ThisOrThatServerHolder class has public constructors that let you create a holder from an existing ThisOrThatServer object, so that you can easily pass the object into this kind of method.
The idltojava compiler generates two more classes from our interface definition: a client stub (_ThisOrThatServerStub) and a base class for a server implementation (_ThisOrThatServerImplBase). The client stub, shown in Example 4-5, implements the generated ThisOrThatServer Java interface and acts as a client-side proxy for a remote ThisOrThatServer object. The stub has implementations of the doThis() and doThat() methods from the interface. Each implementation just generates a request to the ORB to make a remote method call on the server-side object that this stub is a proxy for. The method arguments are bundled up and passed along with the request to the ORB. I'm not going to go into the details of the stub's method implementations because you shouldn't have to worry much about them, but it is enlightening to look at the source code to see how your remote objects do what they do in detail, using the core CORBA functions.
/* * File: ./_THISORTHATSERVERSTUB.JAVA * From: THISORTHATSERVER.IDL * Date: Thu Apr 15 21:42:40 1999 * By: C:\JDK12~1.1\BIN\IDLTOJ~1.EXE Java IDL 1.2 Aug 18 1998 16:25:34 */ public class _ThisOrThatServerStub extends org.omg.CORBA.portable.ObjectImpl implements ThisOrThatServer { public _ThisOrThatServerStub(org.omg.CORBA.portable.Delegate d) { super(); _set_delegate(d); } private static final String _type_ids[] = { "IDL:ThisOrThatServer:1.0" }; public String[] _ids() { return (String[]) _type_ids.clone(); } // IDL operations // Implementation of ::ThisOrThatServer::doThis public String doThis(String what) { org.omg.CORBA.Request r = _request("doThis"); r.set_return_type(org.omg.CORBA.ORB.init().get_primitive_tc( org.omg.CORBA.TCKind.tk_string)); org.omg.CORBA.Any _what = r.add_in_arg(); _what.insert_string(what); r.invoke(); String __result; __result = r.return_value().extract_string(); return __result; } // Implementation of ::ThisOrThatServer::doThat public String doThat(String what) { org.omg.CORBA.Request r = _request("doThat"); r.set_return_type(org.omg.CORBA.ORB.init().get_primitive_tc( org.omg.CORBA.TCKind.tk_string)); org.omg.CORBA.Any _what = r.add_in_arg(); _what.insert_string(what); r.invoke(); String __result; __result = r.return_value().extract_string(); return __result; } };
When a Java client gets a reference to a remote ThisOrThatServer object, it is given one of these stub objects. The client can make method calls on the stub object, and the stub converts these calls into corresponding requests to the ORB to invoke the methods on the remote object and send back the results.
The base class for the server implementation, shown in Example 4-6, accepts requests that are intended for the server implementation from the ORB. The base class converts a request into a method call on the server object and then takes the result of the call and gives it back to the ORB to send to the client stub. All this work is done in the server skeleton's invoke() method. The invoke() method figures out which method is being called, unpacks the method arguments (if any) from the request, and calls the method directly on itself.
Note that the server skeleton doesn't have implementations of the doThis() or doThat() methods declared in the interface. The idltojava compiler doesn't do everything for you; you still need to create a server implementation for your interface.
/* * File: ./_THISORTHATSERVERIMPLBASE.JAVA * From: THISORTHATSERVER.IDL * Date: Thu Apr 15 21:42:40 1999 * By: C:\JDK12~1.1\BIN\IDLTOJ~1.EXE Java IDL 1.2 Aug 18 1998 16:25:34 */ public abstract class _ThisOrThatServerImplBase extends org.omg.CORBA.DynamicImplementation implements ThisOrThatServer { // Constructor public _ThisOrThatServerImplBase() { super(); } // Type strings for this class and its superclasses private static final String _type_ids[] = { "IDL:ThisOrThatServer:1.0" }; public String[] _ids() { return (String[]) _type_ids.clone(); } private static java.util.Dictionary _methods = new java.util.Hashtable(); static { _methods.put("doThis", new java.lang.Integer(0)); _methods.put("doThat", new java.lang.Integer(1)); } // DSI Dispatch call public void invoke(org.omg.CORBA.ServerRequest r) { switch (((java.lang.Integer) _methods.get(r.op_name())).intValue()) { case 0: // ThisOrThatServer.doThis { org.omg.CORBA.NVList _list = _orb().create_list(0); org.omg.CORBA.Any _what = _orb().create_any(); _what.type(org.omg.CORBA.ORB.init().get_primitive_tc( org.omg.CORBA.TCKind.tk_string)); _list.add_value("what", _what, org.omg.CORBA.ARG_IN.value); r.params(_list); String what; what = _what.extract_string(); String ___result; ___result = this.doThis(what); org.omg.CORBA.Any __result = _orb().create_any(); __result.insert_string(___result); r.result(__result); } break; case 1: // ThisOrThatServer.doThat { org.omg.CORBA.NVList _list = _orb().create_list(0); org.omg.CORBA.Any _what = _orb().create_any(); _what.type(org.omg.CORBA.ORB.init().get_primitive_tc( org.omg.CORBA.TCKind.tk_string)); _list.add_value("what", _what, org.omg.CORBA.ARG_IN.value); r.params(_list); String what; what = _what.extract_string(); String ___result; ___result = this.doThat(what); org.omg.CORBA.Any __result = _orb().create_any(); __result.insert_string(___result); r.result(__result); } break; default: throw new org.omg.CORBA.BAD_OPERATION(0, org.omg.CORBA.CompletionStatus.COMPLETED_MAYBE); } } }
So, we've written an IDL interface and generated the Java interface and support classes for it, including the client stub and the server skeleton. Now we need to create concrete server-side implementations of all of the methods on your interface. We do this by subclassing from the _xxxImplBase class generated by the idltojava compiler. For our example, we need to subclass _ThisOrThatServerImplBase and implement the doThis() and doThat() methods. The ThisOrThatServerImpl class in Example 4-7 does just that. Note that we've mimicked the method implementations from the RMI example in Chapter 3, "Remote Method Invocation". The only real difference is that this ThisOrThatServerImpl class extends _ThisOrThatServerImplBase, while the one in Chapter 3, "Remote Method Invocation" extends the UnicastRemoteObject.
public class ThisOrThatServerImpl extends _ThisOrThatServerImplBase { public ThisOrThatServerImpl() {} // Remotely-accessible methods public String doThis(String what) { return doSomething("this", what); } public String doThat(String what) { return doSomething("that", what); } // Non-remote methods private String doSomething(String todo, String what) { String result = todo + " " + what + " is done."; System.out.println("Did " + todo + " to " + what); return result; } }
Copyright © 2001 O'Reilly & Associates. All rights reserved.
This HTML Help has been published using the chm2web software. |