A tutorial on Jini

Jan Newmarch
jan@ise.canberra.edu.au
University of Canberra
DSTC (Distributed Systems Technology CRC)

OVERVIEW


What is Jini?


Jini Federation


Figure 1: Components of a Jini federation

Service Registration


Figure 2: Querying for a service locator

Figure 3: Registrar returned

Figure 4: Service uploaded

Client Lookup


Figure 5: Querying for a service locator

Figure 6: Registrar returned

Figure 7: Asking for a service

Figure 8: Service returned

Support Services


Proxies


Attribute Registration


Service Location


Leasing


Non-distributed Application


Distributed Version


Client Structure

Internally a client will look like

prepare for discovery
discover a lookup service
prepare a template for lookup search
lookup a service
call the service

The following code is simplified from the real case. It attempts to find a FileClassifier service, calling the method getMIMEType() on this service.

public class TestUnicastFileClassifier {

    public static void main(String argv[]) {
	new TestUnicastFileClassifier();
    }

    public TestUnicastFileClassifier() {
	LookupLocator lookup = null;
	ServiceRegistrar registrar = null;
	FileClassifier classifier = null;

        // Prepare for discovery	
        lookup = new LookupLocator("jini://www.all_about_files.com");

        // Discover a lookup service
        // This uses the synchronous unicast protocol
	registrar = lookup.getRegistrar();

        // Prepare a template for lookup search
	Class[] classes = new Class[] {FileClassifier.class};
	ServiceTemplate template = new ServiceTemplate(null, classes, null);

        // Lookup a service
	classifier = (FileClassifier) registrar.lookup(template);

        // Call the service
	MIMEType type;
	type = classifier.getMIMEType("file1.txt");
        System.out.println("Type is " + type.toString());
    }
} // TestUnicastFileClassifier

Server Structure

prepare for discovery
discover a lookup service
create information about a service
export a service
renew leasing periodically

The following code is simplified from the real case. It exports an implementation of a file classifier service, as a FileClassifierImpl object.

public class FileClassifierServer implements DiscoveryListener {
    
    protected LeaseRenewalManager leaseManager = new LeaseRenewalManager();

    public static void main(String argv[]) {
	new FileClassifierServer();

        // keep server running forever to 
	// - allow time for locator discovery and
	// - keep re-registering the lease
        Thread.currentThread().sleep(Lease.FOREVER);
    }

    public FileClassifierServer() {
	LookupDiscovery discover = null;

        // Prepare for discovery - empty here

        // Discover a lookup service
        // This uses the asynchronous multicast protocol,
        // which calls back into the discovered() method
        discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);

        discover.addDiscoveryListener(this);
    }
    
    public void discovered(DiscoveryEvent evt) {
        ServiceRegistrar registrar = evt.getRegistrars()[0];
        // At this point we have discovered a lookup service

        // Create information about a service
	ServiceItem item = new ServiceItem(null,
         				   new FileClassifierImpl(), 
					   null);

        // Export a service
	ServiceRegistration reg = registrar.register(item, Lease.FOREVER);

	// Renew leasing
	leaseManager.renewUntil(reg.getLease(), Lease.FOREVER, this);
    }
} // FileClassifierServer

DISCOVERING A LOOKUP SERVICE


Lookup Service


Unicast discovery


Unicast discovery program

import net.jini.core.discovery.LookupLocator;
import net.jini.core.lookup.ServiceRegistrar;

public class UnicastRegister  {
    
    static public void main(String argv[]) {
        new UnicastRegister();
    }
   
    public UnicastRegister() {
	LookupLocator lookup = null;
	ServiceRegistrar registrar = null;

        try {
            lookup = new LookupLocator("jini://localhost");
        } catch(java.net.MalformedURLException e) {
            System.err.println("Lookup failed: " + e.toString());
	    System.exit(1);
        }

	try {
	    registrar = lookup.getRegistrar();
	} catch (java.io.IOException e) {
            System.err.println("Registrar search failed: " + e.toString());
	    System.exit(1);
	} catch (java.lang.ClassNotFoundException e) {
            System.err.println("Registrar search failed: " + e.toString());
	    System.exit(1);
	}
	System.out.println("Registrar found");

	// the code takes separate routes from here for client or service
    }
   
} // UnicastRegister

