Jini Made Easier - Writing a Jini Service for Seven

I gave a presentation to the local Calgary JUG entitled, “Making Jini Easy (or at least easier)”. For those that want to follow along, I have posted the sample Netbeans project at http://mediacast.sun.com/share/warren/ShippingService.zip

This tutorial will show you how to build and deploy a simple Jini service to the Seven container. Seven is a an implementation of the Jini Service Container (JSC) specification. Both Seven and the JSC spec have been developed as part of the cheiron project. As with the Jini platform, Seven is offered under the Apache 2 license.


The JSC specification is a promising direction towards the goal of making Jini easier to use. The basic idea is to let the container handle the heavy lifting with respect to developing and deploying a Jini Service.

Prerequisites

To build and deploy this tutorial you will need the following components:

  • Netbeans (5.5 or 6.0 builds should be fine)

  • JDK 1.5 (note that Seven has an incompatibility with JDK 1.6 – so best to stay on 1.5 for now)

  • A build of the seven suite ( I used 0.1.1 for this tutorial)

Before opening the Netbeans project create a “JSC” library containing the relevant Seven jar files. Here is what mine looks like (you will need to change this to suit your environment)






The Shipping Service Example

The tutorial presents a simple service that calculates shipping costs for packages based on their weight and the destination postal code. The business interface (this is what the client sees) to the service is simple:

package demo;

import java.rmi.RemoteException;

/**
 *
 * @author warren
 */
public interface ShippingService {
    public float calculateShippingCosts(String postalcode, float packageWeight ) throws RemoteException;
}


For simplicity, our shipping service only takes a destination postal code – but you get the idea behind the interface.

Remote Interface

The remote interface (what the services provides) is as follows:


package demo;

import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.Map;

/**
 * Remote interface for the shipping service. This is
 * the "private" backend protocol between the proxy 
 * and the service.
 * @author warren
 */
public interface ShippingServiceRemote extends Remote {
    public Map getShippingTables() throws RemoteException;
}


If you have not used Jini before, the above may look rather odd., in that the client's view of the service is not the same as what the service actually provides! The missing link here is the “smart proxy”:, which bridges the client to the service. Let's have a look:



package demo;

import java.io.Serializable;
import java.rmi.RemoteException;
import java.util.Map;

/**
 * proxy that gets downloaded to the client.
 * The proxy implements caching of rate tables
 * vended by the backend service. The proxy
 * provides local lookup of shipping rates.
 * 
 * @author warren
 */
public class ShippingServiceProxy implements ShippingService, Serializable {
    
    private Map shippingTables;
    private final ShippingServiceRemote server;
    
    /** Creates a new instance of ShippingServiceProxy */
    public ShippingServiceProxy(Object connectorStub) {
        server = (ShippingServiceRemote)connectorStub;
    }
    
    public float calculateShippingCosts(String postalcode, float packageWeight) throws RemoteException {
        // Do we need the fetch the rate tables from the service?
        if( shippingTables == null )
            shippingTables = server.getShippingTables();
        
        Float costFactor = (Float) shippingTables.get(postalcode);
        if( costFactor == null )
            throw new IllegalArgumentException("Bad Postal code");
        
        return packageWeight * costFactor.floatValue();
    }
    
    private static final long serialVersionUID = -2719045781828648539L;
}


The smart proxy is dynamically downloaded to our client when it looks up our service in the Jini LUS. This is all part of the magic of Jini – which we really don't have time to cover here, but please read Jan Newmarch's excellent tutorials if you are curious.


Smart proxies are one of things that makes Jini so powerful. In the above example, we can see that our proxy is performing local caching of shipping rate tables that it receives from the service. This turns most shipping rate calculations into a local lookup in a rate table instead of a remote procedure call. This is a good example of a service which is impractical to implement as a Web Service, but works fine in Jini (imagine the performance penalty of trying to look up several hundred shipping costs per second using a remote ShippingService interface). Now of course, as a service provider we could advertise the ShippingServiceRemote interface - but this exposes a lot implementation detail to our clients. If the algorithm changes we will have to ask our clients to update their code. The use of smart proxies gives us a clean interface along with a flexible implementation strategy.


The only remaining piece is the actual service code. This is where Seven helps us by providing a container that handles much of the service registration and configuration code. Here is our rather simple service:


package demo;

