Java IDL and Java RMI-IIOP Technologies:

Using Portable Interceptors (PI)


Last Update: 4/25/2002

NOTICE: This document is intended for advanced CORBA developers.

The JavaTM CORBA Object Request Broker (ORB) provides hooks, or interception points, through which ORB services can intercept the normal flow of execution of the ORB. These Portable Interceptors provide a mechanism for plugging in additional ORB behavior, or, by modifying the communications between client and server, for modifying the behavior of the ORB. The example that follows shows different ways of using Portable Interceptors.

Support for Portable Interceptors is a major recent addition to the CORBA specification. Using RequestInterceptors, one can easily write and attach portable ORB hooks that will intercept any ORB-mediated invocation. Using IORInterceptors, one can write code to annotate CORBA object references.

It is highly recommended that you read the Portable Interceptors Specification at ptc/2001-03-04 before reading this document.

The following topics are included in this document:


Interceptor Types There are currently three types of interceptors that can be registered, as shown below. Examples of each are shown in the example that follows.


Registering ORBInitializers in Java

The ORBInitializer interface facilitates interceptor registration and ORB initialization.

Interceptors are intended to be a means by which ORB services gain access to ORB processing, effectively becoming part of the ORB. Since interceptors are part of the ORB, when ORB.init returns an ORB, the interceptors shall have been registered. Interceptors cannot be registered on an ORB after it has been returned by a call to ORB.init.

ORBInitializers are registered via Java ORB properties. An interceptor is registered by registering an associated ORBInitializer object which implements the ORBInitializer interface. When an ORB is initializing, it shall call each registered ORBInitializer, passing it an ORBInitInfo object which is used to register its interceptor.

The property names are of the form:

org.omg.PortableInterceptor.ORBInitializerClass.<Service>
where <Service> is the string name of a class which implements
org.omg.PortableInterceptor.ORBInitializer
To avoid name collisions, the reverse DNS name convention should be used. For example, if company X has three initializers, it could define the following properties:

Note: Any values associated with ORBInitializerClass properties are ignored.

During ORB.init, these ORB properties which begin with org.omg.PortableInterceptor.ORBInitializerClass shall be collected, the <Service> portion of each property shall be extracted, an object shall be instantiated with the <Service> string as its class name, and the pre_init and post_init methods shall be called on that object. If there are any exceptions, the ORB shall ignore them and proceed.

Notes About the Scope of Interceptors

Notes on Interceptor Order

Notes about Registering Interceptors


Portable Interceptor Current (PICurrent)

The PortableInterceptor::Current object (hereafter referred to as PICurrent) is a Current object that is used specifically by portable Interceptors to transfer thread context information to a request context. Portable Interceptors are not required to use PICurrent, but if information from a client's thread context is required at an Interceptor's interception points, then PICurrent can be used to propagate that information. PICurrent allows portable service code to be written regardless of an ORB's threading model.

Note: PICurrent is typically NOT used directly by CORBA client or server code. Instead, it is generally used by interceptor-based service implementations as demonstrated in the AService example interceptor which follows.

Obtaining PICurrent

Before an invocation is made, PICurrent is obtained via a call to ORB::resolve_initial_references ( PICurrent ). From within the interception points, the data on PICurrent that has moved from the thread scope to the request scope is available via the get_slot operation on the RequestInfo object. A PICurrent can still be obtained via resolve_initial_references, but that is the Interceptor's thread scope PICurrent.

Request Scope vs Thread Scope

The thread scope PICurrent (TSC) is the PICurrent that exists within a thread's context. A request scope PICurrent (RSC) is the PICurrent associated with the request. On the client-side, the thread scope PICurrent is logically copied to the request scope PICurrent from the thread s context when a request begins and is attached to the ClientRequestInfo object. On the server-side, the request scope PICurrent is attached to the ServerRequestInfo and follows the request processing. It is logically copied to the thread scope PICurrent after the list of receive_request_service_contexts interception points are processed. See the Updated Interceptors specification, Section, 21.4.4.5, Flow of PICurrent between Scopes for a detailed discussion of the scope of PICurrent.


