next up previous contents
Next: The Interface Repository Up: JacORB Programming Guidev0.9 Previous: Thread models

Interceptors

Interceptors are a flexible means for having code called by the ORB at specific points during operation invocations. Using interceptors you can, e.g., monitor and log method calls or define simple access control policies.

Introducing Interceptors

Interceptors are objects of particular classes which are called at specific points during the processing of operation invocations. By defining subclasses of interceptor classes and registering instances with the ORB, you can "insert" your own code into the invocation path. This provides access to the request itself and thus a convenient and very powerful way of observing and/or modifying requests. On the other hand, it requires more detailed knowledge about ORB data structures than necessary for application level code.

Interceptors can be defined at two levels: at the request level or at the message level. Request-level interceptors are given access to a request object representing the current request and may access and modify this request object before and after it is actually invoked. Message-level interceptors have access to the actual message buffer before and after it is sent across the network and can be used, e.g., to encrypt and decrypt the entire message.

In a CORBA invocation, there are eight points where interceptors are (potentially) given control. On the client side, these are points 1 and 2 (cf. figure), i.e. before the request is sent across the network, and points 7 and 8, i.e. after the result is received and before control is returned to the caller. At point 1, the interceptor has access to a strutured request object, whereas at point 2, the message can be accessed in marshalled form in a buffer just before it is sent onto the network.

Because of this distinction, interceptors come in two flavours: request-level and message-level interceptors. On the server side, intercepting requests can also be done at these different levels (points 3, 4 and 5, 6). For every single one of these eight points, a different method is called on different interceptor objects. Let's first look at request-level interceptors.

Request-level Interceptors

Request-level Interceptors are objects of subclasses of ClientRequestInterceptor on the client side and of ServerRequestInterceptor on the server side. We will look at these two in turn.

Client-side interceptors

On the client side, methods pre_invoke() and post_invoke() of classes implementing jacorb.Orb.ClientRequestInterceptor are called at points 1) and 8), respectively. Below is this interface:

package jacorb.Orb;

public interface ClientRequestInterceptor
{
   /* for JacORB requests */
   public  void pre_invoke( org.omg.CORBA.Object t, 
                            String op, String argsig, 
                            String returnsig, Object[] args 
                          );

   public  void post_invoke( org.omg.CORBA.Object t, 
                            String op, String argsig, 
                            String returnsig, Object[] args 
                            Object result
                          );

   /* for DII requests */
   public  void pre_invoke( org.omg.CORBA.Request r );
   public  void post_invoke( org.omg.CORBA.Request r );
}

Note that different interceptor methods are called depending on whether the invocation was performed using statically compiled stubs or the DII. As a consequence, request information is available in different form when the DII is used as when the static invocation interface (stubs/skeletons) is used. As an example, consider the following interceptor which "traces" method calls:

package jacorb.Orb.util;

/** 
 * a simple request interceptor
**/

public class TraceClientRequestInterceptor
   implements jacorb.Orb.ClientRequestInterceptor
{
   private int indent = 0;

   public TraceClientRequestInterceptor(){}

   public void pre_invoke( org.omg.CORBA.Object t, 
                           String op, String argsig, 
                           String returnsig, Object[] args )
   {
      for( int i = 0; i < indent; i++ )
         System.out.print(" ");
      System.out.println("[ invoke <" + op + "> ]");
      indent += 2;
   }
   public void post_invoke( org.omg.CORBA.Object t, 
                           String op, String argsig, 
                           String returnsig, Object[] args,
                           Object result )
   {
      indent -= 2;
      for( int i = 0; i < indent; i++ )
         System.out.print(" ");
      System.out.println("[ <" + op + "> returns ]");
   }

   public void pre_invoke( org.omg.CORBA.Request r )
   {
      for( int i = 0; i < indent; i++ )
         System.out.print(" ");
      System.out.println("[ invoke " + r.operation() + " ]");
      indent += 2;
   }
   public void post_invoke( org.omg.CORBA.Request r )
   {
      indent -= 2;
      for( int i = 0; i < indent; i++ )
         System.out.print(" ");
      System.out.println("[ " + r.operation() + " returns ]");
   }
}

When the DII is used, request information is available as an object of class org.omg.CORBA.Request whereas it is available through a number of parameters such as org.omg.CORBA.Object and strings when stubs are used.gif

Server-side interceptors

Interceptors that are called at points 2) and 3) on the server side implement the interface jacorb.Orb.ServerRequestInterceptor:

package jacorb.Orb;

