Section 14.3. Creating CORBA Objects


14.3. Creating CORBA Objects

Now that we have a better understanding of the various components 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 its implementation. This involves doing the following:

  • Writing an interface in the CORBA IDL

  • 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 base interface

We'll walk through these steps one by one, starting with a quick primer on CORBA IDL, followed by the requirements for creating a Java implementation of an IDL-defined remote object. A full reference for IDL syntax is provided in Appendix G. The following primer covers the basics needed to follow the rest of this chapter.

14.3.1. An IDL Primer

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, and the like. 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 nonclass 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. Only a single public class can be defined in a given Java file (although Java does allow for multiple inner classes within a single public class definition and multiple nonpublic classes per 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.

14.3.1.1. Modules

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 jent (an abbreviation of the title of this book). In IDL this is declared as follows:

     // IDL     module com {         module oreilly {             module jent {                 module corba {                     interface NeatExample ...                 };             };         };     }; 

If you want to reference the NeatExample interface in other IDL files, you use the syntax com::oreilly::jent::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.

14.3.1.2. Interfaces

Interfaces declared in IDL are mapped into classes or interfaces in Java. As mentioned earlier, 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 defined in 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.

14.3.1.3. Data members and methods

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 Appendix G 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 Appendix G 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.

14.3.1.4. A complete IDL example

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.

14.3.2. Turning IDL into Java

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 can generate the following Java classes from an IDL interface:

  • A Java interface with the same name as the IDL interface (e.g., Server). This interface includes Java method declarations that are a mapping of the operations declared in the IDL interface. An interfaceNameOperations interface (e.g., ServerOperations) is generated that contains these method declarations. The object's mapped Java interface extends this interface. This same operations interface is extended by the server-side skeleton interfaces.

  • 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 server-side 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. Also, inout arguments are initialized with the desired input value before the remote method is called in Java.

  • A client stub class, called _interface-nameStub, that acts as a client-side implementation of the interface. This stub class implements the generated Java interface for the object, but simply knows how to convert client method calls into ORB requests that are forwarded to the actual remote object. The stub class for an interface named Server is called _ServerStub.

  • These classes comprise the "outward-facing" mapping of the CORBA object's interface (the interfaces that clients of the object use directly). The IDL-to-Java compiler can also generate server-side skeleton classes you can use to implement the server-side implementation of the remote CORBA interface.

  • A server skeleton class named interfaceNamePOA (e.g., ServerPOA), which implements the generated interfaceNameOperations interface and extends the POA-related server-side interfaces. The interfaceNameOperations interface contains Java mappings of all the methods declared in the IDL definition.

So, in addition to generating a client-side Java mapping of the IDL interface and some helper classes for the Java interface, the IDL-to-Java 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. Appendix H provides a complete reference for Sun's idlj compiler. We will 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. Tools developed before the POA-compliant IDL-to-Java mapping will tend to use some nonstandard server-side object adaptor styles, while those developed after the POA was introduced should generate POA-compliant server skeleton classes. So when using third-party Java-based CORBA tools and ORBs, it's important to understand which version of the core CORBA spec and which IDL-to-Java mapping they support.

14.3.2.1. The delegation server-side model

The scheme described earlier (which will be demonstrated in Example 14-1) for the generated server-side code for a CORBA object is called an inheritance-based model. It depends on the server-side implementation directly extending a generated class (interfaceNamePOA). There is another option, called the delegation model, available to you in terms of how you "plug" your server code into the CORBA environment.

The delegation model is based on a scheme in which a server-side delegate is generated by the IDL compiler. This delegate extends the generated skeleton class and implements each of the mapped remote methods by delegating the incoming method request to a delegate object. This delegate object needs to implement the interfaceNameOperations interface generated by the IDL compiler, but it doesn't have to extend a concrete or abstract base class. This can prove to be useful in cases in which you have a preexisting Java class with its own inheritance scheme and want to "export" this class through CORBA for remote access. Because Java prohibits multiple inheritance of concrete classes, you don't have the option of extending both the existing class and the generated skeleton class. With the delegation model, you can define a simple delegate class that extends the preexisting Java class and implements the interfaceNameOperations interface generated by the compiler. You can then "publish" an instance of the original class by creating an instance of the new delegate class and an instance of the generated delegation-based server object and associating the server object with your delegate.

We won't provide full details of the delegation model here, but it's important to realize that this option exists. It may prove useful in some situations. There are options available on the IDL-to-Java compiler to instruct it to generate delegation-based server objects (also referred to as ties). These compiler options are documented in Appendix H.

14.3.2.2. A simple server class

The IDL interface shown in Example 14-1 is the IDL equivalent of the Account class we defined in Example 13-1. The interface, named Account, is declared within some nested modules (com, oreilly, jent, corba) and declares methods similar to the Account example in Chapter 13. Since this is IDL, the various method argument and return value data types are represented using IDL data types (e.g., string instead of String and so forth), and method arguments are declared with in, inout, or out modifiers.

Example 14-1. An account interface defined in IDL
 module com {     module oreilly {         module jent {             module corba {                 // Forward-declare the Account interface,                 // for the typedefs below                 interface Account;                 // Declare some useful typedefs: a list                 // of Accounts and of floats                 typedef sequence<Account> AccountList;                 typedef sequence<float> floatList;                 exception InsufficientFundsException {};                 interface Account {                     // Get the name of the account owner                     string getName( );                     // The account balance                     float getBalance( );                     // Withdraw funds from the account                     void withdraw(in float amt)                       raises (InsufficientFundsException);                     // Deposit funds to the account                     void deposit(in float amt);                     // Transfer funds from the source account                     // to this account                     void transfer(in float amt, in Account src)                       raises (InsufficientFundsException);                     // Similar to above, but perform transfers                     // from a series of source accounts                     void transferBatch(in floatList amts,                                        in AccountList srcs)                       raises (InsufficientFundsException);                 };             };         };     }; }; 

We can run the idlj compiler on this IDL interface using the following command line (Windows version):

     C:\>idlj -fall Account.idl 

This command creates the five Java classes described in the previous sections: a Java version of the interface, a helper class, a holder class, a client stub, and a server skeleton. The -fall option tells the compiler to generate both client-side and server-side mapping interfaces (see Appendix H for complete details on the command-line arguments for idlj).

The compiler creates the Java interface shown in Example 14-2, in a file named Account.java. This interface doesn't have much meat to it, because all of the method declarations generated in the AccountOperations interface are extended by this interface, which is shown in Example 14-3. The interface declaration in the IDL file is mapped directly to the Account Java interface declaration, with the interface extending the AccountOperations and org.omg.CORBA.Object interfaces. The module declarations in the IDL file have been mapped into a com.oreilly.jent.corba package statement at the beginning of all the generated Java files. The IDL data types have been converted into the equivalent Java data types, 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.

Example 14-2. Java mapping of the Account interface
 package com.oreilly.jent.corba; /** * com/oreilly/jent/corba/Account.java . * Generated by the IDL-to-Java compiler (portable), version "3.1" * from src/idl/Account.idl * Sunday, September 11, 2005 10:40:07 PM EDT */ public interface Account    extends AccountOperations, org.omg.CORBA.Object,      org.omg.CORBA.portable.IDLEntity { } // interface Account 

Example 14-3. AccountOperations Java interface
 package com.oreilly.jent.corba; /** * com/oreilly/jent/corba/AccountOperations.java . * Generated by the IDL-to-Java compiler (portable), version "3.1" * from src/idl/Account.idl * Sunday, September 11, 2005 10:40:07 PM EDT */ public interface AccountOperations {   // Get the name of the account owner   String getName ();   // The account balance   float getBalance ();   // Withdraw funds from the account   void withdraw (float amt)     throws com.oreilly.jent.corba.InsufficientFundsException;   // Deposit funds to the account   void deposit (float amt);   // Transfer funds from the source account to this account   void transfer (float amt, com.oreilly.jent.corba.Account src)     throws com.oreilly.jent.corba.InsufficientFundsException;   // source Accounts   void transferBatch (float[] amts, com.oreilly.jent.corba.Account[] srcs)     throws com.oreilly.jent.corba.InsufficientFundsException; } // interface AccountOperations 

14.3.2.3. The helper class and narrowing references

The compiler also generates a helper class, called AccountHelper. We won't provide the full code listing for this generated class here, but it can be found in the downloadable source code examples available for this book, along with all of the other Java interfaces generated by the IDL-to-Java compiler from this IDL interface.

The helper class is a standalone utility class that doesn't extend any other interfaces:

     abstract public class AccountHelper { . . . } 

As mentioned earlier, the helper class has static methods that let you read and write Account objects to and from CORBA I/O streams:

     public static       com.oreilly.jent.corba.Account read (org.omg.CORBA.portable.InputStream                                            istream)     public static void write (org.omg.CORBA.portable.OutputStream ostream,                               com.oreilly.jent.corba.Account value) 

a type( ) method that provides the TypeCode for the mapped Account class:

     synchronized public static org.omg.CORBA.TypeCode type ( ) 

and, most importantly, a narrow( ) method that safely narrows a CORBA org.omg.CORBA.Object reference into an Account reference:

     public static com.oreilly.jent.corba.Account narrow         (org.omg.CORBA.Object obj) 

Object narrowing is CORBA's equivalent to directly casting object referenceswe discuss why narrowing is necessary later in this chapter in "Remote Object References and Narrowing." In the implementation of the narrow( ) method, the helper class converts a CORBA Object reference to a reference to a specific type. If the CORBA object can't be narrowed to the requested type (e.g., the passed object reference is a null reference, the object's TypeCode doesn't match the TypeCode of the narrowed type), then the narrow( ) method throws a BAD_PARAM exception.

14.3.2.4. The holder class

The compiler generates a holder class for the Account class, which implements the CORBA Streamable interface:

     public final class AccountHolder       implements org.omg.CORBA.portable.Streamable { . . . } 

The holder class is a wrapper used when Account 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, which includes implementations of the _read( ) and _write( ) methods of the Streamable interface:

     public void _read (org.omg.CORBA.portable.InputStream i)     public void _write (org.omg.CORBA.portable.OutputStream o) 

This allows holders to be transmitted in remote method calls using these _read( ) and _write( ) methods; these methods handle whatever serialization the object needs. This functionality is similar to that provided by Java serialization, but CORBA needs its own scheme because it is independent of any particular language and needs to provide this serialization even if the target language doesn't provide this feature natively.

A holder contains a single instance of the corresponding CORBA object (an Account, in this example) as a data member:

     public com.oreilly.jent.corba.Account value = null; 

This instance is initialized in the constructor of the holder:

     public AccountHolder (com.oreilly.jent.corba.Account initialValue)     {       value = initialValue;     } 

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 instance 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 an Account as an inout parameter:

     // IDL     interface AccountManager {       boolean updateAccount(inout Account account);     }; 

The AccountManagerOperations Java interface generated from this IDL interface uses the AccountHolder class as the type for the corresponding Java method parameter:

     // Java     public interface AccountManagerOperations     {       boolean updateAccount (AccountHolder account);     } // interface AccountManagerOperations 

14.3.2.5. The client stub

The idlj compiler generates a Java client stub (_AccountStub) for our CORBA interface. The client stub implements the generated Account Java interface and acts as a client-side proxy for a remote Account object:

     public class _AccountStub       extends org.omg.CORBA.portable.ObjectImpl       implements com.oreilly.jent.corba.Account 

When a client acquires a reference to a remote Account object (through any of the methods we'll describe in "Finding and Using Remote Objects" later in this chapter), it actually receives an instance of this client stub class. The stub has implementations of all the methods from the interface, as mapped into the AccountOperations interface that is the parent of the Account interface. The stub class serves as a proxy for a remote server object, and each method implementation in the stub generates a request to the ORB to make a remote method call on the corresponding server-side object. The method arguments are bundled up ("marshaled") and passed along with the request to the ORB. We're not going to go into the details of the stub's method implementations, because you shouldn't have to worry much about them under normal conditions. But it is enlightening to look at an example to see how your remote objects do what they do in detail, using the core CORBA functions. As an example, here is the generated client stub implementation of the getName( ) method from our Account interface:

     // Get the name of the account owner     public String getName ( )     {       org.omg.CORBA.portable.InputStream $in = null;       try {         org.omg.CORBA.portable.OutputStream $out = _request ("getName", true);         $in = _invoke ($out);         String $result = $in.read_string ( );         return $result;       } catch (org.omg.CORBA.portable.ApplicationException $ex) {         $in = $ex.getInputStream ( );         String _id = $ex.getId ( );         throw new org.omg.CORBA.MARSHAL (_id);       } catch (org.omg.CORBA.portable.RemarshalException $rm) {         return getName ( );       } finally {         _releaseReply ($in);       }     } // getName 

As mentioned, when a Java client gets a reference to a remote Account 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.

14.3.2.6. Server skeletons

The idlj compiler generates server implementation skeleton classes that follow the POA specification. The base class for the server implementation of our Account interface is called AccountPOA:

     public abstract class AccountPOA       extends org.omg.PortableServer.Servant       implements com.oreilly.jent.corba.AccountOperations,         org.omg.CORBA.portable.InvokeHandler { . . . } 

The particulars of the implementation of this skeleton class follow the POA specification, in terms of how method invocations are mapped to actual operations on the server object. This helps to guarantee that your CORBA object implementations will migrate easily between different CORBA ORB implementations.

14.3.3. Writing the Implementation

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 our interface. We do this by subclassing from the server-side skeleton class generated by the idlj compiler. For our example, we need to subclass AccountPOA. We also need to implement the various methods defined on the Account IDL interface and mapped through the idlj compiler into the AccountOperations Java interface. The AccountImplPOA class in Example 14-4 shows the implementation of our server object.

As you look through the implementation class, you'll notice that the method implementations are similar to the RMI version of the Account server implementation example in Chapter 13. The only real difference is that this AccountImpl class extends the generated AccountPOA class (and, through it, the generated AccountOperations interface and the CORBA Servant class), while the RMI server implementation implements the RMI Account interface and extends java.rmi.server.UnicastRemoteObject. So in the same way that the two remote object schemes are analogous to each other in terms of functionality, the particulars of the server implementations in each case are analogous as well.

Example 14-4. Server implementation of the Account CORBA object
 public class AccountImplPOA extends AccountPOA {   // Our current balance   private float mBalance = 0;   // Name on account   private String mName = "";   // Create a new account with the given name   public AccountImplPOA(String name) {     mName = name;   }   public String getName( ) {     return mName;   }   public float getBalance( ) {     return mBalance;   }   // Withdraw some funds   public void withdraw(float amt) throws InsufficientFundsException {     if (mBalance >= amt) {       mBalance -= amt;       // Log transaction...       System.out.println("--> Withdrew " + amt                          + " from account " + getName( ));       System.out.println("    New balance: " + getBalance( ));     }     else {       throw new InsufficientFundsException("Withdrawal request of " + amt +                                            " exceeds balance of " + mBalance);     }   }   // Deposit some funds   public void deposit(float amt) {     mBalance += amt;     // Log transaction...     System.out.println("--> Deposited " + amt +                        " into account " + getName( ));     System.out.println("    New balance: " + getBalance( ));   }   // Move some funds from another (remote) account into this one   public void transfer(float amt, Account src)     throws InsufficientFundsException {     if (checkTransfer(src, amt)) {       src.withdraw(amt);       this.deposit(amt);       // Log transaction...       System.out.println("--> Transferred " + amt +                          " from account " + getName( ));       System.out.println("    New balance: " + getBalance( ));     }     else {       throw new InsufficientFundsException("Source account balance is less                                            "   + "than the requested transfer.");     }   }   // Make several transfers from other (remote) accounts into this one   public void transferBatch(float[] amts, Account[] srcs)     throws InsufficientFundsException {     // Iterate through the accounts and the amounts to be     // transferred from each     for (int i = 0; i < amts.length; i++) {       float amt = amts[i];       Account src = srcs[i];       // Make the transaction       this.transfer(amt, src);     }   }   // Check to see if the transfer is possible, given the source account   private boolean checkTransfer(Account src, float amt) {     boolean approved = false;     if (src.getBalance( ) >= amt) {       approved = true;     }     return approved;   } } 



Java Enterprise in a Nutshell
Java Enterprise in a Nutshell (In a Nutshell (OReilly))
ISBN: 0596101422
EAN: 2147483647
Year: 2004
Pages: 269

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net