Communicating With Other Xlets

There are times when you may want your Xlet to communicate with other Xlets that are running on the system. This allows a set of Xlets to be crafted in such a way that some common functions could

There are times when you may want your Xlet to communicate with other Xlets that are running on the system. This allows a set of Xlets to be crafted in such a way that some common functions could be implemented in a client-server model, which may be more efficient in some cases.

At first sight, this seems quite easy - after all, we can simply get a reference to an object in the other Xlet and reference that, right? Well, actually, no. The classloader model in Java (and MHP) makes this impossible. Luckily for us, MHP gives us an API that we can use to solve this.

Classloader physics in MHP

The security model in MHP specifies that every application should be loaded using a different classloader. A classloader does pretty much what its name suggests - it loads classes. Classloaders allow the system to provide a mechanism for loading classes from several different sources - for instance, a PC implementation of Java may have one classloader that can load classes from a local hard disk, and another classloader to load classes via an HTTP connection from a remote server.

The important thing is that classloaders are intimately linked to the identity of a class. The Java language specification states that two classes are only identical if their fully-qualified name is the same (e.g. java.lang.String) and they are loaded through the same classloader. If two classes are loaded through different classloaders then they are different classes, even if the contents of the class file is identical.

MHP specifies that each application should have a dedicated classloader, and that all classes for an application should be loaded through its own classloader. This ensures that classes belonging to one application are totally separate from classes belonging to another application. This helps avoid name clashes where two applications have classes with identical fully-qualified names, but it also imposes an extra security barrier. If application A wanted to access an object from application B, it would have to load the class that the object is an instance of. However, by loading the class using its own classloader, the class that application A has loaded is still not the same as the one loaded by application B. This means that references to objects cannot be passed between applications because referencesto their parent classes would break (amongst other things).

If your brain is starting to hurt, that's OK. This is scary stuff. I promise that it will make sense eventually.

As if this wasn't complex enough, we have two more classloaders to worry about. The system classloader is used by the Java VM to load the classes that are provided by the Java implementation (which will include middleware classes, in the case of MHP or OCAP). Before loading any class, a classloader should first check to see whether that class can be loaded by the system classlaoder. This lets the JVM keep a single copy of its system classes in memory, no matter what classloader originally makes the request for that class.

The other classloader works at an even lower level. The primordial classloader is used by the JVM when it starts to load the most basic classes that it needs - classes such as java.lang.Object and java.lang.Thread that must be loaded before the JVM can create its system classloader.  Once the JVM is fully started, all other class loading will be carried out through the system classloader.  Classes loaded by the system classloader or the primordial classloader are visible to classes loaded through other classloaders, although this is the only case where this is possible.

The diagram below shows how the system classloader and the application classloaders are related:

Classloaders in an MHP-based receiver.

Classloaders in an MHP-based receiver

Let's consider a concrete example of this. In this case, we will assume that we have two different MHP applications (each with its own classloader, as we've already seen). Each application will try to load two classes: Java.util.Vector and appspecific.SomeClass

First we'll look at what happens when the two applications need to load the Java.util.Vector class. In each case, the middleware issues a call to the loadClass() method on the instance of the DSM-CC classloader that's used by that application. The DSM-CC classloaders then both try to load the class as a system class by calling their findSystemClass() method which automatically tries to load the class using the system classloader.

In this case, Java.util.Vector is a system class, so the system classloader loads it. The DSM-CC classloaders both get a non-null result from their call to findSystemClass(), and so they simply need to return this result to the part of the runtime system that requested it. Since in both cases, the class was loaded via the system classloader, it is regarded as the same class.

OK, now let's look at the other case. When the DSM-CC classloaders call findSystemClass() to try to load the appspecific.SomeClass class, the system classloader returns a null reference because the system classloader can't find that class. In this case, both classloaders will try to load it from the DSM-CC object carousel that they are currently connected to. In the case of classloader A, it can find the class on the object carousel and will load it. This then gets returned to the runtime system.

Classloader B, however, can't find that class on the object carousel that it is connected to. Therefore, it will return a null reference to the JVM, which will in turn throw an exception.

If this still gives you headaches, take a look at the IBM DeveloperWorks tutorial on classloaders. This is a fairly good tutorial that introduces you to the basics and to the process of creating new classloaders. MHP applications can't create their own classloaders (although they can use the org.dvb.lang.DVBClassLoader class to load classes from other sources - see the tutorial on filesystem access to learn more about this).

Remote Method Invocation

Luckily for us, Java already has something for allowing applications to communicate when it is not possible to simply share a reference. Java Remote Method Invocation (RMI) was originally designed for use in desktop Java implementations so that applications could communicate with applications on a different machine.

Using RMI, every machine that supports RMI runs an RMI registry. This is a daemon that keeps track of what objects are available to be used by remote applications. When an application wants to export an object, it binds the object to a name in the registry. This allows other applications to find that object. In conventional RMI implementations, this name will include a host name and port number identifying the host that is exporting the name. The only restriction on the type of objects that can be exported is that they must implement the java.rmi.Remote interface.

