Building Distributed Applications with Java and CORBA Dr. Dobb's Journal April 1998 Eliminating problems with code distribution, versioning, and more By Bryan Morgan Bryan is a senior member of the technical staff with TASC Inc. He can be reached at bmorgan@gulf.net. The Object Management Group (OMG) was formed to help define product-independent object standards in the form of a distributed object architecture -- the Common Object Request Broker Architecture (CORBA). CORBA is a vendor-independent operating system. In addition, CORBA is a distributed architecture that currently specifies a total of 18 services (known as "CORBAServices") for handling transactions, security, naming, object-lifecycle management, and other tasks. In short, CORBA is a specification that defines how objects interoperate over a network. The actual implementation of this architecture is left to individual software vendors. (For specific information on the OMG and its activities, visit http://www.omg.org/.) Although it's been around since 1991, interest in CORBA is growing due to ntiered client/server development efforts. n-tiered development separates the user interface from the application logic and the underlying data. In this manner, individual tiers can be modified or scaled without requiring changes to other tiers. Because CORBA is inherently object based, it works well as an architecture for building n-tiered client/server systems. Java, however, brings forward the concept of platform-independent, object-oriented applications or applets that can be mobile and dynamically loaded from a remote server. Using Java applets, code doesn't actually even need to be installed on a user's machine. Instead, code can be loaded dynamically to users as required. In theory, an n-tiered web application written in Java eliminates the problems of code distribution and versioning. Each time users hit your site, they receive a new version. A CORBA Backgrounder CORBA objects are accessed through the use of an interface, which is like a Java interface in that it is used to describe -- not implement -- an object's attributes and methods. CORBA interfaces, regardless of the programming language to be used, are first described using the OMG's Interface Definition Language (IDL). IDL can only be used to define attributes, methods, and parameters to those methods within an interface. Once the IDL has been constructed for a interface, it is compiled using a vendor's IDL-to-some-language compiler. (For instance, Visigenic includes an IDLTo-Java compiler with its VisiBroker For Java.) In addition, the OMG also defines mappings between IDL and languages such as C++, Java, and Cobol. Once an official mapping has been defined, IDL compilers will produce code that can be used with any vendor's product. This means that you could use the VisiBroker IDL compiler to produce Java code for use with Iona Technologies OrbixWeb Object Request Broker (ORB). One CORBA application never talks directly to another. Instead, an application issues requests for interfaces to the ORB -- the most important piece of the software architecture -- running on the local machine. The local ORB then passes the request to an ORB on a remote machine. The remote ORB then locates the appropriate object and passes an object reference back to the requester. In short, a CORBA 2.0-compliant ORB can: Lookup and instantiate objects on remote machines. Marshal parameters from one programming language (such as C++) to another language (such as Java). Handle security across your machine's local boundary. Retrieve and publish metadata on objects on the local system for another ORB. Invoke methods on a remote object using static method invocation described by a downloaded stub. Invoke methods on a remote object using dynamic method invocation. Auto-start objects that aren't currently up and running. Route callback methods to the appropriate local object being managed. Communicate with other ORBs using the Internet Inter-ORB Protocol (IIOP). Important functional pieces within an ORB include: The Basic Object Adapter (BOA), which provides policies for activating object implementations (shared server, unshared server, server-per-method). The Implementation Repository, which stores information about each object located on the server. The Object Activation Daemon, which is responsible for activating a server upon receiving a client's request for an object. The Interface Repository (IR), which is an online database of metadata about ORB Object types. The Dynamic Invocation Interface (DII), which allows client programs to dynamically construct requests for objects based on information contained in the IR. All implementation details of the ORB are hidden from you. To use an ORB to access remote CORBA objects, the application obtains a reference to the ORB object, and calls the appropriate methods to access remote objects. IIOP is another interesting feature of CORBA 2.0. IIOP is a wire-level protocol that resides on top of TCP/IP. It lets one vendor's CORBA 2.0-compliant ORB exchange objects with another's. Before IIOP, each vendor's ORB spoke a proprietary protocol and would only interoperate with other ORBs of the same type. Obviously, for objects to coexist and work together on something like the Internet, a standard protocol was required so that CORBA could move beyond its initial status as a niche technology. A CORBA application that is written using Java would have the advantages of being platformindependent, object-oriented, and dynamically loaded, while including support for legacy systems written in C++, Smalltalk, Ada, or Cobol, and supporting scalability from the level of an NT server all the way up to a mainframe running MVS. In addition, this application could have full access to the variety of CORBAServices and support for the access of distributed objects running under realtime embedded operating systems. Here, I'll examine the concepts behind CORBA application development in Java, using Borland's JBuilder 1.0 and Visigenic's VisiBroker For Java 3.0, a CORBA 2.0-compliant Object Request Broker (ORB). Borland's JBuilder client/server version includes a copy of the VisiBroker For Java ORB. All the code I present can be compiled using the Java Development Kit 1.1 (http://www.javasoft.com/). Computing with Distributed Objects Distributed applications can currently be built using Java in either a two-tier (client and server) or a multiple-tier architecture. If the size of the application is fairly small, little code will ever be reused, and if the number of users is small (less than 30), a two-tier architecture will work fine and you are free to consult another source on how to construct a Java client/server application using JDBC. However, if one or more of those requirements are not met, you should probably look into examining a three-tiered solution. The first tier will contain the user interface and some basic logic for handling interactions with the user. It will instantiate and use objects located on the middle tier (the object layer) using some type of object model. Since many applications use some type of relational database, assume the third tier will be the database server containing all data, and any necessary triggers and stored procedures. Java Development with Borland JBuilder JBuilder is a Java 1.1-only tool that includes support for JDBC, the 1.1 delegation-based event model, Java RMI, and JavaBeans. The JBuilder development environment is similar to that of Delphi: User interfaces can be built simply by dragging and dropping components onto a form. As this is done, Java code is generated behind the scenes. Event handlers can also be created by simply pointing and clicking with the mouse. JBuilder is unique in that it supports two-way editing. While many environments generate code based on your actions in the design environment, JBuilder actually modifies the visual form as you modify the code. This lets you avoid "sacred" areas in the code (to avoid confusing the development environment, such areas traditionally cannot be modified). For the purpose of illustration, I'll build a Java application for viewing and editing human-resources information on employees of AlphaBeta Technologies, a fictional software company (the complete source code and related files are available electronically; see "Resource Center," page 3). The distributed architecture could be a "standard" two-tier client/server system using an Oracle database server, or a large-scale telecommunications project that requires dozens of computers to interoperate and share information in a real-time manner. The client application will be written in Java and will access an HR object server via CORBA. For simplicity, the object server will hardwire all data into memory, although in a production application, it could use JDBC to query a database server. Users will be allowed to query for specific employees and can modify data on employees before saving the information back to the server. After creating a new project, selecting the File|New menu option will let you invoke the New Application Wizard (see Figures 1 and 2). Filling in the appropriate information in the Wizard's two dialog boxes creates a Java application class (containing the main() method) and Java frame. By dragging and dropping a few components onto the created frame (see Figure 3), you complete the layout of a basic employee information form. As you can see, the application supports several operations: First Record, Next Record, Previous Record, Last Record, Update, Delete, and New Employee. Because these buttons represent actions within this application, each ButtonDown event triggers the calling of an appropriate method on the remote Employee object. Building CORBA Objects Using VisiBroker for Java VisiBroker For Java 3.0 is a CORBA 2.0-compliant ORB from Visigenic Software. This ORB has been licensed by Netscape, Borland, Oracle, and Novell (among others). Because VisiBroker is included with the Client/Server version of Borland's JBuilder, I'll use it to build the CORBA application. There are only a few basic steps involved in building the CORBA objects. These steps are required in every Java/CORBA application that uses any ORB, no matter how simple or complex the application. 1. Set up the development/run-time environment by modifying the PATH and by setting the appropriate environment variables. 2. Write a definition for each object using IDL. 3. Compile the IDL code to produce client stub code and server skeleton code. 4. Write the client application code. 5. Write the server object code. 6. Compile the client and server code. 7. Start the server. 8. Run the client application. The specific tasks required for setting up your environment (step 1) are detailed in the VisiBroker documentation. Once completed, you design the object's interfaces using the IDL. Just as related classes are grouped into packages in Java, IDL groups these related interfaces into modules. Listing One is an IDL module (TheModule) that contains the interface (TheInterface), which contains a single attribute (TheAttribute) defined to be an integer value. Compiling this IDL module using an IDL-to-Java compiler (such as Visigenic's idl2java) results in the Java interface in Listing Two. Each Java data type maps to a specific IDL data type (see Table 1). Variables are declared in IDL using the IDL data type you want to correspond to the resulting Java data type. IDL also supports user-defined data types and constructed data types (such as enums, structs, unions, sequences, and arrays), which are converted to Java. Methods can be declared in IDL as well. The way in which parameters are passed to these methods is controlled by the use of the in, out, and inout keywords. The in keyword specifies that the parameter is to be passed from the caller to the object; out specifies that the parameter is to be passed from the object to the caller; and inout specifies that the parameter is to be passed in both directions. Because the server will store the sample data set in memory (in the form of multiple Employee objects), you need to define: A struct representing a single Employee containing information about an employee. A sequence representing a list of Employee objects that can be iterated. An EmployeeInfo interface representing all data and methods required by our server object. You already know (from the requirements of the user interface) that you need methods that retrieve individual Employee objects, iterate through the available Employees, and modify (add/update/delete) the Employee list. Listing Three contains the IDL necessary to satisfy these requirements. After running the Visigenic idl2java tool, a set of 14 Java files are created. These files represent what, in CORBA-speak, are known as client stubs and server skeleton files. The client stubs represent the interface from the client application to the client ORB. No implementation details take place within these classes. The server skeletons, however, are where your actual programming will take place. Before implementing the server class (EmployeeInfoImpl), look at Listing Four, which is generated by the idl compiler and represents the Java representation of the EmployeeInfo interface and associated structures and sequences. The next step is to rename the _example_EmployeeInfo.java file (and the _example_EmployeeInfo class contained within) to EmployeeInfoImpl. The _example_EmployeeInfo file contains a skeleton implementation class to be filled in with details. This server class actually manages the employee information in a private Java Vector object. Methods within this class will be called remotely from a client. Listing Five is the completed EmployeeInfoImpl class. To have a fully functional CORBA server, you need to register this server class with the server ORB. To do so, a CORBA application initializes the ORB and its BOA, then registers the EmployeeInfoImpl object with the ORB. Once this has been completed, this object can be retrieved from any remote CORBA client. Listing Six shows the basic statements required to do this using VisiBroker. Because of the seamless nature of Java and CORBA integration, nearly all code on the client looks like standard Java. Once the CORBA object has been obtained, it can be used within a Java application just like any normal Java object. The trick lies in obtaining that object. To do so, you need only initialize the client ORB, then retrieve the object from the server. Listing Seven shows the code required to do this. The infoObject variable is a private variable within the EmployeeFrame class. (EmployeeFrame was generated by JBuilder when we designed the form.) Once you have a valid object, its methods can be called in response to user events. Running the Application To get the server application up and running, execute the osagent command. OSAgent (also called the "VisiBroker Smart Agent") provides a fault-tolerant object location service. Its job is to locate and return an object when it is requested. After osagent is up and running, start up the server application using the Visigenic Java interpreter, vbj. This can be done by executing: vbj Server. Finally, start the client application using vbj as well: vbj EmployeeApp. Figure 4 shows the EmployeeInfo application running. Conclusion To get started with Java and CORBA development, visit one of the ORB vendor's web sites and download a trial version of its ORB. For a complete listing of commercial CORBA vendors, visit the OMG at http://www.omg.org/. DDJ Listing One Module TheModule{ interface TheInterface { long TheAttribute; }; }; Back to Article Listing Two package TheModule;public interface TheInterface { public int TheAttribute; } Back to Article Listing Three // EMPLOYEE represents a single employee's personal informationmodule EmployeeServer { struct Employee { string Name; string Address; string City; string State; long long SSN; float Salary; string Title; }; typedef sequence<Employee> EmployeeList; interface EmployeeInfo { attribute EmployeeList theList; readonly attribute long Count; Employee getEmployee(in long index); void deleteEmployee(in long index); void newEmployee(in Employee newPerson); void updateEmployee(in long index, in Employee newData); }; }; Back to Article Listing Four // Employee.java - Java version of IDL struct Employeefinal public class Employee { public java.lang.String Name; public java.lang.String Address; public java.lang.String City; public java.lang.String State; public long SSN; public float Salary; public java.lang.String Title; public Employee() { } public Employee(java.lang.String Name, java.lang.String Address, java.lang.String City, java.lang.String State, long SSN, float Salary, java.lang.String Title) { this.Name = Name; this.Address = Address; this.City = City; this.State = State; this.SSN = SSN; this.Salary = Salary; this.Title = Title; } public java.lang.String toString() { org.omg.CORBA.Any any = org.omg.CORBA.ORB.init().create_any(); EmployeeServer.EmployeeHelper.insert(any, this); return any.toString(); } } // EmployeeInfo.java - Java interface version of IDL interface EmployeeInfo public interface EmployeeInfo extends org.omg.CORBA.Object { public void theList(EmployeeServer.Employee[] theList); public EmployeeServer.Employee[] theList(); public int Count(); public EmployeeServer.Employee getEmployee(int index); public void deleteEmployee(int index); public void newEmployee(EmployeeServer.Employee newPerson); public void updateEmployee(int index, EmployeeServer.Employee newData); } Back to Article Listing Five package EmployeeServer;public class EmployeeInfoImpl extends EmployeeServer._EmployeeInfoImplBase { private java.util.Vector Employees; /** Construct a persistently named object. */ public EmployeeInfoImpl(java.lang.String name) { super(name); Employees = new java.util.Vector(); } /** Construct a transient object. */ public EmployeeInfoImpl() { super(); Employees = new java.util.Vector(); } public EmployeeServer.Employee getEmployee(int index) { if (index <= (Employees.size() -1)) return (EmployeeServer.Employee)Employees.elementAt(index); else return null; } public void deleteEmployee(int index) { Employees.removeElementAt(index); } public void newEmployee(EmployeeServer.Employee newPerson) { Employees.addElement(newPerson); } public void updateEmployee(int index, EmployeeServer.Employee newData) { Employees.setElementAt(newData, index); } public void theList(EmployeeServer.Employee[] theList) { // implement attribute writer... } public EmployeeServer.Employee[] theList() { // implement attribute reader... return null; } public int Count() { return Employees.size(); } } Back to Article Listing Six public class Server{ public static void main(String[] args) { //Initialize the ORB org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(args, null); //Initialize the BOA org.omg.CORBA.BOA boa = orb.BOA_init(); //Create the EmployeeInfo object EmployeeServer.EmployeeInfo info = new EmployeeServer.EmployeeInfoImpl("EmployeeInfo"); //Export the object boa.obj_is_ready(info); System.out.println(info + " is ready."); //Wait for incoming requests boa.impl_is_ready(); } } Back to Article Listing Seven // Initialize the ORBorg.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(); // Locate and retrieve EmployeeInfo object from server EmployeeFrame.infoObject = EmployeeServer.EmployeeInfoHelper.bind(orb, "EmployeeInfo"); Back to Article