Developing Servers


This section introduces Java IDL support for servers of transient CORBA objects. The Hello World example includes a transient object server.

Topics in this section include:

Transient and Persistent Objects

A transient CORBA object has the same lifetime as the execution of the server process that creates it. When a server terminates, its transient objects disappear with it and any object references held by the client become invalid. Transient objects are often used to effect asynchronous, or callback communication between applications and objects. For example, suppose a timing service notifies a client application when an interval expires; it could do so as follows:

The arrangement, in other words, is much like a callback in procedural programming.

By contrast, a persistent object lives until it is explicitly destroyed. If a client has a reference to a persistent CORBA object, that reference can be used even if the object's server is not running -- an ORB daemon will start the server when the ORB receives an invocation on the object.

At present, Java IDL supports only transient object servers. Although persistent object servers can't yet be written using Java IDL, Java applets and applications can be clients of persistent CORBA objects whose servers are written in other languages, such as C++, or in other Java ORB implementations.

Servants and Servant Base Classes

To implement a server for transient CORBA objects of some type, you write a Java class called a servant, which inherits from a class called the servant base; servant base classes are optionally generated by the idlj compiler. An instance of a transient CORBA object is implemented by an instance of its servant class.

The servant base class is the CORBA type-specific interface between the ORB and the servant code (skeleton) for the type. It unmarshals incoming parameters, invokes servant methods, marshals results, and directs the ORB to return results to the client ORB.

A servant class extends its servant base with a method for each operation in the IDL interface definition it implements. (OMG IDL attributes require one method if they are read only, two if they are writable.) For example, consider the following OMG IDL:

module Notifiers {
   interface SimpleNotifier {
      void alert (in long alert_id);
   };
};

A servant for this type might look as follows:

import Notifiers.*
 
class SimpleNotifierServant extends _SimpleNotifierImplBase {
   
   void alert (int alert_id) {
      // do something about the alert ...
      return;
   }
}

The server class could create an instance of SimpleNotifierServant as follows:

  SimpleNotifierServant SimpleNotifier = new SimpleNotifierServant(); 
  orb.connect(SimpleNotifier); 

The orb.connect() call registers the object and the servant with the ORB so that the ORB will invoke the servant when the CORBA object is invoked. When the server terminates, the ORB destroys its records for that server's CORBA objects so that subsequent invocations will result in an OBJECT_NOT_EXIST exception. A server can alternatively annul its CORBA objects before terminating by calling orb.disconnect().

Using Servant Objects

A transient CORBA object is implemented by a Java servant object. A servant object can be passed as a parameter to any method that needs a CORBA object reference of that type. For example, suppose a Java program wants to learn when the price of Sun Microsystems stock rises above 150. The program has obtained a reference to a CORBA stock-watcher object that provides the following operation (shown in IDL and Java):

// IDL
interface StockWatcher {
void set_threshold(
   in string stock_symbol,
   in short stock_price,
   in Notifiers::SimpleNotifier notifier_obj);
};
 
// Java
void set_threshold(
   String stock_symbol, 
   short stock_price, 
   Notifiers.SimpleNotifier notifier_obj)
  { // set the threshold ... 
  }
 

To call set_threshold(), you need to create a SimpleNotifier CORBA object. Assuming you've imported a servant class called SimpleNotifierImpl, here's how to do it:

SimpleNotifier SUNWnotifier = new SimpleNotifierImpl(); 
int stockPrice = 150;
orb.connect(SUNWnotifier);
StockWatcher aStockWatcher = // code to get a stock-watcher reference; 
 
aStockWatcher.set_threshold ("SUNW", stockPrice, SUNWnotifier);
 

Dynamic Skeleton Interface

DSI allows servers to serve a servant object without prior (compile time) knowledge of the object's interface. Instead of using skeleton code compiled from the IDL interface definition, the server constructs an operation invocation dynamically.

Why Use DSI?

Use DSI for servers that cannot have compile-time knowledge of the implementation interface. For example, you might need a bridging application to allow CORBA clients to invoke services residing in a COM environment while allowing COM clients the same access to services residing in the CORBA realm. Each environment has its own, very different way of building requests and responses.