import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.Map;
import net.jini.core.entry.Entry;
import net.jini.lookup.entry.Name;
import org.cheiron.jsc.JSCException;
import org.cheiron.jsc.JSCFailure;
import org.cheiron.jsc.JiniService;
import org.cheiron.jsc.ServiceContext;
import org.cheiron.jsc.ServiceState;

/**
 * The backend Shipping Service provider.
 * The private protocol between the provider and proxy is
 * an exchange of shipping rate tables. This demonstrates
 * how a proxy can improve performance through caching. 
 * 
 * @author warren
 */
public class ShippingServiceProvider implements JiniService {
    private ServiceContext context;
    
    public void failureDetected(JSCFailure failure) {
    }
    
    public Entry[] getAttributes(Entry[] attributes) throws JSCException {
        return new Entry[]{ new Name("Shipping Cost Calculator Service") };
    }
    
    public Remote[] getServiceProviders() throws JSCException {
        return new Remote[]{ new ProviderImpl() };
    }
    public Object getServiceProxy(Object connectorStub) throws JSCException {
        return new ShippingServiceProxy(connectorStub);
    }
    public void init(ServiceContext context) throws JSCException {
        this.context = context;
    }
    public void stateChanged(ServiceState newState, ServiceState oldState) {
    }
    
    /**
     * The Implementation class - builds and returns
     *  a map of the shipping costs from various postal codes
     */
    private class ProviderImpl implements ShippingServiceRemote {
        public Map getShippingTables() throws RemoteException {
            HashMap map = new HashMap();
            map.put("T2N3L2", new Float(1.21));
            map.put("123456", new Float(1.97));
            return map;
        }
    }    
}


In the above example, the Remote interface is implemented as an inner class. There are a few required life cycle methods – most of which we leave empty for this example.


By implementing the JiniService interface, our service can delegate many tasks to the Jini Service Container. These include:

  • Service Registration with the LUS (Lookup Service)

  • Lease management of our registration with the LUS

  • Life Cycle Management. The container manages creating, starting, stopping and destroying our service

  • Exporting our service proxy

  • Service Configuration

Seven provides other features that I am not going to cover here (for example, server side lease management and service state persistence ).

Building and Deploying

To deploy our service to Seven, we need to create a deployment descriptor. You will find this under the config/ directory in the sample Netbeans project:


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<jsc-descriptor
    xmlns="http://www.cheiron.org/jsc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.cheiron.org/jsc http://www.cheiron.org/schema/jsc.xsd">
    
    <service-group-def oid="2.3.6.1.4.12543.2.3" version="0.1.1">
        
        <description-group>
            <description>Provides Shipping cost calculations for packages</description>
            <display-name>Shipping Cost calculator</display-name>
        </description-group>
        
        <grant-def>
            <permission
                class="net.jini.discovery.DiscoveryPermission" id="perm.discovery">*</permission>
        </grant-def>
    </service-group-def>
    
    <service-def class="demo.ShippingServiceProvider"/>
    
</jsc-descriptor>

The above descriptor declares a service group which is a mechanism used to manage like services as a unit. IANA Object ID's are used to uniquely define service groups (disclaimer: the above OID is not a valid number – it's one I made up!). I'm not sure I am crazy about the use of OIDs as they seems a bit cumbersome to me. My preference would be to use java style package names (reverse dns).


Any permissions required by the service are declared in the deployment descriptor. Seven takes care of standard permissions that are needed to export your proxy, so these do not need to be declared.


Finally, we have our ant build.xml that creates our service archive (.sar) that is deployed via the seven Admin UI:


