CORBA Example Description of the example This example illustrates the basic tasks in building a CORBA distributed application using Java IDL. You will build the classic Hello World program as a distributed application. The Hello World program has a single operation that returns a string to be printed. 1.The client invokes the sayHello method of the HelloServer. 2.The ORB transfers that invocation to the servant object registered for that IDL interface. 3.The servant's sayHello method runs, returning a Java String. 4.The ORB transfers that String back to the client. 5.The client prints the value of the String. Getting started You will need two things: version 1.2 of the JDK software and the idltojava compiler. The JDK provides the API and ORB needed to enable CORBA-based distributed object interaction. The idltojava compiler uses the IDL-to-Java mapping to convert IDL interface definitions to corresponding Java interfaces, classes, and methods, which you can then use to implement your client and server code. Writing the IDL Interface In this section, you will write a simple IDL interface for the Hello World program. The IDL interface defines the contract between the client and server parts of your application, specifying what operations and attributes are available. OMG IDL is programminglanguage independent. You must map it to Java before writing any of the implementation code. (Running idltojava on the IDL file does this for you automatically.) Here's the complete Hello.idl file: module HelloApp { interface Hello { string sayHello(); }; }; OMG IDL is a purely declarative language designed for specifying programminglanguage-independent operational interfaces for distributed applications. OMG specifies a mapping from IDL to several different programming languages, including C, C++, Smalltalk, COBOL, Ada, and Java. When mapped, each statement in OMG IDL is translated to a corresponding statement in the programming language of choice. You can use the tool idltojava to map an IDL interface to Java and implement the client class. When you map the same IDL to C++ and implement the server in that language, the Java client and C++ server interoperate through the ORB as though they were written in the same language. Steps needed to write the IDL for the Hello World application Step 1: Declaring the CORBA IDL Module A CORBA module is a namespace that acts as a container for related interfaces and declarations. It corresponds closely to a Java package. Each module statement in an IDL file is mapped to a Java package statement. module HelloApp { // Add subsequent lines of code here. }; Step 2: Declaring the Interface Like Java interfaces, CORBA interfaces declare the API contract that an object has with other objects. Each interface statement in the IDL maps to a Java interface statement when mapped. In your Hello.idl file, enter the interface statement: module HelloApp { interface Hello // Add { // these // four }; // lines. }; When you compile the IDL, this statement will generate an interface statement in the Java code. Your client and server classes will implement the Hello interface in different ways. Step 3: Declaring the Operations CORBA operations are the behavior that servers promise to perform on behalf of clients that invoke them. Each operation statement in the IDL generates a corresponding method statement in the generated Java interface. In your Hello.idl file, enter the operation statement: module HelloApp { interface Hello { string sayHello(); // Add this line. }; // notice the semicolon }; //notice the semicolon Because our little Hello World application has only a single operation, Hello.idl is now complete. Mapping Hello.idl from IDL to Java The tool idltojava reads OMG IDL files and creates the required Java files. The idltojava defaults are set up so that if you need both client and server files (as you do for our Hello World program), you simply enter the tool name and the name of your IDL file: 1.Go to a command line prompt. 2.Change directory to the location of your Hello.idl file. 3.Enter the compiler command: idltojava Hello.idl If you list the contents of the directory, you will see that a directory called HelloApp has been created and that it contains five files. Open Hello.java in your text editor. It looks like this: /* Hello.java as generated by idltojava */ package HelloApp; public interface Hello extends org.omg.CORBA.Object { String sayHello(); } With an interface this simple, it is easy to see how the IDL statements map to the generated Java statements. Mapping of IDL Statements to Java Statements IDL Statement Java Statement module HelloApp package HelloApp; interface Hello public interface Hello string sayHello(); String sayHello(); The single surprising item is the extends statement. All CORBA objects are derived from org.omg.CORBA.Object to ensure required CORBA functionality. The required code is generated by idltojava; you do not need to do any mapping yourself. Understanding the idltojava Compiler Output The idltojava compiler generates a number of files, based on the options chosen on the command line. Because these provide standard functionality, you can ignore them until it is time to deploy and run your program. The five files generated by idltojava are: _HelloImplBase.java This abstract class is the server skeleton, providing basic CORBA functionality for the server. It implements the Hello.java interface. The server class HelloServant extends _HelloImplBase. _HelloStub.java This class is the client stub, providing CORBA functionality for the client. It implements the Hello.java interface. Hello.java This interface contains the Java version of our IDL interface. It contains the single method sayHello. The Hello.java interface extends org.omg.CORBA.Object, providing standard CORBA object functionality as well. HelloHelper.java This final class provides auxiliary functionality, notably the narrow method required to cast CORBA object references to their proper types. This narrow() operation highlights one of the differences between RMI and CORBA. An RMI client can automatically download the class bytecodes for a remote stub reference from the object server, if the class for the stub object cannot be found locally. CORBA is a language-independent remote object scheme, so there is no portable way to specify a remote object’s type when a client obtains a stub reference. So the stub reference is initially represented a basic ObjectImpl object that knows how to forward method requests to its server methods. The client application is forced to cast this stub reference to the correct local type, using the narrow() method. In the Java mapping of the IDL, this means calling the narrow() method on the corresponding helper class. HelloHolder.java This final class holds a public instance member of type Hello. It provides operations for out and inout arguments, which CORBA has but which do not map easily to Java's semantics. When you write the IDL interface, you do all the programming required to generate all these files for your distributed application. The only additional work required is the actual implementation of client and server classes. Troubleshooting Error Message: "idltojava" not found If you try to run idltojava on the file Hello.idl and the system cannot find idltojava, it is most likely not in your executable path. Make certain that the location of idltojava is in your path, and try again. Error Message: preprocessor failed idltojava uses a C/C++ preprocessor by default. You can change the default by setting two environment variables, CPP and CPPARGS. If you do not want to use a preprocessor, you can turn it off by using the command: idltojava -fno-cpp Hello.idl Developing a Client Application Here's the completed version of HelloClient.java. import HelloApp.*; // The package containing our stubs. import org.omg.CosNaming.*; // HelloClient will use the naming service. import org.omg.CORBA.*; // All CORBA applications need these classes. public class HelloClient { public static void main(String args[]) { try{ // Create and initialize the ORB 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); // Resolve the object reference in naming NameComponent nc = new NameComponent("Hello", ""); NameComponent path[] = {nc}; Hello helloRef = HelloHelper.narrow(ncRef.resolve(path)); // Call the Hello server object and print results String hello = helloRef.sayHello(); System.out.println(hello); } catch(Exception e) { System.out.println("ERROR : " + e); e.printStackTrace(System.out); } } } Importing Required Packages Import the packages required for the client class: import HelloApp.*; // The package containing our stubs. import org.omg.CosNaming.*; // HelloClient will use the naming service. import org.omg.CORBA.*; // All CORBA applications need these classes. Declaring the Client Class In HelloClient.java, declare the client class: public class HelloClient { // // Add the main method here in the next step. } Defining a main Method Every Java application needs a main method. Declare it within the scope of the HelloClient class, as follows: public static void main(String args[]) { // Put the try-catch block here in the next step. } Handling CORBA System Exceptions Because all CORBA programs can throw CORBA system exceptions at runtime, you will place all of the main functionality within a try-catch block. CORBA programs throw system exceptions whenever trouble occurs during any of the processes involved in invoking the server from the client. Our exception handler simply prints the name of the exception and its stack trace to standard output so you can see what kind of thing has gone wrong. Inside main, set up a try-catch block: try { // Add the rest of the HelloClient code here. } catch(Exception e) { System.out.println("ERROR : " + e); e.printStackTrace(System.out); } Creating an ORB Object A CORBA client needs a local ORB object to perform all of its marshaling and IIOP work. Every client instantiates an org.omg.CORBA.ORB object and initializes it by passing to the object certain information about itself. InsideHelloClient.java's try-catch block, declare and initialize an ORB variable: ORB orb = ORB.init(args, null); The call to the ORB's init method passes in your application's command line arguments, allowing you to set certain properties at runtime. Finding the Hello Server Once the application has an ORB, it can ask the ORB to locate the actual service it needs, in this case the Hello server. There are a number of ways for a CORBA client to get an initial object reference; our client application will use the COS Naming Service specified by OMG and provided with Java IDL. (See Using Stringified Object References for information on how to get an initial object reference when no naming service is available). Obtaining the Initial Naming Context The first step in using the naming service is to get the initial naming context. In the try-catch block, below your ORB initialization, call orb.resolve_initial_references to get an object reference to the name server: org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService"); The string "NameService" is defined for all CORBA ORBs. When you pass in that string, the ORB returns the initial naming context, an object reference to the name service. Narrowing the Object Reference As with all CORBA object references, objRef is a generic CORBA object. To use it as a NamingContext object, you must narrow it to its proper type. Add the call to narrow just below the previous statement. NamingContext ncRef = NamingContextHelper.narrow(objRef); Here we see the use of an idltojava -generated helper class, similar in function to HelloHelper. The ncRef object is now an org.omg.CosNaming.NamingContext and you can use it to access the naming service and find other services. Finding a Service in Naming Names can have different structures depending upon the implementation of the naming service. Consequently, CORBA name servers handle complex names by way of NameComponent objects. Each NameComponent holds a single part, or element, of the name. An array of NameComponent objects can hold a fully specified path to an object on any computer file or disk system. To find the Hello server, you first need a NameComponent to hold an identifying string for the Hello server. Add this code directly below the call to narrow. NameComponent nc = new NameComponent("Hello", ""); This statement sets the id field of nc, the new NameComponent, to "Hello" and the kind field to an empty string. Because the path to the Hello object has just one element, create a single-element array out of nc. The NamingContext.resolve method requires this array for its work: NameComponent path[] = {nc}; Finally, pass path to the naming service's resolve method to get an object reference to the Hello server and narrow it to a Hello object: Hello helloRef = HelloHelper.narrow(ncRef.resolve(path)); Here you see the HelloHelper helper class at work. The resolve method returns a generic CORBA object as you saw above when locating the name service itself. Therefore, you immediately narrow it to a Hello object, which is the object reference you need to perform the rest of your work. Invoking the sayHello Operation CORBA invocations look like a method call on a local object. The complications of marshaling parameters to the wire, routing them to the server-side ORB, unmarshaling, and placing the upcall to the server method are completely transparent to the client programmer. Because so much is done for you by generated code, invocation is really the easiest part of CORBA programming. 1.Continuing with the try-catch block in HelloClient.java, enter the following invocation below the call to the name service's resolve method: String hello = helloRef.sayHello(); 2.Finally, add code to print the results of the invocation to standard output: System.out.println(hello); 3.Save and close HelloClient.java. Developing the Hello World Server Here's the complete version of HelloServer.java. // The package containing our stubs. import HelloApp.*; // HelloServer will use the naming service. import org.omg.CosNaming.*; // The package containing special exceptions thrown by the name service. import org.omg.CosNaming.NamingContextPackage.*; // All CORBA applications need these classes. import org.omg.CORBA.*; public class HelloServer { public static void main(String args[]) { try{ // Create and initialize the ORB ORB orb = ORB.init(args, null); // Create the servant and register it with the ORB HelloServant helloRef = new HelloServant(); orb.connect(helloRef); // Get the root naming context org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService"); NamingContext ncRef = NamingContextHelper.narrow(objRef); // Bind the object reference in naming NameComponent nc = new NameComponent("Hello", ""); NameComponent path[] = {nc}; ncRef.rebind(path, helloRef); // Wait for invocations from clients java.lang.Object sync = new java.lang.Object(); synchronized(sync){ sync.wait(); } } catch(Exception e) { System.err.println("ERROR: " + e); e.printStackTrace(System.out); } } } class HelloServant extends _HelloImplBase { public String sayHello() { return "\nHello world!!\n"; } } Importing Required Packages Import the packages required: // The package containing our stubs. import HelloApp.*; // HelloServer will use the naming service. import org.omg.CosNaming.*; // The package containing special exceptions thrown by the name service. import org.omg.CosNaming.NamingContextPackage.*; // All CORBA applications need these classes. import org.omg.CORBA.*; Declaring the Server Class Declare the server class: public class HelloServer { // Add the main method here in the next step. } Defining the main Method Declare a standard main method: public static void main(String args[]) { // Add the try-catch block here in the next step. } Handling CORBA System Exceptions Because all CORBA programs can throw CORBA system exceptions at runtime, you will place all of the main functionality within a try-catch block. CORBA programs throw runtime exceptions whenever trouble occurs during any of the processes (marshaling, unmarshaling, upcall) involved in invocation. The exception handler simply prints the exception and its stack trace to standard output so you can see what kind of thing has gone wrong. Inside main, set up a try-catch block: try { // Add the rest of the HelloServer code here. } catch(Exception e) { System.err.println("ERROR: " + e); e.printStackTrace(System.out); } Creating an ORB Object Just like a client, a CORBA server also needs a local ORB object. Every server instantiates an ORB and registers its servant objects so that the ORB can find the server when it receives an invocation for it. Inside HelloServer.java's try-catch block, declare and initialize an ORB variable: ORB orb = ORB.init(args, null); The call to the ORB's init method passes in the server's command line arguments, allowing you to set certain properties at runtime. Managing the Servant Object A server is a process that instantiates one or more servant objects. The servant implements the interface generated by idltojava and actually performs the work of the operations on that interface. Our HelloServer needs a HelloServant. Instantiating the Servant Object Inside the try-catch block, just below the call to init, instantiate the servant object: HelloServant helloRef = new HelloServant(); This servant class isn't defined yet; you will do that in a later step. Next, connect the servant to the ORB, so that the ORB can recognize invocations on it and pass them along to the correct servant: orb.connect(helloRef); Defining the Servant Class At the end of HelloServer.java, outside the HelloServer class, define the class for the servant object. 1.Declare the servant class: class HelloServant extends _HelloImplBase { // Add the sayHello method here in the next step. } 2.The servant is a subclass of _HelloImplBase so that it inherits the general CORBA functionality enerated for it by the compiler. 3.Declare the required sayHello method: public String sayHello() { // Add the method implementation here in the next step. } 4.Write the sayHello implementation: return "\nHello World!!\n"; Working with COS Naming The HelloServer works with the naming service to make the servant object's operations available to clients. The server needs an object reference to the name service, so that it can register itself and ensure that invocations on the Hello interface are routed to its servant object. Obtaining the Initial Naming Context In the try-catch block, below instantiation of the servant, call orb.resolve_initial_references to get an object reference to the name server: org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService"); The string NameService is defined for all CORBA ORBs. When you pass in that string, the ORB returns a naming context object that is an object reference for the name service. Narrowing the Object Reference As with all CORBA object references, objRef is a generic CORBA object. To use it as a NamingContext object, you must narrow it to its proper type. Add the call to narrow just below the previous statement: NamingContext ncRef = NamingContextHelper.narrow(objRef); Here you see the use of an idltojava -generated helper class, similar in function to HelloHelper. The ncRef object is now an org.omg.CosNaming.NamingContext and you can use it to access the naming service and register the server. You will do that in the next step. Registering the Servant with the Name Server 1.Just below the call to narrow, create a new NameComponent member: NameComponent nc = new NameComponent("Hello", ""); 2.This statement sets the id field of nc, the new NameComponent, to "Hello" and the kind component to the empty string. Because the path to the Hello has a single element, create the single-element array that NamingContext.resolve requires for its work: NameComponent path[] = {nc}; 3.Finally, pass path and the servant object to the naming service, binding the servant object to the "Hello" id: ncRef.rebind(path, helloRef); Now, when the client calls resolve("Hello") on the initial naming context, the naming service returns an object reference to the Hello servant. Waiting for Invocation The server is ready; it simply needs to wait around for a client to request its service. To achieve that, enter the following code at the end of (but within) the try-catch block: java.lang.Object sync = new java.lang.Object(); synchronized(sync) { sync.wait(); } This form of Object.wait requires HelloServer to remain alive (though quiescent) until an invocation comes from the ORB. Because of its placement in main, after an invocation completes and sayHello returns, the server will wait again. Compiling and Running the Hello World Application This section shows you how to compile and run the server and the client program that together make up the "Hello World" application. Compiling the Client Application 1.Compile HelloClient.java: javac HelloClient.java HelloApp\*.java You should see HelloClient.class 2. Compiling the Server javac HelloServer.java HelloApp\*.java You should see HelloServer.class and HelloServant.class . Running the Client-Server Application 1.From an MS-DOS system prompt (Windows) or command shell (UNIX), start the Java IDL name server: tnameserv -ORBInitialPort nameserverport Note that nameserverport is the port on which you want the name server to run. If you do not specify this, port 900 will be chosen by default. Also note that using Solaris software, you must become root to start a process on a port under 1024. For this reason, we recommend that you use a port number greater than or equal to 1024. 2.From a second prompt or shell, start the Hello server: java HelloServer -ORBInitialHost nameserverhost -ORBInitialPort nameserverport Note that nameserverhost is the host on which the IDL name server is running. You can omit -ORBInitialHost nameserverhost if the name server is running on the same host as the Hello server. You can leave out -ORBInitialPort nameserverport if the name server is running on the default port. 3.From a third prompt or shell, run the Hello application client: java HelloClient -ORBInitialHost nameserverhost -ORBInitialPort nameserverport Note that nameserverhost is the host on which the IDL name server is running. You can omit -ORBInitialHost nameserverhost if the name server is running on the same host as the Hello client. You can leave out -ORBInitialPort nameserverport if the name server is running on the default port. 4.The client prints the string from the server to the command line: Hello world!! Remember to stop both the tnameserv and the HelloServer processes after the client application returns successfully. Troubleshooting Specifying ORB Initial Port The default ORB Initial Port is port 900. If you prefer, you can omit the port specifications if you start the name server on port 900. Using Solaris software, you must become root to start a process on a port under 1024. Remember to exit from root access before continuing with the tutorial if you choose to use this port for your name server. Class Definition Not Found Error If the Java compiler (javac) throws a NoClassDefFoundError, try using -cp (classpath) command line option when compiling the source files. javac –cp . *.java HelloApp\*.java Commands to run the CORBA example 1. Command to invoke the idltojava compiler idltojava –fno –cpp Hello.idl 2. Command to compile HelloClient.java and HelloServer.java javac –cp . *.java HelloApp\*.java 3. Command to run the name server on port 2000 timeserv –ORBInitialPort 2000 4. Command to run the server java HelloServer –ORBInitialPort 2000 5. Command to run the client java HelloClient –ORBInitialPort 2000 Applet Note: While this lesson focuses on writing a CORBA client application, many of the steps are identical to those required for writing applets. The major difference is that the applet code appears in the init method rather than in main. Here's the complete code for the applet version: HelloApplet.java // The package containing our stubs. import HelloApp.*; // HelloClient will use the naming service. import org.omg.CosNaming.*; // The package containing special exceptions thrown by the name service. import org.omg.CosNaming.NamingContextPackage.*; // All CORBA applications need these classes. import org.omg.CORBA.*; // Needed for the applet. import java.awt.Graphics; public class HelloApplet extends java.applet.Applet { public void init() { try{ // Create and initialize the ORB ORB orb = ORB.init(this, null); // Get the root naming context org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService"); NamingContext ncRef = NamingContextHelper.narrow(objRef); // Resolve the object reference in naming NameComponent nc = new NameComponent("Hello", ""); NameComponent path[] = {nc}; Hello helloRef = HelloHelper.narrow(ncRef.resolve(path)); // Call the Hello server object and print the results message = helloRef.sayHello(); } catch(Exception e) { System.out.println("HelloApplet exception: " + e); e.printStackTrace(System.out); } } String message = ""; public void paint(Graphics g) { g.drawString(message, 25, 50); } } Setting Up the HTML File (Applets Only) Tutorial.html is provided for displaying your finished applet, but you need to customize a few attributes and parameters. 1.Open Tutorial.html in your text editor. 2.Inside the APPLET tag, enter the path to your project directory as the value for the CODEBASE attribute. 3.In the first PARAM tag, enter the name of the machine where the CORBA name server runs (most likely your local machine name) as the value for ORBInitialHost. 4.Make sure the second PARAM tag is set to the value of ORBInitialPort that you are using to run thename server (it's preset to 1050 to work with the default used in the examples in this trail). In any case, it should be a value above 1028.