Caffeine: defining CORBA interfaces with Java

This chapter explains how to use the java2iiop compiler (also called the Caffeine compiler) to generate IIOP-compliant client stubs and server skeletons from interface definitions written in Java instead of IDL. In addition, this chapter explains how to work with complex data types; in particular, it explains how to pass-by-value using extensible structs.

When you generate IIOP-compliant stubs and skeletons from interface definitions written in Java, you follow the same steps in JBuilder as defined in Outline: creating a CORBA application in JBuilder .

This chapter contains the following topics:


Caffeine

JBuilder ships with VisiBroker. VisiBroker incorporates features, collectively known as Caffeine, which make the product Web-friendly and easy to work with in a Java environment. These features include:

To access the java2iiop compiler in JBuilder,

  1. Select the Java interface file in the Navigation pane.
  2. Right-click on the Java interface file, select Java Source Properties from the context menu.
  3. Check Generate IIOP Interface under VisiBroker Settings. Select OK.
  4. Right-click on the Java interface file in the Navigation pane and select Build to compile the interface definition.

Working with the java2iiop compiler

The java2iiop compiler lets you define interfaces and data types that can then be used as interfaces and data types in CORBA, but the advantage is that you can define them in Java rather than IDL. The compiler reads Java bytecode-it does not read source code or java files but reads class files. The compiler then generates IIOP-compliant stubs and skeletons needed to do all the marshalling and communication required for CORBA.

When you run the java2iiop compiler, it generates the same files as if you had written the interface in IDL. Primitive data types like the numeric types (short, int, long, float, and double), string, CORBA objects or interface objects, Any's, typecodes - all of these are understood by the java2iiop compiler and mapped to corresponding types in IDL.

When using the Caffeine compiler on Java interfaces, you define and mark them as interfaces to be used with remote calls. You mark them by having them extend the org.omg.CORBA.Object interface. (The interface must also define methods that you can use in remote calls.) When you run the compiler, it searches for these special CORBA interfaces. When one is found, it generates all of the marshalling elements, readers and writers which enable you to use the interface for remote calls. An example is shown in Code Sample 1.

Note: For developers who are familiar with RMI (Remote Method Invocations), this is equivalent to a class extending the java.rmi.Remote interface.

For classes, the compiler follows other rules and maps the classes either to IDL structs or extensible structs. For more information about complex data types, see "Mapping of Complex Data Types".

Code Sample 1   Extending org.omg.CORBA.Object.


CODE:
// Dictionary.java

public interface Dictionary extends org.omg.CORBA.Object {
  int size();
  boolean isEmpty();
  Object get(Object key);
  Object put(Object key, Object value);
  Object remove(Object key);
}

Figure 1 shows the development process when using java2iiop.

Figure 1   The Development Process Using the java2iiop Compiler.

RMI vs. Caffeine

Another way to invoke methods on remote Java objects is to use RMI, provided by JavaSoft and others. VisiBroker for Java provides capabilities equivalent to RMI, but, in addition, VisiBroker allows you to create Java objects that communicate with all other objects that are CORBA compliant even if they are not written in Java. For more information on RMI, see Tutorial: created a distributed application in Java RMI.

Running java2iiop

We will use the Dictionary example to demonstrate how to define an interface in Java and have it compile into IIOP-compliant stubs and skeletons, using the java2iiop compiler.

  1. Select File|Close All to close all existing projects.

  2. Select File|Open/Create. Look in the directory jbuilder/samples/VisiBroker/samples/pass_by_value/Dictionary/. Open the project Dictionary.jpr.

  3. In the Navigation pane, right-click on Dictionary.java. Select Java Source Properties from the context menu.

  4. Check the Generate IIOP Interface field in the VisiBroker Settings box, then click OK.

  5. Right-click on Dictionary.java, and select Build from the context menu.

The preceding steps generate the Java bytecode that is used as input to the java2iiop compiler, and generates the client stubs and server skeletons.