This bridging application would use DSI to convert a CORBA client request to a format understood by a COM server. And it would use DII to convert a COM client request to a format understood by a CORBA server. The application programmer writes all the code to perform this work.

Contrast this with a typical static object invocation. The server has access to the compiled skeletons for the interfaces being invoked upon. These skeletons are generated by compiling the IDL interface definitions with the idlj compiler. When the ORB receives a request, it uses the skeleton code to build the operation arguments on the server side and to send back any result.

Steps to Using DSI

To use DSI, implement and register a dynamic servant by doing the following:

  1. Declare your class to extend org.omg.CORBA.DynamicImplementation.

  2. Implement the invoke() method.
    Write the invoke() method code to work with a ServerRequest object to:
    1. Extract the operation name via a call on op_name().
    2. Build an NVList of the operation parameters (this requires type information on the parameters, possibly from an interface repository).
    3. Extract the parameter values via a call on params(), passing the NVList.
    4. Perform the operation, assigning new values to out and inout parameters in the NVList as appropriate.
    5. Call either result() or except(), as appropriate.
    Note: The calls to params() and then to result() or except() must be made in the proper order. You must call params() before calling either of the other two methods, and you must call only one of result() or except() once only. Failure to observe any of these limitations results in a BAD_INV_ORDER exception.

  3. Implement the _ids() method.
    This method is inherited from org.omg.CORBA.portable.ObjectImpl, the superclass of DynamicImplementation. It returns the repository IDs of the interfaces implemented by this dynamic server.

  4. Create an instance of the DSI object and register it with the ORB via a call to org.omg.CORBA.ORB.connect().

DSI Example

Here is DSI.idl, which defines a very simple interface to be implemented dynamically.

//IDL
module JavaIDL {
  interface DSIExample {
    void print_args(in string arg1, in short arg2);
  };
};

Here is the corresponding Java code, from the file DSIServer.java. This portion shows the implementation of invoke() and the dynamic handling of the print_args() operation defined in DSI.idl.

//Java
import java.util.*;
import org.omg.CORBA.*;

// Dynamic servant class implementation
class DSIExampleServantImpl extends DynamicImplementation {

  // Store the repository ID for the interface implemented 
  static String[] myIds = {"IDL:JavaIDL/DSIExample:1.0"};

  // Create a reference to the ORB
  ORB orb;
  DSIExampleServantImpl(ORB orb) {
    this.orb = orb;
  }

  // Implementation of invoke() for handling dynamic requests
  public void invoke(ServerRequest request) {
    try {
      System.out.println("DSI: invoke called, op = "+ request.op_name());
    
      // Create an NVList to hold the parameters
      NVList nvlist = orb.create_list(0);

      // Check if the request is for the operation
      // "print_args"
      if (request.op_name().equals("print_args") == true) {

        // Add first argument to NVList
        Any any1 = orb.create_any();
        any1.insert_string("");
        nvlist.add_value("arg1", any1, ARG_IN.value);

        // Add second argument to NVList
        Any any2 = orb.create_any();
        any2.insert_short((short)0);
        nvlist.add_value("arg2", any2, ARG_IN.value);    

        //Pass the NVList to the request, to get values
        request.params(nvlist);

        // Extract values and print arguments
        System.err.println("Argument 1: In value: " 
			    + nvlist.item(0).value().extract_string());
        System.err.println("Argument 2: In value: "
			    + nvlist.item(1).value().extract_short());
        TypeCode result_tc = orb.get_primitive_tc(TCKind.tk_void);
        Any result_any = orb.create_any(); 
        result_any.type(result_tc);

	// Set the void result
        request.result(result_any);
      }
    } 
    catch ( Exception ex ) {
      ex.printStackTrace();
      System.out.println("DSIExample: Exception thrown: " + ex);
    }
  }

  // Implement the _ids() method to return repository ID of interface
  public String[] _ids() {
    return myIds; 
  }

} 
// File DSIServer.java continues
Here is the remainder of DSIServer.java, showing a standard server implementation. Note that you register a dynamic servant with the ORB by using the same operation you would for a static servant.
// Java

// DSIServer implementation
public class DSIServer {