Once an object has been exported, another application can access it. To do this, the application that wishes to use it must perform a lookup on the remote registry. An RMI client can't simply search all the RMI-enabled machines on a network for a given name, so it must know the name of the machine it wishers to connect to. In a client-server situation, this is not a problem.

This lookup process will return a reference to the remote object (actually, a reference to a stub class that passes method calls to the remote object). Once an application has this, it can call methods in this just as if the object was local. Accessing public fields of the remote object isn't possible, however, due to the pain involved in keeping these field values synchronized between the original and any remote copies.

RMI extensions

There are two big disadvantages to using conventional RMI on an MHP receiver. First, it typically uses TCP/IP for it's communication mechanism, which is a probem because some profiles of the MHP specification do not require TCP/IP support. Second, it requires a set of stub classes to be pre-generated. Since these use native code, this causes major problems in the world of MHP receivers where portability is so important and where it may not be clear which receivers are actually deployed in the marketplace.

To fix this, Sun and DVB added some extensions to the standard RMI API. The most noticeable of these is that RMI is no longer required to have pre-defined stub classes. While this makes things a little harder for middleware implementors, it makes the application developer's life a lot easier. The other major difference is that instead of using the java.rmi.Naming class, the inter-Xlet communication mechanism in MHP provides the org.dvb.io.ixc.IxcRegistry class this is used as the registry. Instead of using the java.rmi.Naming class, MHP applications should use the IxcRegistry class in its place.

The DVB classes are very similar to the inter-Xlet communication mechanism defined in the javax.microedition.xlet.ixc package, which is part of J2ME Personal Basis Profile and Personal Profile. There is a reason for this - they were developed by the same people, and were first used in MHP before being supported as part of the standard J2ME platform. The two sets of classes are intended to complement one another so that implementations of the org.dvb.ixc.* classes can be a simple set of stubs on top of the J2ME classes.

An example of inter-Xlet communication

The following example should hopefully make inter-Xlet communication appear a little easier.

// we need to implement java.rmi.Remote to
// indicate to the middleware that instances
// of this class may be exported over RMI.
public class Exporter
  implements java.rmi.Remote {

  // some private fields.  These could be public,
  // however, and they would still not be
  // directly visible to other applications.
  private value = 0;
  private Exporter exportedObject;

  // Constructor
  private Exporter() {
  }

  // this method allows us to set the value of one
  // of our private fields.  As well as being an
  // example of a remote method, it also shows how
  // we can avoid the restriction of not being able
  // to access fields in remote objects.  We can
  // simply use getter/setter methods instead.
  //
  // Any exported method must throw a
  // RemoteException
  public setValue(int a)
    throws java.rmi.RemoteException {
      value =a;
  }

  // the method that actually sets up and exports an
  // object.
  public static exportMe() {

    // the xlet context is the xlet context of this
    // xlet, which was passed in to the
    // Xlet.initXlet() method
    XletContext exportingXletContext;
    exportingXletContext = myXletContext;

    if (exportedObject == null) {
      // create a new Exporter instance.   This
      // will be the object that we export.
      exportedObject = new Exporter();
    }

    // now, we bind the object we will export to a
    // name in the registry.  We pass the Xlet
    // context of the exporting application, the
    // name the object should be exported under and
    // finally the exported object itself as
    // parameters.
    try {
      org.dvb.io.ixc.IxcRegistry.bind(
        exportingXletContext,
        "Exporter",
        exportedObject);
    }
    catch (AlreadyExportedException aee) {
      // an object has already been exported by
      // this xlet with this name.  In this case,
      // we could use IxcRegistry.rebind()
      // to export the object instead
    }
    catch (NullPointerException npe) {
      // one of the parameters is null, which is
      // not allowed.
    }

    // The object is now exported and can be used by
    // other applications.
    }
  }
}

The class that uses this exported object will do so in the following way. It's important to remember that this takes place in a completely separate application, in a completely separate Xlet context:

public class Importer {

  public useRemoteObject() {

    // the xlet context is the xlet context of this
    // xlet, which was passed in to the
    // Xlet.initXlet() method
    XletContext importingXletContext;
    importingXletContext = myXletContext;

    // first we have to find the remote object.  We do
    // this by calling the lookup() method on the
    // registry, which takes the xlet context of the
    // importing xlet and the name of the object to be
    // imported.
    java.rmi.Remote remoteObject
    remoteObject = org.dvb.io.ixc.IxcRegistry.lookup(
      importingXletContext,
      "1/2/Exporter");

    // assuming we get a non-null response, we need to
    // cast the imported object into the correct type.
    Exporter importedObject = (Exporter) remoteObject;

    // now we can use it, although we need to make sure
    // we catch any remote exceptions.
    try {
      importedObject.setValue(42);
    }
    catch (java.rmi.RemoteException) {
      // do nothing in this case
    }
  }

}

Implementing inter-Xlet communication

In this tutorial we have focussed more on how you use inter-Xlet communication, but implementing it can be a big challenge for middleware developers. While this topic is too big to cover here in any detail, the book associated with this website goes into this topic in much more detail. There are a number of issues that face middleware developers who are trying to do this, but all of them have solutions (even if they are occasionally rather scary). The book goes into this topic in much more detail than we could hope to cover here.