The java2iiop compiler generates all of the usual auxiliary files, like Helper and Holder classes. For more information, see "Code Generation". The files generated by the java2iiop compiler are the same as those generated by the idl2java compiler. For more information about the generated files, see the "Generated Classes" chapter in the VisiBroker for Java Reference Manual at http://www.visigenic.com/techpubs/htmlhelp/vbj30/ref/noframes/vbjreft.htm.

Completing the development process

The examples shown in this chapter can be found in the pass_by_value directory in the java_examples directory of the VisiBroker for Java installation. After generating stubs and skeletons, create your client and server classes. Follow these steps:

  1. Use the skeleton class to create an implementation of server object; for an example, see Code Sample 2.
  2. Compile your server class by right-clicking on the server file in the Navigation pane and selecting Build.
  3. Write client code; for an example, see Code Sample 3.
  4. Compile client code by right-clicking on the client file in the Navigation pane and selecting Build.
  5. Start server objects by right-clicking on the server file in the Navigation pane and selecting Run.
  6. Start client by right-clicking on the client file in the Navigation pane and selecting Run. For an example of sample client output, see Code Sample 4.

Important: The examples shown in this chapter assume that a VisiBroker Smart Agent is running. To run the VisiBroker Smart Agent, select Tools|VisiBroker Smart Agent from the JBuilder menu.

The Dictionary example

The example shows a DictionaryServer interface which is a CORBA version of the Dictionary type that is built into Java. The Java version has the same methods as the CORBA version: size(), isEmpty(), get(Object key), put(Object key, Object value), and remove(Object key). For more information about the Dictionary interface, see Code Sample 1.

In Code Sample 2 the server that implements the CORBA Dictionary is defined. It is implemented by using a Java hash table. The benefit of using a Java hash table is that each of the methods that the Dictionary supports can be simply implemented as a one-line call to the underlying hash table. For example, the size() method is implemented by returning _hashtable.size().

To view the code in the Dictionary Server interface,

  1. Select DictionaryServer.java in the Navigation pane (if you opened the Dictionary.jpr project).
  2. View the code in the Source pane. To see more code, use Alt+Z to toggle a larger source window.

Code Sample 2   Implementing the DictionaryServer interface.
CODE:
// DictionaryServer.java

import java.util.Hashtable;
public class DictionaryServer extends _DictionaryImplBase {
  Hashtable _hashtable = new Hashtable();
  DictionaryServer(String name) {
    super(name);
  }
  public int size() {
    return _hashtable.size();
  }
  public boolean isEmpty() {
    return _hashtable.isEmpty();
  }
  public Object get(Object key) {
    return _hashtable.get(key);
  }
  public Object put(Object key, Object value) {
    return _hashtable.put(key, value);
  }
  public Object remove(Object key) {
    return _hashtable.remove(key);
  }
  public static void main(String[] args) {
    org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(args,null);
    org.omg.CORBA.BOA boa = orb.BOA_init();
    Dictionary impl = new DictionaryServer("Server");
    boa.obj_is_ready(impl);
    System.out.println(impl + " is ready.");
    boa.impl_is_ready();
  }
}

Code Sample 3 shows the client, as usual, binding to the server. The client puts a variety of data types into the Dictionary. The first data type it puts into the Dictionary is a string, the second is a vector. The client is creating a hash table and a vector and it's putting the strings "Hello" and "World" into the hash table and the same into the vector. The client is passing the built-in utility types - hash table and vector - into this CORBA Dictionary. This shows the mixing and matching of CORBA and the built-in Java types, which are all passed using Java serialization.

To view the code in the Dictionary Client interface,

  1. Select DictionaryClient.java in the Navigation pane (if you opened the Dictionary.jpr project).
  2. View the code in the Source pane. To see more code, use Alt+Z to toggle a larger source window.