Multicast discovery


Multicast discovery program

import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryEvent;
import net.jini.core.lookup.ServiceRegistrar;

public class MulticastRegister implements DiscoveryListener {
 
    static public void main(String argv[]) {
        new MulticastRegister();

	// stay around long enough to receive replies
	try {
	    Thread.currentThread().sleep(10000L);
	} catch(java.lang.InterruptedException e) {
	    // do nothing
	}
    }
      
    public MulticastRegister() {
        LookupDiscovery discover = null;
        try {
            discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
        } catch(Exception e) {
            System.err.println(e.toString());
	    e.printStackTrace();
	    System.exit(1);
        }

        discover.addDiscoveryListener(this);
    }
    
    public void discovered(DiscoveryEvent evt) {

        ServiceRegistrar[] registrars = evt.getRegistrars();

        for (int n = 0; n < registrars.length; n++) {
	    ServiceRegistrar registrar = registrars[n];

	    // the code takes separate routes from here for client or service
	    System.out.println("found a service locator");
  	}
    }

    public void discarded(DiscoveryEvent evt) {

    }
} // MulticastRegister

ServiceRegistrar


ENTRIES


Entry class


Creating entries


Interfaces, implementations and entries


CLIENT


Problem domain


Unicast client

Use this pattern when you do know where the lookup locator is:
package client;

import common.FileClassifier;
import common.MIMEType;

import net.jini.core.discovery.LookupLocator;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistration;
import java.rmi.RMISecurityManager;
import net.jini.core.lookup.ServiceTemplate;

public class TestUnicastFileClassifier {

    public static void main(String argv[]) {
	new TestUnicastFileClassifier();
    }

    public TestUnicastFileClassifier() {
	LookupLocator lookup = null;
	ServiceRegistrar registrar = null;
	FileClassifier classifier = null;

        try {
            lookup = new LookupLocator("jini://www.all_about_files.com");
        } catch(java.net.MalformedURLException e) {
            System.err.println("Lookup failed: " + e.toString());
	    System.exit(1);
        }

	System.setSecurityManager(new RMISecurityManager());

	try {
	    registrar = lookup.getRegistrar();
	} catch (java.io.IOException e) {
            System.err.println("Registrar search failed: " + e.toString());
	    System.exit(1);
	} catch (java.lang.ClassNotFoundException e) {
            System.err.println("Registrar search failed: " + e.toString());
	    System.exit(1);
	}

	Class[] classes = new Class[] {FileClassifier.class};
	ServiceTemplate template = new ServiceTemplate(null, classes, null);
	try {
	    classifier = (FileClassifier) registrar.lookup(template);
	} catch(java.rmi.RemoteException e) {
	    e.printStackTrace();
	    System.exit(1);
	}

	if (classifier == null) {
	    System.out.println("Classifier null");
	    System.exit(2);
	}
	MIMEType type;
	try {
	    type = classifier.getMIMEType("file1.txt");
	    System.out.println("Type is " + type.toString());
	} catch(java.rmi.RemoteException e) {
	    System.err.println(e.toString());
	}
	// System.exit(0);
    }
} // TestUnicastFileClassifier

Client and service locator JVM's

The two JVM's look like

Multicast client

Use this pattern when you don't know where the lookup locator(s) are:
package client;

import common.FileClassifier;
import common.MIMEType;

import java.rmi.RMISecurityManager;
import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryEvent;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceTemplate;

public class TestFileClassifier implements DiscoveryListener {

    public static void main(String argv[]) {
	new TestFileClassifier();

        // stay around long enough to receive replies
        try {
            Thread.currentThread().sleep(100000L);
        } catch(java.lang.InterruptedException e) {
            // do nothing
        }
    }

    public TestFileClassifier() {
	System.setSecurityManager(new RMISecurityManager());

	LookupDiscovery discover = null;
        try {
            discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
        } catch(Exception e) {
            System.err.println(e.toString());
            System.exit(1);
        }

        discover.addDiscoveryListener(this);

    }
    