public interface ServerRequestInterceptor
{
   /* for JacORB requests */
   public  void pre_invoke( jacorb.Orb.Request r);
   public  void post_invoke( jacorb.Orb.Request r);

   /* for DII requests */
   public  void pre_invoke( org.omg.CORBA.ServerRequest r );
   public  void post_invoke( org.omg.CORBA.ServerRequest r );
}

These interceptors gain access to the request through objects of type jacorb.Orb.Request and org.omg.CORBA.ServerRequest respectively.

Per-object vs. per-process Interceptors

If the ORB is to use instances of the above class during invocation processing, these interceptors need to be registered with the ORB. There are two options for doing this. An interceptor can be registered for general use, i.e. it will be called during every single invocation from the current address space, regardless of which object is the target of the call. This is called a per-process interceptor.

Another, more fine-grained way of using interceptors is to register them as per-object interceptors, i.e. to register them for use in invocations on particular objects only. As an example, consider the simple tracer again: You might want to trace method calls on all objects, or you might be interested in operations on one particular object only.

Registering Interceptors

A per-process interceptor is registered with the ORB as shown in the following code fragment:

    org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init();
    jacorb.Orb.util.TraceClientRequestInterceptor t = 
            new jacorb.Orb.util.TraceClientRequestInterceptor();
    ...
    orb.addInterceptor( t );
    ...

When addInterceptor() is called, the ORB will append the argument as a per-process interceptor to the end of an internal interceptor chain. On any subsequent invocation, all interceptors in this chain will be called in the order in which they were appended to the chain. Of course, interceptors can also be removed again:

orb.removeInterceptor(t);

Per-object interceptors are registered by calling addInterceptor() with the object reference as the first argument:

    org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init();
    jacorb.Orb.util.TraceClientRequestInterceptor t = 
            new jacorb.Orb.util.TraceClientRequestInterceptor();
    org.omg.CORBA.Object o = ...
    ...
    orb.addInterceptor( o, t );
    ...
    orb.removeInterceptor( o, t);

Example: An Access-Control Interceptor

As an example, consider the following server-side request interceptor which uses a simple ACL object to check authorization for operations using the Principal information which is transmitted with every request.

package jacorb.Orb.Security;

public class AccessControlInterceptor 
   implements jacorb.Orb.ServerRequestInterceptor
{
   private ACL acl;

   public AccessControlInterceptor( ACL _acl)
   {
      acl = _acl;
   }

   public void pre_invoke( jacorb.Orb.Request r )
   {
      if( !acl.is_allowed( r.op_name(), 
         r.req_hdr.requesting_principal.toString() ))
         {
            System.out.println("[ Denied <" + r.op_name() + 
               "> to " + r.req_hdr.requesting_principal + " ]");
            throw new org.omg.CORBA.NO_PERMISSION( 3000, 
               org.omg.CORBA.completion_status.COMPLETED_NO);
         }
   }

   //...

   /* for DSI requests */
   public void pre_invoke( org.omg.CORBA.ServerRequest r )
   {
      org.omg.CORBA.Principal p =
         ((jacorb.Orb.DSI.ServerRequest)r).req_hdr.requesting_principal;

      if( !acl.is_allowed( r.op_name(), p.toString() ))
      {
         System.out.println("[ Denied <" + r.op_name() + 
            "> to " + p + " ]");

         throw new org.omg.CORBA.NO_PERMISSION( 3000, 
            org.omg.CORBA.completion_status.COMPLETED_NO);
      }
   }

   //...
}

A server could install this interceptor like this (in main()):

      org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init();
      org.omg.CORBA.BOA boa = orb.BOA_init();
      NamingContextImpl n = new NamingContextImpl();

      // create and set up an ACL for this implementation
      jacorb.Orb.Security.ACL acl = new jacorb.Orb.Security.ACL( n );
      acl.allow( "bind", "brose" );
      acl.allow( "resolve", "brose" );

      // create and install the access control interceptor 
      jacorb.Orb.Security.AccessControlInterceptor act = 
            new jacorb.Orb.Security.AccessControlInterceptor(acl);
      orb.addServerInterceptor( n, act );

      // tell the BOA
      org.omg.CORBA.Object ctx = boa.create( n,
            "IDL:omg.org/CosNaming/NamingContext:1.0" );
      boa.obj_is_ready( ctx );
      //...
      boa.impl_is_ready();

In this example, the access control interceptor is registered as a per-object interceptor, i.e. it will only be consulted for invocations on this particular implementation. If there were any other objects in this server, they would not be access-protected. In this particular case, a naming context is protected so as to allow only the bind and resolve operations, and these only to a pricipal with identity "brose". All other requests will receive a NO_PERMISSION exception.