Code Sample 3   Testing the Dictionary implementation.
CODE:
// DictionaryClient.java

import java.util.Hashtable;
import java.util.Vector;

public class DictionaryClient {
  public static void main(String[] args) {
    org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(args,null);
    Dictionary d = DictionaryHelper.bind(orb);
    Hashtable h = new Hashtable();
    h.put("Hello", "World");
    Vector v = new Vector();
    v.addElement("Hello");
    v.addElement("World");
    System.out.println("d.size()     " + d.size());
    System.out.println("d.isEmpty()  " + d.isEmpty());
    System.out.println("d.put(h)     " + d.put("h", h));
    System.out.println("d.size()     " + d.size());
    System.out.println("d.put(v)     " + d.put("v", v));
    System.out.println("d.size()     " + d.size());
    System.out.println("d.isEmpty()  " + d.isEmpty());
    System.out.println("d.get(v)     " + d.get("v"));
    System.out.println("d.get(h)     " + d.get("h"));
    System.out.println("d.get(x)     " + d.get("x"));
    System.out.println("d.remove(v)  " + d.remove("v"));
    System.out.println("d.size()     " + d.size());
    System.out.println("d.remove(h)  " + d.remove("h"));
    System.out.println("d.size()     " + d.size());
    System.out.println("d.isEmpty()  " + d.isEmpty());
  }
}

To build this example, do the following:
  1. Open the Dictionary.jpr project, if you have not already done so.

  2. In the Navigation pane, right-click on Dictionary.java. Select Java Source Properties from the context menu.

  3. Check the Generate IIOP Interface field in the VisiBroker Settings box, then click OK.

  4. Right-click on Dictionary.java, and select Build from the context menu.

  5. Right-click on DictionaryServer.java, and select Run from the context menu.

  6. Right-click on DictionaryClient.java, and select Run from the context menu.

The sample client output is shown in Code Sample 4. In the client program, there are system out.print lines; for each print line there is a corresponding line in the output. Each line tells you its function and the results. For example, the first line is supposed to return the Dictionary's size; it prints the results in the right column.

Code Sample 4   Sample client output.
d.size()     0
d.isEmpty()  true
d.put(h)     null
d.size()     1
d.put(v)     null
d.size()     2
d.isEmpty()  false
d.get(v)     [Hello, World]
d.get(h)     {Hello=World}
d.get(x)     null
d.remove(v)  [Hello, World]
d.size()     1
d.remove(h)  {Hello=World}
d.size()     0
d.isEmpty()  true

Mapping of primitive date types

Client stubs generated by java2iiop handle the marshalling of the Java primitive data types that represent an operation request so that they may be transmitted to the object server. When a Java primitive data type is marshalled, it must be converted into an IIOP-compatible format. Table 1 summarizes the mapping of Java primitive data types to IDL/IIOP types.

Table 1   Mapping Java types to IDL/IIOP.

Java TypeIDL/IIOP Type
Package Module
boolean boolean
char char
byte octet
String string
short short
int long
long long long
float float
double double
org.omg.CORBA.Any any
org.omg.CORBA.TypeCode TypeCode
org.omg.CORBA.Principal Principal
org.omg.CORBA.Object Object

Mapping of complex data types

This section discusses interfaces, arrays, Java classes, and extensible structs; it shows how the java2iiop compiler can be used to handle complex data types.

Interfaces

Java interfaces are represented in IDL as CORBA interfaces and they must inherit from the org.omg.CORBA.Object interface. When passing objects that implement these interfaces, they are passed by reference.

Note: The java2iiop compiler does not support overloaded methods on Caffeine interfaces.

Interfaces which do not extend org.omg.CORBA.Object are mapped to an extensible struct.

Arrays

Another complex data type that may be defined in classes is an array. If you have an interface or definitions that use arrays, the arrays map to CORBA unbounded sequences.

Mapping Java classes