    public void discovered(DiscoveryEvent evt) {

        ServiceRegistrar[] registrars = evt.getRegistrars();
	Class [] classes = new Class[] {FileClassifier.class};
	FileClassifier classifier = null;
	ServiceTemplate template = new ServiceTemplate(null, classes, 
						       null);
 
        for (int n = 0; n < registrars.length; n++) {
	    System.out.println("Service found");
            ServiceRegistrar registrar = registrars[n];
	    try {
		classifier = (FileClassifier) registrar.lookup(template);
	    } catch(java.rmi.RemoteException e) {
		e.printStackTrace();
		System.exit(2);
	    }
	    if (classifier == null) {
		System.out.println("Classifier null");
		continue;
	    }
	    MIMEType type;
	    try {
		type = classifier.getMIMEType("file1.txt");
		System.out.println("Type is " + type.toString());
	    } catch(java.rmi.RemoteException e) {
		System.err.println(e.toString());
	    }
	    // System.exit(0);
	}
    }

    public void discarded(DiscoveryEvent evt) {
	// empty
    }
} // TestFileClassifier


PROXY IMPLEMENTATION CHOICES


Proxy is the Service


RMI Proxies


Non-RMI Proxy


RMI and Non-RMI Proxies


PROXY IS THE SERVICE


Service implementation

package option2;

import common.MIMEType;
import common.FileClassifier;

public class FileClassifierImpl implements FileClassifier {

    public MIMEType getMIMEType(String fileName) {
        if (fileName.endsWith(".gif")) {
            return new MIMEType("image", "gif");
        } else if (fileName.endsWith(".jpeg")) {
            return new MIMEType("image", "jpeg");
        } else if (fileName.endsWith(".mpg")) {
            return new MIMEType("video", "mpeg");
        } else if (fileName.endsWith(".txt")) {
            return new MIMEType("text", "plain");
        } else if (fileName.endsWith(".html")) {
            return new MIMEType("text", "html");
        } else
            // fill in lots of other types,
            // but eventually give up and
            return null;
    }

    public FileClassifierImpl() {
	// empty
    }
    
} // FileClassifierImpl

File classifier server

package option2;

import java.rmi.RMISecurityManager;
import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryEvent;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistration;
import net.jini.core.lease.Lease;
import com.sun.jini.lease.LeaseRenewalManager;
import com.sun.jini.lease.LeaseListener;
import com.sun.jini.lease.LeaseRenewalEvent;

public class FileClassifierServer implements DiscoveryListener, 
                                             LeaseListener {
    
    protected LeaseRenewalManager leaseManager = new LeaseRenewalManager();

    public static void main(String argv[]) {
	new FileClassifierServer();

        // keep server running forever to 
	// - allow time for locator discovery and
	// - keep re-registering the lease
	        try {
            Thread.currentThread().sleep(Lease.FOREVER);
        } catch(java.lang.InterruptedException e) {
            // do nothing
        }
    }

    public FileClassifierServer() {

	LookupDiscovery discover = null;
        try {
            discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
        } catch(Exception e) {
            System.err.println(e.toString());
            System.exit(1);
        }

        discover.addDiscoveryListener(this);
    }
    
    public void discovered(DiscoveryEvent evt) {

        ServiceRegistrar[] registrars = evt.getRegistrars();

        for (int n = 0; n < registrars.length; n++) {
            ServiceRegistrar registrar = registrars[n];

	    ServiceItem item = new ServiceItem(null,
					       new FileClassifierImpl(), 
					       null);
	    ServiceRegistration reg = null;
	    try {
		reg = registrar.register(item, Lease.FOREVER);
	    } catch(java.rmi.RemoteException e) {
		System.err.println("Register exception: " + e.toString());
	    }
	    System.out.println("service registered");

	    // set lease renewal in place
	    leaseManager.renewUntil(reg.getLease(), Lease.FOREVER, this);
	}
    }

    public void discarded(DiscoveryEvent evt) {

    }

    public void notify(LeaseRenewalEvent evt) {
	System.out.println("Lease expired " + evt.toString());
    }   
    
} // FileClassifierServer


All the JVM's


JOIN MANAGER


Join Manager