Message-level Interceptors

Message-level Interceptors implement either ClientMessageInterceptor or ServerMessageInterceptor and generally work like request-level interceptors. The main difference is that the request is passed as an unstructered buffer, so there is no need to distinguish between DII accesses and accesses through the static stub layer.

Also note that the client-side interceptor has an additional parameter indicating the target of the invocation. This parameter is not available at the server-side because it is only known after the request message has been interpreted by the transport layer software -- which is only after the message-level interceptor has had access to the message, e.g. for decryption purposes.

package jacorb.Orb;
public interface ClientMessageInterceptor
{
   public  byte [] pre_invoke( org.omg.CORBA.Object t, 
                                 byte [] buf 
                              );

   public  byte [] post_invoke( org.omg.CORBA.Object t, 
                                  byte [] buf 
                              );
}

package jacorb.Orb;
public interface ServerMessageInterceptor
{
   public  byte [] pre_invoke( byte [] buf );
   public  byte [] post_invoke( byte [] buf );
}

Example: A simple Encryption Interceptor

As an example, consider a simple interceptor which "encrypts" the message contents by reversing the message buffer. Note that the interceptor preserves the first 12 bytes of the message -- this is the GIOP message header. The reason for this is that the message can only be correctly received if the receiving side is able to determine the message size. To this end, a GIOP message header with the correct message size must be sent across.

In the example, the transformation on the message buffer does not change the message size, so we can simply reuse the original message header. Many other transformations, however, will result in a different message length, so it is necessary to provide a correct message header. The format of the GIOP message header is described in the CORBA specification. Alternatively, you can examine the class org.omg.GIOP.MessageHeader which gives the Java equivalent of the IDL specification.

public class ClientMessageInterceptor
   implements jacorb.Orb.ClientMessageInterceptor
{
   public  byte [] pre_invoke( org.omg.CORBA.Object t, 
                               byte [] buf )
   {
      byte buf2[] = new byte[buf.length];

      // copy GIOP message header 
      System.arraycopy( buf, 0, buf2, 0, 12 );

      // reverse rest of message
      for( int i = 12; i < buf.length; i++ )
      buf2[i] = buf[ (buf.length+11) - i ];

      return buf2;
   }

   public  byte [] post_invoke( org.omg.CORBA.Object t, 
                                byte [] buf )
   {
      // don't do anything here
      return buf;
   }
}

On the receiving side, the effect of the buffer transformation must be undone before the message can be passed up to the ORB software and be interpreted as a request, so we must apply the same transformation again:

public class ServerMessageInterceptor
   implements jacorb.Orb.ServerMessageInterceptor
{
   public  byte [] pre_invoke( byte [] buf )
   {
      byte buf2[] = new byte[buf.length];

      // copy GIOP message header 
      System.arraycopy( buf, 0, buf2, 0, 12 );

      // reverse rest of message
      for( int i = 12; i < buf.length; i++ )
         buf2[i] = buf[ buf.length+11-i ];

      return buf2;
   }

   public  byte [] post_invoke( byte [] buf )
   {
   // don't do anything here
      return buf;
   }
}

Per-object vs. per-process Interceptors

As with request-level interceptors, it is sometimes useful to determine whether to apply a particular interceptor depending on which object is actually called. Accordingly, message-level interceptors can be registered per-object or per-process. However, this is only possible on the client side:

orb.addInterceptor( (org.omg.CORBA.Object)s, new ClientMessageInterceptor());

Recall that on the receiving side, a reference to the object is only available after the ORB software has interpreted the message -- which it cannot do, e.g., before the message has been decrypted in the above example. As a consequence, server-side message-level interceptors can only be registered per process. If an interceptor may only apply certain transformations on messages to particular objects, it is responsible for determining the target by itself.

      org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init();
      org.omg.CORBA.BOA boa = orb.BOA_init();
      org.omg.CORBA.Object o = boa.create( new dynamicServer(), 
            "IDL:jacorb/demo/Interceptors/server:1.0");

                        // create and register a trace interceptor
      orb.addServerInterceptor( new jacorb.Orb.util.TraceServerRequestInterceptor() );

      boa.named_obj_is_ready( o, "Interceptors-Example Server");

      orb.addServerInterceptor( new ServerMessageInterceptor() );
      boa.impl_is_ready();


next up previous contents
Next: The Interface Repository Up: JacORB Programming Guidev0.9 Previous: Thread models

Gerald Brose
Tue Mar 31 08:47:04 MET DST 1998