In Java classes, you can define arbitrary data types. Some arbitrary data types are analogous to IDL structures (also called structs). If you define a Java class so that it conforms to certain requirements, then the java2iiop compiler maps it to an IDL struct. You can map Java classes to IDL structs if the class fits all of these requirements:

The java2iiop compiler looks at a class, and checks to see if it meets all of these requirements. If the class does meet all of the requirements, the compiler maps it to an IDL struct; if it does not meet all of the requirements, it maps it to an extensible struct. For more information about extensible structs, see Extensible Structs. The following example shows a simple Java class that would fit the requirements and map to an IDL struct.

Code Sample 5   An example of a Java class that would map to an IDL struct.
CODE:
//Java
final public class Address {
    public string name;
    public string street_address;
    public short zipcode;
}

Extensible structs

Any Java class that does not meet all of the requirements listed above is mapped to an extensible struct. An extensible struct is an upwardly-compatible extension of CORBA structs. When you use extensible structs, objects are passed by value.

Pass-by-value is the ability to pass object state to another Java program. Assuming that a class definition of the Java object is present on the server side, the Java program can invoke methods on the cloned object that has the same state as the original object.

The use of extensible structs is a Visigenic extension to the OMG IDL; there is an additional keyword - extensible. If you want to stay within pure CORBA or if you are going to port your code to other ORBs, you should use IDL structs and not extensible structs. Extensible structs allow you to use classes that can be defined in Java but cannot be defined in IDL because of CORBA limitations.

VisiBroker uses Java serialization to pass classes in the form of extensible structs. Java serialization compresses a Java object's state into a serial stream of octets that can be passed on-the-wire as part of the request.

Note: Because of Java serialization, all the data types that are passed must be serializable; that is, they must implement java.io.Serializable.

Extensible structs allow data that use pointer semantics to be passed successfully. For example, defining a linked list in your application code is standard practice, yet there is no way to define a linked list in standard IDL. The solution to this problem is that you can define it by using extensible structs. When you pass extensible structs across address spaces, pointers are maintained.

See the example illustrated in Figure 2. When this data structure arrives at its destination, it looks exactly as it looked when it was originally sent: there are four elements in this linked list, with an h and t pointing to the head and tail respectively. It retains the structure going across the wire-which cannot be done with today's IDL. VisiBroker stubs and skeletons know exactly how to marshal and unmarshal extensible structs.

Figure 2   Two-way linked list.

An extensible struct example

This section will illustrate the concepts about extensible structs by providing several code samples. In Code Sample 6 the code sample shows us a fairly simple class that cannot be defined in IDL. The class has a constructor to help construct linked lists and a toString method which prints out the values of the list. With the mapping rules in mind, there are a few things that point out that this class will map as an extensible struct:

To open the sample project List.jpr which provides an example of this,

  1. Select File|Close All to close all existing projects.

  2. Select File|Open/Create. Look in the directory jbuilder/samples/VisiBroker/samples/pass_by_value/List/. Open the project List.jpr.

  3. Click on List.java in the Navigation pane.

  4. View the code from the sample below in the Source window.

Code Sample 6   An example of a Java class that would be mapped as an extensible struct.
CODE:
// List.java

public class List implements java.io.Serializable {
  private String _data;
  private List _next;
  public List(String data, List next) {

    _data = data;
    _next = next;
  }
  public String data() {
    return _data;
  }
  public List next() {
    return _next;
  }
  public void next(List next) {
    _next = next;
  }
  public String toString() {
    StringBuffer result = new StringBuffer("{ ");
    for(List list = this; list != null; list = list.next()) {
      result.append(list.data()).append(" ");
    }
    return result.append("}").toString();
  }
}

Next, is the ListUtility interface in Code Sample 7. This is an interface that has methods to manipulate the linked list defined in Code Sample 6. The interface has all the attributes of a Caffeine-enabled interface: it is a public interface that extends org.omg.CORBA.Object.

