A service is created by a server and registered with lookup services. The server has a fairly standard format, usually varying only in small details: the actual service, its entry attributes, number of services, etc. The ServiceStarter class can help with some of this, and is used by Sun for its tools such as reggie
In the chapter on configuration we looked at how a "meta-server" might be written that would get information from a configuration file describing a service and use that to build the service. In order to make a service available for use, a number of parameters must be set up. These include
The service class and how to construct it
The transport protocol (Jeri/JRMP)
The proxy for the service
The codebase for the proxy files
The classpath for the local files
A security policy to run the server for this service
Entry/attribute information
Unicast locators of lookup services
Group to join on lookup services
Service item ID
Jini has an interface ServiceDescriptor
to give a
standard way of handling some of these. This class is in the
com.sun.jini.start
package. This package is not
specified by Jini, and may change or even disappear in later
versions of Jini.
interface ServiceDescriptor {
Object create(Configuration config); }
This has a number of implementations
NonActivatableServiceDescriptor
SharedActivatableServiceDescriptor
SharedActivationGroupDescriptor
The first is useful for the most common situation described in
this book, of a non-activatable service. The meat of the
NonActivatableServiceDescriptor
class is in its
constructors:
class NonActivatableServiceDescriptor {
NonActivatableServiceDescriptor(String codebase,
String policy,
String classpath,
String implClassName,
String[] serverConfigArgs);
NonActivatableServiceDescriptor(String codebase,
String policy,
String classpath,
String implClassName,
String[] serverConfigArgs,
LifeCycle lifeCycle);
}
The codebase
is a
url(s) of a directory or jar file of the proxy classes on an
HTTP server; policy
is the filename of the policy
for this service within the context of the policy existing for
the server; classpath
is the classpath for the
service run by the server; serverConfigArgs
is an
array of configuration parameters (typically just a single file name);
it is not yet clear what lifeCycle
is, or how it is used.
It is notable what the constructor does, and does not, describe.
It describes the service and how to run it. It describes the
environment for the proxy, but not how to create it. However,
one of the parameters to the create()
method is a
configuration, and this configuration can be used in ways we
have seen before to specify the transport protocol and create
the service proxy. The constructor does not
describe the entry information, the service id or the groups to
which this service belongs. The parameters described by
NonActivatableServiceDescriptor
describe the
service's runtime/deployment environment,
while the missing parameters describe the service's
advertisement environment.
The NonActivatableServiceDescriptor
class provides
an implementation of the create()
method. This is
defined in the interface to return an Object
but
the class actually returns a
com.sun.jini.start.NonActivatableServiceDescriptor.Created
.
This has no methods, just two public fields
public class Created {
public Object impl;
public Object proxy;
}
From this one can extract the
implementation and its proxy.
There are further wrinkles in using
NonActivatableServiceDescriptor
. The implementation
object must be constructed from its class. The constructor has
two parameters: the string array serverConfigArgs
,
and a lifecycle object. At present it is
not clear what role this object is expected to play, and it is
sufficient to use a default value NoOpLifeCycle
.
In addition to creating the implementation object, the
create()
method must create a proxy object for the
implementation in order to return a Created
object.
This is done by requiring the implementation to support one of
the two interfaces ServiceProxyAccessor
or
ProxyAccessor
and calling
getServiceProxy()
or getProxy()
respectively on the implementation. That is, the service must
include the method getServiceProxy()
(or
getProxy()
) and within this method it will
probably create the proxy (possibly using the configuration information).
object and return it.
The implementation must be Remote
and must be able to create a
proxy. In its constructor it is handed a configuration array, so this may as
well be used to find an exporter to get the proxy. The class
starter.FileClassifierStarterImpl
inherits from
rmi.FileClassifierImpl
which adds Remote
to the basic file classifier:
package starter;
import rmi.FileClassifierImpl;
import com.sun.jini.start.ServiceProxyAccessor;
import com.sun.jini.start.LifeCycle;
import net.jini.config.*;
import net.jini.export.*;
import java.rmi.Remote;
import java.rmi.RemoteException;
public class FileClassifierStarterImpl extends FileClassifierImpl
implements ServiceProxyAccessor {
String[] configArgs;
Remote proxy;
public FileClassifierStarterImpl(String[] configArgs, LifeCycle lifeCycle)
throws RemoteException {
super();
this.configArgs = configArgs;
}
public Object getServiceProxy() {
if (configArgs.length == 0) {
return null;
}
try {
// get the configuration (by default a FileConfiguration)
Configuration config = ConfigurationProvider.getInstance(configArgs);
// and use this to construct an exporter
Exporter exporter = (Exporter) config.getEntry( "FileClassifierServer",
"exporter",
Exporter.class);
// export an object of this class
proxy = exporter.export(this);
} catch(Exception e) {
return null;
}
return proxy;
}
}
A configuration file suitable for using Jeri with the above
resources/starter/file_classifier.config
:
import net.jini.jeri.BasicILFactory;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.tcp.TcpServerEndpoint;
FileClassifierServer {
exporter = new BasicJeriExporter(TcpServerEndpoint.getInstance(0),
new BasicILFactory());
}
The server to start this service needs to set various parameters for the
ServiceDescriptor
. These can be set in another configuration
file such as resources/starter/serviceDesc.config
:
import net.jini.core.discovery.LookupLocator;
import net.jini.discovery.LookupDiscovery;
import net.jini.core.entry.Entry;
import java.io.File;
ServiceDescription {
codebase =
"http://192.168.1.13:8080/classes/starter.ServiceDescription-dl.jar";
policy = "policy.all";
classpath = "starter.ServiceDescription-start.jar";
implClass = "starter.FileClassifierStarterImpl";
serverConfigArgs = new String[] {
"/home/httpd/html/java/jini/tutorial/resources/starter/file_classifier.config"
};
}
AdvertDescription {
entries = new Entry[] {};
groups = LookupDiscovery.ALL_GROUPS;
unicastLocators = new LookupLocator[] { // empty
};
serviceIdFile = new File("serviceId.id");
}
This configuration file contains two sets of configurations: one for
the ServiceDescription
component and one for the
AdvertDescription
component (discussed shortly).
The resources/starter/serviceDesc.config
configuration file uses two jar files,
For
rmi/FileClassifierImpl.class
rmi/RemoteFileClassifier.class
For
common/FileClassifier.class
common/MIMEType.class
rmi/FileClassifierImpl.class
rmi/RemoteFileClassifier.class
starter/FileClassifierStarterImpl.class
starter/ServiceDescription.class
A server which picks up the values from this configuration file and creates
the service and its proxy follows. The program essentially uses two parts:
one to build the service using a ServiceDescriptor
and
its configuration entries, the other to advertise the service using
JoinManager
and its associated
AdvertDescription
configuration entries.
(Although JoinManager
has a constructor which will take
a configuration, this does not support any of the entries we specified
above.)
package starter;
import java.rmi.RMISecurityManager;
import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.config.ConfigurationProvider;
import com.sun.jini.start.ServiceDescriptor;
import com.sun.jini.start.NonActivatableServiceDescriptor;
import com.sun.jini.start.NonActivatableServiceDescriptor.Created;
import net.jini.lookup.JoinManager;
import net.jini.core.lookup.ServiceID;
import net.jini.lookup.ServiceIDListener;
import net.jini.core.discovery.LookupLocator;
import net.jini.core.entry.Entry;
import net.jini.lease.LeaseRenewalManager;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.discovery.LookupDiscovery;
import java.rmi.Remote;
import java.io.*;
/**
* ServiceDescription.java
*/
public class ServiceDescription implements ServiceIDListener {
private Object impl;
private Remote proxy;
private File serviceIdFile;
private Configuration config;
private ServiceID serviceID;
public static void main(String args[]) {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
ServiceDescription s =
new ServiceDescription(args);
// keep server running forever to
// - allow time for locator discovery and
// - keep re-registering the lease
Object keepAlive = new Object();
synchronized(keepAlive) {
try {
keepAlive.wait();
} catch(java.lang.InterruptedException e) {
// do nothing
}
}
}
public ServiceDescription(String[] args) {
if (args.length == 0) {
System.err.println("No configuration specified");
System.exit(1);
}
try {
config = ConfigurationProvider.getInstance(args);
} catch(ConfigurationException e) {
System.err.println("Configuration error: " + e.toString() +
" in file " + args[0]);
System.exit(1);
}
startService();
advertiseService();
}
private void startService() {
String codebase = null;
String policy = null;
String classpath = null;
String implClass = null;
String[] serverConfigArgs = null;
try {
codebase = (String) config.getEntry("ServiceDescription",
"codebase",
String.class);
policy = (String) config.getEntry("ServiceDescription",
"policy",
String.class);
classpath = (String) config.getEntry("ServiceDescription",
"classpath",
String.class);
implClass = (String) config.getEntry("ServiceDescription",
"implClass",
String.class);
serverConfigArgs = (String[]) config.getEntry("ServiceDescription",
"serverConfigArgs",
String[].class);
} catch(ConfigurationException e) {
System.err.println("Configuration error: " + e.toString());
System.exit(1);
}
// Create the new service descriptor
ServiceDescriptor desc =
new NonActivatableServiceDescriptor(codebase,
policy,
classpath,
implClass,
serverConfigArgs);
// and create the service and its proxy
Created created = null;
try {
created = (Created) desc.create(config);
} catch(Exception e) {
e.printStackTrace();
System.exit(1);
}
impl = created.impl;
proxy = (Remote) created.proxy;
}
private void advertiseService() {
Entry[] entries = null;
LookupLocator[] unicastLocators = null;
File serviceIdFile = null;
String[] groups = null;
// Now go on to register the proxy with lookup services, using
// e.g. JoinManager.
// This will need additional parameters: entries, unicast
// locators, group and service ID
try {
unicastLocators = (LookupLocator[])
config.getEntry("AdvertDescription",
"unicastLocators",
LookupLocator[].class,
null); // default
entries = (Entry[])
config.getEntry("AdvertDescription",
"entries",
Entry[].class,
null); // default
groups = (String[])
config.getEntry("AdvertDescription",
"groups",
String[].class,
LookupDiscovery.ALL_GROUPS); // default
serviceIdFile = (File)
config.getEntry("AdvertDescription",
"serviceIdFile",
File.class,
null); // default
} catch(Exception e) {
System.err.println(e.toString());
e.printStackTrace();
System.exit(2);
}
JoinManager joinMgr = null;
try {
LookupDiscoveryManager mgr =
new LookupDiscoveryManager(groups,
unicastLocators, // unicast locators
null); // DiscoveryListener
if (serviceID != null) {
joinMgr = new JoinManager(proxy, // service proxy
entries, // attr sets
serviceID, // ServiceID
mgr, // DiscoveryManager
new LeaseRenewalManager());
} else {
joinMgr = new JoinManager(proxy, // service proxy
entries, // attr sets
this, // ServiceIDListener
mgr, // DiscoveryManager
new LeaseRenewalManager());
}
} catch(Exception e) {
e.printStackTrace();
System.exit(1);
}
}
public void tryRetrieveServiceId() {
// Try to load the service ID from file.
// It isn't an error if we can't load it, because
// maybe this is the first time this service has run
DataInputStream din = null;
try {
din = new DataInputStream(new FileInputStream(serviceIdFile));
serviceID = new ServiceID(din);
System.out.println("Found service ID in file " + serviceIdFile);
din.close();
} catch(Exception e) {
// ignore
}
}
public void serviceIDNotify(ServiceID serviceID) {
// called as a ServiceIDListener
// Should save the id to permanent storage
System.out.println("got service ID " + serviceID.toString());
// try to save the service ID in a file
if (serviceIdFile != null) {
DataOutputStream dout = null;
try {
dout = new DataOutputStream(new FileOutputStream(serviceIdFile));
serviceID.writeBytes(dout);
dout.flush();
dout.close();
System.out.println("Service id saved in " + serviceIdFile);
} catch(Exception e) {
// ignore
}
}
}
} // ServiceDescription
This server may be run from a command line such as
java starter.ServiceDescription resources/starter/serviceDesc.config
using a classpath that includes
To summarise what is going on here
The service is started by running a
The classpath for
The
When the service is started by
The classpath used to start the service includes the files in the
jar file
The codebase used by clients to download the service includes
the jar file
The
The standard tools supplied by Sun such as reggie
all use
the ServiceDescription
class. But instead of starting a
service, they start a server.
So in this case an application is needed to start the server, which
in turn will start the service.
Sun supply a class ServiceStarter
in the
com.sun.jini.start
package. This package is not
specified by Jini, and may change or even disappear in later versions
of Jini. It has a public main()
method, which will
take command line arguments. A configuration can be given as a
command line argument and will be searched for an array of
ServiceDescriptor
's. The search is in component
com.sun.jini.start
and the descriptors are labelled
as serviceDescriptors
. The create()
method will be called on each descriptor to create a server.
To use the ServiceStarter
, you need to write a server
and also a configuration file containing a ServiceDescriptor
for that server. The server does not need to have a main()
method, since the server is started by ServiceStarter
, not
the server itself.
For example, we could use the FileClasifierServerConfig
from the chapter on "Configuration". The configuration file for
this server remains unaltered as
import java.io.*;
ServiceIdDemo {
serviceIdFile = new File("serviceId.id");
}
The server itself need not be altered - its main()
method is just not called. If it were being written from scratch,
there would be no need to include this method. Repeating this from
the "Configuration" chapter,
cant open config/FileClassifierServerConfig.java
What is new is a configuration file for ServiceStarter
.
This could be in resources/starter/start-transient-fileclassifier.config
:
import com.sun.jini.start.ServiceDescriptor;
import com.sun.jini.start.NonActivatableServiceDescriptor;
ServiceIdDemo {
private static codebase =
"http://192.168.1.13:8080/file-classifier-dl.jar";
private static policy = "policy.all";
private static classpath = "file-classifier.jar";
private static config = "resources/starter/file_classifier.config";
static serviceDescriptors = new ServiceDescriptor[] {
new NonActivatableServiceDescriptor(
codebase, policy, classpath,
"config.FileClassifierServerConfig",
new String[] { config })
};
}
The server is set running by
java -jar start.jar ServiceStarter resources/start-transient-fileclassifier.config
A typical descriptor for the reggie
service might be
String codebase = "http://192.168.1.13:8080/reggie-dl.jar";
String policy = "/usr/local/reggie/reggie.policy";
String classpath = "/usr/local/jini2_0/lib/reggie.jar";
String config = "/usr/local/reggie/transient-reggie.config";
ServiceDescriptor desc =
new NonActivatableServiceDescriptor(
codebase, policy, classpath,
"com.sun.jini.reggie.TransientRegistrarImpl",
new String[] {config})
We can now see what is going in when a standard Sun service such as
java -Djava.security.policy=reggie/start.policy -jar \
start.jar start-transient-reggie.config
The configuration file contains information required to start the
import com.sun.jini.start.ServiceDescriptor;
import com.sun.jini.start.NonActivatableServiceDescriptor;
import com.sun.jini.config.ConfigUtil;
com.sun.jini.start {
private static codebase =
ConfigUtil.concat(new Object[] {
"http://",
ConfigUtil.getHostName(),
":8080/reggie-dl.jar"
}
);
private static policy = "/usr/local/reggie/reggie.policy";
private static classpath = "/usr/local/jini2_0/lib/reggie.jar";
private static config = "/usr/local/reggie/transient-reggie.config";
static serviceDescriptors = new ServiceDescriptor[] {
new NonActivatableServiceDescriptor(
codebase, policy, classpath,
"com.sun.jini.reggie.TransientRegistrarImpl",
new String[] { config })
};
}
This particular configuration starts a
When the
com.sun.jini.reggie {
initialMemberGroups = new String[] {};
}
A ServiceDescriptor
allows a service to be created
with given parameters. It can be used directly, or by using
the Sun-supplied ServiceStarter
.
Sun use this to startup their own
services such as reggie
. These classes are in the
com.sun.jini.start
package, and as such are not
guaranteed to be stable or even to exist in future versions of
Jini. Alternatively, like JoinManager
, they might
migrate to the core Jini package in future.
We have seen that using ServiceStarter
for a server essentially
involves no changes to the server or its service. What is the point of it, then?
Typically, starting a service involves a combination of factors from a number
of sources
Some values may come from the environment, such as CLASSPATH
Some values may be defined in Java properties, such as security policy
Some values may be given as command line arguments
ServiceStarter
allows all of these to be placed in a
configuration, so that a single mechanism can be used.
If you found this chapter of value, the full book is available from APress or Amazon . There is a review of the book at Java Zone . The current edition of the book does not yet deal with Jini 2.0, but the next edition will.
This work is licensed under a
Creative Commons License, the replacement for the earlier Open Content License.