<?xml version="1.0" encoding="UTF-8"?>
<!-- You may freely edit this file. See commented blocks below for -->
<!-- some examples of how to customize the build. -->
<!-- (If you delete it and reopen the project it will be recreated.) -->
<project name="jsc-demo" default="default" basedir=".">
    <description>Builds, tests, and runs the project</description>
    <import file="nbproject/build-impl.xml"/>
    <property file='build.properties'/>
    
    <taskdef resource="cheirontasks.properties">
        <classpath>
            <fileset dir="${seven.home}/tools/jini/lib" includes="*.jar"/>
        </classpath>
    </taskdef>
     
    <path id="jsc">
        <pathelement location="build/classes"/>
        <fileset
        dir="${seven.lib}"
        includes="cheiron-util.jar,jsc-platform.jar,jsc-platform-lib.jar,serviceui.jar"/>  
    </path>
    
    <path id="jsc-test">
        <path refid="jsc"/>
        <pathelement location="build/test/classes"/>
        <path path="lib/jsk-platform.jar"/>
    </path>
    
    <target name="create-dl" depends="compile">
        <!-- Create the Service jar file -->
        <dljar
            destfile="build/services.jar"
            platform="jsc"
            inroot="demo">              
            <classpath refid="jsc"/>  
            <class name="demo.ShippingServiceProvider"/>         
            <rootdir name="${build.dir}/classes"/>
        </dljar>
        <!-- Create the codebase download jar file.  -->
        <dljar destfile="build/services-dl.jar" platform="jsc-dl">
            
            <classpath refid="jsc"/>
            <rootdir name="demo"/>
            <apiclass name="net.jini.lookup.entry.Name"/>   
            <proxyclass name="demo.ShippingServiceProxy"/>
        </dljar>
    </target>
    
    <target name="create-sar" depends="create-dl">
        <sar
            destfile="dist/${ant.project.name}-${version}.sar"
            jscxml="config/shippingservice-jsc.xml"
            version="0.1.1">
            
            <lib dir="${build.dir}" includes="services.jar"/>      
            <libdl dir="${build.dir}" includes="services-dl.jar"/>
        </sar>
    </target>
    
    <target name="run-seven">
        <exec executable="${seven.cmd}" />
    </target>
    
    <target name="jini-servicebrowser">
        <exec executable="${incax-browser}" spawn="true"/>
    </target>  
</project>


Seven provides two Ant tasks (dljar and sar) that are used to create the service jars and the service archive. The dljar task (based on the ClassDep code found in the Jini Starter kit) makes it easier to generate download jar files with the correct classes and preferred list. Codebase is beyond the scope of this blog entry, but suffice to say that the service-dl.jar file created in the above example contains the classes that must be dynamically downloaded to our client. When we deploy the .sar file to seven, it will ensure that the codebase is set to correctly annotate our service proxies. The Seven JSC runs it's own internal HTTP server for the codebase jar files.


If you have successfully built the project (run the create-sar task), you should be able to bring up the seven admin UI and start your service. Make sure Seven is running (run seven.bat from the install directory, or you can try the included ant task).


In order to access the Seven UI you will need a Jini service browser that supports the ServiceUI specification. In a nutshell, ServiceUI is way of attaching a graphical interface to a service. A ServiceUI client can dynamically load this UI to interact with and manage a remote service instance. You can use the browser included in the Jini Starter kit, or another nice alternative is the free service browser available from Inca X. The following screen shot shows the seven Admin ServiceUI running under IncaX:





To deploy your service, install the sar file that has been created under your dist/ directory. From the above menu, choose ServiceArchive, browse your .sar file, and upload the archive to Seven. Once uploaded, your service group should show up in the JSC container panel on the right hand side. Right click on your service group, and choose “Add Service”. This should prompt you for service and group instance names.


Right click over the new service entry and select "Start" from the context menu. You should see your service interface (ShippingService) show up in left panel of the browser. This confirms that your service has been deployed and is now registered with the Jini Lookup Service (LUS):






Let's wrap up by showing you one last neato feature of the Inca X browser. If you expand the interface tree for your service, you can see the methods that your service provides. Inca X lets you invoke service methods via the browser. For simple testing, this saves your from writing a test client. Click on your method (in our case calculateShippingCosts), enter parameter values for the call, and press the invoke button:






You should see the result of invoking the service (note that our sample service only accepts two postal codes!).

Now that wasn't so hard, was it?

Comments:

You're in Calgary? I get out there every couple of months. Give me a shout and we'll get together next time.

By the way, Harvester's not dead yet!

Greg.

Posted by Greg Trasuk on October 12, 2006 at 09:31 PM MDT #

Hi Warren, I was quite surprised to find out about your coverage of Seven when catching up with Jini related stuff in an Internet Cafe in Darjeeling India.
With regard to the IANA OID's, this decision has been made a long time ago when I was heavily involved with LDAP and extending schemas, but I admit that remembering them is not always that easy. Your suggestion for java style package names isn't that bad, would you be so kind to file an RFE for that against the JSC project.

Posted by Mark Brouwer on October 14, 2006 at 01:23 AM MDT #

Post a Comment:
Comments are closed for this entry.

This blog copyright 2009 by warren