The ListUtility interface defines these methods:

Code Sample 7   A Caffeine-enabled interface.
CODE:
// ListUtility.java

public interface ListUtility extends org.omg.CORBA.Object {
  public int length(List list);
  public List reverse(List list);
  public List sort(List list);
}

The ListServer class, illustrated in Code Sample 8, is a standard server class. It extends the skeleton, has a constructor, and has implementations of all of the methods (length, reverse, and sort).

Note: The List implementations in these code samples do not support circular lists.

The length and reverse methods are straightforward. The sort method is implemented by doing a merge-sort. The sort method has a base case which calculates that if the length of the list is one or less, the list is already sorted. Otherwise, it splits the list in half, sorts each half of the list, and merges the two halves into a sorted list.

To do the merge portion of the merge-sort, the merge method takes two sorted lists and merges them into a single sorted list.

The main program in ListServer is standard: it initializes the ORB and BOA (Basic Object Adapter), instantiates ListServer, gets the object ready, and prints out that the object is ready.

Code Sample 8   Implementing the List interface.
CODE:
// ListServer.java

public class ListServer extends _ListUtilityImplBase {
  ListServer(String name) {
    super(name);
  }
  public int length(List list) {
    int result = 0;
    for( ; list != null; list = list.next()) {
      result++;
    }
    return result;
  }
      
  public List reverse(List list) {
    List result = null;
    for( ; list != null; list = list.next()) {
      result = new List(list.data(), result);
    }
    return result;
  }

  public List sort(List list) {
    int length = length(list);
    if(length <= 1) {
      return list;
    }
    // split the list in half
    List lhs = list;
    for(int i = 0; i < length / 2 - 1; i++) {
      list = list.next();
    }
    List rhs = list.next();
    // this actually splits the lists
    list.next(null);
    // sort and merge the two halves
    return merge(sort(lhs), sort(rhs));
  }
  public List merge(List lhs, List rhs) {
    if(lhs == null) {
      return rhs;
    }
    if(rhs == null) {
      return lhs;
    }
    // figure out which side is next
    List head;
    if(lhs.data().compareTo(rhs.data()) < 0) {
      head = lhs;
      lhs = lhs.next();
    }
    else {
      head = rhs;
      rhs = rhs.next();
    }
    head.next(merge(lhs, rhs));
    return head;
  }
  public static void main(String[] args) {
    org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(args,null);
    org.omg.CORBA.BOA boa = orb.BOA_init();
    ListUtility impl = new ListServer("demo");
    boa.obj_is_ready(impl);
    System.out.println(impl + " is ready.");
    boa.impl_is_ready();
  }
}

The ListClient is also standard: it binds to the ListServer; in three separate short sections it creates lists and finally it calls the operations on each list. The first section prints out a list called "Hello World". It prints out the length, reverse order, and the sorted version. To view an example of the output, see Code Sample 10. The next section is a slightly more complicated list - "This is a test" - and it does the same thing as the first list. The third section in the code reads in the file for ListUtility.java. It reads it into a string and then breaks it into tokens. It breaks on punctuation and white space. It, too, prints the length, reverse order, and sorted version.

Code Sample 9   The Client Application for the list.
CODE:
// ListClient.java

import java.io.*;
import java.util.*;

