Skip to content

Jini Made Easier – Writing a Jini Service for Seven

October 12, 2006

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?

Advertisements
2 Comments
  1. October 12, 2006 8:31 pm

    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.

  2. Mark Brouwer permalink
    October 14, 2006 12:23 am

    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.

Comments are closed.

%d bloggers like this: