CORBA Example

advertisement
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.
Download