Portable Interceptor Example

This section contains an example logging service application. The sample code for this application is quite complicated because it covers even subtle "corner cases". The following scenarios are covered in this sample application:

  1. A logging service which logs invocations. Neither client nor server explicitly use the logging service.

  2. An "empty" service which passes information from the client to the server. The client and server explicitly use this service but are not aware that it is implemented using interceptors.

Note: These examples explicitly register ORBInitializers to make the code easier to experiment with and setup. Typically this would not be done. Instead, this information would be passed as -D properties to the Java virtual machine when the applications are started. That way the applications are not coupled to the fact that either the service exists (e.g., the logging service) or that a service that they explicitly use (e.g., the AService interface) is implemented as an interceptor.

The purpose of the logging service example is to show how to avoid infinite recursion when making outcalls (i.e., invocations on CORBA references) from within interception points. This turns out to be quite involved when all corner cases are covered.

The purpose of the "empty" service example is to show how to implement services which flow context information from client to server and back.

Flowing Context Information between Clients and Servers

A typical interceptor-based service will flow context information between clients and servers. The AService example shows how this information is flowed from the client thread into the client interceptors, across the wire into the server interceptors, to the servant thread and back.

It is important to note that neither the client nor the servant is aware that the service is implemented using interceptors. Instead, they interact with the service through local object references (the aService reference in this example).

AService diagram

AService diagram

Description of the steps of the AService diagram:

  1. The client invokes aService.begin().

    1.a. aService.begin() sets the service context information in a slot it has reserved in PICurrent.

  2. The client invokes a method on some reference (i.e., ref.method()). This invocation is taking place inside the context of the service since aService.begin() has been invoked.

  3. send_request is entered for the ref.method call.

  4. get_effective_component(s) may be called to determine if the reference being invoked needs to interact with the service.

  5. get_slot is called to retrieve the context set in step 1a. Slots set in PICurrent are logically copied and made available to ClientRequestInfo.

  6. The service interceptor adds a service context containing the appropriate context (most likely depending on the context set in step 1a and any components retrieved in step 4). It uses add_request_service_context to add this context information to the request which is sent over the wire.

  7. The ref.method request arrives at the server, activating the receive_request_service_contexts interception point.

  8. get_request_service_context is used to retrieve the service context added at step 6.

  9. ServerRequestInfo.set_slot is used to transfer the context information from the service context retrieved in step 8 to the logical thread local data.

  10. receive_request is entered.

  11. The "ref" Servant's "method" is entered.

  12. The servant method invokes aService.verify() to interact with the service.

  13. The service uses get_slot on PICurrent to retrieve the context information sent from the client side. The service may also use set_slot on PICurrent to set any return context information.

  14. After the Servant completes, the send_* interception point is entered. If the service set any return context information, it would add a service context to the return. This is not shown in this example.

  15. When the reply arrives at the client, the receive_* interception point is entered. If the service set any return context information, it would be retrieved at this point. This is not shown in this example.

Avoiding Infinite Recursion During Interceptor Outcalls

Some services may need to make invocations on other CORBA object references from within interception points. When making outcalls from within interception points, steps must be taken to avoid infinite recursion since those outcalls will flow through the interception points. The LoggingService example illustrates this case.

The LoggingService example is comprised of ClientRequestInterceptors registered in a client program, and ServerRequestInterceptors registered in a server program. These interceptors send information from the client and server to a LoggingService implemenation which logs this information.

However, since the LoggingService implementation is itself a CORBA server, we must ensure that we do not log calls to the logger. The following diagrams illustrate the steps taken to avoid infinite recursion.

The following diagram shows the simplest case of avoiding recursion when calling an external logger from within interceptors. These steps are useful for the case where the client ORB only contains ClientRequestInterceptors, the server ORB only contains ServerRequestInterceptors, and the LoggingService is external to both the client and server ORBs.

