An object adapter is the primary interface that an implementation uses to access ORB (object request broker) functions. It serves as the glue between the server applications (objects) and the ORB. An object adapter:
- Generates and interprets object references
- Authenticates the principal making the call
- Activates and deactivates the implementation
- Activates and deactivates individual objects
- Invokes methods through skeletons
Until CORBA 2.2, all ORB implementations supported a Basic Object Adapter (BOA) defined by the CORBA specification. However, the BOA suffers from one major weakness: it is vaguely defined. As a result, each vendor implemented their BOAs differently with specific value-added services, thus producing code and implementation incompatibilities. Note that these incompatibilities are not the same as inter-ORB incompatibilities produced during runtime. The IIOP (Internet Inter-Orb Protocol) specification and the mandate that all CORBA 2.0-compliant ORBs support IIOP address most inter-ORB compatibility issues. The incompatibilities created by varying BOA implementations are either compile errors or the more subtle (and worse) errors caused by the application's behavioral differences; for example, corruption can result from improper thread synchronization when one vendor's BOA is thread safe while another vendor's BOA is not. Other examples include performance problems, scalability issues, and even race conditions due to varying thread management algorithms in different BOAs.
The OMG (Object Management Group) introduced the Portable Object Adapter (POA) to replace BOA in CORBA 2.2. The POA specification details the precise POA architecture and operation characteristics; its tone proves more mandatory than suggestive. The bottom line: An object adapter implementation should result in code (and, probably more important, skill) portability across ORBs. In fact, the CORBA specification lists that aspect as one of the top success factors in the POA design.
As part of this four-part series on enterprise CORBA development, in this article, I begin exploring J2SE (Java 2 Platform, Standard Edition) 1.4's POA support.
Read the whole series, "J2SE 1.4 Breathes New Life into the CORBA Community:"
- Part 1: Get started developing enterprise CORBA applications with the latest version of J2SE
- Part 2: Gain code portability with the Portable Object Adapter
- Part 3: Create enterprise-level apps with the POA
- Part 4: Portable interceptors and the Interoperable Naming Service
The CORBA object lifecycle within the POA context
To start, let's go over the differences between a CORBA object, an object reference, and a servant.
The object concept is probably one of the most overloaded, overused, and yet vague concepts in software engineering. In the CORBA world, an object is a programming entity with an identity, an IDL (interface definition language)-defined interface, and an implementation. An object is an abstract concept and cannot serve client requests. To do so, an object must be incarnated or given bodily form—that is, its implementation must be activated. The servant gives the CORBA object its implementation. At any moment, only one servant incarnates a given object, but over an object's lifetime, many (different) servants can incarnate the object at different points in time. Examine Figure 1 to get a better feel for these concepts.
Note the following regarding Figure 1:
- The terms creation and destruction apply to objects, while the terms incarnation and etherealization apply to servants.
- Once an object is created, it can alternate between many activations and deactivations during its lifetime.
- To serve requests, an object must:
- Be activated if it is not active.
- Be associated with a servant if it does not already have one. Just because an object is active does not mean that it has an associated servant. As you will see later, you can configure/program the POA to use a new servant upon request.
- Once an object is destroyed, it ceases to exist, and no more requests to that object will be served.
A client views a CORBA object as an object reference. The fact that a client has an object reference does not mean that a servant is incarnating the object at that time. In fact, the object reference's existence does not indicate an object's existence. If the object does not exist (that is, it has been destroyed), the client will receive an
OBJECT_NOT_EXIST error when it tries to access the object using the object reference. However, as noted above, if the object is in a deactivated condition, it will activate, a process transparent to the client.
Based on their lifetime characteristics, CORBA objects are either persistent or transient.
A persistent object is a CORBA object that can live beyond the process that creates or activates it. The object reference for a persistent CORBA object has the following information:
- The name of the POA that created it
- The object's object ID
- The host name (address) and port number
The host name and port number may be the server's or an implementation repository's (such as ORBD (Object Request Broker Daemon), which is provided with J2SE 1.4). If the host name and port number belong to the server, then the process of locating the server based on the object reference is called direct binding. J2SE 1.4 does not support direct binding for persistent objects. Although direct binding proves efficient, it has two major drawbacks:
- The server must be up and running (and listening for binding requests, which the ORB runtime handles). Therefore, the server consumes system resources, such as CPU time and memory, even if no clients are interested in it. Note: If the server is not up and running, the client will not receive an
OBJECT_NOT_EXISTerror, but rather a
TRANSIENTerror, which basically means try back later.
- The server must always run on the same host and listen to the same port. Direct binding does not intrinsically support failover and relocation of the CORBA object.
An implementation repository such as ORBD allows:
- Server processes to start only when their services (that is, objects within them) are required. Most implementation repositories also have a shutdown time, after which a server process will shut down if it is not being used.
- Fault tolerance and server relocation. As long as the implementation repository is running on the same host and port, servers can be relocated at will.
Transient objects are short-lived objects tied to the lifetime of the process, and more specifically the POA, that created the object.
A transient CORBA object's object reference has the same information as a persistent CORBA object's object reference, with the only difference being the POA name contained within it. A POA that creates transient objects, a transient POA, has a name unique in space and time with respect to all POA names. When a POA becomes a transient POA, the ORB prefixes or appends a UUID (universally unique ID) to the original name the POA received during its initial creation. As you will learn later in the discussion on POA creation and policies, a POA can create either persistent or transient references, but not both.
Some salient features about binding with transient objects:
- Transient objects always use direct binding.
- If the process that created the reference shuts down, then:
TRANSIENTerror returns if no server process is running.
OBJECT_NOT_EXISTerror returns if a server process, even the original one, is running. Why? Because, each time a transient POA is created, the ORB creates a new unique name for it. Therefore, the POA name contained in the previously created object reference will not exist, and hence, the object will not exist.
Basic POA programming
Programming with POA is fairly straightforward. In this section, I explain the fundamentals of programming CORBA applications using POA. For examples, I refer to the Hello World application from Part 1.
Obtain the RootPOA
All ORBs have one POA called
RootPOA. You obtain a reference to this POA the same way you obtain a reference to any other service managed by the ORB—use the
resolve_initial_references() method on an initialized ORB instance and pass
RootPOA as the name of the service to resolve. The first few lines of code in the
HelloServer class demonstrates how to obtain a
. . . // Create and initialize the ORB //ORB orb = ORB.init(args, null); ORB orb = ORB.init(args, System.getProperties()); // Get reference to RootPOA POA rootPOA = POAHelper.narrow(orb.resolve_initial_references("RootPOA")); . . .
Create new POAs
Once you have a reference to the
RootPOA, you can create any other POAs you desire. POAs form a tree hierarchy with the
RootPOA as the tree's root node. Look at Figure 2's POA hierarchy.
As expected, the
RootPOA is at the top of the hierarchy. POAs A and B have been created directly from the
RootPOA, and as a result, are its children. POA C is a direct child of POA A. Here's the code that would create that hierarchy:
. . . // rootPOA is a reference to the Root POA POA A = rootPOA.create_POA("A",null,null); POA B = rootPOA.create_POA("B",null,null); POA C = A.create_POA("C",null,null); . . .
The Hello World example did not use a POA hierarchy, but Part 3's Bank example will, albeit a simple hierarchy.
Specify POA policies
One reason you create POA hierarchies is to create POAs that have different policies. In fact, one of the POA architecture's best features is that it allows the object implementer much more control over the object's identity, state, storage, and lifecycle. The POA architecture allows control over the following aspects/characteristics (we explore the details later):
- Allocation of requests to threads (that is, multithreading)
- CORBA object lifespan
- Object identifiers
- Mapping of objects to servants
- Implicit versus explicit object activation
- Servant retention
- Mapping of request to servants
As in the code snippet shown above, you can create a POA without defining any policies; the default values will be used. In the example above, the
RootPOA and POAs A, B, and C use the default policy values, defined below:
- Allocation of requests to threads: ORB controlled
- CORBA object lifespan: Transient
- Object identifiers: System assigned
- Mapping of objects to servants: Must be unique, that is, servants cannot be shared
- Implicit versus explicit object activation: Explicit activation
- Servant retention: Servants are retained
- Mapping of request to servants: Only the active object map is used (more about the active object map later)
You can also fine-tune the policies for each POA to match your exact requirements. For example, recall that a POA can create either persistent or transient object references, but not both. The
RootPOA can only create transient references, but you could create a new POA that can create persistent references. An example is shown below:
. . . // rootPOA is a reference to the Root POA Policy policies = new Policy; policies = rootPOA.create_lifespan_policy(LifespanPolicyValue.PERSISTENT); POA A = rootPOA.create_POA("A",null,policies); . . .
This code differs from the earlier
RootPOA creation in that instead of passing a
null value as the third parameter in the
create_POA() method, I pass in an array of
Now let's look at each policy more closely.