Chapter 2 Importing and Exporting Object
References
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().
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).