LoggingService diagram

Logging Service 
diagram

Description of the steps of the LoggingService diagram:

  1. The client invokes a method on some reference.

  2. The client interceptor's send_request method is entered for the method invoked in step 1.

  3. A slot reserved for indicating an outcall is set to true on PICurrent.

  4. The same slot is checked on ClientRequestInfo via get_slot. This value will not be set since it represents the client's thread state at step 1. Therefore a call to the logger will be made.

  5. send_request is recursively entered for the logger invocation made in step 4.

  6. A slot reserved for indicating an outcall is set to true on PICurrent. Note, this slot is always unconditionally set. This is necessary when there are 2 or more interceptors doing outcalls. Since interceptors do not know about the existence of other interceptors we always set the PICurrent slot.

  7. The slot is checked on ClientRequestInfo via get_slot. This time the value is set (from step 3). Therefore we do not log this call (which is the call to the logger itself). The send_request point that entered at step 5 finishes.

  8. The ORB sends the log invocation made at step 4 to the logging service.

  9. The send_request point for step 2 finishes. The ORB sends the initial client invocation made in step 1 to the server for the appropriate reference.

  10. The server's receive_request_service_contexts point is entered. It logs the incoming request. A server which does not contain client interceptors which make outcalls and which does not contain the implementation of the logger itself does not need to set recursion avoidance slots. That is what this diagram represents.

  11. -   13.    Therefore all the server side interception points are entered with no further checking and the response from the method initially invoked by the client is returned to the client ORB.

  12. The client's receive_* point is entered. Steps similar to steps 2 through 9 will occur if this point needs to make an outcall.

Avoiding Recursion for Colocated Outcall References

The following diagram illustrates the case where the LoggingService may be colocated in the same ORB as the reference being invoked by the client. In general, it is not possible to know that a particular object reference is NOT colocated with any other objects hosted by that ORB. Therefore, to cover all corner cases, more steps must be taken.

This diagram shows only the server side. The client side steps are identical to those in the preceding diagram.

LoggingServiceColocated diagram

LoggingServiceColocated diagram

Description of the steps of the LoggingServiceColocated diagram:

  1. The request from the client ORB arrives, activating the receive_request_service_contexts interception point.

  2. A slot reserved for indicating an outcall is set to true on PICurrent.

  3. ServerRequestInfo.get_request_service_context is used to check for the presence of a service context indicating an outcall. The service context is not present so the logger is invoked from within the interception point.

  4. The send_request interception point is entered for the logger request.

  5. get_slot checks for the outcall indicator.

  6. The outcall indicator slot has been set (from step 2), therefore add_request_service_context is used to a service context indicating an outcall. This is necessary since there is no logical relationship between client and server threads.

  7. The logger invocation arrives at receive_request_service_contexts.

  8. A slot reserved for indicating an outcall is set to true on PICurrent.

  9. ServerRequestInfo.get_request_service_context is used to check for the presence of a service context indicating an outcall. The service context is present so no further action is taken. receive_request_service_contexts exits.

  10. The logger request proceeds to receive_request. Steps similar to those taken inside receive_request_service_context are necessary if the logger is called at the receive_request point (this is not shown in the diagram (although the example code logs all points)).

  11. The logger request arrives at the LoggingService Servant, which logs the initial client request (not invocation of the logger).

  12. The logger request proceeds to ServerRequestInterceptor.send_* (most likely send_reply).

  13. The logger request proceeds to ClientRequestInterceptor.receive_* (most likely receive_reply).

    After Step 13, the original request that left off at Step 3 would be serviced.

The main point illustrated in this example is the necessity of using both client and server interceptors in conjunction with PICurrent slots and service contexts to indicate an outcall.

Avoid recursion by using multiple ORBs

A simpler way to avoid recursion is to ensure that the outcall references are associated with a different ORB that does not have the logging interceptors registered. That way the outcall invocations never go through interceptors.

