Articles
Java Platform, Standard Edition
|
|
|
|
The Java 2 Platform, Standard Edition 1.4 (J2SE 1.4) has introduced several new features and enhancements for the Common Object Request Broker Architecture (CORBA). The new features and enhancements either address changes in the standard CORBA specifications, or improve the performance of existing features. One of the important new features is the Portable Object Adapter (POA).
This article presents a detailed description of the POA and shows you how to use it to develop more portable CORBA applications. It then:
Since its inception in the early 1990's, CORBA has had to evolve to remain viable as a basis for distributed applications. As part of this continuing evolution, several significant new features have been added to CORBA 2.2 and above. One of these features is the Portable Object Adapter (POA) which has been defined in the CORBA 2.2 specification. For information about the other new features, please see New CORBA Features in J2SE 1.4.
Up to CORBA 2.1, the only standard object adapter defined by the Object Management Group (OMG) is the Basic Object Adapter (BOA), which provides basic services to allow a variety of CORBA objects to be created. ORB vendors and developers, however, discovered that the BOA is ambiguous and missing some features. This led vendors develop their own proprietary extensions, which resulted in poor portability between different ORB implementations. The new standard object adapter is the Portable Object Adapter (POA), which provides new features that allow developers to construct object implementations that are portable between different ORB products supplied by different vendors. The POA acts as a mediator between the ORB and the server application as shown in Figure 1.
The client invokes the request using a reference that refers to the target object. The request is then received by the ORB, which will dispatch the request to the POA that hosts the target object. The POA will then dispatch the request to the servant, which subsequently carries the request and sends the results back to the POA, to the ORB, and finally to the client. Since an application may have multiple POAs, in order for the ORB to dispatch the request to the right POA, it uses an object key, which is an identifier that is part of the request that is kept in the object reference. One part of the object key called the object ID is used by the POA to determine an association (such associations might be stored in a map) between the target object and a servant.
In order to create and use a POA, several steps are required. These steps may vary, however depending on the type of application being developed. The POA life cycle contains the following steps:
The first step is to get the root POA, which is managed by the ORB and provided to the application using the initial object name RootPOA. This is done as follows:
ORB orb = ORB.init(argv, null);
POA rootPOA = POAHelper.narrow(
orb.resolve_initial_references("RootPOA"));
As I mentioned earlier, the POA is an object adapter that can be used with multiple ORB products without the need for rewriting code. It is also designed to allow persistent objects (objects that are always alive even though the server that is hosting them may have been restarted). As a result, the developer is given control over the object's identity, state, storage, and life cycle. This is done through the use of policies related to threads, lifespan, object uniqueness, activation, and others.
There are seven policies that you have control over. These are:
ORB_CTRL_MODEL: This model allows multiple requests to be processed concurrently by multiple threads, and the ORB is responsible for assigning requests to threads (this is the default threading model).SINGLE_THREAD_MODEL: In this model applications don't need to be thread-aware, and therefore all requests are processed sequentially (this however is not supported in J2SE 1.4).Policy p[] = new Policy[7]; p[0] = rootPOA.createt_thread_policy( ThreadPolicyValue.ORB_CTRL_MODEL)In this example, we create an array of seven policy items. In the first item we have created a thread policy. Note however that the thread policy we created is the default anyway. This is a hypothetical example just to show you how to create policies.
TRANSIENT: The objects cannot outlive the POA instance in which they are created (this is the default policy).PERSISTENT: The objects can outlive the process in which they are created.p[1] = rootPOA.create_lifespan_policy( LifespanPolicyValue.PERSISTENT);As you can see from the above two example policies, to create a policy use the
create_nameOf_policy(policNameValue.value) syntax. UNIQUE_ID: Servants support exactly one object ID (this is the default).MULTIPLE_ID: A servant may support one or more object IDs.p[2] = rootPOA.create_id_uniqueness_policy( IdUniquenessPolicyValue.MULTIPLE_ID);
USER_ID: Objects are assigned unique IDs only by the application.SYSTEM_ID: Objects are assigned unique IDs by the POA (this is the default). Note that if the lifespan policy is set to PERSISTENT then assigned object IDs must be unique across all instantiations of the same POA.p[3] = rootPOA.create_id_assignment_policy( IdAssignmentPolicyValue.USER_ID);
RETAIN: Indicates the POA will retain active object in a map (this is the default).NON_RETAIN: Active objects are not retained.p[4] = rootPOA.create_servant_retention_policy( ServantRetentionPolicyValue.NON_RETAIN);
USE_ACTIVE_OBJECT_MAP_ONLY: An OBJECT_NOT_EXIST exception is thrown if the object ID is not found in the active object map (this is the default). Note however, in order to use this you must set the ServantRetentionPolicyValue to RETAIN.USE_DEFAULT_SERVANT: If the object ID is not found in the active object map or the servant retention policy is set to NON_RETAIN then the request is dispatched to the default servant.USE_SERVANT_MANAGER: If the object ID is not found in the active object map or the NON_RETAIN server retention policy is present, the servant manager is given the opportunity to locate or activate a servant or raise an exception.p[5] = create_request_processing_policy( RequestProcessingPolicyValue.USE_DEFAULT_SERVANT);
IMPLICIT_ACTIVATION: Implicit activation of servants. Note however that this requires SYSTEM_ID and RETAIN policies to be present.NO_IMPLICIT_ACTIVATION: No implicit servant activation.Note that the root POA always has the following policies:
ORB_CTRL_MODEL.TRANSIENT.UNIQUE_ID.SYSTEM_ID.RETAIN.USE_ACTIVE_OBJECT_MAP_ONLY.IMPLICIT_ACTIVATION.Create a new POA to allow you to define specific policies. A new POA is created as a child of an existing POA using the create_POA on the parent POA. When creating a new POA, you need to pass the following information:
null is passed then a new POA manager will be created.The following snippet of code shows how a POA is created:
POA poa = rootPOA.create_POA( "childPOA", null, policy);
A POAManager is associated with one or more POA objects. It is responsible for controlling the processing state of the POAs. The POAManager may have one of the following states:
When a POAManager object is created, it is in a HOLD state by default. In other words, it is not automatically activated. It is activated as follows:
poa.the_POAManager().activate();
Without this statement, all calls to a servant will hang in a queue because the POAManager is in a HOLD state.
If the USE_DEFAULT_SERVANT policy is set, the server application requests the POA to activate unknown objects by having the POA invoke a single servant no matter what the object ID is. The server application registers this servant with set_servant.
If, on the other hand, the RETAIN policy is in effect, the servant and its associated object ID are entered into the active object map of the appropriate POA. This activation can be accomplished in one of the following three ways:
activate_object or activate_object_with_id operations.set_servant_manager operation.IMPLICIT_ACTIVATION policy is also set, the POA may implicitly activate an object when the server application attempts to obtain a reference for a servant that is not already active.If, however, the NON_RETAIN policy is in effect, the POA may use either a default servant or a servant manager to locate an active servant. Note, however, that from the POA's point of view, the servant is active only for the duration of that one request. The POA doesn't enter the servant-object association into the active object map.
Once an object reference is created in a server, it can be exported to clients. An object reference contains information related to object identity and others required by the ORB to identify and locate the server and the POA with which the object is associated. Object references can be created in three ways:
servant_to_reference operation to map an activated servant to its corresponding object reference:
org.omg.CORBA.Object obj =
orb.resolve_initial_references("NameService");
NamingContextExt rootctx =
NamingContextExtHelper.narrow(obj);
NameComponent nc[] = rootctx.to_name(
"PersistentMathServer");
rootctx.rebind(nc,
poa.servant_to_reference(servant));
|
Context ctx = new InitialContext();
ctx.rebind("MathServer",
poa.create_reference_with_id(
id, tie._all_interfaces(poa, id)[0]));
IMPLICIT_ACTIVATION policy set.Now let's see how to use the POA to develop a CORBA application. The application that we will develop here is an array adder: the client provides two arrays and the server adds them together and sends the result back to the client. We will develop two versions of the application: a transient server and a persistent server.
The first step in developing any CORBA application is to define the interface in the OMG Interface Definition Language (IDL). The IDL interface for the array adder is shown in Code Sample 1. Here I define a module ArithApp (which is equivalent to a Java package), an interface Add that contains a constant, a new data type array (which is a synonym for an array of longs and an operation addArrays that takes in two arrays as input (specified using the in) and another array as the output holder (specified using the out).
module ArithApp {
interface Add {
const unsigned short SIZE=10;
typedef long array[SIZE];
void addArrays(in array a, in array b,
out array result);
};
};
|
You can now compile this IDL interface to map it to Java and generate stubs and skeletons. This can be done using the idlj compiler. When you run this tool you can request it to generate client stubs only, server side skeletons only, or both. Here you want to generate both, client stubs and server skeletons. To do so use the following command:
prompt> idlj -fall Add.idl
This command will generate several files. Check the local directory where you run the command from to see the files. You will notice that a new subdirectory with the name ArithApp has been created. This is because an OMG IDL module is mapped to a Java package. For more information on the idlj compiler and the options you can use, please see the IDL-to-Java Compiler.
Note: The new
idljcompiler in J2SE 1.4 generates server-side mappings for the Portable Object Adapter (POA). The new compiler is, however, backward compatible with earlier releases since it provides the-ImplBaseflag that can be used to generate server-side mappings for existing applications that have been created using J2SE 1.3 or earlier versions. Therefore, in order to talk to existing applications that have been created using J2SE 1.3 or earlier, you need to use the-ImplBaseflag to generate server-side mappings. New applications do not need to generate these deprecated server-side mappings.
The next step is to implement the IDL interface in Code Sample 1. An implementation is shown in Code Sample 2. The AddImpl class is a subclass of AddPOA, which is generated by the idlj compiler from the IDL interface. Note the third parameter to the addArrays operation. Here I am using an array holder simply because I am using the out parameter as a holder for the output array.
import ArithApp.*;
import org.omg.CORBA.*;
class AddImpl extends AddPOA {
private ORB orb;
public AddImpl(ORB orb) {
this.orb = orb;
}
// implement the addArrays() method
public void addArrays(int a[], int b[],
ArithApp.AddPackage.arrayHolder result) {
result.value = new int[ArithApp.Add.SIZE];
for(int i=0; i<ArithApp.Add.SIZE; i++) {
result.value[i] = a[i] + b[i];
}
}
}
|
The next step is to develop the server. A sample server is shown in Code Sample 3. The server performs the following tasks:
RootPOA and activates the POAManager.
import ArithApp.*;
import org.omg.CORBA.*;
import org.omg.CosNaming.*;
import org.omg.PortableServer.*;
import org.omg.PortableServer.POA;
import org.omg.CosNaming.NamingContextPackage.*;
public class AddServer {
public static void main(String args[]) {
try{
// create and initialize the ORB
ORB orb = ORB.init(args, null);
// create an implementation and register it with the ORB
AddImpl impl = new AddImpl(orb);
// get reference to rootpoa & activate the POAManager
POA rootpoa = POAHelper.narrow(
orb.resolve_initial_references("RootPOA"));
rootpoa.the_POAManager().activate();
// get object reference from the servant
org.omg.CORBA.Object ref =
rootpoa.servant_to_reference(impl);
Add href = AddHelper.narrow(ref);
// get the root naming context
// NameService invokes the name service
org.omg.CORBA.Object objRef =
orb.resolve_initial_references("NameService");
// Use NamingContextExt which is part of the Interoperable
// Naming Service (INS) specification.
NamingContextExt ncRef =
NamingContextExtHelper.narrow(objRef);
// bind the Object Reference in Naming
String name = "Add";
NameComponent path[] = ncRef.to_name( name );
ncRef.rebind(path, href);
System.out.println("AddServer
ready to add up your arrays ....");
// wait for invocations from clients
orb.run();
} catch (Exception e) {
System.err.println("ERROR: " + e);
e.printStackTrace(System.out);
}
System.out.println("AddServer Exiting ....");
}
}
|
Now, implement the client. A sample client is shown in Code Sample 4. The client code performs the following tasks:
addArrays method and prints the results.
import ArithApp.*;
import org.omg.CORBA.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
public class AddClient {
public static void main(String args[]) {
try {
// create and initialize the ORB
ORB orb = ORB.init(args, null);
// get the root naming context
org.omg.CORBA.Object objRef =
orb.resolve_initial_references("NameService");
// Use NamingContextExt instead of NamingContext. This is
// part of the Interoperable Naming Service.
NamingContextExt ncRef =
NamingContextExtHelper.narrow(objRef);
// resolve the Object Reference in Naming
String name = "Add";
Add impl = AddHelper.narrow(ncRef.resolve_str(name));
System.out.println("Handle
obtained on server object: " + impl);
// the arrays to be added
int a[] = {6, 6, 6, 6, 6, 6, 6, 6, 6, 6};
int b[] = {7, 7, 7, 7, 7, 7, 7, 7, 7, 7};
// the result will be saved in this new array
ArithApp.AddPackage.arrayHolder c =
new ArithApp.AddPackage.arrayHolder();
// invoke the method addArrays()
impl.addArrays(a, b, c);
// print the new array
System.out.println("The sum of the two arrays is: ");
for(int i=0;i<ArithApp.Add.SIZE;i++) {
System.out.println(c.value[i]);
}
} catch (Exception e) {
System.out.println("ERROR : " + e) ;
e.printStackTrace(System.out);
}
}
}
|
Now you can compile the classes AddImpl, AddServer, AddClient, and the stubs and skeletons that were generated by the idlj compiler. This is done using the javac compiler as follows:
prompt> javac *.java ArithApp/*.java
To run the application:
orbd, which is a name server:
prompt> orbd -ORBInitialPort 2500
The number 2500 is the port number where you want the orbd to run. Note that the -ORBInitialPort is a require command-line argument. AddServer:
prompt> java AddServer -ORBInitialPort 2500
This command starts the server as shown in Figure 2. AddServer and orbd are running on the same host. If the orbd is running on a different host, use the -ORBInitialHost argument to inform the server where to find the orbd. AddClient:
prompt> java AddClient -ORBInitialPort 2500
You should see the sum of the two arrays as shown in Figure 3. The CORBA Common Object Services (or COS Naming Service) provides a tree-like directory of object references much like a filesystem provides a directory of files. In the previous version of the JDK, the tnameserv was part of the release. The tnameserv is a transient naming service that retains naming contexts as long as it is running. If the naming service is shutdown, all naming contexts are lost. The tnameserv is shipped with J2SE 1.4 for backward compatibility.
In the J2SE 1.4, the ORBD (ORB Daemon) includes a transient naming service and a persistent naming service. Both of these services are an implementation of the COS Naming Service. Unlike the transient naming service, the persistent naming service provides a persistent storage for naming contexts. In other words, in case the ORBD is restarted, the persistent naming service will restore all naming contexts.
Both the client and the server starts by obtaining the root naming context. This can be done as shown in the following snippet of code:
org.omg.CORBA.Object objRef =
orb.resolve_initial_references("NameServce");
NamingContextExt ctx =
NamingContextExtHelper.narrow(objRef);
If you are using the transient naming service tnameserv of a release prior to J2SE 1.4, the first line of the above segment of code returns an object reference to the transient naming service. This object reference, objRef, is a generic CORBA object, and in order for it to be used as a NamingContextExt object, you must cast it to the proper type. This casting is done using the narrow method in CORBA. On the other hand, if you are using the orbd of the J2SE 1.4, the above segment of code returns an object reference to the persistent naming service. To specify that you want to use the transient naming service with the orbd, pass in the string TNameService instead of NameService:
org.omg.CORBA.Object objRef =
orb.resolve_initial_references("TNameServce");
NamingContextExt ctx =
NamingContextExtHelper.narrow(objRef);
Note that the NamingContext Ext and NamingContext ExtHelper are new classes in the J2SE 1.4. This extension is part of the Interoperable Naming Service (INS), which is a URL-based naming system on top of the CORBA Naming Service and a common bootstrap that allows applications to share a common initial naming context. The INS, which is an extension of the COS Naming Service, provides new features including:
corbaloc: and corbaname: formats).NamingContextExt for converting between CosNames, URLs, and Strings.The INS allows the following stringified object reference formats:
orb.object_to_string(objRef) method.corbaloc and corbaname enable you to provide a URL to access CORBA objects. The corbaloc can be used to resolve to a particular CORBA service without the need to go through a naming service, and the corbaname can be used to resolve a stringified name from a specific naming context. For example, the following snippet of code corbaloc:iiop:1.2@SomeDomain.com:3000/TraderService TraderService from the host SomeDomain.com on port 3000. corbaname::SomeDomain.com:4000#conference/speakers conference/speakers. SomDomain.com is the host and the port number is 4000.We will now see how to develop a persistent server where the objects outlive the process that created them. We will follow the same steps as in the previous examples: develop an IDL interface, implement the interface, develop the server and client.
If you wish to run this application, you may want to create a new directory and copy the Add.idl file, of Code Sample 1, to it. We will be using the same IDL interface in this example.
Compile the Add.idl interface using the idlj compiler:
prompt> idlj -fall Add.idl
This command generates client stubs and server skeletons.
The next step is to implement the interface. The implementation is similar to that in Code Sample 2. Here, however, we call the implementation the AddServant. The servant is shown in Code Sample 5.
Code Sample 5: AddServant.java
import ArithApp.*;
import org.omg.CORBA.ORB;
class AddServant extends AddPOA {
private ORB orb;
public AddServant(ORB orb) {
this.orb = orb;
}
// implement the addArrays() method
public void addArrays(int a[], int b[],
ArithApp.AddPackage.arrayHolder result) {
result.value = new int[ArithApp.Add.SIZE];
for(int i=0; i<ArithApp.Add.SIZE; i++) {
result.value[i] = a[i] + b[i];
}
}
}
|
The next step is to implement the persistent server. My implementation is shown in Code Sample 6. This server performs the following tasks:
Code Sample 6: AddServer3.java
import ArithApp.*;
import org.omg.CORBA.ORB;
import org.omg.CORBA.Object;
import org.omg.CosNaming.NameComponent;
import org.omg.CosNaming.NamingContextExt;
import org.omg.CosNaming.NamingContextExtHelper;
import org.omg.CORBA.Policy;
import org.omg.PortableServer.Servant;
import org.omg.PortableServer.*;
import org.omg.PortableServer.POA;
public class AddServer3 {
public static void main(String args[]) {
try {
// create and initialize the ORB
ORB orb = ORB.init(args, null);
// create servant and instantiate it
AddServant servant = new AddServant(orb);
// get reference to rootpoa and activate the POAManager
POA rootpoa = POAHelper.narrow(
orb.resolve_initial_references("RootPOA"));
// Create the Persistent Policy
Policy[] policy = new Policy[1];
policy[0] = rootpoa.create_lifespan_policy(
LifespanPolicyValue.PERSISTENT);
// Create a persistent POA by passing the policy
POA poa = rootpoa.create_POA("childPOA", null, policy );
// Activate PersistentPOA's POAManager. Without this
// all calls to persistent server will hang
// because POAManager
// will be in the 'HOLD' state.
poa.the_POAManager().activate( );
// Associate the servant with PersistentPOA
poa.activate_object( servant );
// Resolve RootNaming context and bind a name
// for the servant.
// "NameService" is used here....persistent name service.
org.omg.CORBA.Object obj =
orb.resolve_initial_references("NameService" );
NamingContextExt rootContext =
NamingContextExtHelper.narrow(obj);
//bind the object reference in the naming context
NameComponent[] nc =
rootContext.to_name("AddServer");
rootContext.rebind(nc, poa.servant_to_reference(servant));
// wait for client requests
orb.run();
} catch (Exception e) {
System.err.println("Exception
in AddServer3 Startup " + e);
}
}
}
|
The last step is to implement the client. A Sample client is shown in Code Sample 7. The client performs the following tasks;
AddServant by using the Interoperable Naming Service corbaname URL. The URL locates the Naming Service running on the localhost and listening on port 2900. When located, it resolves "AddServer" from the Naming Service.addArrays and prints the result. In this example, the client calls the addArrays method every 6 seconds. If the server is down the next time the client makes the request, the client will restart the server because of the persistence lifespan.Code Sample 7: AddClient3.java
import ArithApp.*;
import org.omg.CORBA.ORB;
import org.omg.CORBA.OBJ_ADAPTER;
import org.omg.CosNaming.NamingContext;
import org.omg.CosNaming.NamingContextHelper;
import org.omg.CosNaming.NameComponent;
import org.omg.PortableServer.POA;
public class AddClient3 {
public static void main(String args[]) {
try{
// create and initialize the ORB
ORB orb = ORB.init(args, null);
org.omg.CORBA.Object obj = orb.string_to_object(
"corbaname::localhost:2900#AddServer");
Add impl = AddHelper.narrow(obj);
// the arrays to be added
int a[] = {6, 6, 6, 6, 6, 6, 6, 6, 6, 6};
int b[] = {7, 7, 7, 7, 7, 7, 7, 7, 7, 7};
// the result will be saved in this new array
ArithApp.AddPackage.arrayHolder c =
new ArithApp.AddPackage.arrayHolder();
while(true) {
System.out.println("Calling
the persistent AddServer3..");
impl.addArrays(a, b, c);
// print the new array
System.out.println("The sum of the two arrays is: ");
for(int i=0;i<ArithApp.Add.SIZE;i++) {
System.out.println(c.value[i]);
}
System.out.println("...will
call the server again in a few seconds....");
System.out.println("...if the
server is down, it will be automatically restarted...");
Thread.sleep(6000);
}
} catch ( Exception e ) {
System.err.println( "Exception in AddClient3..." + e );
e.printStackTrace( );
}
}
}
|
Now you can compile the classes AddServant, AddServer3, AddClient3, and the stubs and skeletons that were generated by the idlj compiler. This is done using the javac compiler as follows:
prompt> javac *.java ArithApp/*.java
To run the application:
orbd, which is a name server:
prompt> orbd -ORBInitialPort 2900
The number 2900 is the port number where you want the orbd to run. Note that the -ORBInitialPort is a required command-line argument. AddServer3: servertool, which is a command-line interface for developers to register, unregister, startup, and shutdown a persistent server. servertool can be started as follows (it must be started on the same port number as the orbd):
prompt> servertool -ORBInitialPort 2900
You should see the servertool command line appears as follows: servertool> orbd and servertool are running on the same host. If you wish to run the servertool on a different host, use the -ORBInitialHost command-line argument to specify the host where the orbd is running. AddServer3 using the register command. You need to specify the name of the server, the name of the application, and the classpath to the server class as shown in Figure 4. servertool registers the server and assigns it a unique ID (257 in this case) that can be used later on for housekeeping activities. If you try to register a server that is already registered, the ID will be 0. servertool commands, type help at the command prompt as shown in Figure 5. servertool, please see the Java IDL Server Tool. AddClient3:
prompt> java AddClient3 -ORBInitialPort 2900
You should see the client printing the sum of the two arrays. shutdown command to shut it down. Information about servers and their states is maintained by the orbd. When you run the orbd, it creates a subdirectory under the directory it was started from. The name of the subdirectory is, by default, orb.db. This subdirectory contains information about servers that have been registered and log files for them. For example, if you look under the orb.db/logs subdirectory you will see files with names such as 257.out and 257.err. These files are used to record the starting and shutdown time for servers as well as any errors encountered by the server.
Conclusion
Now that you've seen an example of how to develop CORBA applications with the new POA and seen how to develop transient and persistent servers, you can explore the new CORBA features in J2SE 1.4 more fully on your own.
If you have existing CORBA applications that use the BOA, you may want to rewrite them using the POA. The end result will be that your applications are portable across ORBs from different vendors.
- CORBA Specification (OMG)
- Distributed Programming with Java book (Chapter 11: Overview of CORBA):
- Java IDL Documentation
- Distributed Java Programming with CORBA and RMI
- New Features in CORBA 3.0
- Changes in CORBA Features between J2SE 1.3 and 1.4
Qusay H. Mahmoud has published dozens of articles for the Java Developer Connection and Java Wireless Developer Initiative. He has also presented tutorials at a number of international conferences. He is the author of Distributed Programming with Java (Manning Publications, 1999) and Wireless Java (O'Reilly & Associates, 2002).