  // Main
  public static void main(String[] args) {
    try {
      // Access and initialize the ORB
      org.omg.CORBA.ORB orb = ORB.init(args, null);

      // Create an instance of the dynamic implementation
      DSIExampleServantImpl servant = new DSIExampleServantImpl(orb);

      // Register the dynamic servant with the ORB
      orb.connect(servant);

      // Write IOR into file. 
      // Alternatively, the naming service could be used.
      OutputStream f = new FileOutputStream(
            System.getProperty("user.home") + 
	    System.getProperty("file.separator") + "DSI.ior") ;
      DataOutputStream out = new DataOutputStream(f) ;
      String ior = orb.object_to_string(servant) ;
      out.writeBytes(ior) ;
      out.close();

      System.out.println("IOR is " + ior) ;

      // Wait for requests from client
      java.lang.Object sync = new java.lang.Object();
      synchronized(sync){
        sync.wait();
      }

    } 
    catch (Exception ex) {
      ex.printStackTrace();
      System.err.println("DSIServer: Exception thrown: " + ex);
    }   
  }
}

Delegation-based Skeletons (Ties)

Because Java allows a class to inherit implementation from only a single superclass, it is sometimes awkward to provide CORBA object implementations for objects that rightfully belong in a class hierarchy--the servant base class occupies the single available superclass position. These objects can become CORBA servants most easily by use of delegation-based skeletons.

Delegation-based skeletons move the required CORBA operations from a servant base class (from which your object must inherit) to the generated Tie class. The Tie class acts as the skeleton for this object, receiving invocations from the ORB and delegating them to the servant that actually does the work.

The idlj compiler is installed to the J2SDK's bin directory when you installed the Java 2 Platform, Standard Edition, v1.3. These steps are based on this version of the J2SDK.

Using Ties

To use ties, first compile the IDL interface using the -ftie flag. In addition to the usual Java files, this command also produces an Operations interface and a Tie class. Then write your class, implementing the generated Operations interface.

Ties Example

Consider the following simple IDL interface:
// IDL
interface Frog{
  void croak();
};

Compiling Frog.idl with idlj generates the standard client and server files (servant classes, etc.) and the special files for delegation-based skeletons: _FrogTie and _FrogOperations. The standard implementation of this IDL looks like this:

// FrogImpl.java--without using ties, inherits required
// CORBA functionality from _FrogImplBase

public class FrogImpl extends _FrogImplBase {
  public void croak() {
    getAudioClip(getCodeBase(), "frog.au").play();
  }
  
  public static void main(String[] args) {
    ORB orb = ORB.init();
    Frog frogRef = new FrogImpl();
    orb.connect(frogRef);
    // remainder of code deleted 
  }
}

Note that, because FrogImpl.java inherits its CORBA functionality (such as the dispatch upcall from the ORB) from _FrogImplBase, it must contain the implementation code for the rest of its methods; it cannot inherit them even if they are identical to those in another class.

The same class, using a delegation-based skeleton, would look like this:

// Amphibian.java--provides general behavior

public class Amphibian extends Applet {
  public void breathWater(){
    // implementation deleted
  }
}

// FrogImpl.java--using ties

public class FrogImpl extends Amphibian implements _FrogOperations {
  public void croak() {
    // croak method must still be here; it is in the Operations interface
    getAudioClip(getCodeBase(), "frog.au").play();
  }
  
  public static void main(String[] args) {
    ORB orb = ORB.init();
    FrogImpl servant = new FrogImpl();
    Frog frogRef = new _FrogTie(servant);
    orb.connect(frogRef);
    // remainder of code deleted 
  }
}

Using ties, FrogImpl reserves its superclass slot for inheritance of amphibian behavior while implementing the CORBA operations defined in the IDL interface (and in _FrogOperations).

Note the difference in the orb.connect() call. With a delegation-based skeleton, you pass a new tie class instance to the ORB, rather than passing the actual object implementation.


Clients | Servers | Exceptions | Initialization | Naming

Home

Fundamentals

Programming

References

Tutorial


Copyright © 1996, 1997 Sun Microsystems, Inc., 2550 Garcia Ave., Mtn. View, CA. 94043-1100 USA., All rights reserved.