This seems like a simple solution, but, in general, interceptors are registered via properties passed to the VM during startup. This means that all ORBs created in that VM will contain all interceptors so this solution will not work.

This solution will only work where interceptors are explicitly registered in client code during ORB.init. However, that is neither a typical nor a recommended way to register interceptor-based services.


Example Code

The following files contain the code which the above diagrams illustrate. Instructions for compiling and executing these examples follow the code. The files included in this example are:



Compiling and running the application

The example code, which contains the logging example and the service example, may be compiled and run using a Makefile such as that shown below:


The following steps show how to build and run the example on the Solaris operating system using the above Makefile. From the command prompt, run these commands as shown. The % symbol is used as a reminder that these are commands that are to be run from the command prompt.

  1. % make clean
  2. % make build
  3. % make runorbd &
  4. % make runloggingservice &
  5. % make runarbitraryobject &
  6. % make runclient

    After this step, you will see output such as this:

      resolve send_request
      resolve receive_reply
      arbitraryOperation1 send_request
      Service present: 1
      arbitraryOperation1 receive_reply
      arbitraryOperation2 send_request
      Service present: 1
      arbitraryOperation2 receive_other
      arbitraryOperation3 send_request
      Service not present
      arbitraryOperation3 receive_reply
      arbitraryOperation3 send_request
      Service present: 2
      arbitraryOperation3 receive_exception
      Client done.
      

  7. % jobs

    After this step, you will see output such as this:

      [1]   Running                 make runorbd &
      [2]-  Running                 make runloggingservice &
      [3]+  Running                 make runarbitraryobject &
      

  8. % kill %2 %3
  9. % make runcolocatedservers &
  10. % make runclient

    After this step, you will see output such as this:

      log receive_request_service_contexts
      log receive_request
      resolve send_request
      log send_reply
      log receive_request_service_contexts
      log receive_request
      resolve receive_reply
      log send_reply
      log receive_request_service_contexts
      log receive_request
      arbitraryOperation1 send_request
      log send_reply
      arbitraryOperation1 receive_request_service_contexts
      arbitraryOperation1 receive_request
      Service present: 1
      arbitraryOperation1 send_reply
      log receive_request_service_contexts
      log receive_request
      arbitraryOperation1 receive_reply
      log send_reply
      log receive_request_service_contexts
      log receive_request
      arbitraryOperation2 send_request
      log send_reply
      arbitraryOperation2 receive_request_service_contexts
      arbitraryOperation2 receive_request
      log receive_request_service_contexts
      Service present: 1
      arbitraryOperation2 send_reply
      log receive_request
      arbitraryOperation2 receive_other
      log send_reply
      log receive_request_service_contexts
      log receive_request
      arbitraryOperation3 send_request
      log send_reply
      arbitraryOperation3 receive_request_service_contexts
      arbitraryOperation3 receive_request
      Service not present
      arbitraryOperation3 send_reply
      log receive_request_service_contexts
      log receive_request
      arbitraryOperation3 receive_reply
      log send_reply
      log receive_request_service_contexts
      log receive_request
      arbitraryOperation3 send_request
      log send_reply
      arbitraryOperation3 receive_request_service_contexts
      arbitraryOperation3 receive_request
      Service present: 2
      arbitraryOperation3 send_exception
      log receive_request_service_contexts
      log receive_request
      arbitraryOperation3 receive_exception
      log send_reply
      Client done.
      

  11. % jobs

    After this step, you will see output such as this:

      [1]-  Running                 make runorbd &
      [4]+  Running                 make runcolocatedservers &
      

  12. % kill %1 %4
  13. % make clean


See Also:
ORBInitInfo
org.omg.PortableInterceptor package.
Portable Interceptors Specification at ptc/2001-08-31



Consult the Java Developer's Forum on Distributed Computing at http://forum.java.sun.com/ for more information on Portable Interceptors.