Chapter 2 Importing and Exporting Object References
- Introduction
- Servers should Conditionally Export Object References
- Flexibility in Importing/Exporting Object References
- Implementing Import/Export Algorithms as Java Classes
- Benefits of importObjRef() and exportObjRef()
2.1 Introduction
Although a CORBA server may contain many objects, it is typical for just one or two of these objects to be the initial point(s) of contact for client applications. For example, consider a server that contains one FooFactory object and many Foo objects. When the server starts up, it might export (advertise) an object references for its FooFactory object. When a client application starts, it imports the reference to the FooFactory object and then invokes, say, lookup() or create() operations on it to get access to some Foo objects. The pseudo-code below illustrates the main() function of a typical CORBA server that exports one object reference:
1 main(int argc, char ** argv) 2 { 3 orb = CORBA::ORB_init(argc, argv); 4 obj = ...; 5 exportObjRefToNamingService(obj, ...); 6 orb->run(); 7 }
The above code initializes CORBA (line 3). It then creates one or more objects (line 4) and exports one of these (line 5) to, say, the Naming Service, before going into the event loop (line 6) to receive incoming requests. For the purposes of brevity, some details have been omitted, such as creation of a POA hierarchy and activation of POA managers. The above code contains two common flaws:
- The server unconditionally (re-)exports the object reference every time the server is run. Doing this might appear to be a harmless practice, but it can cause problems in some kinds of deployment.
- The server is hard-coded to export the object reference to the Naming Service (line 5). Although the Naming Service is a popular place for exporting object references, the decision on where to export object references should be left to deployment time rather than being hard-coded at compilation time. For example, some users may prefer to export object references with another technology, such as, say, a Trading Service, a file, a database or FTP. A server that is hard-coded to export object references with one technology can prove to be inconvenient.
The rest of this paper discusses these two problems and explains how they can be overcome in simple, yet very effective ways.
2.2 Servers should Conditionally Export Object References
The pseudo-code below illustrates a good, high-level structure for a server that exports one object reference:
1 main(int argc, char ** argv) 2 { 3 orb = CORBA::ORB_init(argc, argv); 4 parseCmdLineArgs(argc, argv, exportMode, runMode); 5 obj = new ...; 6 if (exportMmode) { 7 exportObjRef(orb, obj, "..."); 8 } 9 if (runMode) { 10 orb->run(); 11 } 12 }
The above code initializes CORBA (line 3) and then parses any remaining command-line arguments (line 4). Very importantly, the code checks for the presence of command-line options called, say, -export and -run and sets boolean variables exportMode and runMode to true if these options are used (line 4). The server exports its main object reference (line 7) only if the -export option was used. Likewise, it goes into the event loop (line 10) only if the -run option was used.
Designing a server to support the -export and -run options provides useful flexibility. For example:
- Some people may wish to re-export object references every time a server is restarted. Developers typically find this mode of operation to be convenient. This can be achieved by always specifying both -export and -run command-line options when running the server.
- Other people may prefer to export an object reference just
once, during the initial installation of a server. This
can be achieved by running the server with just the
-export option, which causes the server to export its
object reference and immediately die (because the -run
option was not given). Once the server has been installed,
thereafter the server can be launched with just the -run
option, which will cause the server to go into its event loop
without re-exporting its object reference.
This mode of launching a server without re-exporting its object reference is vitally important if the deployment site is running, say, the Orbix Naming Service in replicated mode. In this mode, one replica of the Naming Service is the master and it can both read and update its database. All the other replicas of the Naming Service are slaves and they have read-only access to the database. If a slave receives a request that involves updating the database then it forwards that request to the master. If the master replica is not running then the Naming Service becomes read only until the master is restarted. The main purpose of having a replicated Naming Service is to prevent it from becoming it a single point of failure. This scheme works well if the only time a server exports an object reference is when the server is being installed and thereafter the server does not attempt to re-export an object reference whenever it is (re-)started. However, if a server insists on always exporting an object reference whenever it is re-started then the server may fail to start up if the master replica of the Naming Service is currently not running, thereby defeating the purpose of having a replicated Naming Service.
The point is that developers should provide a mechanism that allows exporting of object references and running of the server to be performed independently of each other. This then allows others to choose the policy of how the server should be used in practice. This flexibility is important because developers do not always know how others (such as system administrators) might like to deploy applications. Failing to provide this flexibility can actually cause significant hindrance, as discussed above in the case where a deployment site has a replicated Naming Service.
It may be useful to provide applications with a third option called, say, -install. When a server is run with this option, it performs all the steps required to install the server, such as set up configuration files and databases, register the server with the implementation repository and, of course, export an object reference. Alternatively, some people prefer to write a separate install utility to perform these steps, rather than embed this functionality into the executable of a server application. If you do provide an -install command-line option for a server then it is still useful to provide the -export option because a user may want to do a normal install initially and then later re-export the server’s object reference to a different location.
2.3 Flexibility in Importing/Exporting Object References
Some CORBA clients and servers are hard-coded to import and export object references through text files. This is especially common in demonstration programs in magazine articles or supplied with CORBA vendor products. This approach is simple but it suffers from a lack of geographic scalability because it requires that a client and server have access to a shared file system. Obviously, this will be the case if the client and server are running on the same computer, and it may be the case if they are running on different computers in the same local area network. However, it is rarer for a shared file system to span a wider area network. For this reason, many programmers are told that it is a bad idea to import and export object references through text files, and that use of the Naming Service is better. The result is that many programmers hard-code their clients and servers to import and export object references through the Naming Service.
In reality, it is a bad idea for a programmer to hard-code use of any particular import/export technology (such as text files or the Naming Service) in clients or servers. This is because applications have a habit of being used in ways that their developers did not foresee. For example, a developer might think that importing/exporting object references with the Naming Service is a good idea, but an end user might prefer to import/export with a database, or FTP, or even email. Sometimes the developer works in the same organization where the application is going to be deployed and hence s/he knows what is the preferred technology for importing/exporting object references. However, even in these situations, there might be a change in the preferred import/export technology in a few months time for any variety of reasons. For example, as the organization’s use of CORBA changes, they might prefer to import/export with a Trading Service rather than with a Naming Service. Alternatively, a system administrator might not want to allow a Naming Service to be accessed across a firewall and it might become more convenient to import/export object references through FTP instead. It is time consuming and expensive to have to modify an existing application’s hard-coded import/export logic whenever a change occurs in the organization’s preference for how object references should be imported or exported.
A better approach is for an application to offer some flexibility in how it imports/exports object references. Ideally, an application should be able to import/export object references with any technology (text files, Naming Service, Trading Service, fax, email, FTP, databases or whatever), and the choice of which technology to use should be left to deployment time. Achieving this goal turns out to be surprisingly easy, as we now discuss. Consider the following two utility functions (shown first in C++ and then in Java):
// C++ version (defined in "import_export.h") namespace corbautil { CORBA::Object_ptr importObjRef( CORBA::ORB_ptr orb, const char * instructions) throw(ImportExportException); void exportObjRef( CORBA::ORB_ptr orb, CORBA::Object_ptr obj, const char * instructions) throw(ImportExportException); }; // Java version package com.iona.corbautil; import org.omg.CORBA.*; public class ImportExport { static public org.omg.CORBA.Object importObjRef( ORB orb String instructions) throws ImportExportException; static public void exportObjRef( ORB orb org.omg.CORBA.Object obj, String instructions) throws ImportExportException; }
As their names suggest, importObjRef() and exportObjRef() are used to import and export object references. Both take a reference to an ORB as a parameter, so that they can invoke operations upon it—for example, string_to_object(), object_to_string() or resolve_initial_references()—as an aid to importing or exporting an object reference. Both functions also take an instructions parameter that specifies how the object reference should be imported or exported. This parameter will be discussed in detail shortly. Finally, the exportObjRef() function takes another parameter, which is the object reference to be exported, while importObjRef() returns the imported object reference.
2.3.1 Instructions Passed to exportObjRef()
The instructions parameter passed to exportObjRef() is a string that can be in any of the following formats:
-
"name_service#path/in/naming/service"
This uses resolve_initial_references("NameService") to connect to the Naming Service and then exports an object reference to the specified path within that Naming Service.- Example: "name_service#foo/bar/acme"
- "name_service#path/in/naming/service @ import-instructions"
A Naming Service is contacted by passing the specified import-instructions to importObjRef(). The object reference is then exported to the specified path within that Naming Service.- Example: "name_service#foo/bar/acme @ IOR:..."
- Example: "name_service#foo/bar/acme @ corbaloc:..."
- "file#path/to/file"
This exports an object reference by stringifying it and writing it to a file.- Example: "file#/tmp/obj_ref.ior" (full path to file)
- Example: "file#obj_ref.ior" (relative filename)
- "exec#command with IOR place-holder"
This exports an object reference by stringifying it and passing it as a command-line argument to the specified command that is executed. The IOR place-holder in the command is replaced with the stringified object reference before the command is executed.- Example: "exec#/usr/bin/perl some_script.pl IOR"
- Example: "exec#cmd /c echo IOR > /tmp/obj_ref.ior"
- Example: "exec#nsadmin -b foo/bar/acme IOR"
- "corbaloc_server#name"
This uses proprietary APIs in the CORBA product to make the object accessible to a client that uses a corbaloc URL that contains the server’s host and port, and the specified name. Currently, this variant works with Orbix, Orbacus, TAO and omniORB. - "java_class#fully.scoped.class.name with optional
arguments"
This variant works only with the Java implementation. It exports an object reference by using Java’s reflection APIs to create an instance of the specified class and invoking exportObjRef() upon it.- Example: "java_class#full.package.name.of.class"
The "name_service#..." and "file#..." variants are provided because users commonly want to export with the Naming Service or a file. The "exec#..." variant is provided as a fallback mechanism in case users want to export an object reference with a different technology. Specifically, many technologies (such as FTP, databases, email and so on) can be manipulated by command-line utilities. Exporting an object by such a technology is made possible by an "exec#..." instruction.
The "exec#..." instruction subsumes the power of both "name_service#..." and "file#..." because it can execute command-line utilities that will bind (advertise) an object reference into the Naming Service or write a stringified object reference to a file. However, the "name_service#..." and "file#..." variants are provided both for convenience and for efficiency.
The "java_class#..." variant is supported only in the Java implementation. This variant allows developers to implement alternative algorithms for exportObjRef() in Java. Details on how to write such algorithms are given in Section 2.4.
2.3.2 Instructions Passed to importObjRef()
The instructions parameter passed to importObjRef() is a string that can be in any of the following formats:
- "name_service#path/in/naming/service"
This uses resolve_initial_references("NameService") to connect to the Naming Service and then imports an object reference from the specified path within that Naming Service.- Example: "name_service#foo/bar/acme"
- "name_service#path/in/naming/service @ import-instructions"
A Naming Service is contacted by passing the specified import-instructions to importObjRef(). An object reference is then imported from the specified path within that Naming Service.- Example: "name_service#foo/bar/acme @ IOR:..."
- Example: "name_service#foo/bar/acme @ corbaloc:..."
- "file#path/to/file"
This reads a stringified object reference from a file.- Example: "file#/tmp/obj_ref.ior" (full path to file)
- Example: "file#obj_ref.ior" (relative filename)
- "exec#command"
This imports an object reference by executing the specified command and interpreting the standard output of that command as a stringified object reference.- Example: "exec#/usr/bin/perl some_script.pl"
- Example: "exec#cat /tmp/obj_ref.ior"
- Example: "exec#nsadmin -r foo/bar/acme"
- "java_class#fully.scoped.class.name with optional
arguments"
This variant works only with the Java implementation. It imports an object reference by using reflection APIs to create an instance of the specified class and then invoking importObjRef() upon it.- Example: "java_class#full.package.name.of.class"
- "IOR:...", "corbaloc:..." or
"corbaname:..."
Any of these formats import an object reference by calling string_to_object().
The "name_service#...", "java_class#..." and "file#..." instruction variants have a similar format for both exportObjRef() and importObjRef(). However, the "exec#..." variant is different, depending on whether it is used in exportObjRef() or importObjRef(). In exportObjRef(), "exec#..." takes an IOR place-holder, but in importObjRef() it does not take an IOR place-holder; instead, the executed command should write the stringified object reference to its standard output.
In addition, importObjRef() can accept instructions in any of the URL formats that are specified by the CORBA specification. These include "IOR:..", "corbaloc:..." and "corbaname:...". Since the CORBA specification may define new URL formats in the future (or a CORBA vendor may support proprietary URL formats), importObjRef() assumes that any string starting with letters and a colon is a URL and passes it as a parameter to string_to_object().
2.3.3 C++ Usage
The bold code in the pseudo-code below shows how a C++ server can export an object reference with the exportObjRef() function:
1 #include "import_export.h" 2 main(int argc, char ** argv) 3 { 4 orb = CORBA::ORB_init(argc, argv); 5 parseCmdLineArgs(argc,argv, exportMode, runMode, instructions); 6 obj = ...; 7 if (exportMode) { 8 try { 9 corbautil::exportObjRef(orb, obj, instructions); 10 } 11 catch (const corbautil::ImportExportException & ex) { 12 cerr << ex << endl; 13 orb->destroy(); 14 exit(1); 15 } 16 } 17 if (runMode) { 18 orb->run(); 19 } 20 orb->destroy(); 21 }
The instructions that specify where the object reference is exported to should not be hard-coded into the application, but rather should be obtained from, say, a command-line argument (line 5) or a runtime configuration file. If exportObjRef() fails for any reason then it throws an exception that contains a descriptive error message. For this reason, the call to exportObjRef() (line 9) is enclosed in a try-catch clause. If an exception is thrown then the exception message is printed out and the application gracefully terminates.
The use of importObjRef() is similar, and is shown in bold in the pseudo-code below:
1 #include "import_export.h" 2 main(int argc, char ** argv) 3 { 4 orb = CORBA::ORB_init(argc, argv); 5 parse_cmd_line_args(instructions); 6 try { 7 obj = corbautil::importObjRef(orb, instructions); 8 } catch (const corbautil::ImportExportException & ex) { 9 cerr << ex << endl; 10 orb->destroy(); 11 exit(1); 12 } 13 ... // narrow obj and invoke upon it 14 }
2.3.4 Java Usage
The bold code in the pseudo-code below shows how a Java server can export an object reference with the exportObjRef() function:
1 import com.iona.corbautil.*; 2 import org.omg.CORBA.*; 3 ... 4 public static void main(String[] args) 5 { 6 BooleanHolder exportMode = new BooleanHolder(); 7 BooleanHolder runMode = new BooleanHolder(); 8 StringHolder instructions = new StringHolder(); 9 orb = ORB.init(args); 10 parseCmdLineArgs(args, exportMode, runMode, instructions); 11 obj = ...; 12 if (exportMode.value) { 13 try { 14 ImportExport.exportObjRef(orb, obj, 15 instructions.value); 16 } catch (ImportExportException ex) { 17 System.out.println(ex.getMessage()); 18 orb.destroy(); 19 System.exit(1); 20 } 21 } 22 if (runMode.value) { 23 orb.run(); 24 } 25 }
The instructions that specify where the object reference is exported to should not be hard-coded into the application, but rather should be obtained from, say, a command-line argument (line 10) or a runtime configuration file. If exportObjRef() fails for any reason then it throws an exception that contains a descriptive error message. For this reason, the call to exportObjRef() (line 14) is enclosed in a try-catch clause. If an exception is thrown then the exception message is printed out and the application gracefully terminates.
The use of importObjRef() is similar, and is shown in bold in the pseudo-code below:
1 import com.iona.corbautil.*; 2 import org.omg.CORBA.*; 3 ... 4 public static void main(String[] args) 5 { 6 StringHolder instructions = new StringHolder(); 7 orb = ORB.init(args); 8 parseCmdLineArgs(args, instructions); 9 try { 10 obj = ImportExport.importObjRef(orb, 11 instructions.value); 12 } catch (ImportExportException ex) { 13 System.out.println(ex.getMessage()); 14 orb.destroy(); 15 System.exit(1); 16 } 17 ... // narrow obj and invoke upon it 18 }
2.4 Implementing Import/Export Algorithms as Java Classes
The ImportExportAlgorithm interface (defined in the com.iona.corbautil package) defines the signatures of the importObjRef() and exportObjRef() methods. If you write a Java class that implements this interface then you can use that class to import/export object references by using the "java_class#full.package.name.of.class" variant of instructions.
The code below is from the ImportExportExampleAlgorithm class that is provided in the com.iona.corbautil package. This code imports/exports object references through standard input/output and provides an example of how to write your own Java classes to import/export object references.
package com.iona.corbautil; import org.omg.CORBA.*; import java.io.*; public class ImportExportExampleAlgorithm implements ImportExportAlgorithm { public void exportObjRef( ORB orb, org.omg.CORBA.Object obj, String instructions) throws ImportExportException { String strIOR = null; try { strIOR = orb.object_to_string(obj); } catch(Exception ex) { throw new ImportExportException( "export failed for instructions ‘" + instructions + "’: object_to_string() failed: " + ex); } System.out.println("instructions = ‘" + instructions + "’"); System.out.println("IOR = " + strIOR); } public org.omg.CORBA.Object importObjRef( ORB orb, String instructions) throws ImportExportException { System.out.println("instructions: " + instructions); System.out.println("Enter a stringified obj ref: "); try { BufferedReader stdin = new BufferedReader( new InputStreamReader(System.in)); String strIOR = stdin.readLine(); return orb.string_to_object(strIOR); } catch (Exception ex) { throw new ImportExportException( "import failed for " + "instructions ‘" + instructions + "’: error importing a stringified " + "object reference from the console: " + ex); } } }
2.5 Benefits of importObjRef() and exportObjRef()
The importObjRef() and exportObjRef() functions offer several benefits:
- Applications that use importObjRef() and exportObjRef() do not have to be hard-coded to use one specific import/export technology, but rather can choose which import/export technology to use at runtime. Thus, an important decision is moved from being a compile-time choice made by developers to being a deployment time choice made by users.
- These utility functions can protect developers from vendor lock-in. For example, some CORBA vendors provide proprietary load-balancing enhancements to their implementations of the Naming Service. Applications no longer need to use proprietary APIs to export an object reference to a load-balancing Naming Service. Instead, a runtime configuration file or command-line argument can specify an "exec#..." variant of instructions to export an object reference to the load-balancing Naming Service using a vendor-supplied command-line utility. If the CORBA vendor does not supply such a command-line utility then the developer can write their own such utility, or implement a Java class that provides this functionality.
- The flexibility provided by importObjRef() and exportObjRef() helps to simplify application code. As the code shown earlier illustrates, using importObjRef() and exportObjRef() in applications requires remarkably few lines of code: far fewer than a developer would write if using the raw API of, say, the Naming Service. Furthermore, not only do developers have to write fewer lines of code, the developers are also insulated from some of the complexity of the Naming Service API (or some other import/export technology).