public class ListClient {
  public static void main(String[] args) throws Exception {
    org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(args,null); 
    ListUtility lu = ListUtilityHelper.bind(orb); 
    {
      List list = new List("Hello", new List("World", null));
      System.out.println("list:    " + list);
      System.out.println("length:  " + lu.length(list));
      System.out.println("reverse: " + lu.reverse(list));
      System.out.println("sort:    " + lu.sort(list));
    }
    {
      List list = new List(
        "this", new List("is", new List("a", new List("test", null))));
      System.out.println("list:    " + list);
      System.out.println("length:  " + lu.length(list));
      System.out.println("reverse: " + lu.reverse(list));
      System.out.println("sort:    " + lu.sort(list));
    }
    {
      // read in the contents of a file
      InputStream input = new FileInputStream("ListUtility.java");
      byte[] bytes = new byte[input.available()];
      input.read(bytes);
      String text = new String(bytes);
      List list = null;
      // break the input into tokens (ignoring white space and punctuation)
      StringTokenizer tokenizer = new StringTokenizer(text, " \n.;,/{}()");
      while(tokenizer.hasMoreTokens()) {
        list = new List(tokenizer.nextToken(), list);
      }
      System.out.println("list:    " + list);
      System.out.println("length:  " + lu.length(list));
      System.out.println("reverse: " + lu.reverse(list));
      System.out.println("sort:    " + lu.sort(list));
    }
  }
}

To build this example, do the following:

  1. If you haven't already opened the List.jpr project, select File|Close All to close all existing projects, then select File|Open/Create. Look in the directory jbuilder/samples/VisiBroker/samples/pass_by_value/List/. Open the project List.jpr.

  2. In the Navigation pane, right-click on List.java. Select Java Source Properties from the context menu.

  3. Check the Generate IIOP Interface field in the VisiBroker Settings box, then click OK.

  4. Right-click on List.java, and select Build from the context menu.

  5. Right-click on ListClient.java, and select Run.

  6. Right-click on ListServer.java, and select Run.

Code Sample 10   The List output.
CODE:
list:    { Hello World }
length:  2
reverse: { World Hello }
sort:    { Hello World }
list:    { this is a test }
length:  4
reverse: { test a is this }
sort:    { a is test this }
list:    { list List sort List public list List reverse List public list List 
length int public Object CORBA omg org extends ListUtility interface public 
java ListUtility }
length:  25
reverse: { ListUtility java public interface ListUtility extends org omg CORBA 
Object public int length List list public List reverse List list public List 
sort List list }

sort:    { CORBA List List List List List ListUtility ListUtility Object 
extends int interface java length list list list omg org public public public 
public reverse sort }

Note: In Code Sample 10 the ListUtility.java was inserted in reverse order. So, when you look at the output for reverse, it really is working correctly.

Summary

What the extensible struct code samples are showing is the ability to pass these data types (extensible structs) and that, when passed, they retain their values. They behave like any other CORBA data type plus they have this additional advantage: with extensible structs you can go beyond what you can do with IDL and pass arbitrary Java serializable objects.

The code samples provided here are simple ones; you could write a much more complicated data structure that has a tree, or a complicated linked list, or a skip list. Using extensible structs, you could pass any of these complicated data structures by value.

Extensible structs and GIOP messages

Note: The following section is a discussion of an advanced topic and is provided for developers writing code for non-VisiBroker ORBs who want their code to understand and interpret extensible structs as implemented in VisiBroker. Please be advised that if GIOP formats change, the way VisiBroker works with GIOP messages will also change and your applications may need to be modified.

When messages are sent with an interface that extends org.omg.CORBA.Object, a GIOP (General Inter-ORB protocol) message is created. The standard header is included in the message, as well as the arguments. Each argument is put into an octet sequence containing the Java serialization. An octet sequence is a standard CORBA type used in GIOP messages to pass complex data types.

Figure 3   An octet sequence used in a GIOP message for passing a complex data type as an extensible struct.

If you are working with communication protocols and understand Java serialization, you can take this grouping of bytes in the GIOP message and extract from it the arguments being passed.

Since an extensible struct is just a stream of octets (that is, an array of bytes), a developer working with a non-Visigenic ORB can write code that does the following, if an extensible struct is passed to that non-Visigenic ORB:

For more information, see "Defining CORBA Interfaces With Java" in the "VisiBroker for Java Programmer's Guide", http://www.visigenic.com/techpubs/htmlhelp/vbj30/pg/noframes/chap13.htm