|
Unlike RMI, CORBA lets you make calls between Java objects and objects written in other languages. CORBA depends on having an Object Request Broker (ORB) available on both client and server. You can think of an ORB as a kind of universal translator for interobject CORBA communication. The CORBA 2 specification defines more than a dozen "services" that the ORB can use for various kinds of housekeeping tasks. These range from a "startup service" to get the process going, to a "life cycle service" that you use to create, copy, move, or destroy objects, to a "naming service" that allows you to search for objects if you know their name. JDK 1.2 introduced an implementation of a CORBA 2-compliant ORB, giving Java applications and applets the ability to connect to remote CORBA objects. NOTE
Here are the steps for implementing CORBA objects:
These steps are quite similar to the steps that you use to build distributed applications with RMI, but with two important differences:
In the following sections, you will see how to use IDL to define CORBA interfaces, and how to connect clients implemented in Java with C++ servers and C++ clients with servers implemented in Java. However, CORBA is a complex subject, and we give you only a couple of basic examples to show you how to get started. For more information, we recommend Client/Server Programming with Java and CORBA by Robert Orfali and Dan Harkey [John Wiley & Sons 1998]. More advanced, and definitely not for the faint of heart, is Advanced CORBA Programming with C++ by Michi Henning and Steve Vinoski [Addison-Wesley 1999]. The Interface Definition LanguageTo introduce the IDL syntax, we quickly run through the same example that we used for RMI. In RMI, you started out with an interface in the Java programming language. With CORBA, the starting point is an interface in IDL syntax: interface Product { string getDescription(); }; There are a few subtle differences between IDL and Java. In IDL, the interface definition ends with a semicolon. Note that string is written in lower case. In fact, the string class refers to the CORBA notion of a string, which is different from a Java string. In the Java programming language, strings contain 16-bit Unicode characters. In CORBA, strings contain only 8-bit characters. If you send the 16-bit string through the ORB and the string has characters with nonzero high byte, an exception is thrown. This kind of type mismatch problem is the price you pay for interoperability between programming languages. NOTE
The "IDL to Java" compiler (Java IDL compiler) translates IDL definitions to definitions for Java interfaces. For example, suppose you place the IDL Product definition into a file Product.idl and run idlj Product.idl The result is a file ProductOperations.java with the following contents interface ProductOperations { String getDescription(); } and a file Product.java that defines an interface public interface Product extends ProductOperations, org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity { } NOTE
The IDL compiler also generates a number of other source filesthe stub class for communicating with the ORB and three helper classes that you will encounter later in this section and the next. NOTE
The rules that govern the translation from IDL to the Java programming language are collectively called the Java programming language binding. Language bindings are standardized by the OMG; all CORBA vendors are required to use the same rules for mapping IDL constructs to a particular programming language. We do not discuss all aspects of IDL or the Java programming language bindingsee the CORBA documentation at the web site of the Object Management Group (http://www.omg.org) for a full description. However, there are a number of important concepts that every IDL user needs to know. When defining a method, you have more choices for parameter passing than the Java programming language offers. Every parameter can be declared as in, out, or inout. An in parameter is simply passed to the methodthis is the same parameter-passing mechanism as in Java. However, Java has no analog to an out parameter. A method stores a value in each out parameter before it returns. The caller can retrieve the values stored in out parameters. For example, a find method might store the product object that it has found: interface Warehouse { boolean locate(in String descr, out Product p); . . . }; If the parameter is declared as out only, then the method should not expect the parameter to be initialized. However, if it is declared as inout, then the caller needs to supply a value for the method, and the method can change that value so that the caller can retrieve the changed value. In Java, these parameters are simulated with special holder classes that are generated by the Java IDL compiler. The IDL compiler generates a class with suffix Holder for every interface. For example, when the Product interface is compiled, it automatically generates a ProductHolder class. Every holder class has a public instance variable called value. When a method has an out parameter, the IDL compiler changes the method signature to use a holder, for example, interface Warehouse { boolean locate(String descr, ProductHolder p); . . . }; When calling the method, you must pass in a holder object. After the method returns, you retrieve the value of the out parameter from the holder object. Here is how you call the locate method. Warehouse w = . . .; String descr = . . .; Product p; ProductHolder pHolder = new ProductHolder(); if (w.locate(descr, pHolder)) p = pHolder.value; Holder classes are predefined for fundamental types (such as IntHolder, DoubleHolder, and so on). NOTE
In IDL, you use the sequence construct to define arrays of variable size. You must first define a type before you can declare sequence parameters or return values. For example, here is the definition of a "sequence of products" type. typedef sequence<Product> ProductSeq; You then use that type in method declarations: interface Warehouse { ProductSeq find(in Customer c); . . . }; In the Java programming language, sequences correspond to arrays. For example, the find method is mapped to Product[] find(Customer c) If a method can throw an exception, you first define the exception type and then use a raises declaration. In the following example, the find method can raise a BadCustomer exception. interface Warehouse { exception BadCustomer { string reason; }; ProductSeq find(in Customer c) raises BadCustomer; . . . }; The IDL compiler translates the exception type into a class. final public class BadCustomer extends org.omg.CORBA.UserException { public BadCustomer() {} public BadCustomer(String __reason) { reason = __reason; } public String reason; } If you catch such an exception, you can look into its public instance variables. The raises specifier becomes a throws specifier of the Java method ProductSeq find(Customer c) throws BadCustomer Interfaces can contain constants, for example, interface Warehouse { const int SOLD_OUT = 404; . . . }; Interfaces can also contain attributes. Attributes look like instance variables, but they are actually shorthand for a pair of accessor and mutator methods. For example, here is a Book interface with an isbn attribute: interface Book { attribute string isbn; . . . }; The Java equivalent is a pair of methods, both with the name isbn: String isbn() // accessor void isbn(String __isbn) // mutator If the attribute is declared as readonly, then no mutator method is generated. You cannot specify variables in CORBA interfacesthe data representation for objects is part of the implementation strategy, and IDL does not address implementation at all. CORBA supports interface inheritance, for example, interface Book : Product { /* . . . */ }; You use the colon (:) to denote inheritance. An interface can inherit multiple interfaces. In IDL, you can group definitions of interfaces, types, constants, and exceptions into modules. module corejava { interface Product { . . . }; interface Warehouse { . . . }; }; In Java, modules are translated to packages. Once you have the IDL file, you run the IDL compiler that your ORB vendor supplies to get stubs and helper classes for your target programming language (such as Java or C++). For example, to convert IDL files to Java, you run the idlj program. Supply the name of the IDL file on the command line: idlj Product.idl The program creates five source files:
The same IDL file can be compiled to C++. We use a freely available ORB called omniORB for our examples. The omniORB package contains an IDL-to-C++ compiler called omniidl. To generate C++ stubs, invoke it as omniidl -bcxx Product.idl You get two C++ files:
NOTE
A CORBA ExampleIn our first example, we show you how to call a C++ server object from a Java client, using the CORBA support that is built into the JDK. On the server side, we use omniORB, an open-source ORB that is available from http://omniorb.sourceforge.net. NOTE
Our example C++ server object simply reports the value of an environment variable on the server. The interface is interface Env { string getenv(in string name); }; For example, the following Java program fragment obtains the value of the PATH environment variable of the process in which the server object runs. Env env = . . .; String value = env.getenv("PATH") The C++ implementation code for this interface is straightforward. We simply call the getenv method in the standard C library. class EnvImpl : public POA_Env, public PortableServer::RefCountServantBase { public: virtual char* getenv(const char *name) { char* value = std::getenv(name); return CORBA::string_dup(value); } }; You don't need to understand the C++ code to follow this sectionjust treat it as a bit of legacy code that you want to encapsulate in a CORBA object so that you can call it from Java programs. On the server side, you write a C++ program that does the following:
You can find that program in Example 5-19 at the end of this section. We do not discuss the C++ code in detail. If you are interested, consult the omniORB documentation for more information. The documentation contains a good tutorial that explains each step in detail. Let us now turn to the client code. You already saw how to invoke a method on the server object once you have a reference to the remote object. However, to get to that reference, you have to go through a different set of mumbo-jumbo than in RMI. First, you initialize the ORB. The ORB is simply a code library that knows how to talk to other ORBs and how to marshal and unmarshal parameters. ORB orb = ORB.init(args, null); Next, you locate the naming service that helps you locate other objects. However, in CORBA, the naming service is just another CORBA object. To call the naming service, you first need to locate it. In the days of CORBA 1, this was a major problem because there was no standard way of getting a reference to it. However, a CORBA 2 ORB lets you locate certain standard services by name. The call String[] services = orb.list_initial_services(); lists the names of the standard services to which the ORB can connect. The naming service has the standard name NameService. Most ORBs have additional initial services, such as the RootPOA service that accesses the root Portable Object Adaptor. To obtain an object reference to the service, you use the resolve_initial_references method. It returns a generic CORBA object, an instance of the class org.omg.corba.Object. Use the full package prefix; if you just use Object, then the compiler assumes that you mean java.lang.Object. org.omg.CORBA.Object object = orb.resolve_initial_references("NameService"); Next, convert this reference to a NamingContext reference so that you can invoke the methods of the NamingContext interface. In RMI, you would simply cast the reference to a different type. However, in CORBA, you cannot simply cast references. NamingContext namingContext = (NamingContext) object; // ERROR Instead, you have to use the narrow method of the helper class of the target interface. NamingContext namingContext = NamingContextHelper.narrow(object); CAUTION
Now that you have the naming context, you can use it to locate the object that the server placed into it. The naming context associates names with server objects. Names are nested sequences of name components. You can use the nesting levels to organize hierarchies of names, much like you use directories in a file system. A name component consists of an ID and a kind. The ID is a name for the component that is unique among all names with the same parent component. The kind is some indication of the type of the component. These kinds are not standardized; we use "Context" for name components that have nested names, and "Object" for object names. In our example, the server program has placed the EnvImpl object into the name expressed by the sequence (, kind="Context"), (, kind="Object") We retrieve a remote reference to it by building an array of name components and passing it to the resolve method of the NamingContext interface. NameComponent[] path = { new NameComponent("corejava", "Context"), new NameComponent("Env", "Object") }; org.omg.CORBA.Object envObj = namingContext.resolve(path); Once again, we must narrow the resulting object reference: Env env = EnvHelper.narrow(envObj); Now we are ready to call the remote method: String value = env.getenv("PATH"); You will find the complete code in Example 5-18. This example shows the steps to follow in a typical client program:
To actually test this program, do the following. The C++ instructions depend on the ORB. We give instructions for omniORB; modify them if you use another ORB.
If the server is on a remote machine or if the initial port of the server ORB is not the same as the Java IDL default of 900, then set the ORBInitialHost and ORBInitialPort properties. For example, OmniORB uses port 2809. When using orbd, we also used port 2809 because we need to have root privileges to start a service on a port below 1024 on UNIX/Linux. There are two methods for setting these properties. You can set the system properties org.omg.CORBA.ORBInitialHost org.omg.CORBA.ORBInitialPort for example, by starting the java interpreter with the -D option. Or, you can specify the values on the command line: java EnvClient -ORBInitialHost warthog -ORBInitialPort 2809 The command-line parameters are passed to the ORB by the call ORB orb = ORB.init(args, null); In principle, your ORB vendor should tell you with great clarity how its bootstrap process works. In practice, we have found that vendors blithely assume that you would never dream of mixing their precious ORB with another, and they tend to be less than forthcoming with this information. If your client won't find the naming service, try forcing the initial ports for both the server and the client to the same value. TIP
In this section, you saw how to connect to a server that was implemented in C++. We believe that is a particularly useful scenario. You can wrap legacy services into CORBA objects and access them from your Java programs, without having to deploy additional system software on the client. In the next section, you will see the opposite scenario, where the server is implemented in Java and the client in C++. Example 5-18. EnvClient.java[View full width] 1. import org.omg.CosNaming.*; 2. import org.omg.CORBA.*; 3. 4. public class EnvClient 5. { 6. public static void main(String args[]) 7. { 8. try 9. { 10. ORB orb = ORB.init(args, null); 11. org.omg.CORBA.Object namingContextObj = orb.resolve_initial_references ("NameService"); 12. NamingContext namingContext = NamingContextHelper.narrow(namingContextObj); 13. 14. NameComponent[] path = 15. { 16. new NameComponent("corejava", "Context"), 17. new NameComponent("Env", "Object") 18. }; 19. org.omg.CORBA.Object envObj = namingContext.resolve(path); 20. Env env = EnvHelper.narrow(envObj); 21. System.out.println(env.getenv("PATH")); 22. } 23. catch (Exception e) 24. { 25. e.printStackTrace(); 26. } 27. } 28. } Example 5-19. EnvServer.cpp[View full width] 1. #include <iostream> 2. #include <cstdlib> 3. 4. #include "Env.hh" 5. 6. using namespace std; 7. 8. class EnvImpl : 9. public POA_Env, 10. public PortableServer::RefCountServantBase 11. { 12. public: 13. virtual char* getenv(const char *name); 14. }; 15. 16. char* EnvImpl::getenv(const char *name) 17. { 18. char* value = std::getenv(name); 19. return CORBA::string_dup(value); 20. } 21. 22. static void bindObjectToName(CORBA::ORB_ptr orb, const char name[], CORBA::Object_ptr objref) 23. { 24. CosNaming::NamingContext_var rootContext; 25. 26. try 27. { 28. // Obtain a reference to the root context of the name service: 29. CORBA::Object_var obj; 30. obj = orb->resolve_initial_references("NameService"); 31. 32. // Narrow the reference returned. 33. rootContext = CosNaming::NamingContext::_narrow(obj); 34. if(CORBA::is_nil(rootContext)) 35. { 36. cerr << "Failed to narrow the root naming context." << endl; 37. return; 38. } 39. } 40. catch (CORBA::ORB::InvalidName& ex) 41. { 42. // This should not happen! 43. cerr << "Service required is invalid [does not exist]." << endl; 44. return; 45. } 46. 47. try 48. { 49. CosNaming::Name contextName; 50. contextName.length(1); 51. contextName[0].id = (const char*) "corejava"; 52. contextName[0].kind = (const char*) "Context"; 53. 54. CosNaming::NamingContext_var corejavaContext; 55. try 56. { 57. // Bind the context to root. 58. corejavaContext = rootContext->bind_new_context(contextName); 59. } 60. catch (CosNaming::NamingContext::AlreadyBound& ex) 61. { 62. // If the context already exists, this exception will be raised. In this case, just 63. // resolve the name and assign the context to the object returned: 64. CORBA::Object_var obj; 65. obj = rootContext->resolve(contextName); 66. corejavaContext = CosNaming::NamingContext::_narrow(obj); 67. if( CORBA::is_nil(corejavaContext) ) 68. { 69. cerr << "Failed to narrow naming context." << endl; 70. return; 71. } 72. } 73. 74. // Bind objref with given name to the context: 75. CosNaming::Name objectName; 76. objectName.length(1); 77. objectName[0].id = name; 78. objectName[0].kind = (const char*) "Object"; 79. 80. try 81. { 82. corejavaContext->bind(objectName, objref); 83. } 84. catch (CosNaming::NamingContext::AlreadyBound& ex) 85. { 86. corejavaContext->rebind(objectName, objref); 87. } 88. } 89. catch (CORBA::COMM_FAILURE& ex) 90. { 91. cerr << "Caught system exception COMM_FAILURE--unable to contact the naming service." 92. << endl; 93. } 94. catch (CORBA::SystemException&) 95. { 96. cerr << "Caught a CORBA::SystemException while using the naming service." << endl; 97. } 98. } 99. 100. int main(int argc, char *argv[]) 101. { 102. cout << "Creating and initializing the ORB..." << endl; 103. 104. CORBA::ORB_var orb = CORBA::ORB_init(argc, argv, "omniORB4"); 105. 106. CORBA::Object_var obj = orb->resolve_initial_references("RootPOA"); 107. PortableServer::POA_var poa = PortableServer::POA::_narrow(obj); 108. poa->the_POAManager()->activate(); 109. 110. EnvImpl* envImpl = new EnvImpl(); 111. poa->activate_object(envImpl); 112. 113. // Obtain a reference to the object, and register it in the naming service. 114. obj = envImpl->_this(); 115. 116. cout << orb->object_to_string(obj) << endl; 117. cout << "Binding server implementations to registry..." << endl; 118. bindObjectToName(orb, "Env", obj); 119. envImpl->_remove_ref(); 120. 121. cout << "Waiting for invocations from clients..." << endl; 122. orb->run(); 123. 124. return 0; 125. }
org.omg.CORBA.ORB 1.2
org.omg.CosNaming.NamingContext 1.2
org.omg.CosNaming.NameComponent 1.2
Implementing CORBA ServersIf you are deploying a CORBA infrastructure, you will find that Java is a good implementation language for CORBA server objects. The language binding is natural, and robust server software is more easily built with Java than with C++. This section describes how to implement a CORBA server in the Java programming language. The example program in this section is similar to that of the preceding section. We supply a service to look up a system property of a Java virtual machine. Here is the IDL description: interface SysProp { string getProperty(in string name); }; For example, our client test program calls the server as follows: CORBA::String_var key = "java.vendor"; CORBA::String_var value = sysProp->getProperty(key); The result is a string describing the vendor of the Java virtual machine that is executing the server program. We don't look into the details of the C++ client program. Example 5-21 lists the code. To implement the server, you run the idlj compiler with the -fall option. (By default, idlj only creates client-side stubs.) idlj -fall SysProp.idl Then you extend the SysPropPOA class that the idlj compiler generated from the IDL file. Here is the implementation: class SysPropImpl extends SysPropPOA { public String getProperty(String key) { return System.getProperty(key); } } NOTE
NOTE
Next, write a server program that carries out the following tasks:
You will find the complete code in Example 5-20. Here are the highlights. Start the ORB as you would for a client program: ORB orb = ORB.init(args, null); Next, activate the root POA: POA rootpoa = (POA) orb.resolve_initial_references("RootPOA"); rootpoa.the_POAManager().activate(); Construct the server object and convert it to a CORBA object: SysPropImpl impl = new SysPropImpl(); org.omg.CORBA.Object ref = rootpoa.servant_to_reference(impl); Next, obtain the IOR with the object_to_string method and print it: System.out.println(orb.object_to_string(impl)); You obtain a reference to the naming service in exactly the same way as with a client program: org.omg.CORBA.Object namingContextObj = orb.resolve_initial_references("NameService"); NamingContext namingContext = NamingContextHelper.narrow(namingContextObj); You then build the desired name for the object. Here, we call the object SysProp: NameComponent[] path = { new NameComponent("SysProp", "Object") }; You use the rebind method to bind the object to the name: namingContext.rebind(path, impl); Finally, you wait for client invocations: orb.run(); To test this program, do the following.
You have now seen how to use CORBA to connect clients and servers that were written in different programming languages. This concludes our discussion of CORBA. CORBA has other interesting features, such as dynamic method invocation and a number of standard services such as transaction handling and persistence. We refer you to Client/Server Programming with Java and CORBA by Robert Orfali and Dan Harkey [John Wiley & Sons 1998] for an in-depth discussion of advanced CORBA issues. Example 5-20. SysPropServer.java[View full width] 1. import org.omg.CosNaming.*; 2. import org.omg.CORBA.*; 3. import org.omg.PortableServer.*; 4. 5. class SysPropImpl extends SysPropPOA 6. { 7. public String getProperty(String key) 8. { 9. return System.getProperty(key); 10. } 11. } 12. 13. public class SysPropServer 14. { 15. public static void main(String args[]) 16. { 17. try 18. { 19. System.out.println("Creating and initializing the ORB..."); 20. 21. ORB orb = ORB.init(args, null); 22. 23. System.out.println("Registering server implementation with the ORB..."); 24. 25. POA rootpoa = (POA) orb.resolve_initial_references("RootPOA"); 26. rootpoa.the_POAManager().activate(); 27. 28. SysPropImpl impl = new SysPropImpl(); 29. org.omg.CORBA.Object ref = rootpoa.servant_to_reference(impl); 30. 31. System.out.println(orb.object_to_string(ref)); 32. 33. org.omg.CORBA.Object namingContextObj = orb.resolve_initial_references ("NameService"); 34. NamingContext namingContext = NamingContextHelper.narrow(namingContextObj); 35. NameComponent[] path = 36. { 37. new NameComponent("SysProp", "Object") 38. }; 39. 40. System.out.println("Binding server implemenation to name service..."); 41. namingContext.rebind(path, ref); 42. 43. System.out.println("Waiting for invocations from clients..."); 44. orb.run(); 45. } 46. catch (Exception e) 47. { 48. e.printStackTrace(System.out); 49. } 50. } 51. } Example 5-21. SysPropClient.cpp[View full width] 1. #include <iostream> 2. 3. #include "SysProp.hh" 4. 5. using namespace std; 6. 7. CORBA::Object_ptr getObjectReference(CORBA::ORB_ptr orb, const char serviceName[]) 8. { 9. CosNaming::NamingContext_var rootContext; 10. 11. try 12. { 13. // Obtain a reference to the root context of the name service: 14. CORBA::Object_var initServ; 15. initServ = orb->resolve_initial_references("NameService"); 16. 17. // Narrow the object returned by resolve_initial_references() to a CosNaming: :NamingContext 18. // object 19. rootContext = CosNaming::NamingContext::_narrow(initServ); 20. if (CORBA::is_nil(rootContext)) 21. { 22. cerr << "Failed to narrow naming context." << endl; 23. return CORBA::Object::_nil(); 24. } 25. } 26. catch (CORBA::ORB::InvalidName&) 27. { 28. cerr << "Name service does not exist." << endl; 29. return CORBA::Object::_nil(); 30. } 31. 32. // Create a name object, containing the name corejava/SysProp: 33. CosNaming::Name name; 34. name.length(1); 35. 36. name[0].id = serviceName; 37. name[0].kind = "Object"; 38. 39. CORBA::Object_ptr obj; 40. try 41. { 42. // Resolve the name to an object reference, and assign the returned reference to a 43. // CORBA::Object: 44. obj = rootContext->resolve(name); 45. } 46. catch (CosNaming::NamingContext::NotFound&) 47. { 48. // This exception is thrown if any of the components of the path [contexts or the object] 49. // aren't found: 50. cerr << "Context not found." << endl; 51. return CORBA::Object::_nil(); 52. } 53. return obj; 54. } 55. 56. int main (int argc, char *argv[]) 57. { 58. CORBA::ORB_ptr orb = CORBA::ORB_init(argc, argv, "omniORB4"); 59. 60. CORBA::Object_var obj = getObjectReference(orb, "SysProp"); 61. SysProp_var sysProp = SysProp::_narrow(obj); 62. 63. if (CORBA::is_nil(sysProp)) 64. { 65. cerr << "Cannot invoke on a nil object reference." << endl; 66. return 1; 67. } 68. 69. CORBA::String_var key = "java.vendor"; 70. CORBA::String_var value = sysProp->getProperty(key); 71. 72. cerr << key << "=" << value << endl; 73. 74. return 0; 75. } org.omg.CORBA.ORB 1.2
org.omg.CosNaming.NamingContext 1.2
|
|