Service using Join Manager

package joinmgr;

import option2.FileClassifierImpl;

import com.sun.jini.lookup.JoinManager;
import net.jini.core.lookup.ServiceID;
import com.sun.jini.lookup.ServiceIDListener;
import com.sun.jini.lease.LeaseRenewalManager;
import net.jini.discovery.LookupDiscovery;

public class FileClassifierServer implements ServiceIDListener {
    
    public static void main(String argv[]) {
	new FileClassifierServer();

        // stay around long enough to receive replies
        try {
            Thread.currentThread().sleep(1000000L);
        } catch(java.lang.InterruptedException e) {
            // do nothing
        }

    }

    public FileClassifierServer() {

	JoinManager joinMgr = null;
	try {
	    joinMgr = new JoinManager(new FileClassifierImpl(),
				      null,
				      LookupDiscovery.ALL_GROUPS,
				      null,
				      this,
				      new LeaseRenewalManager());
	} catch(Exception e) {
	    e.printStackTrace();
	    System.exit(1);
	}
    }

    public void serviceIDNotify(ServiceID serviceID) {
	System.out.println("got service ID " + serviceID.toString());
    }
    
} // FileClassifierServer

SECURITY


JDK 1.2 Security


Service requirements

grant {
    permission net.jini.discovery.DiscoveryPermission "*";
    // multicast request address
    permission java.net.SocketPermission "224.0.1.85", "connect,accept";
    // multicast announcement address
    permission java.net.SocketPermission "224.0.1.84", "connect,accept";

    // RMI connections
    permission java.net.SocketPermission "*.dstc.edu.au:1024-", "connect,accept";
    permission java.net.SocketPermission "130.102.176.249:1024-", "connect,accept";
    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,accept";

    // reading parameters
    // like net.jini.discovery.debug!
    permission java.util.PropertyPermission "net.jini.discovery.*", "read";
};

Client requirements

The client is most at risk as it imports remote objects into its address space

Client policy

grant {
    permission net.jini.discovery.DiscoveryPermission "*";

    // multicast request address
    permission java.net.SocketPermission "224.0.1.85", "connect,accept";
    // multicast announcement address
    permission java.net.SocketPermission "224.0.1.84", "connect,accept";

    // RMI connections
    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,accept";
    permission java.net.SocketPermission "*.dstc.edu.au:1024-", "connect,accept";
    permission java.net.SocketPermission "130.102.176.249:1024-", "connect,accept";

    // DANGER
    // HTTP connections - this is where external code may come in - careful!!!
    permission java.net.SocketPermission "127.0.0.1:80", "connect,accept";
    permission java.net.SocketPermission "*.dstc.edu.au:80", "connect,accept";

    // reading parameters
    // like net.jini.discovery.debug!
    permission java.util.PropertyPermission "net.jini.discovery.*", "read";

};

REMOTE EVENTS


Event models


Remote events


A single listener list

A list with only one listener on it can be done by

protected RemoteEventListener listener = null;

public EventRegistration addRemoteListener(RemoteEventListener listener)
       throws java.util.TooManyListenersException {
    if (this.listener == null {
        this.listener = listener;
    } else {
        throw new java.util.TooManyListenersException();
    }
    return new EventRegistration(0L, this, null, 0L);
}

Single listener event notification

The source object can send an event to its listener by

protected void fireNotify(long eventID,
                          long seqNum) {
    if (listener == null) {
        return;
    }

    RemoteEvent remoteEvent = new RemoteEvent(this, eventID, 
                                              seqNum, null);
    listener.notify(remoteEvent);
}

Mutable file classifier

Allow the file classifier to change its list of MIME type mappings, and notify listeners when it does so

package mutable;

import java.rmi.server.UnicastRemoteObject;
import java.rmi.MarshalledObject;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.EventRegistration;
import java.rmi.RemoteException;
import net.jini.core.event.UnknownEventException ;

import javax.swing.event.EventListenerList;

import common.MIMEType;
import common.MutableFileClassifier;
import java.util.Map;
import java.util.HashMap;

public class FileClassifierImpl extends  UnicastRemoteObject 
                                implements RemoteFileClassifier {

    /**
     * Map of String extensions to MIME types
     */
    protected Map map = new HashMap();

    /**
     * Listeners for change events
     */
    protected EventListenerList listenerList = new EventListenerList();

    public MIMEType getMIMEType(String fileName) 
	throws java.rmi.RemoteException {

	MIMEType type;
	String fileExtension;
	int dotIndex = fileName.lastIndexOf('.');

	if (dotIndex == -1 || dotIndex + 1 == fileName.length()) {
	    // can't find suitable suffix
	    return null;
	}

	fileExtension= fileName.substring(dotIndex + 1);
	type = (MIMEType) map.get(fileExtension);
	return type; 
    }

    public void addType(String suffix, MIMEType type)
	throws java.rmi.RemoteException {
	map.put(suffix, type);
	fireNotify(ADD_TYPE);
    }

    public void removeMIMEType(String suffix, MIMEType type)
	throws java.rmi.RemoteException {
	if (map.remove(suffix) != null) {
	    fireNotify(REMOVE_TYPE);
	}
    }

    public EventRegistration addRemoteListener(RemoteEventListener listener)
	throws java.rmi.RemoteException {
	listenerList.add(RemoteEventListener.class, listener);

	return new EventRegistration(0, this, null, 0);
    }

    // Notify all listeners that have registered interest for
    // notification on this event type.  The event instance 
    // is lazily created using the parameters passed into 
    // the fire method.

    protected void fireNotify(long eventID) {
	RemoteEvent remoteEvent = null;
	
	// Guaranteed to return a non-null array
	Object[] listeners = listenerList.getListenerList();
	
	// Process the listeners last to first, notifying
	// those that are interested in this event
	for (int i = listeners.length - 2; i >= 0; i -= 2) {
	    if (listeners[i] == RemoteEventListener.class) {
		RemoteEventListener listener = (RemoteEventListener) listeners[i+1];
		if (remoteEvent == null) {
		    remoteEvent = new RemoteEvent(this, eventID, 
						  0L, null);
		}
		try {
		    listener.notify(remoteEvent);
		} catch(UnknownEventException e) {
		    e.printStackTrace();
		} catch(RemoteException e) {
		    e.printStackTrace();
		}
	    }
	}
    }    

    public FileClassifierImpl()  throws java.rmi.RemoteException {
	// load a predefined set of MIME type mappings
	map.put("gif", new MIMEType("image", "gif"));
	map.put("jpeg", new MIMEType("image", "jpeg"));
	map.put("mpg", new MIMEType("video", "mpeg"));
	map.put("txt", new MIMEType("text", "plain"));
	map.put("html", new MIMEType("text", "html"));
    }
} // FileClassifierImpl

INTERFACING TO HARDWARE


Lego MindStorms

MindStorms is a "Robotics Invention System"

RCX programmed from Java


MindStorms as a Jini Service


RCX Port Impl

This is best done as an RMI proxy to the service on the server. The service has an RCXPort as attribute, and passes on messages to it.

package rcx.jini;

import java.rmi.server.UnicastRemoteObject;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.RemoteEventListener;
import rcx.*;
import java.io.*;
import java.util.*;

public class RCXPortImpl extends UnicastRemoteObject
    implements RemoteRCXPort, RCXListener {

    protected String error = null;
    protected byte[] message = null;
    protected RCXPort port = null;
    protected RemoteEventListener listener = null;
    protected long messageSeqNo, errorSeqNo;  

    public RCXPortImpl() 
	throws java.rmi.RemoteException {

	Properties parameters;
	String portName = null;
	File f = new File("parameters.txt");
        if (!f.exists()) {
            f = new File(System.getProperty("user.dir")
                         + System.getProperty("path.separator")
                         + "parameters.txt");
        }
        if (f.exists()) {
            try {
		FileInputStream fis = new FileInputStream(f);
		parameters = new Properties();
		parameters.load(fis);
		fis.close();
                portName = parameters.getProperty("port");
            } catch (IOException e) { }
        } else {
	    System.err.println("Can't find parameters.txt with \"port=...\" specified");
	    System.exit(1);
	}
	
        port = new RCXPort(portName);
        port.addRCXListener(this);
	
    }
    
    public boolean write(byte[] byteCommands)
	throws java.rmi.RemoteException {
	return port.write(byteCommands);
    }

    public byte[] parseString(String command) 
	throws java.rmi.RemoteException {
	return RCXPort.parseString(command);
    }

    /**
     * Received a message from the RCX.
     * Send it to the listener
     */
    public void receivedMessage(byte[] message) {

	this.message = message;

	// Send it out to listener
	if (listener == null) {
	    return;
	}

	RemoteEvent evt = new RemoteEvent(this, MESSAGE_EVENT, messageSeqNo++, null);
	try {
	    listener.notify(evt); 
	} catch(net.jini.core.event.UnknownEventException e) {
	    e.printStackTrace();
	} catch(java.rmi.RemoteException e) {
	    e.printStackTrace();
	}
    }

    /**
     * Received an error message from the RCX.
     * Send it to the listener
     */
    public void receivedError(String error) {
	// System.err.println(error);

	// Send it out to listener
	if (listener == null) {
	    return;
	}
	this.error = error;
	RemoteEvent evt = new RemoteEvent(this, ERROR_EVENT, errorSeqNo, null);
	try {
	    listener.notify(evt);
	} catch(net.jini.core.event.UnknownEventException e) {
	    e.printStackTrace();
	} catch(java.rmi.RemoteException e) {
	    e.printStackTrace();
	}
    }

    /**
     * Expected use: the RCX has returned a message, 
     * and we have informed the listeners. They query
     * this method to find the message for the message
     * seqence number they were given in the RemoteEvent.
     * We could use this as an index into a table of messages.
     */
    public byte[] getMessage(long msgSeqNo) {
	return message;
    }

    /**
     * Expected use: the RCX has returned an error message, 
     * and we have informed the listeners. They query
     * this method to find the error message for the error message
     * seqence number they were given in the RemoteEvent.
     * We could use this as an index into a table of messages.
     */
    public String getError(long errSeqNo) {
	return error;
    }

    /**
     * Add a listener for RCX messages.
     * Should allow more than one, or throw
     * TooManyListeners if more than one registers
     */
    public void addListener(RemoteEventListener listener) {
	this.listener = listener;
	messageSeqNo = 0;
	errorSeqNo = 0;
    }
} // RCXPortImpl

CORBA AND JINI


CORBA

CORBA is also an infrastructure for distributed systems. It is targeted at a slightly different "space" to Jini, and has some minor and major differences.

CORBA Hello World


Jini/CORBA interaction


Jini Hello service

package corba;

import org.omg.CosNaming.*;
import org.omg.CORBA.*;
import corba.HelloApp.*;

public class JavaHelloImpl implements JavaHello {
    
    protected Hello helloRef = null;
    protected String[] argv;

    public JavaHelloImpl(String[] argv) {
	this.argv = argv;
    }

    public String sayHello() {
	// Hello helloRef = null;

	if (helloRef == null) {
	    helloRef = getHelloRef();
	}
	// now invoke methods on the CORBA proxy
	String hello = helloRef.sayHello();
	return hello;
    }

    protected Hello getHelloRef() {
	ORB orb = null;
	// Act like a CORBA client
	try {
	    orb = ORB.init(argv, null);

	    // find the CORBA name server
	    org.omg.CORBA.Object objRef = 
		orb.resolve_initial_references("NameService");
	    NamingContext ncRef = NamingContextHelper.narrow(objRef);

 	    // find the CORBA Hello proxy
	    NameComponent nc = new NameComponent("Hello", "");
	    NameComponent path[] = {nc};
	    org.omg.CORBA.Object obj = ncRef.resolve(path);
	    Hello helloRef = HelloHelper.narrow(obj);
	    return helloRef;
	} catch(Exception e) {
	    e.printStackTrace();
	    return null;
	}
    }
} // JavaHelloImpl

Future Work


CONCLUSION


Problems and Issues


Conclusion


URLs


Jan Newmarch (http://pandonia.canberra.edu.au)
jan@ise.canberra.edu.au
Last modified: Mon Oct 30 19:09:19 EST 2000
Copyright ©Jan Newmarch